diff --git a/android/app/build.gradle b/android/app/build.gradle index a431a29d1d1e096dcfe8af84a3abb908afc63acf..ab94378c9b60299979ba3ac505d757c864f9e816 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -37,7 +37,7 @@ if (keystorePropertiesFile.exists()) { } android { - compileSdkVersion 33 + compileSdkVersion 34 namespace "org.benoitharrault.scrobbles" defaultConfig { diff --git a/android/gradle.properties b/android/gradle.properties index 547a7f87984e869bc68c9f0a1c8e94cde2dcad43..7dd3e3281588d71aa3b798861776ac6c37f59a4e 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.57 -app.versionCode=57 +app.versionName=0.1.0 +app.versionCode=58 diff --git a/assets/translations/en.json b/assets/translations/en.json index 51578cf240c0ab7a404cca695f530d3cdc9e5937..602be62c1bfb62b56b9bd131a8f8356cff5ee66c 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -1,12 +1,15 @@ { "app_name": "Scrobbles", - "": "", - "bottom_nav_home": "Home", - "bottom_nav_discoveries": "Discoveries", - "bottom_nav_repartition": "Statistics", + "page_home": "Home", + "page_discoveries": "Discoveries", + "page_statistics": "Statistics", "bottom_nav_settings": "Settings", + "about_title": "Informations", + "about_content": "Scrobbles", + "about_version": "Version: {version}", + "global_statistics": "Global statistics", "statistics_total_scrobbles_count": "Total scrobbles count: {count}", "statistics_last_scrobble": "Last scrobble: {datetime}", @@ -50,5 +53,7 @@ "THU": "THU", "FRI": "FRI", "SAT": "SAT", - "SUN": "SUN" + "SUN": "SUN", + + "": "" } diff --git a/assets/translations/fr.json b/assets/translations/fr.json index 86c15ba62ae62218c73dea90988fb32c4e70cdab..2d04be69c0f5cda9dca23a82339a8fc6a80bf3a2 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -1,12 +1,15 @@ { "app_name": "Scrobbles", - "": "", - "bottom_nav_home": "Accueil", - "bottom_nav_discoveries": "Découvertes", - "bottom_nav_repartition": "Statistiques", + "page_home": "Général", + "page_discoveries": "Découvertes", + "page_statistics": "Statistiques", "bottom_nav_settings": "Paramètres", + "about_title": "Informations", + "about_content": "Scrobbles.", + "about_version": "Version : {version}", + "global_statistics": "Statistiques globales d'écoutes", "statistics_total_scrobbles_count": "Nombre total d'écoutes : {count}", "statistics_last_scrobble": "Dernière écoute : {datetime}", @@ -34,7 +37,7 @@ "settings_label_security_token": "Jeton de sécurité : ", "settings_title_days_count": "Nombre de jours : ", "settings_label_discoveries_days_count": "Découvertes : ", - "settings_label_distribution_days_count": "Répartitions (par jour/heure) : ", + "settings_label_distribution_days_count": "Répartitions par jour/heure : ", "settings_label_statistics_recent_days_count": "Statistiques : ", "settings_label_timeline_days_count": "Timeline globale : ", "settings_label_top_artists_days_count": "Top Artistes : ", @@ -50,5 +53,7 @@ "THU": "JEU", "FRI": "VEN", "SAT": "SAM", - "SUN": "DIM" + "SUN": "DIM", + + "": "" } diff --git a/fastlane/metadata/android/en-US/changelogs/58.txt b/fastlane/metadata/android/en-US/changelogs/58.txt new file mode 100644 index 0000000000000000000000000000000000000000..060b202b87dcd33388eb8a9d1a45cbd276fc8ba3 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/58.txt @@ -0,0 +1 @@ +Improve/normalize app architecture. diff --git a/fastlane/metadata/android/fr-FR/changelogs/58.txt b/fastlane/metadata/android/fr-FR/changelogs/58.txt new file mode 100644 index 0000000000000000000000000000000000000000..3cab228cbe7443782c4b187cbe805e4d0ba8e71a --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/58.txt @@ -0,0 +1 @@ +Amélioration/normalisation de l'architecture de l'application. diff --git a/lib/config/activity_page.dart b/lib/config/activity_page.dart new file mode 100644 index 0000000000000000000000000000000000000000..b58d29592d6890feae59b01b48e977be20bdd758 --- /dev/null +++ b/lib/config/activity_page.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:ionicons/ionicons.dart'; + +import 'package:scrobbles/ui/pages/discoveries.dart'; +import 'package:scrobbles/ui/pages/home.dart'; +import 'package:scrobbles/ui/pages/statistics.dart'; + +class ActivityPageItem { + final Icon icon; + final Widget page; + final String code; + + const ActivityPageItem({ + required this.icon, + required this.page, + required this.code, + }); +} + +class ActivityPage { + static const indexHome = 0; + static const pageHome = ActivityPageItem( + icon: Icon(Ionicons.home_outline), + page: PageHome(), + code: 'page_home', + ); + + static const indexDiscoveries = 1; + static const pageDiscoveries = ActivityPageItem( + icon: Icon(Ionicons.star_outline), + page: PageDiscoveries(), + code: 'page_discoveries', + ); + + static const indexStatistics = 2; + static const pageStatistics = ActivityPageItem( + icon: Icon(Ionicons.bar_chart_outline), + page: PageStatistics(), + code: 'page_statistics', + ); + + static Map<int, ActivityPageItem> items = { + indexHome: pageHome, + indexDiscoveries: pageDiscoveries, + indexStatistics: pageStatistics, + }; + + static bool isIndexAllowed(int pageIndex) { + return items.keys.contains(pageIndex); + } + + static ActivityPageItem getPageItem(int pageIndex) { + return items[pageIndex] ?? pageHome; + } + + static Widget getPageWidget(int pageIndex) { + return items[pageIndex]?.page ?? pageHome.page; + } + + static int itemsCount = ActivityPage.items.length; +} diff --git a/lib/config/default_settings.dart b/lib/config/default_global_settings.dart similarity index 94% rename from lib/config/default_settings.dart rename to lib/config/default_global_settings.dart index 525784f01d4966e604ee67e5446e81fe5c778472..0565d15a1afbf20693e921b52dab4327e96c70ca 100644 --- a/lib/config/default_settings.dart +++ b/lib/config/default_global_settings.dart @@ -1,4 +1,4 @@ -class DefaultSettings { +class DefaultGlobalSettings { static const List<int> allowedDaysCountValues = [ 7, 14, diff --git a/lib/config/screen.dart b/lib/config/screen.dart new file mode 100644 index 0000000000000000000000000000000000000000..2846969b1474a42a8c49892c528d2402134ee880 --- /dev/null +++ b/lib/config/screen.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:unicons/unicons.dart'; + +import 'package:scrobbles/ui/screens/about.dart'; +import 'package:scrobbles/ui/screens/activity.dart'; +import 'package:scrobbles/ui/screens/settings.dart'; + +class ScreenItem { + final Icon icon; + final Widget screen; + final bool displayBottomNavBar; + + const ScreenItem({ + required this.icon, + required this.screen, + required this.displayBottomNavBar, + }); +} + +class Screen { + static const indexActivity = 0; + static const screenActivity = ScreenItem( + icon: Icon(UniconsLine.home), + screen: ScreenActivity(), + displayBottomNavBar: true, + ); + + static const indexSettings = 1; + static const screenSettings = ScreenItem( + icon: Icon(UniconsLine.setting), + screen: ScreenSettings(), + displayBottomNavBar: false, + ); + + static const indexAbout = 2; + static const screenAbout = ScreenItem( + icon: Icon(UniconsLine.info_circle), + screen: ScreenAbout(), + displayBottomNavBar: false, + ); + + static Map<int, ScreenItem> items = { + indexActivity: screenActivity, + indexSettings: screenSettings, + indexAbout: screenAbout, + }; + + static bool isIndexAllowed(int screenIndex) { + return items.keys.contains(screenIndex); + } + + static Widget getWidget(int screenIndex) { + return items[screenIndex]?.screen ?? screenActivity.screen; + } + + static bool displayBottomNavBar(int screenIndex) { + return items[screenIndex]?.displayBottomNavBar ?? screenActivity.displayBottomNavBar; + } + + static int itemsCount = Screen.items.length; +} diff --git a/lib/config/theme.dart b/lib/config/theme.dart index be390348c7868e7c63387df13e13c46de43f8a23..138460e58f89bc93afb0899d6cfe8f8e662a466f 100644 --- a/lib/config/theme.dart +++ b/lib/config/theme.dart @@ -39,11 +39,9 @@ final ColorScheme lightColorScheme = ColorScheme.light( secondary: primarySwatch.shade500, onSecondary: Colors.white, error: errorColor, - background: textSwatch.shade200, - onBackground: textSwatch.shade500, onSurface: textSwatch.shade500, surface: textSwatch.shade50, - surfaceVariant: Colors.white, + surfaceContainerHighest: Colors.white, shadow: textSwatch.shade900.withOpacity(.1), ); @@ -52,11 +50,9 @@ final ColorScheme darkColorScheme = ColorScheme.dark( secondary: primarySwatch.shade500, onSecondary: Colors.white, error: errorColor, - background: const Color(0xFF171724), - onBackground: textSwatch.shade400, onSurface: textSwatch.shade300, surface: const Color(0xFF262630), - surfaceVariant: const Color(0xFF282832), + surfaceContainerHighest: const Color(0xFF282832), shadow: textSwatch.shade900.withOpacity(.2), ); diff --git a/lib/cubit/activity_cubit.dart b/lib/cubit/activity_cubit.dart new file mode 100644 index 0000000000000000000000000000000000000000..55bfee6057c3d5261e493a34d1aab22cf39255e9 --- /dev/null +++ b/lib/cubit/activity_cubit.dart @@ -0,0 +1,60 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:scrobbles/cubit/data_counts_by_day_cubit.dart'; +import 'package:scrobbles/cubit/data_counts_by_hour_cubit.dart'; +import 'package:scrobbles/cubit/data_discoveries_cubit.dart'; +import 'package:scrobbles/cubit/data_heatmap_cubit.dart'; +import 'package:scrobbles/cubit/data_new_artists_cubit.dart'; +import 'package:scrobbles/cubit/data_new_tracks_cubit.dart'; +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/models/activity/activity.dart'; + +part 'activity_state.dart'; + +class ActivityCubit extends HydratedCubit<ActivityState> { + ActivityCubit() + : super(ActivityState( + currentActivity: Activity.createNull(), + )); + + void updateState(Activity activity) { + emit(ActivityState( + currentActivity: activity, + )); + } + + void refresh(BuildContext context) { + BlocProvider.of<DataCountsByDayCubit>(context).refresh(context); + BlocProvider.of<DataCountsByHourCubit>(context).refresh(context); + BlocProvider.of<DataDiscoveriesCubit>(context).refresh(context); + BlocProvider.of<DataHeatmapCubit>(context).refresh(context); + BlocProvider.of<DataNewArtistsCubit>(context).refresh(context); + BlocProvider.of<DataNewTracksCubit>(context).refresh(context); + BlocProvider.of<DataStatisticsGlobalCubit>(context).refresh(context); + BlocProvider.of<DataStatisticsRecentCubit>(context).refresh(context); + BlocProvider.of<DataTimelineCubit>(context).refresh(context); + BlocProvider.of<DataTopArtistsCubit>(context).refresh(context); + } + + @override + ActivityState? fromJson(Map<String, dynamic> json) { + final Activity currentActivity = json['currentActivity'] as Activity; + + return ActivityState( + currentActivity: currentActivity, + ); + } + + @override + Map<String, dynamic>? toJson(ActivityState state) { + return <String, dynamic>{ + 'currentActivity': state.currentActivity.toJson(), + }; + } +} diff --git a/lib/cubit/activity_state.dart b/lib/cubit/activity_state.dart new file mode 100644 index 0000000000000000000000000000000000000000..887b45e4255fd7de1cc7744569d82a38a66602f2 --- /dev/null +++ b/lib/cubit/activity_state.dart @@ -0,0 +1,15 @@ +part of 'activity_cubit.dart'; + +@immutable +class ActivityState extends Equatable { + const ActivityState({ + required this.currentActivity, + }); + + final Activity currentActivity; + + @override + List<dynamic> get props => <dynamic>[ + currentActivity, + ]; +} diff --git a/lib/cubit/bottom_nav_cubit.dart b/lib/cubit/bottom_nav_cubit.dart deleted file mode 100644 index 1592a438fa2446d66db1a35519bd675d7f2dd146..0000000000000000000000000000000000000000 --- a/lib/cubit/bottom_nav_cubit.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:hydrated_bloc/hydrated_bloc.dart'; - -class BottomNavCubit extends HydratedCubit<int> { - BottomNavCubit() : super(0); - - int pagesCount = 4; - - void updateIndex(int index) { - if (isIndexAllowed(index)) { - emit(index); - } else { - goToHomePage(); - } - } - - bool isIndexAllowed(int index) { - return (index >= 0) && (index < pagesCount); - } - - void goToHomePage() => emit(0); - - @override - int? fromJson(Map<String, dynamic> json) { - return 0; - } - - @override - Map<String, dynamic>? toJson(int state) { - return <String, int>{'pageIndex': state}; - } -} diff --git a/lib/cubit/data_counts_by_day_cubit.dart b/lib/cubit/data_counts_by_day_cubit.dart index ab40a21ce816fce4154bfb8b4ace9dc56cfbeff8..c2746cfc39f42e466b5fe97170362980ff271e33 100644 --- a/lib/cubit/data_counts_by_day_cubit.dart +++ b/lib/cubit/data_counts_by_day_cubit.dart @@ -1,38 +1,76 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; -import 'package:scrobbles/models/counts_by_day.dart'; +import 'package:scrobbles/cubit/settings_global_cubit.dart'; +import 'package:scrobbles/models/data/counts_by_day.dart'; +import 'package:scrobbles/network/scrobbles.dart'; part 'data_counts_by_day_state.dart'; class DataCountsByDayCubit extends HydratedCubit<DataCountsByDayState> { - DataCountsByDayCubit() : super(const DataCountsByDayState()); + DataCountsByDayCubit() + : super(const DataCountsByDayState( + countsByDay: null, + status: '', + )); - void getData(DataCountsByDayState state) { - emit(state); + void setLoading() { + emit(DataCountsByDayState( + countsByDay: state.countsByDay, + status: 'loading', + )); } - CountsByDayData? getValue() { - return state.countsByDay; + void setValue(CountsByDayData? countsByDay) { + emit(DataCountsByDayState( + countsByDay: countsByDay, + status: '', + )); + } + + void setFailed() { + emit(DataCountsByDayState( + countsByDay: state.countsByDay, + status: 'failed', + )); + } + + void setLoaded() { + emit(DataCountsByDayState( + countsByDay: state.countsByDay, + status: '', + )); } void update(CountsByDayData? countsByDay) { if ((countsByDay != null) && (state.countsByDay.toString() != countsByDay.toString())) { setValue(countsByDay); + } else { + setLoaded(); } } - void setValue(CountsByDayData? countsByDay) { - emit(DataCountsByDayState( - countsByDay: countsByDay, - )); + void refresh(BuildContext context) async { + setLoading(); + + GlobalSettingsCubit settings = BlocProvider.of<GlobalSettingsCubit>(context); + final int daysCount = settings.getDistributionDaysCount(); + + final CountsByDayData? data = await ScrobblesApi.fetchCountsByDay(daysCount); + if (data != null) { + update(data); + } else { + setFailed(); + } } @override DataCountsByDayState? fromJson(Map<String, dynamic> json) { return DataCountsByDayState( countsByDay: CountsByDayData.fromJson(json['countsByDay']), + status: '', ); } @@ -40,6 +78,7 @@ class DataCountsByDayCubit extends HydratedCubit<DataCountsByDayState> { Map<String, Object?>? toJson(DataCountsByDayState state) { return <String, Object?>{ 'countsByDay': state.countsByDay?.toJson(), + 'status': state.status, }; } } diff --git a/lib/cubit/data_counts_by_day_state.dart b/lib/cubit/data_counts_by_day_state.dart index 724bd73411f6c5f22caeab39a101ab92af376713..13b78830c6f3cd59098e1cd412270945e9343a8c 100644 --- a/lib/cubit/data_counts_by_day_state.dart +++ b/lib/cubit/data_counts_by_day_state.dart @@ -3,13 +3,16 @@ part of 'data_counts_by_day_cubit.dart'; @immutable class DataCountsByDayState extends Equatable { const DataCountsByDayState({ - this.countsByDay, + required this.countsByDay, + required this.status, }); final CountsByDayData? countsByDay; + final String status; @override List<Object?> get props => <Object?>[ countsByDay, + status, ]; } diff --git a/lib/cubit/data_counts_by_hour_cubit.dart b/lib/cubit/data_counts_by_hour_cubit.dart index 83cd2d3b5b1e224578e295ca5ec94319364f4690..e4c477092a48cc0862e629728d7fa44dd39fac0c 100644 --- a/lib/cubit/data_counts_by_hour_cubit.dart +++ b/lib/cubit/data_counts_by_hour_cubit.dart @@ -1,38 +1,76 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; -import 'package:scrobbles/models/counts_by_hour.dart'; +import 'package:scrobbles/cubit/settings_global_cubit.dart'; +import 'package:scrobbles/models/data/counts_by_hour.dart'; +import 'package:scrobbles/network/scrobbles.dart'; part 'data_counts_by_hour_state.dart'; class DataCountsByHourCubit extends HydratedCubit<DataCountsByHourState> { - DataCountsByHourCubit() : super(const DataCountsByHourState()); + DataCountsByHourCubit() + : super(const DataCountsByHourState( + countsByHour: null, + status: '', + )); - void getData(DataCountsByHourState state) { - emit(state); + void setLoading() { + emit(DataCountsByHourState( + countsByHour: state.countsByHour, + status: 'loading', + )); } - CountsByHourData? getValue() { - return state.countsByHour; + void setValue(CountsByHourData? countsByHour) { + emit(DataCountsByHourState( + countsByHour: countsByHour, + status: '', + )); + } + + void setFailed() { + emit(DataCountsByHourState( + countsByHour: state.countsByHour, + status: 'failed', + )); + } + + void setLoaded() { + emit(DataCountsByHourState( + countsByHour: state.countsByHour, + status: '', + )); } void update(CountsByHourData? countsByHour) { if ((countsByHour != null) && (state.countsByHour.toString() != countsByHour.toString())) { setValue(countsByHour); + } else { + setLoaded(); } } - void setValue(CountsByHourData? countsByHour) { - emit(DataCountsByHourState( - countsByHour: countsByHour, - )); + void refresh(BuildContext context) async { + setLoading(); + + GlobalSettingsCubit settings = BlocProvider.of<GlobalSettingsCubit>(context); + final int daysCount = settings.getDistributionDaysCount(); + + final CountsByHourData? data = await ScrobblesApi.fetchCountsByHour(daysCount); + if (data != null) { + update(data); + } else { + setFailed(); + } } @override DataCountsByHourState? fromJson(Map<String, dynamic> json) { return DataCountsByHourState( countsByHour: CountsByHourData.fromJson(json['countsByHour']), + status: '', ); } @@ -40,6 +78,7 @@ class DataCountsByHourCubit extends HydratedCubit<DataCountsByHourState> { Map<String, Object?>? toJson(DataCountsByHourState state) { return <String, Object?>{ 'countsByHour': state.countsByHour?.toJson(), + 'status': state.status, }; } } diff --git a/lib/cubit/data_counts_by_hour_state.dart b/lib/cubit/data_counts_by_hour_state.dart index 3fc9aef713e632d27dcb8b002f314cbd62ba578c..c68949ea6261996367d489697d48b537a18b2e51 100644 --- a/lib/cubit/data_counts_by_hour_state.dart +++ b/lib/cubit/data_counts_by_hour_state.dart @@ -3,13 +3,16 @@ part of 'data_counts_by_hour_cubit.dart'; @immutable class DataCountsByHourState extends Equatable { const DataCountsByHourState({ - this.countsByHour, + required this.countsByHour, + required this.status, }); final CountsByHourData? countsByHour; + final String status; @override List<Object?> get props => <Object?>[ countsByHour, + status, ]; } diff --git a/lib/cubit/data_discoveries_cubit.dart b/lib/cubit/data_discoveries_cubit.dart index dabda77cf13f82954889ad4f5de186d5bd96de09..e3acd5392647f218fc5cbcc2eb1f5dcfdd671cdd 100644 --- a/lib/cubit/data_discoveries_cubit.dart +++ b/lib/cubit/data_discoveries_cubit.dart @@ -1,38 +1,76 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; -import 'package:scrobbles/models/discoveries.dart'; +import 'package:scrobbles/cubit/settings_global_cubit.dart'; +import 'package:scrobbles/models/data/discoveries.dart'; +import 'package:scrobbles/network/scrobbles.dart'; part 'data_discoveries_state.dart'; class DataDiscoveriesCubit extends HydratedCubit<DataDiscoveriesState> { - DataDiscoveriesCubit() : super(const DataDiscoveriesState()); + DataDiscoveriesCubit() + : super(const DataDiscoveriesState( + discoveries: null, + status: '', + )); - void getData(DataDiscoveriesState state) { - emit(state); + void setLoading() { + emit(DataDiscoveriesState( + discoveries: state.discoveries, + status: 'loading', + )); } - DiscoveriesData? getValue() { - return state.discoveries; + void setValue(DiscoveriesData? discoveries) { + emit(DataDiscoveriesState( + discoveries: discoveries, + status: '', + )); + } + + void setFailed() { + emit(DataDiscoveriesState( + discoveries: state.discoveries, + status: 'failed', + )); + } + + void setLoaded() { + emit(DataDiscoveriesState( + discoveries: state.discoveries, + status: '', + )); } void update(DiscoveriesData? discoveries) { if ((discoveries != null) && (state.discoveries.toString() != discoveries.toString())) { setValue(discoveries); + } else { + setLoaded(); } } - void setValue(DiscoveriesData? discoveries) { - emit(DataDiscoveriesState( - discoveries: discoveries, - )); + void refresh(BuildContext context) async { + setLoading(); + + GlobalSettingsCubit settings = BlocProvider.of<GlobalSettingsCubit>(context); + final int daysCount = settings.getDiscoveriesDaysCount(); + + final DiscoveriesData? data = await ScrobblesApi.fetchDiscoveries(daysCount); + if (data != null) { + update(data); + } else { + setFailed(); + } } @override DataDiscoveriesState? fromJson(Map<String, dynamic> json) { return DataDiscoveriesState( discoveries: DiscoveriesData.fromJson(json['discoveries']), + status: '', ); } @@ -40,6 +78,7 @@ class DataDiscoveriesCubit extends HydratedCubit<DataDiscoveriesState> { Map<String, Object?>? toJson(DataDiscoveriesState state) { return <String, Object?>{ 'discoveries': state.discoveries?.toJson(), + 'status': state.status, }; } } diff --git a/lib/cubit/data_discoveries_state.dart b/lib/cubit/data_discoveries_state.dart index 1d43883563ef73f81d0ce636c6977b5916d837e8..8bfa33955dfa293bb837329741f18917a49e9dcd 100644 --- a/lib/cubit/data_discoveries_state.dart +++ b/lib/cubit/data_discoveries_state.dart @@ -3,13 +3,16 @@ part of 'data_discoveries_cubit.dart'; @immutable class DataDiscoveriesState extends Equatable { const DataDiscoveriesState({ - this.discoveries, + required this.discoveries, + required this.status, }); final DiscoveriesData? discoveries; + final String status; @override List<Object?> get props => <Object?>[ discoveries, + status, ]; } diff --git a/lib/cubit/data_heatmap_cubit.dart b/lib/cubit/data_heatmap_cubit.dart index 7cd9192e8d0842116c4cde4d124b3d7e87c4318c..dc7e4648d8cf5143d516caae53a96f106f8f0178 100644 --- a/lib/cubit/data_heatmap_cubit.dart +++ b/lib/cubit/data_heatmap_cubit.dart @@ -1,34 +1,76 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; -import 'package:scrobbles/models/heatmap.dart'; +import 'package:scrobbles/cubit/settings_global_cubit.dart'; +import 'package:scrobbles/models/data/heatmap.dart'; +import 'package:scrobbles/network/scrobbles.dart'; part 'data_heatmap_state.dart'; class DataHeatmapCubit extends HydratedCubit<DataHeatmapState> { - DataHeatmapCubit() : super(const DataHeatmapState()); + DataHeatmapCubit() + : super(const DataHeatmapState( + heatmap: null, + status: '', + )); - void getData(DataHeatmapState state) { - emit(state); + void setLoading() { + emit(DataHeatmapState( + heatmap: state.heatmap, + status: 'loading', + )); + } + + void setValue(HeatmapData? heatmapData) { + emit(DataHeatmapState( + heatmap: heatmapData, + status: '', + )); + } + + void setFailed() { + emit(DataHeatmapState( + heatmap: state.heatmap, + status: 'failed', + )); + } + + void setLoaded() { + emit(DataHeatmapState( + heatmap: state.heatmap, + status: '', + )); } void update(HeatmapData? heatmapData) { if ((heatmapData != null) && (state.heatmap.toString() != heatmapData.toString())) { setValue(heatmapData); + } else { + setLoaded(); } } - void setValue(HeatmapData? heatmapData) { - emit(DataHeatmapState( - heatmap: heatmapData, - )); + void refresh(BuildContext context) async { + setLoading(); + + GlobalSettingsCubit settings = BlocProvider.of<GlobalSettingsCubit>(context); + final int daysCount = settings.getDistributionDaysCount(); + + final HeatmapData? data = await ScrobblesApi.fetchHeatmap(daysCount); + if (data != null) { + update(data); + } else { + setFailed(); + } } @override DataHeatmapState? fromJson(Map<String, dynamic> json) { return DataHeatmapState( heatmap: HeatmapData.fromJson(json['heatmap']), + status: '', ); } @@ -36,6 +78,7 @@ class DataHeatmapCubit extends HydratedCubit<DataHeatmapState> { Map<String, Object?>? toJson(DataHeatmapState state) { return <String, Object?>{ 'heatmap': state.heatmap?.toJson(), + 'status': state.status, }; } } diff --git a/lib/cubit/data_heatmap_state.dart b/lib/cubit/data_heatmap_state.dart index 0814a3bd507987f5a7bd0c5a3178a9c767ed4bc2..a8d21e3c21ccb0c4567d41e2d5665f0e2c180107 100644 --- a/lib/cubit/data_heatmap_state.dart +++ b/lib/cubit/data_heatmap_state.dart @@ -3,13 +3,16 @@ part of 'data_heatmap_cubit.dart'; @immutable class DataHeatmapState extends Equatable { const DataHeatmapState({ - this.heatmap, + required this.heatmap, + required this.status, }); final HeatmapData? heatmap; + final String status; @override List<Object?> get props => <Object?>[ heatmap, + status, ]; } diff --git a/lib/cubit/data_new_artists_cubit.dart b/lib/cubit/data_new_artists_cubit.dart index 6ef782b5dfa4b68124f4627951df2b82d6feae44..c858321c19daae38884112a9be58ae54af80b9ea 100644 --- a/lib/cubit/data_new_artists_cubit.dart +++ b/lib/cubit/data_new_artists_cubit.dart @@ -1,38 +1,76 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; -import 'package:scrobbles/models/new_artists.dart'; +import 'package:scrobbles/cubit/settings_global_cubit.dart'; +import 'package:scrobbles/models/data/new_artists.dart'; +import 'package:scrobbles/network/scrobbles.dart'; part 'data_new_artists_state.dart'; class DataNewArtistsCubit extends HydratedCubit<DataNewArtistsState> { - DataNewArtistsCubit() : super(const DataNewArtistsState()); + DataNewArtistsCubit() + : super(const DataNewArtistsState( + newArtists: null, + status: '', + )); - void getData(DataNewArtistsState state) { - emit(state); + void setLoading() { + emit(DataNewArtistsState( + newArtists: state.newArtists, + status: 'loading', + )); } - NewArtistsData? getValue() { - return state.newArtists; + void setValue(NewArtistsData? newArtists) { + emit(DataNewArtistsState( + newArtists: newArtists, + status: '', + )); + } + + void setFailed() { + emit(DataNewArtistsState( + newArtists: state.newArtists, + status: 'failed', + )); + } + + void setLoaded() { + emit(DataNewArtistsState( + newArtists: state.newArtists, + status: '', + )); } void update(NewArtistsData? newArtists) { if ((newArtists != null) && (state.newArtists.toString() != newArtists.toString())) { setValue(newArtists); + } else { + setLoaded(); } } - void setValue(NewArtistsData? newArtists) { - emit(DataNewArtistsState( - newArtists: newArtists, - )); + void refresh(BuildContext context) async { + setLoading(); + + GlobalSettingsCubit settings = BlocProvider.of<GlobalSettingsCubit>(context); + final int count = settings.getNewArtistsCount(); + + final NewArtistsData? data = await ScrobblesApi.fetchNewArtists(count); + if (data != null) { + update(data); + } else { + setFailed(); + } } @override DataNewArtistsState? fromJson(Map<String, dynamic> json) { return DataNewArtistsState( newArtists: NewArtistsData.fromJson(json['newArtists']), + status: '', ); } @@ -40,6 +78,7 @@ class DataNewArtistsCubit extends HydratedCubit<DataNewArtistsState> { Map<String, Object?>? toJson(DataNewArtistsState state) { return <String, Object?>{ 'newArtists': state.newArtists?.toJson(), + 'status': state.status, }; } } diff --git a/lib/cubit/data_new_artists_state.dart b/lib/cubit/data_new_artists_state.dart index eb6678c624e66563b534b788f959532d545ecd00..8a9fe7ab023eb5c4412bf6ae1fcc8bfea8aa0ead 100644 --- a/lib/cubit/data_new_artists_state.dart +++ b/lib/cubit/data_new_artists_state.dart @@ -3,13 +3,16 @@ part of 'data_new_artists_cubit.dart'; @immutable class DataNewArtistsState extends Equatable { const DataNewArtistsState({ - this.newArtists, + required this.newArtists, + required this.status, }); final NewArtistsData? newArtists; + final String status; @override List<Object?> get props => <Object?>[ newArtists, + status, ]; } diff --git a/lib/cubit/data_new_tracks_cubit.dart b/lib/cubit/data_new_tracks_cubit.dart index 10923886b3f7ad976cf67a353e9855326c42bf6c..d1c1e76925fb3ba836baa9e79e9455d105dc5240 100644 --- a/lib/cubit/data_new_tracks_cubit.dart +++ b/lib/cubit/data_new_tracks_cubit.dart @@ -1,38 +1,76 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; -import 'package:scrobbles/models/new_tracks.dart'; +import 'package:scrobbles/cubit/settings_global_cubit.dart'; +import 'package:scrobbles/models/data/new_tracks.dart'; +import 'package:scrobbles/network/scrobbles.dart'; part 'data_new_tracks_state.dart'; class DataNewTracksCubit extends HydratedCubit<DataNewTracksState> { - DataNewTracksCubit() : super(const DataNewTracksState()); + DataNewTracksCubit() + : super(const DataNewTracksState( + newTracks: null, + status: '', + )); - void getData(DataNewTracksState state) { - emit(state); + void setLoading() { + emit(DataNewTracksState( + newTracks: state.newTracks, + status: 'loading', + )); } - NewTracksData? getValue() { - return state.newTracks; + void setValue(NewTracksData? newTracks) { + emit(DataNewTracksState( + newTracks: newTracks, + status: '', + )); + } + + void setFailed() { + emit(DataNewTracksState( + newTracks: state.newTracks, + status: 'failed', + )); + } + + void setLoaded() { + emit(DataNewTracksState( + newTracks: state.newTracks, + status: '', + )); } void update(NewTracksData? newTracks) { if ((newTracks != null) && (state.newTracks.toString() != newTracks.toString())) { setValue(newTracks); + } else { + setLoaded(); } } - void setValue(NewTracksData? newTracks) { - emit(DataNewTracksState( - newTracks: newTracks, - )); + void refresh(BuildContext context) async { + setLoading(); + + GlobalSettingsCubit settings = BlocProvider.of<GlobalSettingsCubit>(context); + final int count = settings.getNewTracksCount(); + + final NewTracksData? data = await ScrobblesApi.fetchNewTracks(count); + if (data != null) { + update(data); + } else { + setFailed(); + } } @override DataNewTracksState? fromJson(Map<String, dynamic> json) { return DataNewTracksState( newTracks: NewTracksData.fromJson(json['newTracks']), + status: '', ); } @@ -40,6 +78,7 @@ class DataNewTracksCubit extends HydratedCubit<DataNewTracksState> { Map<String, Object?>? toJson(DataNewTracksState state) { return <String, Object?>{ 'newTracks': state.newTracks?.toJson(), + 'status': state.status, }; } } diff --git a/lib/cubit/data_new_tracks_state.dart b/lib/cubit/data_new_tracks_state.dart index c23abe47f1c136b5a300957f6a2abdef76fa6b3b..d63798494e1c89ecebecffc6ae29f41e6cd50d3a 100644 --- a/lib/cubit/data_new_tracks_state.dart +++ b/lib/cubit/data_new_tracks_state.dart @@ -3,13 +3,16 @@ part of 'data_new_tracks_cubit.dart'; @immutable class DataNewTracksState extends Equatable { const DataNewTracksState({ - this.newTracks, + required this.newTracks, + required this.status, }); final NewTracksData? newTracks; + final String status; @override List<Object?> get props => <Object?>[ newTracks, + status, ]; } diff --git a/lib/cubit/data_statistics_global_cubit.dart b/lib/cubit/data_statistics_global_cubit.dart index 49e9292f1415184cef543bedb6d2dda1fa1314b1..e441c4ee26f05a23a743618033ce529e8b60a209 100644 --- a/lib/cubit/data_statistics_global_cubit.dart +++ b/lib/cubit/data_statistics_global_cubit.dart @@ -2,38 +2,71 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; -import 'package:scrobbles/models/statistics_global.dart'; +import 'package:scrobbles/models/data/statistics_global.dart'; +import 'package:scrobbles/network/scrobbles.dart'; part 'data_statistics_global_state.dart'; class DataStatisticsGlobalCubit extends HydratedCubit<DataStatisticsGlobalState> { - DataStatisticsGlobalCubit() : super(const DataStatisticsGlobalState()); + DataStatisticsGlobalCubit() + : super(const DataStatisticsGlobalState( + statisticsGlobal: null, + status: '', + )); - void getData(DataStatisticsGlobalState state) { - emit(state); + void setLoading() { + emit(DataStatisticsGlobalState( + statisticsGlobal: state.statisticsGlobal, + status: 'loading', + )); + } + + void setValue(StatisticsGlobalData? statisticsGlobal) { + emit(DataStatisticsGlobalState( + statisticsGlobal: statisticsGlobal, + status: '', + )); + } + + void setFailed() { + emit(DataStatisticsGlobalState( + statisticsGlobal: state.statisticsGlobal, + status: 'failed', + )); } - StatisticsGlobalData? getValue() { - return state.statisticsGlobal; + void setLoaded() { + emit(DataStatisticsGlobalState( + statisticsGlobal: state.statisticsGlobal, + status: '', + )); } void update(StatisticsGlobalData? statisticsGlobal) { if ((statisticsGlobal != null) && (state.statisticsGlobal.toString() != statisticsGlobal.toString())) { setValue(statisticsGlobal); + } else { + setLoaded(); } } - void setValue(StatisticsGlobalData? statisticsGlobal) { - emit(DataStatisticsGlobalState( - statisticsGlobal: statisticsGlobal, - )); + void refresh(BuildContext context) async { + setLoading(); + + final StatisticsGlobalData? data = await ScrobblesApi.fetchGlobalStatistics(); + if (data != null) { + update(data); + } else { + setFailed(); + } } @override DataStatisticsGlobalState? fromJson(Map<String, dynamic> json) { return DataStatisticsGlobalState( statisticsGlobal: StatisticsGlobalData.fromJson(json['statisticsGlobal']), + status: '', ); } @@ -41,6 +74,7 @@ class DataStatisticsGlobalCubit extends HydratedCubit<DataStatisticsGlobalState> Map<String, Object?>? toJson(DataStatisticsGlobalState state) { return <String, Object?>{ 'statisticsGlobal': state.statisticsGlobal?.toJson(), + 'status': state.status, }; } } diff --git a/lib/cubit/data_statistics_global_state.dart b/lib/cubit/data_statistics_global_state.dart index 89ef0fd2a136d43ad291d44fc5899296ae1bd9e8..6d7041877c257929949bface50fe07b698969f3b 100644 --- a/lib/cubit/data_statistics_global_state.dart +++ b/lib/cubit/data_statistics_global_state.dart @@ -3,13 +3,16 @@ part of 'data_statistics_global_cubit.dart'; @immutable class DataStatisticsGlobalState extends Equatable { const DataStatisticsGlobalState({ - this.statisticsGlobal, + required this.statisticsGlobal, + required this.status, }); final StatisticsGlobalData? statisticsGlobal; + final String status; @override List<Object?> get props => <Object?>[ statisticsGlobal, + status, ]; } diff --git a/lib/cubit/data_statistics_recent_cubit.dart b/lib/cubit/data_statistics_recent_cubit.dart index da24b1358002067a457e79ee7d4df48a5e0e634c..c950f64b046f57e19295f5d33925c3280c12f93d 100644 --- a/lib/cubit/data_statistics_recent_cubit.dart +++ b/lib/cubit/data_statistics_recent_cubit.dart @@ -1,39 +1,77 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; -import 'package:scrobbles/models/statistics_recent.dart'; +import 'package:scrobbles/cubit/settings_global_cubit.dart'; +import 'package:scrobbles/models/data/statistics_recent.dart'; +import 'package:scrobbles/network/scrobbles.dart'; part 'data_statistics_recent_state.dart'; class DataStatisticsRecentCubit extends HydratedCubit<DataStatisticsRecentState> { - DataStatisticsRecentCubit() : super(const DataStatisticsRecentState()); + DataStatisticsRecentCubit() + : super(const DataStatisticsRecentState( + statisticsRecent: null, + status: '', + )); - void getData(DataStatisticsRecentState state) { - emit(state); + void setLoading() { + emit(DataStatisticsRecentState( + statisticsRecent: state.statisticsRecent, + status: 'loading', + )); } - StatisticsRecentData? getValue() { - return state.statisticsRecent; + void setValue(StatisticsRecentData? statisticsRecent) { + emit(DataStatisticsRecentState( + statisticsRecent: statisticsRecent, + status: '', + )); + } + + void setFailed() { + emit(DataStatisticsRecentState( + statisticsRecent: state.statisticsRecent, + status: 'failed', + )); + } + + void setLoaded() { + emit(DataStatisticsRecentState( + statisticsRecent: state.statisticsRecent, + status: '', + )); } void update(StatisticsRecentData? statisticsRecent) { if ((statisticsRecent != null) && (state.statisticsRecent.toString() != statisticsRecent.toString())) { setValue(statisticsRecent); + } else { + setLoaded(); } } - void setValue(StatisticsRecentData? statisticsRecent) { - emit(DataStatisticsRecentState( - statisticsRecent: statisticsRecent, - )); + void refresh(BuildContext context) async { + setLoading(); + + GlobalSettingsCubit settings = BlocProvider.of<GlobalSettingsCubit>(context); + final int daysCount = settings.getStatisticsRecentDaysCount(); + + final StatisticsRecentData? data = await ScrobblesApi.fetchRecentStatistics(daysCount); + if (data != null) { + update(data); + } else { + setFailed(); + } } @override DataStatisticsRecentState? fromJson(Map<String, dynamic> json) { return DataStatisticsRecentState( statisticsRecent: StatisticsRecentData.fromJson(json['statisticsRecent']), + status: '', ); } @@ -41,6 +79,7 @@ class DataStatisticsRecentCubit extends HydratedCubit<DataStatisticsRecentState> Map<String, Object?>? toJson(DataStatisticsRecentState state) { return <String, Object?>{ 'statisticsRecent': state.statisticsRecent?.toJson(), + 'status': state.status, }; } } diff --git a/lib/cubit/data_statistics_recent_state.dart b/lib/cubit/data_statistics_recent_state.dart index 1bf9d2f846d7159d1e26151420beac984d7b69e2..056cc9abe6c92fff4bba48e7f8f2ceff1f191539 100644 --- a/lib/cubit/data_statistics_recent_state.dart +++ b/lib/cubit/data_statistics_recent_state.dart @@ -3,13 +3,16 @@ part of 'data_statistics_recent_cubit.dart'; @immutable class DataStatisticsRecentState extends Equatable { const DataStatisticsRecentState({ - this.statisticsRecent, + required this.statisticsRecent, + required this.status, }); final StatisticsRecentData? statisticsRecent; + final String status; @override List<Object?> get props => <Object?>[ statisticsRecent, + status, ]; } diff --git a/lib/cubit/data_timeline_cubit.dart b/lib/cubit/data_timeline_cubit.dart index 692dfcbdec2bf4db214d78eba3c0686158d7cc1e..c82a0d686683be37292ff7c133682fcc3e6935cb 100644 --- a/lib/cubit/data_timeline_cubit.dart +++ b/lib/cubit/data_timeline_cubit.dart @@ -1,38 +1,76 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; -import 'package:scrobbles/models/timeline.dart'; +import 'package:scrobbles/cubit/settings_global_cubit.dart'; +import 'package:scrobbles/models/data/timeline.dart'; +import 'package:scrobbles/network/scrobbles.dart'; part 'data_timeline_state.dart'; class DataTimelineCubit extends HydratedCubit<DataTimelineState> { - DataTimelineCubit() : super(const DataTimelineState()); + DataTimelineCubit() + : super(const DataTimelineState( + timeline: null, + status: '', + )); - void getData(DataTimelineState state) { - emit(state); + void setLoading() { + emit(DataTimelineState( + timeline: state.timeline, + status: 'loading', + )); } - TimelineData? getValue() { - return state.timeline; + void setValue(TimelineData? timeline) { + emit(DataTimelineState( + timeline: timeline, + status: '', + )); + } + + void setFailed() { + emit(DataTimelineState( + timeline: state.timeline, + status: 'failed', + )); + } + + void setLoaded() { + emit(DataTimelineState( + timeline: state.timeline, + status: '', + )); } void update(TimelineData? timeline) { if ((timeline != null) && (state.timeline.toString() != timeline.toString())) { setValue(timeline); + } else { + setLoaded(); } } - void setValue(TimelineData? timeline) { - emit(DataTimelineState( - timeline: timeline, - )); + void refresh(BuildContext context) async { + setLoading(); + + GlobalSettingsCubit settings = BlocProvider.of<GlobalSettingsCubit>(context); + final int daysCount = settings.getTimelineDaysCount(); + + final TimelineData? data = await ScrobblesApi.fetchTimeline(daysCount); + if (data != null) { + update(data); + } else { + setFailed(); + } } @override DataTimelineState? fromJson(Map<String, dynamic> json) { return DataTimelineState( timeline: TimelineData.fromJson(json['timeline']), + status: '', ); } @@ -40,6 +78,7 @@ class DataTimelineCubit extends HydratedCubit<DataTimelineState> { Map<String, Object?>? toJson(DataTimelineState state) { return <String, Object?>{ 'timeline': state.timeline?.toJson(), + 'status': state.status, }; } } diff --git a/lib/cubit/data_timeline_state.dart b/lib/cubit/data_timeline_state.dart index b0531c799f3d0ec00ba4e0f1532830ba513f98c9..c3065dc7559587325321b21cc188ef978f235eaa 100644 --- a/lib/cubit/data_timeline_state.dart +++ b/lib/cubit/data_timeline_state.dart @@ -3,13 +3,16 @@ part of 'data_timeline_cubit.dart'; @immutable class DataTimelineState extends Equatable { const DataTimelineState({ - this.timeline, + required this.timeline, + required this.status, }); final TimelineData? timeline; + final String status; @override List<Object?> get props => <Object?>[ timeline, + status, ]; } diff --git a/lib/cubit/data_top_artists_cubit.dart b/lib/cubit/data_top_artists_cubit.dart index 98fe17a6bee3a4e5a32887fc84f885011dc786c2..c8850eaee028a35feded2e965af7aaa404d865b3 100644 --- a/lib/cubit/data_top_artists_cubit.dart +++ b/lib/cubit/data_top_artists_cubit.dart @@ -1,38 +1,76 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; -import 'package:scrobbles/models/topartists.dart'; +import 'package:scrobbles/cubit/settings_global_cubit.dart'; +import 'package:scrobbles/models/data/topartists.dart'; +import 'package:scrobbles/network/scrobbles.dart'; part 'data_top_artists_state.dart'; class DataTopArtistsCubit extends HydratedCubit<DataTopArtistsState> { - DataTopArtistsCubit() : super(const DataTopArtistsState()); + DataTopArtistsCubit() + : super(const DataTopArtistsState( + topArtists: null, + status: '', + )); - void getData(DataTopArtistsState state) { - emit(state); + void setLoading() { + emit(DataTopArtistsState( + topArtists: state.topArtists, + status: 'loading', + )); } - TopArtistsData? getValue(key) { - return state.topArtists; + void setValue(TopArtistsData? topArtists) { + emit(DataTopArtistsState( + topArtists: topArtists, + status: '', + )); + } + + void setFailed() { + emit(DataTopArtistsState( + topArtists: state.topArtists, + status: 'failed', + )); + } + + void setLoaded() { + emit(DataTopArtistsState( + topArtists: state.topArtists, + status: '', + )); } void update(TopArtistsData? topArtists) { if ((topArtists != null) && (state.topArtists.toString() != topArtists.toString())) { setValue(topArtists); + } else { + setLoaded(); } } - void setValue(TopArtistsData? topArtists) { - emit(DataTopArtistsState( - topArtists: topArtists, - )); + void refresh(BuildContext context) async { + setLoading(); + + GlobalSettingsCubit settings = BlocProvider.of<GlobalSettingsCubit>(context); + final int daysCount = settings.getTopArtistsDaysCount(); + + final TopArtistsData? data = await ScrobblesApi.fetchTopArtists(daysCount); + if (data != null) { + if (context.mounted) update(data); + } else { + if (context.mounted) setFailed(); + } } @override DataTopArtistsState? fromJson(Map<String, dynamic> json) { return DataTopArtistsState( topArtists: TopArtistsData.fromJson(json['topArtists']), + status: '', ); } @@ -40,6 +78,7 @@ class DataTopArtistsCubit extends HydratedCubit<DataTopArtistsState> { Map<String, Object?>? toJson(DataTopArtistsState state) { return <String, Object?>{ 'topArtists': state.topArtists?.toJson(), + 'status': state.status, }; } } diff --git a/lib/cubit/data_top_artists_state.dart b/lib/cubit/data_top_artists_state.dart index e6d14c01e33aa04f23dcb9bb0bb89342f919767a..79d95c06d13addbe23604d1c0c29e6b9843efd63 100644 --- a/lib/cubit/data_top_artists_state.dart +++ b/lib/cubit/data_top_artists_state.dart @@ -3,13 +3,16 @@ part of 'data_top_artists_cubit.dart'; @immutable class DataTopArtistsState extends Equatable { const DataTopArtistsState({ - this.topArtists, + required this.topArtists, + required this.status, }); final TopArtistsData? topArtists; + final String status; @override List<Object?> get props => <Object?>[ topArtists, + status, ]; } diff --git a/lib/cubit/nav_cubit_pages.dart b/lib/cubit/nav_cubit_pages.dart new file mode 100644 index 0000000000000000000000000000000000000000..4caca488df3fdd01091e7c7e967a5152010f2b33 --- /dev/null +++ b/lib/cubit/nav_cubit_pages.dart @@ -0,0 +1,29 @@ +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:scrobbles/config/activity_page.dart'; + +class NavCubitPage extends HydratedCubit<int> { + NavCubitPage() : super(0); + + void updateIndex(int index) { + if (ActivityPage.isIndexAllowed(index)) { + emit(index); + } else { + emit(ActivityPage.indexHome); + } + } + + void goToHomePage() { + emit(ActivityPage.indexHome); + } + + @override + int fromJson(Map<String, dynamic> json) { + return ActivityPage.indexHome; + } + + @override + Map<String, dynamic>? toJson(int state) { + return <String, int>{'pageIndex': state}; + } +} diff --git a/lib/cubit/nav_cubit_screens.dart b/lib/cubit/nav_cubit_screens.dart new file mode 100644 index 0000000000000000000000000000000000000000..43a941f44d1e53e12d533ac957a5d2eb1efe1b78 --- /dev/null +++ b/lib/cubit/nav_cubit_screens.dart @@ -0,0 +1,37 @@ +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:scrobbles/config/screen.dart'; + +class NavCubitScreen extends HydratedCubit<int> { + NavCubitScreen() : super(0); + + void updateIndex(int index) { + if (Screen.isIndexAllowed(index)) { + emit(index); + } else { + goToActivityPage(); + } + } + + void goToActivityPage() { + emit(Screen.indexActivity); + } + + void goToSettingsPage() { + emit(Screen.indexSettings); + } + + void goToAboutPage() { + emit(Screen.indexAbout); + } + + @override + int fromJson(Map<String, dynamic> json) { + return Screen.indexActivity; + } + + @override + Map<String, dynamic>? toJson(int state) { + return <String, int>{'screenIndex': state}; + } +} diff --git a/lib/cubit/settings_cubit.dart b/lib/cubit/settings_global_cubit.dart similarity index 60% rename from lib/cubit/settings_cubit.dart rename to lib/cubit/settings_global_cubit.dart index 6c86444f3d23ddd6bf3e12a466b36f496dcc26aa..f45d5dad21b87d8b6fda9fff4a90ac66653158ee 100644 --- a/lib/cubit/settings_cubit.dart +++ b/lib/cubit/settings_global_cubit.dart @@ -2,12 +2,36 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; -import 'package:scrobbles/config/default_settings.dart'; +import 'package:scrobbles/config/default_global_settings.dart'; -part 'settings_state.dart'; +part 'settings_global_state.dart'; -class SettingsCubit extends HydratedCubit<SettingsState> { - SettingsCubit() : super(const SettingsState()); +class GlobalSettingsCubit extends HydratedCubit<GlobalSettingsState> { + GlobalSettingsCubit() : super(const GlobalSettingsState()); + + void setValues({ + String? username, + String? securityToken, + int? discoveriesDaysCount, + int? distributionDaysCount, + int? statisticsRecentDaysCount, + int? timelineDaysCount, + int? topArtistsDaysCount, + int? newArtistsCount, + int? newTracksCount, + }) { + emit(GlobalSettingsState( + username: username ?? state.username, + securityToken: securityToken ?? state.securityToken, + discoveriesDaysCount: discoveriesDaysCount ?? state.discoveriesDaysCount, + distributionDaysCount: distributionDaysCount ?? state.distributionDaysCount, + statisticsRecentDaysCount: statisticsRecentDaysCount ?? state.statisticsRecentDaysCount, + timelineDaysCount: timelineDaysCount ?? state.timelineDaysCount, + topArtistsDaysCount: topArtistsDaysCount ?? state.topArtistsDaysCount, + newArtistsCount: newArtistsCount ?? state.newArtistsCount, + newTracksCount: newTracksCount ?? state.newTracksCount, + )); + } String getUsername() { return state.username ?? ''; @@ -18,59 +42,36 @@ class SettingsCubit extends HydratedCubit<SettingsState> { } int getDiscoveriesDaysCount() { - return state.discoveriesDaysCount ?? DefaultSettings.defaultDiscoveriesDaysCount; + return state.discoveriesDaysCount ?? DefaultGlobalSettings.defaultDiscoveriesDaysCount; } int getDistributionDaysCount() { - return state.distributionDaysCount ?? DefaultSettings.defaultDistributionDaysCount; + return state.distributionDaysCount ?? DefaultGlobalSettings.defaultDistributionDaysCount; } int getStatisticsRecentDaysCount() { - return state.statisticsRecentDaysCount ?? DefaultSettings.defaultStatisticsRecentDaysCount; + return state.statisticsRecentDaysCount ?? + DefaultGlobalSettings.defaultStatisticsRecentDaysCount; } int getTimelineDaysCount() { - return state.timelineDaysCount ?? DefaultSettings.defaultTimelineDaysCount; + return state.timelineDaysCount ?? DefaultGlobalSettings.defaultTimelineDaysCount; } int getTopArtistsDaysCount() { - return state.topArtistsDaysCount ?? DefaultSettings.defaultTopArtistsDaysCount; + return state.topArtistsDaysCount ?? DefaultGlobalSettings.defaultTopArtistsDaysCount; } int getNewArtistsCount() { - return state.newArtistsCount ?? DefaultSettings.defaultNewArtistsCount; + return state.newArtistsCount ?? DefaultGlobalSettings.defaultNewArtistsCount; } int getNewTracksCount() { - return state.newTracksCount ?? DefaultSettings.defaultNewTracksCount; - } - - void setValues({ - String? username, - String? securityToken, - int? discoveriesDaysCount, - int? distributionDaysCount, - int? statisticsRecentDaysCount, - int? timelineDaysCount, - int? topArtistsDaysCount, - int? newArtistsCount, - int? newTracksCount, - }) { - emit(SettingsState( - username: username ?? state.username, - securityToken: securityToken ?? state.securityToken, - discoveriesDaysCount: discoveriesDaysCount ?? state.discoveriesDaysCount, - distributionDaysCount: distributionDaysCount ?? state.distributionDaysCount, - statisticsRecentDaysCount: statisticsRecentDaysCount ?? state.statisticsRecentDaysCount, - timelineDaysCount: timelineDaysCount ?? state.timelineDaysCount, - topArtistsDaysCount: topArtistsDaysCount ?? state.topArtistsDaysCount, - newArtistsCount: newArtistsCount ?? state.newArtistsCount, - newTracksCount: newTracksCount ?? state.newTracksCount, - )); + return state.newTracksCount ?? DefaultGlobalSettings.defaultNewTracksCount; } @override - SettingsState? fromJson(Map<String, dynamic> json) { + GlobalSettingsState? fromJson(Map<String, dynamic> json) { String username = json['username'] as String; String securityToken = json['securityToken'] as String; int discoveriesDaysCount = json['discoveriesDaysCount'] as int; @@ -81,7 +82,7 @@ class SettingsCubit extends HydratedCubit<SettingsState> { int newArtistsCount = json['newArtistsCount'] as int; int newTracksCount = json['newTracksCount'] as int; - return SettingsState( + return GlobalSettingsState( username: username, securityToken: securityToken, discoveriesDaysCount: discoveriesDaysCount, @@ -95,21 +96,22 @@ class SettingsCubit extends HydratedCubit<SettingsState> { } @override - Map<String, dynamic>? toJson(SettingsState state) { + Map<String, dynamic>? toJson(GlobalSettingsState state) { return <String, dynamic>{ 'username': state.username ?? '', 'securityToken': state.securityToken ?? '', 'discoveriesDaysCount': - state.discoveriesDaysCount ?? DefaultSettings.defaultDiscoveriesDaysCount, + state.discoveriesDaysCount ?? DefaultGlobalSettings.defaultDiscoveriesDaysCount, 'distributionDaysCount': - state.distributionDaysCount ?? DefaultSettings.defaultDistributionDaysCount, - 'statisticsRecentDaysCount': - state.statisticsRecentDaysCount ?? DefaultSettings.defaultStatisticsRecentDaysCount, - 'timelineDaysCount': state.timelineDaysCount ?? DefaultSettings.defaultTimelineDaysCount, + state.distributionDaysCount ?? DefaultGlobalSettings.defaultDistributionDaysCount, + 'statisticsRecentDaysCount': state.statisticsRecentDaysCount ?? + DefaultGlobalSettings.defaultStatisticsRecentDaysCount, + 'timelineDaysCount': + state.timelineDaysCount ?? DefaultGlobalSettings.defaultTimelineDaysCount, 'topArtistsDaysCount': - state.topArtistsDaysCount ?? DefaultSettings.defaultTopArtistsDaysCount, - 'newArtistsCount': state.newArtistsCount ?? DefaultSettings.defaultNewArtistsCount, - 'newTracksCount': state.newTracksCount ?? DefaultSettings.defaultNewTracksCount, + state.topArtistsDaysCount ?? DefaultGlobalSettings.defaultTopArtistsDaysCount, + 'newArtistsCount': state.newArtistsCount ?? DefaultGlobalSettings.defaultNewArtistsCount, + 'newTracksCount': state.newTracksCount ?? DefaultGlobalSettings.defaultNewTracksCount, }; } } diff --git a/lib/cubit/settings_state.dart b/lib/cubit/settings_global_state.dart similarity index 92% rename from lib/cubit/settings_state.dart rename to lib/cubit/settings_global_state.dart index 45ef6bb4eb11e57efbc584c77f3a311fe9090a7e..fb08d54167dba5f5a3c12f09cb0079998eafb49a 100644 --- a/lib/cubit/settings_state.dart +++ b/lib/cubit/settings_global_state.dart @@ -1,8 +1,8 @@ -part of 'settings_cubit.dart'; +part of 'settings_global_cubit.dart'; @immutable -class SettingsState extends Equatable { - const SettingsState({ +class GlobalSettingsState extends Equatable { + const GlobalSettingsState({ this.username, this.securityToken, this.discoveriesDaysCount, diff --git a/lib/main.dart b/lib/main.dart index b9d50e8af9617478cc7565a586559022565d21a4..a69592e7e23bde7d58e1124c2d08e6e8f807f55c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,13 +2,16 @@ import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hive/hive.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:path_provider/path_provider.dart'; import 'package:scrobbles/config/theme.dart'; -import 'package:scrobbles/cubit/bottom_nav_cubit.dart'; +import 'package:scrobbles/cubit/activity_cubit.dart'; +import 'package:scrobbles/cubit/nav_cubit_pages.dart'; +import 'package:scrobbles/cubit/nav_cubit_screens.dart'; import 'package:scrobbles/cubit/data_counts_by_day_cubit.dart'; import 'package:scrobbles/cubit/data_counts_by_hour_cubit.dart'; import 'package:scrobbles/cubit/data_discoveries_cubit.dart'; @@ -19,12 +22,12 @@ 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/cubit/settings_global_cubit.dart'; import 'package:scrobbles/cubit/theme_cubit.dart'; import 'package:scrobbles/ui/skeleton.dart'; void main() async { - /// Initialize packages + // Initialize packages WidgetsFlutterBinding.ensureInitialized(); await EasyLocalization.ensureInitialized(); final Directory tmpDir = await getTemporaryDirectory(); @@ -33,18 +36,17 @@ void main() async { storageDirectory: tmpDir, ); - runApp( - EasyLocalization( - path: 'assets/translations', - supportedLocales: const <Locale>[ - Locale('en'), - Locale('fr'), - ], - fallbackLocale: const Locale('en'), - useFallbackTranslations: true, - child: const MyApp(), - ), - ); + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]) + .then((value) => runApp(EasyLocalization( + path: 'assets/translations', + supportedLocales: const <Locale>[ + Locale('en'), + Locale('fr'), + ], + fallbackLocale: const Locale('en'), + useFallbackTranslations: true, + child: const MyApp(), + ))); } class MyApp extends StatelessWidget { @@ -54,8 +56,11 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MultiBlocProvider( providers: [ - BlocProvider<SettingsCubit>(create: (context) => SettingsCubit()), - BlocProvider<BottomNavCubit>(create: (context) => BottomNavCubit()), + BlocProvider<NavCubitPage>(create: (context) => NavCubitPage()), + BlocProvider<NavCubitScreen>(create: (context) => NavCubitScreen()), + BlocProvider<ThemeCubit>(create: (context) => ThemeCubit()), + BlocProvider<ActivityCubit>(create: (context) => ActivityCubit()), + BlocProvider<GlobalSettingsCubit>(create: (context) => GlobalSettingsCubit()), BlocProvider<DataCountsByDayCubit>(create: (context) => DataCountsByDayCubit()), BlocProvider<DataCountsByHourCubit>(create: (context) => DataCountsByHourCubit()), BlocProvider<DataDiscoveriesCubit>(create: (context) => DataDiscoveriesCubit()), @@ -68,26 +73,26 @@ class MyApp extends StatelessWidget { create: (context) => DataStatisticsRecentCubit()), BlocProvider<DataTimelineCubit>(create: (context) => DataTimelineCubit()), BlocProvider<DataTopArtistsCubit>(create: (context) => DataTopArtistsCubit()), - BlocProvider<ThemeCubit>(create: (context) => ThemeCubit()), ], child: BlocBuilder<ThemeCubit, ThemeModeState>( - builder: (BuildContext context, ThemeModeState state) { - return MaterialApp( - title: 'Scrobbles', - home: const SkeletonScreen(), + builder: (BuildContext context, ThemeModeState state) { + return MaterialApp( + title: 'Scrobbles', + home: const SkeletonScreen(), - // Theme stuff - theme: lightTheme, - darkTheme: darkTheme, - themeMode: state.themeMode, + // Theme stuff + theme: lightTheme, + darkTheme: darkTheme, + themeMode: state.themeMode, - // Localization stuff - localizationsDelegates: context.localizationDelegates, - supportedLocales: context.supportedLocales, - locale: context.locale, - debugShowCheckedModeBanner: false, - ); - }), + // Localization stuff + localizationsDelegates: context.localizationDelegates, + supportedLocales: context.supportedLocales, + locale: context.locale, + debugShowCheckedModeBanner: false, + ); + }, + ), ); } } diff --git a/lib/models/activity/activity.dart b/lib/models/activity/activity.dart new file mode 100644 index 0000000000000000000000000000000000000000..1b890ee1d9be844ac0a28749818edc072b75e7b2 --- /dev/null +++ b/lib/models/activity/activity.dart @@ -0,0 +1,26 @@ +import 'package:scrobbles/utils/tools.dart'; + +class Activity { + const Activity(); + + factory Activity.createNull() { + return const Activity(); + } + + void dump() { + printlog(''); + printlog('## Current activity dump:'); + printlog(''); + printlog('$Activity:'); + printlog(''); + } + + @override + String toString() { + return '$Activity(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{}; + } +} diff --git a/lib/models/artists.dart b/lib/models/data/artists.dart similarity index 100% rename from lib/models/artists.dart rename to lib/models/data/artists.dart diff --git a/lib/models/counts_by_day.dart b/lib/models/data/counts_by_day.dart similarity index 100% rename from lib/models/counts_by_day.dart rename to lib/models/data/counts_by_day.dart diff --git a/lib/models/counts_by_hour.dart b/lib/models/data/counts_by_hour.dart similarity index 100% rename from lib/models/counts_by_hour.dart rename to lib/models/data/counts_by_hour.dart diff --git a/lib/models/discoveries.dart b/lib/models/data/discoveries.dart similarity index 100% rename from lib/models/discoveries.dart rename to lib/models/data/discoveries.dart diff --git a/lib/models/heatmap.dart b/lib/models/data/heatmap.dart similarity index 100% rename from lib/models/heatmap.dart rename to lib/models/data/heatmap.dart diff --git a/lib/models/new_artists.dart b/lib/models/data/new_artists.dart similarity index 95% rename from lib/models/new_artists.dart rename to lib/models/data/new_artists.dart index fffafc628d11ab099f053db462d74d6590adbc73..6d1e845a2d83fc2e022647f174f3492c4d6d1757 100644 --- a/lib/models/new_artists.dart +++ b/lib/models/data/new_artists.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:scrobbles/models/artists.dart'; +import 'package:scrobbles/models/data/artists.dart'; class NewArtistData { final DateTime? firstPlayed; diff --git a/lib/models/new_tracks.dart b/lib/models/data/new_tracks.dart similarity index 95% rename from lib/models/new_tracks.dart rename to lib/models/data/new_tracks.dart index b1105effacdb46a2b19381bf4eda68bd6e67578a..9c39e5820f3c0db283af41f9d31ce8ff1c7511b9 100644 --- a/lib/models/new_tracks.dart +++ b/lib/models/data/new_tracks.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:scrobbles/models/track.dart'; +import 'package:scrobbles/models/data/track.dart'; class NewTrackData { final DateTime? firstPlayed; diff --git a/lib/models/statistics_global.dart b/lib/models/data/statistics_global.dart similarity index 100% rename from lib/models/statistics_global.dart rename to lib/models/data/statistics_global.dart diff --git a/lib/models/statistics_recent.dart b/lib/models/data/statistics_recent.dart similarity index 100% rename from lib/models/statistics_recent.dart rename to lib/models/data/statistics_recent.dart diff --git a/lib/models/timeline.dart b/lib/models/data/timeline.dart similarity index 100% rename from lib/models/timeline.dart rename to lib/models/data/timeline.dart diff --git a/lib/models/topartists.dart b/lib/models/data/topartists.dart similarity index 100% rename from lib/models/topartists.dart rename to lib/models/data/topartists.dart diff --git a/lib/models/track.dart b/lib/models/data/track.dart similarity index 93% rename from lib/models/track.dart rename to lib/models/data/track.dart index 39e3d6a8d9994df8c7a4d7f342068a2fea4e4358..2cb6f2a8f49eb69c2a83933d0d49e986be84d15e 100644 --- a/lib/models/track.dart +++ b/lib/models/data/track.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:scrobbles/models/artists.dart'; +import 'package:scrobbles/models/data/artists.dart'; class Track { final int id; diff --git a/lib/network/scrobbles.dart b/lib/network/scrobbles.dart index 3592365eaade76d0777bf7b2d6631ee582a44399..8c341ae13e0a53a9642843013ce6afea58e1b39b 100644 --- a/lib/network/scrobbles.dart +++ b/lib/network/scrobbles.dart @@ -1,127 +1,137 @@ import 'dart:convert'; import 'package:http/http.dart' as http; -import 'package:scrobbles/models/counts_by_day.dart'; -import 'package:scrobbles/models/counts_by_hour.dart'; -import 'package:scrobbles/models/discoveries.dart'; -import 'package:scrobbles/models/heatmap.dart'; -import 'package:scrobbles/models/new_artists.dart'; -import 'package:scrobbles/models/new_tracks.dart'; -import 'package:scrobbles/models/statistics_global.dart'; -import 'package:scrobbles/models/statistics_recent.dart'; -import 'package:scrobbles/models/timeline.dart'; -import 'package:scrobbles/models/topartists.dart'; +import 'package:scrobbles/models/data/counts_by_day.dart'; +import 'package:scrobbles/models/data/counts_by_hour.dart'; +import 'package:scrobbles/models/data/discoveries.dart'; +import 'package:scrobbles/models/data/heatmap.dart'; +import 'package:scrobbles/models/data/new_artists.dart'; +import 'package:scrobbles/models/data/new_tracks.dart'; +import 'package:scrobbles/models/data/statistics_global.dart'; +import 'package:scrobbles/models/data/statistics_recent.dart'; +import 'package:scrobbles/models/data/timeline.dart'; +import 'package:scrobbles/models/data/topartists.dart'; class ScrobblesApi { static String baseUrl = 'https://scrobble.harrault.fr'; - static Future<StatisticsGlobalData> fetchGlobalStatistics() async { + static Future<StatisticsGlobalData?> fetchGlobalStatistics() async { final String url = '$baseUrl/stats'; final response = await http.get(Uri.parse(url)); if (response.statusCode == 200) { - return StatisticsGlobalData.fromJson(jsonDecode(response.body) as Map<String, dynamic>); - } else { - throw Exception('Failed to get data from API.'); + final Map<String, dynamic> rawData = jsonDecode(response.body) as Map<String, dynamic>; + return StatisticsGlobalData.fromJson(rawData); } + + return null; } - static Future<StatisticsRecentData> fetchRecentStatistics(int daysCount) async { + static Future<StatisticsRecentData?> fetchRecentStatistics(int daysCount) async { final String url = '$baseUrl/$daysCount/stats'; final response = await http.get(Uri.parse(url)); if (response.statusCode == 200) { - return StatisticsRecentData.fromJson(jsonDecode(response.body) as Map<String, dynamic>); - } else { - throw Exception('Failed to get data from API.'); + final Map<String, dynamic> rawData = jsonDecode(response.body) as Map<String, dynamic>; + return StatisticsRecentData.fromJson(rawData); } + + return null; } - static Future<TimelineData> fetchTimeline(int daysCount) async { + static Future<TimelineData?> fetchTimeline(int daysCount) async { final String url = '$baseUrl/data/$daysCount/timeline'; final response = await http.get(Uri.parse(url)); if (response.statusCode == 200) { - return TimelineData.fromJson(jsonDecode(response.body) as Map<String, dynamic>); - } else { - throw Exception('Failed to get data from API.'); + final Map<String, dynamic> rawData = jsonDecode(response.body) as Map<String, dynamic>; + return TimelineData.fromJson(rawData); } + + return null; } - static Future<CountsByDayData> fetchCountsByDay(int daysCount) async { + static Future<CountsByDayData?> fetchCountsByDay(int daysCount) async { final String url = '$baseUrl/data/$daysCount/counts-by-day'; final response = await http.get(Uri.parse(url)); if (response.statusCode == 200) { - return CountsByDayData.fromJson(jsonDecode(response.body) as Map<String, dynamic>); - } else { - throw Exception('Failed to get data from API.'); + final Map<String, dynamic> rawData = jsonDecode(response.body) as Map<String, dynamic>; + return CountsByDayData.fromJson(rawData); } + + return null; } - static Future<CountsByHourData> fetchCountsByHour(int daysCount) async { + static Future<CountsByHourData?> fetchCountsByHour(int daysCount) async { final String url = '$baseUrl/data/$daysCount/counts-by-hour'; final response = await http.get(Uri.parse(url)); if (response.statusCode == 200) { - return CountsByHourData.fromJson(jsonDecode(response.body) as Map<String, dynamic>); - } else { - throw Exception('Failed to get data from API.'); + final Map<String, dynamic> rawData = jsonDecode(response.body) as Map<String, dynamic>; + return CountsByHourData.fromJson(rawData); } + + return null; } - static Future<DiscoveriesData> fetchDiscoveries(int daysCount) async { + static Future<DiscoveriesData?> fetchDiscoveries(int daysCount) async { final String url = '$baseUrl/data/$daysCount/news'; final response = await http.get(Uri.parse(url)); if (response.statusCode == 200) { - return DiscoveriesData.fromJson(jsonDecode(response.body) as Map<String, dynamic>); - } else { - throw Exception('Failed to get data from API.'); + final Map<String, dynamic> rawData = jsonDecode(response.body) as Map<String, dynamic>; + return DiscoveriesData.fromJson(rawData); } + + return null; } - static Future<TopArtistsData> fetchTopArtists(int daysCount) async { + static Future<TopArtistsData?> fetchTopArtists(int daysCount) async { final String url = '$baseUrl/data/$daysCount/top-artists'; final response = await http.get(Uri.parse(url)); if (response.statusCode == 200) { - return TopArtistsData.fromJson(jsonDecode(response.body) as Map<String, dynamic>); - } else { - throw Exception('Failed to get data from API.'); + final Map<String, dynamic> rawData = jsonDecode(response.body) as Map<String, dynamic>; + return TopArtistsData.fromJson(rawData); } + + return null; } - static Future<HeatmapData> fetchHeatmap(int daysCount) async { + static Future<HeatmapData?> fetchHeatmap(int daysCount) async { final String url = '$baseUrl/data/$daysCount/heatmap'; final response = await http.get(Uri.parse(url)); if (response.statusCode == 200) { - return HeatmapData.fromJson(jsonDecode(response.body) as Map<String, dynamic>); - } else { - throw Exception('Failed to get data from API.'); + final Map<String, dynamic> rawData = jsonDecode(response.body) as Map<String, dynamic>; + return HeatmapData.fromJson(rawData); } + + return null; } - static Future<NewArtistsData> fetchNewArtists(int count) async { + static Future<NewArtistsData?> fetchNewArtists(int count) async { final String url = '$baseUrl/data/discoveries/artists/$count'; final response = await http.get(Uri.parse(url)); if (response.statusCode == 200) { - return NewArtistsData.fromJson(jsonDecode(response.body) as List<dynamic>); - } else { - throw Exception('Failed to get data from API.'); + final List<dynamic> rawData = jsonDecode(response.body) as List<dynamic>; + return NewArtistsData.fromJson(rawData); } + + return null; } - static Future<NewTracksData> fetchNewTracks(int count) async { + static Future<NewTracksData?> fetchNewTracks(int count) async { final String url = '$baseUrl/data/discoveries/tracks/$count'; final response = await http.get(Uri.parse(url)); if (response.statusCode == 200) { - return NewTracksData.fromJson(jsonDecode(response.body) as List<dynamic>); - } else { - throw Exception('Failed to get data from API.'); + final List<dynamic> rawData = jsonDecode(response.body) as List<dynamic>; + return NewTracksData.fromJson(rawData); } + + return null; } } diff --git a/lib/ui/widgets/app_titles.dart b/lib/ui/helpers/app_titles.dart similarity index 52% rename from lib/ui/widgets/app_titles.dart rename to lib/ui/helpers/app_titles.dart index 93541243613b0f20e76485520a275d1527a6fcc9..b98107b12fabc3114ebfbec994166b588abcf1ad 100644 --- a/lib/ui/widgets/app_titles.dart +++ b/lib/ui/helpers/app_titles.dart @@ -1,8 +1,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -class AppTitle extends StatelessWidget { - const AppTitle({super.key, required this.text}); +class AppHeader extends StatelessWidget { + const AppHeader({super.key, required this.text}); final String text; @@ -11,37 +11,22 @@ class AppTitle extends StatelessWidget { return Text( tr(text), textAlign: TextAlign.start, - style: Theme.of(context).textTheme.headlineLarge!.apply(fontWeightDelta: 2), + style: Theme.of(context).textTheme.headlineMedium!.apply(fontWeightDelta: 2), ); } } -class AppTitle1 extends StatelessWidget { - const AppTitle1({super.key, required this.text}); +class AppTitle extends StatelessWidget { + const AppTitle({super.key, required this.text}); final String text; @override Widget build(BuildContext context) { return Text( - text, + tr(text), textAlign: TextAlign.start, style: Theme.of(context).textTheme.titleLarge!.apply(fontWeightDelta: 2), ); } } - -class AppTitle2 extends StatelessWidget { - const AppTitle2({super.key, required this.text}); - - final String text; - - @override - Widget build(BuildContext context) { - return Text( - text, - textAlign: TextAlign.start, - style: Theme.of(context).textTheme.titleMedium!.apply(fontWeightDelta: 2), - ); - } -} diff --git a/lib/ui/nav/bottom_nav_bar.dart b/lib/ui/nav/bottom_nav_bar.dart new file mode 100644 index 0000000000000000000000000000000000000000..1cdade62858ef454bfc38b81f15362c677f91d26 --- /dev/null +++ b/lib/ui/nav/bottom_nav_bar.dart @@ -0,0 +1,53 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_swipe/flutter_swipe.dart'; + +import 'package:scrobbles/config/activity_page.dart'; +import 'package:scrobbles/cubit/nav_cubit_pages.dart'; + +class BottomNavBar extends StatelessWidget { + const BottomNavBar({super.key, required this.swipeController}); + + final SwiperController swipeController; + + @override + Widget build(BuildContext context) { + return Card( + margin: const EdgeInsets.all(0), + elevation: 4, + shadowColor: Theme.of(context).colorScheme.shadow, + color: Theme.of(context).colorScheme.surfaceContainerHighest, + shape: const ContinuousRectangleBorder(), + child: BlocBuilder<NavCubitPage, int>( + builder: (BuildContext context, int pageIndex) { + final List<ActivityPageItem> pageItems = [ + ActivityPage.pageHome, + ActivityPage.pageDiscoveries, + ActivityPage.pageStatistics, + ]; + final List<BottomNavigationBarItem> items = pageItems.map((ActivityPageItem item) { + return BottomNavigationBarItem( + icon: item.icon, + label: tr(item.code), + ); + }).toList(); + + return BottomNavigationBar( + currentIndex: pageIndex, + onTap: (int index) { + context.read<NavCubitPage>().updateIndex(index); + swipeController.move(index); + }, + type: BottomNavigationBarType.fixed, + elevation: 0, + backgroundColor: Colors.transparent, + selectedItemColor: Theme.of(context).colorScheme.primary, + unselectedItemColor: Theme.of(context).textTheme.bodySmall!.color, + items: items, + ); + }, + ), + ); + } +} diff --git a/lib/ui/nav/global_app_bar.dart b/lib/ui/nav/global_app_bar.dart new file mode 100644 index 0000000000000000000000000000000000000000..f7cb5e5405a06f7c3f900851c70d145c3d2e4b52 --- /dev/null +++ b/lib/ui/nav/global_app_bar.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:scrobbles/cubit/activity_cubit.dart'; +import 'package:unicons/unicons.dart'; + +import 'package:scrobbles/cubit/nav_cubit_pages.dart'; +import 'package:scrobbles/config/screen.dart'; +import 'package:scrobbles/cubit/nav_cubit_screens.dart'; +import 'package:scrobbles/ui/helpers/app_titles.dart'; + +class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget { + const GlobalAppBar({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<NavCubitScreen, int>( + builder: (BuildContext context, int screenIndex) { + final List<Widget> menuActions = []; + + if (screenIndex == Screen.indexActivity) { + // go to Settings page + menuActions.add(IconButton( + onPressed: () { + context.read<NavCubitScreen>().goToSettingsPage(); + }, + icon: Screen.screenSettings.icon, + )); + + // go to About page + menuActions.add(IconButton( + onPressed: () { + context.read<NavCubitScreen>().goToAboutPage(); + }, + icon: Screen.screenAbout.icon, + )); + + // refresh data + menuActions.add(IconButton( + onPressed: () { + BlocProvider.of<ActivityCubit>(context).refresh(context); + }, + icon: const Icon(UniconsSolid.refresh), + )); + } else { + // back to Home page + menuActions.add(IconButton( + onPressed: () { + context.read<NavCubitScreen>().goToActivityPage(); + context.read<NavCubitPage>().goToHomePage(); + }, + icon: Screen.screenActivity.icon, + )); + } + + return AppBar( + title: const AppHeader(text: 'app_name'), + actions: menuActions, + ); + }, + ); + } + + @override + Size get preferredSize => const Size.fromHeight(50); +} diff --git a/lib/ui/pages/discoveries.dart b/lib/ui/pages/discoveries.dart new file mode 100644 index 0000000000000000000000000000000000000000..ddbbe96f22ad7d434b673538864c6dfb845c1206 --- /dev/null +++ b/lib/ui/pages/discoveries.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:scrobbles/cubit/activity_cubit.dart'; +import 'package:scrobbles/ui/widgets/cards/discoveries.dart'; +import 'package:scrobbles/ui/widgets/cards/new_artists.dart'; +import 'package:scrobbles/ui/widgets/cards/new_tracks.dart'; + +class PageDiscoveries extends StatelessWidget { + const PageDiscoveries({super.key}); + + @override + Widget build(BuildContext context) { + return RefreshIndicator( + onRefresh: () async { + BlocProvider.of<ActivityCubit>(context).refresh(context); + }, + child: ListView( + padding: const EdgeInsets.symmetric(horizontal: 4), + physics: const BouncingScrollPhysics(), + children: const <Widget>[ + SizedBox(height: 8), + CardDiscoveries(), + SizedBox(height: 6), + CardNewArtists(), + SizedBox(height: 6), + CardNewTracks(), + SizedBox(height: 36), + ], + ), + ); + } +} diff --git a/lib/ui/pages/home.dart b/lib/ui/pages/home.dart new file mode 100644 index 0000000000000000000000000000000000000000..f7d4d8bc51ec518cd1af01d39def66ceec82f0a7 --- /dev/null +++ b/lib/ui/pages/home.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:scrobbles/cubit/activity_cubit.dart'; +import 'package:scrobbles/ui/widgets/cards/statistics_global.dart'; +import 'package:scrobbles/ui/widgets/cards/statistics_recent.dart'; +import 'package:scrobbles/ui/widgets/cards/timeline.dart'; +import 'package:scrobbles/ui/widgets/cards/top_artists.dart'; + +class PageHome extends StatelessWidget { + const PageHome({super.key}); + + @override + Widget build(BuildContext context) { + return RefreshIndicator( + onRefresh: () async { + BlocProvider.of<ActivityCubit>(context).refresh(context); + }, + child: ListView( + padding: const EdgeInsets.symmetric(horizontal: 4), + physics: const BouncingScrollPhysics(), + children: const <Widget>[ + SizedBox(height: 8), + CardStatisticsGlobal(), + SizedBox(height: 6), + CardStatisticsRecent(), + SizedBox(height: 6), + CardTimeline(), + SizedBox(height: 6), + CardTopArtists(), + SizedBox(height: 36), + ], + ), + ); + } +} diff --git a/lib/ui/pages/statistics.dart b/lib/ui/pages/statistics.dart new file mode 100644 index 0000000000000000000000000000000000000000..b0448b62e607d806309c1ed0fecda6a052826019 --- /dev/null +++ b/lib/ui/pages/statistics.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:scrobbles/cubit/activity_cubit.dart'; +import 'package:scrobbles/ui/widgets/cards/counts_by_day.dart'; +import 'package:scrobbles/ui/widgets/cards/counts_by_hour.dart'; +import 'package:scrobbles/ui/widgets/cards/heatmap.dart'; + +class PageStatistics extends StatelessWidget { + const PageStatistics({super.key}); + + @override + Widget build(BuildContext context) { + return RefreshIndicator( + onRefresh: () async { + BlocProvider.of<ActivityCubit>(context).refresh(context); + }, + child: ListView( + padding: const EdgeInsets.symmetric(horizontal: 4), + physics: const BouncingScrollPhysics(), + children: const <Widget>[ + SizedBox(height: 8), + CardHeatmap(), + SizedBox(height: 6), + CardCountsByDay(), + SizedBox(height: 6), + CardCountsByHour(), + SizedBox(height: 36), + ], + ), + ); + } +} diff --git a/lib/ui/screens/about.dart b/lib/ui/screens/about.dart new file mode 100644 index 0000000000000000000000000000000000000000..1685d05a94bab039925445ca3b1005c11ed49ea4 --- /dev/null +++ b/lib/ui/screens/about.dart @@ -0,0 +1,41 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +import 'package:scrobbles/ui/helpers/app_titles.dart'; + +class ScreenAbout extends StatelessWidget { + const ScreenAbout({super.key}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: <Widget>[ + const SizedBox(height: 8), + const AppTitle(text: 'about_title'), + const Text('about_content').tr(), + FutureBuilder<PackageInfo>( + future: PackageInfo.fromPlatform(), + builder: (context, snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.done: + return const Text('about_version').tr( + namedArgs: { + 'version': snapshot.data!.version, + }, + ); + default: + return const SizedBox(); + } + }, + ), + ], + ), + ); + } +} diff --git a/lib/ui/screens/activity.dart b/lib/ui/screens/activity.dart new file mode 100644 index 0000000000000000000000000000000000000000..7ebabc1eb9cbabe088c83e8a77979cb69465be8d --- /dev/null +++ b/lib/ui/screens/activity.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_swipe/flutter_swipe.dart'; + +import 'package:scrobbles/config/activity_page.dart'; +import 'package:scrobbles/config/screen.dart'; +import 'package:scrobbles/cubit/nav_cubit_pages.dart'; +import 'package:scrobbles/ui/nav/bottom_nav_bar.dart'; + +class ScreenActivity extends StatelessWidget { + const ScreenActivity({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<NavCubitPage, int>( + builder: (BuildContext context, int pageIndex) { + return Swiper( + itemCount: Screen.itemsCount, + itemBuilder: (BuildContext context, int pageIndex) { + return ActivityPage.getPageWidget(pageIndex); + }, + pagination: SwiperPagination( + margin: const EdgeInsets.all(0), + builder: SwiperCustomPagination( + builder: (BuildContext context, SwiperPluginConfig config) { + return BottomNavBar(swipeController: config.controller); + }, + ), + ), + onIndexChanged: (newPageIndex) { + BlocProvider.of<NavCubitPage>(context).updateIndex(newPageIndex); + }, + outer: true, + loop: false, + ); + }, + ); + } +} diff --git a/lib/ui/screens/discoveries.dart b/lib/ui/screens/discoveries.dart deleted file mode 100644 index 550307b8f3a37ca702a034cc49e05b628d936ab5..0000000000000000000000000000000000000000 --- a/lib/ui/screens/discoveries.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:scrobbles/ui/widgets/cards/discoveries.dart'; -import 'package:scrobbles/ui/widgets/cards/new_artists.dart'; -import 'package:scrobbles/ui/widgets/cards/new_tracks.dart'; - -class ScreenDiscoveries extends StatelessWidget { - final Function() notifyParent; - - const ScreenDiscoveries({super.key, required this.notifyParent}); - - @override - Widget build(BuildContext context) { - return Material( - color: Theme.of(context).colorScheme.background, - child: RefreshIndicator( - onRefresh: () async { - notifyParent(); - }, - child: ListView( - padding: const EdgeInsets.symmetric(horizontal: 4), - physics: const BouncingScrollPhysics(), - children: const <Widget>[ - SizedBox(height: 8), - CardDiscoveries(), - SizedBox(height: 6), - CardNewArtists(), - SizedBox(height: 6), - CardNewTracks(), - SizedBox(height: 36), - ], - ), - ), - ); - } -} diff --git a/lib/ui/screens/home.dart b/lib/ui/screens/home.dart deleted file mode 100644 index e943ff8ebbefe4f26626a728c1124f6b9e780f63..0000000000000000000000000000000000000000 --- a/lib/ui/screens/home.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:scrobbles/ui/widgets/cards/statistics_global.dart'; -import 'package:scrobbles/ui/widgets/cards/statistics_recent.dart'; -import 'package:scrobbles/ui/widgets/cards/timeline.dart'; -import 'package:scrobbles/ui/widgets/cards/top_artists.dart'; - -class ScreenHome extends StatelessWidget { - final Function() notifyParent; - - const ScreenHome({super.key, required this.notifyParent}); - - @override - Widget build(BuildContext context) { - return Material( - color: Theme.of(context).colorScheme.background, - child: RefreshIndicator( - onRefresh: () async { - notifyParent(); - }, - child: ListView( - padding: const EdgeInsets.symmetric(horizontal: 4), - physics: const BouncingScrollPhysics(), - children: const <Widget>[ - SizedBox(height: 8), - CardStatisticsGlobal(), - SizedBox(height: 6), - CardStatisticsRecent(), - SizedBox(height: 6), - CardTimeline(), - SizedBox(height: 6), - CardTopArtists(), - SizedBox(height: 36), - ], - ), - ), - ); - } -} diff --git a/lib/ui/screens/settings.dart b/lib/ui/screens/settings.dart index 736f3a5fe35aaf7ec45686b01805000e21405f37..856ad882a681a2d8d765165b8f57d45a53a07528 100644 --- a/lib/ui/screens/settings.dart +++ b/lib/ui/screens/settings.dart @@ -1,25 +1,22 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:scrobbles/ui/widgets/app_titles.dart'; -import 'package:scrobbles/ui/widgets/settings_form.dart'; +import 'package:scrobbles/ui/helpers/app_titles.dart'; +import 'package:scrobbles/ui/settings/settings_form.dart'; class ScreenSettings extends StatelessWidget { const ScreenSettings({super.key}); @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), - AppTitle1(text: tr('settings_title')), - const SettingsForm(), - ], - ), + return ListView( + padding: const EdgeInsets.symmetric(horizontal: 4), + physics: const BouncingScrollPhysics(), + children: <Widget>[ + const SizedBox(height: 8), + AppTitle(text: tr('settings_title')), + const SettingsForm(), + ], ); } } diff --git a/lib/ui/screens/statistics.dart b/lib/ui/screens/statistics.dart deleted file mode 100644 index 7e35f1ec9905c382d0ed789cbd42217d692f7b85..0000000000000000000000000000000000000000 --- a/lib/ui/screens/statistics.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:scrobbles/ui/widgets/cards/counts_by_day.dart'; -import 'package:scrobbles/ui/widgets/cards/counts_by_hour.dart'; -import 'package:scrobbles/ui/widgets/cards/heatmap.dart'; - -class ScreenStatistics extends StatelessWidget { - final Function() notifyParent; - - const ScreenStatistics({super.key, required this.notifyParent}); - - @override - Widget build(BuildContext context) { - return Material( - color: Theme.of(context).colorScheme.background, - child: RefreshIndicator( - onRefresh: () async { - notifyParent(); - }, - child: ListView( - padding: const EdgeInsets.symmetric(horizontal: 4), - physics: const BouncingScrollPhysics(), - children: const <Widget>[ - SizedBox(height: 8), - CardHeatmap(), - SizedBox(height: 6), - CardCountsByDay(), - SizedBox(height: 6), - CardCountsByHour(), - SizedBox(height: 36), - ], - ), - ), - ); - } -} diff --git a/lib/ui/widgets/settings_form.dart b/lib/ui/settings/settings_form.dart similarity index 78% rename from lib/ui/widgets/settings_form.dart rename to lib/ui/settings/settings_form.dart index 6e5401de8f55105e2e99dd2eb03fb4dc4de4f376..f64158547afe53aef126a63f66f758452244fd8c 100644 --- a/lib/ui/widgets/settings_form.dart +++ b/lib/ui/settings/settings_form.dart @@ -1,12 +1,12 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:scrobbles/ui/widgets/theme_card.dart'; +import 'package:scrobbles/ui/settings/theme_card.dart'; import 'package:unicons/unicons.dart'; -import 'package:scrobbles/config/default_settings.dart'; -import 'package:scrobbles/cubit/settings_cubit.dart'; -import 'package:scrobbles/ui/widgets/app_titles.dart'; +import 'package:scrobbles/config/default_global_settings.dart'; +import 'package:scrobbles/cubit/settings_global_cubit.dart'; +import 'package:scrobbles/ui/helpers/app_titles.dart'; class SettingsForm extends StatefulWidget { const SettingsForm({super.key}); @@ -19,13 +19,13 @@ class _SettingsFormState extends State<SettingsForm> { final usernameController = TextEditingController(); final securityTokenController = TextEditingController(); - int discoveriesDaysCount = DefaultSettings.defaultDiscoveriesDaysCount; - int distributionDaysCount = DefaultSettings.defaultDistributionDaysCount; - int statisticsRecentDaysCount = DefaultSettings.defaultStatisticsRecentDaysCount; - int timelineDaysCount = DefaultSettings.defaultTimelineDaysCount; - int topArtistsDaysCount = DefaultSettings.defaultTopArtistsDaysCount; - int newArtistsCount = DefaultSettings.defaultNewArtistsCount; - int newTracksCount = DefaultSettings.defaultNewTracksCount; + int discoveriesDaysCount = DefaultGlobalSettings.defaultDiscoveriesDaysCount; + int distributionDaysCount = DefaultGlobalSettings.defaultDistributionDaysCount; + int statisticsRecentDaysCount = DefaultGlobalSettings.defaultStatisticsRecentDaysCount; + int timelineDaysCount = DefaultGlobalSettings.defaultTimelineDaysCount; + int topArtistsDaysCount = DefaultGlobalSettings.defaultTopArtistsDaysCount; + int newArtistsCount = DefaultGlobalSettings.defaultNewArtistsCount; + int newTracksCount = DefaultGlobalSettings.defaultNewTracksCount; List<bool> _selectedDiscoveriesDaysCount = []; List<bool> _selectedDistributionDaysCount = []; @@ -37,7 +37,7 @@ class _SettingsFormState extends State<SettingsForm> { @override void didChangeDependencies() { - SettingsCubit settings = BlocProvider.of<SettingsCubit>(context); + GlobalSettingsCubit settings = BlocProvider.of<GlobalSettingsCubit>(context); usernameController.text = settings.getUsername(); securityTokenController.text = settings.getSecurityToken(); @@ -50,23 +50,25 @@ class _SettingsFormState extends State<SettingsForm> { newArtistsCount = settings.getNewArtistsCount(); newTracksCount = settings.getNewTracksCount(); - _selectedDiscoveriesDaysCount = DefaultSettings.allowedDaysCountValues + _selectedDiscoveriesDaysCount = DefaultGlobalSettings.allowedDaysCountValues .map((e) => (e == discoveriesDaysCount)) .toList(); - _selectedDistributionDaysCount = DefaultSettings.allowedDaysCountValues + _selectedDistributionDaysCount = DefaultGlobalSettings.allowedDaysCountValues .map((e) => (e == distributionDaysCount)) .toList(); - _selectedStatisticsRecentDaysCount = DefaultSettings.allowedDaysCountValues + _selectedStatisticsRecentDaysCount = DefaultGlobalSettings.allowedDaysCountValues .map((e) => (e == statisticsRecentDaysCount)) .toList(); - _selectedTimelineDaysCount = - DefaultSettings.allowedDaysCountValues.map((e) => (e == timelineDaysCount)).toList(); - _selectedTopArtistsDaysCount = - DefaultSettings.allowedDaysCountValues.map((e) => (e == topArtistsDaysCount)).toList(); + _selectedTimelineDaysCount = DefaultGlobalSettings.allowedDaysCountValues + .map((e) => (e == timelineDaysCount)) + .toList(); + _selectedTopArtistsDaysCount = DefaultGlobalSettings.allowedDaysCountValues + .map((e) => (e == topArtistsDaysCount)) + .toList(); _selectedNewArtistsCount = - DefaultSettings.allowedCountValues.map((e) => (e == newArtistsCount)).toList(); + DefaultGlobalSettings.allowedCountValues.map((e) => (e == newArtistsCount)).toList(); _selectedNewTracksCount = - DefaultSettings.allowedCountValues.map((e) => (e == newTracksCount)).toList(); + DefaultGlobalSettings.allowedCountValues.map((e) => (e == newTracksCount)).toList(); super.didChangeDependencies(); } @@ -81,7 +83,7 @@ class _SettingsFormState extends State<SettingsForm> { @override Widget build(BuildContext context) { void saveSettings() { - BlocProvider.of<SettingsCubit>(context).setValues( + BlocProvider.of<GlobalSettingsCubit>(context).setValues( username: usernameController.text, securityToken: securityTokenController.text, discoveriesDaysCount: discoveriesDaysCount, @@ -130,7 +132,7 @@ class _SettingsFormState extends State<SettingsForm> { const SizedBox(height: 16), - AppTitle2(text: tr('settings_title_global')), + AppTitle(text: tr('settings_title_global')), // Username const Text('settings_label_username').tr(), @@ -153,7 +155,8 @@ class _SettingsFormState extends State<SettingsForm> { ), const SizedBox(height: 8), - AppTitle2(text: tr('settings_title_days_count')), + + AppTitle(text: tr('settings_title_days_count')), // Statistics (recent) Row( @@ -164,7 +167,8 @@ class _SettingsFormState extends State<SettingsForm> { ToggleButtons( onPressed: (int index) { setState(() { - statisticsRecentDaysCount = DefaultSettings.allowedDaysCountValues[index]; + statisticsRecentDaysCount = + DefaultGlobalSettings.allowedDaysCountValues[index]; for (int i = 0; i < _selectedStatisticsRecentDaysCount.length; i++) { _selectedStatisticsRecentDaysCount[i] = i == index; } @@ -174,7 +178,7 @@ class _SettingsFormState extends State<SettingsForm> { borderRadius: const BorderRadius.all(Radius.circular(8)), constraints: const BoxConstraints(minHeight: 30.0, minWidth: 30.0), isSelected: _selectedStatisticsRecentDaysCount, - children: DefaultSettings.allowedDaysCountValues + children: DefaultGlobalSettings.allowedDaysCountValues .map((e) => Text(e.toString())) .toList(), ), @@ -190,7 +194,7 @@ class _SettingsFormState extends State<SettingsForm> { ToggleButtons( onPressed: (int index) { setState(() { - timelineDaysCount = DefaultSettings.allowedDaysCountValues[index]; + timelineDaysCount = DefaultGlobalSettings.allowedDaysCountValues[index]; for (int i = 0; i < _selectedTimelineDaysCount.length; i++) { _selectedTimelineDaysCount[i] = i == index; } @@ -200,7 +204,7 @@ class _SettingsFormState extends State<SettingsForm> { borderRadius: const BorderRadius.all(Radius.circular(8)), constraints: const BoxConstraints(minHeight: 30.0, minWidth: 30.0), isSelected: _selectedTimelineDaysCount, - children: DefaultSettings.allowedDaysCountValues + children: DefaultGlobalSettings.allowedDaysCountValues .map((e) => Text(e.toString())) .toList(), ), @@ -216,7 +220,7 @@ class _SettingsFormState extends State<SettingsForm> { ToggleButtons( onPressed: (int index) { setState(() { - topArtistsDaysCount = DefaultSettings.allowedDaysCountValues[index]; + topArtistsDaysCount = DefaultGlobalSettings.allowedDaysCountValues[index]; for (int i = 0; i < _selectedTopArtistsDaysCount.length; i++) { _selectedTopArtistsDaysCount[i] = i == index; } @@ -226,7 +230,7 @@ class _SettingsFormState extends State<SettingsForm> { borderRadius: const BorderRadius.all(Radius.circular(8)), constraints: const BoxConstraints(minHeight: 30.0, minWidth: 30.0), isSelected: _selectedTopArtistsDaysCount, - children: DefaultSettings.allowedDaysCountValues + children: DefaultGlobalSettings.allowedDaysCountValues .map((e) => Text(e.toString())) .toList(), ), @@ -242,7 +246,7 @@ class _SettingsFormState extends State<SettingsForm> { ToggleButtons( onPressed: (int index) { setState(() { - discoveriesDaysCount = DefaultSettings.allowedDaysCountValues[index]; + discoveriesDaysCount = DefaultGlobalSettings.allowedDaysCountValues[index]; for (int i = 0; i < _selectedDiscoveriesDaysCount.length; i++) { _selectedDiscoveriesDaysCount[i] = i == index; } @@ -252,7 +256,7 @@ class _SettingsFormState extends State<SettingsForm> { borderRadius: const BorderRadius.all(Radius.circular(8)), constraints: const BoxConstraints(minHeight: 30.0, minWidth: 30.0), isSelected: _selectedDiscoveriesDaysCount, - children: DefaultSettings.allowedDaysCountValues + children: DefaultGlobalSettings.allowedDaysCountValues .map((e) => Text(e.toString())) .toList(), ), @@ -268,7 +272,7 @@ class _SettingsFormState extends State<SettingsForm> { ToggleButtons( onPressed: (int index) { setState(() { - distributionDaysCount = DefaultSettings.allowedDaysCountValues[index]; + distributionDaysCount = DefaultGlobalSettings.allowedDaysCountValues[index]; for (int i = 0; i < _selectedDistributionDaysCount.length; i++) { _selectedDistributionDaysCount[i] = i == index; } @@ -278,7 +282,7 @@ class _SettingsFormState extends State<SettingsForm> { borderRadius: const BorderRadius.all(Radius.circular(8)), constraints: const BoxConstraints(minHeight: 30.0, minWidth: 30.0), isSelected: _selectedDistributionDaysCount, - children: DefaultSettings.allowedDaysCountValues + children: DefaultGlobalSettings.allowedDaysCountValues .map((e) => Text(e.toString())) .toList(), ), @@ -286,7 +290,7 @@ class _SettingsFormState extends State<SettingsForm> { ), const SizedBox(height: 8), - AppTitle2(text: tr('settings_title_counts')), + AppTitle(text: tr('settings_title_counts')), // New artists count Row( @@ -297,7 +301,7 @@ class _SettingsFormState extends State<SettingsForm> { ToggleButtons( onPressed: (int index) { setState(() { - newArtistsCount = DefaultSettings.allowedCountValues[index]; + newArtistsCount = DefaultGlobalSettings.allowedCountValues[index]; for (int i = 0; i < _selectedNewArtistsCount.length; i++) { _selectedNewArtistsCount[i] = i == index; } @@ -307,8 +311,9 @@ class _SettingsFormState extends State<SettingsForm> { borderRadius: const BorderRadius.all(Radius.circular(8)), constraints: const BoxConstraints(minHeight: 30.0, minWidth: 30.0), isSelected: _selectedNewArtistsCount, - children: - DefaultSettings.allowedCountValues.map((e) => Text(e.toString())).toList(), + children: DefaultGlobalSettings.allowedCountValues + .map((e) => Text(e.toString())) + .toList(), ), ], ), @@ -322,7 +327,7 @@ class _SettingsFormState extends State<SettingsForm> { ToggleButtons( onPressed: (int index) { setState(() { - newTracksCount = DefaultSettings.allowedCountValues[index]; + newTracksCount = DefaultGlobalSettings.allowedCountValues[index]; for (int i = 0; i < _selectedNewTracksCount.length; i++) { _selectedNewTracksCount[i] = i == index; } @@ -332,8 +337,9 @@ class _SettingsFormState extends State<SettingsForm> { borderRadius: const BorderRadius.all(Radius.circular(8)), constraints: const BoxConstraints(minHeight: 30.0, minWidth: 30.0), isSelected: _selectedNewTracksCount, - children: - DefaultSettings.allowedCountValues.map((e) => Text(e.toString())).toList(), + children: DefaultGlobalSettings.allowedCountValues + .map((e) => Text(e.toString())) + .toList(), ), ], ), diff --git a/lib/ui/widgets/theme_card.dart b/lib/ui/settings/theme_card.dart similarity index 100% rename from lib/ui/widgets/theme_card.dart rename to lib/ui/settings/theme_card.dart diff --git a/lib/ui/skeleton.dart b/lib/ui/skeleton.dart index 9a720a7b94ce6d50148e8c9af0049e5f11019eee..6d765f6dfb85f3817bc99acffe99d256cf54f020 100644 --- a/lib/ui/skeleton.dart +++ b/lib/ui/skeleton.dart @@ -1,64 +1,34 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -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'; +import 'package:scrobbles/config/screen.dart'; +import 'package:scrobbles/cubit/nav_cubit_screens.dart'; +import 'package:scrobbles/ui/nav/global_app_bar.dart'; -class SkeletonScreen extends StatefulWidget { +class SkeletonScreen extends StatelessWidget { const SkeletonScreen({super.key}); - @override - State<SkeletonScreen> createState() => _SkeletonScreenState(); -} - -class _SkeletonScreenState extends State<SkeletonScreen> { @override Widget build(BuildContext context) { - List<Widget> pageNavigation = <Widget>[ - ScreenHome(notifyParent: refresh), - ScreenDiscoveries(notifyParent: refresh), - ScreenStatistics(notifyParent: refresh), - const ScreenSettings(), - ]; - - return Scaffold( - appBar: StandardAppBar(notifyParent: refresh), - extendBodyBehindAppBar: false, - body: Swiper( - itemCount: BlocProvider.of<BottomNavCubit>(context).pagesCount, - itemBuilder: (BuildContext context, int index) { - return pageNavigation.elementAt(index); - }, - pagination: SwiperPagination( - margin: const EdgeInsets.all(0), - builder: SwiperCustomPagination( - builder: (BuildContext context, SwiperPluginConfig config) { - return BottomNavBar(swipeController: config.controller); - }, + return BlocBuilder<NavCubitScreen, int>( + builder: (BuildContext context, int screenIndex) { + return Scaffold( + appBar: const GlobalAppBar(), + extendBodyBehindAppBar: false, + body: Material( + color: Theme.of(context).colorScheme.surface, + child: Padding( + padding: const EdgeInsets.only( + top: 8, + left: 2, + right: 2, + ), + child: Screen.getWidget(screenIndex), + ), ), - ), - onIndexChanged: (newPageIndex) { - BlocProvider.of<BottomNavCubit>(context).updateIndex(newPageIndex); - }, - outer: true, - loop: false, - ), - backgroundColor: Theme.of(context).colorScheme.background, + backgroundColor: Theme.of(context).colorScheme.surface, + ); + }, ); } - - refresh() { - void rebuild(Element el) { - el.markNeedsBuild(); - el.visitChildren(rebuild); - } - - (context as Element).visitChildren(rebuild); - } } diff --git a/lib/ui/widgets/abstracts/custom_bar_chart.dart b/lib/ui/widgets/abstracts/custom_bar_chart.dart index ebf9fe83969c21ee4d67cf1682f64d6bae1e6abd..9b304a7f29ba6f491cb2672076b4f81bb9d1a3f3 100644 --- a/lib/ui/widgets/abstracts/custom_bar_chart.dart +++ b/lib/ui/widgets/abstracts/custom_bar_chart.dart @@ -5,7 +5,9 @@ import 'package:scrobbles/ui/widgets/abstracts/custom_chart.dart'; import 'package:scrobbles/utils/color_extensions.dart'; class CustomBarChart extends CustomChart { - const CustomBarChart({super.key}); + const CustomBarChart({ + super.key, + }); @override Widget build(BuildContext context) { diff --git a/lib/ui/widgets/abstracts/custom_chart.dart b/lib/ui/widgets/abstracts/custom_chart.dart index 6d220180bbf652b233bdc2d3c7873b97cebc2929..3d53fe1d0bccd06d0096615e19e7b0bb813a657a 100644 --- a/lib/ui/widgets/abstracts/custom_chart.dart +++ b/lib/ui/widgets/abstracts/custom_chart.dart @@ -7,10 +7,15 @@ import 'package:scrobbles/config/app_colors.dart'; class CustomChart extends StatelessWidget { const CustomChart({super.key}); - final double chartHeight = 150.0; - final double verticalTicksInterval = 10; - final String verticalAxisTitleSuffix = ''; - final double titleFontSize = 9; + static const double defaultChartHeight = 150.0; + static const double defaultVerticalTicksInterval = 10; + static const String defaultVerticalAxisTitleSuffix = ''; + static const double defaultTitleFontSize = 9; + + double get chartHeight => defaultChartHeight; + double get verticalTicksInterval => defaultVerticalTicksInterval; + String get verticalAxisTitleSuffix => defaultVerticalAxisTitleSuffix; + double get titleFontSize => defaultTitleFontSize; @override Widget build(BuildContext context) { diff --git a/lib/ui/widgets/abstracts/custom_line_chart.dart b/lib/ui/widgets/abstracts/custom_line_chart.dart index 941b44472281cd6661cc6758cf32cac039a8c939..295af4fec2807bcc0debe0a18e5eabecb31c675f 100644 --- a/lib/ui/widgets/abstracts/custom_line_chart.dart +++ b/lib/ui/widgets/abstracts/custom_line_chart.dart @@ -4,7 +4,9 @@ import 'package:flutter/material.dart'; import 'package:scrobbles/ui/widgets/abstracts/custom_chart.dart'; class CustomLineChart extends CustomChart { - const CustomLineChart({super.key}); + const CustomLineChart({ + super.key, + }); @override Widget build(BuildContext context) { diff --git a/lib/ui/widgets/app_bar.dart b/lib/ui/widgets/app_bar.dart deleted file mode 100644 index cef615a27e52bce077d0d02ce5a0d00ce0692252..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/app_bar.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:unicons/unicons.dart'; - -import 'package:scrobbles/ui/widgets/app_titles.dart'; - -class StandardAppBar extends StatelessWidget implements PreferredSizeWidget { - final Function() notifyParent; - - const StandardAppBar({super.key, required this.notifyParent}); - - @override - Widget build(BuildContext context) { - return AppBar( - title: const AppTitle(text: 'app_name'), - actions: [ - IconButton( - onPressed: () { - notifyParent(); - }, - icon: const Icon(UniconsSolid.refresh), - ), - ], - ); - } - - @override - Size get preferredSize => const Size.fromHeight(50); -} diff --git a/lib/ui/widgets/bottom_nav_bar.dart b/lib/ui/widgets/bottom_nav_bar.dart deleted file mode 100644 index 8e00166e3dbd4711001fdae2f4c528180b095649..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/bottom_nav_bar.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_swipe/flutter_swipe.dart'; -import 'package:ionicons/ionicons.dart'; - -import 'package:scrobbles/cubit/bottom_nav_cubit.dart'; - -class BottomNavBar extends StatelessWidget { - const BottomNavBar({super.key, required this.swipeController}); - - final SwiperController swipeController; - - @override - Widget build(BuildContext context) { - return Card( - margin: const EdgeInsets.all(0), - elevation: 4, - shadowColor: Theme.of(context).colorScheme.shadow, - color: Theme.of(context).colorScheme.surfaceVariant, - shape: const ContinuousRectangleBorder(), - child: BlocBuilder<BottomNavCubit, int>( - builder: (BuildContext context, int state) { - return BottomNavigationBar( - currentIndex: state, - onTap: (int index) { - context.read<BottomNavCubit>().updateIndex(index); - swipeController.move(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'), - ), - BottomNavigationBarItem( - icon: const Icon(Ionicons.settings_outline), - label: tr('bottom_nav_settings'), - ), - ], - ); - }, - ), - ); - } -} diff --git a/lib/ui/widgets/card_content.dart b/lib/ui/widgets/card_content.dart index 0d222cebd72e957a122e9d4012ccd21be8dbb52a..68587f70f622afc42aeb9908cb23c20f61b70137 100644 --- a/lib/ui/widgets/card_content.dart +++ b/lib/ui/widgets/card_content.dart @@ -1,18 +1,19 @@ import 'package:flutter/material.dart'; -import 'package:scrobbles/ui/widgets/app_titles.dart'; + +import 'package:scrobbles/ui/helpers/app_titles.dart'; class CardContent extends StatelessWidget { const CardContent({ super.key, required this.title, required this.color, - required this.loader, + required this.status, required this.content, }); final String title; final Color color; - final Widget loader; + final String status; final Widget content; @override @@ -36,8 +37,10 @@ class CardContent extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - AppTitle1(text: title), - loader, + AppTitle(text: title), + (status == 'loading') + ? const Text('â³') + : ((status == 'failed') ? const Text('âš ï¸') : const Text('')), ], ), const SizedBox(height: 8), diff --git a/lib/ui/widgets/cards/counts_by_day.dart b/lib/ui/widgets/cards/counts_by_day.dart index 5ece893b43f5d5bff5a8ca7f405fad109f7d2956..917183ed62d13355e989b2a871fe88975d45e3ac 100644 --- a/lib/ui/widgets/cards/counts_by_day.dart +++ b/lib/ui/widgets/cards/counts_by_day.dart @@ -3,20 +3,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:scrobbles/cubit/data_counts_by_day_cubit.dart'; -import 'package:scrobbles/cubit/settings_cubit.dart'; -import 'package:scrobbles/models/counts_by_day.dart'; -import 'package:scrobbles/network/scrobbles.dart'; +import 'package:scrobbles/cubit/settings_global_cubit.dart'; +import 'package:scrobbles/models/data/counts_by_day.dart'; import 'package:scrobbles/ui/widgets/card_content.dart'; import 'package:scrobbles/ui/widgets/charts/counts_by_day.dart'; -import 'package:scrobbles/ui/widgets/error.dart'; class CardCountsByDay extends StatelessWidget { const CardCountsByDay({super.key}); @override Widget build(BuildContext context) { - SettingsCubit settings = BlocProvider.of<SettingsCubit>(context); - + GlobalSettingsCubit settings = BlocProvider.of<GlobalSettingsCubit>(context); final int daysCount = settings.getDistributionDaysCount(); return BlocBuilder<DataCountsByDayCubit, DataCountsByDayState>( @@ -30,34 +27,10 @@ class CardCountsByDay extends StatelessWidget { 'daysCount': daysCount.toString(), }, ), - loader: update(daysCount), + status: data.status, content: ChartCountsByDay(chartData: counts), ); }, ); } - - Widget update(int daysCount) { - const Widget loading = Text('â³'); - const Widget done = Text(''); - - late Future<CountsByDayData> future = ScrobblesApi.fetchCountsByDay(daysCount); - - return BlocBuilder<DataCountsByDayCubit, DataCountsByDayState>( - builder: (BuildContext context, DataCountsByDayState data) { - return FutureBuilder<CountsByDayData>( - future: future, - builder: (context, snapshot) { - if (snapshot.hasError) { - return ShowErrorWidget(message: '${snapshot.error}'); - } - - BlocProvider.of<DataCountsByDayCubit>(context).update(snapshot.data); - - return !snapshot.hasData ? loading : done; - }, - ); - }, - ); - } } diff --git a/lib/ui/widgets/cards/counts_by_hour.dart b/lib/ui/widgets/cards/counts_by_hour.dart index f45c0537372b058120831f0c25653663859f8279..6931246700a8d53a8cc54af1808e0bf7c87769dd 100644 --- a/lib/ui/widgets/cards/counts_by_hour.dart +++ b/lib/ui/widgets/cards/counts_by_hour.dart @@ -3,20 +3,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:scrobbles/cubit/data_counts_by_hour_cubit.dart'; -import 'package:scrobbles/cubit/settings_cubit.dart'; -import 'package:scrobbles/models/counts_by_hour.dart'; -import 'package:scrobbles/network/scrobbles.dart'; +import 'package:scrobbles/cubit/settings_global_cubit.dart'; +import 'package:scrobbles/models/data/counts_by_hour.dart'; import 'package:scrobbles/ui/widgets/card_content.dart'; import 'package:scrobbles/ui/widgets/charts/counts_by_hour.dart'; -import 'package:scrobbles/ui/widgets/error.dart'; class CardCountsByHour extends StatelessWidget { const CardCountsByHour({super.key}); @override Widget build(BuildContext context) { - SettingsCubit settings = BlocProvider.of<SettingsCubit>(context); - + GlobalSettingsCubit settings = BlocProvider.of<GlobalSettingsCubit>(context); final int daysCount = settings.getDistributionDaysCount(); return BlocBuilder<DataCountsByHourCubit, DataCountsByHourState>( @@ -30,34 +27,10 @@ class CardCountsByHour extends StatelessWidget { 'daysCount': daysCount.toString(), }, ), - loader: update(daysCount), + status: data.status, content: ChartCountsByHour(chartData: counts), ); }, ); } - - Widget update(int daysCount) { - const Widget loading = Text('â³'); - const Widget done = Text(''); - - late Future<CountsByHourData> future = ScrobblesApi.fetchCountsByHour(daysCount); - - return BlocBuilder<DataCountsByHourCubit, DataCountsByHourState>( - builder: (BuildContext context, DataCountsByHourState data) { - return FutureBuilder<CountsByHourData>( - future: future, - builder: (context, snapshot) { - if (snapshot.hasError) { - return ShowErrorWidget(message: '${snapshot.error}'); - } - - BlocProvider.of<DataCountsByHourCubit>(context).update(snapshot.data); - - return !snapshot.hasData ? loading : done; - }, - ); - }, - ); - } } diff --git a/lib/ui/widgets/cards/discoveries.dart b/lib/ui/widgets/cards/discoveries.dart index 415a68f11330741d0303d87ec56dcfb22fdeded1..c0ec4852a5fe77caacc764f57365be2aa4e35bf4 100644 --- a/lib/ui/widgets/cards/discoveries.dart +++ b/lib/ui/widgets/cards/discoveries.dart @@ -3,21 +3,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:scrobbles/cubit/data_discoveries_cubit.dart'; -import 'package:scrobbles/cubit/settings_cubit.dart'; -import 'package:scrobbles/models/discoveries.dart'; -import 'package:scrobbles/network/scrobbles.dart'; +import 'package:scrobbles/cubit/settings_global_cubit.dart'; +import 'package:scrobbles/models/data/discoveries.dart'; import 'package:scrobbles/ui/widgets/card_content.dart'; import 'package:scrobbles/ui/widgets/charts/discoveries_artists.dart'; import 'package:scrobbles/ui/widgets/charts/discoveries_tracks.dart'; -import 'package:scrobbles/ui/widgets/error.dart'; class CardDiscoveries extends StatelessWidget { const CardDiscoveries({super.key}); @override Widget build(BuildContext context) { - SettingsCubit settings = BlocProvider.of<SettingsCubit>(context); - + GlobalSettingsCubit settings = BlocProvider.of<GlobalSettingsCubit>(context); final int daysCount = settings.getDiscoveriesDaysCount(); return BlocBuilder<DataDiscoveriesCubit, DataDiscoveriesState>( @@ -33,7 +30,7 @@ class CardDiscoveries extends StatelessWidget { 'daysCount': daysCount.toString(), }, ), - loader: update(daysCount), + status: data.status, content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -57,28 +54,4 @@ class CardDiscoveries extends StatelessWidget { }, ); } - - Widget update(int daysCount) { - const Widget loading = Text('â³'); - const Widget done = Text(''); - - late Future<DiscoveriesData> future = ScrobblesApi.fetchDiscoveries(daysCount); - - return BlocBuilder<DataDiscoveriesCubit, DataDiscoveriesState>( - builder: (BuildContext context, DataDiscoveriesState data) { - return FutureBuilder<DiscoveriesData>( - future: future, - builder: (context, snapshot) { - if (snapshot.hasError) { - return ShowErrorWidget(message: '${snapshot.error}'); - } - - BlocProvider.of<DataDiscoveriesCubit>(context).update(snapshot.data); - - return !snapshot.hasData ? loading : done; - }, - ); - }, - ); - } } diff --git a/lib/ui/widgets/cards/heatmap.dart b/lib/ui/widgets/cards/heatmap.dart index 11dc86931ad7c84156c63aa5b0763de317ee8700..e89318bc6f826cd36c1a1edf698c5b62a0890a86 100644 --- a/lib/ui/widgets/cards/heatmap.dart +++ b/lib/ui/widgets/cards/heatmap.dart @@ -3,20 +3,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:scrobbles/cubit/data_heatmap_cubit.dart'; -import 'package:scrobbles/cubit/settings_cubit.dart'; -import 'package:scrobbles/models/heatmap.dart'; -import 'package:scrobbles/network/scrobbles.dart'; +import 'package:scrobbles/cubit/settings_global_cubit.dart'; +import 'package:scrobbles/models/data/heatmap.dart'; import 'package:scrobbles/ui/widgets/card_content.dart'; import 'package:scrobbles/ui/widgets/charts/heatmap.dart'; -import 'package:scrobbles/ui/widgets/error.dart'; class CardHeatmap extends StatelessWidget { const CardHeatmap({super.key}); @override Widget build(BuildContext context) { - SettingsCubit settings = BlocProvider.of<SettingsCubit>(context); - + GlobalSettingsCubit settings = BlocProvider.of<GlobalSettingsCubit>(context); final int daysCount = settings.getDistributionDaysCount(); return BlocBuilder<DataHeatmapCubit, DataHeatmapState>( @@ -30,34 +27,10 @@ class CardHeatmap extends StatelessWidget { 'daysCount': daysCount.toString(), }, ), - loader: update(daysCount), + status: data.status, content: ChartHeatmap(chartData: heatmap), ); }, ); } - - Widget update(int daysCount) { - const Widget loading = Text('â³'); - const Widget done = Text(''); - - late Future<HeatmapData> future = ScrobblesApi.fetchHeatmap(daysCount); - - return BlocBuilder<DataHeatmapCubit, DataHeatmapState>( - builder: (BuildContext context, DataHeatmapState data) { - return FutureBuilder<HeatmapData>( - future: future, - builder: (context, snapshot) { - if (snapshot.hasError) { - return ShowErrorWidget(message: '${snapshot.error}'); - } - - BlocProvider.of<DataHeatmapCubit>(context).update(snapshot.data); - - return !snapshot.hasData ? loading : done; - }, - ); - }, - ); - } } diff --git a/lib/ui/widgets/cards/new_artists.dart b/lib/ui/widgets/cards/new_artists.dart index be377fe4ed84e4af9daf5c66140e177e5d2eab60..6c931373494bb84c74f63883dbd1d5305487fac2 100644 --- a/lib/ui/widgets/cards/new_artists.dart +++ b/lib/ui/widgets/cards/new_artists.dart @@ -3,27 +3,19 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:scrobbles/cubit/data_new_artists_cubit.dart'; -import 'package:scrobbles/cubit/settings_cubit.dart'; -import 'package:scrobbles/models/new_artists.dart'; -import 'package:scrobbles/network/scrobbles.dart'; import 'package:scrobbles/ui/widgets/card_content.dart'; -import 'package:scrobbles/ui/widgets/error.dart'; class CardNewArtists extends StatelessWidget { const CardNewArtists({super.key}); @override Widget build(BuildContext context) { - SettingsCubit settings = BlocProvider.of<SettingsCubit>(context); - - final int count = settings.getNewArtistsCount(); - return BlocBuilder<DataNewArtistsCubit, DataNewArtistsState>( builder: (BuildContext context, DataNewArtistsState data) { return CardContent( color: Theme.of(context).colorScheme.surface, title: 'new_artists_title'.tr(), - loader: update(count), + status: data.status, content: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, @@ -36,28 +28,4 @@ class CardNewArtists extends StatelessWidget { }, ); } - - Widget update(int count) { - const Widget loading = Text('â³'); - const Widget done = Text(''); - - late Future<NewArtistsData> future = ScrobblesApi.fetchNewArtists(count); - - return BlocBuilder<DataNewArtistsCubit, DataNewArtistsState>( - builder: (BuildContext context, DataNewArtistsState data) { - return FutureBuilder<NewArtistsData>( - future: future, - builder: (context, snapshot) { - if (snapshot.hasError) { - return ShowErrorWidget(message: '${snapshot.error}'); - } - - BlocProvider.of<DataNewArtistsCubit>(context).update(snapshot.data); - - return !snapshot.hasData ? loading : done; - }, - ); - }, - ); - } } diff --git a/lib/ui/widgets/cards/new_tracks.dart b/lib/ui/widgets/cards/new_tracks.dart index 4787dbfe4ca6ea8f8f46d2973da438df5c4207e4..28c89e66d96344bf0535f6d6ee13310764a52fc7 100644 --- a/lib/ui/widgets/cards/new_tracks.dart +++ b/lib/ui/widgets/cards/new_tracks.dart @@ -3,32 +3,25 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:scrobbles/cubit/data_new_tracks_cubit.dart'; -import 'package:scrobbles/cubit/settings_cubit.dart'; -import 'package:scrobbles/models/new_tracks.dart'; -import 'package:scrobbles/network/scrobbles.dart'; import 'package:scrobbles/ui/widgets/card_content.dart'; -import 'package:scrobbles/ui/widgets/error.dart'; class CardNewTracks extends StatelessWidget { const CardNewTracks({super.key}); @override Widget build(BuildContext context) { - SettingsCubit settings = BlocProvider.of<SettingsCubit>(context); - - final int count = settings.getNewTracksCount(); - return BlocBuilder<DataNewTracksCubit, DataNewTracksState>( builder: (BuildContext context, DataNewTracksState data) { return CardContent( color: Theme.of(context).colorScheme.surface, title: 'new_tracks_title'.tr(), - loader: update(count), + status: data.status, content: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: data.newTracks?.data - .map((newTrack) => Text('${newTrack.track?.artist.name ?? ''} - ${newTrack.track?.name ?? ''}')) + .map((newTrack) => Text( + '${newTrack.track?.artist.name ?? ''} - ${newTrack.track?.name ?? ''}')) .toList() ?? [], ), @@ -36,28 +29,4 @@ class CardNewTracks extends StatelessWidget { }, ); } - - Widget update(int count) { - const Widget loading = Text('â³'); - const Widget done = Text(''); - - late Future<NewTracksData> future = ScrobblesApi.fetchNewTracks(count); - - return BlocBuilder<DataNewTracksCubit, DataNewTracksState>( - builder: (BuildContext context, DataNewTracksState data) { - return FutureBuilder<NewTracksData>( - future: future, - builder: (context, snapshot) { - if (snapshot.hasError) { - return ShowErrorWidget(message: '${snapshot.error}'); - } - - BlocProvider.of<DataNewTracksCubit>(context).update(snapshot.data); - - return !snapshot.hasData ? loading : done; - }, - ); - }, - ); - } } diff --git a/lib/ui/widgets/cards/statistics_global.dart b/lib/ui/widgets/cards/statistics_global.dart index 6477d3d9917ededd8b0e1a49178a9609b039e63e..0d920f740d2392bf955b761ef9efc25307c6b08d 100644 --- a/lib/ui/widgets/cards/statistics_global.dart +++ b/lib/ui/widgets/cards/statistics_global.dart @@ -3,11 +3,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:scrobbles/cubit/data_statistics_global_cubit.dart'; -import 'package:scrobbles/models/statistics_global.dart'; -import 'package:scrobbles/network/scrobbles.dart'; +import 'package:scrobbles/models/data/statistics_global.dart'; import 'package:scrobbles/ui/widgets/card_content.dart'; import 'package:scrobbles/ui/widgets/content/statistics_global.dart'; -import 'package:scrobbles/ui/widgets/error.dart'; class CardStatisticsGlobal extends StatelessWidget { const CardStatisticsGlobal({super.key}); @@ -22,34 +20,10 @@ class CardStatisticsGlobal extends StatelessWidget { return CardContent( color: Theme.of(context).colorScheme.primary, title: 'global_statistics'.tr(), - loader: update(), + status: data.status, content: ContentStatisticsGlobal(statistics: statistics), ); }, ); } - - Widget update() { - const Widget loading = Text('â³'); - const Widget done = Text(''); - - late Future<StatisticsGlobalData> future = ScrobblesApi.fetchGlobalStatistics(); - - return BlocBuilder<DataStatisticsGlobalCubit, DataStatisticsGlobalState>( - builder: (BuildContext context, DataStatisticsGlobalState data) { - return FutureBuilder<StatisticsGlobalData>( - future: future, - builder: (context, snapshot) { - if (snapshot.hasError) { - return ShowErrorWidget(message: '${snapshot.error}'); - } - - BlocProvider.of<DataStatisticsGlobalCubit>(context).update(snapshot.data); - - return !snapshot.hasData ? loading : done; - }, - ); - }, - ); - } } diff --git a/lib/ui/widgets/cards/statistics_recent.dart b/lib/ui/widgets/cards/statistics_recent.dart index 1ef1aa20f3e327459e8fd011562d7ac823a852c9..c04bf23aab0e3daf380f4475989af03b87b9dc51 100644 --- a/lib/ui/widgets/cards/statistics_recent.dart +++ b/lib/ui/widgets/cards/statistics_recent.dart @@ -3,20 +3,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:scrobbles/cubit/data_statistics_recent_cubit.dart'; -import 'package:scrobbles/cubit/settings_cubit.dart'; -import 'package:scrobbles/models/statistics_recent.dart'; -import 'package:scrobbles/network/scrobbles.dart'; +import 'package:scrobbles/cubit/settings_global_cubit.dart'; +import 'package:scrobbles/models/data/statistics_recent.dart'; import 'package:scrobbles/ui/widgets/card_content.dart'; import 'package:scrobbles/ui/widgets/content/statistics_recent.dart'; -import 'package:scrobbles/ui/widgets/error.dart'; class CardStatisticsRecent extends StatelessWidget { const CardStatisticsRecent({super.key}); @override Widget build(BuildContext context) { - SettingsCubit settings = BlocProvider.of<SettingsCubit>(context); - + GlobalSettingsCubit settings = BlocProvider.of<GlobalSettingsCubit>(context); final int daysCount = settings.getStatisticsRecentDaysCount(); return BlocBuilder<DataStatisticsRecentCubit, DataStatisticsRecentState>( @@ -31,34 +28,10 @@ class CardStatisticsRecent extends StatelessWidget { 'daysCount': daysCount.toString(), }, ), - loader: update(daysCount), + status: data.status, content: ContentStatisticsRecent(statistics: statistics), ); }, ); } - - Widget update(int daysCount) { - const Widget loading = Text('â³'); - const Widget done = Text(''); - - late Future<StatisticsRecentData> future = ScrobblesApi.fetchRecentStatistics(daysCount); - - return BlocBuilder<DataStatisticsRecentCubit, DataStatisticsRecentState>( - builder: (BuildContext context, DataStatisticsRecentState data) { - return FutureBuilder<StatisticsRecentData>( - future: future, - builder: (context, snapshot) { - if (snapshot.hasError) { - return ShowErrorWidget(message: '${snapshot.error}'); - } - - BlocProvider.of<DataStatisticsRecentCubit>(context).update(snapshot.data); - - return !snapshot.hasData ? loading : done; - }, - ); - }, - ); - } } diff --git a/lib/ui/widgets/cards/timeline.dart b/lib/ui/widgets/cards/timeline.dart index 8e551bf0544f4edba8e8f7c0d3e424cdbe40e73c..9524a10210256006fe960ff6f2e01f47b7fb14b0 100644 --- a/lib/ui/widgets/cards/timeline.dart +++ b/lib/ui/widgets/cards/timeline.dart @@ -3,21 +3,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:scrobbles/cubit/data_timeline_cubit.dart'; -import 'package:scrobbles/cubit/settings_cubit.dart'; -import 'package:scrobbles/models/timeline.dart'; -import 'package:scrobbles/network/scrobbles.dart'; +import 'package:scrobbles/cubit/settings_global_cubit.dart'; +import 'package:scrobbles/models/data/timeline.dart'; import 'package:scrobbles/ui/widgets/card_content.dart'; import 'package:scrobbles/ui/widgets/charts/timeline_counts.dart'; import 'package:scrobbles/ui/widgets/charts/timeline_eclecticism.dart'; -import 'package:scrobbles/ui/widgets/error.dart'; class CardTimeline extends StatelessWidget { const CardTimeline({super.key}); @override Widget build(BuildContext context) { - SettingsCubit settings = BlocProvider.of<SettingsCubit>(context); - + GlobalSettingsCubit settings = BlocProvider.of<GlobalSettingsCubit>(context); final int daysCount = settings.getTimelineDaysCount(); return BlocBuilder<DataTimelineCubit, DataTimelineState>( @@ -31,7 +28,7 @@ class CardTimeline extends StatelessWidget { 'daysCount': daysCount.toString(), }, ), - loader: update(daysCount), + status: data.status, content: Stack( children: [ ChartTimelineCounts(chartData: timeline), @@ -42,28 +39,4 @@ class CardTimeline extends StatelessWidget { }, ); } - - Widget update(int daysCount) { - const Widget loading = Text('â³'); - const Widget done = Text(''); - - late Future<TimelineData> future = ScrobblesApi.fetchTimeline(daysCount); - - return BlocBuilder<DataTimelineCubit, DataTimelineState>( - builder: (BuildContext context, DataTimelineState data) { - return FutureBuilder<TimelineData>( - future: future, - builder: (context, snapshot) { - if (snapshot.hasError) { - return ShowErrorWidget(message: '${snapshot.error}'); - } - - BlocProvider.of<DataTimelineCubit>(context).update(snapshot.data); - - return !snapshot.hasData ? loading : done; - }, - ); - }, - ); - } } diff --git a/lib/ui/widgets/cards/top_artists.dart b/lib/ui/widgets/cards/top_artists.dart index 2830bfa8c3c4903f3d12a34c87866aa9de241c3b..20a50e9ca6425789e56022385f8106f7d7b2cb57 100644 --- a/lib/ui/widgets/cards/top_artists.dart +++ b/lib/ui/widgets/cards/top_artists.dart @@ -3,21 +3,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:scrobbles/cubit/data_top_artists_cubit.dart'; -import 'package:scrobbles/cubit/settings_cubit.dart'; -import 'package:scrobbles/models/topartists.dart'; -import 'package:scrobbles/network/scrobbles.dart'; +import 'package:scrobbles/cubit/settings_global_cubit.dart'; +import 'package:scrobbles/models/data/topartists.dart'; import 'package:scrobbles/ui/widgets/card_content.dart'; import 'package:scrobbles/ui/widgets/charts/top_artists.dart'; import 'package:scrobbles/ui/widgets/charts/top_artists_stream.dart'; -import 'package:scrobbles/ui/widgets/error.dart'; class CardTopArtists extends StatelessWidget { const CardTopArtists({super.key}); @override Widget build(BuildContext context) { - SettingsCubit settings = BlocProvider.of<SettingsCubit>(context); - + GlobalSettingsCubit settings = BlocProvider.of<GlobalSettingsCubit>(context); final int daysCount = settings.getTopArtistsDaysCount(); return BlocBuilder<DataTopArtistsCubit, DataTopArtistsState>( @@ -31,7 +28,7 @@ class CardTopArtists extends StatelessWidget { 'daysCount': daysCount.toString(), }, ), - loader: update(daysCount), + status: data.status, content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -45,28 +42,4 @@ class CardTopArtists extends StatelessWidget { }, ); } - - Widget update(int daysCount) { - const Widget loading = Text('â³'); - const Widget done = Text(''); - - late Future<TopArtistsData> future = ScrobblesApi.fetchTopArtists(daysCount); - - return BlocBuilder<DataTopArtistsCubit, DataTopArtistsState>( - builder: (BuildContext context, DataTopArtistsState update) { - return FutureBuilder<TopArtistsData>( - future: future, - builder: (context, snapshot) { - if (snapshot.hasError) { - return ShowErrorWidget(message: '${snapshot.error}'); - } - - BlocProvider.of<DataTopArtistsCubit>(context).update(snapshot.data); - - return !snapshot.hasData ? loading : done; - }, - ); - }, - ); - } } diff --git a/lib/ui/widgets/charts/counts_by_day.dart b/lib/ui/widgets/charts/counts_by_day.dart index c43fc0d71fe4a0f26cd9337445e7d24cba3fa172..805046929cdd77f4ec293d31f15b3594dbbf7525 100644 --- a/lib/ui/widgets/charts/counts_by_day.dart +++ b/lib/ui/widgets/charts/counts_by_day.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:fl_chart/fl_chart.dart'; -import 'package:scrobbles/models/counts_by_day.dart'; +import 'package:scrobbles/models/data/counts_by_day.dart'; import 'package:scrobbles/ui/widgets/abstracts/custom_bar_chart.dart'; class ChartCountsByDay extends CustomBarChart { @@ -10,9 +10,9 @@ class ChartCountsByDay extends CustomBarChart { final CountsByDayData chartData; @override - final double verticalTicksInterval = 5; + double get verticalTicksInterval => 5; @override - final String verticalAxisTitleSuffix = '%'; + String get verticalAxisTitleSuffix => '%'; @override Widget build(BuildContext context) { @@ -31,7 +31,7 @@ class ChartCountsByDay extends CustomBarChart { return getBarChart( barWidth: getBarWidth(maxWidth, barsCount), - backgroundColor: Theme.of(context).colorScheme.background, + backgroundColor: Theme.of(context).colorScheme.surface, ); }, ), diff --git a/lib/ui/widgets/charts/counts_by_hour.dart b/lib/ui/widgets/charts/counts_by_hour.dart index 1a1c1539289a06e2f98f355924a2ffc80adcbc7f..a4c575cace8d78691f980751d02a3da22d779a03 100644 --- a/lib/ui/widgets/charts/counts_by_hour.dart +++ b/lib/ui/widgets/charts/counts_by_hour.dart @@ -2,19 +2,22 @@ import 'package:flutter/material.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:scrobbles/config/app_colors.dart'; -import 'package:scrobbles/models/counts_by_hour.dart'; +import 'package:scrobbles/models/data/counts_by_hour.dart'; import 'package:scrobbles/ui/widgets/abstracts/custom_bar_chart.dart'; import 'package:scrobbles/utils/color_extensions.dart'; class ChartCountsByHour extends CustomBarChart { - const ChartCountsByHour({super.key, required this.chartData}); + const ChartCountsByHour({ + super.key, + required this.chartData, + }); final CountsByHourData chartData; @override - final double verticalTicksInterval = 5; + double get verticalTicksInterval => 5; @override - final String verticalAxisTitleSuffix = '%'; + String get verticalAxisTitleSuffix => '%'; @override Widget build(BuildContext context) { @@ -33,7 +36,7 @@ class ChartCountsByHour extends CustomBarChart { return getBarChart( barWidth: getBarWidth(maxWidth, barsCount), - backgroundColor: Theme.of(context).colorScheme.background, + backgroundColor: Theme.of(context).colorScheme.surface, ); }, ), diff --git a/lib/ui/widgets/charts/discoveries_artists.dart b/lib/ui/widgets/charts/discoveries_artists.dart index 98196656e55717b1bda9e8733aa5bce7a2398c39..75a4034b3da2fa4ce8cf9f43a81d71191c5a4616 100644 --- a/lib/ui/widgets/charts/discoveries_artists.dart +++ b/lib/ui/widgets/charts/discoveries_artists.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:scrobbles/config/app_colors.dart'; -import 'package:scrobbles/models/discoveries.dart'; +import 'package:scrobbles/models/data/discoveries.dart'; import 'package:scrobbles/ui/widgets/abstracts/custom_bar_chart.dart'; import 'package:scrobbles/utils/color_extensions.dart'; @@ -28,7 +28,7 @@ class ChartDiscoveriesArtists extends CustomBarChart { return getBarChart( barWidth: getBarWidth(maxWidth, barsCount), - backgroundColor: Theme.of(context).colorScheme.background, + backgroundColor: Theme.of(context).colorScheme.surface, ); }, ), diff --git a/lib/ui/widgets/charts/discoveries_tracks.dart b/lib/ui/widgets/charts/discoveries_tracks.dart index 2f4626dcdad2ad8c0bfcf8d7c0364595a5b38c56..eae77f8946e09982e599288d75750a83f57c29c5 100644 --- a/lib/ui/widgets/charts/discoveries_tracks.dart +++ b/lib/ui/widgets/charts/discoveries_tracks.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:scrobbles/config/app_colors.dart'; -import 'package:scrobbles/models/discoveries.dart'; +import 'package:scrobbles/models/data/discoveries.dart'; import 'package:scrobbles/ui/widgets/abstracts/custom_bar_chart.dart'; import 'package:scrobbles/utils/color_extensions.dart'; @@ -28,7 +28,7 @@ class ChartDiscoveriesTracks extends CustomBarChart { return getBarChart( barWidth: getBarWidth(maxWidth, barsCount), - backgroundColor: Theme.of(context).colorScheme.background, + backgroundColor: Theme.of(context).colorScheme.surface, ); }, ), diff --git a/lib/ui/widgets/charts/heatmap.dart b/lib/ui/widgets/charts/heatmap.dart index 0625dbfb332f795d310b16bf10c4e50f7bf2f11a..fcb34155ff87c3d4145ed691c938c5285ca76bba 100644 --- a/lib/ui/widgets/charts/heatmap.dart +++ b/lib/ui/widgets/charts/heatmap.dart @@ -3,7 +3,7 @@ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:scrobbles/config/app_colors.dart'; -import 'package:scrobbles/models/heatmap.dart'; +import 'package:scrobbles/models/data/heatmap.dart'; import 'package:scrobbles/ui/widgets/abstracts/custom_chart.dart'; class ChartHeatmap extends CustomChart { @@ -12,9 +12,10 @@ class ChartHeatmap extends CustomChart { final HeatmapData chartData; @override - final double chartHeight = 150.0; + double get chartHeight => 150.0; @override - final double titleFontSize = 9; + double get titleFontSize => 9; + final Color baseColor = AppColors.contentColorPink; final double scale = 8.0; final double darkenAmount = 50; diff --git a/lib/ui/widgets/charts/timeline_counts.dart b/lib/ui/widgets/charts/timeline_counts.dart index 4357774255a3d9f29c2242b5717e92de4cdddc14..eef0fd8189271bd10528751c689b059c9fc00fbe 100644 --- a/lib/ui/widgets/charts/timeline_counts.dart +++ b/lib/ui/widgets/charts/timeline_counts.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:scrobbles/config/app_colors.dart'; -import 'package:scrobbles/models/timeline.dart'; +import 'package:scrobbles/models/data/timeline.dart'; import 'package:scrobbles/ui/widgets/abstracts/custom_bar_chart.dart'; class ChartTimelineCounts extends CustomBarChart { @@ -11,7 +11,7 @@ class ChartTimelineCounts extends CustomBarChart { final TimelineData chartData; @override - final double verticalTicksInterval = 50; + double get verticalTicksInterval => 50; @override Widget build(BuildContext context) { @@ -30,7 +30,7 @@ class ChartTimelineCounts extends CustomBarChart { return getBarChart( barWidth: getBarWidth(maxWidth, barsCount), - backgroundColor: Theme.of(context).colorScheme.background, + backgroundColor: Theme.of(context).colorScheme.surface, ); }, ), diff --git a/lib/ui/widgets/charts/timeline_eclecticism.dart b/lib/ui/widgets/charts/timeline_eclecticism.dart index 3eeffe7b460368c5ada894a44d7e6c3ee397a4bd..bab15c0dd5dbf42fb591db199e6cd63538366376 100644 --- a/lib/ui/widgets/charts/timeline_eclecticism.dart +++ b/lib/ui/widgets/charts/timeline_eclecticism.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:scrobbles/config/app_colors.dart'; -import 'package:scrobbles/models/timeline.dart'; +import 'package:scrobbles/models/data/timeline.dart'; import 'package:scrobbles/ui/widgets/abstracts/custom_line_chart.dart'; import 'package:scrobbles/utils/color_extensions.dart'; @@ -12,7 +12,7 @@ class ChartTimelineEclecticism extends CustomLineChart { final TimelineData chartData; @override - final String verticalAxisTitleSuffix = '%'; + String get verticalAxisTitleSuffix => '%'; @override Widget build(BuildContext context) { diff --git a/lib/ui/widgets/charts/top_artists.dart b/lib/ui/widgets/charts/top_artists.dart index 79b39d317d86516367915ba2e5ba6b270403a337..f3a0181ad6e3000955f225e976fd154ffa0ec7c1 100644 --- a/lib/ui/widgets/charts/top_artists.dart +++ b/lib/ui/widgets/charts/top_artists.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:fl_chart/fl_chart.dart'; -import 'package:scrobbles/models/topartists.dart'; +import 'package:scrobbles/models/data/topartists.dart'; import 'package:scrobbles/ui/widgets/abstracts/custom_chart.dart'; import 'package:scrobbles/utils/color_extensions.dart'; diff --git a/lib/ui/widgets/charts/top_artists_stream.dart b/lib/ui/widgets/charts/top_artists_stream.dart index 523c24dd3e2d33514d5dedfdd291f458f8220392..4f657e70d0763cefbc42dd7dbb0a523b57773275 100644 --- a/lib/ui/widgets/charts/top_artists_stream.dart +++ b/lib/ui/widgets/charts/top_artists_stream.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:fl_chart/fl_chart.dart'; -import 'package:scrobbles/models/topartists.dart'; +import 'package:scrobbles/models/data/topartists.dart'; import 'package:scrobbles/ui/widgets/abstracts/custom_line_chart.dart'; import 'package:scrobbles/utils/color_extensions.dart'; @@ -11,9 +11,9 @@ class ChartTopArtistsStream extends CustomLineChart { final TopArtistsData chartData; @override - final double verticalTicksInterval = 10; + double get verticalTicksInterval => 10; @override - final String verticalAxisTitleSuffix = '%'; + String get verticalAxisTitleSuffix => '%'; @override Widget build(BuildContext context) { diff --git a/lib/ui/widgets/content/statistics_global.dart b/lib/ui/widgets/content/statistics_global.dart index 96a678aa156b34839be6229fb79cffd458655ab4..5da9330783cfaabd2962120b19ded32391b685ce 100644 --- a/lib/ui/widgets/content/statistics_global.dart +++ b/lib/ui/widgets/content/statistics_global.dart @@ -1,7 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:scrobbles/models/statistics_global.dart'; +import 'package:scrobbles/models/data/statistics_global.dart'; class ContentStatisticsGlobal extends StatelessWidget { const ContentStatisticsGlobal({super.key, required this.statistics}); diff --git a/lib/ui/widgets/content/statistics_recent.dart b/lib/ui/widgets/content/statistics_recent.dart index 678623a0f9ca98ee8eb2eb3d1c7eb6a8b06fb052..6c9ba8618333081b5530677bb5996a27a8edb694 100644 --- a/lib/ui/widgets/content/statistics_recent.dart +++ b/lib/ui/widgets/content/statistics_recent.dart @@ -1,7 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:scrobbles/models/statistics_recent.dart'; +import 'package:scrobbles/models/data/statistics_recent.dart'; class ContentStatisticsRecent extends StatelessWidget { const ContentStatisticsRecent({super.key, required this.statistics}); diff --git a/lib/ui/widgets/error.dart b/lib/ui/widgets/error.dart deleted file mode 100644 index aa3d5cd267b3a9ab2903518f8f789bf1b23a2cd6..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/error.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; - -import 'package:scrobbles/utils/tools.dart'; - -class ShowErrorWidget extends StatelessWidget { - const ShowErrorWidget({super.key, required this.message}); - - final String message; - - @override - Widget build(BuildContext context) { - printlog(message); - - return Text( - 'âš ï¸ ${tr(message)}', - textAlign: TextAlign.start, - style: const TextStyle(color: Colors.red), - ); - } -} diff --git a/pubspec.lock b/pubspec.lock index ce3fe9ef11c8a15f00862a7070d897266cfe279c..08aca7896ceb6709dbb6434b464b96237e439c8f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.5.0" async: dependency: transitive description: @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: bloc - sha256: f53a110e3b48dcd78136c10daa5d51512443cea5e1348c9d80a320095fa2db9e + sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e" url: "https://pub.dev" source: hosted - version: "8.1.3" + version: "8.1.4" characters: dependency: transitive description: @@ -61,10 +61,10 @@ packages: dependency: "direct main" description: name: easy_localization - sha256: c145aeb6584aedc7c862ab8c737c3277788f47488bfdf9bae0fe112bd0a4789c + sha256: fa59bcdbbb911a764aa6acf96bbb6fa7a5cf8234354fc45ec1a43a0349ef0201 url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.7" easy_logger: dependency: transitive description: @@ -101,10 +101,10 @@ packages: dependency: "direct main" description: name: fl_chart - sha256: "00b74ae680df6b1135bdbea00a7d1fc072a9180b7c3f3702e4b19a9943f5ed7d" + sha256: d0f0d49112f2f4b192481c16d05b6418bd7820e021e265a3c22db98acf7ed7fb url: "https://pub.dev" source: hosted - version: "0.66.2" + version: "0.68.0" flutter: dependency: "direct main" description: flutter @@ -114,18 +114,18 @@ packages: dependency: "direct main" description: name: flutter_bloc - sha256: "87325da1ac757fcc4813e6b34ed5dd61169973871fdf181d6c2109dd6935ece1" + sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a url: "https://pub.dev" source: hosted - version: "8.1.4" + version: "8.1.6" flutter_lints: dependency: "direct dev" description: name: flutter_lints - sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7 + sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "4.0.0" flutter_localizations: dependency: transitive description: flutter @@ -172,18 +172,18 @@ packages: dependency: "direct main" description: name: hydrated_bloc - sha256: "00a2099680162e74b5a836b8a7f446e478520a9cae9f6032e028ad8129f4432d" + sha256: af35b357739fe41728df10bec03aad422cdc725a1e702e03af9d2a41ea05160c url: "https://pub.dev" source: hosted - version: "9.1.4" + version: "9.1.5" intl: dependency: transitive description: name: intl - sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf url: "https://pub.dev" source: hosted - version: "0.18.1" + version: "0.19.0" ionicons: dependency: "direct main" description: @@ -196,10 +196,10 @@ packages: dependency: transitive description: name: lints - sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "4.0.0" material_color_utilities: dependency: transitive description: @@ -212,10 +212,10 @@ packages: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.12.0" nested: dependency: transitive description: @@ -224,6 +224,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + sha256: b93d8b4d624b4ea19b0a5a208b2d6eff06004bc3ce74c06040b120eeadd00ce0 + url: "https://pub.dev" + source: hosted + version: "8.0.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: f49918f3433a3146047372f9d4f1f847511f2acd5cd030e1f44fe5a50036b70e + url: "https://pub.dev" + source: hosted + version: "3.0.0" path: dependency: transitive description: @@ -236,26 +252,26 @@ packages: dependency: "direct main" description: name: path_provider - sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" + sha256: bca87b0165ffd7cdb9cad8edd22d18d2201e886d9a9f19b4fb3452ea7df3a72a url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.6" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.0" path_provider_linux: dependency: transitive description: @@ -284,10 +300,10 @@ packages: dependency: transitive description: name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: @@ -308,26 +324,26 @@ packages: dependency: transitive description: name: shared_preferences - sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" + sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180 url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.3" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" + sha256: "93d0ec9dd902d85f326068e6a899487d1f65ffcd5798721a95330b26c8131577" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.3" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" + sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7" url: "https://pub.dev" source: hosted - version: "2.3.5" + version: "2.4.0" shared_preferences_linux: dependency: transitive description: @@ -409,10 +425,10 @@ packages: dependency: "direct main" description: name: unicons - sha256: dbfcf93ff4d4ea19b324113857e358e4882115ab85db04417a4ba1c72b17a670 + sha256: "1cca7462df18ff191b7e41b52f747d08854916531d1d7ab7cec0552095995206" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" vector_math: dependency: transitive description: @@ -425,18 +441,18 @@ packages: dependency: transitive description: name: web - sha256: "1d9158c616048c38f712a6646e317a3426da10e884447626167240d45209cbad" + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.5.1" win32: dependency: transitive description: name: win32 - sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8" + sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4 url: "https://pub.dev" source: hosted - version: "5.2.0" + version: "5.5.1" xdg_directories: dependency: transitive description: @@ -446,5 +462,5 @@ packages: source: hosted version: "1.0.4" sdks: - dart: ">=3.3.0 <4.0.0" - flutter: ">=3.19.0" + dart: ">=3.4.0 <4.0.0" + flutter: ">=3.22.0" diff --git a/pubspec.yaml b/pubspec.yaml index 8f5f50bd7f3e274c2c874c6042f2a707591e701a..63996e00d6828c22c05b4ba5c71d3496f0ab2eb4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,34 +1,38 @@ name: scrobbles description: Display scrobbles data and charts -publish_to: 'none' +publish_to: "none" -version: 0.0.57+57 +version: 0.1.0+58 environment: - sdk: '^3.0.0' + sdk: "^3.0.0" dependencies: flutter: sdk: flutter + # base easy_localization: ^3.0.1 equatable: ^2.0.5 - fl_chart: ^0.66.0 flutter_bloc: ^8.1.1 hive: ^2.2.3 - http: ^1.1.0 - path_provider: ^2.0.11 hydrated_bloc: ^9.0.0 - ionicons: ^0.2.2 + package_info_plus: ^8.0.0 + path_provider: ^2.0.11 unicons: ^2.1.1 + + # specific + fl_chart: ^0.68.0 flutter_swipe: ^1.0.1 + http: ^1.1.0 + ionicons: ^0.2.2 dev_dependencies: - flutter_lints: ^3.0.1 + flutter_lints: ^4.0.0 flutter: - uses-material-design: false + uses-material-design: true assets: - assets/translations/ @@ -43,3 +47,4 @@ flutter: weight: 400 - asset: assets/fonts/Nunito-Light.ttf weight: 300 + diff --git a/icons/build_repository_icons.sh b/resources/app/build_application_resources.sh similarity index 98% rename from icons/build_repository_icons.sh rename to resources/app/build_application_resources.sh index 27dbe2647fe4e6d562fbd99451716d1b7d448570..6d67b8f4f9eca701d1aed7331ef41dfb0bd44f20 100755 --- a/icons/build_repository_icons.sh +++ b/resources/app/build_application_resources.sh @@ -6,7 +6,7 @@ command -v scour >/dev/null 2>&1 || { echo >&2 "I require scour but it's not ins command -v optipng >/dev/null 2>&1 || { echo >&2 "I require optipng but it's not installed. Aborting."; exit 1; } CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" -BASE_DIR="$(dirname "${CURRENT_DIR}")" +BASE_DIR="$(dirname "$(dirname "${CURRENT_DIR}")")" SOURCE_ICON="${CURRENT_DIR}/icon.svg" SOURCE_FASTLANE="${CURRENT_DIR}/featureGraphic.svg" diff --git a/icons/featureGraphic.svg b/resources/app/featureGraphic.svg similarity index 100% rename from icons/featureGraphic.svg rename to resources/app/featureGraphic.svg diff --git a/icons/icon.svg b/resources/app/icon.svg similarity index 100% rename from icons/icon.svg rename to resources/app/icon.svg diff --git a/resources/build_resources.sh b/resources/build_resources.sh new file mode 100755 index 0000000000000000000000000000000000000000..8d5848a985ed1068f6e6226379a9cedb69c3fa15 --- /dev/null +++ b/resources/build_resources.sh @@ -0,0 +1,6 @@ +#! /bin/bash + +CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" + +${CURRENT_DIR}/app/build_application_resources.sh +