BLoC pattern in settings page

This commit is contained in:
Daniel Borges 2019-08-14 11:09:05 -03:00
parent d01d9a8965
commit 441d407005
4 changed files with 266 additions and 113 deletions

View File

@ -21,8 +21,10 @@
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
import 'src/bloc/settings_bloc.dart';
import 'src/openlp_mobile_remote_app.dart'; import 'src/openlp_mobile_remote_app.dart';
import 'src/configurations/app_theme.dart'; import 'src/configurations/app_theme.dart';
import 'src/configurations/app_localizations.dart'; import 'src/configurations/app_localizations.dart';
@ -60,7 +62,10 @@ void main() => runApp(
}, },
routes: <String, WidgetBuilder>{ routes: <String, WidgetBuilder>{
'/': (context) => OpenLPMobileRemoteApp(), '/': (context) => OpenLPMobileRemoteApp(),
'/settings': (context) => Settings(), '/settings': (context) => BlocProvider(
builder: (context) => SettingsBloc(),
child: Settings(),
),
}, },
), ),
); );

View File

@ -0,0 +1,139 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';
class SettingsState {
String serverIp;
int serverPort;
bool useHttps;
bool needsAuth;
String userId;
String userPassword;
SettingsState({
@required this.serverIp,
@required this.serverPort,
@required this.useHttps,
@required this.needsAuth,
@required this.userId,
@required this.userPassword,
});
SettingsState copyWith({
String serverIp,
int serverPort,
bool useHttps,
bool needsAuth,
String userId,
String userPassword,
}) {
return SettingsState(
serverIp: serverIp ?? this.serverIp,
serverPort: serverPort ?? this.serverPort,
useHttps: useHttps ?? this.useHttps,
needsAuth: needsAuth ?? this.needsAuth,
userId: userId ?? this.userId,
userPassword: userPassword ?? this.userPassword,
);
}
}
abstract class SettingsEvent<T> extends Equatable {
final T value;
SettingsEvent(this.value, [List props = const <dynamic>[]]) : super(props);
}
class SetServerIpEvent extends SettingsEvent<String> {
SetServerIpEvent(String value) : super(value);
}
class SetServerPortEvent extends SettingsEvent<int> {
SetServerPortEvent(int value) : super(value);
}
class SetUseHttpsEvent extends SettingsEvent<bool> {
SetUseHttpsEvent(bool value) : super(value);
}
class SetNeedsAuthEvent extends SettingsEvent<bool> {
SetNeedsAuthEvent(bool value) : super(value);
}
class SetUserIdEvent extends SettingsEvent<String> {
SetUserIdEvent(String value) : super(value);
}
class SetUserPasswordEvent extends SettingsEvent<String> {
SetUserPasswordEvent(String value) : super(value);
}
class SettingsBloc extends Bloc<SettingsEvent, SettingsState> {
@override
SettingsState get initialState => SettingsState(
serverIp: '192.168.1.100',
serverPort: 4316,
useHttps: false,
needsAuth: false,
userId: 'openlp',
userPassword: 'password',
);
void setServerIp(String serverIp) {
if (serverIp == null || serverIp.isEmpty) {
return;
}
dispatch(SetServerIpEvent(serverIp));
}
void setServerPort(String serverPortStr) {
if (serverPortStr == null || serverPortStr.isEmpty) {
return;
}
int serverPort = int.tryParse(serverPortStr);
dispatch(SetServerPortEvent(serverPort));
}
void setUseHttps(bool useHttps) {
dispatch(SetUseHttpsEvent(useHttps));
}
void setNeedsAuth(bool needsAuth) {
dispatch(SetNeedsAuthEvent(needsAuth));
}
void setUserId(String userId) {
if (userId == null || userId.isEmpty) {
return;
}
dispatch(SetUserIdEvent(userId));
}
void setUserPassword(String userPassword) {
if (userPassword == null || userPassword.isEmpty) {
return;
}
dispatch(SetUserPasswordEvent(userPassword));
}
@override
Stream<SettingsState> mapEventToState(SettingsEvent event) async* {
if (event is SetServerIpEvent) {
yield this.currentState.copyWith(serverIp: event.value);
}
if (event is SetServerPortEvent) {
yield this.currentState.copyWith(serverPort: event.value);
}
if (event is SetUseHttpsEvent) {
yield this.currentState.copyWith(useHttps: event.value);
}
if (event is SetNeedsAuthEvent) {
yield this.currentState.copyWith(needsAuth: event.value);
}
if (event is SetUserIdEvent) {
yield this.currentState.copyWith(userId: event.value);
}
if (event is SetUserPasswordEvent) {
yield this.currentState.copyWith(userPassword: event.value);
}
}
}

View File

