Style and Functional Add Profile Form
This commit is contained in:
parent
7592d8b56e
commit
ec951c0a89
|
@ -39,3 +39,5 @@ app.*.symbols
|
|||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
libCwtch.so
|
16
SPEC.md
16
SPEC.md
|
@ -6,17 +6,29 @@ Cwtch UI implementation.
|
|||
This functionality is implemented in libCwtch and so this work captures just the UI work
|
||||
required - any new Cwtch work is beyond the scope of this initial spec.
|
||||
|
||||
# Functional Requirements
|
||||
- [ ] Kill all processes / isolates on exit
|
||||
- [ ] Android Service?
|
||||
|
||||
# Splash Screen
|
||||
- [ ] Android
|
||||
- [ ] Investigate Lottie [example implementation blog](https://medium.com/swlh/native-splash-screen-in-flutter-using-lottie-121ce2b9b0a4)
|
||||
- [ ] Desktop
|
||||
|
||||
# Custom Styled Widgets
|
||||
- [/] Label Widget
|
||||
- [X] Initial
|
||||
- [ ] With Accessibility / Zoom Integration
|
||||
- [X] Text Field Widget
|
||||
- [X] Password Widget
|
||||
- [ ] Text Button Widget (for Copy)
|
||||
|
||||
## Home Pane (formally Profile Pane)
|
||||
|
||||
- [X] Unlock a profile with a password
|
||||
- [ ] Create a new Profile
|
||||
- [X] Create a new Profile
|
||||
- [X] With a password
|
||||
- [ ] Without a password
|
||||
- [X] Without a password
|
||||
- [X] Display all unlocked profiles
|
||||
- [X] Profile Picture
|
||||
- [X] default images
|
||||
|
|
|
@ -72,7 +72,7 @@ class FlwtchState extends State<Flwtch> {
|
|||
builder: (context, widget) {
|
||||
return Consumer<OpaqueTheme>(
|
||||
builder: (context, opaque, child) => MaterialApp(
|
||||
locale: Locale("es",''),
|
||||
locale: Locale("en",''),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
title: 'Cwtch',
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_app/widgets/cwtchlabel.dart';
|
||||
import 'package:flutter_app/widgets/passwordfield.dart';
|
||||
import 'package:flutter_app/widgets/textfield.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
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}) : super(key: key);
|
||||
final String profileOnion;
|
||||
|
||||
@override
|
||||
|
@ -13,14 +17,19 @@ class AddEditProfileView extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _AddEditProfileViewState extends State<AddEditProfileView> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
|
||||
final ctrlrNick = TextEditingController();
|
||||
final ctrlrPass = TextEditingController(text:"be gay do crime");
|
||||
final ctrlrPass = TextEditingController(text: "");
|
||||
final ctrlrPass2 = TextEditingController(text: "");
|
||||
TextEditingController ctrlrOnion;
|
||||
bool usePassword;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
ctrlrOnion = TextEditingController(text:widget.profileOnion);
|
||||
usePassword = true;
|
||||
ctrlrOnion = TextEditingController(text: widget.profileOnion);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -33,28 +42,130 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
);
|
||||
}
|
||||
|
||||
void _handleSwitchPassword(bool value) {
|
||||
setState(() {
|
||||
usePassword = value;
|
||||
});
|
||||
}
|
||||
|
||||
// A few implementation notes
|
||||
// We use Visibility to hide optional structures when they are not requested.
|
||||
// We used SizedBox for inter-widget height padding in columns, otherwise elements can render a little too close together.
|
||||
Widget _buildForm() {
|
||||
return Center(child:Wrap(
|
||||
direction: Axis.vertical,
|
||||
spacing: 20.0,
|
||||
runSpacing: 20.0,
|
||||
children: <Widget>[
|
||||
Text(AppLocalizations.of(context).displayNameLabel),
|
||||
SizedBox(width:200, height: 60, child: TextField(controller: ctrlrNick,)),
|
||||
widget.profileOnion == "" ? SizedBox(width:1,height:1,) : Text(AppLocalizations.of(context).addressLabel),
|
||||
widget.profileOnion == "" ? SizedBox(width:1,height:1,) : SizedBox(width:200,height:60,child:TextField(controller: ctrlrOnion)),
|
||||
Text(AppLocalizations.of(context).radioUsePassword),
|
||||
Text(AppLocalizations.of(context).radioNoPassword),
|
||||
Text(AppLocalizations.of(context).password1Label),
|
||||
SizedBox(width:200, height: 60, child: TextField(controller: ctrlrPass,)),
|
||||
Text(AppLocalizations.of(context).password2Label),
|
||||
ElevatedButton(onPressed: _createPressed, child: Text(widget.profileOnion == "" ? AppLocalizations.of(context).addNewProfileBtn : AppLocalizations.of(context).saveProfileBtn),),
|
||||
],
|
||||
));
|
||||
return Consumer<OpaqueTheme>(builder: (context, theme, child) {
|
||||
return Form(
|
||||
key: _formKey,
|
||||
child: Container(
|
||||
margin: EdgeInsets.all(30),
|
||||
padding: EdgeInsets.all(20),
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
||||
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
CwtchLabel(label: AppLocalizations.of(context).displayNameLabel),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchTextField(
|
||||
controller: ctrlrNick,
|
||||
labelText: AppLocalizations.of(context).yourDisplayName,
|
||||
validator: (value) {
|
||||
if (value.isEmpty) {
|
||||
return "Please enter a display name";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
]),
|
||||
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()),),
|
||||
]),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Visibility(
|
||||
visible: usePassword,
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start ,children: <Widget>[
|
||||
CwtchLabel(label: AppLocalizations.of(context).password1Label),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchPasswordField(
|
||||
controller: ctrlrPass,
|
||||
validator: (value) {
|
||||
if (value.isEmpty && usePassword) {
|
||||
return AppLocalizations.of(context).passwordErrorEmpty;
|
||||
}
|
||||
if (value != ctrlrPass2.value.text) {
|
||||
return AppLocalizations.of(context).passwordErrorMatch;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchLabel(label: AppLocalizations.of(context).password2Label),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchPasswordField(
|
||||
controller: ctrlrPass2,
|
||||
validator: (value) {
|
||||
if (value.isEmpty && usePassword) {
|
||||
return AppLocalizations.of(context).passwordErrorEmpty;
|
||||
}
|
||||
if (value != ctrlrPass.value.text) {
|
||||
return AppLocalizations.of(context).passwordErrorMatch;
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
]),
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: _createPressed,
|
||||
style: ElevatedButton.styleFrom(primary: theme.current().defaultButtonColor()),
|
||||
child: Text(widget.profileOnion == "" ? AppLocalizations.of(context).addNewProfileBtn : AppLocalizations.of(context).saveProfileBtn),
|
||||
)
|
||||
])));
|
||||
});
|
||||
}
|
||||
|
||||
void _createPressed() {
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.CreateProfile(ctrlrNick.value.text, ctrlrPass.value.text);
|
||||
Navigator.of(context).pop();
|
||||
// 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();
|
||||
} else {
|
||||
Provider
|
||||
.of<FlwtchState>(context, listen: false)
|
||||
.cwtch
|
||||
.CreateProfile(ctrlrNick.value.text, "be gay do crime");
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import '../opaque.dart';
|
||||
|
||||
// Provides a styled Label
|
||||
// Callers must provide a label text
|
||||
// TODO: Integrate this with a settings "zoom" / accessibility setting
|
||||
class CwtchLabel extends StatefulWidget {
|
||||
CwtchLabel({ this.label});
|
||||
final String label;
|
||||
|
||||
@override
|
||||
_CwtchLabelState createState() => _CwtchLabelState();
|
||||
}
|
||||
|
||||
class _CwtchLabelState extends State<CwtchLabel> {
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<OpaqueTheme> (
|
||||
builder: (context, theme, child) {
|
||||
return Text(
|
||||
widget.label,
|
||||
style: TextStyle(fontSize: 20, color: theme.current().mainTextColor()),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import '../opaque.dart';
|
||||
|
||||
// Provides a styled Password Input Field for use in Form Widgets.
|
||||
// Callers must provide a text controller, label helper text and a validator.
|
||||
class CwtchPasswordField extends StatefulWidget {
|
||||
CwtchPasswordField({this.controller, this.validator});
|
||||
final TextEditingController controller;
|
||||
final FormFieldValidator validator;
|
||||
|
||||
@override
|
||||
_CwtchTextFieldState createState() => _CwtchTextFieldState();
|
||||
}
|
||||
|
||||
class _CwtchTextFieldState extends State<CwtchPasswordField> {
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<OpaqueTheme> (
|
||||
builder: (context, theme, child) {
|
||||
return TextFormField(
|
||||
controller: widget.controller,
|
||||
validator: widget.validator,
|
||||
obscureText: true, enableSuggestions: false, autocorrect: false,
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: theme.current().textfieldBackgroundColor(),
|
||||
border: OutlineInputBorder(borderSide: BorderSide(color: theme.current().textfieldBorderColor()), borderRadius: BorderRadius.circular(15))),
|
||||
style: TextStyle(color: theme.current().mainTextColor(), backgroundColor: theme.current().textfieldBackgroundColor()),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
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 CwtchTextField extends StatefulWidget {
|
||||
CwtchTextField({this.controller, this.labelText, this.validator});
|
||||
final TextEditingController controller;
|
||||
final String labelText;
|
||||
final FormFieldValidator validator;
|
||||
|
||||
@override
|
||||
_CwtchTextFieldState createState() => _CwtchTextFieldState();
|
||||
}
|
||||
|
||||
class _CwtchTextFieldState extends State<CwtchTextField> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<OpaqueTheme> (
|
||||
builder: (context, theme, child) {
|
||||
return TextFormField(
|
||||
controller: widget.controller,
|
||||
validator: widget.validator,
|
||||
decoration: InputDecoration(
|
||||
labelText: widget.labelText,
|
||||
labelStyle: TextStyle(color: theme.current().mainTextColor(), backgroundColor: theme.current().textfieldBackgroundColor()),
|
||||
filled: true,
|
||||
fillColor: theme.current().textfieldBackgroundColor(),
|
||||
border: OutlineInputBorder(borderSide: BorderSide(color: theme.current().textfieldBorderColor()), borderRadius: BorderRadius.circular(15))),
|
||||
style: TextStyle(color: theme.current().mainTextColor(), backgroundColor: theme.current().textfieldBackgroundColor()),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue