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.
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'src/bloc/settings_bloc.dart';
import 'src/openlp_mobile_remote_app.dart';
import 'src/configurations/app_theme.dart';
import 'src/configurations/app_localizations.dart';
@ -60,7 +62,10 @@ void main() => runApp(
},
routes: <String, WidgetBuilder>{
'/': (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.
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:url_launcher/url_launcher.dart';
import '../bloc/settings_bloc.dart';
import '../configurations/app_localizations.dart';
class Settings extends StatefulWidget {
@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';
}
class Settings extends StatelessWidget {
@override
Widget build(BuildContext context) {
final SettingsBloc bloc = BlocProvider.of<SettingsBloc>(context);
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context).translate('settings')),
),
body: ListView(
body: BlocBuilder<SettingsBloc, SettingsState>(
builder: (context, state) => ListView(
children: <Widget>[
ListTile(
title: Text(
AppLocalizations.of(context).translate('settings_server_ip')),
subtitle: Text(serverIp),
onTap: () {
showDialog<String>(
subtitle: Text(state.serverIp ?? ''),
onTap: () async {
String title = AppLocalizations.of(context)
.translate('dialog_server_ip_title');
String serverIp = await showDialog<String>(
context: context,
builder: (context) => _InputDialog(AppLocalizations.of(context)
.translate('dialog_server_ip_title')),
builder: (context) {
return _InputDialog(title,
initialValue: state.serverIp,
keyboardType: TextInputType.url);
},
);
bloc.setServerIp(serverIp);
},
),
ListTile(
title: Text(
AppLocalizations.of(context).translate('settings_server_port')),
subtitle: Text('$serverPort'),
onTap: () {
showDialog<String>(
title: Text(AppLocalizations.of(context)
.translate('settings_server_port')),
subtitle: Text('${state.serverPort}'),
onTap: () async {
String title = AppLocalizations.of(context)
.translate('dialog_server_port_title');
String serverPortStr = await showDialog<String>(
context: context,
builder: (context) => _InputDialog(AppLocalizations.of(context)
.translate('dialog_server_port_title')),
builder: (context) {
return _InputDialog(title,
initialValue: '${state.serverPort}',
keyboardType: TextInputType.numberWithOptions(
decimal: true, signed: true));
},
);
bloc.setServerPort(serverPortStr);
},
),
CheckboxListTile(
title: Text(
AppLocalizations.of(context).translate('settings_use_https')),
value: state.useHttps,
onChanged: (value) {
setState(() {
useHttps = value;
});
bloc.setUseHttps(value);
},
value: useHttps,
),
Divider(),
CheckboxListTile(
title: Text(
AppLocalizations.of(context).translate('settings_needs_auth')),
title: Text(AppLocalizations.of(context)
.translate('settings_needs_auth')),
value: state.needsAuth,
onChanged: (value) {
setState(() {
needsAuth = value;
});
bloc.setNeedsAuth(value);
},
value: needsAuth,
),
ListTile(
enabled: needsAuth,
enabled: state.needsAuth,
title: Text(
AppLocalizations.of(context).translate('settings_user_id')),
subtitle: Text(userId),
onTap: () {
showDialog<String>(
subtitle: Text(state.userId ?? ''),
onTap: () async {
String title = AppLocalizations.of(context)
.translate('dialog_server_user_id_title');
String userId = await showDialog<String>(
context: context,
builder: (context) => _InputDialog(AppLocalizations.of(context)
.translate('dialog_server_user_id_title')),
builder: (context) =>
_InputDialog(title, initialValue: state.userId),
);
bloc.setUserId(userId);
},
),
ListTile(
enabled: needsAuth,
enabled: state.needsAuth,
title: Text(
AppLocalizations.of(context).translate('settings_user_pass')),
subtitle: Text(userPassword),
onTap: () {
showDialog<String>(
subtitle: Text(state.userPassword ?? ''),
onTap: () async {
String title = AppLocalizations.of(context)
.translate('dialog_server_user_pass_title');
String userPassword = await showDialog<String>(
context: context,
builder: (context) => _InputDialog(AppLocalizations.of(context)
.translate('dialog_server_user_pass_title')),
builder: (context) =>
_InputDialog(title, initialValue: state.userPassword),
);
bloc.setUserPassword(userPassword);
},
),
Divider(),
@ -138,33 +134,43 @@ class _SettingState extends State<Settings> {
),
],
),
),
);
}
}
class _InputDialog extends StatelessWidget {
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
Widget build(BuildContext context) {
return AlertDialog(
title: Text(this.title),
contentPadding: EdgeInsets.all(15),
content: TextField(autofocus: true),
content: TextField(
autofocus: true,
controller: _textEditingController,
keyboardType: keyboardType,
),
actions: <Widget>[
FlatButton(
child: Text(AppLocalizations.of(context).translate('button_cancel')),
onPressed: () {
Navigator.pop(context);
Navigator.pop(context, '');
},
),
FlatButton(
child: Text(AppLocalizations.of(context).translate('button_ok')),
onPressed: () {
Navigator.pop(context, '');
},
onPressed: () => Navigator.pop(context, _textEditingController.text),
),
],
);

View File

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