Skip to content
Snippets Groups Projects
Commit e1a16ab4 authored by Benoît Harrault's avatar Benoît Harrault
Browse files

Merge branch '44-add-some-state-tests' into 'master'

Resolve "Add some state tests"

Closes #44

See merge request !42
parents 0218a3d8 e86b4f4d
No related branches found
No related tags found
1 merge request!42Resolve "Add some state tests"
Pipeline #4621 passed
org.gradle.jvmargs=-Xmx1536M org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
app.versionName=1.0.32 app.versionName=1.0.33
app.versionCode=33 app.versionCode=34
...@@ -9,6 +9,9 @@ ...@@ -9,6 +9,9 @@
"settings_title": "Settings", "settings_title": "Settings",
"settings_label_api_url": "API URL:", "settings_label_api_url": "API URL:",
"settings_label_security_token": "Security token:", "settings_label_security_token": "Security token:",
"settings_label_interface_type": "Interface type:",
"interface_type_basic": "basic",
"interface_type_expert": "expert",
"settings_button_save": "Save", "settings_button_save": "Save",
"about_title": "About", "about_title": "About",
......
...@@ -9,6 +9,9 @@ ...@@ -9,6 +9,9 @@
"settings_title": "Paramètres", "settings_title": "Paramètres",
"settings_label_api_url": "URL de l'API :", "settings_label_api_url": "URL de l'API :",
"settings_label_security_token": "Jeton de sécurité :", "settings_label_security_token": "Jeton de sécurité :",
"settings_label_interface_type": "Type d'interface :",
"interface_type_basic": "simple",
"interface_type_expert": "expert",
"settings_button_save": "Enregistrer", "settings_button_save": "Enregistrer",
"about_title": "À propos", "about_title": "À propos",
......
...@@ -2,12 +2,14 @@ import 'package:equatable/equatable.dart'; ...@@ -2,12 +2,14 @@ import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:random/models/interface_type.dart';
part 'settings_state.dart'; part 'settings_state.dart';
class SettingsCubit extends HydratedCubit<SettingsState> { class SettingsCubit extends HydratedCubit<SettingsState> {
SettingsCubit() : super(const SettingsState()); SettingsCubit() : super(const SettingsState());
String getSetting(String key, [String? defaultValue]) { Object getSetting(String key, [String? defaultValue]) {
if (state.values.keys.contains(key)) { if (state.values.keys.contains(key)) {
return state.values[key] ?? defaultValue ?? ''; return state.values[key] ?? defaultValue ?? '';
} }
...@@ -15,13 +17,27 @@ class SettingsCubit extends HydratedCubit<SettingsState> { ...@@ -15,13 +17,27 @@ class SettingsCubit extends HydratedCubit<SettingsState> {
return defaultValue ?? ''; return defaultValue ?? '';
} }
String getApiUrl() {
return state.apiUrl ?? '';
}
String getSecurityToken() {
return state.securityToken ?? '';
}
InterfaceType getInterfaceType() {
return state.interfaceType ?? InterfaceType.basic;
}
void setValues({ void setValues({
String? apiUrl, String? apiUrl,
String? securityToken, String? securityToken,
InterfaceType? interfaceType,
}) { }) {
emit(SettingsState( emit(SettingsState(
apiUrl: apiUrl != null ? apiUrl : state.apiUrl, apiUrl: apiUrl != null ? apiUrl : state.apiUrl,
securityToken: securityToken != null ? securityToken : state.securityToken, securityToken: securityToken != null ? securityToken : state.securityToken,
interfaceType: interfaceType != null ? interfaceType : state.interfaceType,
)); ));
} }
...@@ -29,10 +45,23 @@ class SettingsCubit extends HydratedCubit<SettingsState> { ...@@ -29,10 +45,23 @@ class SettingsCubit extends HydratedCubit<SettingsState> {
SettingsState? fromJson(Map<String, dynamic> json) { SettingsState? fromJson(Map<String, dynamic> json) {
String apiUrl = json['apiUrl'] as String; String apiUrl = json['apiUrl'] as String;
String securityToken = json['securityToken'] as String; String securityToken = json['securityToken'] as String;
InterfaceType interfaceType;
switch (json['interfaceType'] as String) {
case 'InterfaceType.basic':
interfaceType = InterfaceType.basic;
break;
case 'InterfaceType.expert':
interfaceType = InterfaceType.expert;
break;
default:
interfaceType = InterfaceType.basic;
}
return SettingsState( return SettingsState(
apiUrl: apiUrl, apiUrl: apiUrl,
securityToken: securityToken, securityToken: securityToken,
interfaceType: interfaceType,
); );
} }
...@@ -41,6 +70,7 @@ class SettingsCubit extends HydratedCubit<SettingsState> { ...@@ -41,6 +70,7 @@ class SettingsCubit extends HydratedCubit<SettingsState> {
return <String, String>{ return <String, String>{
'apiUrl': state.apiUrl ?? '', 'apiUrl': state.apiUrl ?? '',
'securityToken': state.securityToken ?? '', 'securityToken': state.securityToken ?? '',
'interfaceType': state.interfaceType.toString(),
}; };
} }
} }
...@@ -5,19 +5,23 @@ class SettingsState extends Equatable { ...@@ -5,19 +5,23 @@ class SettingsState extends Equatable {
const SettingsState({ const SettingsState({
this.apiUrl, this.apiUrl,
this.securityToken, this.securityToken,
this.interfaceType,
}); });
final String? apiUrl; final String? apiUrl;
final String? securityToken; final String? securityToken;
final InterfaceType? interfaceType;
@override @override
List<String?> get props => <String?>[ List<String?> get props => <String?>[
apiUrl, apiUrl,
securityToken, securityToken,
interfaceType.toString(),
]; ];
Map<String, String?> get values => <String, String?>{ Map<String, String?> get values => <String, String?>{
'apiUrl': apiUrl, 'apiUrl': apiUrl,
'securityToken': securityToken, 'securityToken': securityToken,
'interfaceType': interfaceType.toString(),
}; };
} }
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
enum InterfaceType {
basic,
expert,
}
class InterfaceTypes {
static List<Widget> selectWidgets = <Widget>[
Text('interface_type_basic').tr(),
Text('interface_type_expert').tr(),
];
}
...@@ -78,9 +78,10 @@ class DemoPage extends StatelessWidget { ...@@ -78,9 +78,10 @@ class DemoPage extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Text('apiUrl: ' + settings.getSetting('apiUrl')), Text('apiUrl: ' + settings.getApiUrl()),
Text('securityToken: ' + settings.getSetting('securityToken')), Text('securityToken: ' + settings.getSecurityToken()),
Text('unknown: ' + settings.getSetting('unknown', 'undefined')), Text('interfaceType: ' + settings.getInterfaceType().toString()),
Text('unknown: ' + settings.getSetting('unknown', 'undefined').toString()),
], ],
); );
}, },
......
...@@ -21,6 +21,8 @@ class SkeletonScreen extends StatefulWidget { ...@@ -21,6 +21,8 @@ class SkeletonScreen extends StatefulWidget {
class _SkeletonScreenState extends State<SkeletonScreen> { class _SkeletonScreenState extends State<SkeletonScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
print('SkeletonScreen - build');
const List<Widget> pageNavigation = <Widget>[ const List<Widget> pageNavigation = <Widget>[
DemoPage(), DemoPage(),
GraphPage(), GraphPage(),
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:unicons/unicons.dart';
import 'package:random/ui/widgets/header_app.dart'; import 'package:random/ui/widgets/header_app.dart';
...@@ -8,13 +7,12 @@ class StandardAppBar extends StatelessWidget implements PreferredSizeWidget { ...@@ -8,13 +7,12 @@ class StandardAppBar extends StatelessWidget implements PreferredSizeWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
print('StandardAppBar - build');
return AppBar( return AppBar(
title: const AppHeader(text: 'app_name'), title: AppHeader(text: 'app_name'),
actions: [ actions: [
IconButton( //
onPressed: () {},
icon: const Icon(UniconsSolid.refresh),
),
], ],
); );
} }
......
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:random/cubit/settings_cubit.dart';
import 'package:random/models/interface_type.dart';
class AppHeader extends StatelessWidget { class AppHeader extends StatelessWidget {
const AppHeader({super.key, required this.text}); const AppHeader({super.key, required this.text});
...@@ -8,10 +12,24 @@ class AppHeader extends StatelessWidget { ...@@ -8,10 +12,24 @@ class AppHeader extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Text( print('AppHeader - build (' + this.text + ')');
tr(text),
textAlign: TextAlign.start, return BlocProvider<SettingsCubit>(
style: Theme.of(context).textTheme.headlineMedium!.apply(fontWeightDelta: 2), create: (BuildContext context) => SettingsCubit(),
child: BlocBuilder<SettingsCubit, SettingsState>(
builder: (BuildContext context, SettingsState state) {
SettingsCubit settings = BlocProvider.of<SettingsCubit>(context);
bool isExpert = settings.getInterfaceType() == InterfaceType.expert;
String titleSuffix = isExpert ? ' ⭐' : '';
return Text(
tr(text) + titleSuffix,
textAlign: TextAlign.start,
style: Theme.of(context).textTheme.headlineMedium!.apply(fontWeightDelta: 2),
);
},
),
); );
} }
} }
...@@ -5,6 +5,8 @@ import 'package:unicons/unicons.dart'; ...@@ -5,6 +5,8 @@ import 'package:unicons/unicons.dart';
import 'package:random/cubit/bottom_nav_cubit.dart'; import 'package:random/cubit/bottom_nav_cubit.dart';
import 'package:random/cubit/settings_cubit.dart'; import 'package:random/cubit/settings_cubit.dart';
import 'package:random/config/theme.dart';
import 'package:random/models/interface_type.dart';
class SettingsForm extends StatefulWidget { class SettingsForm extends StatefulWidget {
const SettingsForm({super.key}); const SettingsForm({super.key});
...@@ -16,6 +18,7 @@ class SettingsForm extends StatefulWidget { ...@@ -16,6 +18,7 @@ class SettingsForm extends StatefulWidget {
class _SettingsFormState extends State<SettingsForm> { class _SettingsFormState extends State<SettingsForm> {
final apiUrlController = TextEditingController(); final apiUrlController = TextEditingController();
final securityTokenController = TextEditingController(); final securityTokenController = TextEditingController();
final List<Widget> interfaceTypesWidgets = InterfaceTypes.selectWidgets;
@override @override
void dispose() { void dispose() {
...@@ -24,17 +27,26 @@ class _SettingsFormState extends State<SettingsForm> { ...@@ -24,17 +27,26 @@ class _SettingsFormState extends State<SettingsForm> {
super.dispose(); super.dispose();
} }
List<bool> _selectedInterfaceType = <bool>[];
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
SettingsCubit settings = BlocProvider.of<SettingsCubit>(context); SettingsCubit settings = BlocProvider.of<SettingsCubit>(context);
apiUrlController.text = settings.getSetting('apiUrl'); apiUrlController.text = settings.getApiUrl();
securityTokenController.text = settings.getSetting('securityToken'); securityTokenController.text = settings.getSecurityToken();
if (_selectedInterfaceType.length != 2) {
_selectedInterfaceType = <bool>[
settings.getInterfaceType() == InterfaceType.basic,
settings.getInterfaceType() == InterfaceType.expert,
];
}
void saveSettings() { void saveSettings() {
BlocProvider.of<SettingsCubit>(context).setValues( BlocProvider.of<SettingsCubit>(context).setValues(
apiUrl: apiUrlController.text, apiUrl: apiUrlController.text,
securityToken: securityTokenController.text, securityToken: securityTokenController.text,
interfaceType: _selectedInterfaceType[0] ? InterfaceType.basic : InterfaceType.expert,
); );
BlocProvider.of<BottomNavCubit>(context).getDemoPage(); BlocProvider.of<BottomNavCubit>(context).getDemoPage();
...@@ -61,6 +73,27 @@ class _SettingsFormState extends State<SettingsForm> { ...@@ -61,6 +73,27 @@ class _SettingsFormState extends State<SettingsForm> {
), ),
), ),
SizedBox(height: 16), SizedBox(height: 16),
Text('settings_label_interface_type').tr(),
ToggleButtons(
direction: Axis.horizontal,
onPressed: (int index) {
setState(() {
_selectedInterfaceType = index == 0 ? [true, false] : [false, true];
});
},
borderRadius: const BorderRadius.all(Radius.circular(8)),
selectedBorderColor: appTheme.primaryColor,
selectedColor: appTheme.colorScheme.onSecondary,
fillColor: appTheme.colorScheme.secondary,
color: appTheme.primaryColor,
constraints: const BoxConstraints(
minHeight: 40.0,
minWidth: 80.0,
),
isSelected: _selectedInterfaceType,
children: interfaceTypesWidgets,
),
SizedBox(height: 20),
ElevatedButton( ElevatedButton(
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
......
...@@ -45,10 +45,10 @@ packages: ...@@ -45,10 +45,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.2" version: "1.18.0"
crypto: crypto:
dependency: transitive dependency: transitive
description: description:
...@@ -172,10 +172,10 @@ packages: ...@@ -172,10 +172,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.1" version: "1.10.0"
nested: nested:
dependency: transitive dependency: transitive
description: description:
...@@ -188,10 +188,10 @@ packages: ...@@ -188,10 +188,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: package_info_plus name: package_info_plus
sha256: "7e76fad405b3e4016cd39d08f455a4eb5199723cf594cd1b8916d47140d93017" sha256: "88bc797f44a94814f2213db1c9bd5badebafdfb8290ca9f78d4b9ee2a3db4d79"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.2.0" version: "5.0.1"
package_info_plus_platform_interface: package_info_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
...@@ -268,18 +268,18 @@ packages: ...@@ -268,18 +268,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: plugin_platform_interface name: plugin_platform_interface
sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.6" version: "2.1.7"
provider: provider:
dependency: transitive dependency: transitive
description: description:
name: provider name: provider
sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.5" version: "6.1.1"
shared_preferences: shared_preferences:
dependency: transitive dependency: transitive
description: description:
...@@ -324,10 +324,10 @@ packages: ...@@ -324,10 +324,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_web name: shared_preferences_web
sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.1" version: "2.2.2"
shared_preferences_windows: shared_preferences_windows:
dependency: transitive dependency: transitive
description: description:
...@@ -401,18 +401,18 @@ packages: ...@@ -401,18 +401,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: web name: web
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.4-beta" version: "0.3.0"
win32: win32:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3" sha256: "7c99c0e1e2fa190b48d25c81ca5e42036d5cac81430ef249027d97b0935c553f"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.9" version: "5.1.0"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
...@@ -422,5 +422,5 @@ packages: ...@@ -422,5 +422,5 @@ packages:
source: hosted source: hosted
version: "1.0.3" version: "1.0.3"
sdks: sdks:
dart: ">=3.1.0-185.0.dev <4.0.0" dart: ">=3.2.0 <4.0.0"
flutter: ">=3.7.0" flutter: ">=3.16.0"
...@@ -3,7 +3,7 @@ description: A random application, for testing purpose only. ...@@ -3,7 +3,7 @@ description: A random application, for testing purpose only.
publish_to: 'none' publish_to: 'none'
version: 1.0.32+33 version: 1.0.33+34
environment: environment:
sdk: '^3.0.0' sdk: '^3.0.0'
...@@ -18,7 +18,7 @@ dependencies: ...@@ -18,7 +18,7 @@ dependencies:
path_provider: ^2.0.11 path_provider: ^2.0.11
hydrated_bloc: ^9.0.0 hydrated_bloc: ^9.0.0
unicons: ^2.1.1 unicons: ^2.1.1
package_info_plus: ^4.2.0 package_info_plus: ^5.0.1
flutter: flutter:
uses-material-design: false uses-material-design: false
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment