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,122 +21,119 @@
// 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>(
children: <Widget>[ builder: (context, state) => ListView(
ListTile( children: <Widget>[
title: Text( ListTile(
AppLocalizations.of(context).translate('settings_server_ip')), title: Text(
subtitle: Text(serverIp), AppLocalizations.of(context).translate('settings_server_ip')),
onTap: () { subtitle: Text(state.serverIp ?? ''),
showDialog<String>( onTap: () async {
context: context, String title = AppLocalizations.of(context)
builder: (context) => _InputDialog(AppLocalizations.of(context) .translate('dialog_server_ip_title');
.translate('dialog_server_ip_title')), String serverIp = await showDialog<String>(
); context: context,
}, builder: (context) {
), return _InputDialog(title,
ListTile( initialValue: state.serverIp,
title: Text( keyboardType: TextInputType.url);
AppLocalizations.of(context).translate('settings_server_port')), },
subtitle: Text('$serverPort'), );
onTap: () { bloc.setServerIp(serverIp);
showDialog<String>( },
context: context, ),
builder: (context) => _InputDialog(AppLocalizations.of(context) ListTile(
.translate('dialog_server_port_title')), title: Text(AppLocalizations.of(context)
); .translate('settings_server_port')),
}, subtitle: Text('${state.serverPort}'),
), onTap: () async {
CheckboxListTile( String title = AppLocalizations.of(context)
title: Text( .translate('dialog_server_port_title');
AppLocalizations.of(context).translate('settings_use_https')), String serverPortStr = await showDialog<String>(
onChanged: (value) { context: context,
setState(() { builder: (context) {
useHttps = value; return _InputDialog(title,
}); initialValue: '${state.serverPort}',
}, keyboardType: TextInputType.numberWithOptions(
value: useHttps, decimal: true, signed: true));
), },
Divider(), );
CheckboxListTile( bloc.setServerPort(serverPortStr);
title: Text( },
AppLocalizations.of(context).translate('settings_needs_auth')), ),
onChanged: (value) { CheckboxListTile(
setState(() { title: Text(
needsAuth = value; AppLocalizations.of(context).translate('settings_use_https')),
}); value: state.useHttps,
}, onChanged: (value) {
value: needsAuth, bloc.setUseHttps(value);
), },
ListTile( ),
enabled: needsAuth, Divider(),
title: Text( CheckboxListTile(
AppLocalizations.of(context).translate('settings_user_id')), title: Text(AppLocalizations.of(context)
subtitle: Text(userId), .translate('settings_needs_auth')),
onTap: () { value: state.needsAuth,
showDialog<String>( onChanged: (value) {
context: context, bloc.setNeedsAuth(value);
builder: (context) => _InputDialog(AppLocalizations.of(context) },
.translate('dialog_server_user_id_title')), ),
); ListTile(
}, enabled: state.needsAuth,
), title: Text(
ListTile( AppLocalizations.of(context).translate('settings_user_id')),
enabled: needsAuth, subtitle: Text(state.userId ?? ''),
title: Text( onTap: () async {
AppLocalizations.of(context).translate('settings_user_pass')), String title = AppLocalizations.of(context)
subtitle: Text(userPassword), .translate('dialog_server_user_id_title');
onTap: () { String userId = await showDialog<String>(
showDialog<String>( context: context,
context: context, builder: (context) =>
builder: (context) => _InputDialog(AppLocalizations.of(context) _InputDialog(title, initialValue: state.userId),
.translate('dialog_server_user_pass_title')), );
); bloc.setUserId(userId);
}, },
), ),
Divider(), ListTile(
ListTile( enabled: state.needsAuth,
title: Text(AppLocalizations.of(context) title: Text(
.translate('settings_about_openlp')), AppLocalizations.of(context).translate('settings_user_pass')),
onTap: () { subtitle: Text(state.userPassword ?? ''),
launch('https://openlp.org/'); onTap: () async {
}, String title = AppLocalizations.of(context)
), .translate('dialog_server_user_pass_title');
], String userPassword = await showDialog<String>(
context: context,
builder: (context) =>
_InputDialog(title, initialValue: state.userPassword),
);
bloc.setUserPassword(userPassword);
},
),
Divider(),
ListTile(
title: Text(AppLocalizations.of(context)
.translate('settings_about_openlp')),
onTap: () {
launch('https://openlp.org/');
},
),
],
),
), ),
); );
} }
@ -144,27 +141,36 @@ 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: