From 03f8546183637a3d9225d2285289cfce4e02199d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Harrault?= <benoit@harrault.fr> Date: Wed, 8 Nov 2023 17:02:58 +0100 Subject: [PATCH] Add navigation bar, split charts in pages --- android/gradle.properties | 4 +- assets/translations/en.json | 4 + assets/translations/fr.json | 4 + .../metadata/android/en-US/changelogs/22.txt | 1 + .../metadata/android/fr-FR/changelogs/22.txt | 1 + lib/cubit/bottom_nav_cubit.dart | 21 ++++ lib/main.dart | 12 +++ lib/ui/screens/discoveries_screen.dart | 28 ++++++ lib/ui/screens/home_screen.dart | 31 ++++++ lib/ui/screens/skeleton_screen.dart | 34 +++++-- ...ain_screen.dart => statistics_screen.dart} | 19 +--- lib/ui/widgets/bottom_nav_bar.dart | 51 ++++++++++ lib/ui/widgets/charts/custom_bar_chart.dart | 2 +- lib/ui/widgets/charts/custom_line_chart.dart | 2 +- pubspec.lock | 98 ++++++++++++++++++- pubspec.yaml | 9 +- 16 files changed, 294 insertions(+), 27 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/22.txt create mode 100644 fastlane/metadata/android/fr-FR/changelogs/22.txt create mode 100644 lib/cubit/bottom_nav_cubit.dart create mode 100644 lib/ui/screens/discoveries_screen.dart create mode 100644 lib/ui/screens/home_screen.dart rename lib/ui/screens/{main_screen.dart => statistics_screen.dart} (51%) create mode 100644 lib/ui/widgets/bottom_nav_bar.dart diff --git a/android/gradle.properties b/android/gradle.properties index eeed3ef..ed86f0f 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.21 -app.versionCode=21 +app.versionName=0.0.22 +app.versionCode=22 diff --git a/assets/translations/en.json b/assets/translations/en.json index d944c41..9c047c5 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -1,6 +1,10 @@ { "app_name": "Scrobbles", + "bottom_nav_home": "Home", + "bottom_nav_discoveries": "Discoveries", + "bottom_nav_repartition": "Statistics", + "global_statistics": "Global statistics", "statistics_total_scrobbles_count": "Total scrobbles count: {count}", "statistics_last_scrobble": "Last scrobble: {datetime}", diff --git a/assets/translations/fr.json b/assets/translations/fr.json index a0a5011..6ec386f 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -1,6 +1,10 @@ { "app_name": "Scrobbles", + "bottom_nav_home": "Accueil", + "bottom_nav_discoveries": "Découvertes", + "bottom_nav_repartition": "Statistiques", + "global_statistics": "Statistiques globales d'écoutes", "statistics_total_scrobbles_count": "Nombre total d'écoutes : {count}", "statistics_last_scrobble": "Dernière écoute : {datetime}", diff --git a/fastlane/metadata/android/en-US/changelogs/22.txt b/fastlane/metadata/android/en-US/changelogs/22.txt new file mode 100644 index 0000000..7807734 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/22.txt @@ -0,0 +1 @@ +Add navigation bar, split charts in pages. diff --git a/fastlane/metadata/android/fr-FR/changelogs/22.txt b/fastlane/metadata/android/fr-FR/changelogs/22.txt new file mode 100644 index 0000000..2bbf823 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/22.txt @@ -0,0 +1 @@ +Ajout d'une barre de navigation, séparation des graphiques en plusieurs pages. diff --git a/lib/cubit/bottom_nav_cubit.dart b/lib/cubit/bottom_nav_cubit.dart new file mode 100644 index 0000000..57acf9a --- /dev/null +++ b/lib/cubit/bottom_nav_cubit.dart @@ -0,0 +1,21 @@ +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +class BottomNavCubit extends HydratedCubit<int> { + BottomNavCubit() : super(0); + + void updateIndex(int index) => emit(index); + + void getHomePage() => emit(0); + void getDiscoveriesPage() => emit(1); + void getStatisticsPage() => emit(2); + + @override + int? fromJson(Map<String, dynamic> json) { + return json['pageIndex'] as int?; + } + + @override + Map<String, dynamic>? toJson(int state) { + return <String, int>{'pageIndex': state}; + } +} diff --git a/lib/main.dart b/lib/main.dart index 66de9e0..c6ec2e7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,12 +1,24 @@ +import 'dart:io'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:hive/hive.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; +import 'package:path_provider/path_provider.dart'; import 'config/theme.dart'; + import 'ui/screens/skeleton_screen.dart'; void main() async { + /// Initialize packages WidgetsFlutterBinding.ensureInitialized(); await EasyLocalization.ensureInitialized(); + final Directory tmpDir = await getTemporaryDirectory(); + Hive.init(tmpDir.toString()); + HydratedBloc.storage = await HydratedStorage.build( + storageDirectory: tmpDir, + ); runApp( EasyLocalization( diff --git a/lib/ui/screens/discoveries_screen.dart b/lib/ui/screens/discoveries_screen.dart new file mode 100644 index 0000000..ca590c7 --- /dev/null +++ b/lib/ui/screens/discoveries_screen.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; + +import '../widgets/main_screen/discoveries_card.dart'; + +class DiscoveriesScreen extends StatefulWidget { + const DiscoveriesScreen({super.key}); + + @override + State<DiscoveriesScreen> createState() => _DiscoveriesScreenState(); +} + +class _DiscoveriesScreenState extends State<DiscoveriesScreen> { + @override + Widget build(BuildContext context) { + return Material( + color: Theme.of(context).colorScheme.background, + child: ListView( + padding: const EdgeInsets.symmetric(horizontal: 4), + physics: const BouncingScrollPhysics(), + children: <Widget>[ + const SizedBox(height: 8), + const ChartDiscoveriesCard(), + const SizedBox(height: 36), + ], + ), + ); + } +} diff --git a/lib/ui/screens/home_screen.dart b/lib/ui/screens/home_screen.dart new file mode 100644 index 0000000..d640754 --- /dev/null +++ b/lib/ui/screens/home_screen.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; + +import '../widgets/main_screen/statistics_card.dart'; +import '../widgets/main_screen/timeline_card.dart'; + +class HomeScreen extends StatefulWidget { + const HomeScreen({super.key}); + + @override + State<HomeScreen> createState() => _HomeScreenState(); +} + +class _HomeScreenState extends State<HomeScreen> { + @override + Widget build(BuildContext context) { + return Material( + color: Theme.of(context).colorScheme.background, + child: ListView( + padding: const EdgeInsets.symmetric(horizontal: 4), + physics: const BouncingScrollPhysics(), + children: <Widget>[ + const SizedBox(height: 8), + const StatisticsCard(), + const SizedBox(height: 6), + const ChartTimelineCard(), + const SizedBox(height: 36), + ], + ), + ); + } +} diff --git a/lib/ui/screens/skeleton_screen.dart b/lib/ui/screens/skeleton_screen.dart index 161794e..edef18d 100644 --- a/lib/ui/screens/skeleton_screen.dart +++ b/lib/ui/screens/skeleton_screen.dart @@ -1,7 +1,13 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; -import 'main_screen.dart'; +import '../../cubit/bottom_nav_cubit.dart'; import '../widgets/app_bar.dart'; +import '../widgets/bottom_nav_bar.dart'; + +import 'discoveries_screen.dart'; +import 'home_screen.dart'; +import 'statistics_screen.dart'; class SkeletonScreen extends StatefulWidget { const SkeletonScreen({super.key}); @@ -13,11 +19,27 @@ class SkeletonScreen extends StatefulWidget { class _SkeletonScreenState extends State<SkeletonScreen> { @override Widget build(BuildContext context) { - return Scaffold( - appBar: StandardAppBar(notifyParent: refresh), - extendBodyBehindAppBar: true, - body: const MainScreen(), - backgroundColor: Theme.of(context).colorScheme.background, + const List<Widget> pageNavigation = <Widget>[ + const HomeScreen(), + const DiscoveriesScreen(), + const StatisticsScreen(), + ]; + + return BlocProvider<BottomNavCubit>( + create: (BuildContext context) => BottomNavCubit(), + child: Scaffold( + appBar: StandardAppBar(notifyParent: refresh), + extendBodyBehindAppBar: false, + 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, + ), ); } diff --git a/lib/ui/screens/main_screen.dart b/lib/ui/screens/statistics_screen.dart similarity index 51% rename from lib/ui/screens/main_screen.dart rename to lib/ui/screens/statistics_screen.dart index 780c644..c3f9e46 100644 --- a/lib/ui/screens/main_screen.dart +++ b/lib/ui/screens/statistics_screen.dart @@ -2,18 +2,15 @@ import 'package:flutter/material.dart'; import '../widgets/main_screen/counts_by_day_card.dart'; import '../widgets/main_screen/counts_by_hour_card.dart'; -import '../widgets/main_screen/discoveries_card.dart'; -import '../widgets/main_screen/statistics_card.dart'; -import '../widgets/main_screen/timeline_card.dart'; -class MainScreen extends StatefulWidget { - const MainScreen({super.key}); +class StatisticsScreen extends StatefulWidget { + const StatisticsScreen({super.key}); @override - State<MainScreen> createState() => _MainScreenState(); + State<StatisticsScreen> createState() => _StatisticsScreenState(); } -class _MainScreenState extends State<MainScreen> { +class _StatisticsScreenState extends State<StatisticsScreen> { @override Widget build(BuildContext context) { return Material( @@ -22,13 +19,7 @@ class _MainScreenState extends State<MainScreen> { padding: const EdgeInsets.symmetric(horizontal: 4), physics: const BouncingScrollPhysics(), children: <Widget>[ - const SizedBox(height: 90), - const StatisticsCard(), - const SizedBox(height: 6), - const ChartTimelineCard(), - const SizedBox(height: 6), - const ChartDiscoveriesCard(), - const SizedBox(height: 6), + const SizedBox(height: 8), const ChartCountsByDayCard(), const SizedBox(height: 6), const ChartCountsByHourCard(), diff --git a/lib/ui/widgets/bottom_nav_bar.dart b/lib/ui/widgets/bottom_nav_bar.dart new file mode 100644 index 0000000..c06cecb --- /dev/null +++ b/lib/ui/widgets/bottom_nav_bar.dart @@ -0,0 +1,51 @@ +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 '../../cubit/bottom_nav_cubit.dart'; + +class BottomNavBar extends StatelessWidget { + const BottomNavBar({super.key}); + + @override + Widget build(BuildContext context) { + return Card( + margin: const EdgeInsets.only(top: 1, right: 4, left: 4), + elevation: 4, + shadowColor: Theme.of(context).colorScheme.shadow, + color: Theme.of(context).colorScheme.surfaceVariant, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), + ), + child: BlocBuilder<BottomNavCubit, int>(builder: (BuildContext context, int state) { + return BottomNavigationBar( + currentIndex: state, + onTap: (int index) => context.read<BottomNavCubit>().updateIndex(index), + type: BottomNavigationBarType.fixed, + elevation: 0, + backgroundColor: Colors.transparent, + selectedItemColor: Theme.of(context).colorScheme.primary, + unselectedItemColor: Theme.of(context).textTheme.bodySmall!.color, + items: <BottomNavigationBarItem>[ + BottomNavigationBarItem( + icon: const Icon(Ionicons.home_outline), + label: tr('bottom_nav_home'), + ), + BottomNavigationBarItem( + icon: const Icon(Ionicons.star_outline), + label: tr('bottom_nav_discoveries'), + ), + BottomNavigationBarItem( + icon: const Icon(Ionicons.bar_chart_outline), + label: tr('bottom_nav_repartition'), + ), + ], + ); + }), + ); + } +} diff --git a/lib/ui/widgets/charts/custom_bar_chart.dart b/lib/ui/widgets/charts/custom_bar_chart.dart index 162d53b..8cdd32d 100644 --- a/lib/ui/widgets/charts/custom_bar_chart.dart +++ b/lib/ui/widgets/charts/custom_bar_chart.dart @@ -8,7 +8,7 @@ import '../../../utils/color_extensions.dart'; class CustomBarChart extends StatelessWidget { CustomBarChart({super.key}); - final double chartHeight = 120.0; + final double chartHeight = 150.0; final double verticalTicksInterval = 10; final String verticalAxisTitleSuffix = ''; final double titleFontSize = 10; diff --git a/lib/ui/widgets/charts/custom_line_chart.dart b/lib/ui/widgets/charts/custom_line_chart.dart index 01a48a9..fadf283 100644 --- a/lib/ui/widgets/charts/custom_line_chart.dart +++ b/lib/ui/widgets/charts/custom_line_chart.dart @@ -6,7 +6,7 @@ import '../../../config/app_colors.dart'; class CustomLineChart extends StatelessWidget { CustomLineChart({super.key}); - final double chartHeight = 120.0; + final double chartHeight = 150.0; final double titleFontSize = 10; @override diff --git a/pubspec.lock b/pubspec.lock index 42ca9d5..7dd2956 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -17,6 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" + bloc: + dependency: transitive + description: + name: bloc + sha256: "3820f15f502372d979121de1f6b97bfcf1630ebff8fe1d52fb2b0bfa49be5b49" + url: "https://pub.dev" + source: hosted + version: "8.1.2" characters: dependency: transitive description: @@ -41,6 +49,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.17.2" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" easy_localization: dependency: "direct main" description: @@ -58,7 +74,7 @@ packages: source: hosted version: "0.0.2" equatable: - dependency: transitive + dependency: "direct main" description: name: equatable sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 @@ -94,6 +110,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + sha256: e74efb89ee6945bcbce74a5b3a5a3376b088e5f21f55c263fc38cbdc6237faae + url: "https://pub.dev" + source: hosted + version: "8.1.3" flutter_localizations: dependency: transitive description: flutter @@ -104,6 +128,14 @@ packages: description: flutter source: sdk version: "0.0.0" + hive: + dependency: transitive + description: + name: hive + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" + url: "https://pub.dev" + source: hosted + version: "2.2.3" http: dependency: "direct main" description: @@ -120,6 +152,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" + hydrated_bloc: + dependency: "direct main" + description: + name: hydrated_bloc + sha256: "24994e61f64904d911683cce1a31dc4ef611619da5253f1de2b7b8fc6f79a118" + url: "https://pub.dev" + source: hosted + version: "9.1.2" intl: dependency: transitive description: @@ -128,6 +168,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.18.1" + ionicons: + dependency: "direct main" + description: + name: ionicons + sha256: "5496bc65a16115ecf05b15b78f494ee4a8869504357668f0a11d689e970523cf" + url: "https://pub.dev" + source: hosted + version: "0.2.2" material_color_utilities: dependency: transitive description: @@ -144,6 +192,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" path: dependency: transitive description: @@ -152,6 +208,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.8.3" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa + url: "https://pub.dev" + source: hosted + version: "2.1.1" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" + url: "https://pub.dev" + source: hosted + version: "2.3.1" path_provider_linux: dependency: transitive description: @@ -192,6 +272,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.6" + provider: + dependency: transitive + description: + name: provider + sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f + url: "https://pub.dev" + source: hosted + version: "6.0.5" shared_preferences: dependency: transitive description: @@ -269,6 +357,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60" + url: "https://pub.dev" + source: hosted + version: "3.1.0" term_glyph: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 0e1c58c..415477b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: Display scrobbles data and charts publish_to: 'none' -version: 0.0.21+21 +version: 0.0.22+22 environment: sdk: '^3.0.0' @@ -13,8 +13,13 @@ dependencies: sdk: flutter easy_localization: ^3.0.1 - http: ^1.1.0 + equatable: ^2.0.5 fl_chart: ^0.64.0 + flutter_bloc: ^8.1.1 + http: ^1.1.0 + path_provider: ^2.0.11 + hydrated_bloc: ^9.0.0 + ionicons: ^0.2.2 unicons: ^2.1.1 flutter: -- GitLab