mobile: Support using an account

This commit is contained in:
Elias Projahn 2020-05-11 19:22:50 +02:00
parent 8765edc257
commit 3d3d5d50a6
3 changed files with 310 additions and 36 deletions

View file

@ -0,0 +1,110 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:musicus_common/musicus_common.dart';
import 'register.dart';
/// A screen for logging in using a Musicus account.
class AccountSettingsScreen extends StatefulWidget {
@override
_AccountSettingsScreenState createState() => _AccountSettingsScreenState();
}
class _AccountSettingsScreenState extends State<AccountSettingsScreen> {
final nameController = TextEditingController();
final passwordController = TextEditingController();
MusicusBackendState backend;
StreamSubscription<MusicusAccountSettings> accountSubscription;
@override
void didChangeDependencies() {
super.didChangeDependencies();
backend = MusicusBackend.of(context);
if (accountSubscription != null) {
accountSubscription.cancel();
}
_settingsChanged(backend.settings.account.value);
accountSubscription = backend.settings.account.listen((settings) {
_settingsChanged(settings);
});
}
void _settingsChanged(MusicusAccountSettings settings) {
nameController.text = settings?.username ?? '';
passwordController.text = settings?.password ?? '';
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Account settings'),
actions: <Widget>[
FlatButton(
onPressed: () async {
await backend.settings.setAccount(MusicusAccountSettings(
username: nameController.text,
password: passwordController.text,
));
Navigator.pop(context);
},
child: Text('LOGIN'),
),
],
),
body: ListView(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(16.0),
child: TextField(
controller: nameController,
decoration: InputDecoration(
labelText: 'User name',
),
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: TextField(
controller: passwordController,
obscureText: true,
decoration: InputDecoration(
labelText: 'Password',
),
),
),
ListTile(
title: Text('Create a new account'),
onTap: () {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => RegisterScreen(),
),
);
},
),
ListTile(
title: Text('Don\'t use an account'),
onTap: () {
backend.settings.clearAccount();
Navigator.pop(context);
},
),
],
),
);
}
@override
void dispose() {
super.dispose();
accountSubscription.cancel();
}
}

View file

@ -0,0 +1,142 @@
import 'package:flutter/material.dart';
import 'package:musicus_client/musicus_client.dart';
import 'package:musicus_common/musicus_common.dart';
/// A screen for creating a new Musicus account.
class RegisterScreen extends StatefulWidget {
@override
_RegisterScreenState createState() => _RegisterScreenState();
}
class _RegisterScreenState extends State<RegisterScreen> {
final nameController = TextEditingController();
final emailController = TextEditingController();
final passwordController = TextEditingController();
final repeatController = TextEditingController();
bool _loading = false;
@override
Widget build(BuildContext context) {
final backend = MusicusBackend.of(context);
return Scaffold(
appBar: AppBar(
title: Text('Create account'),
actions: <Widget>[
Builder(
builder: (context) {
if (!_loading) {
return FlatButton(
onPressed: () async {
if (_verify()) {
setState(() {
_loading = true;
});
final success = await backend.client.register(User(
name: nameController.text,
email: emailController.text,
password: passwordController.text,
));
setState(() {
_loading = false;
});
if (success) {
await backend.settings
.setAccount(MusicusAccountSettings(
username: nameController.text,
email: emailController.text,
password: passwordController.text,
));
Navigator.pop(context);
} else {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text('Failed to create account'),
),
);
}
} else {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text('Invalid inputs'),
),
);
}
},
child: Text('REGISTER'),
);
} else {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Center(
child: SizedBox(
width: 24.0,
height: 24.0,
child: CircularProgressIndicator(
strokeWidth: 2.0,
),
),
),
);
}
},
),
],
),
body: ListView(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(16.0),
child: TextField(
controller: nameController,
decoration: InputDecoration(
labelText: 'User name',
),
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: TextField(
controller: emailController,
decoration: InputDecoration(
labelText: 'E-mail address (optional)',
),
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: TextField(
controller: passwordController,
obscureText: true,
decoration: InputDecoration(
labelText: 'Password',
),
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: TextField(
controller: repeatController,
obscureText: true,
decoration: InputDecoration(
labelText: 'Password (repeat)',
),
),
),
],
),
);
}
/// Check whether all requirements are met.
bool _verify() {
return nameController.text.isNotEmpty &&
passwordController.text.isNotEmpty &&
passwordController.text == repeatController.text;
}
}

View file

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:musicus_common/musicus_common.dart'; import 'package:musicus_common/musicus_common.dart';
import 'account_settings.dart';
import 'server_settings.dart'; import 'server_settings.dart';
class SettingsScreen extends StatelessWidget { class SettingsScreen extends StatelessWidget {
@ -26,15 +27,15 @@ class SettingsScreen extends StatelessWidget {
subtitle: Text(snapshot.data ?? 'Choose folder'), subtitle: Text(snapshot.data ?? 'Choose folder'),
isThreeLine: snapshot.hasData, isThreeLine: snapshot.hasData,
onTap: () async { onTap: () async {
final uri = final uri = await _platform.invokeMethod<String>('openTree');
await _platform.invokeMethod<String>('openTree');
if (uri != null) { if (uri != null) {
settings.setMusicLibraryPath(uri); settings.setMusicLibraryPath(uri);
} }
}, },
); );
}), },
),
StreamBuilder<MusicusServerSettings>( StreamBuilder<MusicusServerSettings>(
stream: settings.server, stream: settings.server,
builder: (context, snapshot) { builder: (context, snapshot) {
@ -42,8 +43,8 @@ class SettingsScreen extends StatelessWidget {
return ListTile( return ListTile(
title: Text('Musicus server'), title: Text('Musicus server'),
subtitle: Text( subtitle:
s != null ? '${s.host}:${s.port}${s.apiPath}' : '...'), Text(s != null ? '${s.host}:${s.port}${s.apiPath}' : '...'),
trailing: const Icon(Icons.chevron_right), trailing: const Icon(Icons.chevron_right),
onTap: () async { onTap: () async {
final MusicusServerSettings result = await Navigator.push( final MusicusServerSettings result = await Navigator.push(
@ -58,7 +59,28 @@ class SettingsScreen extends StatelessWidget {
} }
}, },
); );
}), },
),
StreamBuilder<MusicusAccountSettings>(
stream: settings.account,
builder: (context, snapshot) {
final a = snapshot.data;
return ListTile(
title: Text('Account settings'),
subtitle: Text(a != null ? a.username : 'No account'),
trailing: const Icon(Icons.chevron_right),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AccountSettingsScreen(),
),
);
},
);
},
),
], ],
), ),
); );