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
+
+