diff --git a/AUTHORS.md b/AUTHORS.md index e8e7ac60..d886c6d7 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -32,6 +32,7 @@ - sizzlesloth - - Arya Singh - - Xianglin Zeng - +- Sangharsh Sulke - ## Translators diff --git a/Gemfile b/Gemfile index 2ccf2ecb..73fd0e0c 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,9 @@ source "https://rubygems.org" gem "fastlane" +gem 'logger' +gem 'mutex_m' +gem 'abbrev' + plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/Gemfile.lock b/Gemfile.lock index 3b1f941c..f8cc1530 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -5,25 +5,26 @@ GEM base64 nkf rexml + abbrev (0.1.2) addressable (2.8.7) public_suffix (>= 2.0.2, < 7.0) artifactory (3.0.17) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.1003.0) - aws-sdk-core (3.212.0) + aws-partitions (1.1042.0) + aws-sdk-core (3.216.1) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) aws-sigv4 (~> 1.9) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.95.0) - aws-sdk-core (~> 3, >= 3.210.0) + aws-sdk-kms (1.97.0) + aws-sdk-core (~> 3, >= 3.216.0) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.170.0) - aws-sdk-core (~> 3, >= 3.210.0) + aws-sdk-s3 (1.178.0) + aws-sdk-core (~> 3, >= 3.216.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) - aws-sigv4 (1.10.1) + aws-sigv4 (1.11.0) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) base64 (0.2.0) @@ -58,8 +59,8 @@ GEM faraday-em_synchrony (1.0.0) faraday-excon (1.1.0) faraday-httpclient (1.0.1) - faraday-multipart (1.0.4) - multipart-post (~> 2) + faraday-multipart (1.1.0) + multipart-post (~> 2.0) faraday-net_http (1.0.2) faraday-net_http_persistent (1.2.0) faraday-patron (1.0.0) @@ -67,8 +68,8 @@ GEM faraday-retry (1.0.3) faraday_middleware (1.2.1) faraday (~> 1.0) - fastimage (2.3.1) - fastlane (2.225.0) + fastimage (2.4.0) + fastlane (2.226.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -108,7 +109,7 @@ GEM tty-spinner (>= 0.8.0, < 1.0.0) word_wrap (~> 1.0.0) xcodeproj (>= 1.13.0, < 2.0.0) - xcpretty (~> 0.3.0) + xcpretty (~> 0.4.0) xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) fastlane-plugin-versioning (0.7.0) fastlane-sirp (1.0.0) @@ -151,23 +152,25 @@ GEM os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) highline (2.0.3) - http-cookie (1.0.7) + http-cookie (1.0.8) domain_name (~> 0.5) httpclient (2.8.3) jmespath (1.6.2) - json (2.8.1) - jwt (2.9.3) + json (2.9.1) + jwt (2.10.1) base64 + logger (1.6.5) mini_magick (4.13.2) mini_mime (1.1.5) multi_json (1.15.0) multipart-post (2.4.1) + mutex_m (0.3.0) nanaimo (0.4.0) naturally (2.2.1) nkf (0.2.0) optparse (0.6.0) os (1.1.4) - plist (3.7.1) + plist (3.7.2) public_suffix (6.0.1) rake (13.2.1) representable (3.2.0) @@ -175,10 +178,10 @@ GEM trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) - rexml (3.3.9) - rouge (2.0.7) + rexml (3.4.0) + rouge (3.28.0) ruby2_keywords (0.0.5) - rubyzip (2.3.2) + rubyzip (2.4.1) security (0.1.5) signet (0.19.0) addressable (~> 2.8) @@ -207,8 +210,8 @@ GEM colored2 (~> 3.1) nanaimo (~> 0.4.0) rexml (>= 3.3.6, < 4.0) - xcpretty (0.3.0) - rouge (~> 2.0.7) + xcpretty (0.4.0) + rouge (~> 3.28.0) xcpretty-travis-formatter (1.0.1) xcpretty (~> 0.2, >= 0.0.7) @@ -216,11 +219,15 @@ PLATFORMS arm64-darwin-21 arm64-darwin-22 arm64-darwin-23 + arm64-darwin-24 x86_64-linux DEPENDENCIES + abbrev fastlane fastlane-plugin-versioning + logger + mutex_m BUNDLED WITH - 2.2.32 + 2.6.3 diff --git a/fastlane/metadata/android/uk/video.txt b/fastlane/metadata/android/uk/video.txt index e69de29b..8b137891 100644 --- a/fastlane/metadata/android/uk/video.txt +++ b/fastlane/metadata/android/uk/video.txt @@ -0,0 +1 @@ + diff --git a/flatpak/de.wger.flutter.metainfo.xml b/flatpak/de.wger.flutter.metainfo.xml index 2ae67f65..fbcca043 100755 --- a/flatpak/de.wger.flutter.metainfo.xml +++ b/flatpak/de.wger.flutter.metainfo.xml @@ -8,32 +8,26 @@ fitness goals.

+

Workout Management

    -
  • - Workout Management -
      -
    • Create and customize workout routines tailored to your fitness level and - goals -
    • -
    • Track your progress with detailed exercise logs
    • -
    • Access a vast library of exercises
    • -
    -
  • -
  • - Nutrition Tracking -
      -
    • Log your food intake using the Open Food Facts database
    • -
    • Calculate your calorie intake and macronutrient breakdown
    • -
    • Set personalized dietary goals and track your progress
    • -
    -
  • -
  • - Body Measurement Tracking -
      -
    • Monitor your body weight, body fat percentage, and other measurements
    • -
    • Visualize your progress with charts and graphs
    • -
    +
  • Create and customize workout routines tailored to your fitness level and + goals
  • +
  • Track your progress with detailed exercise logs
  • +
  • Access a vast library of exercises
  • +
+ +

Nutrition Tracking

+
    +
  • Log your food intake using the Open Food Facts database
  • +
  • Calculate your calorie intake and macronutrient breakdown
  • +
  • Set personalized dietary goals and track your progress
  • +
+ +

Body Measurement Tracking

+
    +
  • Monitor your body weight, body fat percentage, and other measurements
  • +
  • Visualize your progress with charts and graphs
