diff --git a/flutter/build/test_cache/build/c1a2004662044feb47c136be2870373c.cache.dill.track.dill b/flutter/build/test_cache/build/c1a2004662044feb47c136be2870373c.cache.dill.track.dill index 6607b20f..940dca0a 100644 Binary files a/flutter/build/test_cache/build/c1a2004662044feb47c136be2870373c.cache.dill.track.dill and b/flutter/build/test_cache/build/c1a2004662044feb47c136be2870373c.cache.dill.track.dill differ diff --git a/flutter/lib/features/admin/data/admin_repository.dart b/flutter/lib/features/admin/data/admin_repository.dart index 102d1944..52c4c178 100644 --- a/flutter/lib/features/admin/data/admin_repository.dart +++ b/flutter/lib/features/admin/data/admin_repository.dart @@ -2,6 +2,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../core/api/api_client.dart'; import '../../../core/api/api_paths.dart'; import '../../../core/api/guarded_api_call.dart'; +import '../../auth/data/auth_providers.dart'; import '../domain/user_admin.dart'; final adminRepositoryProvider = Provider((ref) { @@ -14,26 +15,31 @@ class AdminRepository { AdminRepository(this._apiClient, this._ref); + Future _token() => _ref.read(authStateProvider.future); + Future> listUsers() async { + final token = await _token(); final data = await guardedApiCall( _ref, - () => _apiClient.getJson(UserApiPaths.list), + () => _apiClient.getJson(UserApiPaths.list, token: token), ); return (data as List).map((e) => UserAdmin.fromJson(e as Map)).toList(); } Future setRole(int userId, String newRole) async { + final token = await _token(); final data = await guardedApiCall( _ref, - () => _apiClient.patchJson(UserApiPaths.setRole(userId), body: {'role': newRole}), + () => _apiClient.patchJson(UserApiPaths.setRole(userId), body: {'role': newRole}, token: token), ); return UserAdmin.fromJson(data); } Future setPremium(int userId, {required bool isPremium}) async { + final token = await _token(); final data = await guardedApiCall( _ref, - () => _apiClient.patchJson(UserApiPaths.setPremium(userId), body: {'isPremium': isPremium}), + () => _apiClient.patchJson(UserApiPaths.setPremium(userId), body: {'isPremium': isPremium}, token: token), ); return UserAdmin.fromJson(data); } @@ -44,6 +50,7 @@ class AdminRepository { required String password, String role = 'user', }) async { + final token = await _token(); final data = await guardedApiCall( _ref, () => _apiClient.postJson(UserApiPaths.list, body: { @@ -51,21 +58,25 @@ class AdminRepository { 'email': email, 'password': password, 'role': role, - }), + }, token: token), ); return UserAdmin.fromJson(data as Map); } - Future deleteUser(int userId) => guardedApiCall( - _ref, - () => _apiClient.deleteJson(UserApiPaths.delete(userId)), - ); + Future deleteUser(int userId) async { + final token = await _token(); + return guardedApiCall( + _ref, + () => _apiClient.deleteJson(UserApiPaths.delete(userId), token: token), + ); + } /// Returns `{ temporaryPassword, to, subject, body }`. Future> resetPassword(int userId) async { + final token = await _token(); final result = await guardedApiCall( _ref, - () => _apiClient.postJson(UserApiPaths.resetPassword(userId)), + () => _apiClient.postJson(UserApiPaths.resetPassword(userId), token: token), ); return (result as Map); } diff --git a/flutter/lib/features/admin/presentation/admin_screen.dart b/flutter/lib/features/admin/presentation/admin_screen.dart index 5bede6a3..c47ef6b2 100644 --- a/flutter/lib/features/admin/presentation/admin_screen.dart +++ b/flutter/lib/features/admin/presentation/admin_screen.dart @@ -379,7 +379,7 @@ class _CreateUserDialogState extends State<_CreateUserDialog> { ), const SizedBox(height: 12), DropdownButtonFormField( - value: _role, + initialValue: _role, decoration: const InputDecoration(labelText: 'Roll'), items: const [ DropdownMenuItem(value: 'user', child: Text('Användare')), diff --git a/flutter/lib/features/auth/presentation/login_screen.dart b/flutter/lib/features/auth/presentation/login_screen.dart index fed6724f..f00915f8 100644 --- a/flutter/lib/features/auth/presentation/login_screen.dart +++ b/flutter/lib/features/auth/presentation/login_screen.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:go_router/go_router.dart'; import '../../../core/api/api_error_mapper.dart'; import '../../../core/l10n/l10n.dart'; diff --git a/flutter/lib/features/inventory/data/inventory_providers.dart b/flutter/lib/features/inventory/data/inventory_providers.dart index d30c1379..6ad064cf 100644 --- a/flutter/lib/features/inventory/data/inventory_providers.dart +++ b/flutter/lib/features/inventory/data/inventory_providers.dart @@ -19,6 +19,10 @@ class _StringNotifier extends Notifier { final String _initial; @override String build() => _initial; + + void setValue(String value) { + state = value; + } } final inventoryLocationFilterProvider = diff --git a/flutter/lib/features/inventory/presentation/create_inventory_screen.dart b/flutter/lib/features/inventory/presentation/create_inventory_screen.dart index 7218da1b..375c4f63 100644 --- a/flutter/lib/features/inventory/presentation/create_inventory_screen.dart +++ b/flutter/lib/features/inventory/presentation/create_inventory_screen.dart @@ -196,7 +196,7 @@ class _CreateInventoryScreenState const SizedBox(width: 8), Expanded( child: DropdownButtonFormField( - value: _unitController.text.trim().isEmpty + initialValue: _unitController.text.trim().isEmpty ? null : _unitController.text.trim(), isExpanded: true, @@ -224,7 +224,7 @@ class _CreateInventoryScreenState ), const SizedBox(height: 12), DropdownButtonFormField( - value: _locationController.text.trim().isEmpty + initialValue: _locationController.text.trim().isEmpty ? null : _locationController.text.trim(), isExpanded: true, diff --git a/flutter/lib/features/inventory/presentation/inventory_edit_screen.dart b/flutter/lib/features/inventory/presentation/inventory_edit_screen.dart index 9cbfb8b0..dad16f7a 100644 --- a/flutter/lib/features/inventory/presentation/inventory_edit_screen.dart +++ b/flutter/lib/features/inventory/presentation/inventory_edit_screen.dart @@ -175,7 +175,7 @@ class _InventoryEditScreenState extends ConsumerState { const SizedBox(width: 8), Expanded( child: DropdownButtonFormField( - value: _unitController.text.trim().isEmpty + initialValue: _unitController.text.trim().isEmpty ? null : _unitController.text.trim(), isExpanded: true, @@ -204,7 +204,7 @@ class _InventoryEditScreenState extends ConsumerState { ), const SizedBox(height: 12), DropdownButtonFormField( - value: _locationController.text.trim().isEmpty + initialValue: _locationController.text.trim().isEmpty ? null : _locationController.text.trim(), isExpanded: true, diff --git a/flutter/lib/features/inventory/presentation/inventory_screen.dart b/flutter/lib/features/inventory/presentation/inventory_screen.dart index fb4ec1d2..5451acae 100644 --- a/flutter/lib/features/inventory/presentation/inventory_screen.dart +++ b/flutter/lib/features/inventory/presentation/inventory_screen.dart @@ -4,9 +4,7 @@ import 'package:go_router/go_router.dart'; import '../../../core/api/api_error_mapper.dart'; import '../../../core/ui/async_state_views.dart'; -import '../../auth/data/auth_providers.dart'; import '../data/inventory_providers.dart'; -import '../domain/inventory_item.dart'; import 'swipeable_inventory_tile.dart'; class InventoryScreen extends ConsumerWidget { @@ -53,14 +51,14 @@ class InventoryScreen extends ConsumerWidget { selected: location == option, onSelected: (_) => ref .read(inventoryLocationFilterProvider.notifier) - .state = option, + .setValue(option), ), ) .toList(), ), const SizedBox(height: 8), DropdownButtonFormField( - value: sort, + initialValue: sort, isExpanded: true, decoration: const InputDecoration( labelText: 'Sortering', @@ -75,8 +73,9 @@ class InventoryScreen extends ConsumerWidget { ) .toList(), onChanged: (value) { - ref.read(inventorySortFilterProvider.notifier).state = - value ?? ''; + ref + .read(inventorySortFilterProvider.notifier) + .setValue(value ?? ''); }, ), ], diff --git a/flutter/lib/features/meal_plan/data/meal_plan_providers.dart b/flutter/lib/features/meal_plan/data/meal_plan_providers.dart index 99feed1a..bdd6ffba 100644 --- a/flutter/lib/features/meal_plan/data/meal_plan_providers.dart +++ b/flutter/lib/features/meal_plan/data/meal_plan_providers.dart @@ -16,6 +16,18 @@ class _IntNotifier extends Notifier { final int _initial; @override int build() => _initial; + + void increment() { + state = state + 1; + } + + void decrement() { + state = state - 1; + } + + void reset() { + state = 0; + } } final mealPlanWeekOffsetProvider = diff --git a/flutter/lib/features/meal_plan/data/meal_plan_repository.dart b/flutter/lib/features/meal_plan/data/meal_plan_repository.dart index f5f6903e..69c2226b 100644 --- a/flutter/lib/features/meal_plan/data/meal_plan_repository.dart +++ b/flutter/lib/features/meal_plan/data/meal_plan_repository.dart @@ -19,7 +19,7 @@ class MealPlanRepository { message: 'Ogiltigt svar från servern.', ); } - return (data as List) + return data .map((item) => MealPlanEntry.fromJson(item as Map)) .toList(); } on ApiException { @@ -41,7 +41,7 @@ class MealPlanRepository { message: 'Ogiltigt svar från servern.', ); } - return (data as List) + return data .map((item) => ShoppingItem.fromJson(item as Map)) .toList(); } on ApiException { @@ -63,7 +63,7 @@ class MealPlanRepository { message: 'Ogiltigt svar från servern.', ); } - return (data as List) + return data .map((item) => InventoryCompareItem.fromJson(item as Map)) .toList(); } on ApiException { diff --git a/flutter/lib/features/meal_plan/presentation/meal_plan_screen.dart b/flutter/lib/features/meal_plan/presentation/meal_plan_screen.dart index 2585e671..1038cf46 100644 --- a/flutter/lib/features/meal_plan/presentation/meal_plan_screen.dart +++ b/flutter/lib/features/meal_plan/presentation/meal_plan_screen.dart @@ -114,19 +114,19 @@ class _MealPlanScreenState extends ConsumerState { crossAxisAlignment: WrapCrossAlignment.center, children: [ OutlinedButton.icon( - onPressed: () => ref.read(mealPlanWeekOffsetProvider.notifier).state--, + onPressed: () => ref.read(mealPlanWeekOffsetProvider.notifier).decrement(), icon: const Icon(Icons.chevron_left), label: Text(l10n.mealPlanWeekPrevious), ), Chip(label: Text(weekLabel)), OutlinedButton.icon( - onPressed: () => ref.read(mealPlanWeekOffsetProvider.notifier).state++, + onPressed: () => ref.read(mealPlanWeekOffsetProvider.notifier).increment(), icon: const Icon(Icons.chevron_right), label: Text(l10n.mealPlanWeekNext), ), if (ref.watch(mealPlanWeekOffsetProvider) != 0) TextButton( - onPressed: () => ref.read(mealPlanWeekOffsetProvider.notifier).state = 0, + onPressed: () => ref.read(mealPlanWeekOffsetProvider.notifier).reset(), child: Text(l10n.mealPlanWeekCurrent), ), ], @@ -203,7 +203,7 @@ class _DayCard extends StatelessWidget { ), const SizedBox(height: 12), DropdownButtonFormField( - value: selectedValue, + initialValue: selectedValue, isExpanded: true, decoration: InputDecoration( labelText: l10n.mealPlanSelectRecipe, @@ -248,7 +248,7 @@ class _DayCard extends StatelessWidget { SizedBox( width: 220, child: DropdownButtonFormField( - value: currentServings, + initialValue: currentServings, decoration: InputDecoration( labelText: l10n.mealPlanServingsLabel, border: const OutlineInputBorder(), diff --git a/flutter/lib/features/pantry/presentation/pantry_screen.dart b/flutter/lib/features/pantry/presentation/pantry_screen.dart index 053541e3..16a9df02 100644 --- a/flutter/lib/features/pantry/presentation/pantry_screen.dart +++ b/flutter/lib/features/pantry/presentation/pantry_screen.dart @@ -51,7 +51,7 @@ class _PantryScreenState extends ConsumerState { ), const SizedBox(height: 12), DropdownButtonFormField( - value: selectedUnit, + initialValue: selectedUnit, isExpanded: true, decoration: const InputDecoration( labelText: 'Enhet', @@ -72,7 +72,7 @@ class _PantryScreenState extends ConsumerState { ), const SizedBox(height: 12), DropdownButtonFormField( - value: selectedLocation, + initialValue: selectedLocation, isExpanded: true, decoration: const InputDecoration( labelText: 'Plats (valfri)', diff --git a/flutter/lib/features/profile/data/profile_repository.dart b/flutter/lib/features/profile/data/profile_repository.dart index 7a87682d..60547882 100644 --- a/flutter/lib/features/profile/data/profile_repository.dart +++ b/flutter/lib/features/profile/data/profile_repository.dart @@ -16,9 +16,10 @@ class ProfileRepository { ProfileRepository(this._apiClient, this._ref); Future getMe() async { + final token = await _ref.read(authStateProvider.future); final data = await guardedApiCall( _ref, - () => _apiClient.getJson(UserApiPaths.me), + () => _apiClient.getJson(UserApiPaths.me, token: token), ); return UserProfile.fromJson(data); } @@ -28,6 +29,7 @@ class ProfileRepository { String? firstName, String? lastName, }) async { + final token = await _ref.read(authStateProvider.future); final body = { if (email != null) 'email': email, if (firstName != null) 'firstName': firstName, @@ -35,7 +37,7 @@ class ProfileRepository { }; final data = await guardedApiCall( _ref, - () => _apiClient.patchJson(UserApiPaths.me, body: body), + () => _apiClient.patchJson(UserApiPaths.me, body: body, token: token), ); return UserProfile.fromJson(data); } diff --git a/flutter/lib/features/recipes/data/recipe_repository.dart b/flutter/lib/features/recipes/data/recipe_repository.dart index dd97d195..dde489c4 100644 --- a/flutter/lib/features/recipes/data/recipe_repository.dart +++ b/flutter/lib/features/recipes/data/recipe_repository.dart @@ -17,7 +17,7 @@ class RecipeRepository { throw const ApiException( type: ApiErrorType.unknown, message: 'Ogiltigt svar från servern.'); } - return (data as List) + return data .map((e) => Recipe.fromJson(e as Map)) .toList(); } on ApiException { diff --git a/flutter/lib/features/recipes/presentation/recipe_edit_screen.dart b/flutter/lib/features/recipes/presentation/recipe_edit_screen.dart index 6626e4fb..27c99af9 100644 --- a/flutter/lib/features/recipes/presentation/recipe_edit_screen.dart +++ b/flutter/lib/features/recipes/presentation/recipe_edit_screen.dart @@ -399,7 +399,7 @@ class _RecipeEditScreenState extends ConsumerState { ], ), DropdownButtonFormField( - value: ingredient.productId, + initialValue: ingredient.productId, isExpanded: true, decoration: const InputDecoration( labelText: 'Produkt *', @@ -461,7 +461,7 @@ class _RecipeEditScreenState extends ConsumerState { const SizedBox(width: 12), Expanded( child: DropdownButtonFormField( - value: ingredient.unit.trim().isEmpty ? null : ingredient.unit, + initialValue: ingredient.unit.trim().isEmpty ? null : ingredient.unit, isExpanded: true, decoration: const InputDecoration( labelText: 'Enhet *',