From 6ee890a95164d87087f521b5272e791239afd008 Mon Sep 17 00:00:00 2001 From: netharu methmitha Date: Sun, 17 Apr 2022 17:50:37 +0530 Subject: [PATCH] added light and dark theme and also report a bug option --- lib/main.dart | 36 ++-- lib/pages/settings_page.dart | 305 ++++++++++++++++++------------- lib/pages/statuses_page.dart | 3 +- lib/pages/video_timmer.dart | 59 +++--- lib/provider/theme_provider.dart | 136 ++++++++++++++ lib/widgets/status_card.dart | 62 ++++--- pubspec.lock | 70 +++++++ pubspec.yaml | 4 +- 8 files changed, 469 insertions(+), 206 deletions(-) create mode 100644 lib/provider/theme_provider.dart diff --git a/lib/main.dart b/lib/main.dart index 0b5e7ae..ee5c55d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:whatskit/pages/settings_page.dart'; import 'package:whatskit/pages/statuses_page.dart'; import 'package:whatskit/pages/video_timmer.dart'; +import 'package:whatskit/provider/theme_provider.dart'; +import 'package:provider/provider.dart'; void main() { runApp(const MyApp()); @@ -12,26 +14,18 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - primarySwatch: generateMaterialColorFromColor(const Color(0xff00a884)), - scaffoldBackgroundColor: const Color(0xff111b21), - backgroundColor: const Color(0xff111b21), - cardColor: const Color(0xff202c33), - primaryColor: const Color(0xff00a884), - textTheme: Theme.of(context).textTheme.apply(bodyColor: Colors.white), - iconTheme: Theme.of(context) - .iconTheme - .copyWith(color: const Color(0xff00a884)), - bottomNavigationBarTheme: BottomNavigationBarThemeData( - backgroundColor: const Color(0xff202c33), - selectedItemColor: const Color(0xff00a884), - unselectedItemColor: Colors.white.withOpacity(0.5), - ), - dividerColor: Colors.white, - ), - home: const App(), + return ChangeNotifierProvider( + create: (_) => ThemeProvider(), + builder: (context, _) { + final ThemeProvider themeProvider = Provider.of(context); + return MaterialApp( + title: 'Flutter Demo', + themeMode: themeProvider.getThemeMode, + theme: AppThemes.lightTheme, + darkTheme: AppThemes.darkTheme, + home: const App(), + ); + }, ); } } @@ -51,7 +45,7 @@ class _AppState extends State { child: Scaffold( bottomNavigationBar: Container( decoration: BoxDecoration( - color: Theme.of(context).cardColor, + color: Theme.of(context).backgroundColor, ), child: TabBar( indicatorColor: Theme.of(context).primaryColor, diff --git a/lib/pages/settings_page.dart b/lib/pages/settings_page.dart index c797a04..8c82bca 100644 --- a/lib/pages/settings_page.dart +++ b/lib/pages/settings_page.dart @@ -1,7 +1,9 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:package_info_plus/package_info_plus.dart'; +import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:whatskit/provider/theme_provider.dart'; class SettingsPage extends StatefulWidget { const SettingsPage({Key? key}) : super(key: key); @@ -13,72 +15,100 @@ class SettingsPage extends StatefulWidget { class _SettingsPageState extends State { @override Widget build(BuildContext context) { + final ThemeProvider themeProvider = Provider.of(context); return SafeArea( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: ListView( - children: [ - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - color: Theme.of(context).cardColor, - ), - child: Column( - children: [ - Container( - margin: const EdgeInsets.all(12), - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: Theme.of(context).primaryColor, - borderRadius: BorderRadius.circular(40), + child: ListView( + children: [ + Container( + margin: const EdgeInsets.all(12), + child: Material( + color: Colors.transparent, + borderRadius: BorderRadius.circular(12), + elevation: 5, + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Theme.of(context).cardColor, + ), + child: Column( + children: [ + Container( + margin: const EdgeInsets.all(12), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Theme.of(context).primaryColor, + borderRadius: BorderRadius.circular(40), + ), + child: const Icon( + Icons.settings, + size: 85, + color: Colors.white, + ), ), - child: const Icon( - Icons.settings, - size: 85, - color: Colors.white, + const SizedBox(height: 4), + const Text( + 'Settings', + style: TextStyle(fontSize: 24), ), - ), - const SizedBox(height: 4), - const Text( - 'Settings', - style: TextStyle(fontSize: 24), - ), - ], + ], + ), ), ), - const Divider(), - SettingsOption( - title: 'Clear rendered cache', - subtitle: - 'clears the rendered videos cache \npls dont clear it while sharing a video', - iconColor: Colors.pink, - icon: Icons.cleaning_services_rounded, - onTap: () async { - var renderedCacheDir = Directory( - '/storage/emulated/0/Android/data/com.netharuM.whatskit/files/Trimmer'); - if (await renderedCacheDir.exists()) { - await renderedCacheDir.delete(recursive: true); - } - }, - ), - const Divider(), - SettingsOption( - title: 'About', - subtitle: 'about the app', - iconColor: Colors.blue, - icon: Icons.info_outline, - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const AboutPage(), - ), - ); - }, - ), - ], - ), + ), + const Divider(), + SettingsOption( + title: 'Clear rendered cache', + subtitle: + 'clears the rendered videos cache \npls dont clear it while sharing a video', + iconColor: Colors.pink, + icon: Icons.cleaning_services_rounded, + onTap: () async { + var renderedCacheDir = Directory( + '/storage/emulated/0/Android/data/com.netharuM.whatskit/files/Trimmer'); + if (await renderedCacheDir.exists()) { + await renderedCacheDir.delete(recursive: true); + } + }, + ), + SettingsOption( + title: 'Theme', + subtitle: themeProvider.toString(), + icon: themeProvider.getThemeMode == ThemeMode.system + ? Icons.android + : (themeProvider.getThemeMode == ThemeMode.dark + ? Icons.dark_mode + : Icons.light_mode), + onTap: () { + switch (themeProvider.getThemeMode) { + case ThemeMode.dark: + themeProvider.changeThemeMode(ThemeMode.light); + break; + case ThemeMode.light: + themeProvider.changeThemeMode(ThemeMode.system); + break; + case ThemeMode.system: + themeProvider.changeThemeMode(ThemeMode.dark); + break; + } + }, + ), + const Divider(), + SettingsOption( + title: 'About', + subtitle: 'about the app', + iconColor: Colors.blue, + icon: Icons.info_outline, + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const AboutPage(), + ), + ); + }, + ), + ], ), ); } @@ -90,54 +120,57 @@ class SettingsOption extends StatelessWidget { final Color? iconColor; final IconData? icon; final VoidCallback? onTap; + final EdgeInsets? padding; + const SettingsOption( {Key? key, required this.title, required this.subtitle, this.iconColor, this.onTap, - this.icon}) + this.icon, + this.padding}) : super(key: key); @override Widget build(BuildContext context) { - return InkWell( - onTap: onTap, - borderRadius: BorderRadius.circular(12), - child: Container( - padding: const EdgeInsets.all(12), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Flexible( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, + return Padding( + padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0), + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(12), + child: Container( + padding: const EdgeInsets.all(12), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: Theme.of(context).textTheme.titleMedium, ), - ), - const SizedBox(height: 4), - Text( - subtitle, - style: TextStyle(color: Colors.white.withOpacity(0.8)), - ), - ], - ), - ), - Visibility( - visible: icon != null, - child: Icon( - icon, - size: 30, - color: iconColor, + const SizedBox(height: 4), + Text( + subtitle, + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ), ), - ) - ], + Visibility( + visible: icon != null, + child: Icon( + icon, + size: 30, + color: iconColor, + ), + ) + ], + ), ), ), ); @@ -176,31 +209,38 @@ class _AboutPageState extends State { children: [ Container( margin: const EdgeInsets.all(12), - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - color: Theme.of(context).cardColor, - ), - child: Column( - children: [ - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: Theme.of(context).primaryColor, - borderRadius: BorderRadius.circular(40), - ), - child: const Icon( - Icons.info, - size: 85, - color: Colors.white, - ), + child: Material( + color: Colors.transparent, + borderRadius: BorderRadius.circular(12), + elevation: 5, + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Theme.of(context).cardColor, ), - const SizedBox(height: 4), - const Text( - 'About', - style: TextStyle(fontSize: 24), + child: Column( + children: [ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Theme.of(context).primaryColor, + borderRadius: BorderRadius.circular(40), + ), + child: const Icon( + Icons.info, + size: 85, + color: Colors.white, + ), + ), + const SizedBox(height: 4), + const Text( + 'About', + style: TextStyle(fontSize: 24), + ), + ], ), - ], + ), ), ), const Divider(), @@ -273,6 +313,20 @@ class _AboutPageState extends State { } }, ), + AboutCard( + title: 'Report a bug', + subtitle: + 'report an issue you found on this app \nor suggest a new feature', + icon: const Icon(Icons.bug_report_rounded), + onTap: () async { + const url = 'https://github.com/netharuM/whatskit/issues/new'; + if (await canLaunch(url)) { + await launch(url); + } else { + throw 'Could not launch $url'; + } + }, + ), ], ), ); @@ -311,17 +365,12 @@ class AboutCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - title ?? '', - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - ), - ), + Text(title ?? '', + style: Theme.of(context).textTheme.titleMedium), const SizedBox(height: 4), Text( subtitle ?? '', - style: TextStyle(color: Colors.white.withOpacity(0.8)), + style: Theme.of(context).textTheme.bodyMedium, ), ], ), diff --git a/lib/pages/statuses_page.dart b/lib/pages/statuses_page.dart index b7b81a3..bcc4703 100644 --- a/lib/pages/statuses_page.dart +++ b/lib/pages/statuses_page.dart @@ -44,7 +44,7 @@ class _StatusesPageState extends State { Widget build(BuildContext context) { return Scaffold( body: RefreshIndicator( - backgroundColor: Theme.of(context).cardColor, + backgroundColor: Theme.of(context).scaffoldBackgroundColor, onRefresh: _init, child: GridView.count( crossAxisCount: 2, @@ -53,6 +53,7 @@ class _StatusesPageState extends State { Padding( padding: const EdgeInsets.all(8.0), child: WhatsappStatusCard( + elevation: 10, file: _statusFiles[i], ), ), diff --git a/lib/pages/video_timmer.dart b/lib/pages/video_timmer.dart index 2637573..26a6174 100644 --- a/lib/pages/video_timmer.dart +++ b/lib/pages/video_timmer.dart @@ -49,9 +49,7 @@ class _VideoTrimmerPageState extends State { ), Text( 'Tap to select a video', - style: TextStyle( - color: Colors.white.withOpacity(0.5), - ), + style: Theme.of(context).textTheme.labelLarge, ), ], ), @@ -274,32 +272,39 @@ class TrimmedVidCard extends StatelessWidget { @override Widget build(BuildContext context) { - return GestureDetector( - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => TrimmedVideoPage( - start: start, - end: end, - video: video, + return Container( + margin: const EdgeInsets.all(8), + child: Material( + color: Colors.transparent, + elevation: 5, + borderRadius: BorderRadius.circular(12), + child: GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => TrimmedVideoPage( + start: start, + end: end, + video: video, + ), + ), + ); + }, + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Theme.of(context).cardColor, + borderRadius: BorderRadius.circular(12), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Icon(Icons.video_collection_sharp), + Text('${start.inSeconds}s - ${end.inSeconds}s'), + ], ), ), - ); - }, - child: Container( - padding: const EdgeInsets.all(12), - margin: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: Theme.of(context).cardColor, - borderRadius: BorderRadius.circular(12), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Icon(Icons.video_collection_sharp), - Text('${start.inSeconds}s - ${end.inSeconds}s'), - ], ), ), ); diff --git a/lib/provider/theme_provider.dart b/lib/provider/theme_provider.dart new file mode 100644 index 0000000..dc80cda --- /dev/null +++ b/lib/provider/theme_provider.dart @@ -0,0 +1,136 @@ +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class ThemeProvider extends ChangeNotifier { + ThemeProvider() { + _init(); + } + + ThemeMode themeMode = ThemeMode.dark; + late final SharedPreferences _prefs; + + void _init() async { + _prefs = await SharedPreferences.getInstance(); + String? _themeMode = _prefs.getString('themeMode'); + if (_themeMode != null) { + fromString(_themeMode); + } else { + _prefs.setString('themeMode', toString()); + } + } + + void fromString(String themeModeStr) { + assert( + themeModeStr == 'system' || + themeModeStr == 'light' || + themeModeStr == 'dark', + 'themeModeStr must be system, light or dark', + ); + switch (themeModeStr) { + case 'system': + themeMode = ThemeMode.system; + break; + case 'light': + themeMode = ThemeMode.light; + break; + case 'dark': + themeMode = ThemeMode.dark; + break; + } + _prefs.setString('themeMode', themeModeStr); + notifyListeners(); + } + + @override + String toString() { + switch (themeMode) { + case ThemeMode.system: + return 'system'; + case ThemeMode.light: + return 'light'; + case ThemeMode.dark: + return 'dark'; + } + } + + ThemeMode get getThemeMode => themeMode; + void changeThemeMode(ThemeMode theme) { + themeMode = theme; + _prefs.setString('themeMode', toString()); + notifyListeners(); + } +} + +class AppThemes { + static final lightTheme = ThemeData( + primarySwatch: generateMaterialColorFromColor(const Color(0xff008069)), + scaffoldBackgroundColor: Colors.white, + backgroundColor: const Color(0xff008069), + cardColor: Colors.white, + primaryColor: const Color(0xff008069), + tabBarTheme: TabBarTheme( + labelColor: Colors.white, + unselectedLabelColor: Colors.white.withOpacity(0.5), + indicator: const BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.white, + width: 2, + ), + ), + ), + ), + textTheme: TextTheme( + titleMedium: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + bodyMedium: TextStyle(color: Colors.black.withOpacity(0.8)), + labelLarge: TextStyle(color: Colors.black.withOpacity(0.5)), + ), + iconTheme: const IconThemeData( + color: Color(0xff008069), + ), + appBarTheme: const AppBarTheme( + foregroundColor: Colors.black, + ), + dividerColor: Colors.grey, + ); + + static final darkTheme = ThemeData( + primarySwatch: generateMaterialColorFromColor(const Color(0xff00a884)), + scaffoldBackgroundColor: const Color(0xff111b21), + backgroundColor: const Color(0xff202c33), + cardColor: const Color(0xff202c33), + primaryColor: const Color(0xff00a884), + textTheme: TextTheme( + titleMedium: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + bodyMedium: TextStyle(color: Colors.white.withOpacity(0.8)), + labelLarge: TextStyle(color: Colors.white.withOpacity(0.5)), + ), + iconTheme: const IconThemeData( + color: Color(0xff00a884), + ), + dividerColor: Colors.white, + ); +} + +MaterialColor generateMaterialColorFromColor(Color color) { + return MaterialColor(color.value, { + 50: Color.fromRGBO(color.red, color.green, color.blue, 0.1), + 100: Color.fromRGBO(color.red, color.green, color.blue, 0.2), + 200: Color.fromRGBO(color.red, color.green, color.blue, 0.3), + 300: Color.fromRGBO(color.red, color.green, color.blue, 0.4), + 400: Color.fromRGBO(color.red, color.green, color.blue, 0.5), + 500: Color.fromRGBO(color.red, color.green, color.blue, 0.6), + 600: Color.fromRGBO(color.red, color.green, color.blue, 0.7), + 700: Color.fromRGBO(color.red, color.green, color.blue, 0.8), + 800: Color.fromRGBO(color.red, color.green, color.blue, 0.9), + 900: Color.fromRGBO(color.red, color.green, color.blue, 1.0), + }); +} diff --git a/lib/widgets/status_card.dart b/lib/widgets/status_card.dart index 6b21d36..6024dd4 100644 --- a/lib/widgets/status_card.dart +++ b/lib/widgets/status_card.dart @@ -6,40 +6,46 @@ import 'package:whatskit/pages/status_preview_page.dart'; class WhatsappStatusCard extends StatelessWidget { final FileSystemEntity file; - const WhatsappStatusCard({Key? key, required this.file}) : super(key: key); + final double elevation; + const WhatsappStatusCard({Key? key, required this.file, this.elevation = 0}) + : super(key: key); @override Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8.0), - color: Theme.of(context).cardColor, - ), - child: Stack( - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(12), - child: PreviewFile( - file: File(file.path), + return Material( + elevation: elevation, + color: Colors.transparent, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Theme.of(context).cardColor, + ), + child: Stack( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(12), + child: PreviewFile( + file: File(file.path), + ), ), - ), - Material( - color: Colors.transparent, - child: InkWell( - onTap: () async { - Navigator.push( - context, - MaterialPageRoute( - builder: (builder) => PreviewPage( - file: File(file.path), + Material( + color: Colors.transparent, + child: InkWell( + onTap: () async { + Navigator.push( + context, + MaterialPageRoute( + builder: (builder) => PreviewPage( + file: File(file.path), + ), ), - ), - ); - }, - borderRadius: BorderRadius.circular(12), + ); + }, + borderRadius: BorderRadius.circular(12), + ), ), - ), - ], + ], + ), ), ); } diff --git a/pubspec.lock b/pubspec.lock index f7f7d1e..ca0fa2e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -275,6 +275,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.1" + nested: + dependency: transitive + description: + name: nested + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" package_info_plus: dependency: "direct main" description: @@ -436,6 +443,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.2.4" + provider: + dependency: "direct main" + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.2" share_plus: dependency: "direct main" description: @@ -478,6 +492,62 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.0" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.13" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.11" + shared_preferences_ios: + dependency: transitive + description: + name: shared_preferences_ios + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + shared_preferences_macos: + dependency: transitive + description: + name: shared_preferences_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" sky_engine: dependency: transitive description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index dbccf0f..76caed9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.0.3+1 +version: 1.0.3+2 environment: sdk: ">=2.16.2 <3.0.0" @@ -44,6 +44,8 @@ dependencies: git: https://github.com/netharuM/video_trimmer.git package_info_plus: ^1.4.2 url_launcher: ^6.0.20 + provider: ^6.0.2 + shared_preferences: ^2.0.13 dev_dependencies: flutter_test: