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