diff --git a/frontend/lib/api/api_service.dart b/frontend/lib/api/api_service.dart index 16d77e6f..11895af3 100644 --- a/frontend/lib/api/api_service.dart +++ b/frontend/lib/api/api_service.dart @@ -42,7 +42,7 @@ class APIService { final SettingsState settings = ref.read(settingsProvider); final queryParameters = { - 'lang': language ?? settings.language, + 'lang': language ?? settings.current.language, if (lcFilter != null) 'lc_filter': lcFilter.join(','), if (search != null && search.isNotEmpty) 'search': search, if (user != null) "user": user, @@ -72,7 +72,7 @@ class APIService { final SettingsState settings = ref.read(settingsProvider); final queryParameters = { - 'lang': language ?? settings.language, + 'lang': language ?? settings.current.language, if (servings != null) 'servings': servings, if (lcFilter != null) 'lc_filter': lcFilter.join(','), if (toMetric != null && toMetric) 'to_metric': "", @@ -91,7 +91,7 @@ class APIService { final SettingsState settings = ref.read(settingsProvider); final queryParameters = { - 'lang': lang ?? settings.language, + 'lang': lang ?? settings.current.language, }; final url = @@ -162,7 +162,7 @@ class APIService { final SettingsState settings = ref.read(settingsProvider); final queryParameters = { - 'lang': language ?? settings.language, + 'lang': language ?? settings.current.language, if (pageSize != null) 'page_size': pageSize.toString(), }; @@ -234,7 +234,7 @@ class APIService { final SettingsState settings = ref.read(settingsProvider); final queryParameters = { - 'lang': language ?? settings.language, + 'lang': language ?? settings.current.language, if (search != null && search.isNotEmpty) 'search': search, if (pageSize != null) 'page_size': pageSize.toString(), }; @@ -261,7 +261,7 @@ class APIService { final SettingsState settings = ref.read(settingsProvider); final queryParameters = { - 'lang': language ?? settings.language, + 'lang': language ?? settings.current.language, if (search != null && search.isNotEmpty) 'search': search, if (pageSize != null) 'page_size': pageSize.toString(), if (pks != null) 'pks': pks.join(','), @@ -279,7 +279,7 @@ class APIService { final SettingsState settings = ref.read(settingsProvider); final queryParameters = { - 'lang': language ?? settings.language, + 'lang': language ?? settings.current.language, }; final url = getAPIUrl(settings, "/foods/", queryParameters: queryParameters); @@ -299,7 +299,7 @@ class APIService { final SettingsState settings = ref.read(settingsProvider); final queryParameters = { - 'lang': language ?? settings.language, + 'lang': language ?? settings.current.language, if (search != null && search.isNotEmpty) 'search': search, if (pageSize != null) 'page_size': pageSize.toString(), if (pks != null) 'pks': pks.join(','), @@ -319,7 +319,7 @@ class APIService { final SettingsState settings = ref.read(settingsProvider); final queryParameters = { - 'lang': language ?? settings.language, + 'lang': language ?? settings.current.language, }; final url = getAPIUrl(settings, "/foods/$foodId/", @@ -388,7 +388,7 @@ class APIService { final SettingsState settings = ref.read(settingsProvider); final queryParameters = { - 'lang': language ?? settings.language, + 'lang': language ?? settings.current.language, if (search != null && search.isNotEmpty) 'search': search, if (pageSize != null) 'page_size': pageSize.toString(), }; @@ -403,7 +403,7 @@ class APIService { Future createTag(String tagJson, {String? language}) async { final SettingsState settings = ref.read(settingsProvider); final queryParameters = { - 'lang': language ?? settings.language, + 'lang': language ?? settings.current.language, }; final url = getAPIUrl(settings, "/tags/", queryParameters: queryParameters); diff --git a/frontend/lib/api/api_utils.dart b/frontend/lib/api/api_utils.dart index 9598734a..51466933 100644 --- a/frontend/lib/api/api_utils.dart +++ b/frontend/lib/api/api_utils.dart @@ -4,6 +4,6 @@ Uri getAPIUrl(SettingsState settings, String path, {Map? queryParameters, withPostSlash = true}) { final midSlash = path.startsWith('/') ? '' : '/'; final postSlash = path.endsWith('/') || !withPostSlash ? '' : '/'; - return Uri.parse('${settings.apiUrl}$midSlash$path$postSlash') + return Uri.parse('${settings.current.apiUrl}$midSlash$path$postSlash') .replace(queryParameters: queryParameters); } diff --git a/frontend/lib/config/zest_api.dart b/frontend/lib/config/zest_api.dart index 65fea773..9b164e63 100644 --- a/frontend/lib/config/zest_api.dart +++ b/frontend/lib/config/zest_api.dart @@ -3,7 +3,7 @@ import 'package:zest/utils/utils.dart'; // TODO: Set to empty string for (v.1.0.0) release -const String DEFAULT_API_URL = 'http://dbadrian.com/api/v1'; +const String DEFAULT_API_URL = 'https://dbadrian.com/api/v1'; /// const API_RECIPE_SEARCH_FIELDS = { diff --git a/frontend/lib/main.dart b/frontend/lib/main.dart index 574a0319..aa2fc51f 100644 --- a/frontend/lib/main.dart +++ b/frontend/lib/main.dart @@ -97,8 +97,8 @@ class ZestApp extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final router = ref.watch(getRouterProvider); - final Color themeBaseColor = ref - .watch(settingsProvider.select((settings) => settings.themeBaseColor)); + final Color themeBaseColor = ref.watch( + settingsProvider.select((settings) => settings.dirty.themeBaseColor)); final lightTheme_ = lightTheme.copyWith( colorScheme: ColorScheme.fromSeed(seedColor: themeBaseColor)); @@ -106,7 +106,7 @@ class ZestApp extends ConsumerWidget { colorScheme: ColorScheme.fromSeed(seedColor: themeBaseColor)); final useDarkTheme = ref.watch(settingsProvider.select((settings) { - return settings.useDarkTheme; + return settings.dirty.useDarkTheme; })); return MaterialApp.router( diff --git a/frontend/lib/recipes/controller/details_controller.dart b/frontend/lib/recipes/controller/details_controller.dart index 4c62aef7..7edc1c9f 100644 --- a/frontend/lib/recipes/controller/details_controller.dart +++ b/frontend/lib/recipes/controller/details_controller.dart @@ -19,7 +19,7 @@ class RecipeDetailsController extends _$RecipeDetailsController { // afterwards we want silent updates? state = const AsyncValue.loading(); - String language = ref.watch(settingsProvider).language; + String language = ref.watch(settingsProvider).current.language; final recipeValue = await AsyncValue.guard(() => _loadRecipe(language: language)); if (recipeValue.hasError) { diff --git a/frontend/lib/recipes/controller/edit_controller.dart b/frontend/lib/recipes/controller/edit_controller.dart index 64a8a9d4..2a477eb8 100644 --- a/frontend/lib/recipes/controller/edit_controller.dart +++ b/frontend/lib/recipes/controller/edit_controller.dart @@ -233,7 +233,8 @@ class RecipeEditController extends _$RecipeEditController { // set state to loading only for the initial page build // afterwards we want silent updates? state = const AsyncValue.loading(); - final language = ref.watch(settingsProvider.select((v) => v.language)); + final language = + ref.watch(settingsProvider.select((v) => v.current.language)); final recipeValue = await AsyncValue.guard(() => ref.read(apiServiceProvider).getRecipe(recipeId, language: language)); if (recipeValue.hasError) { @@ -282,7 +283,7 @@ class RecipeEditController extends _$RecipeEditController { recipeId: "", dateCreated: DateTime.now(), owner: "", - language: ref.read(settingsProvider).language, + language: ref.read(settingsProvider).current.language, title: "", private: false, ownerComment: "", diff --git a/frontend/lib/recipes/controller/search_controller.dart b/frontend/lib/recipes/controller/search_controller.dart index 34e3e8d9..3d548e7c 100644 --- a/frontend/lib/recipes/controller/search_controller.dart +++ b/frontend/lib/recipes/controller/search_controller.dart @@ -43,12 +43,13 @@ class RecipeSearchFilterSettings extends _$RecipeSearchFilterSettings { // Default: we only show the current user language, unless user // specified to always show all languages final showAllLanguages = - ref.watch(settingsProvider.select((s) => s.searchAllLanguages)); + ref.watch(settingsProvider.select((s) => s.current.searchAllLanguages)); Set lcFilter = {}; if (showAllLanguages) { lcFilter.addAll(AVAILABLE_LANGUAGES.keys); } else { - lcFilter.add(ref.watch(settingsProvider.select((s) => s.language))); + lcFilter + .add(ref.watch(settingsProvider.select((s) => s.current.language))); } return FilterSettingsState( lcFilter: lcFilter, @@ -81,7 +82,9 @@ class RecipeSearchFilterSettings extends _$RecipeSearchFilterSettings { } else { state = state.copyWith( showAllLanguages: false, - lcFilter: {ref.watch(settingsProvider.select((s) => s.language))}, + lcFilter: { + ref.watch(settingsProvider.select((s) => s.current.language)) + }, ); } } diff --git a/frontend/lib/recipes/screens/recipe_details.dart b/frontend/lib/recipes/screens/recipe_details.dart index ca180ba1..674d9da5 100644 --- a/frontend/lib/recipes/screens/recipe_details.dart +++ b/frontend/lib/recipes/screens/recipe_details.dart @@ -566,7 +566,7 @@ TableRow buildIngredientRow(WidgetRef ref, Ingredient ingredient, String recipeId, bool isMarked, Function() markCallback) { final amount = ingredient.getAmount(); final unit = ingredient.getUnitAbbreviation( - matchLanguage: ref.read(settingsProvider).language); + matchLanguage: ref.read(settingsProvider).current.language); final food = ingredient.food; final details = ingredient.details; @@ -612,7 +612,8 @@ TableRow buildIngredientRow(WidgetRef ref, Ingredient ingredient, onConfirm: (String value) async { final translatedName = TranslatedField(values: [ TranslatedValue( - value: value, lang: ref.read(settingsProvider).language) + value: value, + lang: ref.read(settingsProvider).current.language) ]); final translatedFood = food.copyWith(name: translatedName); final json = translatedFood.toJson(); // toJsonExplicit(); diff --git a/frontend/lib/recipes/screens/recipe_list_tile.dart b/frontend/lib/recipes/screens/recipe_list_tile.dart index 454653c4..9be295ce 100644 --- a/frontend/lib/recipes/screens/recipe_list_tile.dart +++ b/frontend/lib/recipes/screens/recipe_list_tile.dart @@ -50,7 +50,7 @@ class RecipeListTile extends ConsumerWidget { trailing: (language != null && language!.isNotEmpty && - language != ref.watch(settingsProvider).language) + language != ref.watch(settingsProvider).current.language) ? Column(mainAxisAlignment: MainAxisAlignment.center, children: [ CountryFlag.fromLanguageCode( language!, diff --git a/frontend/lib/recipes/screens/widgets/ingredient_form.dart b/frontend/lib/recipes/screens/widgets/ingredient_form.dart index 327677af..91990ace 100644 --- a/frontend/lib/recipes/screens/widgets/ingredient_form.dart +++ b/frontend/lib/recipes/screens/widgets/ingredient_form.dart @@ -770,7 +770,7 @@ class FoodCreationTile extends ConsumerWidget { title: Text("Add food '${foodController.text}'"), onTap: () async { final currentLanguage = - ref.watch(settingsProvider.select((s) => s.language)); + ref.watch(settingsProvider.select((s) => s.current.language)); foodCreationDialog( initialLanguage: currentLanguage, initialValue: foodController.text, diff --git a/frontend/lib/recipes/screens/widgets/translatable_field.dart b/frontend/lib/recipes/screens/widgets/translatable_field.dart index c9dcc2d0..22c1de7a 100644 --- a/frontend/lib/recipes/screens/widgets/translatable_field.dart +++ b/frontend/lib/recipes/screens/widgets/translatable_field.dart @@ -113,7 +113,7 @@ class _TranslatableFieldState extends ConsumerState { bool canBeTranslated() { final settings = ref.read(settingsProvider); - return settings.language != widget.field.activeLanguage(); + return settings.current.language != widget.field.activeLanguage(); } Widget _buildTranslationDialog( @@ -126,8 +126,8 @@ class _TranslatableFieldState extends ConsumerState { child: TextFormField( controller: controller, decoration: InputDecoration( - labelText: - AVAILABLE_LANGUAGES[ref.read(settingsProvider).language], + labelText: AVAILABLE_LANGUAGES[ + ref.read(settingsProvider).current.language], // suffixText: "optional", // border: const OutlineInputBorder(), ), diff --git a/frontend/lib/settings/settings_provider.dart b/frontend/lib/settings/settings_provider.dart index 47048466..13971ca0 100644 --- a/frontend/lib/settings/settings_provider.dart +++ b/frontend/lib/settings/settings_provider.dart @@ -1,6 +1,9 @@ import 'package:flutter/material.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:go_router/go_router.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:zest/authentication/auth_service.dart'; +import 'package:zest/ui/login_screen.dart'; import '../config/constants.dart'; import '../config/zest_api.dart'; @@ -24,8 +27,8 @@ const _showAdvancedSettingsKey = 'settings_show_advanced_settings'; const _baseColor = Color.fromARGB(255, 7, 228, 255); @freezed -class SettingsState with _$SettingsState { - factory SettingsState({ +class SettingsStateData with _$SettingsStateData { + const factory SettingsStateData({ // Language: UI and Content // TODO Default language should be infered based on system language @Default(DEFAULT_LANGUAGE) String language, @@ -38,9 +41,18 @@ class SettingsState with _$SettingsState { // API Related @Default(DEFAULT_API_URL) apiUrl, + @Default(false) bool apiUrlDirty, // Advanced Settings @Default(false) bool showAdvancedSettings, + }) = _SettingsStateData; +} + +@freezed +class SettingsState with _$SettingsState { + factory SettingsState({ + @Default(SettingsStateData()) SettingsStateData current, + @Default(SettingsStateData()) SettingsStateData dirty, }) = _SettingsState; } @@ -52,31 +64,31 @@ class Settings extends _$Settings { } void setUseDarkTheme(bool useDarkTheme) { - state = state.copyWith(useDarkTheme: useDarkTheme); + state = state.copyWith.dirty(useDarkTheme: useDarkTheme); } void setPickerColor(Color pickerColor) { - state = state.copyWith(pickerColor: pickerColor); + state = state.copyWith.dirty(pickerColor: pickerColor); } void setThemeColor(Color themeBaseColor) { - state = state.copyWith(themeBaseColor: themeBaseColor); + state = state.copyWith.dirty(themeBaseColor: themeBaseColor); } void setLanguage(String language) { - state = state.copyWith(language: language); + state = state.copyWith.dirty(language: language); } void setSearchAllLanguages(bool searchAllLanguages) { - state = state.copyWith(searchAllLanguages: searchAllLanguages); + state = state.copyWith.dirty(searchAllLanguages: searchAllLanguages); } void setApiUrl(String apiUrl) { - state = state.copyWith(apiUrl: apiUrl); + state = state.copyWith.dirty(apiUrl: apiUrl, apiUrlDirty: true); } void setShowAdvancedSettings(bool showAdvancedSettings) { - state = state.copyWith(showAdvancedSettings: showAdvancedSettings); + state = state.copyWith.dirty(showAdvancedSettings: showAdvancedSettings); } SettingsState loadSettings() { @@ -92,7 +104,9 @@ class Settings extends _$Settings { final showAdvancedSettings = prefs.getBool(_showAdvancedSettingsKey) ?? false; final apiUrl = prefs.getString(apiUrlKey) ?? DEFAULT_API_URL; - return SettingsState( + final apiUrlDirty = false; + + final gt = SettingsStateData( useDarkTheme: useDarkTheme, pickerColor: Color(pickerColor), themeBaseColor: Color(themeBaseColor), @@ -100,32 +114,39 @@ class Settings extends _$Settings { searchAllLanguages: searchAllLanguages, showAdvancedSettings: showAdvancedSettings, apiUrl: apiUrl, + apiUrlDirty: apiUrlDirty, ); + + return SettingsState(current: gt, dirty: gt); } // Load settings from the sharedPrefs - void discardSettings() { + void discardChanges() { state = loadSettings(); } // Load settings from the sharedPrefs void restoreDefaultSettings() { - state = SettingsState(showAdvancedSettings: state.showAdvancedSettings); + final gt = SettingsStateData( + showAdvancedSettings: state.dirty.showAdvancedSettings); + state = SettingsState(current: gt, dirty: gt); } // Write them to the sharedPrefs - void persistSettings() { + void persistSettings() async { // saveSettings(ref, state); final prefs = ref.read(sharedPreferencesProvider); // Theme - prefs.setBool(_themeUseDarkThemeKey, state.useDarkTheme); - prefs.setInt(_themeColorPickerKey, state.pickerColor.value); - prefs.setInt(_themeBaseColorKey, state.themeBaseColor.value); + prefs.setBool(_themeUseDarkThemeKey, state.dirty.useDarkTheme); + prefs.setInt(_themeColorPickerKey, state.dirty.pickerColor.value); + prefs.setInt(_themeBaseColorKey, state.dirty.themeBaseColor.value); // Language - prefs.setString(_languageKey, state.language); - prefs.setBool(_searchAllLanguagesKey, state.searchAllLanguages); + prefs.setString(_languageKey, state.dirty.language); + prefs.setBool(_searchAllLanguagesKey, state.dirty.searchAllLanguages); // Advanced - prefs.setBool(_showAdvancedSettingsKey, state.showAdvancedSettings); - prefs.setString(apiUrlKey, state.apiUrl); + prefs.setBool(_showAdvancedSettingsKey, state.dirty.showAdvancedSettings); + prefs.setString(apiUrlKey, state.dirty.apiUrl); + + state = state.copyWith(current: state.dirty); } } diff --git a/frontend/lib/settings/settings_screen.dart b/frontend/lib/settings/settings_screen.dart index 3e83b699..41fe701e 100644 --- a/frontend/lib/settings/settings_screen.dart +++ b/frontend/lib/settings/settings_screen.dart @@ -1,8 +1,13 @@ +import 'dart:math'; + import 'package:flutter/material.dart'; import 'package:flutter_colorpicker/flutter_colorpicker.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:zest/authentication/auth_service.dart'; +import 'package:zest/recipes/screens/recipe_edit.dart'; +import 'package:zest/ui/login_screen.dart'; import '../config/constants.dart'; import '../ui/widgets/divider_text.dart'; @@ -78,7 +83,7 @@ Widget buildThemeBaseColorOption(ref, context) { content: SingleChildScrollView( child: ColorPicker( pickerColor: ref.watch(settingsProvider - .select((settings) => settings.themeBaseColor)), + .select((settings) => settings.dirty.themeBaseColor)), onColorChanged: (color) { // pickerColor = color; settings.setPickerColor(color); @@ -90,7 +95,7 @@ Widget buildThemeBaseColorOption(ref, context) { child: const Text('Got it'), onPressed: () { settings.setThemeColor(ref.watch(settingsProvider - .select((settings) => settings.pickerColor))); + .select((settings) => settings.dirty.pickerColor))); Navigator.of(context).pop(); }, ), @@ -106,8 +111,8 @@ Widget buildThemeBaseColorOption(ref, context) { Widget buildThemeOption(ref) { final settings = ref.read(settingsProvider.notifier); - final useDarkTheme = - ref.watch(settingsProvider.select((settings) => settings.useDarkTheme)); + final useDarkTheme = ref.watch( + settingsProvider.select((settings) => settings.dirty.useDarkTheme)); return SwitchListTile( secondary: const Icon(Icons.palette), title: const Text("Use Dark Theme"), @@ -148,7 +153,7 @@ Widget buildThemeOption(ref) { Widget buildSearchLanguageIndicator(ref) { final settings = ref.read(settingsProvider.notifier); final bool searchAllLanguages = ref.watch( - settingsProvider.select((settings) => settings.searchAllLanguages)); + settingsProvider.select((settings) => settings.dirty.searchAllLanguages)); return SwitchListTile( value: searchAllLanguages, onChanged: (bool value) { @@ -164,7 +169,7 @@ Widget buildSearchLanguageIndicator(ref) { Widget buildLanguageSelector(ref) { final settings = ref.read(settingsProvider.notifier); final String language = - ref.watch(settingsProvider.select((settings) => settings.language)); + ref.watch(settingsProvider.select((settings) => settings.dirty.language)); return ListTile( leading: const Icon(Icons.description_outlined), title: const Text("Display Language"), @@ -193,8 +198,8 @@ Widget buildLanguageSelector(ref) { Widget buildShowAdvancedSettingsCheckbox(ref) { final settings = ref.read(settingsProvider.notifier); - final bool showAdvancedSettings = ref.watch( - settingsProvider.select((settings) => settings.showAdvancedSettings)); + final bool showAdvancedSettings = ref.watch(settingsProvider + .select((settings) => settings.dirty.showAdvancedSettings)); return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -225,20 +230,43 @@ class APIFieldWidget extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final settings = ref.watch(settingsProvider.notifier); - final apiUrl = ref.watch(settingsProvider.select((s) => s.apiUrl)); + final apiUrl = ref.watch(settingsProvider.select((s) => s.dirty.apiUrl)); final TextEditingController apiUrlCtrl = useTextEditingController(); + final double screenWidth = MediaQuery.of(context).size.width; + // The following updates the text-editing controller with the current value // but it will also reset the cursor position to the end of the text // hence we need to cache the position -> seleciton - final cachePosition = apiUrlCtrl.selection; + final cacheSelection = apiUrlCtrl.selection; apiUrlCtrl.text = apiUrl; // to refresh the text on changes - apiUrlCtrl.selection = cachePosition; + apiUrlCtrl.selection = TextSelection.fromPosition(TextPosition( + offset: min(apiUrlCtrl.text.length, cacheSelection.baseOffset))); + + final isValidURL = Uri.tryParse(apiUrlCtrl.text)?.hasAbsolutePath ?? false; + var showErrorText = false; + var errorText = ""; + if (ref.watch(settingsProvider.select((s) => s.dirty.apiUrlDirty))) { + showErrorText = true; + if (errorText.isNotEmpty) { + errorText += "\n"; + } + errorText = "The API URL is changed, if saved you will be logged out."; + } + if (!isValidURL) { + showErrorText = true; + if (errorText.isNotEmpty) { + errorText += "\n"; + } + errorText += "Invalid URL!"; + } + return ListTile( leading: const Icon(Icons.connect_without_contact), title: const Text("API"), trailing: ConstrainedBox( - constraints: const BoxConstraints(minWidth: 40, maxWidth: 250), + constraints: + BoxConstraints(minWidth: 40, maxWidth: max(250, screenWidth * 0.5)), child: Row( children: [ Expanded( @@ -246,9 +274,10 @@ class APIFieldWidget extends HookConsumerWidget { controller: apiUrlCtrl, onChanged: ((value) => settings.setApiUrl(value)), // controller: ref.apiAddressCtrl, - decoration: const InputDecoration( + decoration: InputDecoration( // border: OutlineInputBorder(), - hintText: 'http://your-domain.com/api/v1', + hintText: 'https://your-domain.com/api/v1', + errorText: showErrorText ? errorText : null, ), textAlign: TextAlign.center, ), @@ -269,8 +298,8 @@ Widget _buildAdvancedSettingsImpl(ref) { } Widget buildAdvancedSettings(ref) { - final bool showAdvancedSettings = ref.watch( - settingsProvider.select((settings) => settings.showAdvancedSettings)); + final bool showAdvancedSettings = ref.watch(settingsProvider + .select((settings) => settings.dirty.showAdvancedSettings)); return Column( children: [ if (showAdvancedSettings) @@ -291,7 +320,7 @@ Widget buildScreenButtons(context, ref) { children: [ OutlinedButton( onPressed: () async { - settings.discardSettings(); + settings.discardChanges(); GoRouter.of(context).pop(); }, child: const Text("Back"), @@ -313,6 +342,20 @@ Widget buildScreenButtons(context, ref) { const SizedBox( width: 25, ), + OutlinedButton( + onPressed: settings.discardChanges, + style: OutlinedButton.styleFrom( + side: BorderSide(color: Theme.of(ref).colorScheme.error), + // backgroundColor: Theme.of(ref).colorScheme.onErrorContainer, + ), + child: Text( + "Discard Changes", + style: TextStyle(color: Theme.of(context).colorScheme.error), + ), + ), + const SizedBox( + width: 25, + ), ElevatedButton( onPressed: () { settings.persistSettings(); @@ -320,7 +363,14 @@ Widget buildScreenButtons(context, ref) { // however, this will require more logic to force a reload of data, // such as recipes which might require a refresh due to language changes. // GoRouter.of(context).goNamed(HomePage.routeName); - GoRouter.of(context).pop(); + + if (ref.read(settingsProvider.select((s) => s.dirty.apiUrlDirty))) { + ref + .read(authenticationServiceProvider.notifier) + .logout() + .whenComplete( + () => GoRouter.of(context).go(LoginPage.routeLocation)); + } }, child: const Text("Save"), ),