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_