From 4dada26306c9e87ae3e593a1e18f0d7bbbb19d40 Mon Sep 17 00:00:00 2001 From: Yudi Setiawan Date: Sat, 30 May 2020 13:48:14 +0700 Subject: [PATCH] Create dark mode support --- lib/app.dart | 19 ++- .../presentation/page/home/home_page.dart | 147 +++++++++++------- .../presentation/page/search/search_page.dart | 6 +- .../page/settings/settings_page.dart | 96 ++++++++++++ lib/injection_container.dart | 3 +- lib/main_development.dart | 4 + lib/main_production.dart | 4 + pubspec.lock | 42 ++--- pubspec.yaml | 7 +- 9 files changed, 232 insertions(+), 96 deletions(-) create mode 100644 lib/feature/presentation/page/settings/settings_page.dart diff --git a/lib/app.dart b/lib/app.dart index ca4259a..41b272f 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -1,13 +1,24 @@ import 'package:flutter/material.dart'; import 'package:flutter_news_app/feature/presentation/page/home/home_page.dart'; +import 'package:hive/hive.dart'; +import 'package:hive_flutter/hive_flutter.dart'; class App extends StatelessWidget { @override Widget build(BuildContext context) { - return MaterialApp( - debugShowCheckedModeBanner: false, - title: 'News App', - home: HomePage(), + return ValueListenableBuilder( + valueListenable: Hive.box('settings').listenable(), + builder: (context, box, widget) { + var isDarkMode = box.get('darkMode') ?? false; + return MaterialApp( + debugShowCheckedModeBanner: false, + title: 'News App', + theme: ThemeData( + brightness: isDarkMode ? Brightness.dark : Brightness.light, + ), + home: HomePage(), + ); + }, ); } } diff --git a/lib/feature/presentation/page/home/home_page.dart b/lib/feature/presentation/page/home/home_page.dart index eea2dca..66be925 100644 --- a/lib/feature/presentation/page/home/home_page.dart +++ b/lib/feature/presentation/page/home/home_page.dart @@ -8,10 +8,13 @@ import 'package:flutter_news_app/feature/data/model/categorynews/category_news_m import 'package:flutter_news_app/feature/data/model/topheadlinesnews/top_headlines_news_response_model.dart'; import 'package:flutter_news_app/feature/presentation/bloc/topheadlinesnews/bloc.dart'; import 'package:flutter_news_app/feature/presentation/page/search/search_page.dart'; +import 'package:flutter_news_app/feature/presentation/page/settings/settings_page.dart'; import 'package:flutter_news_app/feature/presentation/widget/widget_failure_message.dart'; import 'package:flutter_news_app/feature/presentation/widget/widget_item_news.dart'; import 'package:flutter_news_app/injection_container.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:hive/hive.dart'; +import 'package:hive_flutter/hive_flutter.dart'; import 'package:intl/intl.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -76,62 +79,87 @@ class _HomePageState extends State { } } }, - child: Stack( - children: [ - Container( - width: double.infinity, - height: double.infinity, - color: Color(0xFFEFF5F5), - ), - SafeArea( - child: Container( - width: double.infinity, - color: Color(0xFFEFF5F5), - padding: EdgeInsets.symmetric( - vertical: 24.h, + child: ValueListenableBuilder( + valueListenable: Hive.box('settings').listenable(), + builder: (context, box, widget) { + var isDarkMode = box.get('darkMode') ?? false; + return Stack( + children: [ + Container( + width: double.infinity, + height: double.infinity, + color: isDarkMode ? null : Color(0xFFEFF5F5), ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: EdgeInsets.symmetric(horizontal: 48.w), - child: Row( - children: [ - Expanded( - child: Text( - 'Daily News', - style: TextStyle( - fontSize: 48.sp, + SafeArea( + child: Container( + width: double.infinity, + color: isDarkMode ? null : Color(0xFFEFF5F5), + padding: EdgeInsets.symmetric( + vertical: 24.h, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.symmetric(horizontal: 48.w), + child: Row( + children: [ + Expanded( + child: Text( + 'Daily News', + style: TextStyle( + fontSize: 48.sp, + ), + ), ), - ), - ), - GestureDetector( - onTap: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => SearchPage()), - ); - }, - child: Hero( - tag: 'iconSearch', - child: Icon(Icons.search), - ), + GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => SearchPage()), + ); + }, + child: Hero( + tag: 'iconSearch', + child: Icon( + Icons.search, + size: 64.w, + ), + ), + ), + SizedBox(width: 48.w), + GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => SettingsPage(), + ), + ); + }, + child: Icon( + Icons.settings, + size: 64.w, + ), + ), + ], ), - ], - ), - ), - WidgetDateToday(), - SizedBox(height: 24.h), - WidgetCategoryNews(listCategories: listCategories), - SizedBox(height: 24.h), - Expanded( - child: Platform.isIOS ? _buildWidgetContentNewsIOS() : _buildWidgetContentNewsAndroid(), + ), + WidgetDateToday(), + SizedBox(height: 24.h), + WidgetCategoryNews( + listCategories: listCategories, indexDefaultSelected: indexCategorySelected), + SizedBox(height: 24.h), + Expanded( + child: Platform.isIOS ? _buildWidgetContentNewsIOS() : _buildWidgetContentNewsAndroid(), + ), + ], ), - ], + ), ), - ), - ), - ], + ], + ); + }, ), ), ), @@ -404,8 +432,12 @@ class _HomePageState extends State { class WidgetCategoryNews extends StatefulWidget { final List listCategories; + final int indexDefaultSelected; - WidgetCategoryNews({@required this.listCategories}); + WidgetCategoryNews({ + @required this.listCategories, + @required this.indexDefaultSelected, + }); @override _WidgetCategoryNewsState createState() => _WidgetCategoryNewsState(); @@ -416,7 +448,7 @@ class _WidgetCategoryNewsState extends State { @override void initState() { - indexCategorySelected = 0; + indexCategorySelected = widget.indexDefaultSelected; super.initState(); } @@ -439,12 +471,11 @@ class _WidgetCategoryNewsState extends State { if (indexCategorySelected == index) { return; } - setState(() { - indexCategorySelected = index; - }); + setState(() => indexCategorySelected = index); var topHeadlinesNewsBloc = BlocProvider.of(context); - topHeadlinesNewsBloc - .add(ChangeCategoryTopHeadlinesNewsEvent(indexCategorySelected: indexCategorySelected)); + topHeadlinesNewsBloc.add( + ChangeCategoryTopHeadlinesNewsEvent(indexCategorySelected: index), + ); }, child: Container( child: AnimatedContainer( diff --git a/lib/feature/presentation/page/search/search_page.dart b/lib/feature/presentation/page/search/search_page.dart index 7c7a2ff..3b180a4 100644 --- a/lib/feature/presentation/page/search/search_page.dart +++ b/lib/feature/presentation/page/search/search_page.dart @@ -42,6 +42,8 @@ class _SearchPageState extends State { @override Widget build(BuildContext context) { ScreenUtil.init(context); + var theme = Theme.of(context); + var isDarkTheme = theme.brightness == Brightness.dark; return Scaffold( body: BlocProvider( create: (context) => topHeadlinesNewsBloc, @@ -50,11 +52,11 @@ class _SearchPageState extends State { Container( width: double.infinity, height: double.infinity, - color: Color(0xFFEFF5F5), + color: isDarkTheme ? null : Color(0xFFEFF5F5), ), SafeArea( child: Container( - color: Color(0xFFEFF5F5), + color: isDarkTheme ? null : Color(0xFFEFF5F5), width: double.infinity, padding: EdgeInsets.symmetric( vertical: 24.h, diff --git a/lib/feature/presentation/page/settings/settings_page.dart b/lib/feature/presentation/page/settings/settings_page.dart new file mode 100644 index 0000000..36a9da9 --- /dev/null +++ b/lib/feature/presentation/page/settings/settings_page.dart @@ -0,0 +1,96 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:hive/hive.dart'; +import 'package:hive_flutter/hive_flutter.dart'; + +class SettingsPage extends StatefulWidget { + @override + _SettingsPageState createState() => _SettingsPageState(); +} + +class _SettingsPageState extends State { + @override + Widget build(BuildContext context) { + ScreenUtil.init(context); + return Scaffold( + appBar: WidgetAppBar(), + body: Container( + width: double.infinity, + padding: EdgeInsets.all(48.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Interface', + style: TextStyle( + fontSize: 48.sp, + fontWeight: FontWeight.bold, + ), + ), + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Use dark mode', + style: TextStyle( + fontSize: 42.sp, + ), + ), + Text( + 'Get that whiteness out', + style: TextStyle( + fontSize: 36.sp, + color: Colors.grey, + ), + ), + ], + ), + ), + ValueListenableBuilder( + valueListenable: Hive.box('settings').listenable(), + builder: (context, box, widget) { + var isDarkMode = box.get('darkMode') ?? false; + return Switch( + value: isDarkMode, + onChanged: (value) async { + isDarkMode = value; + await box.put('darkMode', isDarkMode); + }, + ); + }, + ), + ], + ), + ], + ), + ), + ); + } +} + +class WidgetAppBar extends PreferredSize { + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: Hive.box('settings').listenable(), + builder: (context, box, widget) { + var isDarkMode = box.get('darkMode') ?? false; + return isDarkMode + ? AppBar( + title: Text('Settings'), + ) + : AppBar( + title: Text('Settings'), + ); + }, + ); + } + + @override + Size get preferredSize { + return Size.fromHeight(kToolbarHeight); + } +} diff --git a/lib/injection_container.dart b/lib/injection_container.dart index 1ba3bb0..a49ae15 100644 --- a/lib/injection_container.dart +++ b/lib/injection_container.dart @@ -9,10 +9,9 @@ import 'package:flutter_news_app/feature/data/repository/news/news_repository_im import 'package:flutter_news_app/feature/domain/repository/news/news_repository.dart'; import 'package:flutter_news_app/feature/domain/usecase/gettopheadlinesnews/get_top_headlines_news.dart'; import 'package:flutter_news_app/feature/domain/usecase/searchtopheadlinesnews/search_top_headlines_news.dart'; +import 'package:flutter_news_app/feature/presentation/bloc/topheadlinesnews/bloc.dart'; import 'package:get_it/get_it.dart'; -import 'feature/presentation/bloc/topheadlinesnews/bloc.dart'; - final sl = GetIt.instance; Future init() async { diff --git a/lib/main_development.dart b/lib/main_development.dart index 24d0f7d..93e6a6c 100644 --- a/lib/main_development.dart +++ b/lib/main_development.dart @@ -3,9 +3,13 @@ import 'package:flutter_news_app/app.dart'; import 'package:flutter_news_app/config/base_url_config.dart'; import 'package:flutter_news_app/config/flavor_config.dart'; import 'package:flutter_news_app/injection_container.dart' as di; +import 'package:hive/hive.dart'; +import 'package:hive_flutter/hive_flutter.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); + await Hive.initFlutter(); + await Hive.openBox('settings'); FlavorConfig( flavor: Flavor.DEVELOPMENT, values: FlavorValues(baseUrl: BaseUrlConfig().baseUrlDevelopment), diff --git a/lib/main_production.dart b/lib/main_production.dart index 8a29ae9..15701e4 100644 --- a/lib/main_production.dart +++ b/lib/main_production.dart @@ -3,9 +3,13 @@ import 'package:flutter_news_app/app.dart'; import 'package:flutter_news_app/config/base_url_config.dart'; import 'package:flutter_news_app/config/flavor_config.dart'; import 'package:flutter_news_app/injection_container.dart' as di; +import 'package:hive/hive.dart'; +import 'package:hive_flutter/hive_flutter.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); + await Hive.initFlutter(); + await Hive.openBox('settings'); FlavorConfig( flavor: Flavor.PRODUCTION, values: FlavorValues(baseUrl: BaseUrlConfig().baseUrlProduction), diff --git a/pubspec.lock b/pubspec.lock index 2de9525..fd6bfe5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -310,6 +310,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.2.0" + hive: + dependency: "direct main" + description: + name: hive + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.1+1" + hive_flutter: + dependency: "direct main" + description: + name: hive_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.0+2" html: dependency: transitive description: @@ -569,34 +583,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.23.1" - shared_preferences: - dependency: "direct main" - description: - name: shared_preferences - url: "https://pub.dartlang.org" - source: hosted - version: "0.5.7+3" - shared_preferences_macos: - dependency: transitive - description: - name: shared_preferences_macos - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.1+9" - shared_preferences_platform_interface: - dependency: transitive - description: - name: shared_preferences_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.4" - shared_preferences_web: - dependency: transitive - description: - name: shared_preferences_web - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.2+7" shelf: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 52450c7..b64ab1e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -61,8 +61,11 @@ dependencies: # Vector Graphics 1.1 files. flutter_svg: ^0.17.4 - # Flutter plugin for reading and writing simple key-value pairs. - shared_preferences: ^0.5.7+3 + # Lightweight and blazing fast key-value database written in pure Dart. + hive: ^1.4.1+1 + + # Extension for Hive. Make it easier to use Hive in Flutter apps. + hive_flutter: ^0.3.0+2 dev_dependencies: flutter_test: