diff --git a/android/app/build.gradle b/android/app/build.gradle
index 42892698..f3b514ba 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -35,7 +35,7 @@ if (keystorePropertiesFile.exists()) {
}
android {
- compileSdkVersion 33
+ compileSdkVersion 34
lintOptions {
disable 'InvalidPackage'
@@ -73,6 +73,9 @@ android {
signingConfig signingConfigs.release
}
}
+ buildFeatures {
+ viewBinding true
+ }
}
flutter {
@@ -83,7 +86,7 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
// https://github.com/flutter/flutter/issues/110658
- implementation "androidx.window:window:1.0.0"
+ implementation "androidx.window:window:1.0.0"
implementation 'androidx.window:window-java:1.0.0'
-
+ implementation 'com.google.code.gson:gson:2.10.1'
}
\ No newline at end of file
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 101dd166..0abb531c 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -1,3 +1,4 @@
+
-
-
+
+
+
-
-
+
+
-
+
@@ -57,4 +86,5 @@
android:name="flutterEmbedding"
android:value="2" />
-
+
+
\ No newline at end of file
diff --git a/android/app/src/main/java/live/iqfareez/waktusolatmalaysia/SolatHorizontalWidget.kt b/android/app/src/main/java/live/iqfareez/waktusolatmalaysia/SolatHorizontalWidget.kt
new file mode 100644
index 00000000..ab85a046
--- /dev/null
+++ b/android/app/src/main/java/live/iqfareez/waktusolatmalaysia/SolatHorizontalWidget.kt
@@ -0,0 +1,233 @@
+package live.iqfareez.waktusolatmalaysia
+
+import android.app.AlarmManager
+import android.app.PendingIntent
+import android.appwidget.AppWidgetManager
+import android.appwidget.AppWidgetProvider
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.SharedPreferences
+import android.os.Build
+import android.util.Log
+import android.view.View
+import android.widget.RemoteViews
+import es.antonborri.home_widget.HomeWidgetPlugin
+import org.json.JSONObject
+import java.text.SimpleDateFormat
+import java.time.YearMonth
+import java.time.format.DateTimeFormatter
+import java.util.Calendar
+import java.util.Date
+import java.util.Locale
+import java.util.TimeZone
+
+
+private const val ACTION_SCHEDULED_UPDATE = "live.iqfareez.waktusolatmalaysia.SCHEDULED_UPDATE"
+private const val LOG_TAG = "MPT_Widget_Horizontal"
+
+/**
+ * Implementation of App Widget functionality.
+ */
+class SolatHorizontalWidget : AppWidgetProvider() {
+ override fun onUpdate(
+ context: Context,
+ appWidgetManager: AppWidgetManager,
+ appWidgetIds: IntArray,
+ ) {
+ val widgetData = HomeWidgetPlugin.getData(context)
+ // There may be multiple widgets active, so update all of them
+ for (appWidgetId in appWidgetIds) {
+ Log.i(LOG_TAG, "onUpdate: SolatHorizontalWidget called")
+ updateAppWidget(context, appWidgetManager, appWidgetId, widgetData, R.layout.solat_horizontal_widget)
+ }
+
+ scheduleNextUpdate(context);
+ }
+
+ override fun onEnabled(context: Context) {
+ // Enter relevant functionality for when the first widget is created
+ }
+
+ override fun onDisabled(context: Context) {
+ // Enter relevant functionality for when the last widget is disabled
+ }
+
+ override fun onReceive(context: Context, intent: Intent) {
+ super.onReceive(context, intent);
+ if (intent.action.equals(ACTION_SCHEDULED_UPDATE)) {
+ val manager = AppWidgetManager.getInstance(context)
+ val ids =
+ manager.getAppWidgetIds(ComponentName(context, SolatHorizontalWidget::class.java))
+ onUpdate(context, manager, ids)
+ }
+ }
+}
+
+internal fun updateAppWidget(
+ context: Context,
+ appWidgetManager: AppWidgetManager,
+ appWidgetId: Int,
+ widgetData: SharedPreferences,
+ layoutId: Int
+) {
+ // Construct the RemoteViews object
+ val views = RemoteViews(context.packageName, layoutId)
+
+ val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)
+ val pendingIntent = PendingIntent.getActivity(
+ context, 0, launchIntent,
+ PendingIntent.FLAG_IMMUTABLE
+ )
+
+ // Set the click listener for the widget
+ views.setOnClickPendingIntent(android.R.id.background, pendingIntent)
+
+ // Parse the JSON in SharedPreferences
+ val prayerData = widgetData.getString("prayer_data", null);
+
+ // If data not available, display outdated layout
+ if (prayerData == null) {
+ views.setViewVisibility(R.id.outdated_text, View.VISIBLE);
+ views.setViewVisibility(R.id.prayer_layout, View.GONE);
+ return;
+ }
+
+ val parsed = JSONObject(prayerData)
+
+ // if the data is outdated (the month & year doesn't match), show outdated layout
+ if (!isDateValid("${parsed.get("month")}-${parsed.get("year")}")) {
+ Log.i(LOG_TAG, "updateAppWidget: Data ${parsed.get("month")}-${parsed.get("year")} is invalid");
+ views.setViewVisibility(R.id.outdated_text, View.VISIBLE);
+ views.setViewVisibility(R.id.prayer_layout, View.GONE);
+ return;
+ }
+
+ Log.i(LOG_TAG, "updateAppWidget: Reading SP json ${parsed.get("zone")}, ${parsed.get("month")}-${parsed.get("year")} ")
+
+ val prayers = parsed.getJSONArray("prayers")
+
+ val calendar = Calendar.getInstance()
+ val todayIndex = calendar.get(Calendar.DAY_OF_WEEK) - 1;
+ val todayPrayer: JSONObject = prayers.get(todayIndex) as JSONObject;
+
+ val subuhTime = todayPrayer.getLong("fajr")
+ val zohorTime = todayPrayer.getLong("dhuhr")
+ val asarTime = todayPrayer.getLong("asr")
+ val maghribTime = todayPrayer.getLong("maghrib")
+ val isyakTime = todayPrayer.getLong("isha")
+
+ val gmt8TimeZone = TimeZone.getTimeZone("GMT+8")
+ val timeFormat = SimpleDateFormat("h:mm a")
+ timeFormat.timeZone = gmt8TimeZone
+
+ fun formatTime(timeInMillis: Long): String {
+ val date = Date(timeInMillis)
+ return timeFormat.format(date)
+ }
+
+ val formattedSubuhTime = formatTime(subuhTime)
+ val formattedZohorTime = formatTime(zohorTime)
+ val formattedAsarTime = formatTime(asarTime)
+ val formattedMaghribTime = formatTime(maghribTime)
+ val formattedIsyakTime = formatTime(isyakTime)
+
+ val dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.getDefault())
+ dateFormat.timeZone = TimeZone.getTimeZone("Asia/Kuala_Lumpur") // Set Malaysia timezone
+ val formattedDate = dateFormat.format(Date())
+
+ val widgetTitle = "${parsed.get("zone")}: ${widgetData.getString("widget_title", null)}"
+
+ // Set content
+ views.setTextViewText(R.id.widget_date, formattedDate)
+
+ views.setTextViewText(
+ R.id.widget_title, widgetTitle
+ ?: "Please open app to set widget data"
+ )
+ views.setTextViewText(
+ R.id.subuh_time, formattedSubuhTime
+ )
+ views.setTextViewText(
+ R.id.zuhur_time, formattedZohorTime
+ )
+ views.setTextViewText(
+ R.id.asar_time, formattedAsarTime
+ )
+ views.setTextViewText(
+ R.id.maghrib_time, formattedMaghribTime
+ )
+ views.setTextViewText(
+ R.id.isyak_time, formattedIsyakTime
+ )
+
+ // Instruct the widget manager to update the widget
+ appWidgetManager.updateAppWidget(appWidgetId, views)
+}
+
+// Credit to: https://stackoverflow.com/a/37901697/13617136
+private fun scheduleNextUpdate(context: Context) {
+ val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
+ // Substitute AppWidget for whatever you named your AppWidgetProvider subclass
+ val intent = Intent(context, SolatHorizontalWidget::class.java)
+ intent.setAction(ACTION_SCHEDULED_UPDATE)
+ val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
+
+ // Get a calendar instance for midnight tomorrow.
+ val midnight: Calendar = Calendar.getInstance()
+ midnight.set(Calendar.HOUR_OF_DAY, 0)
+ midnight.set(Calendar.MINUTE, 0)
+ // Schedule one second after midnight, to be sure we are in the right day next time this
+ // method is called. Otherwise, we risk calling onUpdate multiple times within a few
+ // milliseconds
+ midnight.set(Calendar.SECOND, 1)
+ midnight.set(Calendar.MILLISECOND, 0)
+ midnight.add(Calendar.DAY_OF_YEAR, 1)
+
+ // For API 19 and later, set may fire the intent a little later to save battery,
+ // setExact ensures the intent goes off exactly at midnight.
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
+ alarmManager[AlarmManager.RTC_WAKEUP, midnight.getTimeInMillis()] = pendingIntent
+ } else {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ if (alarmManager.canScheduleExactAlarms()) {
+ alarmManager.setExact(
+ AlarmManager.RTC_WAKEUP,
+ midnight.timeInMillis,
+ pendingIntent
+ )
+ } else {
+ alarmManager.setExact(
+ AlarmManager.RTC_WAKEUP,
+ midnight.timeInMillis,
+ pendingIntent
+ )
+ }
+ }
+ }
+}
+
+// Function to check the validity of the given month and year
+fun isDateValid(jsonDate: String): Boolean {
+ // The YearMonth.parse only accepts month with title-case eg Jan, Feb etc.
+ fun toTitleCase(input: String): String {
+ return input.lowercase().replaceFirstChar { it.uppercase() }
+ }
+
+ val jsonFixed = toTitleCase(jsonDate);
+ return try {
+ // Parse the JSON date into YearMonth
+ val formatter = DateTimeFormatter.ofPattern("MMM-yyyy")
+ val date = YearMonth.parse(jsonFixed, formatter)
+
+ // Get the current month and year
+ val currentMonthYear = YearMonth.now()
+
+ // Check if the parsed date is after or equal to the current month and year
+ !date.isBefore(currentMonthYear) && !date.isAfter(currentMonthYear);
+ } catch (e: Exception) {
+ // Handle parsing or other exceptions
+ e.printStackTrace()
+ false
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/main/java/live/iqfareez/waktusolatmalaysia/SolatVerticalWidget.kt b/android/app/src/main/java/live/iqfareez/waktusolatmalaysia/SolatVerticalWidget.kt
new file mode 100644
index 00000000..b605f557
--- /dev/null
+++ b/android/app/src/main/java/live/iqfareez/waktusolatmalaysia/SolatVerticalWidget.kt
@@ -0,0 +1,97 @@
+package live.iqfareez.waktusolatmalaysia
+
+import android.app.AlarmManager
+import android.app.PendingIntent
+import android.appwidget.AppWidgetManager
+import android.appwidget.AppWidgetProvider
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import android.util.Log
+import es.antonborri.home_widget.HomeWidgetPlugin
+import java.util.Calendar
+
+private const val ACTION_SCHEDULED_UPDATE = "live.iqfareez.waktusolatmalaysia.SCHEDULED_UPDATE"
+private const val LOG_TAG = "MPT_Widget_Vertical"
+
+/**
+ * Implementation of App Widget functionality.
+ */
+class SolatVerticalWidget : AppWidgetProvider() {
+ override fun onUpdate(
+ context: Context,
+ appWidgetManager: AppWidgetManager,
+ appWidgetIds: IntArray
+ ) {
+ val widgetData = HomeWidgetPlugin.getData(context)
+ // There may be multiple widgets active, so update all of them
+ for (appWidgetId in appWidgetIds) {
+ Log.i(LOG_TAG, "onUpdate: SolatVerticalWidget called")
+ // call from SolatHorizontalWidget
+ updateAppWidget(context, appWidgetManager, appWidgetId, widgetData, R.layout.solat_vertical_widget)
+ }
+
+ scheduleNextUpdate(context);
+ }
+
+ override fun onEnabled(context: Context) {
+ // Enter relevant functionality for when the first widget is created
+ }
+
+ override fun onDisabled(context: Context) {
+ // Enter relevant functionality for when the last widget is disabled
+ }
+
+ override fun onReceive(context: Context, intent: Intent) {
+ super.onReceive(context, intent)
+
+ if (intent.action.equals(ACTION_SCHEDULED_UPDATE)) {
+ val manager = AppWidgetManager.getInstance(context)
+ val ids =
+ manager.getAppWidgetIds(ComponentName(context, SolatVerticalWidget::class.java))
+ onUpdate(context, manager, ids)
+ }
+ }
+}
+
+private fun scheduleNextUpdate(context: Context) {
+ val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
+ // Substitute AppWidget for whatever you named your AppWidgetProvider subclass
+ val intent = Intent(context, SolatVerticalWidget::class.java)
+ intent.setAction(ACTION_SCHEDULED_UPDATE)
+ val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
+
+ // Get a calendar instance for midnight tomorrow.
+ val midnight: Calendar = Calendar.getInstance()
+ midnight.set(Calendar.HOUR_OF_DAY, 0)
+ midnight.set(Calendar.MINUTE, 0)
+ // Schedule one second after midnight, to be sure we are in the right day next time this
+ // method is called. Otherwise, we risk calling onUpdate multiple times within a few
+ // milliseconds
+ midnight.set(Calendar.SECOND, 1)
+ midnight.set(Calendar.MILLISECOND, 0)
+ midnight.add(Calendar.DAY_OF_YEAR, 1)
+
+ // For API 19 and later, set may fire the intent a little later to save battery,
+ // setExact ensures the intent goes off exactly at midnight.
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
+ alarmManager[AlarmManager.RTC_WAKEUP, midnight.getTimeInMillis()] = pendingIntent
+ } else {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ if (alarmManager.canScheduleExactAlarms()) {
+ alarmManager.setExact(
+ AlarmManager.RTC_WAKEUP,
+ midnight.timeInMillis,
+ pendingIntent
+ )
+ } else {
+ alarmManager.setExact(
+ AlarmManager.RTC_WAKEUP,
+ midnight.timeInMillis,
+ pendingIntent
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/main/res/drawable-v21/app_widget_background.xml b/android/app/src/main/res/drawable-v21/app_widget_background.xml
new file mode 100644
index 00000000..785445c6
--- /dev/null
+++ b/android/app/src/main/res/drawable-v21/app_widget_background.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/drawable-v21/app_widget_inner_view_background.xml b/android/app/src/main/res/drawable-v21/app_widget_inner_view_background.xml
new file mode 100644
index 00000000..007e2872
--- /dev/null
+++ b/android/app/src/main/res/drawable-v21/app_widget_inner_view_background.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/drawable/solat_horizontal_preview.png b/android/app/src/main/res/drawable/solat_horizontal_preview.png
new file mode 100644
index 00000000..3b1cd0a1
Binary files /dev/null and b/android/app/src/main/res/drawable/solat_horizontal_preview.png differ
diff --git a/android/app/src/main/res/drawable/solat_vertical_preview.png b/android/app/src/main/res/drawable/solat_vertical_preview.png
new file mode 100644
index 00000000..e83b6337
Binary files /dev/null and b/android/app/src/main/res/drawable/solat_vertical_preview.png differ
diff --git a/android/app/src/main/res/layout/solat_horizontal_widget.xml b/android/app/src/main/res/layout/solat_horizontal_widget.xml
new file mode 100644
index 00000000..13377658
--- /dev/null
+++ b/android/app/src/main/res/layout/solat_horizontal_widget.xml
@@ -0,0 +1,201 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/layout/solat_horizontal_widget_preview.xml b/android/app/src/main/res/layout/solat_horizontal_widget_preview.xml
new file mode 100644
index 00000000..907c6563
--- /dev/null
+++ b/android/app/src/main/res/layout/solat_horizontal_widget_preview.xml
@@ -0,0 +1,190 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/layout/solat_vertical_widget.xml b/android/app/src/main/res/layout/solat_vertical_widget.xml
new file mode 100644
index 00000000..522fd4f9
--- /dev/null
+++ b/android/app/src/main/res/layout/solat_vertical_widget.xml
@@ -0,0 +1,218 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/layout/solat_vertical_widget_preview.xml b/android/app/src/main/res/layout/solat_vertical_widget_preview.xml
new file mode 100644
index 00000000..bbd3a781
--- /dev/null
+++ b/android/app/src/main/res/layout/solat_vertical_widget_preview.xml
@@ -0,0 +1,197 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/values-en/strings.xml b/android/app/src/main/res/values-en/strings.xml
index 48a22124..5cdb8b74 100644
--- a/android/app/src/main/res/values-en/strings.xml
+++ b/android/app/src/main/res/values-en/strings.xml
@@ -1,4 +1,5 @@
Prayer Time
+ View 5 daily prayers at a glance
\ No newline at end of file
diff --git a/android/app/src/main/res/values-ms/strings.xml b/android/app/src/main/res/values-ms/strings.xml
index 1cef5811..4af55c21 100644
--- a/android/app/src/main/res/values-ms/strings.xml
+++ b/android/app/src/main/res/values-ms/strings.xml
@@ -1,4 +1,5 @@
Waktu Solat
+ Paparan 5 solat harian
\ No newline at end of file
diff --git a/android/app/src/main/res/values-night-v31/themes.xml b/android/app/src/main/res/values-night-v31/themes.xml
new file mode 100644
index 00000000..f253c9da
--- /dev/null
+++ b/android/app/src/main/res/values-night-v31/themes.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/values-v21/styles.xml b/android/app/src/main/res/values-v21/styles.xml
new file mode 100644
index 00000000..0b35f7d8
--- /dev/null
+++ b/android/app/src/main/res/values-v21/styles.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/values-v31/styles.xml b/android/app/src/main/res/values-v31/styles.xml
new file mode 100644
index 00000000..6b133976
--- /dev/null
+++ b/android/app/src/main/res/values-v31/styles.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/values-v31/themes.xml b/android/app/src/main/res/values-v31/themes.xml
new file mode 100644
index 00000000..badd306a
--- /dev/null
+++ b/android/app/src/main/res/values-v31/themes.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/values/attrs.xml b/android/app/src/main/res/values/attrs.xml
new file mode 100644
index 00000000..7781ac86
--- /dev/null
+++ b/android/app/src/main/res/values/attrs.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml
index 2790dd59..c60e0c62 100644
--- a/android/app/src/main/res/values/colors.xml
+++ b/android/app/src/main/res/values/colors.xml
@@ -1,4 +1,8 @@
#16A085
+ #FFE1F5FE
+ #FF81D4FA
+ #FF039BE5
+ #FF01579B
\ No newline at end of file
diff --git a/android/app/src/main/res/values/dimens.xml b/android/app/src/main/res/values/dimens.xml
new file mode 100644
index 00000000..4db8c590
--- /dev/null
+++ b/android/app/src/main/res/values/dimens.xml
@@ -0,0 +1,10 @@
+
+
+
+
+ 0dp
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index 48a22124..5cdb8b74 100644
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -1,4 +1,5 @@
Prayer Time
+ View 5 daily prayers at a glance
\ No newline at end of file
diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml
index 1f83a33f..1a5a2be2 100644
--- a/android/app/src/main/res/values/styles.xml
+++ b/android/app/src/main/res/values/styles.xml
@@ -15,4 +15,14 @@
+
+
+
+
diff --git a/android/app/src/main/res/values/themes.xml b/android/app/src/main/res/values/themes.xml
new file mode 100644
index 00000000..dcd8899e
--- /dev/null
+++ b/android/app/src/main/res/values/themes.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/xml/solat_horizontal_widget_info.xml b/android/app/src/main/res/xml/solat_horizontal_widget_info.xml
new file mode 100644
index 00000000..be90a6e2
--- /dev/null
+++ b/android/app/src/main/res/xml/solat_horizontal_widget_info.xml
@@ -0,0 +1,14 @@
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/xml/solat_vertical_widget_info.xml b/android/app/src/main/res/xml/solat_vertical_widget_info.xml
new file mode 100644
index 00000000..7a4622aa
--- /dev/null
+++ b/android/app/src/main/res/xml/solat_vertical_widget_info.xml
@@ -0,0 +1,14 @@
+
+
\ No newline at end of file
diff --git a/android/build.gradle b/android/build.gradle
index 0adf2074..52a98273 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -9,7 +9,6 @@ buildscript {
classpath 'com.android.tools.build:gradle:7.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.4'
-
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.1'
}
}
diff --git a/lib/constants.dart b/lib/constants.dart
index df6c58f5..27519de0 100644
--- a/lib/constants.dart
+++ b/lib/constants.dart
@@ -24,6 +24,8 @@ const kTimetableShowOneThird = "tbOneThird";
const kTasbihCount = "tasbihCount";
const kTasbihGradientColour = "tasbihColours";
const kNoAdsStartTime = "noAdsStartTime";
+// store current user location name to be consumed by homescreen widget
+const kWidgetLocation = "widgetLocation";
// API base
const kApiBaseUrl = 'mpt-server.vercel.app';
diff --git a/lib/models/mpt_server_solat.dart b/lib/models/mpt_server_solat.dart
index 9f3d5d96..31635608 100644
--- a/lib/models/mpt_server_solat.dart
+++ b/lib/models/mpt_server_solat.dart
@@ -87,14 +87,14 @@ class Prayers {
Map toJson() {
final Map data = {};
- data['isha'] = isha;
- data['syuruk'] = syuruk;
- data['day'] = day;
- data['dhuhr'] = dhuhr;
- data['maghrib'] = maghrib;
- data['fajr'] = fajr;
- data['asr'] = asr;
- data['hijri'] = hijri;
+ data['isha'] = isha.millisecondsSinceEpoch;
+ data['syuruk'] = syuruk.millisecondsSinceEpoch;
+ data['day'] = day.millisecondsSinceEpoch;
+ data['dhuhr'] = dhuhr.millisecondsSinceEpoch;
+ data['maghrib'] = maghrib.millisecondsSinceEpoch;
+ data['fajr'] = fajr.millisecondsSinceEpoch;
+ data['asr'] = asr.millisecondsSinceEpoch;
+ data['hijri'] = hijri.toString();
return data;
}
}
diff --git a/lib/utils/homescreen.dart b/lib/utils/homescreen.dart
new file mode 100644
index 00000000..095f8cbd
--- /dev/null
+++ b/lib/utils/homescreen.dart
@@ -0,0 +1,19 @@
+import 'dart:convert';
+
+import 'package:home_widget/home_widget.dart';
+
+/// Homescreen widget utility helper
+class Homescreen {
+ /// Save the whole month prayer time data as json
+ static Future savePrayerDataAndUpdateWidget(
+ Map json, String widgetTitle) async {
+ await HomeWidget.saveWidgetData('prayer_data', jsonEncode(json));
+ await HomeWidget.saveWidgetData('widget_title', widgetTitle);
+ await HomeWidget.saveWidgetData('last_updated', DateTime.now().toString());
+
+ // Trigger widgets update to show the new data
+ await HomeWidget.updateWidget(name: 'SolatHorizontalWidget');
+
+ await HomeWidget.updateWidget(name: 'SolatVerticalWidget');
+ }
+}
diff --git a/lib/utils/prayer_data_handler.dart b/lib/utils/prayer_data_handler.dart
index 2afacc49..620b3f4f 100644
--- a/lib/utils/prayer_data_handler.dart
+++ b/lib/utils/prayer_data_handler.dart
@@ -1,5 +1,10 @@
+import 'package:get_storage/get_storage.dart';
+
+import '../constants.dart';
+import '../location_utils/location_database.dart';
import '../models/mpt_server_solat.dart';
import '../networking/mpt_fetch_api.dart';
+import 'homescreen.dart';
final int _day = DateTime.now().day;
@@ -10,6 +15,15 @@ class PrayDataHandler {
/// Returns the hijri date
static Future init(String zone) async {
_mptServerSolat = await MptApiFetch.fetchMpt(zone);
+
+ // Alang2 init, we save the data to widget
+ var widgetLocation = GetStorage().read(kWidgetLocation);
+ if (widgetLocation == null || widgetLocation.isEmpty) {
+ widgetLocation = LocationDatabase.daerah(zone);
+ }
+ Homescreen.savePrayerDataAndUpdateWidget(
+ _mptServerSolat!.toJson(), widgetLocation!);
+
return today().hijri.toString();
}
diff --git a/lib/views/app_body.dart b/lib/views/app_body.dart
index a10b83b7..d421702e 100644
--- a/lib/views/app_body.dart
+++ b/lib/views/app_body.dart
@@ -125,6 +125,7 @@ class _AppBodyState extends State {
errorMessage: snapshot.error.toString(),
onRetryPressed: () => setState(() {}));
}
+
// display the list of prayer timee
return const PrayTimeList();
}),
diff --git a/lib/views/debug_dialog.dart b/lib/views/debug_dialog.dart
index 1fd2cd1d..d95b51ed 100644
--- a/lib/views/debug_dialog.dart
+++ b/lib/views/debug_dialog.dart
@@ -6,6 +6,7 @@ import 'package:restart_app/restart_app.dart';
import '../constants.dart';
import '../location_utils/location_data.dart';
+import 'dev/widget_data_page.dart';
class DebugDialog extends StatelessWidget {
const DebugDialog({super.key});
@@ -56,6 +57,15 @@ class DebugDialog extends StatelessWidget {
title: Text('Notification debug'),
subtitle: Text('Available on notification settings page'),
),
+ ListTile(
+ title: const Text('Homescreen widget debug'),
+ subtitle: const Text('View saved widget data'),
+ onTap: () {
+ Navigator.of(context).push(
+ MaterialPageRoute(builder: (_) => const WidgetDataPage()),
+ );
+ },
+ ),
],
),
);
diff --git a/lib/views/dev/widget_data_page.dart b/lib/views/dev/widget_data_page.dart
new file mode 100644
index 00000000..08afac16
--- /dev/null
+++ b/lib/views/dev/widget_data_page.dart
@@ -0,0 +1,74 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:fluttertoast/fluttertoast.dart';
+import 'package:home_widget/home_widget.dart';
+
+class WidgetDataPage extends StatelessWidget {
+ const WidgetDataPage({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text('Saved Widget Data'),
+ ),
+ body: ListView(
+ padding: const EdgeInsets.all(16),
+ children: [
+ FutureBuilder(
+ future: HomeWidget.getWidgetData('last_updated'),
+ builder: (context, snapshot) {
+ return _WidgetDataItem(
+ title: 'last_updated',
+ data: snapshot.data.toString(),
+ );
+ }),
+ FutureBuilder(
+ future: HomeWidget.getWidgetData('widget_title'),
+ builder: (context, snapshot) {
+ return _WidgetDataItem(
+ title: 'widget_title',
+ data: snapshot.data.toString(),
+ );
+ }),
+ FutureBuilder(
+ future: HomeWidget.getWidgetData('prayer_data'),
+ builder: (context, snapshot) {
+ return _WidgetDataItem(
+ title: 'prayer_data',
+ data: snapshot.data.toString(),
+ );
+ }),
+ ],
+ ),
+ );
+ }
+}
+
+class _WidgetDataItem extends StatelessWidget {
+ const _WidgetDataItem({required this.title, required this.data});
+
+ final String title;
+ final String data;
+
+ @override
+ Widget build(BuildContext context) {
+ return InkWell(
+ onLongPress: () {
+ Clipboard.setData(ClipboardData(text: data));
+ Fluttertoast.showToast(msg: 'Copied $title');
+ },
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ Text(
+ title,
+ style: const TextStyle(fontSize: 18),
+ textAlign: TextAlign.start,
+ ),
+ Text(data),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/views/zone_chooser.dart b/lib/views/zone_chooser.dart
index 5850665d..e61730e4 100644
--- a/lib/views/zone_chooser.dart
+++ b/lib/views/zone_chooser.dart
@@ -141,6 +141,8 @@ class LocationChooser {
Provider.of(context, listen: false).currentLocationCode =
newZone.jakimCode;
onNewLocationSaved(context);
+ print(newZone.daerah);
+ GetStorage().write(kWidgetLocation, newZone.daerah);
}
}
@@ -241,6 +243,8 @@ class ZoneSuccessWidget extends StatelessWidget {
onPressed: () {
value.currentLocationCode = coordinateData.zone;
LocationChooser.onNewLocationSaved(context);
+ GetStorage().write(kWidgetLocation,
+ coordinateData.lokasi ?? coordinateData.negeri);
Navigator.pop(context, true);
},
diff --git a/pubspec.lock b/pubspec.lock
index 39980cd0..a8ae1fed 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -551,6 +551,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.0"
+ home_widget:
+ dependency: "direct main"
+ description:
+ name: home_widget
+ sha256: a6483ac9817a6170c84032340421758c6424a48565cdefc651795ba7049769fc
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.3.1"
http:
dependency: "direct main"
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 215e2c15..99732b95 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: 2.10.1+137
+version: 2.11.0-pre.2+139
environment:
sdk: ">=3.0.0 <4.0.0"
@@ -52,6 +52,7 @@ dependencies:
flutter_staggered_animations: ^1.1.1
path_provider: ^2.1.1
open_file: ^3.3.2
+ home_widget: ^0.3.1
dependency_overrides:
# flutter compass in pub.dev is not updated