diff --git a/android/app/build.gradle b/android/app/build.gradle index 68d11b9827af24ce72f7a2482f642af303138397..ce3ad4412c7ecb9ac76c52f8e7dd79b512e7aada 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.midisynth" defaultConfig { diff --git a/android/gradle.properties b/android/gradle.properties index 4bb5439f682100f8ef4ba80a557fe4f2f0ab14c2..6bf54a6ed821c19f76d860d4a24e7c85d440b575 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.9 -app.versionCode=9 +app.versionName=0.0.10 +app.versionCode=10 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/button_back.png b/assets/icons/button_back.png deleted file mode 100644 index cc48ffb1dbb653d9a996f139dfbe02969724bfa5..0000000000000000000000000000000000000000 Binary files a/assets/icons/button_back.png and /dev/null differ diff --git a/assets/icons/button_start.png b/assets/icons/button_start.png deleted file mode 100644 index 6845e2f5c21598ab61f1684d2075aeec0334bf23..0000000000000000000000000000000000000000 Binary files a/assets/icons/button_start.png and /dev/null differ diff --git a/assets/icons/placeholder.png b/assets/icons/placeholder.png deleted file mode 100644 index 814df31be6ddc4275ebe4490c79365578dbef1f0..0000000000000000000000000000000000000000 Binary files a/assets/icons/placeholder.png and /dev/null differ diff --git a/assets/translations/en.json b/assets/translations/en.json new file mode 100644 index 0000000000000000000000000000000000000000..874e6efd49e604fa85d9620e7c10f8873a4c3004 --- /dev/null +++ b/assets/translations/en.json @@ -0,0 +1,16 @@ +{ + "app_name": "MIDI synth", + + "settings_title": "Settings", + "settings_label_theme": "Theme mode", + + "about_title": "Informations", + "about_content": "MIDI synth", + "about_version": "Version: {version}", + + "page_home": "Home", + "page_editor": "Editor", + "page_player": "Player", + + "": "" +} diff --git a/assets/translations/fr.json b/assets/translations/fr.json new file mode 100644 index 0000000000000000000000000000000000000000..a3341539c8aecf4713b39588620a1e5aa2ff59f9 --- /dev/null +++ b/assets/translations/fr.json @@ -0,0 +1,16 @@ +{ + "app_name": "MIDI synth", + + "settings_title": "Réglages", + "settings_label_theme": "Thème de couleurs", + + "about_title": "Informations", + "about_content": "MIDI synth.", + "about_version": "Version : {version}", + + "page_home": "Accueil", + "page_editor": "Édition", + "page_player": "Lecture", + + "": "" +} diff --git a/fastlane/metadata/android/en-US/changelogs/10.txt b/fastlane/metadata/android/en-US/changelogs/10.txt new file mode 100644 index 0000000000000000000000000000000000000000..060b202b87dcd33388eb8a9d1a45cbd276fc8ba3 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/10.txt @@ -0,0 +1 @@ +Improve/normalize app architecture. diff --git a/fastlane/metadata/android/fr-FR/changelogs/10.txt b/fastlane/metadata/android/fr-FR/changelogs/10.txt new file mode 100644 index 0000000000000000000000000000000000000000..3cab228cbe7443782c4b187cbe805e4d0ba8e71a --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/10.txt @@ -0,0 +1 @@ +Amélioration/normalisation de l'architecture de l'application. diff --git a/fdroid_metadata.yml b/fdroid_metadata.yml index ab040ae9b34bb313800b3e6f07fcf135f08ae637..e1453e66da73c9b8c3bc20a0e8f09d06913800f6 100644 --- a/fdroid_metadata.yml +++ b/fdroid_metadata.yml @@ -1,6 +1,5 @@ AuthorName: 'Benoît Harrault' Categories: - - Games - Multimedia Name: MIDI Synth AutoName: MidiSynth diff --git a/icons/build_application_icons.sh b/icons/build_application_icons.sh deleted file mode 100755 index 2ae92914464ceb9984af27c4b0ad1a4ff920bd55..0000000000000000000000000000000000000000 --- a/icons/build_application_icons.sh +++ /dev/null @@ -1,82 +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}")" -ASSETS_DIR="${BASE_DIR}/assets" - -OPTIPNG_OPTIONS="-preserve -quiet -o7" -ICON_SIZE=192 - -####################################################### - -# Game images -AVAILABLE_GAME_IMAGES=" - button_back - button_start - placeholder -" - -# Settings images -AVAILABLES_GAME_SETTINGS=" -" - -####################################################### - -# optimize svg -function optimize_svg() { - SOURCE="$1" - - cp ${SOURCE} ${SOURCE}.tmp - scour \ - --remove-descriptive-elements \ - --enable-id-stripping \ - --enable-viewboxing \ - --enable-comment-stripping \ - --nindent=4 \ - --quiet \ - -i ${SOURCE}.tmp \ - -o ${SOURCE} - rm ${SOURCE}.tmp -} - -# build icons -function build_icon() { - SOURCE="$1" - TARGET="$2" - - echo "Building ${TARGET}" - - if [ ! -f "${SOURCE}" ]; then - echo "Missing file: ${SOURCE}" - exit 1 - fi - - optimize_svg "${SOURCE}" - - inkscape \ - --export-width=${ICON_SIZE} \ - --export-height=${ICON_SIZE} \ - --export-filename=${TARGET} \ - ${SOURCE} - - optipng ${OPTIPNG_OPTIONS} ${TARGET} -} - -####################################################### - -# Create output folders -mkdir -p ${ASSETS_DIR}/icons - -# Delete existing generated images -find ${ASSETS_DIR}/icons -type f -name "*.png" -delete - -# build game images -for GAME_IMAGE in ${AVAILABLE_GAME_IMAGES} -do - build_icon ${CURRENT_DIR}/${GAME_IMAGE}.svg ${ASSETS_DIR}/icons/${GAME_IMAGE}.png -done diff --git a/icons/button_back.svg b/icons/button_back.svg deleted file mode 100644 index 2622a578dba53ce582afabfc587c2a85a1fb6eaa..0000000000000000000000000000000000000000 --- a/icons/button_back.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 93.665 93.676" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x=".44662" y=".89101" width="92.772" height="91.894" ry="11.689" fill="#e41578" stroke="#fff" stroke-width=".238"/><path d="m59.387 71.362c1.1248 1.1302 4.0012 1.1302 4.0012 0v-45.921c0-1.1316-2.8832-1.1316-4.0121 0l-37.693 20.918c-1.1289 1.1248-1.1479 2.9551-0.02171 4.084z" fill="#fefeff" stroke="#930e4e" stroke-linecap="round" stroke-linejoin="round" stroke-width="8.257"/><path d="m57.857 68.048c0.96243 0.96706 3.4236 0.96706 3.4236 0v-39.292c0-0.96825-2.467-0.96825-3.4329 0l-32.252 17.898c-0.96594 0.96243-0.9822 2.5285-0.01858 3.4945z" fill="#fefeff" stroke="#feffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="4.314"/></svg> diff --git a/icons/button_start.svg b/icons/button_start.svg deleted file mode 100644 index e9d49d2172b9a0305db82779971e3c1e12f34a70..0000000000000000000000000000000000000000 --- a/icons/button_start.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 93.665 93.676" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x=".44662" y=".89101" width="92.772" height="91.894" ry="11.689" fill="#49a1ee" stroke="#fff" stroke-width=".238"/><path d="m34.852 25.44c-1.1248-1.1302-4.0012-1.1302-4.0012 0v45.921c0 1.1316 2.8832 1.1316 4.0121 0l37.693-20.918c1.1289-1.1248 1.1479-2.9551 0.02171-4.084z" fill="#fefeff" stroke="#105ca1" stroke-linecap="round" stroke-linejoin="round" stroke-width="8.257"/><path d="m36.382 28.754c-0.96243-0.96706-3.4236-0.96706-3.4236 0v39.292c0 0.96825 2.467 0.96825 3.4329 0l32.252-17.898c0.96594-0.96243 0.9822-2.5285 0.01858-3.4945z" fill="#fefeff" stroke="#feffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="4.314"/></svg> diff --git a/icons/placeholder.svg b/icons/placeholder.svg deleted file mode 100644 index 23ace81fbb82a8409cc0710c0f7bddd6381f7256..0000000000000000000000000000000000000000 --- a/icons/placeholder.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"/> diff --git a/lib/config/activity_page.dart b/lib/config/activity_page.dart new file mode 100644 index 0000000000000000000000000000000000000000..a3106b2e50a32e17b7f7d9f740feab599b8c2d09 --- /dev/null +++ b/lib/config/activity_page.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:unicons/unicons.dart'; + +import 'package:midisynth/ui/pages/editor.dart'; +import 'package:midisynth/ui/pages/home.dart'; +import 'package:midisynth/ui/pages/player.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 indexEditor = 1; + static const pageEditor = ActivityPageItem( + icon: Icon(UniconsLine.setting), + page: PageEditor(), + code: 'page_editor', + ); + + static const indexPlayer = 2; + static const pagePlayer = ActivityPageItem( + icon: Icon(UniconsLine.info_circle), + page: PagePlayer(), + code: 'page_player', + ); + + static Map<int, ActivityPageItem> items = { + indexHome: pageHome, + indexEditor: pageEditor, + indexPlayer: pagePlayer, + }; + + 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_activity_settings.dart b/lib/config/default_activity_settings.dart new file mode 100644 index 0000000000000000000000000000000000000000..3adf06773f9cfe23441db7256a829d89eb2dc0b8 --- /dev/null +++ b/lib/config/default_activity_settings.dart @@ -0,0 +1,48 @@ +import 'package:midisynth/utils/tools.dart'; + +class DefaultActivitySettings { + // available activity parameters codes + static const String parameterCodeSequenceLength = 'sequenceLength'; + static const List<String> availableParameters = [ + parameterCodeSequenceLength, + ]; + + // items count: available values + static const String sequenceLengthValueShort = 'short'; + static const String sequenceLengthValueMedium = 'medium'; + static const String sequenceLengthValueLong = 'long'; + static const List<String> allowedSequenceLengthValues = [ + sequenceLengthValueShort, + sequenceLengthValueMedium, + sequenceLengthValueLong, + ]; + // items count: default value + static const String defaultSequenceLengthValue = sequenceLengthValueMedium; + + // available values from parameter code + static List<String> getAvailableValues(String parameterCode) { + switch (parameterCode) { + case parameterCodeSequenceLength: + return DefaultActivitySettings.allowedSequenceLengthValues; + } + + printlog('Did not find any available value for activity parameter "$parameterCode".'); + return []; + } + + static int getSequenceLengthFromParameterValueCode(String parameterLevel) { + const Map<String, int> values = { + sequenceLengthValueShort: 8, + sequenceLengthValueMedium: 16, + sequenceLengthValueLong: 32, + }; + return values[parameterLevel] ?? + getSequenceLengthFromParameterValueCode( + DefaultActivitySettings.defaultSequenceLengthValue); + } + + // parameters displayed with assets (instead of painter) + static List<String> displayedWithAssets = [ + // + ]; +} diff --git a/lib/config/default_global_settings.dart b/lib/config/default_global_settings.dart new file mode 100644 index 0000000000000000000000000000000000000000..178f5a540063f79dcc7f18741e3676a4308380d1 --- /dev/null +++ b/lib/config/default_global_settings.dart @@ -0,0 +1,33 @@ +import 'package:midisynth/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..66edd028023cfd25ee7f37f9f23fcb9b8f2060c9 --- /dev/null +++ b/lib/config/screen.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:unicons/unicons.dart'; + +import 'package:midisynth/ui/screens/about.dart'; +import 'package:midisynth/ui/screens/activity.dart'; +import 'package:midisynth/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..e54959e3b969ef0fa8cb271bef47b032424a3542 --- /dev/null +++ b/lib/cubit/activity_cubit.dart @@ -0,0 +1,90 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:midisynth/models/activity/activity.dart'; +import 'package:midisynth/models/settings/settings_activity.dart'; +import 'package:midisynth/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 + activitySettings: state.currentActivity.activitySettings, + globalSettings: state.currentActivity.globalSettings, + // State + isRunning: state.currentActivity.isRunning, + isStarted: state.currentActivity.isStarted, + isFinished: state.currentActivity.isFinished, + animationInProgress: state.currentActivity.animationInProgress, + // Base data + notes: state.currentActivity.notes, + // Activity data + position: state.currentActivity.position, + ); + // activity.dump(); + + updateState(activity); + } + + void startNewActivity({ + required ActivitySettings activitySettings, + required GlobalSettings globalSettings, + }) { + final Activity newActivity = Activity.createNew( + // Settings + activitySettings: activitySettings, + 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; + state.currentActivity.isFinished = true; + 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..5803e81056034223203b23b71206a8592b5b3c8d --- /dev/null +++ b/lib/cubit/nav_cubit_pages.dart @@ -0,0 +1,25 @@ +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:midisynth/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..42109db7475aae69f545ad7f6fddfe99b2609b72 --- /dev/null +++ b/lib/cubit/nav_cubit_screens.dart @@ -0,0 +1,37 @@ +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:midisynth/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_activity_cubit.dart b/lib/cubit/settings_activity_cubit.dart new file mode 100644 index 0000000000000000000000000000000000000000..24fa54e3a378b0cbc1a1f41eef02cf1f222c1094 --- /dev/null +++ b/lib/cubit/settings_activity_cubit.dart @@ -0,0 +1,64 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:midisynth/config/default_activity_settings.dart'; +import 'package:midisynth/models/settings/settings_activity.dart'; + +part 'settings_activity_state.dart'; + +class ActivitySettingsCubit extends HydratedCubit<ActivitySettingsState> { + ActivitySettingsCubit() + : super(ActivitySettingsState(settings: ActivitySettings.createDefault())); + + void setValues({ + String? sequenceLength, + }) { + emit( + ActivitySettingsState( + settings: ActivitySettings( + sequenceLength: sequenceLength ?? state.settings.sequenceLength, + ), + ), + ); + } + + String getParameterValue(String code) { + switch (code) { + case DefaultActivitySettings.parameterCodeSequenceLength: + return ActivitySettings.getSequenceLengthValueFromUnsafe( + state.settings.sequenceLength); + } + + return ''; + } + + void setParameterValue(String code, String value) { + final String sequenceLength = code == DefaultActivitySettings.parameterCodeSequenceLength + ? value + : getParameterValue(DefaultActivitySettings.parameterCodeSequenceLength); + + setValues( + sequenceLength: sequenceLength, + ); + } + + @override + ActivitySettingsState? fromJson(Map<String, dynamic> json) { + final String sequenceLength = + json[DefaultActivitySettings.parameterCodeSequenceLength] as String; + + return ActivitySettingsState( + settings: ActivitySettings( + sequenceLength: sequenceLength, + ), + ); + } + + @override + Map<String, dynamic>? toJson(ActivitySettingsState state) { + return <String, dynamic>{ + DefaultActivitySettings.parameterCodeSequenceLength: state.settings.sequenceLength, + }; + } +} diff --git a/lib/cubit/settings_activity_state.dart b/lib/cubit/settings_activity_state.dart new file mode 100644 index 0000000000000000000000000000000000000000..2b2de42011634e81ae9e6f8bcaa1577f239c778b --- /dev/null +++ b/lib/cubit/settings_activity_state.dart @@ -0,0 +1,15 @@ +part of 'settings_activity_cubit.dart'; + +@immutable +class ActivitySettingsState extends Equatable { + const ActivitySettingsState({ + required this.settings, + }); + + final ActivitySettings settings; + + @override + List<dynamic> get props => <dynamic>[ + settings, + ]; +} diff --git a/lib/cubit/settings_global_cubit.dart b/lib/cubit/settings_global_cubit.dart new file mode 100644 index 0000000000000000000000000000000000000000..5d9883b6d4c8e6f83247702b3a4e7b8135bc2ce9 --- /dev/null +++ b/lib/cubit/settings_global_cubit.dart @@ -0,0 +1,60 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:midisynth/config/default_global_settings.dart'; +import 'package:midisynth/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, + }) { + emit( + GlobalSettingsState( + settings: GlobalSettings( + skin: skin ?? state.settings.skin, + ), + ), + ); + } + + String getParameterValue(String code) { + switch (code) { + case DefaultGlobalSettings.parameterCodeSkin: + return GlobalSettings.getSkinValueFromUnsafe(state.settings.skin); + } + return ''; + } + + void setParameterValue(String code, String value) { + final String skin = (code == DefaultGlobalSettings.parameterCodeSkin) + ? value + : getParameterValue(DefaultGlobalSettings.parameterCodeSkin); + + setValues( + skin: skin, + ); + } + + @override + GlobalSettingsState? fromJson(Map<String, dynamic> json) { + final String skin = json[DefaultGlobalSettings.parameterCodeSkin] as String; + + return GlobalSettingsState( + settings: GlobalSettings( + skin: skin, + ), + ); + } + + @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/board.dart b/lib/layout/board.dart deleted file mode 100644 index b6fd796dc9dd297d077e32f5b8af1456341ef601..0000000000000000000000000000000000000000 --- a/lib/layout/board.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:midisynth/provider/data.dart'; - -class Board { - static Widget buildGameBoard(Data myProvider, double screenWidth) { - return const Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text(''), - ], - ); - } -} diff --git a/lib/layout/game.dart b/lib/layout/game.dart deleted file mode 100644 index 3a0990bd6ffb7500409e37c4350e3968c4dadcb0..0000000000000000000000000000000000000000 --- a/lib/layout/game.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:midisynth/layout/board.dart'; -import 'package:midisynth/provider/data.dart'; - -class Game { - static Container buildGameWidget(Data myProvider, double screenWidth) { - return Container( - child: Board.buildGameBoard(myProvider, screenWidth), - ); - } -} diff --git a/lib/layout/parameters.dart b/lib/layout/parameters.dart deleted file mode 100644 index 962d9fc075a1982a316756f15e329ac61106b7e5..0000000000000000000000000000000000000000 --- a/lib/layout/parameters.dart +++ /dev/null @@ -1,146 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:midisynth/provider/data.dart'; -import 'package:midisynth/utils/game_utils.dart'; - -class Parameters { - static double separatorHeight = 2.0; - static double blockMargin = 3.0; - static double blockPadding = 2.0; - static Color buttonBackgroundColor = Colors.white; - static Color buttonBorderColorActive = Colors.blue; - static Color buttonBorderColorInactive = Colors.white; - static double buttonBorderWidth = 10.0; - static double buttonBorderRadius = 8.0; - static double buttonPadding = 0.0; - static double buttonMargin = 0.0; - - static Widget buildParametersSelector(Data myProvider) { - final List<Widget> lines = []; - - final List<String> parameters = myProvider.availableParameters; - for (int index = 0; index < parameters.length; index++) { - lines.add(buildParameterSelector(myProvider, parameters[index])); - lines.add(SizedBox(height: separatorHeight)); - } - - final Widget buttonsBlock = buildStartNewGameButton(myProvider); - - return Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox(height: separatorHeight), - Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: lines, - ), - ), - SizedBox(height: separatorHeight), - Container( - child: buttonsBlock, - ), - ], - ); - } - - static Image buildImageWidget(String imageAssetCode) { - return Image( - image: AssetImage('assets/icons/$imageAssetCode.png'), - fit: BoxFit.fill, - ); - } - - static Container buildImageContainerWidget(String imageAssetCode) { - return Container( - child: buildImageWidget(imageAssetCode), - ); - } - - static Column buildDecorationImageWidget() { - return Column( - children: [ - TextButton( - child: buildImageContainerWidget('placeholder'), - onPressed: () {}, - ), - ], - ); - } - - static Container buildStartNewGameButton(Data myProvider) { - return Container( - margin: EdgeInsets.all(blockMargin), - padding: EdgeInsets.all(blockPadding), - child: Table( - defaultColumnWidth: const IntrinsicColumnWidth(), - children: [ - TableRow( - children: [ - buildDecorationImageWidget(), - Column( - children: [ - TextButton( - child: buildImageContainerWidget('button_start'), - onPressed: () => GameUtils.startNewGame(myProvider), - ), - ], - ), - buildDecorationImageWidget(), - ], - ), - ], - ), - ); - } - - static Widget buildParameterSelector(Data myProvider, String parameterCode) { - List availableValues = myProvider.getParameterAvailableValues(parameterCode); - - if (availableValues.length == 1) { - return const SizedBox(height: 0.0); - } - - return Table( - defaultColumnWidth: const IntrinsicColumnWidth(), - children: [ - TableRow( - children: [ - for (var index = 0; index < availableValues.length; index++) - Column( - children: [ - _buildParameterButton(myProvider, parameterCode, availableValues[index]) - ], - ), - ], - ), - ], - ); - } - - static Widget _buildParameterButton( - Data myProvider, String parameterCode, String parameterValue) { - String currentValue = myProvider.getParameterValue(parameterCode).toString(); - - bool isActive = (parameterValue == currentValue); - String imageAsset = '${parameterCode}_$parameterValue'; - - return TextButton( - child: Container( - margin: EdgeInsets.all(buttonMargin), - padding: EdgeInsets.all(buttonPadding), - decoration: BoxDecoration( - color: buttonBackgroundColor, - borderRadius: BorderRadius.circular(buttonBorderRadius), - border: Border.all( - color: isActive ? buttonBorderColorActive : buttonBorderColorInactive, - width: buttonBorderWidth, - ), - ), - child: buildImageWidget(imageAsset), - ), - onPressed: () => myProvider.setParameterValue(parameterCode, parameterValue), - ); - } -} diff --git a/lib/main.dart b/lib/main.dart index ef53a94780703fecbda9512606535fd49af379be..65ba2e7ff910b15576349605c2a2e07aa103f28a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,14 +1,43 @@ +import 'dart:io'; + +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:midisynth/provider/data.dart'; -import 'package:midisynth/screens/home.dart'; -import 'package:provider/provider.dart'; -import 'package:overlay_support/overlay_support.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:midisynth/cubit/activity_cubit.dart'; +import 'package:midisynth/cubit/settings_activity_cubit.dart'; +import 'package:midisynth/config/theme.dart'; +import 'package:midisynth/cubit/nav_cubit_pages.dart'; +import 'package:midisynth/cubit/nav_cubit_screens.dart'; +import 'package:midisynth/cubit/settings_global_cubit.dart'; +import 'package:midisynth/cubit/theme_cubit.dart'; +import 'package:midisynth/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 { @@ -16,23 +45,34 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return ChangeNotifierProvider( - create: (BuildContext context) => Data(), - child: Consumer<Data>(builder: (context, data, child) { - return OverlaySupport( - child: MaterialApp( + 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()), + BlocProvider<ActivitySettingsCubit>(create: (context) => ActivitySettingsCubit()), + ], + child: BlocBuilder<ThemeCubit, ThemeModeState>( + builder: (BuildContext context, ThemeModeState state) { + return MaterialApp( + title: 'MIDI Synth', + 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, - theme: ThemeData( - primaryColor: Colors.blue, - visualDensity: VisualDensity.adaptivePlatformDensity, - ), - home: const Home(), - routes: { - Home.id: (context) => const Home(), - }, - ), - ); - }), + ); + }, + ), ); } } diff --git a/lib/models/activity/activity.dart b/lib/models/activity/activity.dart new file mode 100644 index 0000000000000000000000000000000000000000..13cf7f6e5eaaa0cf4df9091dada8b7ba88e92836 --- /dev/null +++ b/lib/models/activity/activity.dart @@ -0,0 +1,113 @@ +import 'package:midisynth/models/data/note.dart'; +import 'package:midisynth/models/settings/settings_activity.dart'; +import 'package:midisynth/models/settings/settings_global.dart'; +import 'package:midisynth/utils/tools.dart'; + +class Activity { + Activity({ + // Settings + required this.activitySettings, + required this.globalSettings, + + // State + this.isRunning = false, + this.isStarted = false, + this.isFinished = false, + this.animationInProgress = false, + + // Base data + this.notes = const [], + + // Activity data + this.position = 1, + }); + + // Settings + final ActivitySettings activitySettings; + final GlobalSettings globalSettings; + + // State + bool isRunning; + bool isStarted; + bool isFinished; + bool animationInProgress; + + // Base data + List<Note> notes; + + // Activity data + int position; + + factory Activity.createNull() { + return Activity( + // Settings + activitySettings: ActivitySettings.createDefault(), + globalSettings: GlobalSettings.createDefault(), + // Base data + notes: [], + ); + } + + factory Activity.createNew({ + ActivitySettings? activitySettings, + GlobalSettings? globalSettings, + }) { + final ActivitySettings newActivitySettings = + activitySettings ?? ActivitySettings.createDefault(); + final GlobalSettings newGlobalSettings = globalSettings ?? GlobalSettings.createDefault(); + + return Activity( + // Settings + activitySettings: newActivitySettings, + globalSettings: newGlobalSettings, + // State + isRunning: true, + // Base data + notes: [], + ); + } + + bool get canBeResumed => isStarted && !isFinished; + + void dump() { + printlog(''); + printlog('## Current activity dump:'); + printlog(''); + printlog('$Activity:'); + printlog(' Settings'); + activitySettings.dump(); + globalSettings.dump(); + printlog(' State'); + printlog(' isRunning: $isRunning'); + printlog(' isStarted: $isStarted'); + printlog(' isFinished: $isFinished'); + printlog(' animationInProgress: $animationInProgress'); + printlog(' Base data'); + printlog(' notes: $notes'); + printlog(' Activity data'); + printlog(' position: $position'); + printlog(''); + } + + @override + String toString() { + return '$Activity(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + // Settings + 'activitySettings': activitySettings.toJson(), + 'globalSettings': globalSettings.toJson(), + // State + 'isRunning': isRunning, + 'isStarted': isStarted, + 'isFinished': isFinished, + 'animationInProgress': animationInProgress, + // Base data + 'notes': notes, + // Activity data + 'position': position, + }; + } +} diff --git a/lib/models/data/note.dart b/lib/models/data/note.dart new file mode 100644 index 0000000000000000000000000000000000000000..a6f707071181d7f4802a04410afccb95e48333c5 --- /dev/null +++ b/lib/models/data/note.dart @@ -0,0 +1,21 @@ +class Note { + final String key; + final String note; + + const Note({ + required this.key, + required this.note, + }); + + @override + String toString() { + return '$Note(${toJson()})'; + } + + Map<String, dynamic> toJson() { + return { + 'key': key, + 'note': note, + }; + } +} diff --git a/lib/models/settings/settings_activity.dart b/lib/models/settings/settings_activity.dart new file mode 100644 index 0000000000000000000000000000000000000000..d17d7066bfad5f7f052c7f909bd719648f5953ca --- /dev/null +++ b/lib/models/settings/settings_activity.dart @@ -0,0 +1,44 @@ +import 'package:midisynth/config/default_activity_settings.dart'; +import 'package:midisynth/utils/tools.dart'; + +class ActivitySettings { + final String sequenceLength; + + ActivitySettings({ + required this.sequenceLength, + }); + + // Getters to convert String to int + int get sequenceLengthValue => int.parse(sequenceLength); + + static String getSequenceLengthValueFromUnsafe(String sequenceLength) { + if (DefaultActivitySettings.allowedSequenceLengthValues.contains(sequenceLength)) { + return sequenceLength; + } + + return DefaultActivitySettings.defaultSequenceLengthValue; + } + + factory ActivitySettings.createDefault() { + return ActivitySettings( + sequenceLength: DefaultActivitySettings.defaultSequenceLengthValue, + ); + } + + void dump() { + printlog('$ActivitySettings:'); + printlog(' ${DefaultActivitySettings.parameterCodeSequenceLength}: $sequenceLength'); + printlog(''); + } + + @override + String toString() { + return '$ActivitySettings(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + DefaultActivitySettings.parameterCodeSequenceLength: sequenceLength, + }; + } +} diff --git a/lib/models/settings/settings_global.dart b/lib/models/settings/settings_global.dart new file mode 100644 index 0000000000000000000000000000000000000000..28d8d6059082eb8f9d4e1f277ec82de9933cc8eb --- /dev/null +++ b/lib/models/settings/settings_global.dart @@ -0,0 +1,41 @@ +import 'package:midisynth/config/default_global_settings.dart'; +import 'package:midisynth/utils/tools.dart'; + +class GlobalSettings { + String skin; + + GlobalSettings({ + required this.skin, + }); + + static String getSkinValueFromUnsafe(String skin) { + if (DefaultGlobalSettings.allowedSkinValues.contains(skin)) { + return skin; + } + + return DefaultGlobalSettings.defaultSkinValue; + } + + factory GlobalSettings.createDefault() { + return GlobalSettings( + skin: DefaultGlobalSettings.defaultSkinValue, + ); + } + + void dump() { + printlog('$GlobalSettings:'); + printlog(' ${DefaultGlobalSettings.parameterCodeSkin}: $skin'); + printlog(''); + } + + @override + String toString() { + return '$GlobalSettings(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + DefaultGlobalSettings.parameterCodeSkin: skin, + }; + } +} diff --git a/lib/provider/data.dart b/lib/provider/data.dart deleted file mode 100644 index 2d87dbc85dcdf759eceec244dadf626e9b5442c2..0000000000000000000000000000000000000000 --- a/lib/provider/data.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -class Data extends ChangeNotifier { - // Configuration available parameters - final List<String> _availableParameters = []; - - List<String> get availableParameters => _availableParameters; - - // Application default configuration - - // Application current configuration - String getParameterValue(String parameterCode) { - switch (parameterCode) { - // - } - return ''; - } - - List getParameterAvailableValues(String parameterCode) { - switch (parameterCode) { - // - } - return []; - } - - void setParameterValue(String parameterCode, String parameterValue) async { - switch (parameterCode) { - // - } - final prefs = await SharedPreferences.getInstance(); - prefs.setString(parameterCode, parameterValue); - } - - // Game data - bool _gameIsRunning = false; - bool _gameIsFinished = false; - - bool get isGameRunning => _gameIsRunning; - void updateGameIsRunning(bool gameIsRunning) { - _gameIsRunning = gameIsRunning; - notifyListeners(); - } - - bool get isGameFinished => _gameIsFinished; - void updateGameIsFinished(bool gameIsFinished) { - _gameIsFinished = gameIsFinished; - notifyListeners(); - } - - void redraw() { - notifyListeners(); - } - - void resetGame() { - _gameIsRunning = false; - _gameIsFinished = false; - notifyListeners(); - } -} diff --git a/lib/screens/home.dart b/lib/screens/home.dart deleted file mode 100644 index d28800487700a4f2c392965115076e652e0bc0fd..0000000000000000000000000000000000000000 --- a/lib/screens/home.dart +++ /dev/null @@ -1,64 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:midisynth/layout/game.dart'; -import 'package:midisynth/layout/parameters.dart'; -import 'package:midisynth/provider/data.dart'; -import 'package:midisynth/utils/game_utils.dart'; -import 'package:provider/provider.dart'; -import 'package:overlay_support/overlay_support.dart'; - -class Home extends StatefulWidget { - const Home({super.key}); - - static const String id = 'home'; - - @override - HomeState createState() => HomeState(); -} - -class HomeState extends State<Home> { - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - final Data myProvider = Provider.of<Data>(context); - final double screenWidth = MediaQuery.of(context).size.width; - - final List<Widget> menuActions = []; - - if (myProvider.isGameRunning) { - menuActions.add( - TextButton( - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - border: Border.all( - color: Colors.blue, - width: 4, - ), - ), - child: const Image( - image: AssetImage('assets/icons/button_back.png'), - fit: BoxFit.fill, - ), - ), - onPressed: () => toast('Long press to quit game...'), - onLongPress: () => GameUtils.quitGame(myProvider), - ), - ); - } - - return Scaffold( - appBar: AppBar( - actions: menuActions, - ), - body: SafeArea( - child: myProvider.isGameRunning - ? Game.buildGameWidget(myProvider, screenWidth) - : Parameters.buildParametersSelector(myProvider), - ), - ); - } -} 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..726979e0f712c0fcf842938d2bae0474e736c9cc --- /dev/null +++ b/lib/ui/helpers/outlined_text_widget.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; + +import 'package:midisynth/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..bf852611ac00b5c0c0c4c173b30b70279f30a45b --- /dev/null +++ b/lib/ui/nav/bottom_nav_bar.dart @@ -0,0 +1,50 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:midisynth/config/activity_page.dart'; +import 'package:midisynth/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.pageEditor, + ActivityPage.pagePlayer, + ]; + 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..6e1da077af91e0b46bfe19067d4abf37c342eb93 --- /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:midisynth/config/screen.dart'; +import 'package:midisynth/cubit/activity_cubit.dart'; +import 'package:midisynth/cubit/nav_cubit_screens.dart'; +import 'package:midisynth/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/editor.dart b/lib/ui/pages/editor.dart new file mode 100644 index 0000000000000000000000000000000000000000..000b752269f15e46274f16dd657d2c92be518352 --- /dev/null +++ b/lib/ui/pages/editor.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:midisynth/cubit/activity_cubit.dart'; +import 'package:midisynth/models/activity/activity.dart'; +import 'package:midisynth/ui/helpers/outlined_text_widget.dart'; +import 'package:midisynth/utils/color_extensions.dart'; + +class PageEditor extends StatelessWidget { + const PageEditor({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: '[editor]', + fontSize: 50, + textColor: Colors.blueAccent, + outlineColor: Colors.blueAccent.lighten(20), + ), + Text(currentActivity.toString()), + ], + ); + }, + ); + } +} diff --git a/lib/ui/pages/home.dart b/lib/ui/pages/home.dart new file mode 100644 index 0000000000000000000000000000000000000000..a31b40a84818b0548205c489e85097127e09b1f6 --- /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:midisynth/cubit/activity_cubit.dart'; +import 'package:midisynth/models/activity/activity.dart'; +import 'package:midisynth/ui/helpers/outlined_text_widget.dart'; +import 'package:midisynth/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..d35fd7d0d486ccd8811d4ff89c2df14c7003ed7e --- /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:midisynth/cubit/activity_cubit.dart'; +import 'package:midisynth/models/activity/activity.dart'; +import 'package:midisynth/ui/helpers/outlined_text_widget.dart'; +import 'package:midisynth/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/screens/about.dart b/lib/ui/screens/about.dart new file mode 100644 index 0000000000000000000000000000000000000000..f3cbaff6195953cc7a9eb17044b3f49e8de44da3 --- /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:midisynth/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..e024a7bdcb3d603ae758f367afd6d63db130273e --- /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:midisynth/config/activity_page.dart'; +import 'package:midisynth/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..771644362de79ed0cba27f0dfda1482dc5f1304e --- /dev/null +++ b/lib/ui/screens/settings.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; + +import 'package:midisynth/ui/helpers/app_titles.dart'; +import 'package:midisynth/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..4a92bf99b557f3b0bc76fced37e0daf9cecc0152 --- /dev/null +++ b/lib/ui/settings/settings_form.dart @@ -0,0 +1,63 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:unicons/unicons.dart'; + +import 'package:midisynth/ui/settings/theme_card.dart'; + +class SettingsForm extends StatefulWidget { + const SettingsForm({super.key}); + + @override + State<SettingsForm> createState() => _SettingsFormState(); +} + +class _SettingsFormState extends State<SettingsForm> { + @override + void dispose() { + super.dispose(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + } + + @override + Widget build(BuildContext context) { + 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), + ], + ); + } +} diff --git a/lib/ui/settings/theme_card.dart b/lib/ui/settings/theme_card.dart new file mode 100644 index 0000000000000000000000000000000000000000..bbf579d5ba60f5a219ff9af4a87df9c7499b9253 --- /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:midisynth/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..82d066f3169198c9a8eddb2782d3ab649a6023ac --- /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:midisynth/config/screen.dart'; +import 'package:midisynth/cubit/nav_cubit_screens.dart'; +import 'package:midisynth/ui/nav/bottom_nav_bar.dart'; +import 'package:midisynth/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/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/lib/utils/game_utils.dart b/lib/utils/game_utils.dart deleted file mode 100644 index e07ec70e63a49572866168471f50a1bf745440c1..0000000000000000000000000000000000000000 --- a/lib/utils/game_utils.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:midisynth/provider/data.dart'; - -import 'package:midisynth/utils/tools.dart'; - -class GameUtils { - static Future<void> quitGame(Data myProvider) async { - myProvider.updateGameIsRunning(false); - } - - static Future<void> startNewGame(Data myProvider) async { - printlog('Starting new game'); - - myProvider.resetGame(); - myProvider.updateGameIsRunning(true); - } -} diff --git a/pubspec.lock b/pubspec.lock index 6b1fdfef808496ebd4ff8ce5bb9e51bfbc6469e8..2b8cadbe20147bf06079a99728c9572392dee747 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,27 +102,80 @@ 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: transitive + description: + name: http + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + 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: @@ -79,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: @@ -91,14 +200,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" - overlay_support: + package_info_plus: dependency: "direct main" description: - name: overlay_support - sha256: fc39389bfd94e6985e1e13b2a88a125fc4027608485d2d4e2847afe1b2bb339c + name: package_info_plus + sha256: b93d8b4d624b4ea19b0a5a208b2d6eff06004bc3ce74c06040b120eeadd00ce0 url: "https://pub.dev" source: hosted - version: "2.1.0" + 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: @@ -107,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: @@ -135,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: @@ -148,7 +289,7 @@ packages: source: hosted version: "2.1.8" provider: - dependency: "direct main" + dependency: transitive description: name: provider sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c @@ -156,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: @@ -216,6 +357,54 @@ packages: description: flutter source: sdk version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + 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: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + 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: @@ -228,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: @@ -249,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 1f88ff7d783e398a52712e73f10ed106c00f56af..d38ac0d6b06500427fff5d4824e93d9f362580cf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,22 +1,44 @@ name: midisynth description: MIDI Synth -publish_to: 'none' -version: 0.0.9+9 + +publish_to: "none" + +version: 0.0.10+10 environment: - sdk: '^3.0.0' + sdk: "^3.0.0" dependencies: flutter: sdk: flutter - provider: ^6.0.5 - shared_preferences: ^2.2.1 - overlay_support: ^2.1.0 + + # 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 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_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/icons/build_icons.sh b/resources/build_resources.sh similarity index 51% rename from icons/build_icons.sh rename to resources/build_resources.sh index 915a16cb0b0b9b3a7109ef6686af811fc93cb8bb..8d5848a985ed1068f6e6226379a9cedb69c3fa15 100755 --- a/icons/build_icons.sh +++ b/resources/build_resources.sh @@ -2,5 +2,5 @@ CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" -"${CURRENT_DIR}/build_application_icons.sh" -"${CURRENT_DIR}/build_repository_icons.sh" +${CURRENT_DIR}/app/build_application_resources.sh +