diff --git a/android/gradle.properties b/android/gradle.properties index 4617f96d772a363abed31166843abc0fc2c01732..32638a39cd697ca58b0dd482eb1cd46e5e051809 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.enableJetifier=true -app.versionName=0.0.34 -app.versionCode=34 +app.versionName=0.0.35 +app.versionCode=35 diff --git a/assets/translations/en.json b/assets/translations/en.json index 6b82e28719f34bf4b043f9414da9ef18fb13bd43..6b7214cae0652de42dcf91671facd5518f05afa4 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -5,6 +5,7 @@ "bottom_nav_home": "Home", "bottom_nav_discoveries": "Discoveries", "bottom_nav_repartition": "Statistics", + "bottom_nav_settings": "Settings", "global_statistics": "Global statistics", "statistics_total_scrobbles_count": "Total scrobbles count: {count}", @@ -23,6 +24,11 @@ "top_artists_title": "Top artists ({daysCount} days)", + "settings_title": "Settings", + "settings_label_username": "Username: ", + "settings_label_security_token": "Security token: ", + "settings_button_save": "Save", + "MON": "MON", "TUE": "TUE", "WED": "WED", diff --git a/assets/translations/fr.json b/assets/translations/fr.json index 84b42363ca5567aaae73d4e7a84fc84cf192d47e..b495f443ab3e8d7cebd8159e0564771744033379 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -5,6 +5,7 @@ "bottom_nav_home": "Accueil", "bottom_nav_discoveries": "Découvertes", "bottom_nav_repartition": "Statistiques", + "bottom_nav_settings": "Paramètres", "global_statistics": "Statistiques globales d'écoutes", "statistics_total_scrobbles_count": "Nombre total d'écoutes : {count}", @@ -23,6 +24,11 @@ "top_artists_title": "Top artistes ({daysCount} jours)", + "settings_title": "Paramètres", + "settings_label_username": "Utilisateur : ", + "settings_label_security_token": "Jeton de sécurité : ", + "settings_button_save": "Enregistrer", + "MON": "LUN", "TUE": "MAR", "WED": "MER", diff --git a/fastlane/metadata/android/en-US/changelogs/35.txt b/fastlane/metadata/android/en-US/changelogs/35.txt new file mode 100644 index 0000000000000000000000000000000000000000..5a6ab6b957dd25817c5c1921d71b710bd41e0cc8 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/35.txt @@ -0,0 +1 @@ +Add settings page (username, security token). diff --git a/fastlane/metadata/android/fr-FR/changelogs/35.txt b/fastlane/metadata/android/fr-FR/changelogs/35.txt new file mode 100644 index 0000000000000000000000000000000000000000..84037e39ff0fbb7c7b8f4000d0daf3a9dae7953b --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/35.txt @@ -0,0 +1 @@ +Ajout d'une page de paramètres (nom d'utilisateur, jeton de sécurité). diff --git a/lib/cubit/bottom_nav_cubit.dart b/lib/cubit/bottom_nav_cubit.dart index 8753255ac7acba27b594ea5cfb5b46aafee31625..1592a438fa2446d66db1a35519bd675d7f2dd146 100644 --- a/lib/cubit/bottom_nav_cubit.dart +++ b/lib/cubit/bottom_nav_cubit.dart @@ -3,7 +3,7 @@ import 'package:hydrated_bloc/hydrated_bloc.dart'; class BottomNavCubit extends HydratedCubit<int> { BottomNavCubit() : super(0); - int pagesCount = 3; + int pagesCount = 4; void updateIndex(int index) { if (isIndexAllowed(index)) { diff --git a/lib/cubit/settings_cubit.dart b/lib/cubit/settings_cubit.dart new file mode 100644 index 0000000000000000000000000000000000000000..45df233828485d0dd6df7b4399f3cdc09b67071b --- /dev/null +++ b/lib/cubit/settings_cubit.dart @@ -0,0 +1,46 @@ +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 getUsername() { + return state.username ?? ''; + } + + String getSecurityToken() { + return state.securityToken ?? ''; + } + + void setValues({ + String? username, + String? securityToken, + }) { + emit(SettingsState( + username: username != null ? username : state.username, + securityToken: securityToken != null ? securityToken : state.securityToken, + )); + } + + @override + SettingsState? fromJson(Map<String, dynamic> json) { + String username = json['username'] as String; + String securityToken = json['securityToken'] as String; + + return SettingsState( + username: username, + securityToken: securityToken, + ); + } + + @override + Map<String, String>? toJson(SettingsState state) { + return <String, String>{ + 'username': state.username ?? '', + 'securityToken': state.securityToken ?? '', + }; + } +} diff --git a/lib/cubit/settings_state.dart b/lib/cubit/settings_state.dart new file mode 100644 index 0000000000000000000000000000000000000000..c32e6bb60c484a7539a52a2cb1d2bc3c27b5f967 --- /dev/null +++ b/lib/cubit/settings_state.dart @@ -0,0 +1,23 @@ +part of 'settings_cubit.dart'; + +@immutable +class SettingsState extends Equatable { + const SettingsState({ + this.username, + this.securityToken, + }); + + final String? username; + final String? securityToken; + + @override + List<String?> get props => <String?>[ + username, + securityToken, + ]; + + Map<String, String?> get values => <String, String?>{ + 'username': username, + 'securityToken': securityToken, + }; +} diff --git a/lib/main.dart b/lib/main.dart index 52ae6ef743ac43935bfb2a373427b4a32b82950e..d4ca87cf3c8e94138b5fa45e851b5c143fa6ce1f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -16,6 +16,7 @@ import 'package:scrobbles/cubit/data_statistics_global_cubit.dart'; import 'package:scrobbles/cubit/data_statistics_recent_cubit.dart'; import 'package:scrobbles/cubit/data_timeline_cubit.dart'; import 'package:scrobbles/cubit/data_top_artists_cubit.dart'; +import 'package:scrobbles/cubit/settings_cubit.dart'; import 'package:scrobbles/ui/skeleton.dart'; void main() async { @@ -49,6 +50,7 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MultiBlocProvider( providers: [ + BlocProvider<SettingsCubit>(create: (context) => SettingsCubit()), BlocProvider<BottomNavCubit>(create: (context) => BottomNavCubit()), BlocProvider<DataCountsByDayCubit>(create: (context) => DataCountsByDayCubit()), BlocProvider<DataCountsByHourCubit>(create: (context) => DataCountsByHourCubit()), diff --git a/lib/ui/screens/settings.dart b/lib/ui/screens/settings.dart new file mode 100644 index 0000000000000000000000000000000000000000..2dc9cec7f67713bf5f6f966eca30dbc60dc7442d --- /dev/null +++ b/lib/ui/screens/settings.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; + +import 'package:scrobbles/ui/widgets/header_app.dart'; +import 'package:scrobbles/ui/widgets/settings_form.dart'; + +class ScreenSettings extends StatelessWidget { + const ScreenSettings({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: <Widget>[ + SizedBox(height: 8), + AppHeader(text: 'settings_title'), + SizedBox(height: 8), + SettingsForm(), + ], + ); + } +} diff --git a/lib/ui/skeleton.dart b/lib/ui/skeleton.dart index fe99383846d2e450b605a8e70b100dd0602e1f6e..46c449c208958e368280f2deb58f355176ca7d65 100644 --- a/lib/ui/skeleton.dart +++ b/lib/ui/skeleton.dart @@ -5,6 +5,7 @@ import 'package:flutter_swipe/flutter_swipe.dart'; import 'package:scrobbles/cubit/bottom_nav_cubit.dart'; import 'package:scrobbles/ui/screens/discoveries.dart'; import 'package:scrobbles/ui/screens/home.dart'; +import 'package:scrobbles/ui/screens/settings.dart'; import 'package:scrobbles/ui/screens/statistics.dart'; import 'package:scrobbles/ui/widgets/app_bar.dart'; import 'package:scrobbles/ui/widgets/bottom_nav_bar.dart'; @@ -23,6 +24,7 @@ class _SkeletonScreenState extends State<SkeletonScreen> { const ScreenHome(), const ScreenDiscoveries(), const ScreenStatistics(), + const ScreenSettings(), ]; return Scaffold( diff --git a/lib/ui/widgets/bottom_nav_bar.dart b/lib/ui/widgets/bottom_nav_bar.dart index daaed377dc3b2c28f8bf1a9cf0d0927396552e81..c775a6ac79e7ff50eabd40ae2bca30fb9913174c 100644 --- a/lib/ui/widgets/bottom_nav_bar.dart +++ b/lib/ui/widgets/bottom_nav_bar.dart @@ -54,6 +54,10 @@ class BottomNavBar extends StatelessWidget { icon: const Icon(Ionicons.bar_chart_outline), label: tr('bottom_nav_repartition'), ), + BottomNavigationBarItem( + icon: const Icon(Ionicons.settings_outline), + label: tr('bottom_nav_settings'), + ), ], ); }, diff --git a/lib/ui/widgets/settings_form.dart b/lib/ui/widgets/settings_form.dart new file mode 100644 index 0000000000000000000000000000000000000000..5af5f14463616d8371faec9efbdf3f3fcc7e2129 --- /dev/null +++ b/lib/ui/widgets/settings_form.dart @@ -0,0 +1,76 @@ +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:scrobbles/cubit/settings_cubit.dart'; + +class SettingsForm extends StatefulWidget { + const SettingsForm({super.key}); + + @override + State<SettingsForm> createState() => _SettingsFormState(); +} + +class _SettingsFormState extends State<SettingsForm> { + final usernameController = TextEditingController(); + final securityTokenController = TextEditingController(); + + @override + void dispose() { + usernameController.dispose(); + securityTokenController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + SettingsCubit settings = BlocProvider.of<SettingsCubit>(context); + + usernameController.text = settings.getUsername(); + securityTokenController.text = settings.getSecurityToken(); + + void saveSettings() { + BlocProvider.of<SettingsCubit>(context).setValues( + username: usernameController.text, + securityToken: securityTokenController.text, + ); + } + + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: <Widget>[ + Text('settings_label_username').tr(), + TextFormField( + controller: usernameController, + decoration: InputDecoration( + border: UnderlineInputBorder(), + ), + ), + SizedBox(height: 16), + Text('settings_label_security_token').tr(), + TextFormField( + controller: securityTokenController, + decoration: InputDecoration( + border: UnderlineInputBorder(), + ), + ), + SizedBox(height: 20), + ElevatedButton( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon(UniconsLine.save), + SizedBox(width: 8), + Text('settings_button_save').tr(), + ], + ), + onPressed: () => saveSettings(), + ), + ], + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 8c8dc91094df1092d75a2305961405c996af1fae..8a873de9f3a12fc15e37335882e096d2bf0bac6b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: Display scrobbles data and charts publish_to: 'none' -version: 0.0.34+34 +version: 0.0.35+35 environment: sdk: '^3.0.0'