Skip to content

Commit

Permalink
refactor: improve categories type state management (#99)
Browse files Browse the repository at this point in the history
* refactor: improve categories type state management

* refactor: improve categories type state management

Co-authored-by: André Masson <[email protected]>
  • Loading branch information
amwebexpert and Andre-at-LaCapitale authored Aug 18, 2022
1 parent 822c999 commit 8f39783
Show file tree
Hide file tree
Showing 11 changed files with 77 additions and 83 deletions.
56 changes: 43 additions & 13 deletions lib/features/categories/categories.screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

import '../../service.locator.dart';
import '../../services/logger/logger.service.dart';
import '../../services/storage/shared.preferences.enum.dart';
import '../../services/storage/shared.preferences.services.dart';
import '../../theme/widgets/app.bar.title.widget.dart';
Expand All @@ -10,15 +11,47 @@ import '../../theme/widgets/responsive/navigation/responsive.navigation.rail.or.
import 'local/local.categories.widget.dart';
import 'remote/remote.categories.widget.dart';

class CategoriesWidget extends StatelessWidget {
class CategoriesWidget extends StatefulWidget {
const CategoriesWidget({Key? key}) : super(key: key);

@override
State<CategoriesWidget> createState() => _CategoriesWidgetState();
}

class _CategoriesWidgetState extends State<CategoriesWidget> {
final SharedPreferencesService preferences = serviceLocator.get();
final _lastSelectionKey = SharedPreferenceKey.lastSelectedCategoriesListType.name;
final LoggerService loggerService = serviceLocator.get();

late int _currentIndex;
late Widget _child;

@override
void initState() {
super.initState();
_currentIndex = preferences.getInt(SharedPreferenceKey.lastSelectedCategoriesListType.name, defaultValue: 0);
_child = _computeChild(_currentIndex);
}

Widget _computeChild(int index) {
switch (index) {
case 0:
return const RemoteCategoriesWidget();
case 1:
return const LocalCategoriesWidget();

default:
loggerService.error('unhandled child index $index, returning default');
return const RemoteCategoriesWidget();
}
}

CategoriesWidget({Key? key}) : super(key: key);
void _onTap(int index) {
preferences.setInt(SharedPreferenceKey.lastSelectedCategoriesListType.name, index);

Widget childBuilder(int index) {
preferences.setInt(_lastSelectionKey, index);
return index == 0 ? const RemoteCategoriesWidget() : const LocalCategoriesWidget();
setState(() {
_child = _computeChild(index);
_currentIndex = index;
});
}

@override
Expand All @@ -29,12 +62,9 @@ class CategoriesWidget extends StatelessWidget {
appBar: AppBar(
title: AppBarTitle(title: localizations.categories),
),
body: ResponsiveNavigationRailOrBar(
currentIndex: preferences.getInt(_lastSelectionKey, defaultValue: 0),
items: [
NavigationChoices(text: localizations.categoryTypeCloud, icon: const Icon(Icons.cloud)),
NavigationChoices(text: localizations.categoryTypeDevice, icon: const Icon(Icons.save_alt)),
],
childBuilder: childBuilder));
body: ResponsiveNavigationRailOrBar(items: [
NavigationChoices(text: localizations.categoryTypeCloud, icon: const Icon(Icons.cloud)),
NavigationChoices(text: localizations.categoryTypeDevice, icon: const Icon(Icons.save_alt)),
], currentIndex: _currentIndex, onTap: _onTap, child: _child));
}
}
8 changes: 3 additions & 5 deletions lib/features/game/game.store.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,7 @@ abstract class _GameStoreBase with Store {
Future<void> _initialize() async {
List<ApiCategory> categories = await textsService.getCategories();

String lastSelectedCategoryUuid =
preferences.getString(SharedPreferenceKey.lastSelectedCategory.name, defaultValue: categories.first.uuid);
String lastSelectedCategoryUuid = preferences.getString(SharedPreferenceKey.lastSelectedCategory.name, defaultValue: categories.first.uuid);
ApiCategory initialCategory = categories.where((element) => element.uuid == lastSelectedCategoryUuid).first;

await selectCategory(initialCategory);
Expand All @@ -56,7 +55,7 @@ abstract class _GameStoreBase with Store {
}

preferences.setString(SharedPreferenceKey.lastSelectedCategory.name, selected.uuid).onError((e, stackTrace) {
logger.error("Can't write preference ${SharedPreferenceKey.lastSelectedCategory}", e, stackTrace: stackTrace);
logger.error("Can't write preference ${SharedPreferenceKey.lastSelectedCategory}", error: e, stackTrace: stackTrace);
return false;
});

Expand Down Expand Up @@ -99,8 +98,7 @@ abstract class _GameStoreBase with Store {
textToGuess = textToGuess.tryChar(c: c);

if (textToGuess.isGameOver()) {
currentCategoryPlayedItems =
await gamePlayedItemsStorageService.addPlayedItem(currentCategory.uuid, textToGuess.original);
currentCategoryPlayedItems = await gamePlayedItemsStorageService.addPlayedItem(currentCategory.uuid, textToGuess.original);
}
}

Expand Down
14 changes: 5 additions & 9 deletions lib/features/settings/settings.store.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,13 @@ abstract class _SettingsStoreBase with Store {

Locale _initLocale() {
final String platformLanguageCode = Locale(Platform.localeName.split('_')[0]).languageCode;
final String appLanguage =
preferences.getString(SharedPreferenceKey.appLanguage.name, defaultValue: platformLanguageCode);
final String appLanguage = preferences.getString(SharedPreferenceKey.appLanguage.name, defaultValue: platformLanguageCode);
return languageToLocaleMap[appLanguage] ?? defaultAppLocale;
}

bool _initDarkTheme() {
final platformThemeMode = SchedulerBinding.instance.window.platformBrightness;
return preferences.getBool(SharedPreferenceKey.appIsThemeDark.name,
defaultValue: platformThemeMode == Brightness.dark);
return preferences.getBool(SharedPreferenceKey.appIsThemeDark.name, defaultValue: platformThemeMode == Brightness.dark);
}

@action
Expand All @@ -53,10 +51,8 @@ abstract class _SettingsStoreBase with Store {

final languageCode = languageToCodeMap[newLanguage];
locale = languageToLocaleMap[languageCode]!;
preferences
.setString(SharedPreferenceKey.appLanguage.name, languageToCodeMap[newLanguage]!)
.onError((e, stackTrace) {
logger.error("Can't write preference ${SharedPreferenceKey.appLanguage}", e, stackTrace: stackTrace);
preferences.setString(SharedPreferenceKey.appLanguage.name, languageToCodeMap[newLanguage]!).onError((e, stackTrace) {
logger.error("Can't write preference ${SharedPreferenceKey.appLanguage}", error: e, stackTrace: stackTrace);
return false;
});
}
Expand All @@ -66,7 +62,7 @@ abstract class _SettingsStoreBase with Store {
final bool newValue = !isDarkTheme;
isDarkTheme = newValue;
preferences.setBool(SharedPreferenceKey.appIsThemeDark.name, newValue).onError((e, stackTrace) {
logger.error("Can't write preference ${SharedPreferenceKey.appIsThemeDark}", e, stackTrace: stackTrace);
logger.error("Can't write preference ${SharedPreferenceKey.appIsThemeDark}", error: e, stackTrace: stackTrace);
return false;
});
}
Expand Down
2 changes: 1 addition & 1 deletion lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(const HangmanApp());
}, (error, stackTrace) {
LoggerService().error('unhandled error occured in root zone', error, stackTrace: stackTrace);
LoggerService().error('unhandled error occured in root zone', error: error, stackTrace: stackTrace);
});
}

Expand Down
8 changes: 3 additions & 5 deletions lib/route.generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Route? onGenerateRoute(RouteSettings settings) {
return MaterialPageRoute(builder: (_) => const GameWidget());

case '/categories':
return MaterialPageRoute(builder: (_) => CategoriesWidget());
return MaterialPageRoute(builder: (_) => const CategoriesWidget());

case '/onTheFlyChallenge':
return MaterialPageRoute(
Expand All @@ -31,15 +31,13 @@ Route? onGenerateRoute(RouteSettings settings) {
));

case '/settings':
return PageRouteBuilder(
pageBuilder: ((context, animation, secondaryAnimation) => const SettingsWidget()),
transitionDuration: const Duration(seconds: 2));
return PageRouteBuilder(pageBuilder: ((context, animation, secondaryAnimation) => const SettingsWidget()), transitionDuration: const Duration(seconds: 2));

case '/about':
return MaterialPageRoute(builder: (_) => const AboutWidget());

default:
logger.error('Invalid navigation', settings.name);
logger.error('Invalid navigation: ${settings.name}');
return MaterialPageRoute(builder: (_) => const GameWidget());
}
}
Expand Down
4 changes: 2 additions & 2 deletions lib/services/file/file.service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class FileService {
return getTemporaryDirectory();

default:
logger.error('unsupported type, returning temporary dir as default', directoryType);
logger.error('unsupported type $directoryType, returning temporary dir as default');
return getTemporaryDirectory();
}
}
Expand All @@ -45,7 +45,7 @@ class FileService {
final File file = File(fullFilenanme);

if (!file.existsSync()) {
logger.error('file does not exists', fullFilenanme);
logger.error('file does not exists: $fullFilenanme');
return '';
}

Expand Down
4 changes: 2 additions & 2 deletions lib/services/logger/logger.service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class LoggerService {
log(message, time: DateTime.now(), name: 'INFO');
}

void error(String message, dynamic error, {StackTrace? stackTrace}) {
log(message, time: DateTime.now(), name: 'ERROR', error: error.toString(), stackTrace: stackTrace);
void error(String message, {dynamic error, StackTrace? stackTrace}) {
log(message, time: DateTime.now(), name: 'ERROR', error: error?.toString(), stackTrace: stackTrace);
}
}
8 changes: 4 additions & 4 deletions lib/services/text.service/api.texts.service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class TextsService {

return ApiAbout.fromJson(data);
} catch (e) {
logger.error('About request failed', e);
logger.error('About request failed', error: e);
return const ApiAbout();
}
}
Expand Down Expand Up @@ -97,7 +97,7 @@ class TextsService {

return jsonContent;
} catch (e) {
logger.error('Request failed, will try to load from "$cacheFile", url: "$url"', e);
logger.error('Request failed, will try to load from "$cacheFile", url: "$url"', error: e);

String jsonContent = await loadFromCache(cacheFile);
if (jsonContent.isBlank) {
Expand All @@ -113,7 +113,7 @@ class TextsService {
}

const message = 'Request failed with status';
logger.error(message, response.statusCode);
logger.error('$message: ${response.statusCode}');
throw Exception('$message : ${response.statusCode}.');
}

Expand All @@ -136,7 +136,7 @@ class TextsService {

return jsonContent;
} catch (e) {
logger.error('\texception while reading $cacheFile', e);
logger.error('\texception while reading $cacheFile', error: e);
return '';
}
}
Expand Down
3 changes: 2 additions & 1 deletion lib/theme/widgets/app.error.widget.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';

import '/service.locator.dart';
import '/services/logger/logger.service.dart';

Expand All @@ -9,7 +10,7 @@ class AppErrorWidget extends StatelessWidget {

@override
Widget build(BuildContext context) {
serviceLocator.get<LoggerService>().error(details.exception.toString(), details.stack);
serviceLocator.get<LoggerService>().error(details.exception.toString(), stackTrace: details.stack);

return const Material(
child: Center(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,45 +1,23 @@
import 'package:flutter/material.dart';

import 'navigation.choices.model.dart';
import 'navigation.landscape.layout.dart';
import 'navigation.portrait.layout.widget.dart';

class ResponsiveNavigationRailOrBar extends StatefulWidget {
class ResponsiveNavigationRailOrBar extends StatelessWidget {
final List<NavigationChoices> items;
final int currentIndex;
final Widget Function(int index) childBuilder;

const ResponsiveNavigationRailOrBar(
{Key? key, required this.items, required this.childBuilder, this.currentIndex = 0})
: super(key: key);

@override
State<ResponsiveNavigationRailOrBar> createState() => _ResponsiveNavigationRailOrBarState();
}

class _ResponsiveNavigationRailOrBarState extends State<ResponsiveNavigationRailOrBar> {
late int currentIndex;
late Widget child;

@override
void initState() {
super.initState();
currentIndex = widget.currentIndex;
child = widget.childBuilder(widget.currentIndex);
}
final Widget child;
final Function(int index) onTap;

void onTap(int index) {
setState(() {
currentIndex = index;
child = widget.childBuilder(index);
});
}
const ResponsiveNavigationRailOrBar({Key? key, required this.items, required this.currentIndex, required this.child, required this.onTap}) : super(key: key);

@override
Widget build(BuildContext context) => OrientationBuilder(
builder: (context, orientation) {
return orientation == Orientation.portrait
? NavigationPortraitLayout(items: widget.items, currentIndex: currentIndex, onTap: onTap, child: child)
: NavigationLandscapeLayout(items: widget.items, currentIndex: currentIndex, onTap: onTap, child: child);
? NavigationPortraitLayout(items: items, currentIndex: currentIndex, onTap: onTap, child: child)
: NavigationLandscapeLayout(items: items, currentIndex: currentIndex, onTap: onTap, child: child);
},
);
}
17 changes: 5 additions & 12 deletions test/features/categories/categories.screen_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,7 @@ void main() {
GetIt.I.reset(),
});

final mockCategories = [
mockAnimalsCategory,
mockTransportsCategory,
mockColorsCategory,
mockUSStatesCategory,
mockPlanetsCategory,
mockCountriesCategory
];
final mockCategories = [mockAnimalsCategory, mockTransportsCategory, mockColorsCategory, mockUSStatesCategory, mockPlanetsCategory, mockCountriesCategory];
final spinnerFinder = find.byKey(const Key('categories_loading'));

Widget wrapper(Widget widget) => MaterialApp(
Expand All @@ -62,7 +55,7 @@ void main() {
when(mockTextsService.getCategories()).thenAnswer((_) => Future.value(mockCategories));

// when
await tester.pumpWidget(wrapper(CategoriesWidget()));
await tester.pumpWidget(wrapper(const CategoriesWidget()));

// then
expect(spinnerFinder, findsOneWidget);
Expand All @@ -79,7 +72,7 @@ void main() {
when(mockTextsService.getCategories()).thenAnswer((_) => Future.value(mockCategories));

// when
await tester.pumpWidget(wrapper(CategoriesWidget()));
await tester.pumpWidget(wrapper(const CategoriesWidget()));
await tester.pump(); // wait for the spinner to disappear

// then
Expand All @@ -97,7 +90,7 @@ void main() {
when(mockGameStore.selectCategory(any)).thenAnswer((_) => Future.value(null));

// asserts before acting
await tester.pumpWidget(wrapper(CategoriesWidget()));
await tester.pumpWidget(wrapper(const CategoriesWidget()));
await tester.pump(); // wait for the spinner to disappear

// when
Expand All @@ -117,7 +110,7 @@ void main() {
reset(mockNavigatorObserver);

// asserts before acting
await tester.pumpWidget(wrapper(CategoriesWidget()));
await tester.pumpWidget(wrapper(const CategoriesWidget()));
await tester.pump(); // wait for the spinner to disappear

// when
Expand Down

0 comments on commit 8f39783

Please sign in to comment.