diff --git a/uhabits-android/src/main/AndroidManifest.xml b/uhabits-android/src/main/AndroidManifest.xml index b7cbb4520..9cd76e6fc 100644 --- a/uhabits-android/src/main/AndroidManifest.xml +++ b/uhabits-android/src/main/AndroidManifest.xml @@ -103,6 +103,20 @@ android:name="android.appwidget.provider" android:resource="@xml/widget_checkmark_info"/> + + + + + + + + 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 index 0f5f27525..bf4ec7844 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidget.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidget.java @@ -156,7 +156,7 @@ public abstract class BaseWidget } @NonNull - private RemoteViews getRemoteViews(int width, int height) + protected RemoteViews getRemoteViews(int width, int height) { View view = buildView(); measureView(view, width, height); 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 index 31307bd8c..573ae2f33 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java @@ -19,15 +19,21 @@ package org.isoron.uhabits.widgets; -import android.appwidget.*; -import android.content.*; -import android.os.*; -import android.support.annotation.*; -import android.widget.*; - -import org.isoron.uhabits.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.preferences.*; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProvider; +import android.content.Context; +import android.os.Bundle; +import android.os.Looper; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +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.WidgetPreferences; import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT; import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH; @@ -77,7 +83,7 @@ public abstract class BaseWidgetProvider extends AppWidgetProvider 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"); - context.setTheme(R.style.TransparentWidgetTheme); + context.setTheme(R.style.OpaqueWidgetTheme); updateDependencies(context); @@ -123,7 +129,7 @@ public abstract class BaseWidgetProvider extends AppWidgetProvider 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"); - context.setTheme(R.style.TransparentWidgetTheme); + context.setTheme(R.style.OpaqueWidgetTheme); updateDependencies(context); diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackGroupWidget.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackGroupWidget.kt new file mode 100644 index 000000000..d978a7b6a --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackGroupWidget.kt @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2016 Á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.view.View +import android.widget.RemoteViews +import org.isoron.uhabits.R +import org.isoron.uhabits.widgets.views.CheckmarkWidgetView + +class StackGroupWidget( + context: Context, + widgetId: Int +) : BaseWidget(context, widgetId) { + + override fun getOnClickPendingIntent(context: Context) = null + + override fun refreshData(v: View) { + // unused + } + + override fun getRemoteViews(width: Int, height: Int): RemoteViews { + val remoteViews = RemoteViews(context.packageName, R.layout.widget_stackview) + val serviceIntent = Intent(context, StackWidgetService::class.java) + serviceIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id) + // TODO this commented line is taken from official examples, but adding it seems to make the widget not update immediately when completing the habit + // serviceIntent.data = Uri.parse(serviceIntent.toUri(Intent.URI_INTENT_SCHEME)) // embed extras so they don't get ignored + remoteViews.setRemoteAdapter(R.id.stackWidgetView, serviceIntent) + AppWidgetManager.getInstance(context).notifyAppWidgetViewDataChanged(id, R.id.stackWidgetView) + // TODO what should the empty view look like? + remoteViews.setEmptyView(R.id.stackWidgetView, R.id.stackWidgetEmptyView) + 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/StackWidgetProvider.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetProvider.kt new file mode 100644 index 000000000..5abd7603f --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetProvider.kt @@ -0,0 +1,17 @@ +package org.isoron.uhabits.widgets + +import android.appwidget.AppWidgetManager +import android.content.Context +import android.content.Intent +import android.util.Log +import org.isoron.uhabits.R + +/** + * Created by victoryu on 9/30/17. + */ + +class StackWidgetProvider : BaseWidgetProvider() { + override fun getWidgetFromId(context: Context, id: Int): StackGroupWidget { + return StackGroupWidget(context, id) + } +} 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 new file mode 100644 index 000000000..6a6f788e7 --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetService.java @@ -0,0 +1,110 @@ +package org.isoron.uhabits.widgets; + +import android.appwidget.AppWidgetManager; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.widget.RemoteViews; +import android.widget.RemoteViewsService; + +import org.isoron.uhabits.HabitsApplication; +import org.isoron.uhabits.core.models.Habit; + +import java.util.ArrayList; + +import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT; +import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH; +import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT; +import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH; +import static org.isoron.androidbase.utils.InterfaceUtils.dpToPixels; + +/** + * Created by victoryu on 9/30/17. + */ + +public class StackWidgetService extends RemoteViewsService { + + @Override + public RemoteViewsFactory onGetViewFactory(Intent intent) { + return new StackRemoteViewsFactory(this.getApplicationContext(), intent); + } +} + +class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory { + private Context mContext; + private int mAppWidgetId; + private ArrayList mHabitList; + + public StackRemoteViewsFactory(Context context, Intent intent) { + mContext = context; + mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); + } + + public void onCreate() { + + } + + public void onDestroy() { + mHabitList.clear(); + } + + public int getCount() { + return mHabitList.size(); + } + + @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) { + RemoteViews rv = null; + + if (position < getCount()) { + Habit habit = mHabitList.get(position); + CheckmarkWidget checkmarkWidget = new CheckmarkWidget(mContext, mAppWidgetId, habit); + Bundle options = AppWidgetManager.getInstance(mContext).getAppWidgetOptions(mAppWidgetId); + checkmarkWidget.setDimensions(getDimensionsFromOptions(mContext, options)); + RemoteViews landscape = checkmarkWidget.getLandscapeRemoteViews(); + RemoteViews portrait = checkmarkWidget.getPortraitRemoteViews(); + rv = new RemoteViews(landscape, portrait); + } + + return rv; + } + + public RemoteViews getLoadingView() { + return null; + } + + public int getViewTypeCount() { + return 1; + } + + public long getItemId(int position) { + return position; + } + + public boolean hasStableIds() { + return false; + } + + public void onDataSetChanged() { + mHabitList = new ArrayList<>(); + HabitsApplication app = (HabitsApplication) mContext.getApplicationContext(); + for (Habit h : app.getComponent().getHabitList()) { + mHabitList.add(h); + } + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/WidgetUpdater.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/WidgetUpdater.kt index 80c159dad..49f810952 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/WidgetUpdater.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/WidgetUpdater.kt @@ -61,6 +61,7 @@ class WidgetUpdater fun updateWidgets() { taskRunner.execute { updateWidgets(CheckmarkWidgetProvider::class.java) + updateWidgets(StackWidgetProvider::class.java) updateWidgets(HistoryWidgetProvider::class.java) updateWidgets(ScoreWidgetProvider::class.java) updateWidgets(StreakWidgetProvider::class.java) diff --git a/uhabits-android/src/main/res/layout/widget_stackview.xml b/uhabits-android/src/main/res/layout/widget_stackview.xml new file mode 100644 index 000000000..56b8149f8 --- /dev/null +++ b/uhabits-android/src/main/res/layout/widget_stackview.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/uhabits-android/src/main/res/values/strings.xml b/uhabits-android/src/main/res/values/strings.xml index 3ea1d2f76..45bf1823b 100644 --- a/uhabits-android/src/main/res/values/strings.xml +++ b/uhabits-android/src/main/res/values/strings.xml @@ -124,6 +124,7 @@ Version %s Frequency Checkmark + Stacked Checkmark Strength Best streaks diff --git a/uhabits-android/src/main/res/values/styles.xml b/uhabits-android/src/main/res/values/styles.xml index 622fffe3e..4342faed0 100644 --- a/uhabits-android/src/main/res/values/styles.xml +++ b/uhabits-android/src/main/res/values/styles.xml @@ -154,6 +154,10 @@ 0.25 + +