refactor: MVC sign up view

This commit is contained in:
Christian Pauly 2021-04-12 17:31:53 +02:00
parent 76199418b2
commit db19b37f72
7 changed files with 121 additions and 80 deletions

View File

@ -1,6 +1,7 @@
import 'package:adaptive_page_layout/adaptive_page_layout.dart'; import 'package:adaptive_page_layout/adaptive_page_layout.dart';
import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/controllers/homeserver_picker_controller.dart'; import 'package:fluffychat/controllers/homeserver_picker_controller.dart';
import 'package:fluffychat/controllers/sign_up_controller.dart';
import 'package:fluffychat/views/widgets/matrix.dart'; import 'package:fluffychat/views/widgets/matrix.dart';
import 'package:fluffychat/views/archive.dart'; import 'package:fluffychat/views/archive.dart';
import 'package:fluffychat/views/chat.dart'; import 'package:fluffychat/views/chat.dart';
@ -24,7 +25,6 @@ import 'package:fluffychat/views/settings_ignore_list.dart';
import 'package:fluffychat/views/settings_multiple_emotes.dart'; import 'package:fluffychat/views/settings_multiple_emotes.dart';
import 'package:fluffychat/views/settings_notifications.dart'; import 'package:fluffychat/views/settings_notifications.dart';
import 'package:fluffychat/views/settings_style.dart'; import 'package:fluffychat/views/settings_style.dart';
import 'package:fluffychat/views/sign_up.dart';
import 'package:fluffychat/views/sign_up_password.dart'; import 'package:fluffychat/views/sign_up_password.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';

View File

@ -0,0 +1,71 @@
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
import 'package:famedlysdk/famedlysdk.dart';
import 'package:file_picker_cross/file_picker_cross.dart';
import 'package:fluffychat/views/sign_up_view.dart';
import 'package:fluffychat/views/widgets/matrix.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
class SignUp extends StatefulWidget {
@override
SignUpController createState() => SignUpController();
}
class SignUpController extends State<SignUp> {
final TextEditingController usernameController = TextEditingController();
String usernameError;
bool loading = false;
MatrixFile avatar;
void setAvatarAction() async {
var file =
await FilePickerCross.importFromStorage(type: FileTypeCross.image);
if (file != null) {
setState(
() => avatar = MatrixFile(
bytes: file.toUint8List(),
name: file.fileName,
),
);
}
}
void resetAvatarAction() => setState(() => avatar = null);
void signUpAction([_]) async {
var matrix = Matrix.of(context);
if (usernameController.text.isEmpty) {
setState(() => usernameError = L10n.of(context).pleaseChooseAUsername);
} else {
setState(() => usernameError = null);
}
if (usernameController.text.isEmpty) {
return;
}
setState(() => loading = true);
final preferredUsername =
usernameController.text.toLowerCase().trim().replaceAll(' ', '-');
try {
await matrix.client.usernameAvailable(preferredUsername);
} on MatrixException catch (exception) {
setState(() => usernameError = exception.errorMessage);
return setState(() => loading = false);
} catch (exception) {
setState(() => usernameError = exception.toString());
return setState(() => loading = false);
}
setState(() => loading = false);
await AdaptivePageLayout.of(context).pushNamed(
'/signup/password/${Uri.encodeComponent(preferredUsername)}/${Uri.encodeComponent(usernameController.text)}',
arguments: avatar,
);
}
@override
Widget build(BuildContext context) => SignUpView(this);
}

View File

