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

Merge branch '35-add-settings-page' into 'master'

Resolve "Add settings page"

Closes #35

See merge request !34
parents 2823e0f5 3cd493e2
No related branches found
No related tags found
1 merge request!34Resolve "Add settings page"
Pipeline #4540 passed
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
app.versionName=1.0.25
app.versionCode=26
app.versionName=1.0.26
app.versionCode=27
......@@ -3,8 +3,14 @@
"bottom_nav_sample": "Sample",
"bottom_nav_chart": "Graph",
"bottom_nav_settings": "Settings",
"bottom_nav_about": "About",
"settings_title": "Settings",
"settings_label_api_url": "API URL:",
"settings_label_security_token": "Security token:",
"settings_button_save": "Save",
"about_title": "About",
"about_content": "A random application, for testing purpose only.",
......
......@@ -3,8 +3,14 @@
"bottom_nav_sample": "Démo",
"bottom_nav_chart": "Graph",
"bottom_nav_settings": "Paramètres",
"bottom_nav_about": "À propos",
"settings_title": "Paramètres",
"settings_label_api_url": "URL de l'API :",
"settings_label_security_token": "Jeton de sécurité :",
"settings_button_save": "Enregistrer",
"about_title": "À propos",
"about_content": "Application fourre-tout, à des fins de tests uniquement.",
......
......@@ -2,8 +2,10 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:ionicons/ionicons.dart';
import 'package:random/config/theme.dart';
import 'package:random/cubit/data_cubit.dart';
import 'package:random/cubit/settings_cubit.dart';
class ActivityDemoPage extends StatelessWidget {
const ActivityDemoPage({super.key});
......@@ -28,6 +30,8 @@ class ActivityDemoPage extends StatelessWidget {
SizedBox(height: 20),
persistedCounterBlock(),
SizedBox(height: 20),
fakeApiCall(),
SizedBox(height: 20),
Text('BOTTOM').tr(),
],
),
......@@ -73,4 +77,25 @@ class ActivityDemoPage extends StatelessWidget {
),
);
}
Widget fakeApiCall() {
return BlocProvider<SettingsCubit>(
create: (BuildContext context) => SettingsCubit(),
child: BlocBuilder<SettingsCubit, SettingsState>(
builder: (BuildContext context, SettingsState state) {
SettingsCubit settings = BlocProvider.of<SettingsCubit>(context);
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text('apiUrl: ' + settings.getSetting('apiUrl')),
Text('securityToken: ' + settings.getSetting('securityToken')),
Text('unknown: ' + settings.getSetting('unknown', 'undefined')),
],
);
},
),
);
}
}
......@@ -7,7 +7,8 @@ class BottomNavCubit extends HydratedCubit<int> {
void getDemoPage() => emit(0);
void getGraphPage() => emit(1);
void getAboutPage() => emit(2);
void getSettingsPage() => emit(2);
void getAboutPage() => emit(3);
@override
int? fromJson(Map<String, dynamic> json) {
......
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
part 'settings_state.dart';
class SettingsCubit extends HydratedCubit<SettingsState> {
SettingsCubit() : super(const SettingsState());
String getSetting(String key, [String? defaultValue]) {
if (state.values.keys.contains(key)) {
return state.values[key] ?? defaultValue ?? '';
}
return defaultValue ?? '';
}
void setValues({
String? apiUrl,
String? securityToken,
}) {
emit(SettingsState(
apiUrl: apiUrl != null ? apiUrl : state.apiUrl,
securityToken: securityToken != null ? securityToken : state.securityToken,
));
}
@override
SettingsState? fromJson(Map<String, dynamic> json) {
String apiUrl = json['apiUrl'] as String;
String securityToken = json['securityToken'] as String;
return SettingsState(
apiUrl: apiUrl,
securityToken: securityToken,
);
}
@override
Map<String, String>? toJson(SettingsState state) {
return <String, String>{
'apiUrl': state.apiUrl ?? '',
'securityToken': state.securityToken ?? '',
};
}
}
part of 'settings_cubit.dart';
@immutable
class SettingsState extends Equatable {
const SettingsState({
this.apiUrl,
this.securityToken,
});
final String? apiUrl;
final String? securityToken;
@override
List<String?> get props => <String?>[
apiUrl,
securityToken,
];
Map<String, String?> get values => <String, String?>{
'apiUrl': apiUrl,
'securityToken': securityToken,
};
}
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:random/ui/widgets/settings_form.dart';
class SettingsPage extends StatelessWidget {
const SettingsPage({super.key});
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
SizedBox(height: 50),
Text(
'settings_title',
textAlign: TextAlign.start,
style: Theme.of(context).textTheme.headlineMedium!.apply(fontWeightDelta: 2),
).tr(),
SizedBox(height: 8),
SettingsForm(),
],
);
}
}
......@@ -4,7 +4,9 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:random/activities/ActivityDemoPage.dart';
import 'package:random/activities/ActivityGraphPage.dart';
import 'package:random/cubit/bottom_nav_cubit.dart';
import 'package:random/cubit/settings_cubit.dart';
import 'package:random/ui/screens/about_page.dart';
import 'package:random/ui/screens/settings_page.dart';
import 'package:random/ui/widgets/custom_app_bar.dart';
import 'package:random/ui/widgets/bottom_nav_bar.dart';
......@@ -16,23 +18,27 @@ class SkeletonScreen extends StatelessWidget {
const List<Widget> pageNavigation = <Widget>[
ActivityDemoPage(),
ActivityGraphPage(),
SettingsPage(),
AboutPage(),
];
return BlocProvider<BottomNavCubit>(
create: (BuildContext context) => BottomNavCubit(),
child: Scaffold(
extendBodyBehindAppBar: true,
appBar: const CustomAppBarGone(),
body: BlocBuilder<BottomNavCubit, int>(
builder: (BuildContext context, int state) {
return AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: pageNavigation.elementAt(state));
},
return BlocProvider<SettingsCubit>(
create: (BuildContext context) => SettingsCubit(),
child: BlocProvider<BottomNavCubit>(
create: (BuildContext context) => BottomNavCubit(),
child: Scaffold(
extendBodyBehindAppBar: true,
appBar: const CustomAppBarGone(),
body: BlocBuilder<BottomNavCubit, int>(
builder: (BuildContext context, int state) {
return AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: pageNavigation.elementAt(state));
},
),
bottomNavigationBar: const BottomNavBar(),
backgroundColor: Theme.of(context).colorScheme.background,
),
bottomNavigationBar: const BottomNavBar(),
backgroundColor: Theme.of(context).colorScheme.background,
),
);
}
......
......@@ -39,6 +39,10 @@ class BottomNavBar extends StatelessWidget {
icon: const Icon(Ionicons.pencil_outline),
label: tr('bottom_nav_chart'),
),
BottomNavigationBarItem(
icon: const Icon(Ionicons.settings_outline),
label: tr('bottom_nav_settings'),
),
BottomNavigationBarItem(
icon: const Icon(Ionicons.information_circle),
label: tr('bottom_nav_about'),
......
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:unicons/unicons.dart';
import 'package:random/cubit/bottom_nav_cubit.dart';
import 'package:random/cubit/settings_cubit.dart';
class SettingsForm extends StatefulWidget {
const SettingsForm({super.key});
@override
State<SettingsForm> createState() => _SettingsFormState();
}
class _SettingsFormState extends State<SettingsForm> {
final apiUrlController = TextEditingController();
final securityTokenController = TextEditingController();
@override
void dispose() {
apiUrlController.dispose();
securityTokenController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
SettingsCubit settings = BlocProvider.of<SettingsCubit>(context);
apiUrlController.text = settings.getSetting('apiUrl');
securityTokenController.text = settings.getSetting('securityToken');
void saveSettings() {
BlocProvider.of<SettingsCubit>(context).setValues(
apiUrl: apiUrlController.text,
securityToken: securityTokenController.text,
);
BlocProvider.of<BottomNavCubit>(context).getDemoPage();
}
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Text('settings_label_api_url').tr(),
TextFormField(
controller: apiUrlController,
decoration: InputDecoration(
border: UnderlineInputBorder(),
),
),
SizedBox(height: 16),
Text('settings_label_security_token').tr(),
TextFormField(
controller: securityTokenController,
decoration: InputDecoration(
border: UnderlineInputBorder(),
),
),
SizedBox(height: 16),
ElevatedButton(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(UniconsLine.save),
SizedBox(width: 8),
Text('settings_button_save').tr(),
],
),
onPressed: () => saveSettings(),
),
],
);
}
}
......@@ -325,6 +325,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.2"
unicons:
dependency: "direct main"
description:
name: unicons
sha256: dbfcf93ff4d4ea19b324113857e358e4882115ab85db04417a4ba1c72b17a670
url: "https://pub.dev"
source: hosted
version: "2.1.1"
vector_math:
dependency: transitive
description:
......
......@@ -3,7 +3,7 @@ description: A random application, for testing purpose only.
publish_to: 'none'
version: 1.0.25+26
version: 1.0.26+27
environment:
sdk: '^3.0.0'
......@@ -18,6 +18,7 @@ dependencies:
path_provider: ^2.0.11
hydrated_bloc: ^9.0.0
ionicons: ^0.2.2
unicons: ^2.1.1
flutter:
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