From eb8d39fbe33031c002cb22907dbcfe7d93bd7e1c Mon Sep 17 00:00:00 2001 From: Quentin Hibon Date: Thu, 14 Jan 2021 18:51:23 +0100 Subject: [PATCH] Convert org.isoron.uhabits.core.ui.widgets --- .../isoron/uhabits/widgets/BaseWidget.java | 214 ----------------- .../org/isoron/uhabits/widgets/BaseWidget.kt | 162 +++++++++++++ .../uhabits/widgets/BaseWidgetProvider.java | 206 ---------------- .../uhabits/widgets/BaseWidgetProvider.kt | 177 ++++++++++++++ .../isoron/uhabits/widgets/CheckmarkWidget.kt | 31 +-- .../org/isoron/uhabits/widgets/EmptyWidget.kt | 9 +- .../isoron/uhabits/widgets/FrequencyWidget.kt | 10 +- .../widgets/FrequencyWidgetProvider.kt | 2 +- .../isoron/uhabits/widgets/HistoryWidget.kt | 10 +- .../org/isoron/uhabits/widgets/ScoreWidget.kt | 10 +- .../org/isoron/uhabits/widgets/StackWidget.kt | 16 +- .../uhabits/widgets/StackWidgetService.java | 186 --------------- .../uhabits/widgets/StackWidgetService.kt | 162 +++++++++++++ .../uhabits/widgets/StackWidgetType.java | 117 --------- .../isoron/uhabits/widgets/StackWidgetType.kt | 79 ++++++ .../isoron/uhabits/widgets/StreakWidget.kt | 10 +- .../isoron/uhabits/widgets/TargetWidget.kt | 10 +- .../widgets/views/CheckmarkWidgetView.java | 225 ------------------ .../widgets/views/CheckmarkWidgetView.kt | 156 ++++++++++++ ...mptyWidgetView.java => EmptyWidgetView.kt} | 45 ++-- .../widgets/views/GraphWidgetView.java | 74 ------ .../uhabits/widgets/views/GraphWidgetView.kt | 51 ++++ .../widgets/views/HabitWidgetView.java | 121 ---------- .../uhabits/widgets/views/HabitWidgetView.kt | 104 ++++++++ 24 files changed, 965 insertions(+), 1222 deletions(-) delete mode 100644 uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidget.java create mode 100644 uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidget.kt delete mode 100644 uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java create mode 100644 uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.kt delete mode 100644 uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetService.java create mode 100644 uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetService.kt delete mode 100644 uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetType.java create mode 100644 uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetType.kt delete mode 100644 uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetView.java create mode 100644 uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetView.kt rename uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/{EmptyWidgetView.java => EmptyWidgetView.kt} (54%) delete mode 100644 uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/GraphWidgetView.java create mode 100644 uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/GraphWidgetView.kt delete mode 100644 uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/HabitWidgetView.java create mode 100644 uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/HabitWidgetView.kt diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidget.java b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidget.java deleted file mode 100644 index 8c35f6cbc..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidget.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright (C) 2016-2021 Álinson Santos Xavier - * - * This file is part of Loop Habit Tracker. - * - * Loop Habit Tracker is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Loop Habit Tracker is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -package org.isoron.uhabits.widgets; - -import android.app.*; -import android.content.*; -import android.graphics.*; -import android.view.*; -import android.widget.*; - -import androidx.annotation.NonNull; - -import org.isoron.uhabits.*; -import org.isoron.uhabits.core.commands.*; -import org.isoron.uhabits.core.preferences.*; -import org.isoron.uhabits.intents.*; - -import static android.view.View.MeasureSpec.makeMeasureSpec; - -public abstract class BaseWidget -{ - private final int id; - - @NonNull - protected final WidgetPreferences widgetPrefs; - - @NonNull - protected final Preferences prefs; - - @NonNull - protected final PendingIntentFactory pendingIntentFactory; - - @NonNull - private final Context context; - - @NonNull - protected final CommandRunner commandRunner; - - @NonNull - private WidgetDimensions dimensions; - - public BaseWidget(@NonNull Context context, int id) - { - this.id = id; - this.context = context; - - HabitsApplication app = - (HabitsApplication) context.getApplicationContext(); - - widgetPrefs = app.getComponent().getWidgetPreferences(); - prefs = app.getComponent().getPreferences(); - commandRunner = app.getComponent().getCommandRunner(); - pendingIntentFactory = app.getComponent().getPendingIntentFactory(); - dimensions = new WidgetDimensions(getDefaultWidth(), getDefaultHeight(), - getDefaultWidth(), getDefaultHeight()); - } - - public void delete() - { - widgetPrefs.removeWidget(id); - } - - @NonNull - public Context getContext() - { - return context; - } - - public int getId() - { - return id; - } - - @NonNull - public RemoteViews getLandscapeRemoteViews() - { - return getRemoteViews(dimensions.getLandscapeWidth(), - dimensions.getLandscapeHeight()); - } - - public abstract PendingIntent getOnClickPendingIntent(Context context); - - @NonNull - public RemoteViews getPortraitRemoteViews() - { - return getRemoteViews(dimensions.getPortraitWidth(), - dimensions.getPortraitHeight()); - } - - public abstract void refreshData(View widgetView); - - public void setDimensions(@NonNull WidgetDimensions dimensions) - { - this.dimensions = dimensions; - } - - protected abstract View buildView(); - - protected abstract int getDefaultHeight(); - - protected abstract int getDefaultWidth(); - - private void adjustRemoteViewsPadding(RemoteViews remoteViews, - View view, - int width, - int height) - { - int imageWidth = view.getMeasuredWidth(); - int imageHeight = view.getMeasuredHeight(); - int p[] = calculatePadding(width, height, imageWidth, imageHeight); - remoteViews.setViewPadding(R.id.buttonOverlay, p[0], p[1], p[2], p[3]); - } - - private void buildRemoteViews(View view, - RemoteViews remoteViews, - int width, - int height) - { - Bitmap bitmap = getBitmapFromView(view); - remoteViews.setImageViewBitmap(R.id.imageView, bitmap); - - adjustRemoteViewsPadding(remoteViews, view, width, height); - - PendingIntent onClickIntent = getOnClickPendingIntent(context); - if (onClickIntent != null) - remoteViews.setOnClickPendingIntent(R.id.button, onClickIntent); - } - - private int[] calculatePadding(int entireWidth, - int entireHeight, - int imageWidth, - int imageHeight) - { - int w = (int) (((float) entireWidth - imageWidth) / 2); - int h = (int) (((float) entireHeight - imageHeight) / 2); - - return new int[]{w, h, w, h}; - } - - @NonNull - private Bitmap getBitmapFromView(View view) - { - view.invalidate(); - int width = view.getMeasuredWidth(); - int height = view.getMeasuredHeight(); - - Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - view.draw(canvas); - return bitmap; - } - - @NonNull - protected RemoteViews getRemoteViews(int width, int height) - { - View view = buildView(); - measureView(view, width, height); - - refreshData(view); - - if (view.isLayoutRequested()) measureView(view, width, height); - - RemoteViews remoteViews = - new RemoteViews(context.getPackageName(), R.layout.widget_wrapper); - - buildRemoteViews(view, remoteViews, width, height); - - return remoteViews; - } - - private void measureView(View view, int width, int height) - { - LayoutInflater inflater = LayoutInflater.from(context); - View entireView = inflater.inflate(R.layout.widget_wrapper, null); - - int specWidth = makeMeasureSpec(width, View.MeasureSpec.EXACTLY); - int specHeight = makeMeasureSpec(height, View.MeasureSpec.EXACTLY); - - entireView.measure(specWidth, specHeight); - entireView.layout(0, 0, entireView.getMeasuredWidth(), - entireView.getMeasuredHeight()); - - View imageView = entireView.findViewById(R.id.imageView); - width = imageView.getMeasuredWidth(); - height = imageView.getMeasuredHeight(); - - specWidth = makeMeasureSpec(width, View.MeasureSpec.EXACTLY); - specHeight = makeMeasureSpec(height, View.MeasureSpec.EXACTLY); - - view.measure(specWidth, specHeight); - view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); - } - - protected int getPreferedBackgroundAlpha() { - return prefs.getWidgetOpacity(); - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidget.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidget.kt new file mode 100644 index 000000000..aef9a3a4c --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidget.kt @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2016-2021 Álinson Santos Xavier + * + * This file is part of Loop Habit Tracker. + * + * Loop Habit Tracker is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Loop Habit Tracker is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ +package org.isoron.uhabits.widgets + +import android.app.PendingIntent +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.view.LayoutInflater +import android.view.View +import android.view.View.MeasureSpec +import android.widget.RemoteViews +import org.isoron.uhabits.HabitsApplication +import org.isoron.uhabits.R +import org.isoron.uhabits.core.commands.CommandRunner +import org.isoron.uhabits.core.preferences.Preferences +import org.isoron.uhabits.core.preferences.WidgetPreferences +import org.isoron.uhabits.intents.PendingIntentFactory + +abstract class BaseWidget(val context: Context, val id: Int) { + protected val widgetPrefs: WidgetPreferences + protected val prefs: Preferences + protected val pendingIntentFactory: PendingIntentFactory + protected val commandRunner: CommandRunner + private var dimensions: WidgetDimensions + fun delete() { + widgetPrefs.removeWidget(id) + } + + val landscapeRemoteViews: RemoteViews + get() = getRemoteViews( + dimensions.landscapeWidth, + dimensions.landscapeHeight + ) + + abstract fun getOnClickPendingIntent(context: Context): PendingIntent? + val portraitRemoteViews: RemoteViews + get() = getRemoteViews( + dimensions.portraitWidth, + dimensions.portraitHeight + ) + + abstract fun refreshData(widgetView: View) + fun setDimensions(dimensions: WidgetDimensions) { + this.dimensions = dimensions + } + + protected abstract fun buildView(): View? + protected abstract val defaultHeight: Int + protected abstract val defaultWidth: Int + private fun adjustRemoteViewsPadding( + remoteViews: RemoteViews, + view: View, + width: Int, + height: Int + ) { + val imageWidth = view.measuredWidth + val imageHeight = view.measuredHeight + val p = calculatePadding(width, height, imageWidth, imageHeight) + remoteViews.setViewPadding(R.id.buttonOverlay, p[0], p[1], p[2], p[3]) + } + + private fun buildRemoteViews( + view: View, + remoteViews: RemoteViews, + width: Int, + height: Int + ) { + val bitmap = getBitmapFromView(view) + remoteViews.setImageViewBitmap(R.id.imageView, bitmap) + adjustRemoteViewsPadding(remoteViews, view, width, height) + val onClickIntent = getOnClickPendingIntent(context) + if (onClickIntent != null) remoteViews.setOnClickPendingIntent(R.id.button, onClickIntent) + } + + private fun calculatePadding( + entireWidth: Int, + entireHeight: Int, + imageWidth: Int, + imageHeight: Int + ): IntArray { + val w = ((entireWidth.toFloat() - imageWidth) / 2).toInt() + val h = ((entireHeight.toFloat() - imageHeight) / 2).toInt() + return intArrayOf(w, h, w, h) + } + + private fun getBitmapFromView(view: View): Bitmap { + view.invalidate() + val width = view.measuredWidth + val height = view.measuredHeight + val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + view.draw(canvas) + return bitmap + } + + protected open fun getRemoteViews(width: Int, height: Int): RemoteViews { + val view = buildView()!! + measureView(view, width, height) + refreshData(view) + if (view.isLayoutRequested) measureView(view, width, height) + val remoteViews = RemoteViews(context.packageName, R.layout.widget_wrapper) + buildRemoteViews(view, remoteViews, width, height) + return remoteViews + } + + private fun measureView(view: View, width: Int, height: Int) { + var width = width + var height = height + val inflater = LayoutInflater.from(context) + val entireView = inflater.inflate(R.layout.widget_wrapper, null) + var specWidth = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY) + var specHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY) + entireView.measure(specWidth, specHeight) + entireView.layout( + 0, + 0, + entireView.measuredWidth, + entireView.measuredHeight + ) + val imageView = entireView.findViewById(R.id.imageView) + width = imageView.measuredWidth + height = imageView.measuredHeight + specWidth = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY) + specHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY) + view.measure(specWidth, specHeight) + view.layout(0, 0, view.measuredWidth, view.measuredHeight) + } + + protected val preferedBackgroundAlpha: Int + protected get() = prefs.widgetOpacity + + init { + val app = context.applicationContext as HabitsApplication + widgetPrefs = app.component.widgetPreferences + prefs = app.component.preferences + commandRunner = app.component.commandRunner + pendingIntentFactory = app.component.pendingIntentFactory + dimensions = WidgetDimensions( + defaultWidth, + defaultHeight, + defaultWidth, + defaultHeight + ) + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java deleted file mode 100644 index 946a7e966..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright (C) 2016-2021 Álinson Santos Xavier - * - * This file is part of Loop Habit Tracker. - * - * Loop Habit Tracker is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Loop Habit Tracker is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -package org.isoron.uhabits.widgets; - -import android.appwidget.*; -import android.content.*; -import android.os.*; -import android.widget.*; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.isoron.uhabits.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.preferences.*; - -import java.util.*; - -import static android.appwidget.AppWidgetManager.*; -import static org.isoron.uhabits.utils.InterfaceUtils.dpToPixels; - -public abstract class BaseWidgetProvider extends AppWidgetProvider -{ - private HabitList habits; - - private Preferences preferences; - - private WidgetPreferences widgetPrefs; - - public static void updateAppWidget(@NonNull AppWidgetManager manager, - @NonNull BaseWidget widget) - { - RemoteViews landscape = widget.getLandscapeRemoteViews(); - RemoteViews portrait = widget.getPortraitRemoteViews(); - RemoteViews views = new RemoteViews(landscape, portrait); - manager.updateAppWidget(widget.getId(), views); - } - - @NonNull - public WidgetDimensions getDimensionsFromOptions(@NonNull Context ctx, - @NonNull Bundle options) - { - int maxWidth = - (int) dpToPixels(ctx, options.getInt(OPTION_APPWIDGET_MAX_WIDTH)); - int maxHeight = - (int) dpToPixels(ctx, options.getInt(OPTION_APPWIDGET_MAX_HEIGHT)); - int minWidth = - (int) dpToPixels(ctx, options.getInt(OPTION_APPWIDGET_MIN_WIDTH)); - int minHeight = - (int) dpToPixels(ctx, options.getInt(OPTION_APPWIDGET_MIN_HEIGHT)); - - return new WidgetDimensions(minWidth, maxHeight, maxWidth, minHeight); - } - - @Override - public void onAppWidgetOptionsChanged(@Nullable Context context, - @Nullable AppWidgetManager manager, - int widgetId, - @Nullable Bundle options) - { - try - { - if (context == null) throw new RuntimeException("context is null"); - if (manager == null) throw new RuntimeException("manager is null"); - if (options == null) throw new RuntimeException("options is null"); - updateDependencies(context); - - context.setTheme(R.style.WidgetTheme); - - BaseWidget widget = getWidgetFromId(context, widgetId); - WidgetDimensions dims = getDimensionsFromOptions(context, options); - widget.setDimensions(dims); - updateAppWidget(manager, widget); - } - catch (RuntimeException e) - { - drawErrorWidget(context, manager, widgetId, e); - e.printStackTrace(); - } - } - - @Override - public void onDeleted(@Nullable Context context, @Nullable int[] ids) - { - if (context == null) throw new RuntimeException("context is null"); - if (ids == null) throw new RuntimeException("ids is null"); - - updateDependencies(context); - - for (int id : ids) - { - try - { - BaseWidget widget = getWidgetFromId(context, id); - widget.delete(); - } - catch (HabitNotFoundException e) - { - e.printStackTrace(); - } - } - } - - @Override - public void onUpdate(@Nullable Context context, - @Nullable AppWidgetManager manager, - @Nullable int[] widgetIds) - { - if (context == null) throw new RuntimeException("context is null"); - if (manager == null) throw new RuntimeException("manager is null"); - if (widgetIds == null) throw new RuntimeException("widgetIds is null"); - updateDependencies(context); - context.setTheme(R.style.WidgetTheme); - - new Thread(() -> - { - Looper.prepare(); - for (int id : widgetIds) - update(context, manager, id); - }).start(); - } - - protected List getHabitsFromWidgetId(int widgetId) - { - long selectedIds[] = widgetPrefs.getHabitIdsFromWidgetId(widgetId); - ArrayList selectedHabits = new ArrayList<>(selectedIds.length); - for (long id : selectedIds) - { - Habit h = habits.getById(id); - if (h == null) throw new HabitNotFoundException(); - selectedHabits.add(h); - } - - return selectedHabits; - } - - @NonNull - protected abstract BaseWidget getWidgetFromId(@NonNull Context context, - int id); - - private void drawErrorWidget(Context context, - AppWidgetManager manager, - int widgetId, - RuntimeException e) - { - RemoteViews errorView = - new RemoteViews(context.getPackageName(), R.layout.widget_error); - - if (e instanceof HabitNotFoundException) - { - errorView.setCharSequence(R.id.label, "setText", - context.getString(R.string.habit_not_found)); - } - - manager.updateAppWidget(widgetId, errorView); - } - - private void update(@NonNull Context context, - @NonNull AppWidgetManager manager, - int widgetId) - { - try - { - BaseWidget widget = getWidgetFromId(context, widgetId); - Bundle options = manager.getAppWidgetOptions(widgetId); - widget.setDimensions(getDimensionsFromOptions(context, options)); - updateAppWidget(manager, widget); - } - catch (RuntimeException e) - { - drawErrorWidget(context, manager, widgetId, e); - e.printStackTrace(); - } - } - - private void updateDependencies(Context context) - { - HabitsApplication app = - (HabitsApplication) context.getApplicationContext(); - habits = app.getComponent().getHabitList(); - preferences = app.getComponent().getPreferences(); - widgetPrefs = app.getComponent().getWidgetPreferences(); - } - - public Preferences getPreferences() - { - return preferences; - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.kt new file mode 100644 index 000000000..1a41d0f4f --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.kt @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2016-2021 Álinson Santos Xavier + * + * This file is part of Loop Habit Tracker. + * + * Loop Habit Tracker is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Loop Habit Tracker is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ +package org.isoron.uhabits.widgets + +import android.appwidget.AppWidgetManager +import android.appwidget.AppWidgetProvider +import android.content.Context +import android.os.Bundle +import android.os.Looper +import android.widget.RemoteViews +import org.isoron.uhabits.HabitsApplication +import org.isoron.uhabits.R +import org.isoron.uhabits.core.models.Habit +import org.isoron.uhabits.core.models.HabitList +import org.isoron.uhabits.core.models.HabitNotFoundException +import org.isoron.uhabits.core.preferences.Preferences +import org.isoron.uhabits.core.preferences.WidgetPreferences +import org.isoron.uhabits.utils.InterfaceUtils.dpToPixels +import java.util.ArrayList + +abstract class BaseWidgetProvider : AppWidgetProvider() { + private lateinit var habits: HabitList + lateinit var preferences: Preferences + private set + private lateinit var widgetPrefs: WidgetPreferences + fun getDimensionsFromOptions( + ctx: Context, + options: Bundle + ): WidgetDimensions { + val maxWidth = dpToPixels( + ctx, + options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH).toFloat() + ).toInt() + val maxHeight = dpToPixels( + ctx, + options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT).toFloat() + ).toInt() + val minWidth = dpToPixels( + ctx, + options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH).toFloat() + ).toInt() + val minHeight = dpToPixels( + ctx, + options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT).toFloat() + ).toInt() + return WidgetDimensions(minWidth, maxHeight, maxWidth, minHeight) + } + + override fun onAppWidgetOptionsChanged( + context: Context, + manager: AppWidgetManager, + widgetId: Int, + options: Bundle + ) { + try { + updateDependencies(context) + context.setTheme(R.style.WidgetTheme) + val widget = getWidgetFromId(context, widgetId) + val dims = getDimensionsFromOptions(context, options) + widget.setDimensions(dims) + updateAppWidget(manager, widget) + } catch (e: RuntimeException) { + drawErrorWidget(context, manager, widgetId, e) + e.printStackTrace() + } + } + + override fun onDeleted(context: Context?, ids: IntArray?) { + if (context == null) throw RuntimeException("context is null") + if (ids == null) throw RuntimeException("ids is null") + updateDependencies(context) + for (id in ids) { + try { + val widget = getWidgetFromId(context, id) + widget.delete() + } catch (e: HabitNotFoundException) { + e.printStackTrace() + } + } + } + + override fun onUpdate( + context: Context, + manager: AppWidgetManager, + widgetIds: IntArray + ) { + updateDependencies(context) + context.setTheme(R.style.WidgetTheme) + Thread { + Looper.prepare() + for (id in widgetIds) update(context, manager, id) + }.start() + } + + protected fun getHabitsFromWidgetId(widgetId: Int): List { + val selectedIds = widgetPrefs.getHabitIdsFromWidgetId(widgetId) + val selectedHabits = ArrayList(selectedIds.size) + for (id in selectedIds) { + val h = habits.getById(id) ?: throw HabitNotFoundException() + selectedHabits.add(h) + } + return selectedHabits + } + + protected abstract fun getWidgetFromId( + context: Context, + id: Int + ): BaseWidget + + private fun drawErrorWidget( + context: Context, + manager: AppWidgetManager, + widgetId: Int, + e: RuntimeException + ) { + val errorView = RemoteViews(context.packageName, R.layout.widget_error) + if (e is HabitNotFoundException) { + errorView.setCharSequence( + R.id.label, + "setText", + context.getString(R.string.habit_not_found) + ) + } + manager.updateAppWidget(widgetId, errorView) + } + + private fun update( + context: Context, + manager: AppWidgetManager, + widgetId: Int + ) { + try { + val widget = getWidgetFromId(context, widgetId) + val options = manager.getAppWidgetOptions(widgetId) + widget.setDimensions(getDimensionsFromOptions(context, options)) + updateAppWidget(manager, widget) + } catch (e: RuntimeException) { + drawErrorWidget(context, manager, widgetId, e) + e.printStackTrace() + } + } + + private fun updateDependencies(context: Context) { + val app = context.applicationContext as HabitsApplication + habits = app.component.habitList + preferences = app.component.preferences + widgetPrefs = app.component.widgetPreferences + } + + companion object { + fun updateAppWidget( + manager: AppWidgetManager, + widget: BaseWidget + ) { + val landscape = widget.landscapeRemoteViews + val portrait = widget.portraitRemoteViews + val views = RemoteViews(landscape, portrait) + manager.updateAppWidget(widget.id, views) + } + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidget.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidget.kt index a62bc910a..95d0394bf 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidget.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidget.kt @@ -21,7 +21,9 @@ package org.isoron.uhabits.widgets import android.app.PendingIntent import android.content.Context +import android.os.Build import android.view.View +import androidx.annotation.RequiresApi import org.isoron.uhabits.core.models.Entry import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.utils.DateUtils @@ -31,10 +33,13 @@ import org.isoron.uhabits.widgets.views.CheckmarkWidgetView open class CheckmarkWidget( context: Context, widgetId: Int, - protected val habit: Habit + protected val habit: Habit, ) : BaseWidget(context, widgetId) { - override fun getOnClickPendingIntent(context: Context): PendingIntent { + override val defaultHeight: Int = 125 + override val defaultWidth: Int = 125 + + override fun getOnClickPendingIntent(context: Context): PendingIntent? { return if (habit.isNumerical) { pendingIntentFactory.setNumericalValue(context, habit, 10, null) } else { @@ -42,20 +47,21 @@ open class CheckmarkWidget( } } - override fun refreshData(v: View) { - (v as CheckmarkWidgetView).apply { + @RequiresApi(Build.VERSION_CODES.O) + override fun refreshData(widgetView: View) { + (widgetView as CheckmarkWidgetView).apply { val today = DateUtils.getTodayWithOffset() setBackgroundAlpha(preferedBackgroundAlpha) - setActiveColor(habit.color.toThemedAndroidColor(context)) - setName(habit.name) - setEntryValue(habit.computedEntries.get(today).value) + activeColor = habit.color.toThemedAndroidColor(context) + name = habit.name + entryValue = habit.computedEntries.get(today).value if (habit.isNumerical) { - setNumerical(true) - setEntryState(getNumericalEntryState()) + isNumerical = true + entryState = getNumericalEntryState() } else { - setEntryState(habit.computedEntries.get(today).value) + entryState = habit.computedEntries.get(today).value } - setPercentage(habit.scores.get(today).value.toFloat()) + percentage = habit.scores[today].value.toFloat() refresh() } } @@ -64,9 +70,6 @@ open class CheckmarkWidget( return CheckmarkWidgetView(context) } - override fun getDefaultHeight() = 125 - override fun getDefaultWidth() = 125 - private fun getNumericalEntryState(): Int { return if (habit.isCompletedToday()) { Entry.YES_MANUAL diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/EmptyWidget.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/EmptyWidget.kt index 2f95ee987..1e0c2bad5 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/EmptyWidget.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/EmptyWidget.kt @@ -19,18 +19,19 @@ package org.isoron.uhabits.widgets +import android.app.PendingIntent import android.content.Context import android.view.View import org.isoron.uhabits.widgets.views.EmptyWidgetView class EmptyWidget( context: Context, - widgetId: Int + widgetId: Int, ) : BaseWidget(context, widgetId) { + override val defaultHeight: Int = 200 + override val defaultWidth: Int = 200 - override fun getOnClickPendingIntent(context: Context) = null + override fun getOnClickPendingIntent(context: Context): PendingIntent? = null override fun refreshData(v: View) {} override fun buildView() = EmptyWidgetView(context) - override fun getDefaultHeight() = 200 - override fun getDefaultWidth() = 200 } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/FrequencyWidget.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/FrequencyWidget.kt index 09c969cab..568f98bc1 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/FrequencyWidget.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/FrequencyWidget.kt @@ -19,6 +19,7 @@ package org.isoron.uhabits.widgets +import android.app.PendingIntent import android.content.Context import android.view.View import org.isoron.uhabits.activities.common.views.FrequencyChart @@ -30,10 +31,12 @@ class FrequencyWidget( context: Context, widgetId: Int, private val habit: Habit, - private val firstWeekday: Int + private val firstWeekday: Int, ) : BaseWidget(context, widgetId) { + override val defaultHeight: Int = 200 + override val defaultWidth: Int = 200 - override fun getOnClickPendingIntent(context: Context) = + override fun getOnClickPendingIntent(context: Context): PendingIntent? = pendingIntentFactory.showHabit(habit) override fun refreshData(v: View) { @@ -50,7 +53,4 @@ class FrequencyWidget( override fun buildView() = GraphWidgetView(context, FrequencyChart(context)) - - override fun getDefaultHeight() = 200 - override fun getDefaultWidth() = 200 } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.kt index 907264c1c..6ff8cc250 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.kt @@ -28,7 +28,7 @@ class FrequencyWidgetProvider : BaseWidgetProvider() { context, id, habits[0], - preferences.firstWeekdayInt + preferences!!.firstWeekdayInt ) else return StackWidget(context, id, StackWidgetType.FREQUENCY, habits) } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/HistoryWidget.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/HistoryWidget.kt index e39ff405b..e7c2800b9 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/HistoryWidget.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/HistoryWidget.kt @@ -35,10 +35,13 @@ import java.util.Locale class HistoryWidget( context: Context, id: Int, - private val habit: Habit + private val habit: Habit, ) : BaseWidget(context, id) { - override fun getOnClickPendingIntent(context: Context): PendingIntent { + override val defaultHeight: Int = 250 + override val defaultWidth: Int = 250 + + override fun getOnClickPendingIntent(context: Context): PendingIntent? { return pendingIntentFactory.showHabit(habit) } @@ -72,7 +75,4 @@ class HistoryWidget( ).apply { setTitle(habit.name) } - - override fun getDefaultHeight() = 250 - override fun getDefaultWidth() = 250 } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/ScoreWidget.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/ScoreWidget.kt index ad2f970c5..46c5a432c 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/ScoreWidget.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/ScoreWidget.kt @@ -19,6 +19,7 @@ package org.isoron.uhabits.widgets +import android.app.PendingIntent import android.content.Context import android.view.View import org.isoron.uhabits.activities.common.views.ScoreChart @@ -30,10 +31,12 @@ import org.isoron.uhabits.widgets.views.GraphWidgetView class ScoreWidget( context: Context, id: Int, - private val habit: Habit + private val habit: Habit, ) : BaseWidget(context, id) { + override val defaultHeight: Int = 300 + override val defaultWidth: Int = 300 - override fun getOnClickPendingIntent(context: Context) = + override fun getOnClickPendingIntent(context: Context): PendingIntent? = pendingIntentFactory.showHabit(habit) override fun refreshData(view: View) { @@ -57,7 +60,4 @@ class ScoreWidget( GraphWidgetView(context, ScoreChart(context)).apply { setTitle(habit.name) } - - override fun getDefaultHeight() = 300 - override fun getDefaultWidth() = 300 } 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 51c4ef496..106fd2b2f 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 @@ -19,6 +19,7 @@ package org.isoron.uhabits.widgets +import android.app.PendingIntent import android.appwidget.AppWidgetManager import android.content.Context import android.content.Intent @@ -32,15 +33,22 @@ class StackWidget( context: Context, widgetId: Int, private val widgetType: StackWidgetType, - private val habits: List + private val habits: List, ) : BaseWidget(context, widgetId) { + override val defaultHeight: Int = 0 + override val defaultWidth: Int = 0 - override fun getOnClickPendingIntent(context: Context) = null + override fun getOnClickPendingIntent(context: Context): PendingIntent? = null override fun refreshData(v: View) { // unused } + override fun buildView(): View? { + // unused + return null + } + override fun getRemoteViews(width: Int, height: Int): RemoteViews { val manager = AppWidgetManager.getInstance(context) val remoteViews = RemoteViews(context.packageName, StackWidgetType.getStackWidgetLayoutId(widgetType)) @@ -59,8 +67,4 @@ class StackWidget( ) return remoteViews } - - override fun buildView() = null // unused - override fun getDefaultHeight() = 0 // unused - override fun getDefaultWidth() = 0 // unused } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetService.java b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetService.java deleted file mode 100644 index d5b7fe110..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetService.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright (C) 2016-2021 Álinson Santos Xavier - * - * This file is part of Loop Habit Tracker. - * - * Loop Habit Tracker is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Loop Habit Tracker is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -package org.isoron.uhabits.widgets; - -import android.appwidget.*; -import android.content.*; -import android.os.*; -import android.util.Log; -import android.widget.*; - -import androidx.annotation.NonNull; - -import org.isoron.uhabits.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.preferences.*; -import org.isoron.uhabits.core.utils.*; - -import java.util.*; - -import static android.appwidget.AppWidgetManager.*; -import static org.isoron.uhabits.utils.InterfaceUtils.dpToPixels; -import static org.isoron.uhabits.widgets.StackWidgetService.*; - -public class StackWidgetService extends RemoteViewsService -{ - public static final String WIDGET_TYPE = "WIDGET_TYPE"; - public static final String HABIT_IDS = "HABIT_IDS"; - - @Override - public RemoteViewsFactory onGetViewFactory(Intent intent) - { - return new StackRemoteViewsFactory(this.getApplicationContext(), intent); - } -} - -class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory -{ - private Context context; - private int widgetId; - private long[] habitIds; - private StackWidgetType widgetType; - private ArrayList remoteViews = new ArrayList<>(); - - public StackRemoteViewsFactory(Context context, Intent intent) - { - this.context = context; - widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, - AppWidgetManager.INVALID_APPWIDGET_ID); - int widgetTypeValue = intent.getIntExtra(WIDGET_TYPE, -1); - String habitIdsStr = intent.getStringExtra(HABIT_IDS); - - if (widgetTypeValue < 0) throw new RuntimeException("invalid widget type"); - if (habitIdsStr == null) throw new RuntimeException("habitIdsStr is null"); - - widgetType = StackWidgetType.getWidgetTypeFromValue(widgetTypeValue); - habitIds = StringUtils.splitLongs(habitIdsStr); - } - - public void onCreate() - { - - } - - public void onDestroy() - { - - } - - public int getCount() - { - return habitIds.length; - } - - @NonNull - public WidgetDimensions getDimensionsFromOptions(@NonNull Context ctx, - @NonNull Bundle options) - { - int maxWidth = (int) dpToPixels(ctx, options.getInt(OPTION_APPWIDGET_MAX_WIDTH)); - int maxHeight = (int) dpToPixels(ctx, options.getInt(OPTION_APPWIDGET_MAX_HEIGHT)); - int minWidth = (int) dpToPixels(ctx, options.getInt(OPTION_APPWIDGET_MIN_WIDTH)); - int minHeight = (int) dpToPixels(ctx, options.getInt(OPTION_APPWIDGET_MIN_HEIGHT)); - - return new WidgetDimensions(minWidth, maxHeight, maxWidth, minHeight); - } - - public RemoteViews getViewAt(int position) - { - Log.i("StackRemoteViewsFactory", "getViewAt " + position); - if (position < 0 || position > remoteViews.size()) return null; - return remoteViews.get(position); - } - - @NonNull - private BaseWidget constructWidget(@NonNull Habit habit, - @NonNull Preferences prefs) - { - switch (widgetType) - { - case CHECKMARK: - return new CheckmarkWidget(context, widgetId, habit); - case FREQUENCY: - return new FrequencyWidget(context, widgetId, habit, prefs.getFirstWeekdayInt()); - case SCORE: - return new ScoreWidget(context, widgetId, habit); - case HISTORY: - return new HistoryWidget(context, widgetId, habit); - case STREAKS: - return new StreakWidget(context, widgetId, habit); - } - - throw new IllegalStateException(); - } - - public RemoteViews getLoadingView() - { - Bundle options = AppWidgetManager.getInstance(context).getAppWidgetOptions(widgetId); - EmptyWidget widget = new EmptyWidget(context, widgetId); - widget.setDimensions(getDimensionsFromOptions(context, options)); - RemoteViews landscapeViews = widget.getLandscapeRemoteViews(); - RemoteViews portraitViews = widget.getPortraitRemoteViews(); - return new RemoteViews(landscapeViews, portraitViews); - } - - public int getViewTypeCount() - { - return 1; - } - - public long getItemId(int position) - { - return habitIds[position]; - } - - public boolean hasStableIds() - { - return true; - } - - public void onDataSetChanged() - { - Log.i("StackRemoteViewsFactory", "onDataSetChanged started"); - - HabitsApplication app = (HabitsApplication) context.getApplicationContext(); - Preferences prefs = app.getComponent().getPreferences(); - HabitList habitList = app.getComponent().getHabitList(); - Bundle options = AppWidgetManager.getInstance(context).getAppWidgetOptions(widgetId); - ArrayList newRemoteViews = new ArrayList<>(); - - if (Looper.myLooper() == null) Looper.prepare(); - - for (long id : habitIds) - { - Habit h = habitList.getById(id); - if (h == null) throw new HabitNotFoundException(); - - BaseWidget widget = constructWidget(h, prefs); - widget.setDimensions(getDimensionsFromOptions(context, options)); - - RemoteViews landscapeViews = widget.getLandscapeRemoteViews(); - RemoteViews portraitViews = widget.getPortraitRemoteViews(); - newRemoteViews.add(new RemoteViews(landscapeViews, portraitViews)); - - Log.i("StackRemoteViewsFactory", "onDataSetChanged constructed widget " + id); - } - - remoteViews = newRemoteViews; - Log.i("StackRemoteViewsFactory", "onDataSetChanged ended"); - } -} 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 new file mode 100644 index 000000000..a16bda608 --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetService.kt @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2016-2021 Álinson Santos Xavier + * + * This file is part of Loop Habit Tracker. + * + * Loop Habit Tracker is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Loop Habit Tracker is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ +package org.isoron.uhabits.widgets + +import android.appwidget.AppWidgetManager +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.os.Looper +import android.util.Log +import android.widget.RemoteViews +import android.widget.RemoteViewsService +import android.widget.RemoteViewsService.RemoteViewsFactory +import org.isoron.uhabits.HabitsApplication +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.StringUtils.Companion.splitLongs +import org.isoron.uhabits.utils.InterfaceUtils.dpToPixels +import java.util.ArrayList + +class StackWidgetService : RemoteViewsService() { + override fun onGetViewFactory(intent: Intent): RemoteViewsFactory { + return StackRemoteViewsFactory(this.applicationContext, intent) + } + + companion object { + const val WIDGET_TYPE = "WIDGET_TYPE" + const val HABIT_IDS = "HABIT_IDS" + } +} + +internal class StackRemoteViewsFactory(private val context: Context, intent: Intent) : + RemoteViewsFactory { + private val widgetId: Int + private val habitIds: LongArray + private val widgetType: StackWidgetType? + private var remoteViews = ArrayList() + override fun onCreate() {} + override fun onDestroy() {} + override fun getCount(): Int { + return habitIds.size + } + + fun getDimensionsFromOptions( + ctx: Context, + options: Bundle + ): WidgetDimensions { + val maxWidth = dpToPixels( + ctx, + options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH).toFloat() + ).toInt() + val maxHeight = dpToPixels( + ctx, + options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT).toFloat() + ).toInt() + val minWidth = dpToPixels( + ctx, + options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH).toFloat() + ).toInt() + val minHeight = dpToPixels( + ctx, + options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT).toFloat() + ).toInt() + return WidgetDimensions(minWidth, maxHeight, maxWidth, minHeight) + } + + override fun getViewAt(position: Int): RemoteViews? { + Log.i("StackRemoteViewsFactory", "getViewAt $position") + return if (position < 0 || position > remoteViews.size) null else remoteViews[position] + } + + private fun constructWidget( + habit: Habit, + prefs: Preferences + ): BaseWidget { + when (widgetType) { + StackWidgetType.CHECKMARK -> return CheckmarkWidget(context, widgetId, habit) + StackWidgetType.FREQUENCY -> return FrequencyWidget( + context, + widgetId, + habit, + prefs.firstWeekdayInt + ) + StackWidgetType.SCORE -> return ScoreWidget(context, widgetId, habit) + StackWidgetType.HISTORY -> return HistoryWidget(context, widgetId, habit) + StackWidgetType.STREAKS -> return StreakWidget(context, widgetId, habit) + } + throw IllegalStateException() + } + + override fun getLoadingView(): RemoteViews { + val options = AppWidgetManager.getInstance(context).getAppWidgetOptions(widgetId) + val widget = EmptyWidget(context, widgetId) + widget.setDimensions(getDimensionsFromOptions(context, options)) + val landscapeViews = widget.landscapeRemoteViews + val portraitViews = widget.portraitRemoteViews + return RemoteViews(landscapeViews, portraitViews) + } + + override fun getViewTypeCount(): Int { + return 1 + } + + override fun getItemId(position: Int): Long { + return habitIds[position] + } + + override fun hasStableIds(): Boolean { + return true + } + + 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 { + widgetId = intent.getIntExtra( + AppWidgetManager.EXTRA_APPWIDGET_ID, + AppWidgetManager.INVALID_APPWIDGET_ID + ) + val widgetTypeValue = intent.getIntExtra(StackWidgetService.WIDGET_TYPE, -1) + val habitIdsStr = intent.getStringExtra(StackWidgetService.HABIT_IDS) + if (widgetTypeValue < 0) throw RuntimeException("invalid widget type") + if (habitIdsStr == null) throw RuntimeException("habitIdsStr is null") + widgetType = StackWidgetType.Companion.getWidgetTypeFromValue(widgetTypeValue) + habitIds = splitLongs(habitIdsStr) + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetType.java b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetType.java deleted file mode 100644 index e686e2387..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetType.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2016-2021 Álinson Santos Xavier - * - * This file is part of Loop Habit Tracker. - * - * Loop Habit Tracker is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Loop Habit Tracker is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -package org.isoron.uhabits.widgets; - -import org.isoron.uhabits.R; - -/** - * Created by victoryu on 11/3/17. - */ - -public enum StackWidgetType { - - CHECKMARK(0), - FREQUENCY(1), - SCORE(2), // habit strength widget - HISTORY(3), - STREAKS(4), - TARGET(5); - - private int value; - StackWidgetType(int value) { - this.value = value; - } - - public int getValue() { - return value; - } - - public static StackWidgetType getWidgetTypeFromValue(int value) { - if (CHECKMARK.getValue() == value) { - return CHECKMARK; - } else if (FREQUENCY.getValue() == value) { - return FREQUENCY; - } else if (SCORE.getValue() == value) { - return SCORE; - } else if (HISTORY.getValue() == value) { - return HISTORY; - } else if (STREAKS.getValue() == value) { - return STREAKS; - } else if (TARGET.getValue() == value) { - return TARGET; - } - return null; - } - - public static int getStackWidgetLayoutId(StackWidgetType type) { - switch (type) { - case CHECKMARK: - return R.layout.checkmark_stackview_widget; - case FREQUENCY: - return R.layout.frequency_stackview_widget; - case SCORE: - return R.layout.score_stackview_widget; - case HISTORY: - return R.layout.history_stackview_widget; - case STREAKS: - return R.layout.streak_stackview_widget; - case TARGET: - return R.layout.target_stackview_widget; - } - return 0; - } - - public static int getStackWidgetAdapterViewId(StackWidgetType type) { - switch (type) { - case CHECKMARK: - return R.id.checkmarkStackWidgetView; - case FREQUENCY: - return R.id.frequencyStackWidgetView; - case SCORE: - return R.id.scoreStackWidgetView; - case HISTORY: - return R.id.historyStackWidgetView; - case STREAKS: - return R.id.streakStackWidgetView; - case TARGET: - return R.id.targetStackWidgetView; - } - return 0; - } - - public static int getStackWidgetEmptyViewId(StackWidgetType type) { - switch (type) { - case CHECKMARK: - return R.id.checkmarkStackWidgetEmptyView; - case FREQUENCY: - return R.id.frequencyStackWidgetEmptyView; - case SCORE: - return R.id.scoreStackWidgetEmptyView; - case HISTORY: - return R.id.historyStackWidgetEmptyView; - case STREAKS: - return R.id.streakStackWidgetEmptyView; - case TARGET: - return R.id.targetStackWidgetEmptyView; - } - return 0; - } - -} 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 new file mode 100644 index 000000000..ca120142b --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetType.kt @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2016-2021 Álinson Santos Xavier + * + * This file is part of Loop Habit Tracker. + * + * Loop Habit Tracker is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Loop Habit Tracker is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ +package org.isoron.uhabits.widgets + +import org.isoron.uhabits.R + +/** + * Created by victoryu on 11/3/17. + */ +enum class StackWidgetType(val value: Int) { + CHECKMARK(0), FREQUENCY(1), SCORE(2), // habit strength widget + HISTORY(3), STREAKS(4), TARGET(5); + + companion object { + fun getWidgetTypeFromValue(value: Int): StackWidgetType? { + return when { + CHECKMARK.value == value -> CHECKMARK + FREQUENCY.value == value -> FREQUENCY + SCORE.value == value -> SCORE + HISTORY.value == value -> HISTORY + STREAKS.value == value -> STREAKS + TARGET.value == value -> TARGET + else -> null + } + } + + fun getStackWidgetLayoutId(type: StackWidgetType?): Int { + when (type) { + CHECKMARK -> return R.layout.checkmark_stackview_widget + FREQUENCY -> return R.layout.frequency_stackview_widget + SCORE -> return R.layout.score_stackview_widget + HISTORY -> return R.layout.history_stackview_widget + STREAKS -> return R.layout.streak_stackview_widget + TARGET -> return R.layout.target_stackview_widget + } + return 0 + } + + fun getStackWidgetAdapterViewId(type: StackWidgetType?): Int { + when (type) { + CHECKMARK -> return R.id.checkmarkStackWidgetView + FREQUENCY -> return R.id.frequencyStackWidgetView + SCORE -> return R.id.scoreStackWidgetView + HISTORY -> return R.id.historyStackWidgetView + STREAKS -> return R.id.streakStackWidgetView + TARGET -> return R.id.targetStackWidgetView + } + return 0 + } + + fun getStackWidgetEmptyViewId(type: StackWidgetType?): Int { + when (type) { + CHECKMARK -> return R.id.checkmarkStackWidgetEmptyView + FREQUENCY -> return R.id.frequencyStackWidgetEmptyView + SCORE -> return R.id.scoreStackWidgetEmptyView + HISTORY -> return R.id.historyStackWidgetEmptyView + STREAKS -> return R.id.streakStackWidgetEmptyView + TARGET -> return R.id.targetStackWidgetEmptyView + } + return 0 + } + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StreakWidget.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StreakWidget.kt index 0a5658c09..fb2ee2866 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StreakWidget.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StreakWidget.kt @@ -19,6 +19,7 @@ package org.isoron.uhabits.widgets +import android.app.PendingIntent import android.content.Context import android.view.View import android.view.ViewGroup.LayoutParams @@ -31,10 +32,12 @@ import org.isoron.uhabits.widgets.views.GraphWidgetView class StreakWidget( context: Context, id: Int, - private val habit: Habit + private val habit: Habit, ) : BaseWidget(context, id) { + override val defaultHeight: Int = 200 + override val defaultWidth: Int = 200 - override fun getOnClickPendingIntent(context: Context) = + override fun getOnClickPendingIntent(context: Context): PendingIntent? = pendingIntentFactory.showHabit(habit) override fun refreshData(view: View) { @@ -53,7 +56,4 @@ class StreakWidget( layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT) } } - - override fun getDefaultHeight() = 200 - override fun getDefaultWidth() = 200 } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/TargetWidget.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/TargetWidget.kt index bd1667b55..dcfbd8cc2 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/TargetWidget.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/TargetWidget.kt @@ -19,6 +19,7 @@ package org.isoron.uhabits.widgets +import android.app.PendingIntent import android.content.Context import android.view.View import android.view.ViewGroup.LayoutParams @@ -34,10 +35,12 @@ import org.isoron.uhabits.widgets.views.GraphWidgetView class TargetWidget( context: Context, id: Int, - private val habit: Habit + private val habit: Habit, ) : BaseWidget(context, id) { + override val defaultHeight: Int = 200 + override val defaultWidth: Int = 200 - override fun getOnClickPendingIntent(context: Context) = + override fun getOnClickPendingIntent(context: Context): PendingIntent? = pendingIntentFactory.showHabit(habit) override fun refreshData(view: View) = runBlocking { @@ -58,7 +61,4 @@ class TargetWidget( layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT) } } - - override fun getDefaultHeight() = 200 - override fun getDefaultWidth() = 200 } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetView.java b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetView.java deleted file mode 100644 index 930bcdef3..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetView.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright (C) 2016-2021 Álinson Santos Xavier - * - * This file is part of Loop Habit Tracker. - * - * Loop Habit Tracker is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Loop Habit Tracker is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -package org.isoron.uhabits.widgets.views; - -import android.content.*; -import android.util.*; -import android.widget.*; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.isoron.uhabits.*; -import org.isoron.uhabits.activities.habits.list.views.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.activities.common.views.*; -import org.isoron.uhabits.core.preferences.*; -import org.isoron.uhabits.inject.*; -import org.isoron.uhabits.utils.*; - -import static org.isoron.uhabits.utils.InterfaceUtils.getDimension; - -public class CheckmarkWidgetView extends HabitWidgetView { - protected int activeColor; - - protected float percentage; - - @Nullable - protected String name; - - protected RingView ring; - - protected TextView label; - - protected int entryValue; - - protected int entryState; - - protected boolean isNumerical; - - private Preferences preferences; - - public CheckmarkWidgetView(Context context) - { - super(context); - init(); - } - - public CheckmarkWidgetView(Context context, AttributeSet attrs) - { - super(context, attrs); - init(); - } - - public void refresh() - { - if (backgroundPaint == null || frame == null || ring == null) return; - - StyledResources res = new StyledResources(getContext()); - - int bgColor; - int fgColor; - - switch (entryState) { - case Entry.YES_MANUAL: - case Entry.SKIP: - bgColor = activeColor; - fgColor = res.getColor(R.attr.highContrastReverseTextColor); - setShadowAlpha(0x4f); - backgroundPaint.setColor(bgColor); - frame.setBackgroundDrawable(background); - break; - - case Entry.YES_AUTO: - case Entry.NO: - case Entry.UNKNOWN: - default: - bgColor = res.getColor(R.attr.cardBgColor); - fgColor = res.getColor(R.attr.mediumContrastTextColor); - setShadowAlpha(0x00); - break; - } - - ring.setPercentage(percentage); - ring.setColor(fgColor); - ring.setBackgroundColor(bgColor); - ring.setText(getText()); - - label.setText(name); - label.setTextColor(fgColor); - - requestLayout(); - postInvalidate(); - } - - public void setEntryState(int entryState) - { - this.entryState = entryState; - } - - protected String getText() - { - if (isNumerical) return NumberButtonViewKt.toShortString(entryValue / 1000.0); - switch (entryState) { - case Entry.YES_MANUAL: - case Entry.YES_AUTO: - return getResources().getString(R.string.fa_check); - case Entry.SKIP: - return getResources().getString(R.string.fa_skipped); - case Entry.UNKNOWN: - { - if (preferences.areQuestionMarksEnabled()) - return getResources().getString(R.string.fa_question); - else - getResources().getString(R.string.fa_times); - } - case Entry.NO: - default: - return getResources().getString(R.string.fa_times); - } - } - - public void setActiveColor(int activeColor) - { - this.activeColor = activeColor; - } - - public void setEntryValue(int entryValue) - { - this.entryValue = entryValue; - } - - public void setName(@NonNull String name) - { - this.name = name; - } - - public void setPercentage(float percentage) - { - this.percentage = percentage; - } - - public void setNumerical(boolean isNumerical) - { - this.isNumerical = isNumerical; - } - - @Override - @NonNull - protected Integer getInnerLayoutId() - { - return R.layout.widget_checkmark; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) - { - int width = MeasureSpec.getSize(widthMeasureSpec); - int height = MeasureSpec.getSize(heightMeasureSpec); - - float w = width; - float h = width * 1.25f; - float scale = Math.min(width / w, height / h); - - w *= scale; - h *= scale; - - if (h < getDimension(getContext(), R.dimen.checkmarkWidget_heightBreakpoint)) - ring.setVisibility(GONE); - else - ring.setVisibility(VISIBLE); - - widthMeasureSpec = - MeasureSpec.makeMeasureSpec((int) w, MeasureSpec.EXACTLY); - heightMeasureSpec = - MeasureSpec.makeMeasureSpec((int) h, MeasureSpec.EXACTLY); - - float textSize = 0.15f * h; - float maxTextSize = getDimension(getContext(), R.dimen.smallerTextSize); - textSize = Math.min(textSize, maxTextSize); - - label.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); - ring.setTextSize(textSize); - ring.setThickness(0.15f * textSize); - - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - - private void init() - { - HabitsApplicationComponent appComponent; - appComponent = ((HabitsApplication) getContext().getApplicationContext()).getComponent(); - preferences = appComponent.getPreferences(); - - ring = (RingView) findViewById(R.id.scoreRing); - label = (TextView) findViewById(R.id.label); - - if (ring != null) ring.setIsTransparencyEnabled(true); - - if (isInEditMode()) - { - percentage = 0.75f; - name = "Wake up early"; - activeColor = PaletteUtils.getAndroidTestColor(6); - entryValue = Entry.YES_MANUAL; - refresh(); - } - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetView.kt new file mode 100644 index 000000000..5caa04625 --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetView.kt @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2016-2021 Álinson Santos Xavier + * + * This file is part of Loop Habit Tracker. + * + * Loop Habit Tracker is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Loop Habit Tracker is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ +package org.isoron.uhabits.widgets.views + +import android.content.Context +import android.util.AttributeSet +import android.util.TypedValue +import android.view.View +import android.widget.TextView +import org.isoron.uhabits.HabitsApplication +import org.isoron.uhabits.R +import org.isoron.uhabits.activities.common.views.RingView +import org.isoron.uhabits.activities.habits.list.views.toShortString +import org.isoron.uhabits.core.models.Entry.Companion.NO +import org.isoron.uhabits.core.models.Entry.Companion.SKIP +import org.isoron.uhabits.core.models.Entry.Companion.UNKNOWN +import org.isoron.uhabits.core.models.Entry.Companion.YES_AUTO +import org.isoron.uhabits.core.models.Entry.Companion.YES_MANUAL +import org.isoron.uhabits.core.preferences.Preferences +import org.isoron.uhabits.inject.HabitsApplicationComponent +import org.isoron.uhabits.utils.InterfaceUtils.getDimension +import org.isoron.uhabits.utils.PaletteUtils.getAndroidTestColor +import org.isoron.uhabits.utils.StyledResources + +class CheckmarkWidgetView : HabitWidgetView { + var activeColor: Int = 0 + + var percentage = 0f + var name: String? = null + protected lateinit var ring: RingView + protected lateinit var label: TextView + var entryValue = 0 + var entryState = 0 + var isNumerical = false + private var preferences: Preferences? = null + + constructor(context: Context?) : super(context) { + init() + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init() + } + + fun refresh() { + if (backgroundPaint == null || frame == null) return + val res = StyledResources(context) + val bgColor: Int + val fgColor: Int + when (entryState) { + YES_MANUAL, SKIP -> { + bgColor = activeColor + fgColor = res.getColor(R.attr.highContrastReverseTextColor) + setShadowAlpha(0x4f) + backgroundPaint!!.color = bgColor + frame!!.setBackgroundDrawable(background) + } + YES_AUTO, NO, UNKNOWN -> { + bgColor = res.getColor(R.attr.cardBgColor) + fgColor = res.getColor(R.attr.mediumContrastTextColor) + setShadowAlpha(0x00) + } + else -> { + bgColor = res.getColor(R.attr.cardBgColor) + fgColor = res.getColor(R.attr.mediumContrastTextColor) + setShadowAlpha(0x00) + } + } + ring.percentage = percentage + ring.color = fgColor + ring.setBackgroundColor(bgColor) + ring.setText(text) + label.text = name + label.setTextColor(fgColor) + requestLayout() + postInvalidate() + } + + protected val text: String + get() = if (isNumerical) { + (entryValue / 1000.0).toShortString() + } else when (entryState) { + YES_MANUAL, YES_AUTO -> resources.getString(R.string.fa_check) + SKIP -> resources.getString(R.string.fa_skipped) + UNKNOWN -> { + run { + if (preferences!!.areQuestionMarksEnabled()) { + return resources.getString(R.string.fa_question) + } else { + resources.getString(R.string.fa_times) + } + } + resources.getString(R.string.fa_times) + } + NO -> resources.getString(R.string.fa_times) + else -> resources.getString(R.string.fa_times) + } + + override val innerLayoutId: Int + get() = R.layout.widget_checkmark + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + var widthMeasureSpec = widthMeasureSpec + var heightMeasureSpec = heightMeasureSpec + val width = MeasureSpec.getSize(widthMeasureSpec) + val height = MeasureSpec.getSize(heightMeasureSpec) + var w = width.toFloat() + var h = width * 1.25f + val scale = Math.min(width / w, height / h) + w *= scale + h *= scale + if (h < getDimension(context, R.dimen.checkmarkWidget_heightBreakpoint)) ring.visibility = + GONE else ring.visibility = VISIBLE + widthMeasureSpec = MeasureSpec.makeMeasureSpec(w.toInt(), MeasureSpec.EXACTLY) + heightMeasureSpec = MeasureSpec.makeMeasureSpec(h.toInt(), MeasureSpec.EXACTLY) + var textSize = 0.15f * h + val maxTextSize = getDimension(context, R.dimen.smallerTextSize) + textSize = Math.min(textSize, maxTextSize) + label.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize) + ring.setTextSize(textSize) + ring.setThickness(0.15f * textSize) + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + } + + private fun init() { + val appComponent: HabitsApplicationComponent + appComponent = (context.applicationContext as HabitsApplication).component + preferences = appComponent.preferences + ring = findViewById(R.id.scoreRing) as RingView + label = findViewById(R.id.label) as TextView + ring.setIsTransparencyEnabled(true) + if (isInEditMode) { + percentage = 0.75f + name = "Wake up early" + activeColor = getAndroidTestColor(6) + entryValue = YES_MANUAL + refresh() + } + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/EmptyWidgetView.java b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/EmptyWidgetView.kt similarity index 54% rename from uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/EmptyWidgetView.java rename to uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/EmptyWidgetView.kt index 83ff943fd..814302fc8 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/EmptyWidgetView.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/EmptyWidgetView.kt @@ -16,41 +16,28 @@ * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ +package org.isoron.uhabits.widgets.views -package org.isoron.uhabits.widgets.views; +import android.content.Context +import android.view.View +import android.widget.TextView +import org.isoron.uhabits.R -import android.content.Context; -import androidx.annotation.NonNull; -import android.widget.TextView; - -import org.isoron.uhabits.R; - -public class EmptyWidgetView extends HabitWidgetView -{ - - private TextView title; - - public EmptyWidgetView(Context context) - { - super(context); - init(); +class EmptyWidgetView(context: Context?) : HabitWidgetView(context) { + private lateinit var title: TextView + fun setTitle(text: String?) { + title.text = text } - public void setTitle(String text) - { - title.setText(text); - } + override val innerLayoutId: Int + get() = R.layout.widget_graph - @Override - @NonNull - protected Integer getInnerLayoutId() - { - return R.layout.widget_graph; + private fun init() { + title = findViewById(R.id.title) as TextView + title.visibility = VISIBLE } - private void init() - { - title = (TextView) findViewById(R.id.title); - title.setVisibility(VISIBLE); + init { + init() } } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/GraphWidgetView.java b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/GraphWidgetView.java deleted file mode 100644 index b574d9b56..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/GraphWidgetView.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2016-2021 Álinson Santos Xavier - * - * This file is part of Loop Habit Tracker. - * - * Loop Habit Tracker is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Loop Habit Tracker is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -package org.isoron.uhabits.widgets.views; - -import android.content.*; -import android.view.*; -import android.widget.*; - -import androidx.annotation.NonNull; - -import org.isoron.uhabits.*; - -public class GraphWidgetView extends HabitWidgetView -{ - - private final View dataView; - - private TextView title; - - public GraphWidgetView(Context context, View dataView) - { - super(context); - this.dataView = dataView; - init(); - } - - public View getDataView() - { - return dataView; - } - - public void setTitle(String text) - { - title.setText(text); - } - - @Override - @NonNull - protected Integer getInnerLayoutId() - { - return R.layout.widget_graph; - } - - private void init() - { - ViewGroup.LayoutParams params = - new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT); - dataView.setLayoutParams(params); - - ViewGroup innerFrame = (ViewGroup) findViewById(R.id.innerFrame); - innerFrame.addView(dataView); - - title = (TextView) findViewById(R.id.title); - title.setVisibility(VISIBLE); - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/GraphWidgetView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/GraphWidgetView.kt new file mode 100644 index 000000000..0f935bdf1 --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/GraphWidgetView.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2016-2021 Álinson Santos Xavier + * + * This file is part of Loop Habit Tracker. + * + * Loop Habit Tracker is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Loop Habit Tracker is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ +package org.isoron.uhabits.widgets.views + +import android.content.Context +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import org.isoron.uhabits.R + +class GraphWidgetView(context: Context?, val dataView: View) : HabitWidgetView(context) { + private lateinit var title: TextView + fun setTitle(text: String?) { + title.text = text + } + + override val innerLayoutId: Int + get() = R.layout.widget_graph + + private fun init() { + val params = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + dataView.layoutParams = params + val innerFrame = findViewById(R.id.innerFrame) as ViewGroup + innerFrame.addView(dataView) + title = findViewById(R.id.title) as TextView + title.visibility = VISIBLE + } + + init { + init() + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/HabitWidgetView.java b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/HabitWidgetView.java deleted file mode 100644 index 96f0285fc..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/HabitWidgetView.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2016-2021 Álinson Santos Xavier - * - * This file is part of Loop Habit Tracker. - * - * Loop Habit Tracker is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Loop Habit Tracker is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -package org.isoron.uhabits.widgets.views; - -import android.content.*; -import android.graphics.*; -import android.graphics.drawable.*; -import android.graphics.drawable.shapes.*; -import android.util.*; -import android.view.*; -import android.widget.*; - -import androidx.annotation.*; - -import org.isoron.uhabits.*; -import org.isoron.uhabits.utils.*; - -import java.util.*; - -import static org.isoron.uhabits.utils.InterfaceUtils.*; - -public abstract class HabitWidgetView extends FrameLayout -{ - @Nullable - protected InsetDrawable background; - - @Nullable - protected Paint backgroundPaint; - - protected ViewGroup frame; - - private int shadowAlpha; - - private StyledResources res; - - private int backgroundAlpha; - - public HabitWidgetView(Context context) - { - super(context); - init(); - } - - public HabitWidgetView(Context context, AttributeSet attrs) - { - super(context, attrs); - init(); - } - - public void setShadowAlpha(int shadowAlpha) - { - this.shadowAlpha = shadowAlpha; - rebuildBackground(); - } - - public void setBackgroundAlpha(int backgroundAlpha) - { - this.backgroundAlpha = backgroundAlpha; - rebuildBackground(); - } - - protected abstract - @NonNull - Integer getInnerLayoutId(); - - public void rebuildBackground() - { - Context context = getContext(); - - int shadowRadius = (int) dpToPixels(context, 2); - int shadowOffset = (int) dpToPixels(context, 1); - int shadowColor = Color.argb(shadowAlpha, 0, 0, 0); - - float cornerRadius = dpToPixels(context, 5); - float[] radii = new float[8]; - Arrays.fill(radii, cornerRadius); - - RoundRectShape shape = new RoundRectShape(radii, null, null); - ShapeDrawable innerDrawable = new ShapeDrawable(shape); - - int insetLeftTop = Math.max(shadowRadius - shadowOffset, 0); - int insetRightBottom = shadowRadius + shadowOffset; - - background = - new InsetDrawable(innerDrawable, insetLeftTop, insetLeftTop, - insetRightBottom, insetRightBottom); - backgroundPaint = innerDrawable.getPaint(); - backgroundPaint.setShadowLayer(shadowRadius, shadowOffset, shadowOffset, - shadowColor); - backgroundPaint.setColor(res.getColor(R.attr.cardBgColor)); - backgroundPaint.setAlpha(backgroundAlpha); - - frame = (ViewGroup) findViewById(R.id.frame); - if (frame != null) frame.setBackground(background); - } - - private void init() - { - inflate(getContext(), getInnerLayoutId(), this); - res = new StyledResources(getContext()); - shadowAlpha = (int) (255 * res.getFloat(R.attr.widgetShadowAlpha)); - rebuildBackground(); - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/HabitWidgetView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/HabitWidgetView.kt new file mode 100644 index 000000000..d1465b56e --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/HabitWidgetView.kt @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2016-2021 Álinson Santos Xavier + * + * This file is part of Loop Habit Tracker. + * + * Loop Habit Tracker is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Loop Habit Tracker is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ +package org.isoron.uhabits.widgets.views + +import android.content.Context +import android.graphics.Color +import android.graphics.Paint +import android.graphics.drawable.InsetDrawable +import android.graphics.drawable.ShapeDrawable +import android.graphics.drawable.shapes.RoundRectShape +import android.util.AttributeSet +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import org.isoron.uhabits.R +import org.isoron.uhabits.utils.InterfaceUtils.dpToPixels +import org.isoron.uhabits.utils.StyledResources +import java.util.Arrays + +abstract class HabitWidgetView : FrameLayout { + protected var background: InsetDrawable? = null + protected var backgroundPaint: Paint? = null + protected var frame: ViewGroup? = null + private var shadowAlpha = 0 + private var res: StyledResources? = null + private var backgroundAlpha = 0 + + constructor(context: Context?) : super(context!!) { + init() + } + + constructor(context: Context?, attrs: AttributeSet?) : super( + context!!, + attrs + ) { + init() + } + + fun setShadowAlpha(shadowAlpha: Int) { + this.shadowAlpha = shadowAlpha + rebuildBackground() + } + + fun setBackgroundAlpha(backgroundAlpha: Int) { + this.backgroundAlpha = backgroundAlpha + rebuildBackground() + } + + protected abstract val innerLayoutId: Int + fun rebuildBackground() { + val context = context + val shadowRadius = dpToPixels(context, 2f).toInt() + val shadowOffset = dpToPixels(context, 1f).toInt() + val shadowColor = Color.argb(shadowAlpha, 0, 0, 0) + val cornerRadius = dpToPixels(context, 5f) + val radii = FloatArray(8) + Arrays.fill(radii, cornerRadius) + val shape = RoundRectShape(radii, null, null) + val innerDrawable = ShapeDrawable(shape) + val insetLeftTop = Math.max(shadowRadius - shadowOffset, 0) + val insetRightBottom = shadowRadius + shadowOffset + background = InsetDrawable( + innerDrawable, + insetLeftTop, + insetLeftTop, + insetRightBottom, + insetRightBottom + ) + backgroundPaint = innerDrawable.paint + backgroundPaint?.setShadowLayer( + shadowRadius.toFloat(), + shadowOffset.toFloat(), + shadowOffset.toFloat(), + shadowColor + ) + backgroundPaint?.color = res!!.getColor(R.attr.cardBgColor) + backgroundPaint?.alpha = backgroundAlpha + frame = findViewById(R.id.frame) as ViewGroup + if (frame != null) frame!!.background = background + } + + private fun init() { + inflate(context, innerLayoutId, this) + res = StyledResources(context) + shadowAlpha = (255 * res!!.getFloat(R.attr.widgetShadowAlpha)).toInt() + rebuildBackground() + } +}