diff --git a/lib/helpers/consts.dart b/lib/helpers/consts.dart index 1d255751..db66b4f8 100644 --- a/lib/helpers/consts.dart +++ b/lib/helpers/consts.dart @@ -62,6 +62,7 @@ const PREFS_LAST_UPDATED_LANGUAGES = 'lastUpdatedLanguages'; const PREFS_INGREDIENTS = 'ingredientData'; const PREFS_WORKOUT_UNITS = 'workoutUnits'; const PREFS_USER = 'userData'; +const PREFS_USER_DARK_THEME = 'userDarkMode'; const PREFS_LAST_SERVER = 'lastServer'; const DEFAULT_ANIMATION_DURATION = Duration(milliseconds: 200); diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index cd52c000..fde38997 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -925,5 +925,9 @@ "indicatorAvg": "avg", "@indicatorAvg": { "description": "added for localization of Class Indicator's field text" - } + }, + "themeMode": "Theme mode", + "darkMode": "Always dark mode", + "lightMode": "Always light mode", + "systemMode": "System settings" } diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/lib/l10n/app_nl.arb @@ -0,0 +1 @@ +{} diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 6888e85f..811f59ad 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -161,7 +161,7 @@ "@planned": { "description": "Header for the column of 'planned' nutritional values, i.e. what should be eaten" }, - "weekAverage": "média dos 7 dias", + "weekAverage": "Média dos 7 dias", "@weekAverage": { "description": "Header for the column of '7 day average' nutritional values, i.e. what was logged last week" }, @@ -775,5 +775,90 @@ "aboutDonateTitle": "Doar", "@aboutDonateTitle": {}, "aboutDonateText": "Nos compre um café para ajudar o projeto, pagar os custos do servidor e nos manter energizados", - "@aboutDonateText": {} + "@aboutDonateText": {}, + "onlyLogging": "Só controlar calorias", + "@onlyLogging": {}, + "goalEnergy": "Meta energética", + "@goalEnergy": {}, + "goalProtein": "Meta proteica", + "@goalProtein": {}, + "goalCarbohydrates": "Meta de carboidratos", + "@goalCarbohydrates": {}, + "goalFat": "Meta de gordura", + "@goalFat": {}, + "today": "Hoje", + "@today": {}, + "kcalValue": "{value} kcal", + "@kcalValue": { + "description": "A value in kcal, e.g. 500 kcal", + "type": "text", + "placeholders": { + "value": {} + } + }, + "gValue": "{value} g", + "@gValue": { + "description": "A value in grams, e.g. 5 g", + "type": "text", + "placeholders": { + "value": {} + } + }, + "percentValue": "{value} %", + "@percentValue": { + "description": "A value in percent, e.g. 10 %", + "type": "text", + "placeholders": { + "value": {} + } + }, + "noIngredientsDefined": "Sem ingredientes definido ainda", + "@noIngredientsDefined": {}, + "ingredientLogged": "Ingrediente ativo registrado diariamente", + "@ingredientLogged": {}, + "onlyLoggingHelpText": "Marque a caixa se você desejar apenas o seu registo de calorias e não quer a configuração de um detalhado plano nutricional com refeições", + "@onlyLoggingHelpText": {}, + "goalMacro": "Macro objetivos", + "@goalMacro": { + "description": "The goal for macronutrients" + }, + "selectMealToLog": "Selecione uma refeição para entrar no diário", + "@selectMealToLog": {}, + "goalFiber": "Meta de fibra", + "@goalFiber": {}, + "deficit": "Défice", + "@deficit": { + "description": "Caloric deficit (either planned or unplanned)" + }, + "surplus": "Excedente", + "@surplus": { + "description": "Caloric surplus (either planned or unplanned)" + }, + "chart30DaysTitle": "{name} Ultimos 30 dias", + "@chart30DaysTitle": { + "description": "last 30 days chart of 'name' (e.g. 'weight', 'body fat' etc.)", + "type": "text", + "placeholders": { + "name": {} + } + }, + "chartDuringPlanTitle": "{chartName} Durante o plano nutricional {planName}", + "@chartDuringPlanTitle": { + "description": "chart of 'chartName' (e.g. 'weight', 'body fat' etc.) logged during plan", + "type": "text", + "placeholders": { + "chartName": {}, + "planName": {} + } + }, + "loggedToday": "Registrado hoje", + "@loggedToday": {}, + "chartAllTimeTitle": "{name} de todo o tempo", + "@chartAllTimeTitle": { + "description": "All-time chart of 'name' (e.g. 'weight', 'body fat' etc.)", + "type": "text", + "placeholders": { + "name": {} + } + } } diff --git a/lib/l10n/app_pt_BR.arb b/lib/l10n/app_pt_BR.arb index 60d6a0ea..25c9a66f 100644 --- a/lib/l10n/app_pt_BR.arb +++ b/lib/l10n/app_pt_BR.arb @@ -99,7 +99,7 @@ "@comment": { "description": "Comment, additional information" }, - "logIngredient": "Salvar no diário nutricional", + "logIngredient": "Salvar ingrediente no diário nutricional", "@logIngredient": {}, "equipment": "Equipamento", "@equipment": { @@ -221,7 +221,7 @@ "@enterValue": { "description": "Error message when the user hasn't entered a value on a required field" }, - "logMeal": "Registrar esta refeição", + "logMeal": "Registrar refeição para diário de nutrição", "@logMeal": {}, "newEntry": "Nova entrada", "@newEntry": { @@ -677,5 +677,274 @@ "ingredientLogged": "Ingrediente registrado no diário", "@ingredientLogged": {}, "noIngredientsDefined": "Nenhum ingrediente definido ainda", - "@noIngredientsDefined": {} + "@noIngredientsDefined": {}, + "bench": "Banco", + "@bench": { + "description": "Generated entry for translation for server strings" + }, + "goalFiber": "Meta de fibras", + "@goalFiber": {}, + "overallChangeWeight": "Mudança geral", + "@overallChangeWeight": { + "description": "Overall change in weight, added for localization" + }, + "goalTypeMeals": "De refeições", + "@goalTypeMeals": { + "description": "added for localization of Class GoalType's filed meals" + }, + "goalTypeBasic": "Básico", + "@goalTypeBasic": { + "description": "added for localization of Class GoalType's filed basic" + }, + "goalTypeAdvanced": "Avançado", + "@goalTypeAdvanced": { + "description": "added for localization of Class GoalType's filed advanced" + }, + "indicatorRaw": "bruto", + "@indicatorRaw": { + "description": "added for localization of Class Indicator's field text" + }, + "indicatorAvg": "média", + "@indicatorAvg": { + "description": "added for localization of Class Indicator's field text" + }, + "seconds": "Segundos", + "@seconds": { + "description": "Generated entry for translation for server strings" + }, + "textPromptTitle": "Pronto para iniciar?", + "@textPromptTitle": {}, + "textPromptSubheading": "Aperte o botão de ação para começar", + "@textPromptSubheading": {}, + "chest": "Peito", + "@chest": { + "description": "Generated entry for translation for server strings" + }, + "max_reps": "Repetições Máximas", + "@max_reps": { + "description": "Generated entry for translation for server strings" + }, + "pull_up_bar": "Barra de Pull-up", + "@pull_up_bar": { + "description": "Generated entry for translation for server strings" + }, + "quads": "Quadríceps", + "@quads": { + "description": "Generated entry for translation for server strings" + }, + "goalFat": "Meta de gordura", + "@goalFat": {}, + "body_weight": "Peso Corporal", + "@body_weight": { + "description": "Generated entry for translation for server strings" + }, + "kilometers": "Quilômetros", + "@kilometers": { + "description": "Generated entry for translation for server strings" + }, + "kilometers_per_hour": "Quilômetros por hora", + "@kilometers_per_hour": { + "description": "Generated entry for translation for server strings" + }, + "miles_per_hour": "Milhas por hora", + "@miles_per_hour": { + "description": "Generated entry for translation for server strings" + }, + "minutes": "Minutos", + "@minutes": { + "description": "Generated entry for translation for server strings" + }, + "sz_bar": "Barra Curvada", + "@sz_bar": { + "description": "Generated entry for translation for server strings" + }, + "goalProtein": "Meta de proteína", + "@goalProtein": {}, + "goalEnergy": "Meta energética", + "@goalEnergy": {}, + "today": "Hoje", + "@today": {}, + "arms": "Braços", + "@arms": { + "description": "Generated entry for translation for server strings" + }, + "calves": "Panturrilha", + "@calves": { + "description": "Generated entry for translation for server strings" + }, + "triceps": "Tríceps", + "@triceps": { + "description": "Generated entry for translation for server strings" + }, + "goalCarbohydrates": "Meta de carbohidratos", + "@goalCarbohydrates": {}, + "onlyLoggingHelpText": "Marque a caixa se você apenas quiser registrar suas calorias e não quer configurar um plano nutricional detalhado com refeições específicas", + "@onlyLoggingHelpText": {}, + "kcalValue": "{value} kcal", + "@kcalValue": { + "description": "A value in kcal, e.g. 500 kcal", + "type": "text", + "placeholders": { + "value": {} + } + }, + "percentValue": "{value} %", + "@percentValue": { + "description": "A value in percent, e.g. 10 %", + "type": "text", + "placeholders": { + "value": {} + } + }, + "goalMacro": "Macro Objetivos", + "@goalMacro": { + "description": "The goal for macronutrients" + }, + "selectMealToLog": "Selecione uma refeição para registrar no diário", + "@selectMealToLog": {}, + "loggedToday": "Registrado hoje", + "@loggedToday": {}, + "surplus": "excedente", + "@surplus": { + "description": "Caloric surplus (either planned or unplanned)" + }, + "deficit": "déficit", + "@deficit": { + "description": "Caloric deficit (either planned or unplanned)" + }, + "gValue": "{value} g", + "@gValue": { + "description": "A value in grams, e.g. 5 g", + "type": "text", + "placeholders": { + "value": {} + } + }, + "abs": "Abdômen", + "@abs": { + "description": "Generated entry for translation for server strings" + }, + "back": "Costas", + "@back": { + "description": "Generated entry for translation for server strings" + }, + "barbell": "Barra", + "@barbell": { + "description": "Generated entry for translation for server strings" + }, + "cardio": "Cardio", + "@cardio": { + "description": "Generated entry for translation for server strings" + }, + "dumbbell": "Haltere", + "@dumbbell": { + "description": "Generated entry for translation for server strings" + }, + "glutes": "Glúteos", + "@glutes": { + "description": "Generated entry for translation for server strings" + }, + "gym_mat": "Tapete", + "@gym_mat": { + "description": "Generated entry for translation for server strings" + }, + "hamstrings": "Tendões", + "@hamstrings": { + "description": "Generated entry for translation for server strings" + }, + "kettlebell": "Kettlebell", + "@kettlebell": { + "description": "Generated entry for translation for server strings" + }, + "lats": "Dorsal", + "@lats": { + "description": "Generated entry for translation for server strings" + }, + "legs": "Pernas", + "@legs": { + "description": "Generated entry for translation for server strings" + }, + "lower_back": "Lombar", + "@lower_back": { + "description": "Generated entry for translation for server strings" + }, + "plates": "Anilhas", + "@plates": { + "description": "Generated entry for translation for server strings" + }, + "repetitions": "Repetições", + "@repetitions": { + "description": "Generated entry for translation for server strings" + }, + "shoulders": "Ombros", + "@shoulders": { + "description": "Generated entry for translation for server strings" + }, + "until_failure": "Até a falha", + "@until_failure": { + "description": "Generated entry for translation for server strings" + }, + "kg": "kg", + "@kg": { + "description": "Generated entry for translation for server strings" + }, + "none__bodyweight_exercise_": "nenhum (exercício com peso corporal)", + "@none__bodyweight_exercise_": { + "description": "Generated entry for translation for server strings" + }, + "lb": "lb", + "@lb": { + "description": "Generated entry for translation for server strings" + }, + "chartAllTimeTitle": "{name} total", + "@chartAllTimeTitle": { + "description": "All-time chart of 'name' (e.g. 'weight', 'body fat' etc.)", + "type": "text", + "placeholders": { + "name": {} + } + }, + "chart30DaysTitle": "{name} últimos 30 dias", + "@chart30DaysTitle": { + "description": "last 30 days chart of 'name' (e.g. 'weight', 'body fat' etc.)", + "type": "text", + "placeholders": { + "name": {} + } + }, + "chartDuringPlanTitle": "{chartName} durante plano nutricional {planName}", + "@chartDuringPlanTitle": { + "description": "chart of 'chartName' (e.g. 'weight', 'body fat' etc.) logged during plan", + "type": "text", + "placeholders": { + "chartName": {}, + "planName": {} + } + }, + "settingsIngredientCacheDescription": "Cache de Ingredientes", + "@settingsIngredientCacheDescription": {}, + "cacheWarning": "Devido ao cache pode demorar algum tempo até que mudanças estejam visíveis na aplicação.", + "@cacheWarning": {}, + "swiss_ball": "Bola Suíça", + "@swiss_ball": { + "description": "Generated entry for translation for server strings" + }, + "log": "Registro", + "@log": { + "description": "Log a specific meal (imperative form)" + }, + "done": "Feito", + "@done": {}, + "incline_bench": "Banco inclinado", + "@incline_bench": { + "description": "Generated entry for translation for server strings" + }, + "miles": "Milhas", + "@miles": { + "description": "Generated entry for translation for server strings" + }, + "biceps": "Bíceps", + "@biceps": { + "description": "Generated entry for translation for server strings" + } } diff --git a/lib/main.dart b/lib/main.dart index 0f3968f1..eeccefa1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -137,48 +137,50 @@ class MyApp extends StatelessWidget { ), ], child: Consumer( - builder: (ctx, auth, _) => MaterialApp( - title: 'wger', - theme: wgerLightTheme, - darkTheme: wgerDarkTheme, - highContrastTheme: wgerLightThemeHc, - highContrastDarkTheme: wgerDarkThemeHc, - themeMode: ThemeMode.system, - home: auth.isAuth - ? const HomeTabsScreen() - : FutureBuilder( - future: auth.tryAutoLogin(), - builder: (ctx, authResultSnapshot) => - authResultSnapshot.connectionState == ConnectionState.waiting - ? const SplashScreen() - : const AuthScreen(), - ), - routes: { - DashboardScreen.routeName: (ctx) => const DashboardScreen(), - FormScreen.routeName: (ctx) => const FormScreen(), - GalleryScreen.routeName: (ctx) => const GalleryScreen(), - GymModeScreen.routeName: (ctx) => const GymModeScreen(), - HomeTabsScreen.routeName: (ctx) => const HomeTabsScreen(), - MeasurementCategoriesScreen.routeName: (ctx) => const MeasurementCategoriesScreen(), - MeasurementEntriesScreen.routeName: (ctx) => const MeasurementEntriesScreen(), - NutritionalPlansScreen.routeName: (ctx) => const NutritionalPlansScreen(), - NutritionalDiaryScreen.routeName: (ctx) => const NutritionalDiaryScreen(), - NutritionalPlanScreen.routeName: (ctx) => const NutritionalPlanScreen(), - LogMealsScreen.routeName: (ctx) => const LogMealsScreen(), - LogMealScreen.routeName: (ctx) => const LogMealScreen(), - WeightScreen.routeName: (ctx) => const WeightScreen(), - RoutineScreen.routeName: (ctx) => const RoutineScreen(), - RoutineEditScreen.routeName: (ctx) => const RoutineEditScreen(), - WorkoutLogsScreen.routeName: (ctx) => const WorkoutLogsScreen(), - RoutineListScreen.routeName: (ctx) => const RoutineListScreen(), - ExercisesScreen.routeName: (ctx) => const ExercisesScreen(), - ExerciseDetailScreen.routeName: (ctx) => const ExerciseDetailScreen(), - AddExerciseScreen.routeName: (ctx) => const AddExerciseScreen(), - AboutPage.routeName: (ctx) => const AboutPage(), - SettingsPage.routeName: (ctx) => const SettingsPage(), - }, - localizationsDelegates: AppLocalizations.localizationsDelegates, - supportedLocales: AppLocalizations.supportedLocales, + builder: (ctx, auth, _) => Consumer( + builder: (ctx, user, _) => MaterialApp( + title: 'wger', + theme: wgerLightTheme, + darkTheme: wgerDarkTheme, + highContrastTheme: wgerLightThemeHc, + highContrastDarkTheme: wgerDarkThemeHc, + themeMode: user.themeMode, + home: auth.isAuth + ? const HomeTabsScreen() + : FutureBuilder( + future: auth.tryAutoLogin(), + builder: (ctx, authResultSnapshot) => + authResultSnapshot.connectionState == ConnectionState.waiting + ? const SplashScreen() + : const AuthScreen(), + ), + routes: { + DashboardScreen.routeName: (ctx) => const DashboardScreen(), + FormScreen.routeName: (ctx) => const FormScreen(), + GalleryScreen.routeName: (ctx) => const GalleryScreen(), + GymModeScreen.routeName: (ctx) => const GymModeScreen(), + HomeTabsScreen.routeName: (ctx) => const HomeTabsScreen(), + MeasurementCategoriesScreen.routeName: (ctx) => const MeasurementCategoriesScreen(), + MeasurementEntriesScreen.routeName: (ctx) => const MeasurementEntriesScreen(), + NutritionalPlansScreen.routeName: (ctx) => const NutritionalPlansScreen(), + NutritionalDiaryScreen.routeName: (ctx) => const NutritionalDiaryScreen(), + NutritionalPlanScreen.routeName: (ctx) => const NutritionalPlanScreen(), + LogMealsScreen.routeName: (ctx) => const LogMealsScreen(), + LogMealScreen.routeName: (ctx) => const LogMealScreen(), + WeightScreen.routeName: (ctx) => const WeightScreen(), + RoutineScreen.routeName: (ctx) => const RoutineScreen(), + RoutineEditScreen.routeName: (ctx) => const RoutineEditScreen(), + WorkoutLogsScreen.routeName: (ctx) => const WorkoutLogsScreen(), + RoutineListScreen.routeName: (ctx) => const RoutineListScreen(), + ExercisesScreen.routeName: (ctx) => const ExercisesScreen(), + ExerciseDetailScreen.routeName: (ctx) => const ExerciseDetailScreen(), + AddExerciseScreen.routeName: (ctx) => const AddExerciseScreen(), + AboutPage.routeName: (ctx) => const AboutPage(), + SettingsPage.routeName: (ctx) => const SettingsPage(), + }, + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + ), ), ), ); diff --git a/lib/models/exercises/exercise.dart b/lib/models/exercises/exercise.dart index 69bd82e4..e642dd15 100644 --- a/lib/models/exercises/exercise.dart +++ b/lib/models/exercises/exercise.dart @@ -15,6 +15,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ +import 'dart:developer'; import 'package:collection/collection.dart'; import 'package:equatable/equatable.dart'; @@ -161,7 +162,15 @@ class Exercise extends Equatable { equipment = baseData.equipment; category = baseData.category; translations = baseData.translations.map((e) { - e.language = languages.firstWhere((l) => l.id == e.languageId); + e.language = languages.firstWhere( + (l) => l.id == e.languageId, + + // workaround for https://github.com/wger-project/flutter/issues/722 + orElse: () { + log('Could not find language for translation ${e.languageId}'); + return Language(id: e.languageId, shortName: 'unknown', fullName: 'unknown'); + }, + ); return e; }).toList(); videos = baseData.videos; diff --git a/lib/providers/auth.dart b/lib/providers/auth.dart index 1bfbdca1..6e914e81 100644 --- a/lib/providers/auth.dart +++ b/lib/providers/auth.dart @@ -22,6 +22,7 @@ import 'dart:developer'; import 'dart:io'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:http/http.dart' as http; import 'package:package_info_plus/package_info_plus.dart'; diff --git a/lib/providers/exercises.dart b/lib/providers/exercises.dart index 8f43ec34..2956890f 100644 --- a/lib/providers/exercises.dart +++ b/lib/providers/exercises.dart @@ -298,20 +298,16 @@ class ExercisesProvider with ChangeNotifier { Exercise exercise; // TODO: this should be a .getSingleOrNull()!!! However, for some reason there - // are duplicates in the db. Perhaps a race condition so that two + // can be duplicates in the db. Perhaps a race condition so that two // entries are written at the same time or something? final exerciseResult = await (database.select(database.exercises)..where((e) => e.id.equals(exerciseId))).get(); ExerciseTable? exerciseDb; - if (exerciseResult.length > 0) { + if (exerciseResult.isNotEmpty) { exerciseDb = exerciseResult.first; - } else { - exerciseDb = null; } - log(exerciseResult.toString()); - // Exercise is already known locally if (exerciseDb != null) { final nextFetch = exerciseDb.lastFetched.add(const Duration(hours: EXERCISE_FETCH_HOURS)); diff --git a/lib/providers/user.dart b/lib/providers/user.dart index cb547c49..8e6c4456 100644 --- a/lib/providers/user.dart +++ b/lib/providers/user.dart @@ -19,12 +19,20 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:wger/helpers/consts.dart'; import 'package:wger/models/user/profile.dart'; import 'package:wger/providers/base_provider.dart'; class UserProvider with ChangeNotifier { + ThemeMode themeMode = ThemeMode.system; final WgerBaseProvider baseProvider; - UserProvider(this.baseProvider); + late SharedPreferencesAsync prefs; + + UserProvider(this.baseProvider, {SharedPreferencesAsync? prefs}) { + this.prefs = prefs ?? SharedPreferencesAsync(); + _loadThemeMode(); + } static const PROFILE_URL = 'userprofile'; static const VERIFY_EMAIL = 'verify-email'; @@ -36,6 +44,37 @@ class UserProvider with ChangeNotifier { profile = null; } + // Load theme mode from SharedPreferences + Future _loadThemeMode() async { + final prefsDarkMode = await prefs.getBool(PREFS_USER_DARK_THEME); + + print(prefsDarkMode); + + if (prefsDarkMode == null) { + themeMode = ThemeMode.system; + } else { + themeMode = prefsDarkMode ? ThemeMode.dark : ThemeMode.light; + } + + print(themeMode); + + notifyListeners(); + } + + // Change mode on switch button click + void setThemeMode(ThemeMode mode) async { + themeMode = mode; + + // Save to SharedPreferences + if (themeMode == ThemeMode.system) { + await prefs.remove(PREFS_USER_DARK_THEME); + } else { + await prefs.setBool(PREFS_USER_DARK_THEME, themeMode == ThemeMode.dark); + } + + notifyListeners(); + } + /// Fetch the current user's profile Future fetchAndSetProfile() async { final userData = await baseProvider.fetch(baseProvider.makeUrl(PROFILE_URL)); diff --git a/lib/widgets/core/settings.dart b/lib/widgets/core/settings.dart index 9dde8f8b..62b48be3 100644 --- a/lib/widgets/core/settings.dart +++ b/lib/widgets/core/settings.dart @@ -22,6 +22,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:provider/provider.dart'; import 'package:wger/providers/exercises.dart'; import 'package:wger/providers/nutrition.dart'; +import 'package:wger/providers/user.dart'; class SettingsPage extends StatelessWidget { static String routeName = '/SettingsPage'; @@ -33,11 +34,11 @@ class SettingsPage extends StatelessWidget { final i18n = AppLocalizations.of(context); final exerciseProvider = Provider.of(context, listen: false); final nutritionProvider = Provider.of(context, listen: false); + final userProvider = Provider.of(context); + final i18n = AppLocalizations.of(context); return Scaffold( - appBar: AppBar( - title: Text(AppLocalizations.of(context).settingsTitle), - ), + appBar: AppBar(title: Text(i18n.settingsTitle)), body: ListView( children: [ ListTile( @@ -64,7 +65,7 @@ class SettingsPage extends StatelessWidget { ), ), ListTile( - title: Text(AppLocalizations.of(context).settingsIngredientCacheDescription), + title: Text(i18n.settingsIngredientCacheDescription), trailing: IconButton( key: const ValueKey('cacheIconIngredients'), icon: const Icon(Icons.delete), @@ -73,7 +74,7 @@ class SettingsPage extends StatelessWidget { if (context.mounted) { final snackBar = SnackBar( - content: Text(AppLocalizations.of(context).settingsCacheDeletedSnackbar), + content: Text(i18n.settingsCacheDeletedSnackbar), ); ScaffoldMessenger.of(context).showSnackBar(snackBar); @@ -81,6 +82,35 @@ class SettingsPage extends StatelessWidget { }, ), ), + ListTile( + title: Text(i18n.themeMode), + trailing: DropdownButton( + key: const ValueKey('themeModeDropdown'), + value: userProvider.themeMode, + onChanged: (ThemeMode? newValue) { + if (newValue != null) { + userProvider.setThemeMode(newValue); + } + }, + items: ThemeMode.values.map>((ThemeMode value) { + final label = (() { + switch (value) { + case ThemeMode.system: + return i18n.systemMode; + case ThemeMode.light: + return i18n.lightMode; + case ThemeMode.dark: + return i18n.darkMode; + } + })(); + + return DropdownMenuItem( + value: value, + child: Text(label), + ); + }).toList(), + ), + ), ], ), ); diff --git a/pubspec.lock b/pubspec.lock index 26471dbf..08709222 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" + sha256: "03f6da266a27a4538a69295ec142cb5717d7d4e5727b84658b63e1e1509bac9c" url: "https://pub.dev" source: hosted - version: "76.0.0" + version: "79.0.0" _macros: dependency: transitive description: dart @@ -18,18 +18,18 @@ packages: dependency: transitive description: name: analyzer - sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" + sha256: c9040fc56483c22a5e04a9f6a251313118b1a3c42423770623128fa484115643 url: "https://pub.dev" source: hosted - version: "6.11.0" + version: "7.2.0" archive: dependency: transitive description: name: archive - sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d + sha256: "6199c74e3db4fbfbd04f66d739e72fe11c8a8957d5f219f1f4482dbde6420b5a" url: "https://pub.dev" source: hosted - version: "3.6.1" + version: "4.0.2" args: dependency: transitive description: @@ -58,34 +58,34 @@ packages: dependency: transitive description: name: build - sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" build_config: dependency: transitive description: name: build_config - sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" build_daemon: dependency: transitive description: name: build_daemon - sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" + sha256: "294a2edaf4814a378725bfe6358210196f5ea37af89ecd81bfa32960113d4948" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.0.3" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + sha256: "99d3980049739a985cf9b21f30881f46db3ebc62c5b8d5e60e27440876b1ba1e" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.3" build_runner: dependency: "direct dev" description: @@ -114,42 +114,42 @@ packages: dependency: transitive description: name: built_value - sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb + sha256: "28a712df2576b63c6c005c465989a348604960c0958d28be5303ba9baa841ac2" url: "https://pub.dev" source: hosted - version: "8.9.2" + version: "8.9.3" camera: dependency: transitive description: name: camera - sha256: "26ff41045772153f222ffffecba711a206f670f5834d40ebf5eed3811692f167" + sha256: "413d2b34fe28496c35c69ede5b232fb9dd5ca2c3a4cb606b14efc1c7546cc8cb" url: "https://pub.dev" source: hosted - version: "0.11.0+2" + version: "0.11.1" camera_android_camerax: dependency: transitive description: name: camera_android_camerax - sha256: e3627fdc2132d89212b8a8676679f5b07008c7e3d8ae00cea775c3397f9e742b + sha256: "7cc6adf1868bdcf4e63a56b24b41692dfbad2bec1cdceea451c77798f6a605c3" url: "https://pub.dev" source: hosted - version: "0.6.10" + version: "0.6.13" camera_avfoundation: dependency: transitive description: name: camera_avfoundation - sha256: "2e4c568f70e406ccb87376bc06b53d2f5bebaab71e2fbcc1a950e31449381bcf" + sha256: "3f81ee3e88a79b0b010f0233d42625926299551b05d5dc995267a0b35bc33247" url: "https://pub.dev" source: hosted - version: "0.9.17+5" + version: "0.9.18" camera_platform_interface: dependency: transitive description: name: camera_platform_interface - sha256: b3ede1f171532e0d83111fe0980b46d17f1aa9788a07a2fbed07366bbdbb9061 + sha256: "953e7baed3a7c8fae92f7200afeb2be503ff1a17c3b4e4ed7b76f008c2810a31" url: "https://pub.dev" source: hosted - version: "2.8.0" + version: "2.9.0" camera_web: dependency: transitive description: @@ -170,10 +170,10 @@ packages: dependency: transitive description: name: change - sha256: "65db7f966dc7e786687f49900a94c5f08b0eb9ca8c4a3e7eed3a55e980b455e2" + sha256: "3bda1ce98526b21927cdea05688563c7065fd7e66d1abbe176c354fef3b2057b" url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.7.5" characters: dependency: transitive description: @@ -186,10 +186,10 @@ packages: dependency: transitive description: name: charcode - sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 + sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.4.0" checked_yaml: dependency: transitive description: @@ -282,26 +282,26 @@ packages: dependency: transitive description: name: dart_style - sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" + sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac" url: "https://pub.dev" source: hosted - version: "2.3.7" + version: "3.0.1" drift: dependency: "direct main" description: name: drift - sha256: af3941e4d544727b2eb80590eb64e9cb8d77cd68c7690265502ea6a2427aa621 + sha256: "76f23535e19a9f2be92f954e74d8802e96f526e5195d7408c1a20f6659043941" url: "https://pub.dev" source: hosted - version: "2.23.1" + version: "2.24.0" drift_dev: dependency: "direct dev" description: name: drift_dev - sha256: fa98fdbb7303a1b5b2dc110cb516eda2253a5d291680f8cbc72b1af24099f7f9 + sha256: d1d90b0d55b22de412b77186f3bf3179a4b7e2acc4c8fb3a7aaf28a01abc194b url: "https://pub.dev" source: hosted - version: "2.23.1" + version: "2.24.0" equatable: dependency: "direct main" description: @@ -338,10 +338,10 @@ packages: dependency: transitive description: name: file_selector_linux - sha256: b2b91daf8a68ecfa4a01b778a6f52edef9b14ecd506e771488ea0f2e0784198b + sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" url: "https://pub.dev" source: hosted - version: "0.9.3+1" + version: "0.9.3+2" file_selector_macos: dependency: transitive description: @@ -386,10 +386,10 @@ packages: dependency: "direct main" description: name: flex_color_scheme - sha256: "90f4fe67b9561ae8a4af117df65a8ce9988624025667c54e6d304e65cff77d52" + sha256: "09bea5d776f694c5a67f2229f2aa500cc7cce369322dc6500ab01cf9ad1b4e1a" url: "https://pub.dev" source: hosted - version: "8.0.2" + version: "8.1.0" flex_seed_scheme: dependency: "direct main" description: @@ -497,18 +497,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "9b78450b89f059e96c9ebb355fa6b3df1d6b330436e0b885fb49594c41721398" + sha256: "615a505aef59b151b46bbeef55b36ce2b6ed299d160c51d84281946f0aa0ce0e" url: "https://pub.dev" source: hosted - version: "2.0.23" - flutter_riverpod: - dependency: "direct main" - description: - name: flutter_riverpod - sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1" - url: "https://pub.dev" - source: hosted - version: "2.6.1" + version: "2.0.24" flutter_staggered_grid_view: dependency: "direct main" description: @@ -521,10 +513,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: "54900a1a1243f3c4a5506d853a2b5c2dbc38d5f27e52a52618a8054401431123" + sha256: c200fd79c918a40c5cd50ea0877fa13f81bdaf6f0a5d3dbcc2a13e3285d6aa1b url: "https://pub.dev" source: hosted - version: "2.0.16" + version: "2.0.17" flutter_svg_icons: dependency: "direct main" description: @@ -571,10 +563,10 @@ packages: dependency: "direct dev" description: name: freezed - sha256: "44c19278dd9d89292cf46e97dc0c1e52ce03275f40a97c5a348e802a924bf40e" + sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c" url: "https://pub.dev" source: hosted - version: "2.5.7" + version: "2.5.8" freezed_annotation: dependency: "direct main" description: @@ -608,10 +600,10 @@ packages: dependency: transitive description: name: glob - sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" golden_toolkit: dependency: "direct dev" description: @@ -640,34 +632,34 @@ packages: dependency: "direct main" description: name: http - sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 + sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.3.0" http_multi_server: dependency: transitive description: name: http_multi_server - sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" http_parser: dependency: transitive description: name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.1.2" image: dependency: transitive description: name: image - sha256: f31d52537dc417fdcde36088fdf11d191026fd5e4fae742491ebd40e5a8bea7d + sha256: "8346ad4b5173924b5ddddab782fc7d8a6300178c8b1dc427775405a01701c4a6" url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "4.5.2" image_picker: dependency: "direct main" description: @@ -680,10 +672,10 @@ packages: dependency: transitive description: name: image_picker_android - sha256: "8faba09ba361d4b246dc0a17cb4289b3324c2b9f6db7b3d457ee69106a86bd32" + sha256: b62d34a506e12bb965e824b6db4fbf709ee4589cf5d3e99b45ab2287b008ee0c url: "https://pub.dev" source: hosted - version: "0.8.12+17" + version: "0.8.12+20" image_picker_for_web: dependency: transitive description: @@ -696,10 +688,10 @@ packages: dependency: transitive description: name: image_picker_ios - sha256: "4f0568120c6fcc0aaa04511cb9f9f4d29fc3d0139884b1d06be88dcec7641d6b" + sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100" url: "https://pub.dev" source: hosted - version: "0.8.12+1" + version: "0.8.12+2" image_picker_linux: dependency: transitive description: @@ -712,18 +704,18 @@ packages: dependency: transitive description: name: image_picker_macos - sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62" + sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1" url: "https://pub.dev" source: hosted - version: "0.2.1+1" + version: "0.2.1+2" image_picker_platform_interface: dependency: transitive description: name: image_picker_platform_interface - sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80" + sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.10.1" image_picker_windows: dependency: transitive description: @@ -749,10 +741,10 @@ packages: dependency: transitive description: name: io - sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" js: dependency: transitive description: @@ -773,10 +765,10 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: c2fcb3920cf2b6ae6845954186420fca40bc0a8abcc84903b7801f17d7050d7c + sha256: b0a98230538fe5d0b60a22fb6bf1b6cb03471b53e3324ff6069c591679dd59c9 url: "https://pub.dev" source: hosted - version: "6.9.0" + version: "6.9.3" leak_tracker: dependency: transitive description: @@ -805,10 +797,10 @@ packages: dependency: transitive description: name: lints - sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413" + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "5.1.1" list_counter: dependency: transitive description: @@ -837,10 +829,10 @@ packages: dependency: transitive description: name: markdown - sha256: ef2a1298144e3f985cc736b22e0ccdaf188b5b3970648f2d9dc13efd1d9df051 + sha256: "935e23e1ff3bc02d390bad4d4be001208ee92cc217cb5b5a6c19bc14aaa318c1" url: "https://pub.dev" source: hosted - version: "7.2.2" + version: "7.3.0" marker: dependency: transitive description: @@ -885,10 +877,10 @@ packages: dependency: "direct dev" description: name: mockito - sha256: "6841eed20a7befac0ce07df8116c8b8233ed1f4486a7647c7fc5a02ae6163917" + sha256: f99d8d072e249f719a5531735d146d8cf04c580d93920b04de75bef6dfb2daf6 url: "https://pub.dev" source: hosted - version: "5.4.4" + version: "5.4.5" multi_select_flutter: dependency: "direct main" description: @@ -917,26 +909,26 @@ packages: dependency: transitive description: name: package_config - sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" package_info_plus: dependency: "direct main" description: name: package_info_plus - sha256: da8d9ac8c4b1df253d1a328b7bf01ae77ef132833479ab40763334db13b91cce + sha256: "739e0a5c3c4055152520fa321d0645ee98e932718b4c8efeeb51451968fe0790" url: "https://pub.dev" source: hosted - version: "8.1.1" + version: "8.1.3" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: ac1f4a4847f1ade8e6a87d1f39f5d7c67490738642e2542f559ec38c37489a66 + sha256: a5ef9986efc7bf772f2696183a3992615baa76c1ffb1189318dd8803778fb05b url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" path: dependency: "direct main" description: @@ -965,18 +957,18 @@ packages: dependency: transitive description: name: path_provider_android - sha256: c464428172cb986b758c6d1724c603097febb8fb855aa265aeecc9280c294d4a + sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" url: "https://pub.dev" source: hosted - version: "2.2.12" + version: "2.2.15" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" path_provider_linux: dependency: transitive description: @@ -1065,6 +1057,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" + posix: + dependency: transitive + description: + name: posix + sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a + url: "https://pub.dev" + source: hosted + version: "6.0.1" process: dependency: transitive description: @@ -1085,18 +1085,18 @@ packages: dependency: transitive description: name: pub_semver - sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" pubspec_parse: dependency: transitive description: name: pubspec_parse - sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.5.0" recase: dependency: transitive description: @@ -1129,14 +1129,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.4.15" - riverpod: - dependency: transitive - description: - name: riverpod - sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959" - url: "https://pub.dev" - source: hosted - version: "2.6.1" shared_preferences: dependency: "direct main" description: @@ -1149,18 +1141,18 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: "02a7d8a9ef346c9af715811b01fbd8e27845ad2c41148eefd31321471b41863d" + sha256: "138b7bbbc7f59c56236e426c37afb8f78cbc57b094ac64c440e0bb90e380a4f5" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.2" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d" + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" url: "https://pub.dev" source: hosted - version: "2.5.3" + version: "2.5.4" shared_preferences_linux: dependency: transitive description: @@ -1170,7 +1162,7 @@ packages: source: hosted version: "2.4.1" shared_preferences_platform_interface: - dependency: transitive + dependency: "direct dev" description: name: shared_preferences_platform_interface sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" @@ -1197,18 +1189,18 @@ packages: dependency: transitive description: name: shelf - sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.4.2" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" + sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.0.1" simple_gesture_detector: dependency: transitive description: @@ -1226,18 +1218,18 @@ packages: dependency: transitive description: name: source_gen - sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "2.0.0" source_helper: dependency: transitive description: name: source_helper - sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd" + sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" url: "https://pub.dev" source: hosted - version: "1.3.4" + version: "1.3.5" source_span: dependency: transitive description: @@ -1250,26 +1242,26 @@ packages: dependency: transitive description: name: sqlite3 - sha256: bb174b3ec2527f9c5f680f73a89af8149dd99782fbb56ea88ad0807c5638f2ed + sha256: "35d3726fe18ab1463403a5cc8d97dbc81f2a0b08082e8173851363fcc97b6627" url: "https://pub.dev" source: hosted - version: "2.4.7" + version: "2.7.2" sqlite3_flutter_libs: dependency: "direct main" description: name: sqlite3_flutter_libs - sha256: "73016db8419f019e807b7a5e5fbf2a7bd45c165fed403b8e7681230f3a102785" + sha256: "50a7e3f294c741d3142eed0ff228e38498334e11e0ccb9d73e0496e005949e44" url: "https://pub.dev" source: hosted - version: "0.5.28" + version: "0.5.29" sqlparser: dependency: transitive description: name: sqlparser - sha256: "4cad4b2c5f63dc9ea1a8dcffb58cf762322bea5dd8836870164a65e913bdae41" + sha256: "27dd0a9f0c02e22ac0eb42a23df9ea079ce69b52bb4a3b478d64e0ef34a263ee" url: "https://pub.dev" source: hosted - version: "0.40.0" + version: "0.41.0" stack_trace: dependency: transitive description: @@ -1278,14 +1270,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.12.0" - state_notifier: - dependency: transitive - description: - name: state_notifier - sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb - url: "https://pub.dev" - source: hosted - version: "1.0.0" stream_channel: dependency: transitive description: @@ -1298,10 +1282,10 @@ packages: dependency: transitive description: name: stream_transform - sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" string_scanner: dependency: transitive description: @@ -1322,10 +1306,10 @@ packages: dependency: "direct main" description: name: table_calendar - sha256: "4ca32b2fc919452c9974abd4c6ea611a63e33b9e4f0b8c38dba3ac1f4a6549d1" + sha256: "0c0c6219878b363a2d5f40c7afb159d845f253d061dc3c822aa0d5fe0f721982" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.2.0" term_glyph: dependency: transitive description: @@ -1346,10 +1330,10 @@ packages: dependency: transitive description: name: timing - sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.2" typed_data: dependency: transitive description: @@ -1378,10 +1362,10 @@ packages: dependency: transitive description: name: url_launcher_ios - sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e + sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" url: "https://pub.dev" source: hosted - version: "6.3.1" + version: "6.3.2" url_launcher_linux: dependency: transitive description: @@ -1394,10 +1378,10 @@ packages: dependency: transitive description: name: url_launcher_macos - sha256: "769549c999acdb42b8bcfa7c43d72bf79a382ca7441ab18a808e101149daf672" + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" url_launcher_platform_interface: dependency: transitive description: @@ -1410,42 +1394,42 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" + sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9" url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.4.0" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "44cf3aabcedde30f2dba119a9dea3b0f2672fbe6fa96e85536251d678216b3c4" + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.1.4" vector_graphics: dependency: transitive description: name: vector_graphics - sha256: "773c9522d66d523e1c7b25dfb95cc91c26a1e17b107039cfe147285e92de7878" + sha256: "27d5fefe86fb9aace4a9f8375b56b3c292b64d8c04510df230f849850d912cb7" url: "https://pub.dev" source: hosted - version: "1.1.14" + version: "1.1.15" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: "2430b973a4ca3c4dbc9999b62b8c719a160100dcbae5c819bae0cacce32c9cdb" + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" url: "https://pub.dev" source: hosted - version: "1.1.12" + version: "1.1.13" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: ab9ff38fc771e9ee1139320adbe3d18a60327370c218c60752068ebee4b49ab1 + sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad" url: "https://pub.dev" source: hosted - version: "1.1.15" + version: "1.1.16" vector_math: dependency: transitive description: @@ -1482,18 +1466,18 @@ packages: dependency: transitive description: name: video_player_android - sha256: "391e092ba4abe2f93b3e625bd6b6a6ec7d7414279462c1c0ee42b5ab8d0a0898" + sha256: "7018dbcb395e2bca0b9a898e73989e67c0c4a5db269528e1b036ca38bcca0d0b" url: "https://pub.dev" source: hosted - version: "2.7.16" + version: "2.7.17" video_player_avfoundation: dependency: transitive description: name: video_player_avfoundation - sha256: cd5ab8a8bc0eab65ab0cea40304097edc46da574c8c1ecdee96f28cd8ef3792f + sha256: "8a4e73a3faf2b13512978a43cf1cdda66feeeb900a0527f1fbfd7b19cf3458d3" url: "https://pub.dev" source: hosted - version: "2.6.2" + version: "2.6.7" video_player_platform_interface: dependency: transitive description: @@ -1522,10 +1506,10 @@ packages: dependency: transitive description: name: watcher - sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" web: dependency: transitive description: @@ -1546,10 +1530,10 @@ packages: dependency: transitive description: name: web_socket_channel - sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" + sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" webdriver: dependency: transitive description: @@ -1562,10 +1546,10 @@ packages: dependency: transitive description: name: win32 - sha256: "84ba388638ed7a8cb3445a320c8273136ab2631cd5f2c57888335504ddab1bc2" + sha256: "154360849a56b7b67331c21f09a386562d88903f90a1099c5987afc1912e1f29" url: "https://pub.dev" source: hosted - version: "5.8.0" + version: "5.10.0" xdg_directories: dependency: transitive description: @@ -1586,10 +1570,10 @@ packages: dependency: transitive description: name: yaml - sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.3" sdks: dart: ">=3.6.0 <4.0.0" flutter: ">=3.27.0" diff --git a/pubspec.yaml b/pubspec.yaml index 7eaa6c39..a1ecac09 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,13 +39,13 @@ dependencies: drift: ^2.16.0 equatable: ^2.0.7 fl_chart: ^0.69.2 - flex_color_scheme: ^8.0.2 + flex_color_scheme: ^8.1.0 flex_seed_scheme: ^3.5.0 flutter_barcode_scanner: ^2.0.0 flutter_calendar_carousel: ^2.4.4 flutter_html: ^3.0.0-beta.2 flutter_staggered_grid_view: ^0.7.0 - flutter_svg: ^2.0.16 + flutter_svg: ^2.0.17 flutter_svg_icons: ^0.0.1 flutter_typeahead: ^5.2.0 flutter_zxing: ^1.8.2 @@ -57,13 +57,13 @@ dependencies: intl: ^0.19.0 json_annotation: ^4.8.1 multi_select_flutter: ^4.1.3 - package_info_plus: ^8.1.1 + package_info_plus: ^8.1.3 path: ^1.9.0 path_provider: ^2.1.5 provider: ^6.1.2 rive: ^0.13.20 shared_preferences: ^2.3.5 - sqlite3_flutter_libs: ^0.5.28 + sqlite3_flutter_libs: ^0.5.29 table_calendar: ^3.0.8 url_launcher: ^6.3.1 version: ^3.0.2 @@ -87,6 +87,7 @@ dev_dependencies: json_serializable: ^6.9.0 mockito: ^5.4.4 network_image_mock: ^2.1.1 + shared_preferences_platform_interface: ^2.0.0 # Script to read out unused translations #translations_cleaner: ^0.0.5 diff --git a/test/core/settings_test.dart b/test/core/settings_test.dart index 72806b48..8a1d6ff8 100644 --- a/test/core/settings_test.dart +++ b/test/core/settings_test.dart @@ -22,22 +22,39 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:wger/helpers/consts.dart'; +import 'package:wger/providers/base_provider.dart'; import 'package:wger/providers/exercises.dart'; import 'package:wger/providers/nutrition.dart'; +import 'package:wger/providers/user.dart'; import 'package:wger/widgets/core/settings.dart'; import 'settings_test.mocks.dart'; -@GenerateMocks([ExercisesProvider, NutritionPlansProvider]) +@GenerateMocks([ + ExercisesProvider, + NutritionPlansProvider, + UserProvider, + WgerBaseProvider, + SharedPreferencesAsync, +]) void main() { final mockExerciseProvider = MockExercisesProvider(); final mockNutritionProvider = MockNutritionPlansProvider(); + final mockSharedPreferences = MockSharedPreferencesAsync(); + final mockUserProvider = MockUserProvider(); + + setUp(() { + when(mockUserProvider.themeMode).thenReturn(ThemeMode.system); + }); Widget createSettingsScreen({locale = 'en'}) { return MultiProvider( providers: [ ChangeNotifierProvider(create: (context) => mockNutritionProvider), ChangeNotifierProvider(create: (context) => mockExerciseProvider), + ChangeNotifierProvider(create: (context) => mockUserProvider), ], child: MaterialApp( locale: Locale(locale), @@ -65,4 +82,35 @@ void main() { verify(mockNutritionProvider.clearIngredientCache()); }); }); + + group('Theme settings', () { + test('Default theme is system', () async { + when(mockSharedPreferences.getBool(PREFS_USER_DARK_THEME)).thenAnswer((_) async => null); + final userProvider = await UserProvider(MockWgerBaseProvider(), prefs: mockSharedPreferences); + expect(userProvider.themeMode, ThemeMode.system); + }); + + test('Loads light theme', () async { + when(mockSharedPreferences.getBool(PREFS_USER_DARK_THEME)).thenAnswer((_) async => false); + final userProvider = await UserProvider(MockWgerBaseProvider(), prefs: mockSharedPreferences); + expect(userProvider.themeMode, ThemeMode.light); + }); + + test('Saves theme to prefs', () { + when(mockSharedPreferences.getBool(any)).thenAnswer((_) async => null); + final userProvider = UserProvider(MockWgerBaseProvider(), prefs: mockSharedPreferences); + userProvider.setThemeMode(ThemeMode.dark); + verify(mockSharedPreferences.setBool(PREFS_USER_DARK_THEME, true)).called(1); + expect(userProvider.themeMode, ThemeMode.dark); + }); + + testWidgets('Test changing the theme mode in preferences', (WidgetTester tester) async { + await tester.pumpWidget(createSettingsScreen()); + await tester.tap(find.byKey(const ValueKey('themeModeDropdown'))); + await tester.pumpAndSettle(); + await tester.tap(find.text('Always light mode')); + + verify(mockUserProvider.setThemeMode(ThemeMode.light)).called(1); + }); + }); } diff --git a/test/user/provider_test.dart b/test/user/provider_test.dart index 6d13688c..0aec436d 100644 --- a/test/user/provider_test.dart +++ b/test/user/provider_test.dart @@ -21,6 +21,8 @@ import 'dart:convert'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; +import 'package:shared_preferences_platform_interface/in_memory_shared_preferences_async.dart'; +import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart'; import 'package:wger/providers/base_provider.dart'; import 'package:wger/providers/user.dart'; @@ -29,6 +31,7 @@ import 'provider_test.mocks.dart'; @GenerateMocks([WgerBaseProvider]) void main() { + TestWidgetsFlutterBinding.ensureInitialized(); late UserProvider userProvider; late MockWgerBaseProvider mockWgerBaseProvider; @@ -47,6 +50,7 @@ void main() { ); setUp(() { + SharedPreferencesAsyncPlatform.instance = InMemorySharedPreferencesAsync.empty(); mockWgerBaseProvider = MockWgerBaseProvider(); userProvider = UserProvider(mockWgerBaseProvider);