@ -21,111 +21,107 @@
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import '../bloc/settings_bloc.dart';
import '../configurations/app_localizations.dart'; import '../configurations/app_localizations.dart';
class Settings extends StatefulWidget { class Settings extends StatelessWidget {
@override
State<StatefulWidget> createState() => _SettingState();
}
class _SettingState extends State<Settings> {
String serverIp;
int serverPort;
bool useHttps;
bool needsAuth;
String userId;
String userPassword;
@override
void initState() {
super.initState();
serverIp = '192.168.1.100';
serverPort = 4316;
useHttps = false;
needsAuth = false;
userId = 'openlp';
userPassword = 'password';
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final SettingsBloc bloc = BlocProvider.of<SettingsBloc>(context);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(AppLocalizations.of(context).translate('settings')), title: Text(AppLocalizations.of(context).translate('settings')),
), ),
body: ListView( body: BlocBuilder<SettingsBloc, SettingsState>(
builder: (context, state) => ListView(
children: <Widget>[ children: <Widget>[
ListTile( ListTile(
title: Text( title: Text(
AppLocalizations.of(context).translate('settings_server_ip')), AppLocalizations.of(context).translate('settings_server_ip')),
subtitle: Text(serverIp), subtitle: Text(state.serverIp ?? ''),
onTap: () { onTap: () async {
showDialog<String>( String title = AppLocalizations.of(context)
.translate('dialog_server_ip_title');
String serverIp = await showDialog<String>(
context: context, context: context,
builder: (context) => _InputDialog(AppLocalizations.of(context) builder: (context) {
.translate('dialog_server_ip_title')), return _InputDialog(title,
initialValue: state.serverIp,
keyboardType: TextInputType.url);
},
); );
bloc.setServerIp(serverIp);
}, },
), ),
ListTile( ListTile(
title: Text( title: Text(AppLocalizations.of(context)
AppLocalizations.of(context).translate('settings_server_port')), .translate('settings_server_port')),
subtitle: Text('$serverPort'), subtitle: Text('${state.serverPort}'),
onTap: () { onTap: () async {
showDialog<String>( String title = AppLocalizations.of(context)
.translate('dialog_server_port_title');
String serverPortStr = await showDialog<String>(
context: context, context: context,
builder: (context) => _InputDialog(AppLocalizations.of(context) builder: (context) {
.translate('dialog_server_port_title')), return _InputDialog(title,
initialValue: '${state.serverPort}',
keyboardType: TextInputType.numberWithOptions(
decimal: true, signed: true));
},
); );
bloc.setServerPort(serverPortStr);
}, },
), ),
CheckboxListTile( CheckboxListTile(
title: Text( title: Text(
AppLocalizations.of(context).translate('settings_use_https')), AppLocalizations.of(context).translate('settings_use_https')),
value: state.useHttps,
onChanged: (value) { onChanged: (value) {
setState(() { bloc.setUseHttps(value);
useHttps = value;
});
}, },
value: useHttps,
), ),
Divider(), Divider(),
CheckboxListTile( CheckboxListTile(
title: Text( title: Text(AppLocalizations.of(context)
AppLocalizations.of(context).translate('settings_needs_auth')), .translate('settings_needs_auth')),
value: state.needsAuth,
onChanged: (value) { onChanged: (value) {
setState(() { bloc.setNeedsAuth(value);
needsAuth = value;
});
}, },
value: needsAuth,
), ),
ListTile( ListTile(
enabled: needsAuth, enabled: state.needsAuth,
title: Text( title: Text(
AppLocalizations.of(context).translate('settings_user_id')), AppLocalizations.of(context).translate('settings_user_id')),
subtitle: Text(userId), subtitle: Text(state.userId ?? ''),
onTap: () { onTap: () async {
showDialog<String>( String title = AppLocalizations.of(context)
.translate('dialog_server_user_id_title');
String userId = await showDialog<String>(
context: context, context: context,
builder: (context) => _InputDialog(AppLocalizations.of(context) builder: (context) =>
.translate('dialog_server_user_id_title')), _InputDialog(title, initialValue: state.userId),
); );
bloc.setUserId(userId);
}, },
), ),
ListTile( ListTile(
enabled: needsAuth, enabled: state.needsAuth,
title: Text( title: Text(
AppLocalizations.of(context).translate('settings_user_pass')), AppLocalizations.of(context).translate('settings_user_pass')),
subtitle: Text(userPassword), subtitle: Text(state.userPassword ?? ''),
onTap: () { onTap: () async {
showDialog<String>( String title = AppLocalizations.of(context)
.translate('dialog_server_user_pass_title');
String userPassword = await showDialog<String>(
context: context, context: context,
builder: (context) => _InputDialog(AppLocalizations.of(context) builder: (context) =>
.translate('dialog_server_user_pass_title')), _InputDialog(title, initialValue: state.userPassword),
); );
bloc.setUserPassword(userPassword);
}, },
), ),
Divider(), Divider(),
@ -138,33 +134,43 @@ class _SettingState extends State<Settings> {
), ),
], ],
), ),
),
); );
} }
} }
class _InputDialog extends StatelessWidget { class _InputDialog extends StatelessWidget {
final String title; final String title;
final String initialValue;
final TextInputType keyboardType;
final TextEditingController _textEditingController;
_InputDialog(this.title); _InputDialog(
this.title, {
this.initialValue,
this.keyboardType = TextInputType.text,
}) : _textEditingController = TextEditingController(text: initialValue ?? '');
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AlertDialog( return AlertDialog(
title: Text(this.title), title: Text(this.title),
contentPadding: EdgeInsets.all(15), contentPadding: EdgeInsets.all(15),
content: TextField(autofocus: true), content: TextField(
autofocus: true,
controller: _textEditingController,
keyboardType: keyboardType,
),
actions: <Widget>[ actions: <Widget>[
FlatButton( FlatButton(
child: Text(AppLocalizations.of(context).translate('button_cancel')), child: Text(AppLocalizations.of(context).translate('button_cancel')),
onPressed: () { onPressed: () {
Navigator.pop(context); Navigator.pop(context, '');
}, },
), ),
FlatButton( FlatButton(
child: Text(AppLocalizations.of(context).translate('button_ok')), child: Text(AppLocalizations.of(context).translate('button_ok')),
onPressed: () { onPressed: () => Navigator.pop(context, _textEditingController.text),
Navigator.pop(context, '');
},
), ),
], ],
); );

View File

@ -34,6 +34,9 @@ dependencies:
url_launcher: ^5.1.2 url_launcher: ^5.1.2
flutter_localizations: flutter_localizations:
sdk: flutter sdk: flutter
bloc: ^0.14.0
flutter_bloc: ^0.20.0
equatable: ^0.4.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: