Add/Edit Profile Screen with Themeing and Changing Profile
This commit is contained in:
parent
138d34af2a
commit
f9d0c44c96
|
@ -40,4 +40,5 @@ app.*.symbols
|
|||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
libCwtch.so
|
||||
libCwtch.so
|
||||
android/cwtch/cwtch.aar
|
|
@ -127,6 +127,11 @@ class MainActivity: FlutterActivity() {
|
|||
val end = (call.argument("end") as? Long) ?: 0;
|
||||
result.success(Cwtch.getMessages(profile, handle, start, end))
|
||||
}
|
||||
"SendProfileEvent" -> {
|
||||
val onion = (call.argument("onion") as? String) ?: "";
|
||||
val jsonEvent = (call.argument("jsonEvent") as? String) ?: "";
|
||||
Cwtch.sendProfileEvent(onion, jsonEvent);
|
||||
}
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -4,6 +4,7 @@ abstract class Cwtch {
|
|||
void SelectProfile(String onion);
|
||||
void CreateProfile(String nick, String pass);
|
||||
void LoadProfiles(String pass);
|
||||
void SendProfileEvent(String onion, String jsonEvent);
|
||||
|
||||
Future<String> ACNEvents();
|
||||
Future<String> ContactEvents();
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:convert';
|
|||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_app/cwtch/cwtchNotifier.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
|
@ -26,6 +27,9 @@ typedef NextEventFn = void Function();
|
|||
typedef string_to_void_function = Void Function(Pointer<Utf8> str, Int32 length);
|
||||
typedef StringFn = void Function(Pointer<Utf8> dir, int);
|
||||
|
||||
typedef string_string_to_void_function = Void Function(Pointer<Utf8> str, Int32 length, Pointer<Utf8> str2, Int32 length2);
|
||||
typedef StringStringFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int);
|
||||
|
||||
typedef get_json_blob_void_function = Pointer<Utf8> Function();
|
||||
typedef GetJsonBlobVoidFn = Pointer<Utf8> Function();
|
||||
|
||||
|
@ -206,4 +210,12 @@ class CwtchFfi implements Cwtch {
|
|||
String jsonMessages = Utf8.fromUtf8(jsonMessagesBytes);
|
||||
return jsonMessages;
|
||||
}
|
||||
|
||||
@override
|
||||
void SendProfileEvent(String onion, String json) {
|
||||
var sendAppBusEvent = library.lookup<NativeFunction<string_string_to_void_function>>("c_SendProfileEvent");
|
||||
// ignore: non_constant_identifier_names
|
||||
final SendAppBusEvent = sendAppBusEvent.asFunction<StringStringFn>();
|
||||
SendAppBusEvent(Utf8.toUtf8(onion), onion.length, Utf8.toUtf8(json), json.length);
|
||||
}
|
||||
}
|
|
@ -100,4 +100,10 @@ class CwtchGomobile implements Cwtch {
|
|||
return cwtchPlatform.invokeMethod("GetMessage", {"profile" : profile, "contact": handle, "start": start, "end": end});
|
||||
}
|
||||
|
||||
@override
|
||||
void SendProfileEvent(String onion, String jsonEvent) {
|
||||
cwtchPlatform.invokeMethod("SendProfileEvent", {"onion" : onion, "jsonEvent": jsonEvent});
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,4 +1,8 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_app/widgets/buttontextfield.dart';
|
||||
import 'package:flutter_app/widgets/cwtchlabel.dart';
|
||||
import 'package:flutter_app/widgets/passwordfield.dart';
|
||||
import 'package:flutter_app/widgets/textfield.dart';
|
||||
|
@ -9,8 +13,10 @@ import '../main.dart';
|
|||
import '../opaque.dart';
|
||||
|
||||
class AddEditProfileView extends StatefulWidget {
|
||||
const AddEditProfileView({Key key, this.profileOnion}) : super(key: key);
|
||||
const AddEditProfileView({Key key, this.profileOnion, this.profileDisplayName, this.profileImage="profiles/001-centaur.png"}) : super(key: key);
|
||||
final String profileOnion;
|
||||
final String profileDisplayName;
|
||||
final String profileImage;
|
||||
|
||||
@override
|
||||
_AddEditProfileViewState createState() => _AddEditProfileViewState();
|
||||
|
@ -19,7 +25,7 @@ class AddEditProfileView extends StatefulWidget {
|
|||
class _AddEditProfileViewState extends State<AddEditProfileView> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
|
||||
final ctrlrNick = TextEditingController();
|
||||
final ctrlrNick = TextEditingController(text: "");
|
||||
final ctrlrPass = TextEditingController(text: "");
|
||||
final ctrlrPass2 = TextEditingController(text: "");
|
||||
TextEditingController ctrlrOnion;
|
||||
|
@ -30,6 +36,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
super.initState();
|
||||
usePassword = true;
|
||||
ctrlrOnion = TextEditingController(text: widget.profileOnion);
|
||||
ctrlrNick.text = widget.profileDisplayName;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -58,7 +65,29 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
child: Container(
|
||||
margin: EdgeInsets.all(30),
|
||||
padding: EdgeInsets.all(20),
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
||||
child: SingleChildScrollView (child:Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
||||
Visibility(
|
||||
visible: widget.profileOnion.isNotEmpty,
|
||||
child: Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
|
||||
SizedBox(
|
||||
width: 120,
|
||||
height: 120,
|
||||
child: ClipOval(
|
||||
child: SizedBox(
|
||||
width: 120,
|
||||
height: 120,
|
||||
child: Container(
|
||||
color: Colors.white,
|
||||
width: 120,
|
||||
height: 120,
|
||||
child: Image(
|
||||
image: AssetImage("assets/" + widget.profileImage),
|
||||
width: 100,
|
||||
height: 100,
|
||||
))),
|
||||
),
|
||||
)
|
||||
])),
|
||||
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
CwtchLabel(label: AppLocalizations.of(context).displayNameLabel),
|
||||
SizedBox(
|
||||
|
@ -75,31 +104,52 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
},
|
||||
),
|
||||
]),
|
||||
Visibility(visible: widget.profileOnion != "", child: Text(AppLocalizations.of(context).addressLabel)),
|
||||
Visibility(visible: widget.profileOnion != "", child: TextField(controller: ctrlrOnion)),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
|
||||
Radio(
|
||||
value: false,
|
||||
groupValue: usePassword,
|
||||
onChanged: _handleSwitchPassword,
|
||||
),
|
||||
Text(AppLocalizations.of(context).radioNoPassword, style: TextStyle(color: theme.current().mainTextColor()),),
|
||||
Radio(
|
||||
value: true,
|
||||
groupValue: usePassword,
|
||||
onChanged: _handleSwitchPassword,
|
||||
),
|
||||
Text(AppLocalizations.of(context).radioUsePassword, style: TextStyle(color: theme.current().mainTextColor()),),
|
||||
]),
|
||||
Visibility(
|
||||
visible: widget.profileOnion != "",
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchLabel(label: AppLocalizations.of(context).addressLabel),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchButtonTextField(
|
||||
controller: ctrlrOnion,
|
||||
onPressed: _copyOnion,
|
||||
icon: Icon(Icons.copy),
|
||||
tooltip: AppLocalizations.of(context).copyBtn,
|
||||
)
|
||||
])),
|
||||
// We only allow setting password types on profile creation
|
||||
Visibility(
|
||||
visible: widget.profileOnion == "",
|
||||
child: Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
|
||||
Radio(
|
||||
value: false,
|
||||
groupValue: usePassword,
|
||||
onChanged: _handleSwitchPassword,
|
||||
),
|
||||
Text(
|
||||
AppLocalizations.of(context).radioNoPassword,
|
||||
style: TextStyle(color: theme.current().mainTextColor()),
|
||||
),
|
||||
Radio(
|
||||
value: true,
|
||||
groupValue: usePassword,
|
||||
onChanged: _handleSwitchPassword,
|
||||
),
|
||||
Text(
|
||||
AppLocalizations.of(context).radioUsePassword,
|
||||
style: TextStyle(color: theme.current().mainTextColor()),
|
||||
),
|
||||
])),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Visibility(
|
||||
visible: usePassword,
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start ,children: <Widget>[
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
|
||||
CwtchLabel(label: AppLocalizations.of(context).password1Label),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
|
@ -107,7 +157,8 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
CwtchPasswordField(
|
||||
controller: ctrlrPass,
|
||||
validator: (value) {
|
||||
if (value.isEmpty && usePassword) {
|
||||
// Password field can be empty when just updating the profile, not on creation
|
||||
if (widget.profileOnion == "" && value.isEmpty && usePassword) {
|
||||
return AppLocalizations.of(context).passwordErrorEmpty;
|
||||
}
|
||||
if (value != ctrlrPass2.value.text) {
|
||||
|
@ -126,7 +177,8 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
CwtchPasswordField(
|
||||
controller: ctrlrPass2,
|
||||
validator: (value) {
|
||||
if (value.isEmpty && usePassword) {
|
||||
// Password field can be empty when just updating the profile, not on creation
|
||||
if (widget.profileOnion == "" && value.isEmpty && usePassword) {
|
||||
return AppLocalizations.of(context).passwordErrorEmpty;
|
||||
}
|
||||
if (value != ctrlrPass.value.text) {
|
||||
|
@ -144,27 +196,43 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
style: ElevatedButton.styleFrom(primary: theme.current().defaultButtonColor()),
|
||||
child: Text(widget.profileOnion == "" ? AppLocalizations.of(context).addNewProfileBtn : AppLocalizations.of(context).saveProfileBtn),
|
||||
)
|
||||
])));
|
||||
]))));
|
||||
});
|
||||
}
|
||||
|
||||
void _copyOnion() {
|
||||
Clipboard.setData(new ClipboardData(text: widget.profileOnion));
|
||||
// TODO Toast
|
||||
}
|
||||
|
||||
void _createPressed() {
|
||||
// This will run all the validations in the form including
|
||||
// checking that display name is not empty, and an actual check that the passwords
|
||||
// match (and are provided if the user has requested an encrypted profile).
|
||||
if (_formKey.currentState.validate()) {
|
||||
if (usePassword == true) {
|
||||
Provider
|
||||
.of<FlwtchState>(context, listen: false)
|
||||
.cwtch
|
||||
.CreateProfile(ctrlrNick.value.text, ctrlrPass.value.text);
|
||||
Navigator.of(context).pop();
|
||||
if (widget.profileOnion.isEmpty) {
|
||||
if (usePassword == true) {
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.CreateProfile(ctrlrNick.value.text, ctrlrPass.value.text);
|
||||
Navigator.of(context).pop();
|
||||
} else {
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.CreateProfile(ctrlrNick.value.text, "be gay do crime");
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
} else {
|
||||
Provider
|
||||
.of<FlwtchState>(context, listen: false)
|
||||
.cwtch
|
||||
.CreateProfile(ctrlrNick.value.text, "be gay do crime");
|
||||
Navigator.of(context).pop();
|
||||
// Profile Editing
|
||||
if (ctrlrPass.value.text.isEmpty) {
|
||||
// Don't update password, only update name
|
||||
final event = {
|
||||
"EventType": "SetAttribute",
|
||||
"Data": {"Key": "public.name", "Data": ctrlrNick.value.text}
|
||||
};
|
||||
final json = jsonEncode(event);
|
||||
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SendProfileEvent(widget.profileOnion, json);
|
||||
Navigator.of(context).pop();
|
||||
} else {
|
||||
// TODO Update Password
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
|
|||
}
|
||||
|
||||
void _testChangingContactInfo() {
|
||||
Provider.of<ProfileListState>(context, listen:false).onions.first.nickname = "yay!";
|
||||
Provider.of<ProfileListState>(context, listen:false).notifyListeners();
|
||||
}
|
||||
|
||||
void _pushGlobalSettings() {
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../opaque.dart';
|
||||
|
||||
// Provides a styled Text Field for use in Form Widgets.
|
||||
// Callers must provide a text controller, label helper text and a validator.
|
||||
class CwtchButtonTextField extends StatefulWidget {
|
||||
CwtchButtonTextField({this.controller, this.onPressed, this.icon, this.tooltip});
|
||||
final TextEditingController controller;
|
||||
final Function onPressed;
|
||||
final Icon icon;
|
||||
final String tooltip;
|
||||
|
||||
@override
|
||||
_CwtchButtonTextFieldState createState() => _CwtchButtonTextFieldState();
|
||||
}
|
||||
|
||||
class _CwtchButtonTextFieldState extends State<CwtchButtonTextField> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<OpaqueTheme>(builder: (context, theme, child) {
|
||||
return TextField(
|
||||
controller: widget.controller,
|
||||
readOnly: true,
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: IconButton(
|
||||
onPressed: widget.onPressed,
|
||||
icon: widget.icon,
|
||||
tooltip: widget.tooltip,
|
||||
enableFeedback: true,
|
||||
color: theme.current().mainTextColor(),
|
||||
highlightColor: theme.current().defaultButtonColor(),
|
||||
focusColor: theme.current().defaultButtonActiveColor(),
|
||||
splashColor: theme.current().defaultButtonActiveColor(),
|
||||
),
|
||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||
filled: true,
|
||||
focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(15.0), borderSide: BorderSide(color: theme.current().textfieldBorderColor(), width: 3.0)),
|
||||
focusedErrorBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(15.0), borderSide: BorderSide(color: theme.current().textfieldErrorColor(), width: 3.0)),
|
||||
errorBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(15.0), borderSide: BorderSide(color: theme.current().textfieldErrorColor(), width: 3.0)),
|
||||
errorStyle: TextStyle (color: theme.current().textfieldErrorColor(), fontWeight: FontWeight.bold,),
|
||||
fillColor: theme.current().textfieldBackgroundColor(),
|
||||
contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
|
||||
enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(15.0), borderSide: BorderSide(color: theme.current().textfieldBorderColor(), width: 3.0))),
|
||||
|
||||
style: TextStyle(color: theme.current().mainTextColor(), backgroundColor: theme.current().textfieldBackgroundColor()),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -27,7 +27,7 @@ class _ProfileRowState extends State<ProfileRow> {
|
|||
) ,
|
||||
trailing: IconButton(
|
||||
icon: Icon(Icons.create, color: Provider.of<OpaqueTheme>(context).current().mainTextColor()),
|
||||
onPressed: () { _pushAddEditProfile(onion: profile.onion); },
|
||||
onPressed: () { _pushAddEditProfile(onion: profile.onion, displayName: profile.nickname, profileImage: profile.imagePath); },
|
||||
),//(nb: Icons.create is a pencil and we use it for "edit", not create)
|
||||
title: Text(
|
||||
profile.nickname,
|
||||
|
@ -68,13 +68,14 @@ class _ProfileRowState extends State<ProfileRow> {
|
|||
);
|
||||
}
|
||||
|
||||
void _pushAddEditProfile({onion: ""}) {
|
||||
|
||||
void _pushAddEditProfile({onion: "", displayName: "", profileImage: ""}) {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (BuildContext context) {
|
||||
return Provider (
|
||||
create: (_) => Provider.of<FlwtchState>(context, listen: false),
|
||||
child: AddEditProfileView(profileOnion: onion),
|
||||
child: AddEditProfileView(profileOnion: onion, profileDisplayName: displayName, profileImage: profileImage,),
|
||||
);
|
||||
},
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue