diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.kt index 64c7a02c9..19d8cf7ea 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.kt @@ -85,7 +85,6 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener { Thread.setDefaultUncaughtExceptionHandler(BaseExceptionHandler(this)) component.listHabitsBehavior.onStartup() setContentView(rootView) - parseIntents() } override fun onPause() { @@ -110,6 +109,7 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener { if (prefs.theme == THEME_DARK && prefs.isPureBlackEnabled != pureBlack) { restartWithFade(ListHabitsActivity::class.java) } + parseIntents() super.onResume() } @@ -129,6 +129,7 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener { } private fun parseIntents() { + if (intent == null) return if (intent.action == ACTION_EDIT) { val habitId = intent.extras?.getLong("habit") val timestamp = intent.extras?.getLong("timestamp") @@ -137,6 +138,12 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener { component.listHabitsBehavior.onEdit(habit, Timestamp(timestamp)) } } + intent = null + } + + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + setIntent(intent) } companion object { diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/intents/PendingIntentFactory.kt b/uhabits-android/src/main/java/org/isoron/uhabits/intents/PendingIntentFactory.kt index 3a1ee29c5..038cfe3a4 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/intents/PendingIntentFactory.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/intents/PendingIntentFactory.kt @@ -21,13 +21,16 @@ package org.isoron.uhabits.intents import android.app.PendingIntent import android.app.PendingIntent.FLAG_IMMUTABLE +import android.app.PendingIntent.FLAG_MUTABLE import android.app.PendingIntent.FLAG_UPDATE_CURRENT import android.app.PendingIntent.getActivity import android.app.PendingIntent.getBroadcast import android.content.Context import android.content.Intent import android.net.Uri +import android.os.Build import org.isoron.uhabits.activities.habits.list.ListHabitsActivity +import org.isoron.uhabits.activities.habits.show.ShowHabitActivity import org.isoron.uhabits.core.AppScope import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Timestamp @@ -89,6 +92,20 @@ class PendingIntentFactory ) .getPendingIntent(0, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT)!! + fun showHabitTemplate(): PendingIntent { + return getActivity( + context, + 0, + Intent(context, ShowHabitActivity::class.java), + getIntentTemplateFlags() + ) + } + + fun showHabitFillIn(habit: Habit) = + Intent().apply { + data = Uri.parse(habit.uriString) + } + fun showReminder( habit: Habit, reminderTime: Long?, @@ -142,7 +159,7 @@ class PendingIntentFactory fun showNumberPicker(habit: Habit, timestamp: Timestamp): PendingIntent? { return getActivity( context, - 0, + (habit.id!! % Integer.MAX_VALUE).toInt() + 1, Intent(context, ListHabitsActivity::class.java).apply { action = ListHabitsActivity.ACTION_EDIT putExtra("habit", habit.id) @@ -151,4 +168,43 @@ class PendingIntentFactory FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT ) } + + fun showNumberPickerTemplate(): PendingIntent { + return getActivity( + context, + 1, + Intent(context, ListHabitsActivity::class.java).apply { + action = ListHabitsActivity.ACTION_EDIT + }, + getIntentTemplateFlags() + ) + } + + fun showNumberPickerFillIn(habit: Habit, timestamp: Timestamp) = Intent().apply { + putExtra("habit", habit.id) + putExtra("timestamp", timestamp.unixTime) + } + + private fun getIntentTemplateFlags(): Int { + var flags = 0 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + flags = flags or FLAG_MUTABLE + } + return flags + } + + fun toggleCheckmarkTemplate(): PendingIntent = + getBroadcast( + context, + 2, + Intent(context, WidgetReceiver::class.java).apply { + action = WidgetReceiver.ACTION_TOGGLE_REPETITION + }, + getIntentTemplateFlags() + ) + + fun toggleCheckmarkFillIn(habit: Habit, timestamp: Timestamp) = Intent().apply { + data = Uri.parse(habit.uriString) + putExtra("timestamp", timestamp.unixTime) + } } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidget.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidget.kt index fa6452a0e..ab2bace27 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidget.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidget.kt @@ -73,6 +73,10 @@ class StackWidget( StackWidgetType.getStackWidgetAdapterViewId(widgetType), StackWidgetType.getStackWidgetEmptyViewId(widgetType) ) + remoteViews.setPendingIntentTemplate( + StackWidgetType.getStackWidgetAdapterViewId(widgetType), + StackWidgetType.getPendingIntentTemplate(pendingIntentFactory, widgetType, habits) + ) return remoteViews } } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetService.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetService.kt index dc86b9b66..d23005c96 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetService.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetService.kt @@ -29,11 +29,14 @@ import android.widget.RemoteViewsService import android.widget.RemoteViewsService.RemoteViewsFactory import org.isoron.platform.utils.StringUtils.Companion.splitLongs import org.isoron.uhabits.HabitsApplication +import org.isoron.uhabits.R import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.HabitNotFoundException import org.isoron.uhabits.core.preferences.Preferences +import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday +import org.isoron.uhabits.intents.IntentFactory +import org.isoron.uhabits.intents.PendingIntentFactory import org.isoron.uhabits.utils.InterfaceUtils.dpToPixels -import java.util.ArrayList class StackWidgetService : RemoteViewsService() { override fun onGetViewFactory(intent: Intent): RemoteViewsFactory { @@ -54,7 +57,6 @@ internal class StackRemoteViewsFactory(private val context: Context, intent: Int ) private val habitIds: LongArray private val widgetType: StackWidgetType - private var remoteViews = ArrayList() override fun onCreate() {} override fun onDestroy() {} override fun getCount(): Int { @@ -85,8 +87,26 @@ internal class StackRemoteViewsFactory(private val context: Context, intent: Int } override fun getViewAt(position: Int): RemoteViews? { - Log.i("StackRemoteViewsFactory", "getViewAt $position") - return if (0 <= position && position < remoteViews.size) remoteViews[position] else null + Log.i("StackRemoteViewsFactory", "getViewAt $position started") + if (position < 0 || position >= habitIds.size) return null + val app = context.applicationContext as HabitsApplication + val prefs = app.component.preferences + val habitList = app.component.habitList + val options = AppWidgetManager.getInstance(context).getAppWidgetOptions(widgetId) + if (Looper.myLooper() == null) Looper.prepare() + val habits = habitIds.map { habitList.getById(it) ?: throw HabitNotFoundException() } + val h = habits[position] + val widget = constructWidget(h, prefs) + widget.setDimensions(getDimensionsFromOptions(context, options)) + val landscapeViews = widget.landscapeRemoteViews + val portraitViews = widget.portraitRemoteViews + val factory = PendingIntentFactory(context, IntentFactory()) + val intent = StackWidgetType.getIntentFillIn(factory, widgetType, h, habits, getToday()) + landscapeViews.setOnClickFillInIntent(R.id.button, intent) + portraitViews.setOnClickFillInIntent(R.id.button, intent) + val remoteViews = RemoteViews(landscapeViews, portraitViews) + Log.i("StackRemoteViewsFactory", "getViewAt $position ended") + return remoteViews } private fun constructWidget( @@ -131,24 +151,6 @@ internal class StackRemoteViewsFactory(private val context: Context, intent: Int } override fun onDataSetChanged() { - Log.i("StackRemoteViewsFactory", "onDataSetChanged started") - val app = context.applicationContext as HabitsApplication - val prefs = app.component.preferences - val habitList = app.component.habitList - val options = AppWidgetManager.getInstance(context).getAppWidgetOptions(widgetId) - val newRemoteViews = ArrayList() - if (Looper.myLooper() == null) Looper.prepare() - for (id in habitIds) { - val h = habitList.getById(id) ?: throw HabitNotFoundException() - val widget = constructWidget(h, prefs) - widget.setDimensions(getDimensionsFromOptions(context, options)) - val landscapeViews = widget.landscapeRemoteViews - val portraitViews = widget.portraitRemoteViews - newRemoteViews.add(RemoteViews(landscapeViews, portraitViews)) - Log.i("StackRemoteViewsFactory", "onDataSetChanged constructed widget $id") - } - remoteViews = newRemoteViews - Log.i("StackRemoteViewsFactory", "onDataSetChanged ended") } init { diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetType.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetType.kt index 68e8018aa..1e3a5d217 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetType.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetType.kt @@ -18,7 +18,12 @@ */ package org.isoron.uhabits.widgets +import android.app.PendingIntent +import android.content.Intent import org.isoron.uhabits.R +import org.isoron.uhabits.core.models.Habit +import org.isoron.uhabits.core.models.Timestamp +import org.isoron.uhabits.intents.PendingIntentFactory import java.lang.IllegalStateException enum class StackWidgetType(val value: Int) { @@ -73,5 +78,39 @@ enum class StackWidgetType(val value: Int) { else -> throw IllegalStateException() } } + + fun getPendingIntentTemplate( + factory: PendingIntentFactory, + widgetType: StackWidgetType, + habits: List + ): PendingIntent { + val containsNumerical = habits.any { it.isNumerical } + return when (widgetType) { + CHECKMARK -> if (containsNumerical) { + factory.showNumberPickerTemplate() + } else { + factory.toggleCheckmarkTemplate() + } + FREQUENCY, SCORE, HISTORY, STREAKS, TARGET -> factory.showHabitTemplate() + } + } + + fun getIntentFillIn( + factory: PendingIntentFactory, + widgetType: StackWidgetType, + habit: Habit, + allHabitsInStackWidget: List, + timestamp: Timestamp + ): Intent { + val containsNumerical = allHabitsInStackWidget.any { it.isNumerical } + return when (widgetType) { + CHECKMARK -> if (containsNumerical) { + factory.showNumberPickerFillIn(habit, timestamp) + } else { + factory.toggleCheckmarkFillIn(habit, timestamp) + } + FREQUENCY, SCORE, HISTORY, STREAKS, TARGET -> factory.showHabitFillIn(habit) + } + } } } diff --git a/uhabits-core/src/commonMain/kotlin/org/isoron/platform/utils/StringUtils.kt b/uhabits-core/src/commonMain/kotlin/org/isoron/platform/utils/StringUtils.kt index 8f3cf62d0..f6105b4cc 100644 --- a/uhabits-core/src/commonMain/kotlin/org/isoron/platform/utils/StringUtils.kt +++ b/uhabits-core/src/commonMain/kotlin/org/isoron/platform/utils/StringUtils.kt @@ -25,6 +25,12 @@ class StringUtils { fun joinLongs(values: LongArray): String = values.joinToString(separator = ",") - fun splitLongs(str: String): LongArray = str.split(",").map { it.toLong() }.toLongArray() + fun splitLongs(str: String): LongArray { + return try { + str.split(",").map { it.toLong() }.toLongArray() + } catch (e: NumberFormatException) { + LongArray(0) + } + } } }