diff --git a/client/ios/Runner.xcodeproj/project.pbxproj b/client/ios/Runner.xcodeproj/project.pbxproj index bffaa903..d223de01 100644 --- a/client/ios/Runner.xcodeproj/project.pbxproj +++ b/client/ios/Runner.xcodeproj/project.pbxproj @@ -536,4 +536,4 @@ /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; -} \ No newline at end of file +} diff --git a/client/lib/main.dart b/client/lib/main.dart index e8ec8797..92d92384 100644 --- a/client/lib/main.dart +++ b/client/lib/main.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:pr12er/service.dart'; +import 'package:pr12er/sort_preference.dart'; import 'package:pr12er/view_models/view_model_videos.dart'; import 'package:provider/provider.dart'; @@ -18,7 +19,10 @@ void main() => runApp(MultiProvider(providers: [ ), ChangeNotifierProvider( create: (context) => FavoriteVideoViewModel(), - ) + ), + ChangeNotifierProvider( + create: (context) => SortMode(), + ), ], child: const MainApp())); class MainApp extends StatelessWidget { diff --git a/client/lib/screens/main_screen.dart b/client/lib/screens/main_screen.dart index 6120ca41..fd646730 100644 --- a/client/lib/screens/main_screen.dart +++ b/client/lib/screens/main_screen.dart @@ -4,6 +4,7 @@ import 'package:provider/provider.dart'; import '../protos/pkg/pr12er/messages.pb.dart'; import '../service.dart'; +import '../sort_preference.dart'; import '../widgets/components/custom_app_bar.dart'; import '../widgets/components/custom_bottom_navigation_bar.dart'; import '../widgets/main/main_screen_favorite_view.dart'; @@ -37,25 +38,33 @@ class _MainScreenState extends State { .where((video) => video.hasTitle() && video.hasLink()) .toList(); - return Scaffold( - appBar: CustomAppBar( - videoSearchDelegate: videoSearchDelegate, - context: context, - title: appName, - ), - body: IndexedStack( - index: _selectedBottomNavIndex, - children: [ - MainScreenListView(cleanList: cleanList), - MainScreenFavoriteView(cleanList: cleanList) - ], - ), - bottomNavigationBar: CustomBottomNavigationBar( - selectedBottomNavIndex: _selectedBottomNavIndex, - onTap: (index) => setState(() { - _selectedBottomNavIndex = index; - videoSearchDelegate.showOnlyBookmarkItems = index == 1; - }))); + return Consumer( + builder: (context, sort, _child) => Scaffold( + appBar: CustomAppBar( + videoSearchDelegate: videoSearchDelegate, + context: context, + title: appName, + ), + body: IndexedStack( + index: _selectedBottomNavIndex, + children: [ + MainScreenListView( + cleanList: sort.isDescOrder + ? cleanList + : List.from(cleanList.reversed)), + MainScreenFavoriteView( + cleanList: sort.isDescOrder + ? cleanList + : List.from(cleanList.reversed)) + ], + ), + bottomNavigationBar: CustomBottomNavigationBar( + selectedBottomNavIndex: _selectedBottomNavIndex, + onTap: (index) => setState(() { + _selectedBottomNavIndex = index; + videoSearchDelegate.showOnlyBookmarkItems = + index == 1; + })))); }); } } diff --git a/client/lib/sort_preference.dart b/client/lib/sort_preference.dart new file mode 100644 index 00000000..5714729a --- /dev/null +++ b/client/lib/sort_preference.dart @@ -0,0 +1,43 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +const _sharedPreferenceSortModeKey = "isDescOrder"; + +class SortMode extends ChangeNotifier { + bool _isDescOrder = false; + + bool get isDescOrder { + return _isDescOrder; + } + + /// sharedPreferences is only visible for testing. + /// It is used to test isDarkMode is being loaded from SharedPreferences. + @visibleForTesting + Future get sharedPreferences => + SharedPreferences.getInstance(); + + void toggleMode() { + _isDescOrder = !_isDescOrder; + SharedPreferences.getInstance().then( + (pref) => pref.setBool(_sharedPreferenceSortModeKey, _isDescOrder)); + notifyListeners(); + } + + // This type should not be a widget(e.g. Icon) because of mockito support... + IconData get icon => + _isDescOrder ? Icons.vertical_align_top : Icons.vertical_align_bottom; + + // This type should not be a widget(e.g. Text) because of mockito support... + String get text => _isDescOrder ? "오름차순으로" : "내림차순으로"; + + SortMode() { + SharedPreferences.getInstance().then((pref) { + final isDescOrder = pref.getBool(_sharedPreferenceSortModeKey); + if (isDescOrder != null) { + _isDescOrder = isDescOrder; + notifyListeners(); + } + }); + } +} diff --git a/client/lib/widgets/components/custom_app_bar.dart b/client/lib/widgets/components/custom_app_bar.dart index 56283400..e72e22ed 100644 --- a/client/lib/widgets/components/custom_app_bar.dart +++ b/client/lib/widgets/components/custom_app_bar.dart @@ -1,11 +1,12 @@ import 'package:flutter/material.dart'; import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; import 'package:pr12er/custom_theme.dart'; +import 'package:pr12er/sort_preference.dart'; import 'package:pr12er/widgets/main/report.dart'; import 'package:pr12er/widgets/main/video_search_delegate.dart'; import 'package:provider/provider.dart'; -enum VertMenu { themeMode, issueReport } +enum VertMenu { themeMode, sortMode, issueReport } class CustomAppBar extends AppBar { CustomAppBar({ @@ -36,6 +37,13 @@ class CustomAppBar extends AppBar { leading: Icon(context.read().icon), title: Text(context.read().text))), const PopupMenuDivider(height: 5), + PopupMenuItem( + key: const ValueKey("icon-sort-toggle-button"), + value: VertMenu.sortMode, + child: ListTile( + leading: Icon(context.read().icon), + title: Text(context.read().text))), + const PopupMenuDivider(height: 5), const PopupMenuItem( key: ValueKey("popup-menu-item-issue-report"), value: VertMenu.issueReport, @@ -50,6 +58,9 @@ class CustomAppBar extends AppBar { case VertMenu.themeMode: context.read().toggleMode(); break; + case VertMenu.sortMode: + context.read().toggleMode(); + break; case VertMenu.issueReport: showMaterialModalBottomSheet( context: context, diff --git a/client/test/screens/main_screen_with_mocks_test.dart b/client/test/screens/main_screen_with_mocks_test.dart index 6e3b1337..f0f021c9 100644 --- a/client/test/screens/main_screen_with_mocks_test.dart +++ b/client/test/screens/main_screen_with_mocks_test.dart @@ -6,6 +6,7 @@ import 'package:pr12er/custom_theme.dart'; import 'package:pr12er/protos/pkg/pr12er/messages.pb.dart'; import 'package:pr12er/screens/main_screen.dart'; import 'package:pr12er/service.dart'; +import 'package:pr12er/sort_preference.dart'; import 'package:pr12er/view_models/view_model_videos.dart'; import 'package:provider/provider.dart'; @@ -112,6 +113,9 @@ MultiProvider wrapWithProviders( ChangeNotifierProvider(create: (context) => mockCustomTheme), ChangeNotifierProvider( create: (context) => mockFavoriteVideoViewModel), + ChangeNotifierProvider( + create: (context) => SortMode(), + ) ], builder: (context, child) => MaterialApp(home: MainScreen()), );