diff --git a/.gitignore b/.gitignore index 668eaae..7b048d8 100644 --- a/.gitignore +++ b/.gitignore @@ -49,5 +49,8 @@ app.*.map.json # key file key.properties -#scratch file +# secret file +SECRETS.dart + +# scratch file scratch.dart diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..f6b8e86 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,30 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + file_names: false + # library_prefixes: false +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/android/app/build.gradle b/android/app/build.gradle index 4bf12b1..902b08c 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -41,12 +41,11 @@ android { defaultConfig { applicationId "live.iqfareez.waktusolatmalaysia" - minSdkVersion 16 + minSdkVersion 19 targetSdkVersion 30 versionCode flutterVersionCode.toInteger() versionName flutterVersionName multiDexEnabled true - } signingConfigs { @@ -70,15 +69,5 @@ flutter { } dependencies { - // Import the Firebase BoM -// implementation platform('com.google.firebase:firebase-bom:26.1.1') - - // Add the dependency for the Firebase SDK for Google Analytics - // When using the BoM, don't specify versions in Firebase dependencies -// implementation 'com.google.firebase:firebase-analytics' implementation 'androidx.multidex:multidex:2.0.1' - - - // Add the dependencies for any other desired Firebase products - // https://firebase.google.com/docs/android/setup#available-libraries } \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index bc07d22..eaf41ad 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -35,13 +35,17 @@ Flutter's first frame. --> + android:resource="@drawable/launch_background"/> + + + getCurrentLocation() async { - try { - Position position = await Geolocator.getCurrentPosition( - desiredAccuracy: - LocationAccuracy.low); //on Android, low is in 500m radius - _position = position; - print('[LocationData] Sucess getting $position'); - return position; - } catch (e) { - print('[LocationData] Error is $e'); - throw 'Error occured: $e'; - } + Position position = await Geolocator.getCurrentPosition( + desiredAccuracy: + LocationAccuracy.low); //on Android, low is in 500m radius + _position = position; + return position; } - static get position => _position; + static Position get position => _position; } diff --git a/lib/locationUtil/locationDatabase.dart b/lib/locationUtil/locationDatabase.dart index a551696..4cda07c 100644 --- a/lib/locationUtil/locationDatabase.dart +++ b/lib/locationUtil/locationDatabase.dart @@ -4,7 +4,7 @@ import 'location.dart'; class LocationDatabase { - static List _locationDatabase = [ + static final List _locationDatabase = [ //JOHOR Location( jakimCode: 'JHR01', @@ -387,7 +387,6 @@ class LocationDatabase { var jakimCaps = jakimCode.toUpperCase(); var index = _locationDatabase .indexWhere((element) => element.jakimCode == jakimCaps); - print('index of $jakimCaps is at $index'); return index; } diff --git a/lib/locationUtil/location_coordinate.dart b/lib/locationUtil/location_coordinate.dart index 2b9e5fb..4b6dddb 100644 --- a/lib/locationUtil/location_coordinate.dart +++ b/lib/locationUtil/location_coordinate.dart @@ -5,7 +5,7 @@ import 'package:geolocator/geolocator.dart'; import 'location_coordinate_model.dart'; class LocationCoordinate { - static List _locationCoordinate = [ + static final List _locationCoordinate = [ LocationCoordinateData( zone: "JHR01", negeri: "Johor", @@ -1563,7 +1563,6 @@ class LocationCoordinate { tempIndex.add(i); } } - print('tempIndex is $tempIndex'); for (var index in tempIndex) { // calculate distance each of indexes location with user location diff --git a/lib/locationUtil/location_provider.dart b/lib/locationUtil/location_provider.dart index 843cb01..3ff1c81 100644 --- a/lib/locationUtil/location_provider.dart +++ b/lib/locationUtil/location_provider.dart @@ -6,7 +6,6 @@ class LocationProvider with ChangeNotifier { int _currentLocationIndex = GetStorage().read(kStoredGlobalIndex); set currentLocationIndex(int value) { - print('inside provider: $value'); _currentLocationIndex = value; GetStorage().write(kStoredGlobalIndex, value); notifyListeners(); diff --git a/lib/main.dart b/lib/main.dart index ec966c8..df3addc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,6 +4,8 @@ import 'package:flutter/services.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:get_storage/get_storage.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:google_mobile_ads/google_mobile_ads.dart'; +import 'package:in_app_review/in_app_review.dart'; import 'package:provider/provider.dart'; import 'package:timezone/data/latest.dart' as tz; import 'package:timezone/timezone.dart' as tz; @@ -18,27 +20,35 @@ import 'views/bottomAppBar.dart'; import 'views/onboarding_page.dart'; NotificationAppLaunchDetails notifLaunch; -final FlutterLocalNotificationsPlugin notifsPlugin = - FlutterLocalNotificationsPlugin(); void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await GetStorage.init(); + await Firebase.initializeApp(); + MobileAds.instance.initialize(); + final FlutterLocalNotificationsPlugin notifsPlugin = + FlutterLocalNotificationsPlugin(); await _configureLocalTimeZone(); notifLaunch = await notifsPlugin.getNotificationAppLaunchDetails(); await initNotifications(notifsPlugin); // requestIOSPermissions(notifsPlugin); - await Firebase.initializeApp(); initGetStorage(); // readAllGetStorage(); + /// Increment app launch counter + GetStorage().write(kAppLaunchCount, GetStorage().read(kAppLaunchCount) + 1); SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light); - runApp(MyApp()); + runApp(const MyApp()); + + showReviewPrompt(); } class MyApp extends StatelessWidget { + const MyApp({Key key}) : super(key: key); final _primaryColour = Colors.teal; @override @@ -68,10 +78,9 @@ class MyApp extends StatelessWidget { visualDensity: VisualDensity.adaptivePlatformDensity, appBarTheme: AppBarTheme(color: _primaryColour.shade800)), themeMode: value.themeMode, - // home: OnboardingPage(), home: GetStorage().read(kIsFirstRun) - ? OnboardingPage() - : MyHomePage(), + ? const OnboardingPage() + : const MyHomePage(), ); }, ), @@ -80,6 +89,8 @@ class MyApp extends StatelessWidget { } class MyHomePage extends StatelessWidget { + const MyHomePage({Key key}) : super(key: key); + @override Widget build(BuildContext context) { return Scaffold( @@ -92,10 +103,10 @@ class MyHomePage extends StatelessWidget { centerTitle: true, toolbarHeight: 50, ), - bottomNavigationBar: MyBottomAppBar(), - floatingActionButton: ShareFAB(), - floatingActionButtonLocation: FloatingActionButtonLocation.endDocked, - body: SingleChildScrollView(child: AppBody()), + bottomNavigationBar: const MyBottomAppBar(), + floatingActionButton: const ShareFAB(), + floatingActionButtonLocation: FloatingActionButtonLocation.miniEndDocked, + body: const SingleChildScrollView(child: AppBody()), ); } } @@ -103,6 +114,8 @@ class MyHomePage extends StatelessWidget { void initGetStorage() { // init default settings GetStorage _get = GetStorage(); + _get.writeIfNull(kShowNotifPrompt, true); + _get.writeIfNull(kAppLaunchCount, 0); _get.writeIfNull(kIsFirstRun, true); _get.writeIfNull(kStoredGlobalIndex, 0); _get.writeIfNull(kStoredTimeIs12, true); @@ -121,10 +134,11 @@ void initGetStorage() { Future _configureLocalTimeZone() async { // use for notification tz.initializeTimeZones(); - final String timeZoneName = 'Asia/Kuala_Lumpur'; + const String timeZoneName = 'Asia/Kuala_Lumpur'; tz.setLocalLocation(tz.getLocation(timeZoneName)); } +// ignore_for_file: avoid_print void readAllGetStorage() { // print (almost) all GetStorage item to the console print("-----All GET STORAGE-----"); @@ -143,3 +157,15 @@ void readAllGetStorage() { 'kDiscoveredDeveloperOption is ${_get.read(kDiscoveredDeveloperOption)}'); print('-----------------------'); } + +/// Show InAppReview if all conditions are met +void showReviewPrompt() async { + final InAppReview inAppReview = InAppReview.instance; + + int _appLaunchCount = GetStorage().read(kAppLaunchCount); + + if (_appLaunchCount == 10 && await inAppReview.isAvailable()) { + await Future.delayed(const Duration(seconds: 2)); + inAppReview.requestReview(); + } +} diff --git a/lib/models/mpti906PrayerData.dart b/lib/models/mpti906PrayerData.dart index e050844..74cd317 100644 --- a/lib/models/mpti906PrayerData.dart +++ b/lib/models/mpti906PrayerData.dart @@ -4,30 +4,81 @@ class Mpti906PrayerModel { Mpti906PrayerModel({this.data}); Mpti906PrayerModel.fromJson(Map json) { - data = json['data'] != null ? new Data.fromJson(json['data']) : null; + data = json["data"] == null ? null : Data.fromJson(json["data"]); } Map toJson() { - final Map data = new Map(); + final Map data = {}; if (this.data != null) { - data['data'] = this.data.toJson(); + data["data"] = this.data.toJson(); } return data; } } class Data { - String times; + String provider; + String code; + int year; + int month; + String place; + Attributes attributes; + List> times; - Data({this.times}); + Data( + {this.provider, + this.code, + this.year, + this.month, + this.place, + this.attributes, + this.times}); Data.fromJson(Map json) { - times = json['times'].toString(); //force text + provider = json["provider"]; + code = json["code"]; + year = json["year"]; + month = json["month"]; + place = json["place"]; + attributes = json["attributes"] == null + ? null + : Attributes.fromJson(json["attributes"]); + times = + json["times"] == null ? null : List>.from(json["times"]); } Map toJson() { - final Map data = new Map(); - data['times'] = this.times; + final Map data = {}; + data["provider"] = provider; + data["code"] = code; + data["year"] = year; + data["month"] = month; + data["place"] = place; + if (attributes != null) { + data["attributes"] = attributes.toJson(); + } + if (times != null) { + data["times"] = times; + } + return data; + } +} + +class Attributes { + String jakimCode; + String jakimSource; + + Attributes({this.jakimCode, this.jakimSource}); + + Attributes.fromJson(Map json) { + jakimCode = json["jakim_code"]; + jakimSource = json["jakim_source"]; + } + + Map toJson() { + final Map data = {}; + data["jakim_code"] = jakimCode; + data["jakim_source"] = jakimSource; return data; } } diff --git a/lib/notificationUtil/isolate_handler_notification.dart b/lib/notificationUtil/isolate_handler_notification.dart index edec654..f1a77ce 100644 --- a/lib/notificationUtil/isolate_handler_notification.dart +++ b/lib/notificationUtil/isolate_handler_notification.dart @@ -1,28 +1,25 @@ -import 'package:flutter/cupertino.dart'; -import 'package:fluttertoast/fluttertoast.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:get_storage/get_storage.dart'; import 'package:isolate_handler/isolate_handler.dart'; import 'package:timezone/timezone.dart' as tz; +import 'package:waktusolatmalaysia/utils/debug_toast.dart'; import '../CONSTANTS.dart'; import '../locationUtil/locationDatabase.dart'; -import '../main.dart'; import 'notifications_helper.dart'; // https://gist.github.com/taciomedeiros/50472cf94c742befba720853e9d598b6 final IsolateHandler isolateHandler = IsolateHandler(); +final _notifsPlugin = FlutterLocalNotificationsPlugin(); DateTime currentDate = DateTime.now(); void schedulePrayNotification(List times) async { - await notifsPlugin.cancelAll(); //reset all + await _notifsPlugin.cancelAll(); //reset all String currentLocation = LocationDatabase.getDaerah(GetStorage().read(kStoredGlobalIndex)); - print(currentLocation); - var currentTime = DateTime.now().millisecondsSinceEpoch; - - var howMuchToSchedule; + int howMuchToSchedule; if (GetStorage().read(kStoredNotificationLimit)) { //should limit to 7 @@ -31,13 +28,8 @@ void schedulePrayNotification(List times) async { howMuchToSchedule = times.length; } - if (GetStorage().read(kIsDebugMode)) { - Fluttertoast.showToast( - msg: 'SCHEDULING $howMuchToSchedule notiifcations', - backgroundColor: Color(0xFFD17777)); - } + DebugToast.show('SCHEDULING $howMuchToSchedule notifications'); - print('howMuchToSchedule is $howMuchToSchedule'); // for debug dialog GetStorage().write(kNumberOfNotifsScheduled, howMuchToSchedule); @@ -54,7 +46,7 @@ void schedulePrayNotification(List times) async { //to make sure the time is in future await schedulePrayerNotification( name: 'Fajr', - notifsPlugin: notifsPlugin, + notifsPlugin: _notifsPlugin, id: (subuhTimeEpoch / 1000).truncate(), title: 'It\'s Subuh', scheduledTime: tz.TZDateTime.from( @@ -65,7 +57,7 @@ void schedulePrayNotification(List times) async { if (!(syurukTimeEpoch < currentTime)) { await schedulePrayerNotification( name: 'Syuruk', - notifsPlugin: notifsPlugin, + notifsPlugin: _notifsPlugin, id: (syurukTimeEpoch / 1000).truncate(), title: 'It\'s Syuruk', body: 'in ' + currentLocation, @@ -75,7 +67,7 @@ void schedulePrayNotification(List times) async { if (!(zuhrTimeEpoch < currentTime)) { await schedulePrayerNotification( name: 'Zuhr', - notifsPlugin: notifsPlugin, + notifsPlugin: _notifsPlugin, id: (zuhrTimeEpoch / 1000).truncate(), title: 'It\'s Zohor', body: 'in ' + currentLocation, @@ -85,7 +77,7 @@ void schedulePrayNotification(List times) async { if (!(asarTimeEpoch < currentTime)) { await schedulePrayerNotification( name: 'Asr', - notifsPlugin: notifsPlugin, + notifsPlugin: _notifsPlugin, id: (asarTimeEpoch / 1000).truncate(), title: 'It\'s Asar', body: 'in ' + currentLocation, @@ -95,7 +87,7 @@ void schedulePrayNotification(List times) async { if (!(maghribTimeEpoch < currentTime)) { await schedulePrayerNotification( name: 'Maghrib', - notifsPlugin: notifsPlugin, + notifsPlugin: _notifsPlugin, id: (maghribTimeEpoch / 1000).truncate(), title: 'It\'s Maghrib', body: 'in ' + currentLocation, @@ -105,25 +97,17 @@ void schedulePrayNotification(List times) async { if (!(isyakTimeEpoch < currentTime)) { await schedulePrayerNotification( name: 'Isya\'', - notifsPlugin: notifsPlugin, + notifsPlugin: _notifsPlugin, id: (isyakTimeEpoch / 1000).truncate(), title: 'It\'s Isyak', body: 'in ' + currentLocation, scheduledTime: tz.TZDateTime.from( DateTime.fromMillisecondsSinceEpoch(isyakTimeEpoch), tz.local)); } - - print('Notification scheduled #${i + 1}'); - print('Subuh @ $subuhTimeEpoch'); - print('Syuruk @ $syurukTimeEpoch'); - print('Zohor @ $zuhrTimeEpoch'); - print('Asar @ $asarTimeEpoch'); - print('Maghrib @ $maghribTimeEpoch'); - print('Isyak @ $isyakTimeEpoch'); } scheduleAlertNotification( - notifsPlugin: notifsPlugin, + notifsPlugin: _notifsPlugin, id: 2190, title: 'Monthly refresh reminder', body: @@ -133,11 +117,7 @@ void schedulePrayNotification(List times) async { 1, 0, 5), //2021-01-01 00:05:00.000+0800 ); - print('DONE SCHEDULING NOTIFS'); - if (GetStorage().read(kIsDebugMode)) { - Fluttertoast.showToast( - msg: 'FINISH SCHEDULE NOTIFS', toastLength: Toast.LENGTH_LONG); - } + DebugToast.show('FINISH SCHEDULE NOTIFS'); //This timestamp is later used to determine wether notification should be updated or not GetStorage() @@ -160,8 +140,9 @@ startScheduleNotifications(String _remindersAsString) { } void killCurrentScheduleNotifications() { - if (isolateHandler.isolates.containsKey('scheduleNotifications')) + if (isolateHandler.isolates.containsKey('scheduleNotifications')) { isolateHandler.kill('scheduleNotifications'); + } } void entryPoint(Map context) { diff --git a/lib/notificationUtil/notifications_helper.dart b/lib/notificationUtil/notifications_helper.dart index 33aa948..99968c7 100644 --- a/lib/notificationUtil/notifications_helper.dart +++ b/lib/notificationUtil/notifications_helper.dart @@ -4,15 +4,14 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart' as notifs; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:fluttertoast/fluttertoast.dart'; -import 'package:rxdart/subjects.dart' as rxSub; +import 'package:rxdart/subjects.dart' as rxsub; import '../CONSTANTS.dart'; -import '../main.dart'; -final rxSub.BehaviorSubject +final rxsub.BehaviorSubject didReceiveLocalNotificationSubject = - rxSub.BehaviorSubject(); -final rxSub.BehaviorSubject selectNotificationSubject = - rxSub.BehaviorSubject(); + rxsub.BehaviorSubject(); +final rxsub.BehaviorSubject selectNotificationSubject = + rxsub.BehaviorSubject(); class NotificationClass { final int id; @@ -26,7 +25,7 @@ class NotificationClass { Future initNotifications( notifs.FlutterLocalNotificationsPlugin notifsPlugin) async { var initializationSettingsAndroid = - notifs.AndroidInitializationSettings('icon'); + const notifs.AndroidInitializationSettings('icon'); var initializationSettingsIOS = notifs.IOSInitializationSettings( requestAlertPermission: false, requestBadgePermission: false, @@ -45,27 +44,26 @@ Future initNotifications( } selectNotificationSubject.add(payload); }); - print("Notifications initialised successfully"); } -void requestIOSPermissions( - notifs.FlutterLocalNotificationsPlugin notifsPlugin) { - notifsPlugin - .resolvePlatformSpecificImplementation< - notifs.IOSFlutterLocalNotificationsPlugin>() - ?.requestPermissions( - alert: true, - badge: true, - sound: true, - ); -} +// void requestIOSPermissions( +// notifs.FlutterLocalNotificationsPlugin notifsPlugin) { +// notifsPlugin +// .resolvePlatformSpecificImplementation< +// notifs.IOSFlutterLocalNotificationsPlugin>() +// ?.requestPermissions( +// alert: true, +// badge: true, +// sound: true, +// ); +// } void configureSelectNotificationSubject(BuildContext context) { selectNotificationSubject.stream.listen((String payload) async { if (payload == kPayloadMonthly) { Fluttertoast.showToast( msg: - 'Please wait for a few seconds for the notification to be resheduled.', + 'Please wait for a few seconds for the notification to be resheduled', toastLength: Toast.LENGTH_LONG); } else if (payload == kPayloadDebug) { Fluttertoast.showToast( @@ -90,9 +88,9 @@ Future schedulePrayerNotification( priority: notifs.Priority.max, importance: notifs.Importance.high, styleInformation: styleInformation, - color: Color(0xFF19e3cb), + color: const Color(0xFF19e3cb), ); - var iOSSpecifics = notifs.IOSNotificationDetails(); + var iOSSpecifics = const notifs.IOSNotificationDetails(); var platformChannelSpecifics = notifs.NotificationDetails(android: androidSpecifics, iOS: iOSSpecifics); await notifsPlugin.zonedSchedule( @@ -119,10 +117,10 @@ Future scheduleAlertNotification( priority: notifs.Priority.defaultPriority, importance: notifs.Importance.high, styleInformation: styleInformation, - color: Color(0xFFfcbd00), + color: const Color(0xFFfcbd00), ); - var iOSSpecifics = notifs.IOSNotificationDetails(); + var iOSSpecifics = const notifs.IOSNotificationDetails(); var platformChannelSpecifics = notifs.NotificationDetails(android: androidSpecifics, iOS: iOSSpecifics); await notifsPlugin.zonedSchedule( @@ -145,6 +143,6 @@ Future showDebugNotification() async { const NotificationDetails platformChannelSpecifics = NotificationDetails( android: androidPlatformChannelSpecifics, ); - await notifsPlugin.show( + await FlutterLocalNotificationsPlugin().show( 0, 'Debug notifs.', 'For developer purposes', platformChannelSpecifics); } diff --git a/lib/notificationUtil/prevent_update_notifs.dart b/lib/notificationUtil/prevent_update_notifs.dart index 4882aae..e63dd9e 100644 --- a/lib/notificationUtil/prevent_update_notifs.dart +++ b/lib/notificationUtil/prevent_update_notifs.dart @@ -1,7 +1,7 @@ //If less than 2 days, since the last notif is scheduled, do not rescehdule -import 'package:fluttertoast/fluttertoast.dart'; import 'package:get_storage/get_storage.dart'; +import 'package:waktusolatmalaysia/utils/debug_toast.dart'; import '../CONSTANTS.dart'; import '../utils/DateAndTime.dart'; @@ -13,21 +13,21 @@ class PreventUpdatingNotifs { static void setNow() { if (GetStorage().read(kForceUpdateNotif)) { //checks is force update,if true then notif should update, - shouldUpdateNotification(GetStorage().read(kIsDebugMode)); + shouldUpdateNotification(); } else { - if (DateAndTime.isTheSameMonth( + if (DateAndTime.isSameMonthFromMillis( GetStorage().read(kStoredLastUpdateNotif))) { //check if same month or mot, notification will update if not in the month if ((DateTime.now().millisecondsSinceEpoch - GetStorage().read(kStoredLastUpdateNotif)) < - Duration(days: 2).inMilliseconds) { + const Duration(days: 2).inMilliseconds) { //check if certain period o time has reached - dontUpdateNotification(GetStorage().read(kIsDebugMode)); + dontUpdateNotification(); } else { - shouldUpdateNotification(GetStorage().read(kIsDebugMode)); + shouldUpdateNotification(); } } else { - shouldUpdateNotification(GetStorage().read(kIsDebugMode)); + shouldUpdateNotification(); } } @@ -36,18 +36,12 @@ class PreventUpdatingNotifs { } } -void shouldUpdateNotification(bool isDebug) { +void shouldUpdateNotification() { GetStorage().write(kStoredShouldUpdateNotif, true); - print('Notification should update'); - if (isDebug) { - Fluttertoast.showToast(msg: 'Notification should update'); - } + DebugToast.show('Notification should update'); } -void dontUpdateNotification(bool isDebug) { - print('Notification should not update'); - if (isDebug) { - Fluttertoast.showToast(msg: 'Notification should not update'); - } +void dontUpdateNotification() { + DebugToast.show('Notification should not update'); GetStorage().write(kStoredShouldUpdateNotif, false); } diff --git a/lib/utils/DateAndTime.dart b/lib/utils/DateAndTime.dart index 0a7395c..1d525c8 100644 --- a/lib/utils/DateAndTime.dart +++ b/lib/utils/DateAndTime.dart @@ -3,17 +3,45 @@ import 'package:intl/intl.dart'; class DateAndTime { static String toTimeReadable(int unix, bool is12hr) { var formatToReadable = is12hr ? DateFormat('h:mm a') : DateFormat('HH:mm'); - var date = - new DateTime.fromMillisecondsSinceEpoch(unix * 1000, isUtc: true); - date = date.add(Duration(hours: 8)); //phone already formatted like this + var date = DateTime.fromMillisecondsSinceEpoch(unix * 1000, isUtc: true); + date = + date.add(const Duration(hours: 8)); //phone already formatted like this var formattedTime = formatToReadable.format(date); return (formattedTime); } - static bool isTheSameMonth(int savedMillis) { - var savedMonth = DateTime.fromMillisecondsSinceEpoch(savedMillis).month; + static bool isSameMonthFromMillis(int millis) { + var savedMonth = DateTime.fromMillisecondsSinceEpoch(millis).month; var currentMonth = DateTime.now().month; return savedMonth == currentMonth; } + + /// Accept month in integer, for eg: 7 (for July) etc. + static bool isSameMonthFromM(int month) { + return month == DateTime.now().month; + } + + /// Accept year in int, for eg: 2021 + static bool isTheSameYear(int year) { + return year == DateTime.now().year; + } + + ///Convert int month to month name + static String monthName(int month) { + return [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December' + ][month - 1]; + } } diff --git a/lib/utils/RawPrayDataHandler.dart b/lib/utils/RawPrayDataHandler.dart index 38e0545..42ec66d 100644 --- a/lib/utils/RawPrayDataHandler.dart +++ b/lib/utils/RawPrayDataHandler.dart @@ -1,33 +1,18 @@ -/// Convert a json array to List object -/// Handle 6 prayer times that come from API only -import 'dart:convert'; -import 'package:intl/intl.dart'; - -final int day = int.parse(DateFormat('d').format(DateTime.now())); -var prayDataList; -var todayPrayData; -var prayDataCurrentDateOnwards = []; +final int day = DateTime.now().day; class PrayDataHandler { - PrayDataHandler(String dataStream) { - prayDataList = jsonDecode(dataStream); - todayPrayData = prayDataList[day - 1]; - } - - List getPrayDataList() => prayDataList; - - List getPrayDataCurrentDateOnwards() { - prayDataCurrentDateOnwards.clear(); - for (int i = 0; i < prayDataList.length; i++) { + static List removePastDate(List> times) { + List _temp = []; + for (int i = 0; i < times.length; i++) { //ignore the previous date if (!(i < day - 1)) { - print('day is ${i + 1} : ${prayDataList[i]}'); - prayDataCurrentDateOnwards.add(prayDataList[i]); + _temp.add(times[i]); } } - - return prayDataCurrentDateOnwards; + // return today and future dates only + return _temp; } - List getTodayPrayData() => todayPrayData; + static List todayPrayData(List> times) => + times[day - 1]; } diff --git a/lib/utils/copyAndShare.dart b/lib/utils/copyAndShare.dart index d90a085..f9a43f3 100644 --- a/lib/utils/copyAndShare.dart +++ b/lib/utils/copyAndShare.dart @@ -1,18 +1,18 @@ import 'package:get_storage/get_storage.dart'; import 'package:hijri/hijri_calendar.dart'; import 'package:intl/intl.dart'; -import '../CONSTANTS.dart' as Constants; +import '../CONSTANTS.dart' as constants; import '../locationUtil/locationDatabase.dart'; -import 'cachedPrayerData.dart'; +import 'temp_prayer_data.dart'; class CopyAndShare { static String getMessage({int type = 1}) { var hijriToday = HijriCalendar.fromDate(DateTime.now() - .add(Duration(days: GetStorage().read(Constants.kHijriOffset)))) + .add(Duration(days: GetStorage().read(constants.kHijriOffset)))) .toFormat('dd MMMM yyyy'); var _dayFormat = DateFormat('EEEE').format(DateTime.now()).toUpperCase(); var _dateFormat = DateFormat('dd MMMM yyyy').format(DateTime.now()); - var _globalIndex = GetStorage().read(Constants.kStoredGlobalIndex); + var _globalIndex = GetStorage().read(constants.kStoredGlobalIndex); var daerah = LocationDatabase.getDaerah(_globalIndex); var negeri = LocationDatabase.getNegeri(_globalIndex); switch (type) { @@ -23,13 +23,13 @@ Solat timetable: $_dayFormat, $_dateFormat 📍 $daerah ($negeri) 📆 ${hijriToday}H -☁ Subuh: ${CachedPrayerTimeData.allPrayerTime()[0]} -🌞 Zohor: ${CachedPrayerTimeData.allPrayerTime()[1]} -☀ Asar: ${CachedPrayerTimeData.allPrayerTime()[2]} -🌙 Maghrib: ${CachedPrayerTimeData.allPrayerTime()[3]} -⭐ Isyak: ${CachedPrayerTimeData.allPrayerTime()[4]} +☁ Subuh: ${TempPrayerTimeData.allPrayerTime()[0]} +🌞 Zohor: ${TempPrayerTimeData.allPrayerTime()[1]} +☀ Asar: ${TempPrayerTimeData.allPrayerTime()[2]} +🌙 Maghrib: ${TempPrayerTimeData.allPrayerTime()[3]} +⭐ Isyak: ${TempPrayerTimeData.allPrayerTime()[4]} -Get the app: ${Constants.kMptFdlGetLink}'''; +Get the app: ${constants.kMptFdlGetLink}'''; break; case 2: return ''' @@ -38,13 +38,13 @@ Get the app: ${Constants.kMptFdlGetLink}'''; 📍 _$daerah *($negeri)*_ 📆 ${hijriToday}H -```☁ Subuh : ${CachedPrayerTimeData.allPrayerTime()[0]}``` -```🌞 Zohor : ${CachedPrayerTimeData.allPrayerTime()[1]}``` -```☀ Asar : ${CachedPrayerTimeData.allPrayerTime()[2]}``` -```🌙 Maghrib : ${CachedPrayerTimeData.allPrayerTime()[3]}``` -```⭐ Isyak : ${CachedPrayerTimeData.allPrayerTime()[4]}``` +```☁ Subuh : ${TempPrayerTimeData.allPrayerTime()[0]}``` +```🌞 Zohor : ${TempPrayerTimeData.allPrayerTime()[1]}``` +```☀ Asar : ${TempPrayerTimeData.allPrayerTime()[2]}``` +```🌙 Maghrib : ${TempPrayerTimeData.allPrayerTime()[3]}``` +```⭐ Isyak : ${TempPrayerTimeData.allPrayerTime()[4]}``` -Get the app: ${Constants.kMptFdlGetLink}'''; +Get the app: ${constants.kMptFdlGetLink}'''; break; default: return ''; diff --git a/lib/utils/cupertinoSwitchListTile.dart b/lib/utils/cupertinoSwitchListTile.dart index 6e4ba49..0c0d9c9 100644 --- a/lib/utils/cupertinoSwitchListTile.dart +++ b/lib/utils/cupertinoSwitchListTile.dart @@ -12,10 +12,10 @@ class CupertinoSwitchListTile extends StatelessWidget { this.activeColor, this.title, this.subtitle, - this.isThreeLine: false, + this.isThreeLine = false, this.dense, this.secondary, - this.selected: false, + this.selected = false, }) : assert(value != null), assert(isThreeLine != null), assert(!isThreeLine || subtitle != null), @@ -94,17 +94,15 @@ class CupertinoSwitchListTile extends StatelessWidget { @override Widget build(BuildContext context) { - var color = activeColor ?? Theme.of(context).colorScheme.secondary; - print("Active color: ${color.red} ${color.green} ${color.blue}"); - final Widget control = new CupertinoSwitch( + final Widget control = CupertinoSwitch( value: value, onChanged: onChanged, activeColor: activeColor ?? CupertinoColors.activeGreen, ); - return new MergeSemantics( + return MergeSemantics( child: ListTileTheme.merge( selectedColor: activeColor ?? CupertinoColors.activeGreen, - child: new ListTile( + child: ListTile( leading: secondary, title: title, subtitle: subtitle, diff --git a/lib/utils/navigator_pop.dart b/lib/utils/custom_navigator_pop.dart similarity index 100% rename from lib/utils/navigator_pop.dart rename to lib/utils/custom_navigator_pop.dart diff --git a/lib/utils/debug_toast.dart b/lib/utils/debug_toast.dart new file mode 100644 index 0000000..4393476 --- /dev/null +++ b/lib/utils/debug_toast.dart @@ -0,0 +1,11 @@ +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:get_storage/get_storage.dart'; +import 'package:waktusolatmalaysia/CONSTANTS.dart'; + +class DebugToast { + static void show(String mesage, {bool force = false}) { + if (force || GetStorage().read(kIsDebugMode)) { + Fluttertoast.showToast(msg: mesage); + } + } +} diff --git a/lib/utils/launchUrl.dart b/lib/utils/launchUrl.dart index 0b1c98f..6f8c6a8 100644 --- a/lib/utils/launchUrl.dart +++ b/lib/utils/launchUrl.dart @@ -23,7 +23,6 @@ class LaunchUrl { static void sendViaEmail(String messageContent) { final emailLink = Uri.encodeFull( 'mailto:$kDevEmail?subject=Feedback MPT&body=$messageContent'); - print(emailLink); _launchURL(emailLink); } } diff --git a/lib/utils/mpt_fetch_api.dart b/lib/utils/mpt_fetch_api.dart index e1ae3e2..42dbbb0 100644 --- a/lib/utils/mpt_fetch_api.dart +++ b/lib/utils/mpt_fetch_api.dart @@ -1,27 +1,46 @@ import 'dart:convert'; -import 'package:fluttertoast/fluttertoast.dart'; +import 'dart:io'; import 'package:get_storage/get_storage.dart'; import 'package:http/http.dart' as http; +import 'package:waktusolatmalaysia/utils/DateAndTime.dart'; +import 'package:waktusolatmalaysia/utils/debug_toast.dart'; import '../CONSTANTS.dart'; import '../models/mpti906PrayerData.dart'; class MptApiFetch { + /// Attempt to read from cache first, if failed, fetch the api static Future fetchMpt(String location) async { - final api = Uri.https('mpt.i906.my', 'api/prayer/$location'); - final response = await http.get(api); - GetStorage().write(kStoredApiPrayerCall, api.toString()); //for debug dialog - if (GetStorage().read(kIsDebugMode)) - Fluttertoast.showToast(msg: api.toString()); - print(response.body); + if (GetStorage().read(kJsonCache) != null) { + var json = GetStorage().read(kJsonCache); + var parsedModel = Mpti906PrayerModel.fromJson(json); + // Check is same location code, month and year + if ((parsedModel.data.code == location) && + DateAndTime.isSameMonthFromM(parsedModel.data.month) && + DateAndTime.isTheSameYear(parsedModel.data.year)) { + DebugToast.show('Reading from cache'); + return parsedModel; + } + } - if (response.statusCode == 200) { - // If the server did return a 200 OK response, - // then parse the JSON. - return Mpti906PrayerModel.fromJson(jsonDecode(response.body)); - } else { - // If the server did not return a 200 OK response, - // then throw an exception. - throw Exception('Failed to load prayer time'); + try { + final api = Uri.https('mpt.i906.my', 'api/prayer/$location'); + final response = await http.get(api); + GetStorage() + .write(kStoredApiPrayerCall, api.toString()); //for debug dialog + DebugToast.show('Calling $api'); + if (response.statusCode == 200) { + // If the server did return a 200 OK response, + // then parse the JSON. + var json = jsonDecode(response.body); + GetStorage().write(kJsonCache, json); + return Mpti906PrayerModel.fromJson(json); + } else { + // If the server did not return a 200 OK response, + // then throw an exception. + throw 'Failed to load prayer time. Status code ${response.statusCode}'; + } + } on SocketException { + throw 'No internet connection.'; } } } diff --git a/lib/utils/sharing_fab.dart b/lib/utils/sharing_fab.dart index 0ad5389..feb74a6 100644 --- a/lib/utils/sharing_fab.dart +++ b/lib/utils/sharing_fab.dart @@ -1,20 +1,38 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:fluttertoast/fluttertoast.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:get_storage/get_storage.dart'; import 'package:provider/provider.dart'; import 'package:share/share.dart'; +import 'package:waktusolatmalaysia/CONSTANTS.dart'; import '../views/Settings%20part/settingsProvider.dart'; import 'copyAndShare.dart'; import 'launchUrl.dart'; class ShareFAB extends StatelessWidget { + const ShareFAB({Key key}) : super(key: key); + @override Widget build(BuildContext context) { return Consumer(builder: (context, setting, child) { return FloatingActionButton( backgroundColor: Theme.of(context).primaryColor, - child: FaIcon(setting.sharingFormat == 2 - ? FontAwesomeIcons.whatsapp - : FontAwesomeIcons.shareAlt), + child: Builder( + builder: (context) { + switch (setting.sharingFormat) { + case 2: + return const FaIcon(FontAwesomeIcons.whatsapp); + break; + case 3: + return const FaIcon(FontAwesomeIcons.clone); + break; + default: + return const FaIcon(FontAwesomeIcons.shareAlt); + break; + } + }, + ), mini: true, tooltip: 'Share solat time', onPressed: () { @@ -25,6 +43,9 @@ class ShareFAB extends StatelessWidget { case 2: shareToWhatsApp(); break; + case 3: + copy(); + break; default: showShareDialog(context); break; @@ -34,49 +55,62 @@ class ShareFAB extends StatelessWidget { }); } - void showShareDialog(BuildContext context) { - showModalBottomSheet( + void showShareDialog(BuildContext context) async { + await showModalBottomSheet( context: context, builder: (context) { return Column( mainAxisSize: MainAxisSize.min, children: [ ListTile( - title: Text('Share as plain text'), - subtitle: Text('Compatible to all apps'), + title: const Text('Share as plain text'), + subtitle: const Text('Compatible to all apps'), onTap: () { Navigator.pop(context); shareUniversal(); }, ), ListTile( - title: Text('Share to WhatsApp'), - subtitle: Text('Using WhatsApp compatible format'), - trailing: FaIcon(FontAwesomeIcons.whatsapp), + title: const Text('Share to WhatsApp'), + subtitle: const Text('Using WhatsApp compatible format'), + trailing: const FaIcon(FontAwesomeIcons.whatsapp), onTap: () { Navigator.pop(context); shareToWhatsApp(); }, ), - Padding( - padding: const EdgeInsets.all(10), - child: RichText( - text: TextSpan( - text: 'You can set defaults in ', - style: DefaultTextStyle.of(context) - .style - .copyWith(fontSize: 12), - children: [ - TextSpan( - text: 'Setting -> Sharing', - style: TextStyle(fontWeight: FontWeight.bold)), - ], + ListTile( + title: const Text('Copy to clipboard'), + trailing: const FaIcon(FontAwesomeIcons.clone), + onTap: () { + copy(); + Navigator.pop(context); + }, + ), + // Message should only show once + GetStorage().read(kHasOpenSharingDialog) ?? false + ? const SizedBox.shrink() + : Padding( + padding: const EdgeInsets.all(10), + child: RichText( + text: TextSpan( + text: 'You can set defaults in ', + style: DefaultTextStyle.of(context) + .style + .copyWith(fontSize: 12), + children: const [ + TextSpan( + text: 'Setting -> Sharing', + style: TextStyle(fontWeight: FontWeight.bold)), + ], + ), + ), ), - )), ], ); }, ); + GetStorage().write(kHasOpenSharingDialog, true); } void shareToWhatsApp() => LaunchUrl.normalLaunchUrl( @@ -85,4 +119,11 @@ class ShareFAB extends StatelessWidget { void shareUniversal() => Share.share(CopyAndShare.getMessage(), subject: 'Malaysia prayer time for today'); + + void copy() => + Clipboard.setData(ClipboardData(text: CopyAndShare.getMessage())).then( + (value) { + Fluttertoast.showToast(msg: 'Timetable copied'); + }, + ); } diff --git a/lib/utils/cachedPrayerData.dart b/lib/utils/temp_prayer_data.dart similarity index 56% rename from lib/utils/cachedPrayerData.dart rename to lib/utils/temp_prayer_data.dart index 7cc6022..40f2b50 100644 --- a/lib/utils/cachedPrayerData.dart +++ b/lib/utils/temp_prayer_data.dart @@ -1,12 +1,13 @@ -class CachedPrayerTimeData { +/// This class will be used to hold prayer data so when copy or share is invoked, +/// the data can be accessed immediately +class TempPrayerTimeData { static String subuhTime; static String zohorTime; static String asarTime; static String maghribTime; static String isyaTime; - //next time maybe will use local database or something - + /// Returns all (six) prayer time static List allPrayerTime() { return [subuhTime, zohorTime, asarTime, maghribTime, isyaTime]; } diff --git a/lib/views/GetPrayerTime.dart b/lib/views/GetPrayerTime.dart index 7053f13..4fce77b 100644 --- a/lib/views/GetPrayerTime.dart +++ b/lib/views/GetPrayerTime.dart @@ -4,6 +4,7 @@ import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:get_storage/get_storage.dart'; import 'package:provider/provider.dart'; +import 'Settings%20part/settingsProvider.dart'; import '../CONSTANTS.dart'; import '../locationUtil/locationDatabase.dart'; import '../locationUtil/location_provider.dart'; @@ -12,14 +13,14 @@ import '../notificationUtil/isolate_handler_notification.dart'; import '../notificationUtil/prevent_update_notifs.dart'; import '../utils/DateAndTime.dart'; import '../utils/RawPrayDataHandler.dart'; -import '../utils/cachedPrayerData.dart'; +import '../utils/temp_prayer_data.dart'; import '../utils/mpt_fetch_api.dart'; import '../utils/sizeconfig.dart'; -import 'Settings%20part/settingsProvider.dart'; String location; class GetPrayerTime extends StatefulWidget { + const GetPrayerTime({Key key}) : super(key: key); @override _GetPrayerTimeState createState() => _GetPrayerTimeState(); } @@ -40,7 +41,7 @@ class _GetPrayerTimeState extends State { LocationDatabase.getMptLocationCode(value.currentLocationIndex)), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { - return Loading(); + return const Loading(); } else if (snapshot.hasData) { return PrayTimeList(prayerTime: snapshot.data); } else { @@ -57,8 +58,7 @@ class _GetPrayerTimeState extends State { } class PrayTimeList extends StatefulWidget { - PrayTimeList({Key key, this.prayerTime}) : super(key: key); - + const PrayTimeList({Key key, this.prayerTime}) : super(key: key); final Mpti906PrayerModel prayerTime; @override @@ -66,54 +66,45 @@ class PrayTimeList extends StatefulWidget { } class _PrayTimeListState extends State { - PrayDataHandler handler; bool use12hour = GetStorage().read(kStoredTimeIs12); bool showOtherPrayerTime; @override Widget build(BuildContext context) { var prayerTimeData = widget.prayerTime.data; - //process the string data from JSON - handler = PrayDataHandler(prayerTimeData.times); if (GetStorage().read(kStoredShouldUpdateNotif)) { //schedule notification if needed - schedulePrayNotification(handler.getPrayDataCurrentDateOnwards()); + schedulePrayNotification( + PrayDataHandler.removePastDate(prayerTimeData.times)); } - return Container(child: Consumer( + return Consumer( builder: (context, setting, child) { use12hour = setting.use12hour; showOtherPrayerTime = setting.showOtherPrayerTime; - var todayPrayData = handler.getTodayPrayData(); + var _today = PrayDataHandler.todayPrayData(prayerTimeData.times); String imsakTime = DateAndTime.toTimeReadable( - todayPrayData[0] - (10 * 60), use12hour); //minus 10 min from subuh - String subuhTime = - DateAndTime.toTimeReadable(todayPrayData[0], use12hour); - String syurukTime = - DateAndTime.toTimeReadable(todayPrayData[1], use12hour); + _today[0] - (10 * 60), use12hour); //minus 10 min from subuh + String subuhTime = DateAndTime.toTimeReadable(_today[0], use12hour); + String syurukTime = DateAndTime.toTimeReadable(_today[1], use12hour); String dhuhaTime = DateAndTime.toTimeReadable( - todayPrayData[1] + (28 * 60), use12hour); //add 28 min from syuruk - String zohorTime = - DateAndTime.toTimeReadable(todayPrayData[2], use12hour); - String asarTime = - DateAndTime.toTimeReadable(todayPrayData[3], use12hour); - String maghribTime = - DateAndTime.toTimeReadable(todayPrayData[4], use12hour); - String isyaTime = - DateAndTime.toTimeReadable(todayPrayData[5], use12hour); - - CachedPrayerTimeData.subuhTime = subuhTime; - CachedPrayerTimeData.zohorTime = zohorTime; - CachedPrayerTimeData.asarTime = asarTime; - CachedPrayerTimeData.maghribTime = maghribTime; - CachedPrayerTimeData.isyaTime = isyaTime; + _today[1] + (28 * 60), use12hour); //add 28 min from syuruk + String zohorTime = DateAndTime.toTimeReadable(_today[2], use12hour); + String asarTime = DateAndTime.toTimeReadable(_today[3], use12hour); + String maghribTime = DateAndTime.toTimeReadable(_today[4], use12hour); + String isyaTime = DateAndTime.toTimeReadable(_today[5], use12hour); + + TempPrayerTimeData.subuhTime = subuhTime; + TempPrayerTimeData.zohorTime = zohorTime; + TempPrayerTimeData.asarTime = asarTime; + TempPrayerTimeData.maghribTime = maghribTime; + TempPrayerTimeData.isyaTime = isyaTime; return Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.stretch, children: [ showOtherPrayerTime ? solatCard(imsakTime, 'Imsak', false) @@ -132,29 +123,32 @@ class _PrayTimeListState extends State { ], ); }, - )); + ); } } -Widget solatCard(String time, String name, bool useFullHeight) { +Widget solatCard(String time, String name, bool isOtherPrayerTime) { return Container( + constraints: const BoxConstraints(maxWidth: 320), margin: EdgeInsets.symmetric(vertical: SizeConfig.screenHeight / 320), - width: 300, - height: useFullHeight ? 80 : 55, + height: isOtherPrayerTime ? 80 : 55, child: Card( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.0)), + margin: const EdgeInsets.symmetric(vertical: 4.0), + shadowColor: Colors.black54, elevation: 4.0, child: InkWell( borderRadius: BorderRadius.circular(10.0), splashColor: Colors.teal.withAlpha(30), onLongPress: () => - Clipboard.setData(new ClipboardData(text: '$name: $time')) + Clipboard.setData(ClipboardData(text: '$name: $time')) .then((value) { Fluttertoast.showToast( - msg: 'Copied to clipboard', - toastLength: Toast.LENGTH_SHORT, - backgroundColor: Colors.grey.shade700, - textColor: Colors.white); + msg: 'Copied to clipboard', + toastLength: Toast.LENGTH_SHORT, + backgroundColor: Colors.grey.shade700, + textColor: Colors.white, + ); }), child: Center(child: Consumer( builder: (context, setting, child) { @@ -184,16 +178,13 @@ class Error extends StatelessWidget { Text( errorMessage, textAlign: TextAlign.center, - style: TextStyle( + style: const TextStyle( fontSize: 18, ), ), - SizedBox(height: 8), + const SizedBox(height: 8), ElevatedButton( - style: ElevatedButton.styleFrom( - primary: Theme.of(context).buttonColor, - ), - child: Text('Retry', style: TextStyle(color: Colors.black)), + child: const Text('Retry', style: TextStyle(color: Colors.black)), onPressed: onRetryPressed, ) ], @@ -202,10 +193,11 @@ class Error extends StatelessWidget { } class Loading extends StatelessWidget { + const Loading({Key key}) : super(key: key); @override Widget build(BuildContext context) { return Column( - children: [ + children: const [ Text( 'Fetching prayer time. Please wait.', textAlign: TextAlign.center, diff --git a/lib/views/Qibla part/location_error_widget.dart b/lib/views/Qibla part/location_error_widget.dart index ea0cd58..53e51ce 100644 --- a/lib/views/Qibla part/location_error_widget.dart +++ b/lib/views/Qibla part/location_error_widget.dart @@ -9,36 +9,31 @@ class LocationErrorWidget extends StatelessWidget { @override Widget build(BuildContext context) { - final box = SizedBox(height: 32); + const box = SizedBox(height: 32); - return Container( - child: Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.location_off, - size: 130, - color: Colors.redAccent, - ), - box, - Text( - error, - style: TextStyle( - color: Colors.redAccent, fontWeight: FontWeight.bold), - ), - box, - ElevatedButton( - style: ElevatedButton.styleFrom( - primary: Theme.of(context).buttonColor, - ), - child: Text("Retry"), - onPressed: () { - if (callback != null) callback(); - }, - ), - ], - ), + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.location_off, + size: 130, + color: Colors.redAccent, + ), + box, + Text( + error, + style: const TextStyle( + color: Colors.redAccent, fontWeight: FontWeight.bold), + ), + box, + ElevatedButton( + child: const Text("Retry"), + onPressed: () { + if (callback != null) callback(); + }, + ), + ], ), ); } diff --git a/lib/views/Qibla part/no_compass_sensor.dart b/lib/views/Qibla part/no_compass_sensor.dart index 35abdf0..9853973 100644 --- a/lib/views/Qibla part/no_compass_sensor.dart +++ b/lib/views/Qibla part/no_compass_sensor.dart @@ -5,35 +5,30 @@ class NoCompassSensor extends StatelessWidget { @override Widget build(BuildContext context) { - final box = SizedBox(height: 32); + const box = SizedBox(height: 32); return Padding( padding: const EdgeInsets.all(8.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon( + const Icon( Icons.explore_off_outlined, size: 130, color: Colors.redAccent, ), box, - Text( + const Text( 'Sorry. No compass sensor is available in this device.', textAlign: TextAlign.center, + style: + TextStyle(color: Colors.redAccent, fontWeight: FontWeight.bold), ), box, ElevatedButton( - style: ElevatedButton.styleFrom( - primary: Theme.of(context).buttonColor, - ), - onPressed: () { - Navigator.pop(context); - }, - child: Text( + onPressed: () => Navigator.pop(context), + child: const Text( 'Go back', - style: TextStyle( - color: Colors.redAccent, fontWeight: FontWeight.bold), ), ) ], diff --git a/lib/views/Qibla part/qibla.dart b/lib/views/Qibla part/qibla.dart index ef76cc3..e1d0545 100644 --- a/lib/views/Qibla part/qibla.dart +++ b/lib/views/Qibla part/qibla.dart @@ -9,6 +9,7 @@ import '../Qibla%20part/qibla_compass.dart'; import 'no_compass_sensor.dart'; class Qibla extends StatefulWidget { + const Qibla({Key key}) : super(key: key); @override _QiblaState createState() => _QiblaState(); } @@ -20,12 +21,12 @@ class _QiblaState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text('Qibla Compass'), + title: const Text('Qibla Compass'), centerTitle: true, ), body: Stack( children: [ - Align( + const Align( alignment: Alignment.topCenter, child: ListTile( leading: FaIcon(FontAwesomeIcons.info), @@ -41,22 +42,25 @@ class _QiblaState extends State { child: FutureBuilder( future: _deviceSupport, builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) - return Center( + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center( child: CircularProgressIndicator( strokeWidth: 2, ), ); + } - if (snapshot.hasError) + if (snapshot.hasError) { return Center( child: Text('Error: ${snapshot.error.toString()}'), ); + } - if (snapshot.data) - return QiblaCompass(); - else - return NoCompassSensor(); + if (snapshot.data) { + return const QiblaCompass(); + } else { + return const NoCompassSensor(); + } }, ), ), @@ -81,27 +85,28 @@ Widget compassActionButton(BuildContext context) { url: 'https://g.co/qiblafinder', useCustomTabs: true); }, onLongPress: () { - Clipboard.setData(ClipboardData(text: 'g.co/qiblafinder')) + Clipboard.setData(const ClipboardData(text: 'g.co/qiblafinder')) .then((value) => Fluttertoast.showToast(msg: 'URL copied :)')); }, child: Row( - children: [ + children: const [ Text('Google Qiblafinder'), SizedBox(width: 8), FaIcon(FontAwesomeIcons.externalLinkSquareAlt, size: 13) ], ), ), - SizedBox(width: 10), + const SizedBox(width: 10), OutlinedButton( onPressed: () => _showCalibrateCompassDialog(context), - child: Text('Calibration tip'), + child: const Text('Calibration tip'), ) ], ); } void _showCalibrateCompassDialog(BuildContext context) { + //TODO: Kasi lawa showDialog( context: context, builder: (context) { @@ -113,7 +118,7 @@ void _showCalibrateCompassDialog(BuildContext context) { mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( + const Text( 'Move your phone in "figure 8 pattern".', style: TextStyle(fontSize: 16), ), diff --git a/lib/views/Qibla part/qibla_compass.dart b/lib/views/Qibla part/qibla_compass.dart index 70db332..015b103 100644 --- a/lib/views/Qibla part/qibla_compass.dart +++ b/lib/views/Qibla part/qibla_compass.dart @@ -8,6 +8,7 @@ import 'package:geolocator/geolocator.dart'; import '../Qibla%20part/location_error_widget.dart'; class QiblaCompass extends StatefulWidget { + const QiblaCompass({Key key}) : super(key: key); @override _QiblaCompassState createState() => _QiblaCompassState(); } @@ -32,8 +33,9 @@ class _QiblaCompassState extends State { child: StreamBuilder( stream: stream, builder: (context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) - return CupertinoActivityIndicator(); + if (snapshot.connectionState == ConnectionState.waiting) { + return const CupertinoActivityIndicator(); + } if (snapshot.data.enabled == true) { switch (snapshot.data.status) { case LocationPermission.always: @@ -76,8 +78,9 @@ class _QiblaCompassState extends State { await FlutterQiblah.requestPermissions(); final s = await FlutterQiblah.checkLocationStatus(); _locationStreamController.sink.add(s); - } else + } else { _locationStreamController.sink.add(locationStatus); + } } @override @@ -89,6 +92,7 @@ class _QiblaCompassState extends State { } class QiblahCompassWidget extends StatelessWidget { + QiblahCompassWidget({Key key}) : super(key: key); final _kaabaSvg = SvgPicture.asset('assets/qibla/kaaba.svg'); @override @@ -97,8 +101,9 @@ class QiblahCompassWidget extends StatelessWidget { return StreamBuilder( stream: FlutterQiblah.qiblahStream, builder: (_, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) - return CupertinoActivityIndicator(); + if (snapshot.connectionState == ConnectionState.waiting) { + return const CupertinoActivityIndicator(); + } final qiblahDirection = snapshot.data; var _angle = ((qiblahDirection.qiblah ?? 0) * (pi / 180) * -1); diff --git a/lib/views/Settings part/AboutPage.dart b/lib/views/Settings part/AboutPage.dart index 4ead689..d8ffc6a 100644 --- a/lib/views/Settings part/AboutPage.dart +++ b/lib/views/Settings part/AboutPage.dart @@ -4,352 +4,263 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:get_storage/get_storage.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:package_info/package_info.dart'; import 'package:provider/provider.dart'; -import 'package:timezone/timezone.dart' as tz; +import 'package:waktusolatmalaysia/views/debug_widgets.dart'; import '../../CONSTANTS.dart'; -import '../../locationUtil/LocationData.dart'; -import '../../main.dart'; -import '../../notificationUtil/notifications_helper.dart'; import '../../utils/launchUrl.dart'; import '../Settings%20part/settingsProvider.dart'; import '../contributionPage.dart'; import '../faq.dart'; class AboutAppPage extends StatelessWidget { - AboutAppPage(this.packageInfo); + const AboutAppPage({Key key, this.packageInfo}) : super(key: key); final PackageInfo packageInfo; final appLegalese = 'Copyright © 2020-2021 Fareez Iqmal'; - void showDebugDialog(BuildContext ctx) { - showDialog( - context: ctx, - builder: (context) => Dialog( - child: ListView( - shrinkWrap: true, - padding: EdgeInsets.all(8.0), - children: [ - Text( - 'Debug dialog (for dev)', - textAlign: TextAlign.center, - ), - ListTile( - title: Text('Prayer time API calls'), - subtitle: Text( - GetStorage().read(kStoredApiPrayerCall) ?? 'no calls yet'), - onLongPress: () { - Clipboard.setData(ClipboardData( - text: GetStorage().read(kStoredApiPrayerCall) ?? - 'no calls yet')) - .then((value) => Fluttertoast.showToast(msg: 'Copied url')); - }, - ), - ListTile( - title: Text('Last position'), - subtitle: Text(LocationData.position.toString() ?? 'no detect'), - onLongPress: () { - Clipboard.setData(ClipboardData( - text: LocationData.position.toString() ?? 'no detect')) - .then((value) => - Fluttertoast.showToast(msg: 'Copied position')); - }, - ), - ListTile( - title: Text('Send immediate test notification'), - onTap: () async { - await showDebugNotification(); - }, - ), - ListTile( - title: Text('Send alert test in one minute'), - subtitle: Text('Payload: $kPayloadDebug'), - onTap: () async { - await scheduleAlertNotification( - notifsPlugin: notifsPlugin, - title: 'debug payload', - id: 219, //randrom int haha - body: 'With payload', - payload: kPayloadDebug, - scheduledTime: tz.TZDateTime.now(tz.local).add( - Duration(minutes: 1), - )); - }, - ), - ListTile( - title: Text('Global location index'), - subtitle: Text('${GetStorage().read(kStoredGlobalIndex)}')), - ListTile( - title: Text('Last update notification'), - subtitle: Text(DateTime.fromMillisecondsSinceEpoch( - GetStorage().read(kStoredLastUpdateNotif)) - .toString()), - onLongPress: () { - Clipboard.setData(ClipboardData( - text: GetStorage() - .read(kStoredLastUpdateNotif) - .toString())) - .then((value) => - Fluttertoast.showToast(msg: 'Copied millis')); - }, - ), - ListTile( - title: Text('Number of scheduled notification'), - subtitle: - Text(GetStorage().read(kNumberOfNotifsScheduled).toString()), - ) - ], - ), - ), - ); - } - @override Widget build(BuildContext context) { bool isFirstTry = true; - return GestureDetector( - onTap: () => FocusScope.of(context).unfocus(), - child: Scaffold( - resizeToAvoidBottomInset: false, - appBar: AppBar( - title: Text('About App'), - centerTitle: true, - ), - body: SingleChildScrollView( - physics: BouncingScrollPhysics(), - child: Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 8.0), - child: Consumer( - builder: (context, setting, child) { - return Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - GestureDetector( - onLongPress: () { - if (isFirstTry && !setting.isDeveloperOption) { - Fluttertoast.showToast(msg: '(⌐■_■)'); - isFirstTry = false; - } else { - if (!setting.isDeveloperOption) { - Fluttertoast.showToast( - msg: 'Developer mode discovered', - toastLength: Toast.LENGTH_LONG, - backgroundColor: Colors.teal); + return Scaffold( + resizeToAvoidBottomInset: false, + appBar: AppBar( + title: const Text('About App'), + centerTitle: true, + ), + body: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 8.0), + child: Consumer( + builder: (context, setting, child) { + return Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + GestureDetector( + onLongPress: () { + if (isFirstTry && !setting.isDeveloperOption) { + Fluttertoast.showToast(msg: '(⌐■_■)'); + isFirstTry = false; + } else { + if (!setting.isDeveloperOption) { + Fluttertoast.showToast( + msg: 'Developer mode discovered', + toastLength: Toast.LENGTH_LONG, + backgroundColor: Colors.teal); - setting.isDeveloperOption = true; - } - showDebugDialog(context); + setting.isDeveloperOption = true; } - }, - child: Hero( - tag: kAppIconTag, - child: ClipRRect( - borderRadius: BorderRadius.circular(18), - child: CachedNetworkImage( - width: 70, - height: 70, - fit: BoxFit.scaleDown, - imageUrl: kAppIconUrl, - progressIndicatorBuilder: - (context, url, downloadProgress) => SizedBox( - height: 60, - width: 60, - child: CircularProgressIndicator( - value: downloadProgress.progress), - ), - errorWidget: (context, url, error) => - FaIcon(FontAwesomeIcons.exclamation), + // Show the debug dialog + showDialog( + context: context, + builder: (context) => DebugWidgets.debugDialog()); + } + }, + child: Hero( + tag: kAppIconTag, + child: ClipRRect( + borderRadius: BorderRadius.circular(18), + child: CachedNetworkImage( + width: 70, + height: 70, + fit: BoxFit.scaleDown, + imageUrl: kAppIconUrl, + progressIndicatorBuilder: + (context, url, downloadProgress) => Padding( + padding: const EdgeInsets.all(18.0), + child: CircularProgressIndicator( + value: downloadProgress.progress), ), + errorWidget: (context, url, error) => + const FaIcon(FontAwesomeIcons.exclamation), ), ), ), - Text( - '\nMPT 2021', + ), + const Text( + '\nMPT 2021', + textAlign: TextAlign.center, + ), + GestureDetector( + onLongPress: () { + Clipboard.setData( + ClipboardData(text: packageInfo.version)) + .then((value) => Fluttertoast.showToast( + msg: 'Copied version info')); + }, + child: Text( + 'Version ${packageInfo.version}\n', textAlign: TextAlign.center, + style: GoogleFonts.aBeeZee(fontWeight: FontWeight.bold), ), - GestureDetector( - onLongPress: () { - Clipboard.setData( - ClipboardData(text: packageInfo.version)) - .then((value) => Fluttertoast.showToast( - msg: 'Copied version info')); - }, - child: Text( - 'Version ${packageInfo.version}\n', + ), + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Theme.of(context).bottomAppBarColor, + borderRadius: BorderRadius.circular(10)), + child: RichText( textAlign: TextAlign.center, - style: GoogleFonts.aBeeZee(fontWeight: FontWeight.bold), - ), - ), - Container( - padding: EdgeInsets.all(8), - decoration: BoxDecoration( - color: Theme.of(context).bottomAppBarColor, - borderRadius: BorderRadius.circular(10)), - child: RichText( - textAlign: TextAlign.center, - text: TextSpan( - style: TextStyle( - color: Theme.of(context) - .textTheme - .bodyText2 - .color), - children: [ - TextSpan( - text: 'Prayer data are fetched from', - ), - TextSpan( - text: - ' Jabatan Kemajuan Islam Malaysia (e-solat)', - style: TextStyle(color: Colors.blueAccent), - recognizer: TapGestureRecognizer() - ..onTap = () { - LaunchUrl.normalLaunchUrl( - url: kSolatJakimLink); - }, - ), - TextSpan( - text: ' tunnelled through ', - ), - TextSpan( - text: 'mpti906 API', - style: TextStyle(color: Colors.blueAccent), - recognizer: TapGestureRecognizer() - ..onTap = () { - LaunchUrl.normalLaunchUrl( - url: kMptWebsiteLink); - }, - ), - TextSpan(text: '.') - ], - ), - )), - SizedBox(height: 8), - Card( - child: ListTile( - title: Text( - 'Contribution and Support', - textAlign: TextAlign.center, - ), - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => - ContributionPage())); - }, - ), - ), - Card( - child: ListTile( - title: Text( - 'Release Notes', - textAlign: TextAlign.center, + text: TextSpan( + style: TextStyle( + color: + Theme.of(context).textTheme.bodyText2.color), + children: [ + const TextSpan( + text: 'Prayer data are fetched from', + ), + TextSpan( + text: + ' Jabatan Kemajuan Islam Malaysia (e-solat)', + style: const TextStyle(color: Colors.blueAccent), + recognizer: TapGestureRecognizer() + ..onTap = () { + LaunchUrl.normalLaunchUrl( + url: kSolatJakimLink); + }, + ), + const TextSpan( + text: ' tunnelled through ', + ), + TextSpan( + text: 'mpti906 API', + style: const TextStyle(color: Colors.blueAccent), + recognizer: TapGestureRecognizer() + ..onTap = () { + LaunchUrl.normalLaunchUrl( + url: kMptWebsiteLink); + }, + ), + const TextSpan(text: '.') + ], ), - onTap: () { - LaunchUrl.normalLaunchUrl( - url: kReleaseNotesLink, useCustomTabs: true); - }, + )), + const SizedBox(height: 8), + Card( + child: ListTile( + title: const Text( + 'Contribution and Support', + textAlign: TextAlign.center, ), - ), - Card( - child: ListTile( - title: Text( - 'Frequently Asked Questions (FAQ)', - textAlign: TextAlign.center, - ), - onTap: () { - Navigator.push( + onTap: () { + Navigator.push( context, MaterialPageRoute( - builder: (context) => FaqPage(), - ), - ); - }, - ), + builder: (BuildContext context) => + const ContributionPage())); + }, ), - Card( - child: ListTile( - title: Text( - 'Open Source Licenses', - textAlign: TextAlign.center, - ), - onTap: () { - showLicensePage( - context: context, - applicationName: packageInfo.appName, - applicationVersion: packageInfo.version, - applicationIcon: Hero( - tag: kAppIconTag, - child: CachedNetworkImage( - width: 70, - imageUrl: kAppIconUrl, - progressIndicatorBuilder: - (context, url, downloadProgress) => - CircularProgressIndicator( - value: downloadProgress.progress), - errorWidget: (context, url, error) => - Icon(Icons.error), - ), - )); - }, + ), + Card( + child: ListTile( + title: const Text( + 'Release Notes', + textAlign: TextAlign.center, ), + onTap: () { + LaunchUrl.normalLaunchUrl( + url: kReleaseNotesLink, useCustomTabs: true); + }, ), - Card( - child: ListTile( - title: Text( - 'Privacy Policy', - textAlign: TextAlign.center, - ), - onTap: () { - LaunchUrl.normalLaunchUrl( - url: kPrivacyPolicyLink, useCustomTabs: true); - }, + ), + Card( + child: ListTile( + title: const Text( + 'Frequently Asked Questions (FAQ)', + textAlign: TextAlign.center, ), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const FaqPage(), + ), + ); + }, ), - Divider(height: 8, thickness: 2), - Card( - child: ListTile( - title: Text('More apps from me', - textAlign: TextAlign.center), - onTap: () { - LaunchUrl.normalLaunchUrl(url: kPlayStoreDevLink); - }), + ), + Card( + child: ListTile( + title: const Text( + 'Open Source Licenses', + textAlign: TextAlign.center, + ), + onTap: () { + showLicensePage( + context: context, + applicationName: packageInfo.appName, + applicationVersion: packageInfo.version, + applicationIcon: Hero( + tag: kAppIconTag, + child: CachedNetworkImage( + width: 70, + imageUrl: kAppIconUrl, + progressIndicatorBuilder: + (context, url, downloadProgress) => + CircularProgressIndicator( + value: downloadProgress.progress), + errorWidget: (context, url, error) => + const Icon(Icons.error), + ), + )); + }, ), - Card( - child: ListTile( - title: Text('Twitter', textAlign: TextAlign.center), - onTap: () { - LaunchUrl.normalLaunchUrl(url: kDevTwitter); - }, + ), + Card( + child: ListTile( + title: const Text( + 'Privacy Policy', + textAlign: TextAlign.center, ), + onTap: () { + LaunchUrl.normalLaunchUrl( + url: kPrivacyPolicyLink, useCustomTabs: true); + }, ), - Card( - child: ListTile( - title: Text( - 'Dev logs', - textAlign: TextAlign.center, - ), + ), + const Divider(height: 8, thickness: 2), + Card( + child: ListTile( + title: const Text('More apps from me', + textAlign: TextAlign.center), onTap: () { - LaunchUrl.normalLaunchUrl(url: kInstaStoryDevlog); - }, + LaunchUrl.normalLaunchUrl(url: kPlayStoreDevLink); + }), + ), + Card( + child: ListTile( + title: const Text('Twitter', textAlign: TextAlign.center), + onTap: () { + LaunchUrl.normalLaunchUrl(url: kDevTwitter); + }, + ), + ), + Card( + child: ListTile( + title: const Text( + 'Dev logs', + textAlign: TextAlign.center, ), + onTap: () { + LaunchUrl.normalLaunchUrl(url: kInstaStoryDevlog); + }, ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 30.0), - child: Opacity( - opacity: 0.58, - child: Text( - appLegalese, - textAlign: TextAlign.center, - ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 30.0), + child: Opacity( + opacity: 0.58, + child: Text( + appLegalese, + textAlign: TextAlign.center, ), ), - ], - ); - }, - ), + ), + ], + ); + }, ), ), ), diff --git a/lib/views/Settings part/NotificationSettingPage.dart b/lib/views/Settings part/NotificationSettingPage.dart index 7022e62..8966010 100644 --- a/lib/views/Settings part/NotificationSettingPage.dart +++ b/lib/views/Settings part/NotificationSettingPage.dart @@ -3,133 +3,73 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; +import 'troubleshoot_notif.dart'; import '../../CONSTANTS.dart'; import '../../utils/cupertinoSwitchListTile.dart'; -import '../../utils/navigator_pop.dart'; +import '../../utils/custom_navigator_pop.dart'; class NotificationPageSetting extends StatefulWidget { + const NotificationPageSetting({Key key}) : super(key: key); @override _NotificationPageSettingState createState() => _NotificationPageSettingState(); } class _NotificationPageSettingState extends State { - var prayerNotification = [true, true, true, true, true, true, true]; - bool isAnythingChanged = false; //effect the button - @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text('Notification'), + title: const Text('Notification'), centerTitle: true, - // actions: [ - // FlatButton( - // shape: CircleBorder(side: BorderSide(color: Colors.transparent)), - // textColor: Colors.white, - // onPressed: !isAnythingChanged - // ? null - // : () { - // print('Save button clicked'); - // print(prayerNotification); - // Navigator.pop(context); - // }, - // child: Text( - // 'SAVE', - // ), - // ) - // ], ), body: ListView( - padding: EdgeInsets.all(16), + padding: const EdgeInsets.all(16), children: [ - //Turned of for a while - // Text('Basic'), - // Card( - // child: Column(children: [ - // CupertinoSwitchListTile( - // title: Text(PrayerName.prayerName[0]), - // value: prayerNotification[0], - // onChanged: (bool value) { - // setState(() { - // isAnythingChanged = true; - // prayerNotification[0] = value; - // }); - // }), - // CupertinoSwitchListTile( - // title: Text(PrayerName.prayerName[1]), - // value: prayerNotification[1], - // onChanged: (bool value) { - // setState(() { - // isAnythingChanged = true; - // prayerNotification[1] = value; - // }); - // }), - // CupertinoSwitchListTile( - // title: Text(PrayerName.prayerName[2]), - // value: prayerNotification[2], - // onChanged: (bool value) { - // setState(() { - // isAnythingChanged = true; - // prayerNotification[2] = value; - // }); - // }), - // CupertinoSwitchListTile( - // title: Text(PrayerName.prayerName[3]), - // value: prayerNotification[3], - // onChanged: (bool value) { - // setState(() { - // isAnythingChanged = true; - // prayerNotification[3] = value; - // }); - // }), - // CupertinoSwitchListTile( - // title: Text(PrayerName.prayerName[4]), - // value: prayerNotification[4], - // onChanged: (bool value) { - // setState(() { - // isAnythingChanged = true; - // prayerNotification[4] = value; - // }); - // }), - // CupertinoSwitchListTile( - // title: Text(PrayerName.prayerName[5]), - // value: prayerNotification[5], - // onChanged: (bool value) { - // setState(() { - // isAnythingChanged = true; - // prayerNotification[5] = value; - // }); - // }), - // ]), - // ), - Padding(padding: const EdgeInsets.all(8.0), child: Text('Basic')), + const Padding(padding: EdgeInsets.all(8.0), child: Text('Basic')), Card( child: ListTile( - contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), - title: Text('App notification System Setting'), - subtitle: Text( + title: const Text('App notification System Setting'), + isThreeLine: true, + subtitle: const Text( 'Customize sound, toggle channel of prayer notification etc.'), - trailing: Icon(Icons.launch_rounded), + trailing: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + Icon(Icons.launch_rounded), + ], + ), onTap: () async { await AppSettings.openNotificationSettings(); }, ), ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Text('Troubleshooting')), - + const Padding( + padding: EdgeInsets.all(8.0), child: Text('Troubleshooting')), + Card( + child: ListTile( + title: const Text('Fix notification not showing on some devices'), + subtitle: const Text('Example: Xiaomi / Redmi, Realme etc.'), + onTap: () => Navigator.push( + context, + MaterialPageRoute( + builder: (_) => const TroubleshootNotif(), + )), + ), + ), + const Padding( + padding: EdgeInsets.all(8.0), + child: Text('Advanced troubleshooting')), Card( child: Container( padding: const EdgeInsets.symmetric(vertical: 8.0), child: CupertinoSwitchListTile( - title: Text('Limit notification scheduling'), - subtitle: Text( + activeColor: CupertinoColors.activeBlue, + title: const Text('Limit notification scheduling'), + subtitle: const Text( 'Enable if you experiencing an extreme slowdown in app. Notification will schedule weekly basis. Default is OFF (monthly).'), value: GetStorage().read(kStoredNotificationLimit), onChanged: (value) { - // print(value); setState(() { GetStorage().write(kStoredNotificationLimit, value); }); @@ -138,33 +78,33 @@ class _NotificationPageSettingState extends State { ), Card( child: ListTile( - title: Text('Force rescheduling notification...'), + title: const Text('Force rescheduling notification...'), onTap: () { showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - content: Text( - 'By default, notification will not rescheduled if the last scheduler ran is less than two days.\n\nTap proceed to start an immediate notification scheduling. The app will be restarted.'), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - }, - child: Text('Cancel'), - ), - TextButton( - onPressed: () { - GetStorage().write(kForceUpdateNotif, true); - print(GetStorage().read(kForceUpdateNotif)); - Get.forceAppUpdate(); - CustomNavigatorPop.popTo(context, 3); - }, - child: Text('Proceed'), - ) - ], - ); - }); + context: context, + builder: (BuildContext context) { + return AlertDialog( + content: const Text( + 'By default, notification will not rescheduled if the last scheduler ran is less than two days.\n\nTap proceed to start an immediate notification scheduling. The app will be restarted.'), + actions: [ + TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + GetStorage().write(kForceUpdateNotif, true); + Get.forceAppUpdate(); + CustomNavigatorPop.popTo(context, 3); + }, + child: const Text('Proceed'), + ) + ], + ); + }, + ); }, ), ), diff --git a/lib/views/Settings part/SettingsPage.dart b/lib/views/Settings part/SettingsPage.dart index 63e62a7..83fc5e9 100644 --- a/lib/views/Settings part/SettingsPage.dart +++ b/lib/views/Settings part/SettingsPage.dart @@ -6,34 +6,25 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get_storage/get_storage.dart'; import 'package:package_info/package_info.dart'; import 'package:provider/provider.dart'; -import '../../CONSTANTS.dart' as Constants; +import '../../CONSTANTS.dart' as constants; import '../../utils/cupertinoSwitchListTile.dart'; -import '../../utils/navigator_pop.dart'; +import '../../utils/custom_navigator_pop.dart'; import '../Settings%20part/AboutPage.dart'; import '../Settings%20part/NotificationSettingPage.dart'; import '../Settings%20part/settingsProvider.dart'; class SettingsPage extends StatefulWidget { + const SettingsPage({Key key}) : super(key: key); @override _SettingsPageState createState() => _SettingsPageState(); } class _SettingsPageState extends State { - String timeFormat; - - @override - void initState() { - super.initState(); - timeFormat = - GetStorage().read(Constants.kStoredTimeIs12) ? '12 hour' : '24 hour'; - print(timeFormat); - } - @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text('Settings'), + title: const Text('Settings'), centerTitle: true, ), body: Consumer( @@ -41,33 +32,32 @@ class _SettingsPageState extends State { return ListView( padding: const EdgeInsets.only(left: 16.0, right: 16.0), children: [ - Padding( - padding: const EdgeInsets.all(8.0), child: Text('Display')), + const Padding( + padding: EdgeInsets.all(8.0), child: Text('Display')), buildTimeFormat(setting), - SizedBox(height: 3), + const SizedBox(height: 3), buildShowOtherPrayerTime(setting), - SizedBox(height: 3), + const SizedBox(height: 3), buildFontSizeSetting(setting), - SizedBox(height: 3), + const SizedBox(height: 3), buildHijriOffset(setting), - Padding( - padding: const EdgeInsets.all(8.0), - child: Text('Notification')), + const Padding( + padding: EdgeInsets.all(8.0), child: Text('Notification')), buildNotificationSetting(context), - Padding( - padding: const EdgeInsets.all(8.0), child: Text('Sharing')), + const Padding( + padding: EdgeInsets.all(8.0), child: Text('Sharing')), buildSharingSetting(setting), - Padding(padding: const EdgeInsets.all(8.0), child: Text('More')), + const Padding(padding: EdgeInsets.all(8.0), child: Text('More')), buildAboutApp(context), - SizedBox(height: 3), + const SizedBox(height: 3), setting.isDeveloperOption ? buildVerboseDebugMode(context) - : SizedBox.shrink(), - SizedBox(height: 3), + : const SizedBox.shrink(), + const SizedBox(height: 3), setting.isDeveloperOption ? buildResetAllSetting(context) - : SizedBox.shrink(), - SizedBox(height: 40) + : const SizedBox.shrink(), + const SizedBox(height: 40) ], ); }, @@ -76,34 +66,12 @@ class _SettingsPageState extends State { } Card buildHijriOffset(SettingProvider setting) { - return Card( + return const Card( child: ListTile( title: Text('Hijri date offset'), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - TextButton( - style: TextButton.styleFrom( - minimumSize: Size(5, 5), - backgroundColor: CupertinoColors.tertiarySystemFill), - onPressed: setting.hijriOffset <= -2 - ? null - : () => setting.hijriOffset--, - child: FaIcon(FontAwesomeIcons.minus, size: 11)), - Container( - child: Text( - '${setting.hijriOffset} ${setting.hijriOffset == 1 ? 'day' : 'days'}'), - ), - TextButton( - style: TextButton.styleFrom( - minimumSize: Size(5, 5), - backgroundColor: CupertinoColors.tertiarySystemFill), - onPressed: setting.hijriOffset >= 2 - ? null - : () => setting.hijriOffset++, - child: FaIcon(FontAwesomeIcons.plus, size: 11)), - ], - ), + isThreeLine: true, + subtitle: Text( + 'Data will be fetched automatically from the server. Will remove this setting in future updates.'), ), ); } @@ -111,8 +79,8 @@ class _SettingsPageState extends State { Card buildFontSizeSetting(SettingProvider setting) { return Card( child: ListTile( - title: Padding( - padding: const EdgeInsets.only(top: 8), + title: const Padding( + padding: EdgeInsets.only(top: 8), child: Text('Font size'), ), subtitle: Slider( @@ -134,8 +102,8 @@ class _SettingsPageState extends State { Card buildSharingSetting(SettingProvider setting) { return Card( child: ListTile( - title: Padding( - padding: const EdgeInsets.only(top: 8.0, bottom: 2.0), + title: const Padding( + padding: EdgeInsets.only(top: 8.0, bottom: 2.0), child: Text( 'Specify the default behavior', ), @@ -145,11 +113,12 @@ class _SettingsPageState extends State { child: CupertinoSlidingSegmentedControl( groupValue: setting.sharingFormat, onValueChanged: (value) => setting.sharingFormat = value, - children: { + children: const { //defaulted to always ask 0: Text('Always ask'), 1: Text('Plain Text'), 2: Text('WhatsApp'), + 3: Text('Copy') }, ), ), @@ -160,40 +129,40 @@ class _SettingsPageState extends State { Card buildVerboseDebugMode(BuildContext context) { return Card( child: ListTile( - title: Text('Verbose debug mode'), - subtitle: Text('For developer purposes'), + title: const Text('Verbose debug mode'), + subtitle: const Text('For developer purposes'), onTap: () { showDialog( context: context, builder: (context) { return AlertDialog( - title: Text(GetStorage().read(Constants.kIsDebugMode) + title: Text(GetStorage().read(constants.kIsDebugMode) ? 'Verbose debug mode is ON' : 'Verbose debug mode is OFF'), contentPadding: const EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 1.0), - content: Text( + content: const Text( 'Toast message or similar will show throughout usage of the app'), actions: [ TextButton( onPressed: () { Navigator.of(context, rootNavigator: true).pop(); }, - child: Text('Cancel')), + child: const Text('Cancel')), TextButton( - onPressed: () { - print('PROCEED'); - //inverse if false then become true & vice versa - GetStorage().write(Constants.kIsDebugMode, - !GetStorage().read(Constants.kIsDebugMode)); - CustomNavigatorPop.popTo(context, 2); - }, - child: GetStorage().read(Constants.kIsDebugMode) - ? Text('Turn off') - : Text( - 'Turn on', - style: TextStyle(color: Colors.red), - )) + onPressed: () { + //inverse if false then become true & vice versa + GetStorage().write(constants.kIsDebugMode, + !GetStorage().read(constants.kIsDebugMode)); + CustomNavigatorPop.popTo(context, 2); + }, + child: GetStorage().read(constants.kIsDebugMode) + ? const Text('Turn off') + : const Text( + 'Turn on', + style: TextStyle(color: Colors.red), + ), + ) ], ); }, @@ -206,8 +175,8 @@ class _SettingsPageState extends State { Card buildResetAllSetting(BuildContext context) { return Card( child: ListTile( - title: Text('Reset all setting'), - subtitle: Text('Deletes all GetStorage() items'), + title: const Text('Reset all setting'), + subtitle: const Text('Deletes all GetStorage() items'), onTap: () { showDialog( context: context, @@ -215,18 +184,19 @@ class _SettingsPageState extends State { return AlertDialog( contentPadding: const EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 1.0), - content: Text('Proceed? The app will exit automatically.'), + content: + const Text('Proceed? The app will exit automatically.'), actions: [ TextButton( onPressed: () => Navigator.of(context, rootNavigator: true).pop(), - child: Text('Cancel')), + child: const Text('Cancel')), TextButton( onPressed: () => GetStorage().erase().then((value) => { Fluttertoast.showToast(msg: 'Reset done'), SystemNavigator.pop() }), - child: Text( + child: const Text( 'Yes. Reset all', style: TextStyle(color: Colors.red), )) @@ -244,25 +214,27 @@ class _SettingsPageState extends State { child: FutureBuilder( future: PackageInfo.fromPlatform(), builder: (context, AsyncSnapshot snapshot) { - if (snapshot.hasData) + if (snapshot.hasData) { return ListTile( title: Text('About app (Ver. ${snapshot.data.version})'), onTap: () { Navigator.push( context, MaterialPageRoute( - builder: (context) => AboutAppPage(snapshot.data), + builder: (context) => + AboutAppPage(packageInfo: snapshot.data), ), ); }, - subtitle: Text('Release Notes, Contribution, Twitter etc.'), + subtitle: const Text('Release Notes, Contribution, Twitter etc.'), ); - else - return ListTile( + } else { + return const ListTile( leading: SizedBox( child: CircularProgressIndicator(), ), ); + } }, ), ); @@ -271,12 +243,13 @@ class _SettingsPageState extends State { Card buildNotificationSetting(BuildContext context) { return Card( child: ListTile( - title: Text('Notification settings'), + title: const Text('Notification settings'), onTap: () async { Navigator.push( context, MaterialPageRoute( - builder: (BuildContext context) => NotificationPageSetting(), + builder: (BuildContext context) => + const NotificationPageSetting(), ), ); }, @@ -288,13 +261,10 @@ class _SettingsPageState extends State { return Card( child: CupertinoSwitchListTile( activeColor: CupertinoColors.activeBlue, - title: Text('Show other prayer times'), - subtitle: Text('Imsak, Syuruk, Dhuha'), + title: const Text('Show other prayer times'), + subtitle: const Text('Imsak, Syuruk, Dhuha'), onChanged: (bool value) { - setState(() { - setting.showOtherPrayerTime = value; - GetStorage().write(Constants.kStoredShowOtherPrayerTime, value); - }); + setting.showOtherPrayerTime = value; }, value: setting.showOtherPrayerTime, ), @@ -304,10 +274,10 @@ class _SettingsPageState extends State { Card buildTimeFormat(SettingProvider setting) { return Card( child: ListTile( - title: Text('Time format'), + title: const Text('Time format'), trailing: DropdownButton( - icon: Padding( - padding: const EdgeInsets.all(4.0), + icon: const Padding( + padding: EdgeInsets.all(4.0), child: FaIcon(FontAwesomeIcons.caretDown, size: 13), ), items: ['12 hour', '24 hour'] @@ -317,17 +287,9 @@ class _SettingsPageState extends State { child: Text(value), ); }).toList(), - onChanged: (String newValue) { - var is12 = newValue == '12 hour'; - // print('NewValue $newValue'); - setting.use12hour = is12; - GetStorage().write(Constants.kStoredTimeIs12, is12); - setState(() { - timeFormat = newValue; - print(GetStorage().read(Constants.kStoredTimeIs12)); - }); - }, - value: timeFormat, + onChanged: (String newValue) => + setting.use12hour = (newValue == '12 hour'), + value: setting.use12hour ? '12 hour' : '24 hour', ), ), ); diff --git a/lib/views/Settings part/ThemeController.dart b/lib/views/Settings part/ThemeController.dart index ccb0646..398628d 100644 --- a/lib/views/Settings part/ThemeController.dart +++ b/lib/views/Settings part/ThemeController.dart @@ -23,7 +23,6 @@ class ThemeController with ChangeNotifier { _themeMode = ThemeMode.values.firstWhere((e) => describeEnum(e) == themeText); } catch (e) { - print('err: $e'); _themeMode = ThemeMode.system; } } diff --git a/lib/views/Settings part/ThemePage.dart b/lib/views/Settings part/ThemePage.dart index f44129e..c7e433c 100644 --- a/lib/views/Settings part/ThemePage.dart +++ b/lib/views/Settings part/ThemePage.dart @@ -3,6 +3,7 @@ import 'package:provider/provider.dart'; import '../Settings%20part/ThemeController.dart'; class ThemesPage extends StatefulWidget { + const ThemesPage({Key key}) : super(key: key); @override _ThemesPageState createState() => _ThemesPageState(); } @@ -14,14 +15,14 @@ class _ThemesPageState extends State void initState() { super.initState(); _animationController = AnimationController( - vsync: this, duration: Duration(milliseconds: 1090)); + vsync: this, duration: const Duration(milliseconds: 1090)); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text('Theme'), + title: const Text('Theme'), centerTitle: true, ), body: Column( @@ -50,7 +51,7 @@ class _ThemesPageState extends State ), ), ), - Expanded(child: ThemesOption()), + const Expanded(child: ThemesOption()), ], ), ); @@ -99,7 +100,7 @@ class AnimatedMoon extends StatelessWidget { ), ), Transform.translate( - offset: Offset(40, 0), + offset: const Offset(40, 0), child: ScaleTransition( scale: _animationController.drive( Tween(begin: 0.0, end: 1.0).chain( @@ -123,12 +124,13 @@ class AnimatedMoon extends StatelessWidget { } class ThemesOption extends StatefulWidget { + const ThemesOption({Key key}) : super(key: key); @override _ThemesOptionState createState() => _ThemesOptionState(); } class _ThemesOptionState extends State { - Map _themeOptions = { + final Map _themeOptions = { 'System Theme': ThemeMode.system, 'Light Theme': ThemeMode.light, 'Dark Theme': ThemeMode.dark @@ -139,12 +141,13 @@ class _ThemesOptionState extends State { builder: (context, setting, child) { return ListView.builder( shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), + physics: const NeverScrollableScrollPhysics(), itemCount: _themeOptions.length, itemBuilder: (context, index) { return RadioListTile( title: Text(_themeOptions.keys.elementAt(index)), - subtitle: index == 0 ? Text('On supported device only') : null, + subtitle: + index == 0 ? const Text('On supported device only') : null, value: _themeOptions.values.elementAt(index), groupValue: setting.themeMode, onChanged: (value) { diff --git a/lib/views/Settings part/settingsProvider.dart b/lib/views/Settings part/settingsProvider.dart index bd59273..17c40ae 100644 --- a/lib/views/Settings part/settingsProvider.dart +++ b/lib/views/Settings part/settingsProvider.dart @@ -8,15 +8,6 @@ class SettingProvider with ChangeNotifier { bool _isDeveloperOption = GetStorage().read(kDiscoveredDeveloperOption); int _sharingFormat = GetStorage().read(kSharingFormat); double _fontSize = GetStorage().read(kFontSize); - int _hijriOffset = GetStorage().read(kHijriOffset); - - set hijriOffset(int value) { - _hijriOffset = value; - GetStorage().write(kHijriOffset, value); - notifyListeners(); - } - - int get hijriOffset => _hijriOffset; set prayerFontSize(double newValue) { _fontSize = newValue; @@ -36,6 +27,7 @@ class SettingProvider with ChangeNotifier { set use12hour(newValue) { _use12hour = newValue; + GetStorage().write(kStoredTimeIs12, newValue); notifyListeners(); } @@ -43,6 +35,7 @@ class SettingProvider with ChangeNotifier { set showOtherPrayerTime(newValue) { _showOtherPrayerTime = newValue; + GetStorage().write(kStoredShowOtherPrayerTime, newValue); notifyListeners(); } diff --git a/lib/views/Settings part/troubleshoot_notif.dart b/lib/views/Settings part/troubleshoot_notif.dart new file mode 100644 index 0000000..7eab72c --- /dev/null +++ b/lib/views/Settings part/troubleshoot_notif.dart @@ -0,0 +1,76 @@ +import 'package:app_settings/app_settings.dart'; +import 'package:flutter/material.dart'; +import '../../utils/launchUrl.dart'; + +class TroubleshootNotif extends StatelessWidget { + const TroubleshootNotif({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text.rich( + TextSpan( + style: TextStyle(height: 1.3), + children: [ + TextSpan(text: 'Some apps installed from the '), + TextSpan( + text: 'Google Play Store ', + style: TextStyle(fontWeight: FontWeight.bold)), + TextSpan(text: 'will disable '), + TextSpan( + text: 'Autostart ', + style: TextStyle(fontWeight: FontWeight.bold)), + TextSpan( + text: + 'by default. Due to this behaviour, prayer notification (sometimes) will not appear on your phone. '), + ], + ), + ), + const SizedBox(height: 5), + const Text.rich( + TextSpan( + style: TextStyle(height: 1.3), + children: [ + TextSpan(text: 'The solution is to enable '), + TextSpan( + text: 'Autostart ', + style: TextStyle(fontWeight: FontWeight.bold)), + TextSpan( + text: + 'for this app. Tap the button below to open App Setting, then find the Autostart option there to enable it.'), + ], + ), + ), + const SizedBox(height: 5), + Card( + child: ListTile( + title: const Text('Open App Setting'), + onTap: () => AppSettings.openAppSettings(), + trailing: const Icon(Icons.launch_rounded), + ), + ), + const Text( + '\nRelated Article:', + style: TextStyle(fontWeight: FontWeight.bold), + ), + TextButton( + onPressed: () { + LaunchUrl.normalLaunchUrl( + url: + 'https://telegra.ph/Notification-didnt-show-on-some-devices-07-31'); + }, + child: + const Text('Notification didn\'t show on some devices')) + ], + ), + ), + ), + ); + } +} diff --git a/lib/views/ZoneChooser.dart b/lib/views/ZoneChooser.dart index 093c16b..d7840a2 100644 --- a/lib/views/ZoneChooser.dart +++ b/lib/views/ZoneChooser.dart @@ -1,13 +1,15 @@ ///This widget is rendered as Location button at header part. ///Also handle the location selection import 'dart:async'; +import 'package:app_settings/app_settings.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; -import 'package:fluttertoast/fluttertoast.dart'; import 'package:geocoding/geocoding.dart'; import 'package:geolocator/geolocator.dart'; import 'package:get_storage/get_storage.dart'; import 'package:provider/provider.dart'; +import 'package:waktusolatmalaysia/utils/debug_toast.dart'; import '../CONSTANTS.dart'; import '../locationUtil/LocationData.dart'; import '../locationUtil/locationDatabase.dart'; @@ -23,7 +25,7 @@ class LocationChooser { ScaffoldMessenger.of(context).showSnackBar( SnackBar( behavior: SnackBarBehavior.floating, - duration: Duration(milliseconds: 1500), + duration: const Duration(milliseconds: 1500), content: Row( children: [ Icon( @@ -32,10 +34,8 @@ class LocationChooser { ? Colors.black87 : Colors.white70, ), - SizedBox( - width: 10, - ), - Text('Updated and saved'), + const SizedBox(width: 10), + const Text('Updated and saved'), ], ), ), @@ -43,29 +43,31 @@ class LocationChooser { } static Future _getAllLocationData() async { - var administrativeArea; - var locality; + String administrativeArea; + String locality; + String country; Position _pos = await LocationData.getCurrentLocation(); - if (GetStorage().read(kIsDebugMode)) { - Fluttertoast.showToast(msg: _pos.toString()); - } - + DebugToast.show(_pos.toString()); try { List placemarks = await placemarkFromCoordinates(_pos.latitude, _pos.longitude); var first = placemarks.first; - print( - '[_getAllLocationData] ${first.locality}, ${first.administrativeArea}'); administrativeArea = first.administrativeArea; locality = first.locality; + country = first.country; GetStorage().write(kStoredLocationLocality, locality); - } catch (e) { - print('[_getAllLocationData] Error: $e'); - GetStorage().write(kStoredLocationLocality, e.toString()); - Fluttertoast.showToast( - msg: 'Error $e occured. Sorry', backgroundColor: Colors.red); - throw e; + } on PlatformException catch (e) { + GetStorage().write(kStoredLocationLocality, e.message.toString()); + if (e.message.contains('A network error occurred')) { + throw 'A network error occurred trying to lookup the supplied coordinates.'; + } else { + rethrow; + } + } + DebugToast.show(country); + if (country.toLowerCase() != "malaysia") { + throw 'Outside Malaysia'; } var zone = LocationCoordinate.getJakimCodeNearby( @@ -88,10 +90,11 @@ class LocationChooser { borderRadius: BorderRadius.circular(8.0), ), child: Container( - padding: EdgeInsets.fromLTRB(8, 16, 8, 4), + padding: const EdgeInsets.fromLTRB(8, 16, 8, 4), height: 250, child: FutureBuilder( - future: _getAllLocationData().timeout(Duration(seconds: 12)), + future: + _getAllLocationData().timeout(const Duration(seconds: 12)), builder: (context, AsyncSnapshot snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { @@ -126,7 +129,7 @@ class LocationChooser { return FractionallySizedBox( heightFactor: 0.68, child: ClipRRect( - borderRadius: BorderRadius.only( + borderRadius: const BorderRadius.only( topLeft: Radius.circular(26.0), topRight: Radius.circular(26.0)), child: Container( @@ -168,7 +171,7 @@ class LocationChooser { static Widget locationBubble(BuildContext context, String shortCode) { return Container( - padding: EdgeInsets.all(4.0), + padding: const EdgeInsets.all(4.0), decoration: BoxDecoration( border: Border.all( color: Theme.of(context).brightness == Brightness.light @@ -186,13 +189,12 @@ class LocationChooser { {@required LocationCoordinateData location}) { var index = LocationDatabase.indexOfLocation(location.zone); - print('detected index is $index'); return Consumer( builder: (context, value, child) { return Column( mainAxisSize: MainAxisSize.min, children: [ - Expanded( + const Expanded( flex: 1, child: Center(child: Text('Your location')), ), @@ -202,7 +204,8 @@ class LocationChooser { child: Text( location.lokasi, textAlign: TextAlign.center, - style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + style: const TextStyle( + fontSize: 24, fontWeight: FontWeight.bold), ), )), Container( @@ -219,17 +222,15 @@ class LocationChooser { ), title: Text( LocationDatabase.getDaerah(index), - style: TextStyle(fontSize: 13), + style: const TextStyle(fontSize: 13), ), subtitle: Text( LocationDatabase.getNegeri(index), - style: TextStyle(fontSize: 11), + style: const TextStyle(fontSize: 11), ), ), ), - SizedBox( - height: 5, - ), + const SizedBox(height: 5), Expanded( flex: 1, child: Align( @@ -238,9 +239,7 @@ class LocationChooser { mainAxisSize: MainAxisSize.min, children: [ TextButton( - child: Text( - 'Set manually', - ), + child: const Text('Set manually'), onPressed: () async { bool res = await openLocationBottomSheet(context) ?? false; @@ -248,9 +247,7 @@ class LocationChooser { }, ), TextButton( - child: Text( - 'Set this location', - ), + child: const Text('Set this location'), onPressed: () { value.currentLocationIndex = index; onNewLocationSaved(context); @@ -270,13 +267,12 @@ class LocationChooser { static Widget onErrorWidget(BuildContext context, {@required String errorMessage, Function onRetryPressed}) { - print(errorMessage); return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.max, children: [ - Expanded( + const Expanded( flex: 1, child: Center( child: Text('Error'), @@ -291,38 +287,57 @@ class LocationChooser { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.fmd_bad_outlined, + size: 40, + color: Colors.red.shade300, + ), + Icon( + Icons + .signal_cellular_connected_no_internet_0_bar_outlined, + size: 40, + color: Colors.red.shade300, + ), + ], + ), Text.rich( - TextSpan( + const TextSpan( children: [ - TextSpan(text: 'Please make sure your '), TextSpan( - text: 'GPS is turned on.', + text: 'Check your ', + ), + TextSpan( + text: 'internet connection ', style: TextStyle( fontWeight: FontWeight.bold, ), ), - ], - ), - ), - Text('\nYou can try the following:'), - Text.rich( - TextSpan( - children: [ - TextSpan(text: 'Try closing'), + TextSpan(text: 'or '), TextSpan( - text: ' this ', + text: 'location services.', style: TextStyle( fontWeight: FontWeight.bold, ), ), - TextSpan(text: 'dialog and open it back, or'), ], ), + textAlign: TextAlign.center, + style: TextStyle(color: Colors.red.shade300), ), - Text.rich( + const Text.rich( TextSpan( children: [ - TextSpan(text: 'Set your location'), + TextSpan(text: '\nPlease'), + TextSpan( + text: ' retry ', + style: TextStyle( + fontWeight: FontWeight.bold, + ), + ), + TextSpan(text: 'or set your location'), TextSpan( text: ' manually.', style: TextStyle(fontWeight: FontWeight.bold), @@ -340,7 +355,7 @@ class LocationChooser { child: Text( errorMessage, textAlign: TextAlign.center, - style: TextStyle( + style: const TextStyle( color: Colors.red, fontSize: 10, fontStyle: FontStyle.italic), @@ -353,15 +368,21 @@ class LocationChooser { mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( - onPressed: () async { - bool res = - await openLocationBottomSheet(context) ?? false; - Navigator.pop(context, res); - }, - child: Text( - 'Set manually', - // style: TextStyle(color: Colors.teal.shade800), - )), + onPressed: () async => + await AppSettings.openLocationSettings(), + child: const Text( + 'Open Location Settings', + ), + ), + TextButton( + onPressed: () async { + bool res = await openLocationBottomSheet(context) ?? false; + Navigator.pop(context, res); + }, + child: const Text( + 'Set manually', + ), + ), ], ), ) @@ -378,12 +399,12 @@ class LocationChooser { Text( loadingMessage, textAlign: TextAlign.center, - style: TextStyle( + style: const TextStyle( fontSize: 24, ), ), - SizedBox(height: 24), - SpinKitPulse( + const SizedBox(height: 24), + const SpinKitPulse( color: Colors.teal, ) ], diff --git a/lib/views/appBody.dart b/lib/views/appBody.dart index 93d52d6..7bb94ab 100644 --- a/lib/views/appBody.dart +++ b/lib/views/appBody.dart @@ -1,20 +1,79 @@ import 'package:auto_size_text/auto_size_text.dart'; +import 'package:firebase_remote_config/firebase_remote_config.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:get_storage/get_storage.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:google_mobile_ads/google_mobile_ads.dart'; import 'package:hijri/hijri_calendar.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; +import 'package:waktusolatmalaysia/SECRETS.dart'; +import 'package:waktusolatmalaysia/views/Settings%20part/NotificationSettingPage.dart'; +import '../CONSTANTS.dart'; +import 'package:waktusolatmalaysia/views/debug_widgets.dart'; import '../locationUtil/locationDatabase.dart'; import '../locationUtil/location_provider.dart'; import '../utils/sizeconfig.dart'; import 'GetPrayerTime.dart'; -import 'Settings%20part/settingsProvider.dart'; import 'ZoneChooser.dart'; -class AppBody extends StatelessWidget { - final _dayFormat = DateFormat('EEEE').format(DateTime.now()); - final _dateFormat = DateFormat('dd MMM yyyy').format(DateTime.now()); +class AppBody extends StatefulWidget { + const AppBody({Key key}) : super(key: key); + + @override + State createState() => _AppBodyState(); +} + +class _AppBodyState extends State { + BannerAd _ad; + bool _isAdLoaded = false; + bool showFirstChild = true; + bool _showNotifPrompt; + + @override + void initState() { + super.initState(); + _showNotifPrompt = GetStorage().read(kShowNotifPrompt) && + GetStorage().read(kAppLaunchCount) > 5; + + MobileAds.instance.updateRequestConfiguration(RequestConfiguration( + testDeviceIds: [ + 'DF693493239FEF390746FE861B201FC3', + 'EB458550DFD9A5B6EF3D8FD1A0705EFA' + ])); + + _ad = BannerAd( + size: AdSize.banner, + adUnitId: kHomeBannerAdId, + listener: BannerAdListener( + onAdLoaded: (_) { + setState(() { + _isAdLoaded = true; + }); + }, + onAdFailedToLoad: (ad, error) { + // Releases an ad resource when it fails to load + ad.dispose(); + + throw error; + }, + ), + request: const AdRequest()); + _ad.load(); + } + + Future fetchRemoteConfig() async { + final RemoteConfig remoteConfig = RemoteConfig.instance; + await remoteConfig.setConfigSettings(RemoteConfigSettings( + fetchTimeout: const Duration(seconds: 15), + minimumFetchInterval: const Duration(hours: 8), + )); + // RemoteConfigValue(null, ValueSource.valueStatic); + await remoteConfig.fetchAndActivate(); + return remoteConfig; + } @override Widget build(BuildContext context) { @@ -25,80 +84,80 @@ class AppBody extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.start, children: [ Container( - width: SizeConfig.screenWidth, - height: SizeConfig.screenHeight / 6, + width: double.infinity, decoration: BoxDecoration( color: Theme.of(context).appBarTheme.color, - borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(40), - bottomRight: Radius.circular(40)), + borderRadius: + const BorderRadius.vertical(bottom: Radius.circular(40)), ), - padding: EdgeInsets.all(5.0), + padding: const EdgeInsets.fromLTRB(5, 0, 5, 10), child: Column( - crossAxisAlignment: CrossAxisAlignment.center, children: [ Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded( - child: Container( - child: Column( - children: [ - Container( - padding: EdgeInsets.symmetric( + child: Column( + children: [ + GestureDetector( + onLongPress: () { + // open to read current hijri offset + if (GetStorage() + .read(kDiscoveredDeveloperOption)) { + showDialog( + context: context, + builder: (context) { + return DebugWidgets.hijriDialog(); + }); + } + }, + child: Container( + padding: const EdgeInsets.symmetric( horizontal: 10.0, vertical: 8.0), decoration: BoxDecoration( color: Colors.white.withAlpha(70), borderRadius: BorderRadius.circular(8.0), ), - child: Consumer( - builder: (context, setting, child) { - var _hijriToday = HijriCalendar.fromDate( - DateTime.now().add( - Duration(days: setting.hijriOffset))); - return Column( - children: [ - Text( - _dayFormat, - style: GoogleFonts.spartan( - color: Colors.white), - ), - AutoSizeText( - _hijriToday.toFormat("dd MMMM yyyy"), - style: GoogleFonts.acme( - color: Colors.white, fontSize: 17), - stepGranularity: 1, + child: FutureBuilder( + future: fetchRemoteConfig(), + builder: (context, + AsyncSnapshot snapshot) { + /// Fetch data from server whenever possible + if (snapshot.hasData) { + int _offset = + snapshot.data.getInt('hijri_offset'); + GetStorage().write(kHijriOffset, _offset); + return DateWidget( + hijriOffset: Duration(days: _offset), + ); + } else { + return DateWidget( + hijriOffset: Duration( + days: GetStorage().read(kHijriOffset), ), - Text( - _dateFormat, - style: TextStyle( - color: Colors.teal.shade100, - fontSize: 12), - ), - ], - ); + ); + } }, ), ), - ], - ), + ), + ], ), ), Expanded( - // flex: 3, child: Consumer( builder: (context, value, child) { String shortCode = LocationDatabase.getJakimCode( value.currentLocationIndex); return Container( - margin: EdgeInsets.all(5.0), - padding: EdgeInsets.all(18.0), + margin: const EdgeInsets.all(5.0), + padding: const EdgeInsets.all(18.0), child: TextButton( style: TextButton.styleFrom( - padding: EdgeInsets.all(-5.0), + padding: const EdgeInsets.all(-5.0), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.0), - side: BorderSide(color: Colors.white), + side: const BorderSide(color: Colors.white), ), ), onPressed: () { @@ -130,7 +189,7 @@ class AppBody extends StatelessWidget { Text( ' ${shortCode.substring(0, 3).toUpperCase()} ${shortCode.substring(3, 5)}', style: GoogleFonts.montserrat( - textStyle: TextStyle( + textStyle: const TextStyle( color: Colors.white, fontSize: 13), ), ), @@ -146,19 +205,145 @@ class AppBody extends StatelessWidget { ], ), ), - SizedBox( - height: SizeConfig.screenHeight / 69, - ), - Padding( - padding: EdgeInsets.fromLTRB(SizeConfig.screenWidth / 10, 8.0, - SizeConfig.screenWidth / 10, 8.0), + showNotifPrompt(context), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 26), child: GetPrayerTime(), ), - SizedBox( - height: SizeConfig.screenHeight / 45, - ) + Builder(builder: (context) { + if (_isAdLoaded) { + return Container( + child: AdWidget(ad: _ad), + width: _ad.size.width.toDouble(), + height: _ad.size.height.toDouble() + 30, + alignment: Alignment.center); + } else { + return const SizedBox.shrink(); + } + }) ], ), ); } + + Builder showNotifPrompt(BuildContext context) { + return Builder(builder: (builder) { + if (_showNotifPrompt) { + return AnimatedCrossFade( + layoutBuilder: (topChild, topChildKey, bottomChild, bottomChildKey) { + return Stack( + clipBehavior: Clip.antiAlias, + alignment: Alignment.center, + children: [ + Positioned( + child: bottomChild, + top: 0, + key: bottomChildKey, + ), + Positioned( + child: topChild, + key: topChildKey, + ) + ], + ); + }, + duration: const Duration(milliseconds: 200), + crossFadeState: showFirstChild + ? CrossFadeState.showFirst + : CrossFadeState.showSecond, + firstChild: Column( + children: [ + const SizedBox(height: 10), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 4.0), + child: Opacity( + opacity: 0.8, + child: Text( + 'Did notification(s) from this app shows at prayer time?', + ), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + TextButton( + style: TextButton.styleFrom( + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + onPressed: () { + setState(() { + showFirstChild = false; + }); + Future.delayed(const Duration(seconds: 3)) + .then((value) => setState(() { + _showNotifPrompt = false; + GetStorage().write(kShowNotifPrompt, false); + })); + }, + child: const Text('Yes')), + TextButton( + style: TextButton.styleFrom( + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => + const NotificationPageSetting())); + }, + child: const Text('No')) + ], + ) + ], + ), + secondChild: const Padding( + padding: EdgeInsets.all(4.0), + child: Opacity( + opacity: 0.8, + child: Text( + 'Cool. Glad to hear that!', + textAlign: TextAlign.center, + ), + ), + ), + ); + } else { + return const SizedBox(height: 10); + } + }); + } +} + +class DateWidget extends StatelessWidget { + const DateWidget({ + Key key, + @required Duration hijriOffset, + }) : _hijriOffset = hijriOffset, + super(key: key); + + final Duration _hijriOffset; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Text( + DateFormat('EEEE').format(DateTime.now()), + style: GoogleFonts.spartan(color: Colors.white), + ), + AutoSizeText( + HijriCalendar.fromDate(DateTime.now().add(_hijriOffset)) + .toFormat("dd MMMM yyyy"), + style: GoogleFonts.acme(color: Colors.white, fontSize: 17), + stepGranularity: 1, + ), + Text( + DateFormat('dd MMM yyyy').format(DateTime.now()), + style: TextStyle(color: Colors.teal.shade100, fontSize: 12), + ), + ], + ); + } } diff --git a/lib/views/bottomAppBar.dart b/lib/views/bottomAppBar.dart index c3cf60a..645dff7 100644 --- a/lib/views/bottomAppBar.dart +++ b/lib/views/bottomAppBar.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:waktusolatmalaysia/views/prayer_full_table.dart'; import '../CONSTANTS.dart'; -import '../utils/copyAndShare.dart'; import '../utils/launchUrl.dart'; import 'Qibla%20part/qibla.dart'; import 'Settings%20part/SettingsPage.dart'; @@ -11,6 +11,7 @@ import 'Settings%20part/ThemePage.dart'; import 'feedbackPage.dart'; class MyBottomAppBar extends StatelessWidget { + const MyBottomAppBar({Key key}) : super(key: key); @override Widget build(BuildContext context) { // ignore: unused_local_variable @@ -19,36 +20,32 @@ class MyBottomAppBar extends StatelessWidget { : Colors.white60; return BottomAppBar( elevation: 18.0, - shape: CircularNotchedRectangle(), + shape: const CircularNotchedRectangle(), child: Row( children: [ IconButton( tooltip: 'Open menu', - icon: FaIcon(FontAwesomeIcons.bars), + icon: const FaIcon(FontAwesomeIcons.bars), color: iconColour, onPressed: () { menuModalBottomSheet(context); }), IconButton( - icon: FaIcon(FontAwesomeIcons.clone), - tooltip: 'Copy timetable', + icon: const FaIcon(FontAwesomeIcons.calendarAlt), + tooltip: 'Full timetable', color: iconColour, onPressed: () { - Clipboard.setData(ClipboardData(text: CopyAndShare.getMessage())) - .then( - (value) { - Fluttertoast.showToast(msg: 'Timetable copied'); - }, - ); + Navigator.of(context) + .push(MaterialPageRoute(builder: (_) => PrayerFullTable())); }, ), IconButton( - icon: FaIcon(FontAwesomeIcons.kaaba), + icon: const FaIcon(FontAwesomeIcons.kaaba), color: iconColour, tooltip: 'Kibla compass', onPressed: () { - Navigator.push( - context, MaterialPageRoute(builder: (context) => Qibla())); + Navigator.push(context, + MaterialPageRoute(builder: (context) => const Qibla())); }, ) ], @@ -60,47 +57,49 @@ class MyBottomAppBar extends StatelessWidget { void menuModalBottomSheet(BuildContext context) { showModalBottomSheet( context: context, - shape: RoundedRectangleBorder( + shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical( top: Radius.circular(25.0), ), ), builder: (BuildContext context) { return Container( - padding: EdgeInsets.all(4.0), + padding: const EdgeInsets.all(4.0), child: Wrap( children: [ ListTile( - title: Text('Themes'), - leading: FaIcon(FontAwesomeIcons.palette), + title: const Text('Themes'), + leading: const FaIcon(FontAwesomeIcons.palette), onTap: () { Navigator.pop(context); - Navigator.push(context, - MaterialPageRoute(builder: (context) => ThemesPage())); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const ThemesPage())); }, ), ListTile( - title: Text('Settings'), - leading: FaIcon(FontAwesomeIcons.cog), + title: const Text('Settings'), + leading: const FaIcon(FontAwesomeIcons.cog), onTap: () { Navigator.pop(context); Navigator.push( context, MaterialPageRoute( - builder: (context) => SettingsPage(), + builder: (context) => const SettingsPage(), ), ); }, ), - Divider( + const Divider( thickness: 1, height: 0.0, ), ListTile( - title: Text('Rate and review'), - leading: FaIcon(FontAwesomeIcons.solidStar), - trailing: - FaIcon(FontAwesomeIcons.externalLinkSquareAlt, size: 21), + title: const Text('Rate and review'), + leading: const FaIcon(FontAwesomeIcons.solidStar), + trailing: const FaIcon(FontAwesomeIcons.externalLinkSquareAlt, + size: 21), onTap: () { Navigator.pop(context); Fluttertoast.showToast( @@ -112,18 +111,18 @@ void menuModalBottomSheet(BuildContext context) { }, ), ListTile( - title: Text('MPT on web'), - leading: FaIcon(FontAwesomeIcons.chrome), - trailing: - FaIcon(FontAwesomeIcons.externalLinkSquareAlt, size: 21), + title: const Text('MPT on web'), + leading: const FaIcon(FontAwesomeIcons.chrome), + trailing: const FaIcon(FontAwesomeIcons.externalLinkSquareAlt, + size: 21), onTap: () { Navigator.pop(context); LaunchUrl.normalLaunchUrl(url: kWebappUrl); }, ), ListTile( - title: Text('Send feedback'), - leading: FaIcon(FontAwesomeIcons.solidCommentDots), + title: const Text('Send feedback'), + leading: const FaIcon(FontAwesomeIcons.solidCommentDots), onTap: () { Navigator.pop(context); openFeedbackDialog(context); @@ -141,7 +140,7 @@ void openFeedbackDialog(BuildContext context) { context, MaterialPageRoute( builder: (BuildContext context) { - return FeedbackPage(); + return const FeedbackPage(); }, fullscreenDialog: true), ); diff --git a/lib/views/contributionPage.dart b/lib/views/contributionPage.dart index f1a2074..bed4fb5 100644 --- a/lib/views/contributionPage.dart +++ b/lib/views/contributionPage.dart @@ -3,7 +3,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:share/share.dart'; -import '../CONSTANTS.dart' as Constants; +import '../CONSTANTS.dart' as constants; import '../utils/launchUrl.dart'; class ButtonContent { @@ -13,11 +13,12 @@ class ButtonContent { } class ContributionPage extends StatelessWidget { + const ContributionPage({Key key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text('Contribution and Support'), + title: const Text('Contribution and Support'), centerTitle: true, ), body: SingleChildScrollView( @@ -25,9 +26,9 @@ class ContributionPage extends StatelessWidget { padding: const EdgeInsets.all(16.0), child: Column( children: [ - Text( + const Text( 'Alhamdulillah. Thank you for your interest in donating to the Malaysia Prayer Time app. May Allah SWT rewards your kindness.'), - SizedBox( + const SizedBox( height: 8, ), MyCard( @@ -37,47 +38,47 @@ class ContributionPage extends StatelessWidget { buttonContent: [ ButtonContent('Share now', () { Share.share( - "Hi. I'm using the Malaysia Prayer Time app. It's fast and free.\nTry it now:\n${Constants.kPlayStoreListingShortLink} (Google Play)\n ${Constants.kWebappUrl} (Web app)", + "Hi. I'm using the Malaysia Prayer Time app. It's fast and free.\nTry it now:\n${constants.kPlayStoreListingShortLink} (Google Play)\n ${constants.kWebappUrl} (Web app)", subject: 'Sharing MPT App'); }) ]), - Divider(), + const Divider(), MyCard( title: 'Buy me a coffee?', description: - 'One cup of Nescafe is usually enough for me to code all night.\n\n${Constants.kBuyMeACoffeeLink.substring(12)}', //substring will remove 'https://www' stuffs. + 'One cup of Nescafe is usually enough for me to code all night.\n\n${constants.kBuyMeACoffeeLink.substring(12)}', //substring will remove 'https://www' stuffs. buttonContent: [ ButtonContent( 'Copy', - () => copyClipboard(Constants.kBuyMeACoffeeLink), + () => copyClipboard(constants.kBuyMeACoffeeLink), ), ButtonContent('Open', () { - LaunchUrl.normalLaunchUrl(url: Constants.kBuyMeACoffeeLink); + LaunchUrl.normalLaunchUrl(url: constants.kBuyMeACoffeeLink); }) ], ), MyCard( title: 'Direct support', description: - '${Constants.kMaybankAccNo} - Muhammad Fareez Iqmal (Maybank)', + '${constants.kMaybankAccNo} - Muhammad Fareez Iqmal (Maybank)', buttonContent: [ ButtonContent( 'Copy', - () => copyClipboard(Constants.kMaybankAccNo), + () => copyClipboard(constants.kMaybankAccNo), ), ], ), MyCard( title: 'Contribute to source', description: - 'MPT is now open source. Report any bugs or contribute directly to the source code. It is licensed under GNU GPLv3.', + 'MPT is open source. Report any bugs or contribute directly to the source code. It is licensed under GNU GPLv3.', buttonContent: [ ButtonContent( - 'Copy', () => copyClipboard(Constants.kGithubRepoLink)), + 'Copy', () => copyClipboard(constants.kGithubRepoLink)), ButtonContent( 'Open GitHub', () => LaunchUrl.normalLaunchUrl( - url: Constants.kGithubRepoLink)), + url: constants.kGithubRepoLink)), ], ), FittedBox( @@ -85,7 +86,7 @@ class ContributionPage extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(32.0), child: - SvgPicture.network(Constants.kDeveloperActivityImage), + SvgPicture.network(constants.kDeveloperActivityImage), )) ], ), @@ -126,10 +127,10 @@ class MyCard extends StatelessWidget { child: Column( children: [ ListTile( - contentPadding: EdgeInsets.all(0), + contentPadding: const EdgeInsets.all(0), title: Text( title, - style: TextStyle(fontSize: 18), + style: const TextStyle(fontSize: 18), ), subtitle: Text('\n$description'), ), diff --git a/lib/views/debug_widgets.dart b/lib/views/debug_widgets.dart new file mode 100644 index 0000000..853e09f --- /dev/null +++ b/lib/views/debug_widgets.dart @@ -0,0 +1,125 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:get_storage/get_storage.dart'; +import 'package:in_app_review/in_app_review.dart'; +import 'package:waktusolatmalaysia/locationUtil/LocationData.dart'; +import 'package:waktusolatmalaysia/notificationUtil/notifications_helper.dart'; +import 'package:timezone/timezone.dart' as tz; +import 'package:waktusolatmalaysia/utils/launchUrl.dart'; +import '../CONSTANTS.dart'; + +class DebugWidgets { + static Dialog debugDialog() { + return Dialog( + child: ListView( + shrinkWrap: true, + padding: const EdgeInsets.all(8.0), + children: [ + const Text( + 'Debug dialog (for dev)', + textAlign: TextAlign.center, + ), + ListTile( + title: const Text('Prayer time API calls'), + subtitle: + Text(GetStorage().read(kStoredApiPrayerCall) ?? 'no calls yet'), + onLongPress: () { + Clipboard.setData(ClipboardData( + text: GetStorage().read(kStoredApiPrayerCall) ?? + 'no calls yet')) + .then((value) => Fluttertoast.showToast(msg: 'Copied url')); + }, + ), + ListTile( + title: const Text('Last position'), + subtitle: Text(LocationData.position.toString() ?? 'no detect'), + onLongPress: () { + Clipboard.setData(ClipboardData( + text: LocationData.position.toString() ?? 'no detect')) + .then((value) => + Fluttertoast.showToast(msg: 'Copied position')); + }, + ), + ListTile( + title: const Text('Send immediate test notification'), + onTap: () async { + await showDebugNotification(); + }, + ), + ListTile( + title: const Text('Send alert test in one minute'), + subtitle: const Text('Payload: $kPayloadDebug'), + onTap: () async { + await scheduleAlertNotification( + notifsPlugin: FlutterLocalNotificationsPlugin(), + title: 'debug payload', + id: 219, //randrom int haha + body: 'With payload', + payload: kPayloadDebug, + scheduledTime: tz.TZDateTime.now(tz.local).add( + const Duration(minutes: 1), + )); + }, + ), + ListTile( + title: const Text('Global location index'), + subtitle: Text('${GetStorage().read(kStoredGlobalIndex)}')), + ListTile( + title: const Text('Last update notification'), + subtitle: Text(DateTime.fromMillisecondsSinceEpoch( + GetStorage().read(kStoredLastUpdateNotif)) + .toString()), + onLongPress: () { + Clipboard.setData(ClipboardData( + text: + GetStorage().read(kStoredLastUpdateNotif).toString())) + .then( + (value) => Fluttertoast.showToast(msg: 'Copied millis')); + }, + ), + ListTile( + title: const Text('Number of scheduled notification'), + subtitle: + Text(GetStorage().read(kNumberOfNotifsScheduled).toString()), + ), + ListTile( + title: const Text('Open in app review'), + subtitle: const Text( + 'This should not be used frequently as the underlying API\'s enforce strict quotas.'), + onTap: () async { + final InAppReview inAppReview = InAppReview.instance; + + if (await inAppReview.isAvailable()) { + inAppReview.requestReview(); + } + }, + ), + ], + ), + ); + } + + static Dialog hijriDialog() { + return Dialog( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Hijri offset: ${GetStorage().read(kHijriOffset)}', + style: const TextStyle(fontSize: 26), + ), + ElevatedButton( + onPressed: () { + LaunchUrl.normalLaunchUrl( + url: 'https://mpt-hijri-converter.web.app/'); + }, + child: const Text('Open MPT Hijri')) + ], + ), + )); + } +} diff --git a/lib/views/faq.dart b/lib/views/faq.dart index 712fa04..4d493dd 100644 --- a/lib/views/faq.dart +++ b/lib/views/faq.dart @@ -5,6 +5,7 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import '../utils/launchUrl.dart'; class FaqPage extends StatefulWidget { + const FaqPage({Key key}) : super(key: key); @override _FaqPageState createState() => _FaqPageState(); } @@ -21,7 +22,7 @@ class _FaqPageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text('FAQs'), + title: const Text('FAQs'), centerTitle: true, ), body: FutureBuilder( @@ -33,8 +34,12 @@ class _FaqPageState extends State { itemBuilder: (context, index) { return ListTile( title: Text(snapshot.data.docs[index]['title']), - subtitle: Text(snapshot.data.docs[index]['url']), - trailing: FaIcon( + subtitle: Text( + snapshot.data.docs[index]['url'], + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + trailing: const FaIcon( FontAwesomeIcons.externalLinkAlt, size: 18, ), @@ -47,12 +52,12 @@ class _FaqPageState extends State { }, ); } else if (snapshot.connectionState == ConnectionState.waiting) { - return Center( + return const Center( child: SpinKitCubeGrid( color: Colors.teal, )); } else { - return Center( + return const Center( child: Text(':")'), ); } diff --git a/lib/views/feedbackPage.dart b/lib/views/feedbackPage.dart index bc1ce10..427aaf6 100644 --- a/lib/views/feedbackPage.dart +++ b/lib/views/feedbackPage.dart @@ -8,24 +8,25 @@ import 'package:fluttertoast/fluttertoast.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get_storage/get_storage.dart'; import 'package:package_info/package_info.dart'; -import '../CONSTANTS.dart' as Constants; +import '../CONSTANTS.dart' as constants; import '../CONSTANTS.dart'; import '../locationUtil/LocationData.dart'; import '../utils/launchUrl.dart'; import 'faq.dart'; class FeedbackPage extends StatefulWidget { + const FeedbackPage({Key key}) : super(key: key); @override _FeedbackPageState createState() => _FeedbackPageState(); } class _FeedbackPageState extends State { - TextEditingController _messageController = TextEditingController(); - TextEditingController _emailController = TextEditingController(); + final TextEditingController _messageController = TextEditingController(); + final TextEditingController _emailController = TextEditingController(); + final GlobalKey _formKey = GlobalKey(); CollectionReference _reportsCollection; Map _deviceInfo; PackageInfo packageInfo; - GlobalKey _formKey = GlobalKey(); bool _isSendLoading = false; @override @@ -49,15 +50,15 @@ class _FeedbackPageState extends State { child: Scaffold( resizeToAvoidBottomInset: false, appBar: AppBar( - title: Text('Feedback'), + title: const Text('Feedback'), centerTitle: true, ), body: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ - SizedBox(height: 10), + const SizedBox(height: 10), Padding( - padding: EdgeInsets.all(10), + padding: const EdgeInsets.all(10), child: Form( key: _formKey, child: Column( @@ -67,13 +68,13 @@ class _FeedbackPageState extends State { validator: (value) => value.isNotEmpty ? null : 'Field can\'t be empty', controller: _messageController, - decoration: InputDecoration( + decoration: const InputDecoration( hintText: 'Please leave your feedback/report here', border: OutlineInputBorder()), keyboardType: TextInputType.text, textInputAction: TextInputAction.next, maxLines: 4), - SizedBox(height: 10), + const SizedBox(height: 10), TextFormField( validator: (value) => value.isNotEmpty ? EmailValidator.validate(value) @@ -81,10 +82,9 @@ class _FeedbackPageState extends State { : 'Incorrect email format' : null, controller: _emailController, - decoration: InputDecoration( + decoration: const InputDecoration( isDense: true, hintText: 'Your email address (optional)', - helperText: 'We may reach you if needed', border: OutlineInputBorder()), textInputAction: TextInputAction.done, keyboardType: TextInputType.emailAddress, @@ -93,97 +93,120 @@ class _FeedbackPageState extends State { ), ), ), - Container( - child: FutureBuilder( - future: DeviceInfoPlugin().androidInfo, - builder: (context, AsyncSnapshot snapshot) { - if (snapshot.hasData) { - _deviceInfo = { - 'Android version': snapshot.data.version.release, - 'Android Sdk': snapshot.data.version.sdkInt, - 'Device': snapshot.data.device, - 'Brand': snapshot.data.brand, - 'Model': snapshot.data.model, - 'Supported ABIs': snapshot.data.supportedAbis, - 'Screen Sizes': MediaQuery.of(context).size.toString() - }; + FutureBuilder( + future: DeviceInfoPlugin().androidInfo, + builder: (context, AsyncSnapshot snapshot) { + if (snapshot.hasData) { + _deviceInfo = { + 'Android version': snapshot.data.version.release, + 'Android Sdk': snapshot.data.version.sdkInt, + 'Device': snapshot.data.device, + 'Brand': snapshot.data.brand, + 'Model': snapshot.data.model, + 'Supported ABIs': snapshot.data.supportedAbis, + 'Screen Sizes': MediaQuery.of(context).size.toString() + }; - return CheckboxListTile( - secondary: OutlinedButton( - child: Text('View...'), - onPressed: () { - showDialog( - context: context, - builder: (context) { - return Dialog( - child: ListView.builder( - shrinkWrap: true, - itemCount: _deviceInfo.length + 1, - itemBuilder: (context, index) { - print(_deviceInfo.length); - if (index < _deviceInfo.length) { - var key = - _deviceInfo.keys.elementAt(index); - return ListTile( - leading: Column( - mainAxisAlignment: - MainAxisAlignment.center, - children: [Text(key)], - ), - title: - Text(_deviceInfo[key].toString()), - ); - } else { - return TextButton.icon( - icon: FaIcon(FontAwesomeIcons.copy, - size: 12), - onPressed: () { - Clipboard.setData(ClipboardData( - text: - _deviceInfo.toString())) - .then((value) => - Fluttertoast.showToast( - msg: 'Copied')); - }, - label: Text('Copy all')); - } - }, - )); - }, - ); - }, - ), - controlAffinity: ListTileControlAffinity.leading, - subtitle: Text('(Recommended)'), - title: Text( - 'Include device info', - ), - value: _logIsChecked, - onChanged: (value) { - setState(() { - _logIsChecked = value; - }); + return CheckboxListTile( + secondary: OutlinedButton( + child: const Text('View...'), + onPressed: () { + showDialog( + context: context, + builder: (context) { + return Dialog( + child: ListView.builder( + shrinkWrap: true, + itemCount: _deviceInfo.length + 1, + itemBuilder: (context, index) { + if (index < _deviceInfo.length) { + var key = _deviceInfo.keys.elementAt(index); + return ListTile( + leading: Column( + mainAxisAlignment: + MainAxisAlignment.center, + children: [Text(key)], + ), + title: Text(_deviceInfo[key].toString()), + ); + } else { + return TextButton.icon( + icon: const FaIcon( + FontAwesomeIcons.copy, + size: 12), + onPressed: () { + Clipboard.setData(ClipboardData( + text: _deviceInfo.toString())) + .then((value) => + Fluttertoast.showToast( + msg: 'Copied')); + }, + label: const Text('Copy all')); + } + }, + )); + }, + ); + }, + ), + controlAffinity: ListTileControlAffinity.leading, + subtitle: const Text('(Recommended)'), + title: const Text( + 'Include device info', + ), + value: _logIsChecked, + onChanged: (value) { + setState(() { + _logIsChecked = value; }); - } else if (snapshot.hasError) { - return Text('Trouble getting device info'); - } else { - return ListTile( - leading: SizedBox( - height: 15, - width: 15, - child: CircularProgressIndicator()), - title: Text('Getting device info...'), - ); - } - }, - ), + }); + } else if (snapshot.hasError) { + return const Text('Trouble getting device info'); + } else { + return const ListTile( + leading: SizedBox( + height: 15, + width: 15, + child: CircularProgressIndicator()), + title: Text('Getting device info...'), + ); + } + }, ), ElevatedButton.icon( onPressed: () async { + if (_emailController.text.isEmpty && + _messageController.text.contains('?')) { + var res = await showDialog( + context: context, + builder: (ctx) { + return AlertDialog( + content: const Text( + 'Looks like your message contain question(s). Provide your email so we can get back to you.\n\nWould you like to add your email?'), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(true); + }, + child: const Text('Send without email')), + TextButton( + onPressed: () { + Navigator.of(context).pop(false); + }, + child: const Text('Add email')) + ], + ); + }); + + // Cancel next operation for user to enter their email + if (!res) { + return; + } + } + if (_formKey.currentState.validate()) { FocusScope.of(context).unfocus(); setState(() => _isSendLoading = true); - print('Sending report...'); try { await _reportsCollection.add({ 'Date creation': FieldValue.serverTimestamp(), @@ -194,20 +217,20 @@ class _FeedbackPageState extends State { 'Prayer API called': GetStorage().read(kStoredApiPrayerCall) ?? 'no pray api called', - 'Position': - LocationData.position.toString() ?? 'no detect', + 'Position': (LocationData.position != null) + ? GeoPoint(LocationData.position.latitude, + LocationData.position.longitude) + : 'no detect', 'Locality': GetStorage().read(kStoredLocationLocality) ?? 'no locality called', 'Device info': _logIsChecked ? _deviceInfo : null, 'Hijri Offset': - GetStorage().read(Constants.kHijriOffset), + GetStorage().read(constants.kHijriOffset), }); setState(() => _isSendLoading = false); Fluttertoast.showToast( - msg: _emailController.text.isEmpty - ? 'Thank you for your valuable feedback.' - : 'Thank you for your valuable feedback. A copy of your response will be sent to your email', + msg: 'Thank you for your valuable feedback.', backgroundColor: Colors.green, toastLength: Toast.LENGTH_LONG) .then((value) => Navigator.pop(context)); @@ -217,42 +240,47 @@ class _FeedbackPageState extends State { backgroundColor: Colors.red, )); setState(() => _isSendLoading = false); - } catch (e) { - print('Err: $e'); } } }, - icon: FaIcon(FontAwesomeIcons.paperPlane, size: 13), + icon: !_isSendLoading + ? const FaIcon(FontAwesomeIcons.paperPlane, size: 13) + : const SizedBox.shrink(), label: _isSendLoading - ? SpinKitRotatingCircle(size: 12, color: Colors.white) - : Text('Send')), - Spacer(flex: 3), + ? const SizedBox( + height: 15, + width: 15, + child: CircularProgressIndicator( + color: Colors.white, + )) + : const Text('Send')), + const Spacer(flex: 3), Row( - children: [ + children: const [ Expanded(child: Divider()), Text('OR'), Expanded(child: Divider()) ], ), - SizedBox(height: 10), + const SizedBox(height: 10), ElevatedButton.icon( - icon: FaIcon(FontAwesomeIcons.questionCircle, size: 13), + icon: const FaIcon(FontAwesomeIcons.questionCircle, size: 13), onPressed: () { - Navigator.push( - context, MaterialPageRoute(builder: (_) => FaqPage())); + Navigator.push(context, + MaterialPageRoute(builder: (_) => const FaqPage())); }, - label: Text('Read Frequently Asked Questions (FAQ)'), + label: const Text('Read Frequently Asked Questions (FAQ)'), ), ElevatedButton.icon( style: ElevatedButton.styleFrom(primary: Colors.black), - icon: FaIcon(FontAwesomeIcons.github, size: 13), + icon: const FaIcon(FontAwesomeIcons.github, size: 13), onPressed: () { LaunchUrl.normalLaunchUrl( - url: Constants.kGithubRepoLink + '/issues'); + url: constants.kGithubRepoLink + '/issues'); }, - label: Text('Report / Follow issues on GitHub'), + label: const Text('Report / Follow issues on GitHub'), ), - Spacer(), + const Spacer(), ], ), ), diff --git a/lib/views/onboarding_page.dart b/lib/views/onboarding_page.dart index fc0c40f..d0b623a 100644 --- a/lib/views/onboarding_page.dart +++ b/lib/views/onboarding_page.dart @@ -7,6 +7,7 @@ import 'Settings%20part/ThemePage.dart'; import 'ZoneChooser.dart'; class OnboardingPage extends StatefulWidget { + const OnboardingPage({Key key}) : super(key: key); @override _OnboardingPageState createState() => _OnboardingPageState(); } @@ -15,7 +16,7 @@ class _OnboardingPageState extends State with SingleTickerProviderStateMixin { var pageDecoration = const PageDecoration( titleTextStyle: TextStyle(fontSize: 28.0, fontWeight: FontWeight.w700), - bodyTextStyle: const TextStyle(fontSize: 19.0), + bodyTextStyle: TextStyle(fontSize: 19.0), descriptionPadding: EdgeInsets.fromLTRB(16.0, 0.0, 16.0, 16.0), imagePadding: EdgeInsets.all(8.0), ); @@ -27,7 +28,7 @@ class _OnboardingPageState extends State void initState() { super.initState(); _animController = - AnimationController(vsync: this, duration: Duration(seconds: 1)); + AnimationController(vsync: this, duration: const Duration(seconds: 1)); } @override @@ -43,7 +44,7 @@ class _OnboardingPageState extends State ), decoration: pageDecoration, footer: _isDoneSetLocation - ? Text( + ? const Text( 'Location set. You can change location anytime by tapping the location code at upper right corner.', textAlign: TextAlign.center, ) @@ -58,7 +59,7 @@ class _OnboardingPageState extends State }); } }, - child: Text( + child: const Text( 'Set location', ), )), @@ -86,7 +87,7 @@ class _OnboardingPageState extends State }, ), ), - bodyWidget: ThemesOption(), + bodyWidget: const ThemesOption(), title: "Set your favourite theme", decoration: pageDecoration, ), @@ -118,8 +119,8 @@ class _OnboardingPageState extends State curve: Curves.fastLinearToSlowEaseIn, onDone: () { GetStorage().write(kIsFirstRun, false); - Navigator.pushReplacement( - context, MaterialPageRoute(builder: (builder) => MyHomePage())); + Navigator.pushReplacement(context, + MaterialPageRoute(builder: (builder) => const MyHomePage())); }); } } diff --git a/lib/views/prayer_full_table.dart b/lib/views/prayer_full_table.dart new file mode 100644 index 0000000..07fce14 --- /dev/null +++ b/lib/views/prayer_full_table.dart @@ -0,0 +1,119 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:get_storage/get_storage.dart'; +import 'package:intl/intl.dart'; +import 'package:waktusolatmalaysia/locationUtil/locationDatabase.dart'; +import 'package:waktusolatmalaysia/models/mpti906PrayerData.dart'; +import 'package:waktusolatmalaysia/utils/DateAndTime.dart'; +import 'package:waktusolatmalaysia/utils/mpt_fetch_api.dart'; + +import '../CONSTANTS.dart'; + +class PrayerFullTable extends StatelessWidget { + PrayerFullTable({Key key}) : super(key: key); + final int todayIndex = DateTime.now().day - 1; + final int month = DateTime.now().month; + final int year = DateTime.now().year; + final int locationIndex = GetStorage().read(kStoredGlobalIndex); + + @override + Widget build(BuildContext context) { + return Scaffold( + // https://stackoverflow.com/questions/51948252/hide-appbar-on-scroll-flutter + body: NestedScrollView( + headerSliverBuilder: (ctx, innerboxIsScrolled) { + return [ + SliverAppBar( + floating: true, + expandedHeight: 130, + flexibleSpace: FlexibleSpaceBar( + background: CachedNetworkImage( + imageUrl: + 'https://i2.wp.com/news.iium.edu.my/wp-content/uploads/2017/06/10982272836_29abebc100_b.jpg?ssl=1', + fit: BoxFit.cover, + color: Colors.black.withOpacity(0.4), + colorBlendMode: BlendMode.overlay, + ), + centerTitle: true, + title: Text( + '${DateAndTime.monthName(month)} timetable (${LocationDatabase.getJakimCode(locationIndex)})', + ), + ), + ) + ]; + }, + body: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: FutureBuilder( + future: MptApiFetch.fetchMpt( + LocationDatabase.getMptLocationCode( + locationIndex, + ), + ), + builder: (context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center( + child: SpinKitFadingCube(size: 35, color: Colors.teal)); + } else if (snapshot.hasError) { + return Text(snapshot.error); + } else if (snapshot.hasData) { + return DataTable( + columns: [ + 'Date', + 'Subuh', + 'Imsak', + 'Zohor', + 'Asar', + 'Maghrib', + 'Isyak' + ] + .map( + (text) => DataColumn( + label: Text( + text, + style: const TextStyle(fontStyle: FontStyle.italic), + )), + ) + .toList(), + rows: + List.generate(snapshot.data.data.times.length, (index) { + return DataRow(selected: index == todayIndex, cells: [ + DataCell( + Text( + '${index + 1} / ${snapshot.data.data.month} (${DateFormat('E').format(DateTime(year, month, index + 1))})', + style: TextStyle( + fontWeight: index == todayIndex + ? FontWeight.bold + : null), + ), + ), + ...snapshot.data.data.times[index].map((day) { + return DataCell(Center( + child: Opacity( + opacity: (index < todayIndex) ? 0.55 : 1.0, + child: Text(DateAndTime.toTimeReadable(day, true), + style: TextStyle( + fontWeight: index == todayIndex + ? FontWeight.bold + : null)), + ), + )); + }).toList(), + ]); + }), + ); + } else { + return const Text('ERROR!'); + } + }, + ), + ), + ), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index f06663c..d86b236 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -8,13 +8,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.1.1" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.6.1" + version: "2.7.0" auto_size_text: dependency: "direct main" description: @@ -35,7 +42,21 @@ packages: name: cached_network_image url: "https://pub.dartlang.org" source: hosted - version: "3.0.0" + version: "3.1.0" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" characters: dependency: transitive description: @@ -63,21 +84,21 @@ packages: name: cloud_firestore url: "https://pub.dartlang.org" source: hosted - version: "2.2.2" + version: "2.4.0" cloud_firestore_platform_interface: dependency: transitive description: name: cloud_firestore_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "5.1.2" + version: "5.3.0" cloud_firestore_web: dependency: transitive description: name: cloud_firestore_web url: "https://pub.dartlang.org" source: hosted - version: "2.1.2" + version: "2.3.0" collection: dependency: transitive description: @@ -92,6 +113,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.1" + dbus: + dependency: transitive + description: + name: dbus + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.4" device_info: dependency: "direct main" description: @@ -147,7 +175,7 @@ packages: name: firebase_core url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.4.0" firebase_core_platform_interface: dependency: transitive description: @@ -162,6 +190,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" + firebase_remote_config: + dependency: "direct main" + description: + name: firebase_remote_config + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.0+3" + firebase_remote_config_platform_interface: + dependency: transitive + description: + name: firebase_remote_config_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.0+3" flutter: dependency: "direct main" description: flutter @@ -195,20 +237,34 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" flutter_local_notifications: dependency: "direct main" description: name: flutter_local_notifications url: "https://pub.dartlang.org" source: hosted - version: "6.0.0" + version: "6.1.0" + flutter_local_notifications_linux: + dependency: transitive + description: + name: flutter_local_notifications_linux + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0+1" flutter_local_notifications_platform_interface: dependency: transitive description: name: flutter_local_notifications_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "4.0.0" + version: "4.0.1" flutter_qiblah: dependency: "direct main" description: @@ -241,7 +297,7 @@ packages: name: flutter_web_browser url: "https://pub.dartlang.org" source: hosted - version: "0.14.0" + version: "0.15.0" flutter_web_plugins: dependency: transitive description: flutter @@ -281,35 +337,35 @@ packages: name: geolocator url: "https://pub.dartlang.org" source: hosted - version: "7.2.0+1" + version: "7.3.1" geolocator_platform_interface: dependency: transitive description: name: geolocator_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.2.0" + version: "2.3.2" geolocator_web: dependency: transitive description: name: geolocator_web url: "https://pub.dartlang.org" source: hosted - version: "2.0.3" + version: "2.0.6" get: dependency: transitive description: name: get url: "https://pub.dartlang.org" source: hosted - version: "4.1.4" + version: "4.3.0" get_storage: dependency: "direct main" description: name: get_storage url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "2.0.3" google_fonts: dependency: "direct main" description: @@ -317,15 +373,22 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + google_mobile_ads: + dependency: "direct main" + description: + name: google_mobile_ads + url: "https://pub.dartlang.org" + source: hosted + version: "0.13.2" hijri: dependency: "direct main" description: path: "." ref: HEAD - resolved-ref: "86cc31daab46e6e9b15a0580521ea53942568333" + resolved-ref: "7616970a477cb006e3d51fc2ed62ea43fc3f0146" url: "https://github.com/iqfareez/hijri_date.git" source: git - version: "3.0.1" + version: "3.0.2" http: dependency: "direct main" description: @@ -340,6 +403,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.0.0" + in_app_review: + dependency: "direct main" + description: + name: in_app_review + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + in_app_review_platform_interface: + dependency: transitive + description: + name: in_app_review_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" intl: dependency: "direct main" description: @@ -368,6 +445,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.6.3" + lints: + dependency: transitive + description: + name: lints + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" matcher: dependency: transitive description: @@ -381,7 +465,7 @@ packages: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.4.0" mime: dependency: transitive description: @@ -479,7 +563,7 @@ packages: name: petitparser url: "https://pub.dartlang.org" source: hosted - version: "4.1.0" + version: "4.2.0" platform: dependency: transitive description: @@ -493,14 +577,21 @@ packages: name: plugin_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.1" + posix: + dependency: transitive + description: + name: posix + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" process: dependency: transitive description: name: process url: "https://pub.dartlang.org" source: hosted - version: "4.2.1" + version: "4.2.3" provider: dependency: "direct main" description: @@ -596,7 +687,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.3.0" + version: "0.4.0" timezone: dependency: transitive description: @@ -617,7 +708,7 @@ packages: name: url_launcher url: "https://pub.dartlang.org" source: hosted - version: "6.0.7" + version: "6.0.9" url_launcher_linux: dependency: transitive description: @@ -638,7 +729,7 @@ packages: name: url_launcher_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.3" + version: "2.0.4" url_launcher_web: dependency: transitive description: @@ -673,7 +764,7 @@ packages: name: win32 url: "https://pub.dartlang.org" source: hosted - version: "2.2.4" + version: "2.2.5" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index ad1f915..cb7d4d6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: App waktu solat seluruh Malaysia publish_to: "none" # Remove this line if you wish to publish to pub.dev -version: 1.20.127+59 +version: 1.26.143+66 environment: sdk: ">=2.7.0 <3.0.0" @@ -12,7 +12,6 @@ dependencies: flutter: sdk: flutter - # cupertino_icons: ^0.1.3 http: ^0.13.1 intl: ^0.17.0 google_fonts: ^2.0.0 @@ -33,7 +32,7 @@ dependencies: flutter_local_notifications: ^6.0.0 app_settings: ^4.1.0 isolate_handler: ^1.0.0 - flutter_web_browser: ^0.14.0 + flutter_web_browser: ^0.15.0 geocoding: ^2.0.0 font_awesome_flutter: ^9.0.0 flutter_qiblah: ^2.0.1 @@ -41,10 +40,14 @@ dependencies: firebase_core: ^1.0.2 email_validator: ^2.0.1 introduction_screen: ^2.1.0 + firebase_remote_config: ^0.10.0+3 + in_app_review: ^2.0.2 + google_mobile_ads: ^0.13.2 dev_dependencies: flutter_test: sdk: flutter + flutter_lints: ^1.0.4 flutter: # The following line ensures that the Material Icons font is diff --git a/scratch.dart b/scratch.dart index 761b0aa..2a39b2f 100644 --- a/scratch.dart +++ b/scratch.dart @@ -1,14 +1,12 @@ // import 'package:geolocator/geolocator.dart'; +//ignore_for_file: avoid_print import 'package:intl/intl.dart'; void main() { - for (var i = -10; i < 10; i++) { - print('current value is $i'); - if (i <= 2 && i >= -2) { - print(' is valid'); - } - } + var now = DateTime.now(); + // print(month); + print(now.year); } Uri uriHttps() { diff --git a/test/widget_test.dart b/test/widget_test.dart index 4410bb8..62a986e 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -12,7 +12,7 @@ import 'package:waktusolatmalaysia/main.dart'; void main() { testWidgets('MPT smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. - await tester.pumpWidget(MyApp()); + await tester.pumpWidget(const MyApp()); // Verify that our counter starts at 0. // expect(find.textContaining('🇲🇾 Prayer Time'), findsOneWidget); diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index ddfcf7c..d9fdd53 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #include "generated_plugin_registrant.h" #include diff --git a/windows/flutter/generated_plugin_registrant.h b/windows/flutter/generated_plugin_registrant.h index 9846246..dc139d8 100644 --- a/windows/flutter/generated_plugin_registrant.h +++ b/windows/flutter/generated_plugin_registrant.h @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_