@ -51,11 +51,13 @@ void main() async {
} }
class FluffyChatApp extends StatelessWidget { class FluffyChatApp extends StatelessWidget {
final Widget test; final Widget testWidget;
final Client testClient;
static final GlobalKey<AdaptivePageLayoutState> _apl = static final GlobalKey<AdaptivePageLayoutState> _apl =
GlobalKey<AdaptivePageLayoutState>(); GlobalKey<AdaptivePageLayoutState>();
const FluffyChatApp({Key key, this.test}) : super(key: key); const FluffyChatApp({Key key, this.testWidget, this.testClient})
: super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AdaptiveTheme( return AdaptiveTheme(
@ -75,13 +77,14 @@ class FluffyChatApp extends StatelessWidget {
builder: (context) => Matrix( builder: (context) => Matrix(
context: context, context: context,
apl: _apl, apl: _apl,
testClient: testClient,
child: Builder( child: Builder(
builder: (context) => AdaptivePageLayout( builder: (context) => AdaptivePageLayout(
key: _apl, key: _apl,
safeAreaOnColumnView: false, safeAreaOnColumnView: false,
onGenerateRoute: test == null onGenerateRoute: testWidget == null
? FluffyRoutes(context).onGenerateRoute ? FluffyRoutes(context).onGenerateRoute
: (_) => ViewData(mainView: (_) => test), : (_) => ViewData(mainView: (_) => testWidget),
dividerColor: Theme.of(context).dividerColor, dividerColor: Theme.of(context).dividerColor,
columnWidth: FluffyThemes.columnWidth, columnWidth: FluffyThemes.columnWidth,
dividerWidth: 1.0, dividerWidth: 1.0,

View File

@ -1,6 +1,5 @@
import 'package:adaptive_page_layout/adaptive_page_layout.dart'; import 'package:adaptive_page_layout/adaptive_page_layout.dart';
import 'package:famedlysdk/famedlysdk.dart'; import 'package:fluffychat/controllers/sign_up_controller.dart';
import 'package:file_picker_cross/file_picker_cross.dart';
import 'package:fluffychat/views/widgets/fluffy_banner.dart'; import 'package:fluffychat/views/widgets/fluffy_banner.dart';
import 'package:fluffychat/views/widgets/matrix.dart'; import 'package:fluffychat/views/widgets/matrix.dart';
@ -9,61 +8,13 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
class SignUp extends StatefulWidget { class SignUpView extends StatelessWidget {
@override final SignUpController controller;
_SignUpState createState() => _SignUpState();
}
class _SignUpState extends State<SignUp> { const SignUpView(
final TextEditingController usernameController = TextEditingController(); this.controller, {
String usernameError; Key key,
bool loading = false; }) : super(key: key);
MatrixFile avatar;
void setAvatarAction() async {
var file =
await FilePickerCross.importFromStorage(type: FileTypeCross.image);
if (file != null) {
setState(
() => avatar = MatrixFile(
bytes: file.toUint8List(),
name: file.fileName,
),
);
}
}
void signUpAction(BuildContext context) async {
var matrix = Matrix.of(context);
if (usernameController.text.isEmpty) {
setState(() => usernameError = L10n.of(context).pleaseChooseAUsername);
} else {
setState(() => usernameError = null);
}
if (usernameController.text.isEmpty) {
return;
}
setState(() => loading = true);
final preferredUsername =
usernameController.text.toLowerCase().trim().replaceAll(' ', '-');
try {
await matrix.client.usernameAvailable(preferredUsername);
} on MatrixException catch (exception) {
setState(() => usernameError = exception.errorMessage);
return setState(() => loading = false);
} catch (exception) {
setState(() => usernameError = exception.toString());
return setState(() => loading = false);
}
setState(() => loading = false);
await AdaptivePageLayout.of(context).pushNamed(
'/signup/password/${Uri.encodeComponent(preferredUsername)}/${Uri.encodeComponent(usernameController.text)}',
arguments: avatar,
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -71,7 +22,7 @@ class _SignUpState extends State<SignUp> {
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
elevation: 0, elevation: 0,
leading: loading ? Container() : BackButton(), leading: controller.loading ? Container() : BackButton(),
title: Text( title: Text(
Matrix.of(context) Matrix.of(context)
.client .client
@ -89,15 +40,16 @@ class _SignUpState extends State<SignUp> {
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0), padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: TextField( child: TextField(
readOnly: loading, readOnly: controller.loading,
autocorrect: false, autocorrect: false,
controller: usernameController, controller: controller.usernameController,
onSubmitted: (s) => signUpAction(context), onSubmitted: controller.signUpAction,
autofillHints: loading ? null : [AutofillHints.newUsername], autofillHints:
controller.loading ? null : [AutofillHints.newUsername],
decoration: InputDecoration( decoration: InputDecoration(
prefixIcon: Icon(Icons.account_circle_outlined), prefixIcon: Icon(Icons.account_circle_outlined),
hintText: L10n.of(context).username, hintText: L10n.of(context).username,
errorText: usernameError, errorText: controller.usernameError,
labelText: L10n.of(context).chooseAUsername, labelText: L10n.of(context).chooseAUsername,
), ),
), ),
@ -105,30 +57,31 @@ class _SignUpState extends State<SignUp> {
SizedBox(height: 8), SizedBox(height: 8),
ListTile( ListTile(
leading: CircleAvatar( leading: CircleAvatar(
backgroundImage: backgroundImage: controller.avatar == null
avatar == null ? null : MemoryImage(avatar.bytes), ? null
backgroundColor: avatar == null : MemoryImage(controller.avatar.bytes),
backgroundColor: controller.avatar == null
? Theme.of(context).brightness == Brightness.dark ? Theme.of(context).brightness == Brightness.dark
? Color(0xff121212) ? Color(0xff121212)
: Colors.white : Colors.white
: Theme.of(context).secondaryHeaderColor, : Theme.of(context).secondaryHeaderColor,
child: avatar == null child: controller.avatar == null
? Icon(Icons.camera_alt_outlined, ? Icon(Icons.camera_alt_outlined,
color: Theme.of(context).primaryColor) color: Theme.of(context).primaryColor)
: null, : null,
), ),
trailing: avatar == null trailing: controller.avatar == null
? null ? null
: Icon( : Icon(
Icons.close, Icons.close,
color: Colors.red, color: Colors.red,
), ),
title: Text(avatar == null title: Text(controller.avatar == null
? L10n.of(context).setAProfilePicture ? L10n.of(context).setAProfilePicture
: L10n.of(context).discardPicture), : L10n.of(context).discardPicture),
onTap: avatar == null onTap: controller.avatar == null
? setAvatarAction ? controller.setAvatarAction
: () => setState(() => avatar = null), : controller.resetAvatarAction,
), ),
SizedBox(height: 16), SizedBox(height: 16),
Hero( Hero(
@ -136,8 +89,8 @@ class _SignUpState extends State<SignUp> {
child: Padding( child: Padding(
padding: EdgeInsets.symmetric(horizontal: 12), padding: EdgeInsets.symmetric(horizontal: 12),
child: ElevatedButton( child: ElevatedButton(
onPressed: loading ? null : () => signUpAction(context), onPressed: controller.loading ? null : controller.signUpAction,
child: loading child: controller.loading
? LinearProgressIndicator() ? LinearProgressIndicator()
: Text( : Text(
L10n.of(context).signUp.toUpperCase(), L10n.of(context).signUp.toUpperCase(),

View File

@ -43,10 +43,13 @@ class Matrix extends StatefulWidget {
final BuildContext context; final BuildContext context;
final Client testClient;
Matrix({ Matrix({
this.child, this.child,
@required this.apl, @required this.apl,
@required this.context, @required this.context,
this.testClient,
Key key, Key key,
}) : super(key: key); }) : super(key: key);
@ -66,6 +69,8 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
BackgroundPush _backgroundPush; BackgroundPush _backgroundPush;
bool get testMode => widget.testClient != null;
Map<String, dynamic> get shareContent => _shareContent; Map<String, dynamic> get shareContent => _shareContent;
set shareContent(Map<String, dynamic> content) { set shareContent(Map<String, dynamic> content) {
_shareContent = content; _shareContent = content;
@ -256,7 +261,7 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
}); });
}); });
} }
client = FluffyClient(); client = widget.testClient ?? FluffyClient();
LoadingDialog.defaultTitle = L10n.of(context).loadingPleaseWait; LoadingDialog.defaultTitle = L10n.of(context).loadingPleaseWait;
LoadingDialog.defaultBackLabel = L10n.of(context).close; LoadingDialog.defaultBackLabel = L10n.of(context).close;
LoadingDialog.defaultOnError = (Object e) => e.toLocalizedString(context); LoadingDialog.defaultOnError = (Object e) => e.toLocalizedString(context);

View File

@ -5,7 +5,7 @@ import 'package:flutter_test/flutter_test.dart';
void main() { void main() {
testWidgets('Test if the widget can be created', (WidgetTester tester) async { testWidgets('Test if the widget can be created', (WidgetTester tester) async {
await tester.pumpWidget(FluffyChatApp(test: HomeserverPicker())); await tester.pumpWidget(FluffyChatApp(testWidget: HomeserverPicker()));
await tester.tap(find.byType(TextField)); await tester.tap(find.byType(TextField));
await tester.tap(find.byType(ElevatedButton)); await tester.tap(find.byType(ElevatedButton));

9
test/sign_up_test.dart Normal file
View File

@ -0,0 +1,9 @@
import 'package:fluffychat/controllers/sign_up_controller.dart';
import 'package:fluffychat/main.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Test if the widget can be created', (WidgetTester tester) async {
await tester.pumpWidget(FluffyChatApp(testWidget: SignUp()));
});
}