diff --git a/android/app/build.gradle b/android/app/build.gradle index 21a9eb139869ecb2b0876d6317575b9e4357596b..a32ab7a818c4540d1a3d091d0680d829f7283a32 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.plotter" defaultConfig { diff --git a/android/gradle.properties b/android/gradle.properties index 777ac2de0980e935649cf32bd85097eaf789185a..cd2d833ca96b3d1ada4a39df51dc5f5ee67665b7 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.16 -app.versionCode=16 +app.versionName=0.0.17 +app.versionCode=17 diff --git a/assets/fonts/Nunito-Bold.ttf b/assets/fonts/Nunito-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..6519feb781449ebe0015cbc74dfd9e13110fbba9 Binary files /dev/null and b/assets/fonts/Nunito-Bold.ttf differ diff --git a/assets/fonts/Nunito-Light.ttf b/assets/fonts/Nunito-Light.ttf new file mode 100644 index 0000000000000000000000000000000000000000..8a0736c41cd6c2a1225d356bf274de1d0afc3497 Binary files /dev/null and b/assets/fonts/Nunito-Light.ttf differ diff --git a/assets/fonts/Nunito-Medium.ttf b/assets/fonts/Nunito-Medium.ttf new file mode 100644 index 0000000000000000000000000000000000000000..88fccdc0638b6f5d6ac49d9d269dc3d518618ad1 Binary files /dev/null and b/assets/fonts/Nunito-Medium.ttf differ diff --git a/assets/fonts/Nunito-Regular.ttf b/assets/fonts/Nunito-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e7b8375a896ef0cd8e06730a78c84532b377e784 Binary files /dev/null and b/assets/fonts/Nunito-Regular.ttf differ diff --git a/assets/icons/application.png b/assets/icons/application.png deleted file mode 100644 index 20f13b46a5bdbc19ac735249aa0790402548970f..0000000000000000000000000000000000000000 Binary files a/assets/icons/application.png and /dev/null differ diff --git a/assets/translations/en.json b/assets/translations/en.json new file mode 100644 index 0000000000000000000000000000000000000000..b060f0b5f2d6850dfffd8766bfb7bbcd8c732db8 --- /dev/null +++ b/assets/translations/en.json @@ -0,0 +1,16 @@ +{ + "app_name": "Plotter", + + "settings_title": "Settings", + "settings_label_theme": "Theme mode", + "settings_label_api_host": "API host", + + "about_title": "Informations", + "about_content": "Plotter", + "about_version": "Version: {version}", + + "page_home": "Home", + "page_remote_control": "Control", + + "": "" +} diff --git a/assets/translations/fr.json b/assets/translations/fr.json new file mode 100644 index 0000000000000000000000000000000000000000..718515612759a1034a1813885cba17c5388cbca0 --- /dev/null +++ b/assets/translations/fr.json @@ -0,0 +1,16 @@ +{ + "app_name": "Plotter", + + "settings_title": "Réglages", + "settings_label_theme": "Thème de couleurs", + "settings_label_api_host": "URL de l'API", + + "about_title": "Informations", + "about_content": "Plotter.", + "about_version": "Version : {version}", + + "page_home": "Accueil", + "page_remote_control": "Télécommande", + + "": "" +} diff --git a/fastlane/metadata/android/en-US/changelogs/17.txt b/fastlane/metadata/android/en-US/changelogs/17.txt new file mode 100644 index 0000000000000000000000000000000000000000..060b202b87dcd33388eb8a9d1a45cbd276fc8ba3 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/17.txt @@ -0,0 +1 @@ +Improve/normalize app architecture. diff --git a/fastlane/metadata/android/fr-FR/changelogs/17.txt b/fastlane/metadata/android/fr-FR/changelogs/17.txt new file mode 100644 index 0000000000000000000000000000000000000000..3cab228cbe7443782c4b187cbe805e4d0ba8e71a --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/17.txt @@ -0,0 +1 @@ +Amélioration/normalisation de l'architecture de l'application. diff --git a/icons/build_repository_icons.sh b/icons/build_repository_icons.sh deleted file mode 100755 index 569a16a50dd3015a45c291a93305ff0f67f77c72..0000000000000000000000000000000000000000 --- a/icons/build_repository_icons.sh +++ /dev/null @@ -1,48 +0,0 @@ -#! /bin/bash - -# Check dependencies -command -v inkscape >/dev/null 2>&1 || { echo >&2 "I require inkscape but it's not installed. Aborting."; exit 1; } -command -v scour >/dev/null 2>&1 || { echo >&2 "I require scour but it's not installed. Aborting."; exit 1; } -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}")" - -SOURCE="${CURRENT_DIR}/icon.svg" -OPTIPNG_OPTIONS="-preserve -quiet -o7" - -# optimize svg -cp ${SOURCE} ${SOURCE}.tmp -scour \ - --remove-descriptive-elements \ - --enable-id-stripping \ - --enable-viewboxing \ - --enable-comment-stripping \ - --nindent=4 \ - -i ${SOURCE}.tmp \ - -o ${SOURCE} -rm ${SOURCE}.tmp - -# build icons -function build_icon() { - ICON_SIZE="$1" - TARGET="$2" - - TARGET_PNG="${TARGET}.png" - - inkscape \ - --export-width=${ICON_SIZE} \ - --export-height=${ICON_SIZE} \ - --export-filename=${TARGET_PNG} \ - ${SOURCE} - - optipng ${OPTIPNG_OPTIONS} ${TARGET_PNG} -} - - -build_icon 72 ${BASE_DIR}/android/app/src/main/res/mipmap-hdpi/ic_launcher -build_icon 48 ${BASE_DIR}/android/app/src/main/res/mipmap-mdpi/ic_launcher -build_icon 96 ${BASE_DIR}/android/app/src/main/res/mipmap-xhdpi/ic_launcher -build_icon 144 ${BASE_DIR}/android/app/src/main/res/mipmap-xxhdpi/ic_launcher -build_icon 192 ${BASE_DIR}/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher -build_icon 512 ${BASE_DIR}/fastlane/metadata/android/en-US/images/icon diff --git a/lib/config/activity_page.dart b/lib/config/activity_page.dart new file mode 100644 index 0000000000000000000000000000000000000000..33e4c09df971eac6f52b0b6cf68840a8d069bbd5 --- /dev/null +++ b/lib/config/activity_page.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:unicons/unicons.dart'; + +import 'package:plotter/ui/pages/remote_control.dart'; +import 'package:plotter/ui/pages/home.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(UniconsLine.home), + page: PageHome(), + code: 'page_home', + ); + + static const indexRemoteControl = 1; + static const pageRemoteControl = ActivityPageItem( + icon: Icon(UniconsLine.arrow), + page: PageRemoteControl(), + code: 'page_remote_control', + ); + + static Map<int, ActivityPageItem> items = { + indexHome: pageHome, + indexRemoteControl: pageRemoteControl, + }; + + 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_global_settings.dart b/lib/config/default_global_settings.dart new file mode 100644 index 0000000000000000000000000000000000000000..3906b1fea4e387ef10e5519d6bde3e87935c7f7f --- /dev/null +++ b/lib/config/default_global_settings.dart @@ -0,0 +1,33 @@ +import 'package:plotter/utils/tools.dart'; + +class DefaultGlobalSettings { + // available global parameters codes + static const String parameterCodeSkin = 'skin'; + static const List<String> availableParameters = [ + parameterCodeSkin, + ]; + + // skin: available values + static const String skinValueDefault = 'default'; + static const List<String> allowedSkinValues = [ + skinValueDefault, + ]; + // skin: default value + static const String defaultSkinValue = skinValueDefault; + + // available values from parameter code + static List<String> getAvailableValues(String parameterCode) { + switch (parameterCode) { + case parameterCodeSkin: + return DefaultGlobalSettings.allowedSkinValues; + } + + printlog('Did not find any available value for global parameter "$parameterCode".'); + return []; + } + + // parameters displayed with assets (instead of painter) + static List<String> displayedWithAssets = [ + // + ]; +} diff --git a/lib/config/screen.dart b/lib/config/screen.dart new file mode 100644 index 0000000000000000000000000000000000000000..3cf39b6d0c0770f20dcb6e2372474232ea2d2378 --- /dev/null +++ b/lib/config/screen.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:unicons/unicons.dart'; + +import 'package:plotter/ui/screens/about.dart'; +import 'package:plotter/ui/screens/activity.dart'; +import 'package:plotter/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 new file mode 100644 index 0000000000000000000000000000000000000000..74f532fd5abf693979118609564d29167e902009 --- /dev/null +++ b/lib/config/theme.dart @@ -0,0 +1,190 @@ +import 'package:flutter/material.dart'; + +/// Colors from Tailwind CSS (v3.0) - June 2022 +/// +/// https://tailwindcss.com/docs/customizing-colors + +const int _primaryColor = 0xFF6366F1; +const MaterialColor primarySwatch = MaterialColor(_primaryColor, <int, Color>{ + 50: Color(0xFFEEF2FF), // indigo-50 + 100: Color(0xFFE0E7FF), // indigo-100 + 200: Color(0xFFC7D2FE), // indigo-200 + 300: Color(0xFFA5B4FC), // indigo-300 + 400: Color(0xFF818CF8), // indigo-400 + 500: Color(_primaryColor), // indigo-500 + 600: Color(0xFF4F46E5), // indigo-600 + 700: Color(0xFF4338CA), // indigo-700 + 800: Color(0xFF3730A3), // indigo-800 + 900: Color(0xFF312E81), // indigo-900 +}); + +const int _textColor = 0xFF64748B; +const MaterialColor textSwatch = MaterialColor(_textColor, <int, Color>{ + 50: Color(0xFFF8FAFC), // slate-50 + 100: Color(0xFFF1F5F9), // slate-100 + 200: Color(0xFFE2E8F0), // slate-200 + 300: Color(0xFFCBD5E1), // slate-300 + 400: Color(0xFF94A3B8), // slate-400 + 500: Color(_textColor), // slate-500 + 600: Color(0xFF475569), // slate-600 + 700: Color(0xFF334155), // slate-700 + 800: Color(0xFF1E293B), // slate-800 + 900: Color(0xFF0F172A), // slate-900 +}); + +const Color errorColor = Color(0xFFDC2626); // red-600 + +final ColorScheme lightColorScheme = ColorScheme.light( + primary: primarySwatch.shade500, + secondary: primarySwatch.shade500, + onSecondary: Colors.white, + error: errorColor, + onSurface: textSwatch.shade500, + surface: textSwatch.shade50, + surfaceContainerHighest: Colors.white, + shadow: textSwatch.shade900.withOpacity(.1), +); + +final ColorScheme darkColorScheme = ColorScheme.dark( + primary: primarySwatch.shade500, + secondary: primarySwatch.shade500, + onSecondary: Colors.white, + error: errorColor, + onSurface: textSwatch.shade300, + surface: const Color(0xFF262630), + surfaceContainerHighest: const Color(0xFF282832), + shadow: textSwatch.shade900.withOpacity(.2), +); + +final ThemeData lightTheme = ThemeData( + colorScheme: lightColorScheme, + fontFamily: 'Nunito', + textTheme: TextTheme( + displayLarge: TextStyle( + color: textSwatch.shade700, + fontFamily: 'Nunito', + ), + displayMedium: TextStyle( + color: textSwatch.shade600, + fontFamily: 'Nunito', + ), + displaySmall: TextStyle( + color: textSwatch.shade500, + fontFamily: 'Nunito', + ), + headlineLarge: TextStyle( + color: textSwatch.shade700, + fontFamily: 'Nunito', + ), + headlineMedium: TextStyle( + color: textSwatch.shade600, + fontFamily: 'Nunito', + ), + headlineSmall: TextStyle( + color: textSwatch.shade500, + fontFamily: 'Nunito', + ), + titleLarge: TextStyle( + color: textSwatch.shade700, + fontFamily: 'Nunito', + ), + titleMedium: TextStyle( + color: textSwatch.shade600, + fontFamily: 'Nunito', + ), + titleSmall: TextStyle( + color: textSwatch.shade500, + fontFamily: 'Nunito', + ), + bodyLarge: TextStyle( + color: textSwatch.shade700, + fontFamily: 'Nunito', + ), + bodyMedium: TextStyle( + color: textSwatch.shade600, + fontFamily: 'Nunito', + ), + bodySmall: TextStyle( + color: textSwatch.shade500, + fontFamily: 'Nunito', + ), + labelLarge: TextStyle( + color: textSwatch.shade700, + fontFamily: 'Nunito', + ), + labelMedium: TextStyle( + color: textSwatch.shade600, + fontFamily: 'Nunito', + ), + labelSmall: TextStyle( + color: textSwatch.shade500, + fontFamily: 'Nunito', + ), + ), +); + +final ThemeData darkTheme = lightTheme.copyWith( + colorScheme: darkColorScheme, + textTheme: TextTheme( + displayLarge: TextStyle( + color: textSwatch.shade200, + fontFamily: 'Nunito', + ), + displayMedium: TextStyle( + color: textSwatch.shade300, + fontFamily: 'Nunito', + ), + displaySmall: TextStyle( + color: textSwatch.shade400, + fontFamily: 'Nunito', + ), + headlineLarge: TextStyle( + color: textSwatch.shade200, + fontFamily: 'Nunito', + ), + headlineMedium: TextStyle( + color: textSwatch.shade300, + fontFamily: 'Nunito', + ), + headlineSmall: TextStyle( + color: textSwatch.shade400, + fontFamily: 'Nunito', + ), + titleLarge: TextStyle( + color: textSwatch.shade200, + fontFamily: 'Nunito', + ), + titleMedium: TextStyle( + color: textSwatch.shade300, + fontFamily: 'Nunito', + ), + titleSmall: TextStyle( + color: textSwatch.shade400, + fontFamily: 'Nunito', + ), + bodyLarge: TextStyle( + color: textSwatch.shade200, + fontFamily: 'Nunito', + ), + bodyMedium: TextStyle( + color: textSwatch.shade300, + fontFamily: 'Nunito', + ), + bodySmall: TextStyle( + color: textSwatch.shade400, + fontFamily: 'Nunito', + ), + labelLarge: TextStyle( + color: textSwatch.shade200, + fontFamily: 'Nunito', + ), + labelMedium: TextStyle( + color: textSwatch.shade300, + fontFamily: 'Nunito', + ), + labelSmall: TextStyle( + color: textSwatch.shade400, + fontFamily: 'Nunito', + ), + ), +); diff --git a/lib/cubit/activity_cubit.dart b/lib/cubit/activity_cubit.dart new file mode 100644 index 0000000000000000000000000000000000000000..e0e45d1501b88dd7a32e2d80d52c935366b836dc --- /dev/null +++ b/lib/cubit/activity_cubit.dart @@ -0,0 +1,92 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:plotter/models/activity/activity.dart'; +import 'package:plotter/models/settings/settings_global.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() { + final Activity activity = Activity( + // Settings + globalSettings: state.currentActivity.globalSettings, + // State + isRunning: state.currentActivity.isRunning, + // Base data + apiHost: state.currentActivity.apiHost, + // Activity data + apiStatus: state.currentActivity.apiStatus, + ); + // activity.dump(); + + updateState(activity); + } + + void startNewActivity({ + required GlobalSettings globalSettings, + }) { + final Activity newActivity = Activity.createNew( + // Settings + globalSettings: globalSettings, + ); + + newActivity.dump(); + + updateState(newActivity); + refresh(); + } + + void quitActivity() { + state.currentActivity.isRunning = false; + refresh(); + } + + void resumeSavedActivity() { + state.currentActivity.isRunning = true; + refresh(); + } + + void deleteSavedActivity() { + state.currentActivity.isRunning = false; + refresh(); + } + + void setApiHost(String apiHost) { + state.currentActivity.apiHost = apiHost; + refresh(); + } + + void updateApiStatus(String apiStatus) { + state.currentActivity.apiStatus = apiStatus; + refresh(); + } + + @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/nav_cubit_pages.dart b/lib/cubit/nav_cubit_pages.dart new file mode 100644 index 0000000000000000000000000000000000000000..aee283fd13a259db40373a04a2e4182a20624b99 --- /dev/null +++ b/lib/cubit/nav_cubit_pages.dart @@ -0,0 +1,25 @@ +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:plotter/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); + } + } + + @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..32f8d9b4322401d7d27eabfd9fb4d26e2c7d4c6e --- /dev/null +++ b/lib/cubit/nav_cubit_screens.dart @@ -0,0 +1,37 @@ +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:plotter/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_global_cubit.dart b/lib/cubit/settings_global_cubit.dart new file mode 100644 index 0000000000000000000000000000000000000000..67344c1b4e9b2d5e573608211ac20ab18f9ce866 --- /dev/null +++ b/lib/cubit/settings_global_cubit.dart @@ -0,0 +1,68 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:plotter/config/default_global_settings.dart'; +import 'package:plotter/models/settings/settings_global.dart'; + +part 'settings_global_state.dart'; + +class GlobalSettingsCubit extends HydratedCubit<GlobalSettingsState> { + GlobalSettingsCubit() : super(GlobalSettingsState(settings: GlobalSettings.createDefault())); + + void setValues({ + String? skin, + String? apiHost, + }) { + emit( + GlobalSettingsState( + settings: GlobalSettings( + skin: skin ?? state.settings.skin, + apiHost: apiHost ?? state.settings.apiHost, + ), + ), + ); + } + + String getParameterValue(String code) { + switch (code) { + case DefaultGlobalSettings.parameterCodeSkin: + return GlobalSettings.getSkinValueFromUnsafe(state.settings.skin); + case 'apiHost': + return state.settings.apiHost; + } + return ''; + } + + void setParameterValue(String code, String value) { + final String skin = (code == DefaultGlobalSettings.parameterCodeSkin) + ? value + : getParameterValue(DefaultGlobalSettings.parameterCodeSkin); + final String apiHost = (code == 'apiHost') ? value : getParameterValue('apiHost'); + + setValues( + skin: skin, + apiHost: apiHost, + ); + } + + @override + GlobalSettingsState? fromJson(Map<String, dynamic> json) { + final String skin = json[DefaultGlobalSettings.parameterCodeSkin] as String; + final String apiHost = json['apiHost'] as String; + + return GlobalSettingsState( + settings: GlobalSettings( + skin: skin, + apiHost: apiHost, + ), + ); + } + + @override + Map<String, dynamic>? toJson(GlobalSettingsState state) { + return <String, dynamic>{ + DefaultGlobalSettings.parameterCodeSkin: state.settings.skin, + }; + } +} diff --git a/lib/cubit/settings_global_state.dart b/lib/cubit/settings_global_state.dart new file mode 100644 index 0000000000000000000000000000000000000000..ebcddd700f252257223ca8e16c85202b04f3ff24 --- /dev/null +++ b/lib/cubit/settings_global_state.dart @@ -0,0 +1,15 @@ +part of 'settings_global_cubit.dart'; + +@immutable +class GlobalSettingsState extends Equatable { + const GlobalSettingsState({ + required this.settings, + }); + + final GlobalSettings settings; + + @override + List<dynamic> get props => <dynamic>[ + settings, + ]; +} diff --git a/lib/cubit/theme_cubit.dart b/lib/cubit/theme_cubit.dart new file mode 100644 index 0000000000000000000000000000000000000000..b793e895dbb0c672d451cd403e0036c3d9ac9b42 --- /dev/null +++ b/lib/cubit/theme_cubit.dart @@ -0,0 +1,31 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +part 'theme_state.dart'; + +class ThemeCubit extends HydratedCubit<ThemeModeState> { + ThemeCubit() : super(const ThemeModeState()); + + void getTheme(ThemeModeState state) { + emit(state); + } + + @override + ThemeModeState? fromJson(Map<String, dynamic> json) { + switch (json['themeMode']) { + case 'ThemeMode.dark': + return const ThemeModeState(themeMode: ThemeMode.dark); + case 'ThemeMode.light': + return const ThemeModeState(themeMode: ThemeMode.light); + case 'ThemeMode.system': + default: + return const ThemeModeState(themeMode: ThemeMode.system); + } + } + + @override + Map<String, String>? toJson(ThemeModeState state) { + return <String, String>{'themeMode': state.themeMode.toString()}; + } +} diff --git a/lib/cubit/theme_state.dart b/lib/cubit/theme_state.dart new file mode 100644 index 0000000000000000000000000000000000000000..e479a50f12fe72a35a1fd1722ff72afbb692a136 --- /dev/null +++ b/lib/cubit/theme_state.dart @@ -0,0 +1,15 @@ +part of 'theme_cubit.dart'; + +@immutable +class ThemeModeState extends Equatable { + const ThemeModeState({ + this.themeMode, + }); + + final ThemeMode? themeMode; + + @override + List<Object?> get props => <Object?>[ + themeMode, + ]; +} diff --git a/lib/layout/keyboard.dart b/lib/layout/keyboard.dart deleted file mode 100644 index 33536402876df56dac117f23eb4c2882593566df..0000000000000000000000000000000000000000 --- a/lib/layout/keyboard.dart +++ /dev/null @@ -1,74 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:plotter/provider/data.dart'; -import 'package:plotter/utils/api.dart'; - -class Keyboard { - static Container buildWidget(Data myProvider) { - Widget buildKeyWidget(String direction) { - String keyText = ''; - - String north = '🔼'; - String south = '🔽'; - String west = 'â—€ï¸'; - String east = 'â–¶ï¸'; - - if (direction == 'north') { - keyText = north; - } else { - if (direction == 'south') { - keyText = south; - } else { - if (direction == 'west') { - keyText = west; - } else { - if (direction == 'east') { - keyText = east; - } - } - } - } - - return TextButton( - style: TextButton.styleFrom( - padding: const EdgeInsets.all(0), - ), - child: Text(keyText, - style: const TextStyle( - fontSize: 60.0, - fontWeight: FontWeight.w800, - ), - textAlign: TextAlign.center), - onPressed: () { - if (keyText != '') { - Api.move(myProvider, direction); - } - }, - ); - } - - return Container( - margin: const EdgeInsets.symmetric(horizontal: 2), - padding: const EdgeInsets.all(2), - child: Table( - defaultVerticalAlignment: TableCellVerticalAlignment.middle, - children: [ - TableRow(children: [ - TableCell(child: buildKeyWidget('')), - TableCell(child: buildKeyWidget('north')), - TableCell(child: buildKeyWidget('')), - ]), - TableRow(children: [ - TableCell(child: buildKeyWidget('west')), - TableCell(child: buildKeyWidget('')), - TableCell(child: buildKeyWidget('east')), - ]), - TableRow(children: [ - TableCell(child: buildKeyWidget('')), - TableCell(child: buildKeyWidget('south')), - TableCell(child: buildKeyWidget('')), - ]), - ], - )); - } -} diff --git a/lib/main.dart b/lib/main.dart index 6a1c1d8321c952f9132ced9b83c458b4324ed1ea..04a3dfed0a74b879bbc26f9bbfb93888edd366f1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,15 +1,42 @@ +import 'dart:io'; + +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:provider/provider.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 'provider/data.dart'; -import 'screens/home.dart'; -import 'screens/settings.dart'; +import 'package:plotter/config/theme.dart'; +import 'package:plotter/cubit/activity_cubit.dart'; +import 'package:plotter/cubit/nav_cubit_pages.dart'; +import 'package:plotter/cubit/nav_cubit_screens.dart'; +import 'package:plotter/cubit/settings_global_cubit.dart'; +import 'package:plotter/cubit/theme_cubit.dart'; +import 'package:plotter/ui/skeleton.dart'; -void main() { +void main() async { + // Initialize packages WidgetsFlutterBinding.ensureInitialized(); + await EasyLocalization.ensureInitialized(); + final Directory tmpDir = await getTemporaryDirectory(); + Hive.init(tmpDir.toString()); + HydratedBloc.storage = await HydratedStorage.build( + storageDirectory: tmpDir, + ); + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]) - .then((value) => runApp(const MyApp())); + .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 { @@ -17,22 +44,33 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return ChangeNotifierProvider( - create: (BuildContext context) => Data(), - child: Consumer<Data>(builder: (context, data, child) { - return MaterialApp( - debugShowCheckedModeBanner: false, - theme: ThemeData( - primaryColor: Colors.blue, - visualDensity: VisualDensity.adaptivePlatformDensity, - ), - home: const Home(), - routes: { - Home.id: (context) => const Home(), - SettingsPage.id: (context) => const SettingsPage(), - }, - ); - }), + return MultiBlocProvider( + providers: [ + 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()), + ], + child: BlocBuilder<ThemeCubit, ThemeModeState>( + builder: (BuildContext context, ThemeModeState state) { + return MaterialApp( + title: 'Stepper plotter assistant', + home: const SkeletonScreen(), + + // Theme stuff + theme: lightTheme, + darkTheme: darkTheme, + themeMode: state.themeMode, + + // 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..39ee19cb4e0388a80f9f4f5b165ad67056d23b2c --- /dev/null +++ b/lib/models/activity/activity.dart @@ -0,0 +1,88 @@ +import 'package:plotter/models/settings/settings_global.dart'; +import 'package:plotter/utils/tools.dart'; + +class Activity { + Activity({ + // Settings + required this.globalSettings, + + // State + this.isRunning = false, + + // Base data + this.apiHost = '', + + // Activity data + this.apiStatus = '', + }); + + // Settings + final GlobalSettings globalSettings; + + // State + bool isRunning; + + // Base data + String apiHost; + + // Activity data + String apiStatus; + + factory Activity.createNull() { + return Activity( + // Settings + globalSettings: GlobalSettings.createDefault(), + // Base data + apiHost: '', + ); + } + + factory Activity.createNew({ + GlobalSettings? globalSettings, + }) { + final GlobalSettings newGlobalSettings = globalSettings ?? GlobalSettings.createDefault(); + + return Activity( + // Settings + globalSettings: newGlobalSettings, + // State + isRunning: true, + // Base data + apiHost: '', + ); + } + + void dump() { + printlog(''); + printlog('## Current activity dump:'); + printlog(''); + printlog('$Activity:'); + printlog(' Settings'); + globalSettings.dump(); + printlog(' State'); + printlog(' isRunning: $isRunning'); + printlog(' Base data'); + printlog(' apiHost: $apiHost'); + printlog(' Activity data'); + printlog(' apiStatus: $apiStatus'); + printlog(''); + } + + @override + String toString() { + return '$Activity(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + // Settings + 'globalSettings': globalSettings.toJson(), + // State + 'isRunning': isRunning, + // Base data + 'apiHost': apiHost, + // Activity data + 'apiStatus': apiStatus, + }; + } +} diff --git a/lib/models/settings/settings_global.dart b/lib/models/settings/settings_global.dart new file mode 100644 index 0000000000000000000000000000000000000000..e22f927595ed5f22eefff58d47def8dcb445e005 --- /dev/null +++ b/lib/models/settings/settings_global.dart @@ -0,0 +1,46 @@ +import 'package:plotter/config/default_global_settings.dart'; +import 'package:plotter/utils/tools.dart'; + +class GlobalSettings { + String skin; + String apiHost; + + GlobalSettings({ + required this.skin, + required this.apiHost, + }); + + static String getSkinValueFromUnsafe(String skin) { + if (DefaultGlobalSettings.allowedSkinValues.contains(skin)) { + return skin; + } + + return DefaultGlobalSettings.defaultSkinValue; + } + + factory GlobalSettings.createDefault() { + return GlobalSettings( + skin: DefaultGlobalSettings.defaultSkinValue, + apiHost: '', + ); + } + + void dump() { + printlog('$GlobalSettings:'); + printlog(' ${DefaultGlobalSettings.parameterCodeSkin}: $skin'); + printlog(' apiHost: $apiHost'); + printlog(''); + } + + @override + String toString() { + return '$GlobalSettings(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + DefaultGlobalSettings.parameterCodeSkin: skin, + 'apiHost': apiHost, + }; + } +} diff --git a/lib/provider/data.dart b/lib/provider/data.dart deleted file mode 100644 index 1293e1a5314ba11e7a5fd5220fd580e38f3ddd43..0000000000000000000000000000000000000000 --- a/lib/provider/data.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -import 'package:plotter/utils/tools.dart'; - -class Data extends ChangeNotifier { - // Application settings - String _apiHost = ''; - String _apiStatus = ''; - - String defaultApiHost = '127.0.0.1'; - - String getParameterValue(String parameterCode) { - switch (parameterCode) { - case 'apiHost': - { - return _apiHost; - } - } - - return ''; - } - - setParameterValue(String parameterCode, String parameterValue) async { - printlog('set parameter "$parameterCode" to value "$parameterValue"'); - switch (parameterCode) { - case 'apiHost': - { - updateApiHost(parameterValue); - } - break; - } - final prefs = await SharedPreferences.getInstance(); - prefs.setString(parameterCode, parameterValue); - } - - void initParametersValues() async { - final prefs = await SharedPreferences.getInstance(); - setParameterValue('apiHost', prefs.getString('apiHost') ?? defaultApiHost); - } - - String get apiHost => _apiHost; - void updateApiHost(String apiHost) { - _apiHost = apiHost; - notifyListeners(); - } - - String get apiStatus => _apiStatus; - void updateApiStatus(String apiStatus) { - printlog('new API status: $apiStatus'); - _apiStatus = apiStatus; - notifyListeners(); - } -} diff --git a/lib/screens/home.dart b/lib/screens/home.dart deleted file mode 100644 index 737a27291a793f72c4389b8616d2339496dfa32d..0000000000000000000000000000000000000000 --- a/lib/screens/home.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -import 'package:plotter/layout/keyboard.dart'; -import 'package:plotter/provider/data.dart'; -import 'package:plotter/screens/settings.dart'; -import 'package:plotter/utils/api.dart'; - -class Home extends StatefulWidget { - static const String id = 'home'; - - const Home({super.key}); - - @override - HomeState createState() => HomeState(); -} - -class HomeState extends State<Home> { - @override - void initState() { - super.initState(); - - Data myProvider = Provider.of<Data>(context, listen: false); - myProvider.initParametersValues(); - } - - @override - Widget build(BuildContext context) { - Data myProvider = Provider.of<Data>(context); - - return Scaffold( - appBar: AppBar( - title: const Text('Stepper plotter assistant'), - actions: [ - IconButton( - icon: const Icon(Icons.settings), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) { - return const SettingsPage(); - }), - ); - }, - ) - ], - leading: IconButton( - icon: Image.asset('assets/icons/application.png'), - onPressed: () {}, - ), - ), - body: SafeArea( - child: Center( - child: Column( - children: [ - Keyboard.buildWidget(myProvider), - TextButton( - child: const Text('get API status'), - onPressed: () => Api.updateApiStatus(myProvider), - ), - Text(myProvider.apiStatus) - ], - )), - )); - } -} diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart deleted file mode 100644 index 886c375a2061e402315b1acf82c8d0eb6c9d528d..0000000000000000000000000000000000000000 --- a/lib/screens/settings.dart +++ /dev/null @@ -1,59 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -import 'package:plotter/provider/data.dart'; - -class SettingsPage extends StatefulWidget { - const SettingsPage({super.key}); - - static const String id = 'settings'; - - @override - SettingsPageState createState() => SettingsPageState(); -} - -class SettingsPageState extends State<SettingsPage> { - TextEditingController apiHostFieldController = TextEditingController(); - - @override - void dispose() { - apiHostFieldController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - Data myProvider = Provider.of<Data>(context); - - apiHostFieldController.text = myProvider.getParameterValue('apiHost'); - - return Scaffold( - appBar: AppBar( - title: const Text('Stepper - settings'), - actions: [ - IconButton( - icon: const Icon(Icons.save), - onPressed: () { - myProvider.setParameterValue('apiHost', apiHostFieldController.text); - Navigator.pop(context); - }, - ) - ], - leading: IconButton( - icon: Image.asset('assets/icons/application.png'), - onPressed: () {}, - ), - ), - body: Padding( - padding: const EdgeInsets.all(16.0), - child: TextFormField( - controller: apiHostFieldController, - decoration: const InputDecoration( - border: UnderlineInputBorder(), - labelText: 'API Host', - ), - ), - ), - ); - } -} diff --git a/lib/ui/helpers/app_titles.dart b/lib/ui/helpers/app_titles.dart new file mode 100644 index 0000000000000000000000000000000000000000..b98107b12fabc3114ebfbec994166b588abcf1ad --- /dev/null +++ b/lib/ui/helpers/app_titles.dart @@ -0,0 +1,32 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; + +class AppHeader extends StatelessWidget { + const AppHeader({super.key, required this.text}); + + final String text; + + @override + Widget build(BuildContext context) { + return Text( + tr(text), + textAlign: TextAlign.start, + style: Theme.of(context).textTheme.headlineMedium!.apply(fontWeightDelta: 2), + ); + } +} + +class AppTitle extends StatelessWidget { + const AppTitle({super.key, required this.text}); + + final String text; + + @override + Widget build(BuildContext context) { + return Text( + tr(text), + textAlign: TextAlign.start, + style: Theme.of(context).textTheme.titleLarge!.apply(fontWeightDelta: 2), + ); + } +} diff --git a/lib/ui/helpers/outlined_text_widget.dart b/lib/ui/helpers/outlined_text_widget.dart new file mode 100644 index 0000000000000000000000000000000000000000..8488ded2c4cae42b1cb3f487c0d3b06f58b3d8fc --- /dev/null +++ b/lib/ui/helpers/outlined_text_widget.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; + +import 'package:plotter/utils/color_extensions.dart'; + +class OutlinedText extends StatelessWidget { + const OutlinedText({ + super.key, + required this.text, + required this.fontSize, + required this.textColor, + this.outlineColor, + }); + + final String text; + final double fontSize; + final Color textColor; + final Color? outlineColor; + + @override + Widget build(BuildContext context) { + final double delta = fontSize / 30; + + return Text( + text, + style: TextStyle( + inherit: true, + fontSize: fontSize, + fontWeight: FontWeight.w600, + color: textColor, + shadows: [ + Shadow( + offset: Offset(-delta, -delta), + color: outlineColor ?? textColor.darken(), + ), + Shadow( + offset: Offset(delta, -delta), + color: outlineColor ?? textColor.darken(), + ), + Shadow( + offset: Offset(delta, delta), + color: outlineColor ?? textColor.darken(), + ), + Shadow( + offset: Offset(-delta, delta), + color: outlineColor ?? textColor.darken(), + ), + ], + ), + ); + } +} diff --git a/lib/ui/nav/bottom_nav_bar.dart b/lib/ui/nav/bottom_nav_bar.dart new file mode 100644 index 0000000000000000000000000000000000000000..d66026388a61e3a1445f98f7d90a94f848116c35 --- /dev/null +++ b/lib/ui/nav/bottom_nav_bar.dart @@ -0,0 +1,49 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:plotter/config/activity_page.dart'; +import 'package:plotter/cubit/nav_cubit_pages.dart'; + +class BottomNavBar extends StatelessWidget { + const BottomNavBar({super.key}); + + @override + Widget build(BuildContext context) { + return Card( + margin: const EdgeInsets.only(top: 1, right: 4, left: 4), + elevation: 4, + shadowColor: Theme.of(context).colorScheme.shadow, + color: Theme.of(context).colorScheme.surfaceContainerHighest, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), + ), + child: BlocBuilder<NavCubitPage, int>(builder: (BuildContext context, int state) { + final List<ActivityPageItem> pageItems = [ + ActivityPage.pageHome, + ActivityPage.pageRemoteControl, + ]; + final List<BottomNavigationBarItem> items = pageItems.map((ActivityPageItem item) { + return BottomNavigationBarItem( + icon: item.icon, + label: tr(item.code), + ); + }).toList(); + + return BottomNavigationBar( + currentIndex: state, + onTap: (int index) => context.read<NavCubitPage>().updateIndex(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..ba02c8429493cae500084438d499901b4c44dc96 --- /dev/null +++ b/lib/ui/nav/global_app_bar.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:plotter/config/screen.dart'; +import 'package:plotter/cubit/activity_cubit.dart'; +import 'package:plotter/cubit/nav_cubit_screens.dart'; +import 'package:plotter/ui/helpers/app_titles.dart'; + +class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget { + const GlobalAppBar({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<ActivityCubit, ActivityState>( + builder: (BuildContext context, ActivityState activityState) { + return BlocBuilder<NavCubitScreen, int>( + builder: (BuildContext context, int screenIndex) { + final List<Widget> menuActions = []; + + if (screenIndex == Screen.indexActivity) { + // go to Settings page + menuActions.add(ElevatedButton( + onPressed: () { + context.read<NavCubitScreen>().goToSettingsPage(); + }, + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + ), + child: Screen.screenSettings.icon, + )); + + // go to About page + menuActions.add(ElevatedButton( + onPressed: () { + context.read<NavCubitScreen>().goToAboutPage(); + }, + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + ), + child: Screen.screenAbout.icon, + )); + } else { + // back to Home page + menuActions.add(ElevatedButton( + onPressed: () { + context.read<NavCubitScreen>().goToActivityPage(); + }, + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + ), + child: 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/home.dart b/lib/ui/pages/home.dart new file mode 100644 index 0000000000000000000000000000000000000000..2c0c20edf8de7b014022e985ca70787a82e2b864 --- /dev/null +++ b/lib/ui/pages/home.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:plotter/cubit/activity_cubit.dart'; +import 'package:plotter/models/activity/activity.dart'; +import 'package:plotter/ui/helpers/outlined_text_widget.dart'; +import 'package:plotter/utils/color_extensions.dart'; + +class PageHome extends StatelessWidget { + const PageHome({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<ActivityCubit, ActivityState>( + builder: (BuildContext context, ActivityState activityState) { + final Activity currentActivity = activityState.currentActivity; + + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: <Widget>[ + const SizedBox(height: 8), + OutlinedText( + text: '[home]', + fontSize: 50, + textColor: Colors.blueAccent, + outlineColor: Colors.blueAccent.lighten(20), + ), + Text(currentActivity.toString()), + ], + ); + }, + ); + } +} diff --git a/lib/ui/pages/player.dart b/lib/ui/pages/player.dart new file mode 100644 index 0000000000000000000000000000000000000000..7bc966803618c0c9c91dad8a626144ac3c864775 --- /dev/null +++ b/lib/ui/pages/player.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:plotter/cubit/activity_cubit.dart'; +import 'package:plotter/models/activity/activity.dart'; +import 'package:plotter/ui/helpers/outlined_text_widget.dart'; +import 'package:plotter/utils/color_extensions.dart'; + +class PagePlayer extends StatelessWidget { + const PagePlayer({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<ActivityCubit, ActivityState>( + builder: (BuildContext context, ActivityState activityState) { + final Activity currentActivity = activityState.currentActivity; + + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: <Widget>[ + const SizedBox(height: 8), + OutlinedText( + text: '[player]', + fontSize: 50, + textColor: Colors.blueAccent, + outlineColor: Colors.blueAccent.lighten(20), + ), + Text(currentActivity.toString()), + ], + ); + }, + ); + } +} diff --git a/lib/ui/pages/remote_control.dart b/lib/ui/pages/remote_control.dart new file mode 100644 index 0000000000000000000000000000000000000000..00f1a3dc2c8c1b972d44ce7993413b3c71f203e8 --- /dev/null +++ b/lib/ui/pages/remote_control.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:plotter/cubit/activity_cubit.dart'; +import 'package:plotter/models/activity/activity.dart'; +import 'package:plotter/ui/widgets/keyboard.dart'; +import 'package:plotter/utils/api.dart'; + +class PageRemoteControl extends StatelessWidget { + const PageRemoteControl({super.key}); + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Center( + child: Column( + children: [ + const Keyboard(), + TextButton( + child: const Text('get API status'), + onPressed: () { + final ActivityCubit activityCubit = BlocProvider.of<ActivityCubit>(context); + + Api.updateApiStatus(activityCubit); + }, + ), + BlocBuilder<ActivityCubit, ActivityState>( + builder: (BuildContext context, ActivityState activityState) { + final Activity currentActivity = activityState.currentActivity; + + return Text(currentActivity.apiStatus); + }, + ) + ], + ), + ), + ); + } +} diff --git a/lib/ui/screens/about.dart b/lib/ui/screens/about.dart new file mode 100644 index 0000000000000000000000000000000000000000..cf8463f8c594967778d1451d8c1883483a9becfa --- /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:plotter/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..edebf87e0f2899c18c60f8eb5af8953fdb1db4eb --- /dev/null +++ b/lib/ui/screens/activity.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:plotter/config/activity_page.dart'; +import 'package:plotter/cubit/nav_cubit_pages.dart'; + +class ScreenActivity extends StatelessWidget { + const ScreenActivity({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<NavCubitPage, int>( + builder: (BuildContext context, int pageIndex) { + return ActivityPage.getPageWidget(pageIndex); + }, + ); + } +} diff --git a/lib/ui/screens/settings.dart b/lib/ui/screens/settings.dart new file mode 100644 index 0000000000000000000000000000000000000000..93c7b97937b1c37591d5981acf9b3f2e58e2b4a7 --- /dev/null +++ b/lib/ui/screens/settings.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; + +import 'package:plotter/ui/helpers/app_titles.dart'; +import 'package:plotter/ui/settings/settings_form.dart'; + +class ScreenSettings extends StatelessWidget { + const ScreenSettings({super.key}); + + @override + Widget build(BuildContext context) { + return const Padding( + padding: EdgeInsets.symmetric(horizontal: 8), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: <Widget>[ + SizedBox(height: 8), + AppTitle(text: 'settings_title'), + SizedBox(height: 8), + SettingsForm(), + ], + ), + ); + } +} diff --git a/lib/ui/settings/settings_form.dart b/lib/ui/settings/settings_form.dart new file mode 100644 index 0000000000000000000000000000000000000000..d9d3ad81e761f1548c79c493c167f867191fea84 --- /dev/null +++ b/lib/ui/settings/settings_form.dart @@ -0,0 +1,100 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:plotter/cubit/settings_global_cubit.dart'; +import 'package:unicons/unicons.dart'; + +import 'package:plotter/ui/settings/theme_card.dart'; + +class SettingsForm extends StatefulWidget { + const SettingsForm({super.key}); + + @override + State<SettingsForm> createState() => _SettingsFormState(); +} + +class _SettingsFormState extends State<SettingsForm> { + final apiHostFieldController = TextEditingController(); + + @override + void didChangeDependencies() { + GlobalSettingsCubit settings = BlocProvider.of<GlobalSettingsCubit>(context); + + apiHostFieldController.text = settings.getParameterValue('apiHost'); + + super.didChangeDependencies(); + } + + @override + void dispose() { + apiHostFieldController.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + void saveSettings() { + BlocProvider.of<GlobalSettingsCubit>(context).setValues( + apiHost: apiHostFieldController.text, + ); + } + + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: <Widget>[ + // Light/dark theme + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: <Widget>[ + const Text('settings_label_theme').tr(), + const Row( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ThemeCard( + mode: ThemeMode.system, + icon: UniconsLine.cog, + ), + ThemeCard( + mode: ThemeMode.light, + icon: UniconsLine.sun, + ), + ThemeCard( + mode: ThemeMode.dark, + icon: UniconsLine.moon, + ) + ], + ), + ], + ), + const SizedBox(height: 16), + + // Api settings + const Text('settings_label_api_host').tr(), + TextFormField( + controller: apiHostFieldController, + decoration: InputDecoration( + border: const UnderlineInputBorder(), + suffixIcon: ElevatedButton( + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6.0), + ), + ), + child: const Icon(UniconsLine.save), + onPressed: () { + saveSettings(); + }, + ), + ), + ), + + const SizedBox(height: 16), + ], + ); + } +} diff --git a/lib/ui/settings/theme_card.dart b/lib/ui/settings/theme_card.dart new file mode 100644 index 0000000000000000000000000000000000000000..85d394eda698cdd6d9c742385521a88c9fe7978c --- /dev/null +++ b/lib/ui/settings/theme_card.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:plotter/cubit/theme_cubit.dart'; + +class ThemeCard extends StatelessWidget { + const ThemeCard({ + super.key, + required this.mode, + required this.icon, + }); + + final IconData icon; + final ThemeMode mode; + + @override + Widget build(BuildContext context) { + return BlocBuilder<ThemeCubit, ThemeModeState>( + builder: (BuildContext context, ThemeModeState state) { + return Card( + elevation: 2, + shadowColor: Theme.of(context).colorScheme.shadow, + color: state.themeMode == mode + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.surface, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(12)), + ), + margin: const EdgeInsets.all(5), + child: InkWell( + onTap: () => BlocProvider.of<ThemeCubit>(context).getTheme( + ThemeModeState(themeMode: mode), + ), + borderRadius: const BorderRadius.all(Radius.circular(12)), + child: Icon( + icon, + size: 32, + color: state.themeMode != mode + ? Theme.of(context).colorScheme.primary + : Colors.white, + ), + ), + ); + }, + ); + } +} diff --git a/lib/ui/skeleton.dart b/lib/ui/skeleton.dart new file mode 100644 index 0000000000000000000000000000000000000000..27cd6f11c1776736c9f9ad52a016dc84def522b6 --- /dev/null +++ b/lib/ui/skeleton.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:plotter/config/screen.dart'; +import 'package:plotter/cubit/nav_cubit_screens.dart'; +import 'package:plotter/ui/nav/bottom_nav_bar.dart'; +import 'package:plotter/ui/nav/global_app_bar.dart'; + +class SkeletonScreen extends StatelessWidget { + const SkeletonScreen({super.key}); + + @override + Widget build(BuildContext context) { + 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), + ), + ), + backgroundColor: Theme.of(context).colorScheme.surface, + bottomNavigationBar: + Screen.displayBottomNavBar(screenIndex) ? const BottomNavBar() : null, + ); + }, + ); + } +} diff --git a/lib/ui/widgets/activity/activity_bottom.dart b/lib/ui/widgets/activity/activity_bottom.dart new file mode 100644 index 0000000000000000000000000000000000000000..3ea404ef9858222912094c0371610f2abe179f09 --- /dev/null +++ b/lib/ui/widgets/activity/activity_bottom.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:plotter/cubit/activity_cubit.dart'; +import 'package:plotter/models/activity/activity.dart'; + +class ActivityBottomWidget extends StatelessWidget { + const ActivityBottomWidget({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<ActivityCubit, ActivityState>( + builder: (BuildContext context, ActivityState activityState) { + final Activity currentActivity = activityState.currentActivity; + + return Text(currentActivity.apiHost.toString()); + }, + ); + } +} diff --git a/lib/ui/widgets/activity/activity_content.dart b/lib/ui/widgets/activity/activity_content.dart new file mode 100644 index 0000000000000000000000000000000000000000..cf150db18504143a7ddc14baed8f286838b726b0 --- /dev/null +++ b/lib/ui/widgets/activity/activity_content.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:plotter/cubit/activity_cubit.dart'; +import 'package:plotter/models/activity/activity.dart'; + +class ActivityContentWidget extends StatelessWidget { + const ActivityContentWidget({super.key}); + + @override + Widget build(BuildContext context) { + return Center( + child: BlocBuilder<ActivityCubit, ActivityState>( + builder: (BuildContext context, ActivityState activityState) { + final Activity currentActivity = activityState.currentActivity; + + return Text(currentActivity.toString()); + }, + ), + ); + } +} diff --git a/lib/ui/widgets/keyboard.dart b/lib/ui/widgets/keyboard.dart new file mode 100644 index 0000000000000000000000000000000000000000..774cd4c9848c5cd19b53833cced521efb8fed740 --- /dev/null +++ b/lib/ui/widgets/keyboard.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:plotter/cubit/activity_cubit.dart'; +import 'package:plotter/utils/api.dart'; + +class Keyboard extends StatelessWidget { + const Keyboard({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 2), + padding: const EdgeInsets.all(2), + child: Table( + defaultVerticalAlignment: TableCellVerticalAlignment.middle, + children: [ + TableRow( + children: [ + TableCell(child: buildKeyWidget(context, '')), + TableCell(child: buildKeyWidget(context, 'north')), + TableCell(child: buildKeyWidget(context, '')), + ], + ), + TableRow( + children: [ + TableCell(child: buildKeyWidget(context, 'west')), + TableCell(child: buildKeyWidget(context, '')), + TableCell(child: buildKeyWidget(context, 'east')), + ], + ), + TableRow( + children: [ + TableCell(child: buildKeyWidget(context, '')), + TableCell(child: buildKeyWidget(context, 'south')), + TableCell(child: buildKeyWidget(context, '')), + ], + ), + ], + ), + ); + } + + Widget buildKeyWidget(BuildContext context, String direction) { + String keyText = ''; + + String north = '🔼'; + String south = '🔽'; + String west = 'â—€ï¸'; + String east = 'â–¶ï¸'; + + if (direction == 'north') { + keyText = north; + } else { + if (direction == 'south') { + keyText = south; + } else { + if (direction == 'west') { + keyText = west; + } else { + if (direction == 'east') { + keyText = east; + } + } + } + } + + return TextButton( + style: TextButton.styleFrom( + padding: const EdgeInsets.all(0), + ), + child: Text(keyText, + style: const TextStyle( + fontSize: 60.0, + fontWeight: FontWeight.w800, + ), + textAlign: TextAlign.center), + onPressed: () { + if (keyText != '') { + final ActivityCubit activityCubit = BlocProvider.of<ActivityCubit>(context); + + Api.move(activityCubit, direction); + } + }, + ); + } +} diff --git a/lib/utils/api.dart b/lib/utils/api.dart index eb394946784d0ad93520800f0f2f476ac8192b7e..3767284f504adbc28346bfc76cca818585a5d6dd 100644 --- a/lib/utils/api.dart +++ b/lib/utils/api.dart @@ -1,24 +1,27 @@ import 'dart:convert'; import 'package:http/http.dart' as http; - -import 'package:plotter/provider/data.dart'; +import 'package:plotter/cubit/activity_cubit.dart'; +import 'package:plotter/models/activity/activity.dart'; class Api { - static Future<void> updateApiStatus(Data myProvider) async { - final response = await http.get(Uri.http(myProvider.apiHost, 'status')); + static Future<void> updateApiStatus(ActivityCubit activityCubit) async { + final Activity currentActivity = activityCubit.state.currentActivity; + final response = await http.get(Uri.http(currentActivity.apiHost, 'status')); String responseData = utf8.decode(response.bodyBytes); Map<String, dynamic> status = json.decode(responseData); - myProvider.updateApiStatus(status['status']); + activityCubit.updateApiStatus(status['status']); } - static Future<void> move(Data myProvider, String direction) async { - final response = await http.get(Uri.http(myProvider.apiHost, direction)); + static Future<void> move(ActivityCubit activityCubit, String direction) async { + final Activity currentActivity = activityCubit.state.currentActivity; + + final response = await http.get(Uri.http(currentActivity.apiHost, direction)); String responseData = utf8.decode(response.bodyBytes); Map<String, dynamic> result = json.decode(responseData); - myProvider.updateApiStatus(result['result']); + activityCubit.updateApiStatus(result['result']); } } diff --git a/lib/utils/color_extensions.dart b/lib/utils/color_extensions.dart new file mode 100644 index 0000000000000000000000000000000000000000..4e55e338f0d3ed98b233d1ef887b7b3e17e29d97 --- /dev/null +++ b/lib/utils/color_extensions.dart @@ -0,0 +1,33 @@ +import 'dart:ui'; + +extension ColorExtension on Color { + Color darken([int percent = 40]) { + assert(1 <= percent && percent <= 100); + final value = 1 - percent / 100; + return Color.fromARGB( + alpha, + (red * value).round(), + (green * value).round(), + (blue * value).round(), + ); + } + + Color lighten([int percent = 40]) { + assert(1 <= percent && percent <= 100); + final value = percent / 100; + return Color.fromARGB( + alpha, + (red + ((255 - red) * value)).round(), + (green + ((255 - green) * value)).round(), + (blue + ((255 - blue) * value)).round(), + ); + } + + Color avg(Color other) { + final red = (this.red + other.red) ~/ 2; + final green = (this.green + other.green) ~/ 2; + final blue = (this.blue + other.blue) ~/ 2; + final alpha = (this.alpha + other.alpha) ~/ 2; + return Color.fromARGB(alpha, red, green, blue); + } +} diff --git a/pubspec.lock b/pubspec.lock index 036b3cd865fce9d2c16bd69eea5f54585156f8d0..c1e8da24d00536dce1d9409b0ef6d537b7ff69db 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + args: + dependency: transitive + description: + name: args + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + url: "https://pub.dev" + source: hosted + version: "2.5.0" async: dependency: transitive description: @@ -9,6 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" + bloc: + dependency: transitive + description: + name: bloc + sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e" + url: "https://pub.dev" + source: hosted + version: "8.1.4" characters: dependency: transitive description: @@ -17,6 +33,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" collection: dependency: transitive description: @@ -25,6 +49,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + easy_localization: + dependency: "direct main" + description: + name: easy_localization + sha256: fa59bcdbbb911a764aa6acf96bbb6fa7a5cf8234354fc45ec1a43a0349ef0201 + url: "https://pub.dev" + source: hosted + version: "3.0.7" + easy_logger: + dependency: transitive + description: + name: easy_logger + sha256: c764a6e024846f33405a2342caf91c62e357c24b02c04dbc712ef232bf30ffb7 + url: "https://pub.dev" + source: hosted + version: "0.0.2" + equatable: + dependency: "direct main" + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" ffi: dependency: transitive description: @@ -46,19 +102,40 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a + url: "https://pub.dev" + source: hosted + 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 + source: sdk + version: "0.0.0" flutter_web_plugins: dependency: transitive description: flutter source: sdk version: "0.0.0" + hive: + dependency: "direct main" + description: + name: hive + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" + url: "https://pub.dev" + source: hosted + version: "2.2.3" http: dependency: "direct main" description: @@ -75,14 +152,30 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" + hydrated_bloc: + dependency: "direct main" + description: + name: hydrated_bloc + sha256: af35b357739fe41728df10bec03aad422cdc725a1e702e03af9d2a41ea05160c + url: "https://pub.dev" + source: hosted + version: "9.1.5" + intl: + dependency: transitive + description: + name: intl + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + url: "https://pub.dev" + source: hosted + version: "0.19.0" lints: 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: @@ -95,10 +188,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: @@ -107,6 +200,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: @@ -115,6 +224,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 + url: "https://pub.dev" + source: hosted + version: "2.1.3" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: bca87b0165ffd7cdb9cad8edd22d18d2201e886d9a9f19b4fb3452ea7df3a72a + url: "https://pub.dev" + source: hosted + version: "2.2.6" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + url: "https://pub.dev" + source: hosted + version: "2.4.0" path_provider_linux: dependency: transitive description: @@ -143,10 +276,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: @@ -156,7 +289,7 @@ packages: source: hosted version: "2.1.8" provider: - dependency: "direct main" + dependency: transitive description: name: provider sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c @@ -164,29 +297,29 @@ packages: source: hosted version: "6.1.2" shared_preferences: - dependency: "direct main" + 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: @@ -240,6 +373,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558" + url: "https://pub.dev" + source: hosted + version: "3.1.0+1" term_glyph: dependency: transitive description: @@ -256,6 +397,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + unicons: + dependency: "direct main" + description: + name: unicons + sha256: "1cca7462df18ff191b7e41b52f747d08854916531d1d7ab7cec0552095995206" + url: "https://pub.dev" + source: hosted + version: "2.1.2" vector_math: dependency: transitive description: @@ -268,18 +417,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: @@ -289,5 +438,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 c9b48d1f1fdca6e033f14817780b071315c7598c..dbc4f9f2974a51a9fe978041692da149da5ae877 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,22 +1,47 @@ name: plotter description: A plotter helper application. -publish_to: 'none' -version: 0.0.16+16 + +publish_to: "none" + +version: 0.0.17+17 environment: - sdk: '^3.0.0' + sdk: "^3.0.0" dependencies: flutter: sdk: flutter - provider: ^6.0.5 - shared_preferences: ^2.2.1 + + # base + easy_localization: ^3.0.1 + equatable: ^2.0.5 + flutter_bloc: ^8.1.1 + hive: ^2.2.3 + hydrated_bloc: ^9.0.0 + package_info_plus: ^8.0.0 + path_provider: ^2.0.11 + unicons: ^2.1.1 + + # specific http: ^1.1.0 dev_dependencies: - flutter_lints: ^3.0.1 + flutter_lints: ^4.0.0 flutter: uses-material-design: true assets: - - assets/icons/ + - assets/translations/ + + fonts: + - family: Nunito + fonts: + - asset: assets/fonts/Nunito-Bold.ttf + weight: 700 + - asset: assets/fonts/Nunito-Medium.ttf + weight: 500 + - asset: assets/fonts/Nunito-Regular.ttf + weight: 400 + - asset: assets/fonts/Nunito-Light.ttf + weight: 300 + diff --git a/icons/build_application_icons.sh b/resources/app/build_application_resources.sh similarity index 98% rename from icons/build_application_icons.sh rename to resources/app/build_application_resources.sh index 27dbe2647fe4e6d562fbd99451716d1b7d448570..6d67b8f4f9eca701d1aed7331ef41dfb0bd44f20 100755 --- a/icons/build_application_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 +