StackWidget: allow user to select habits

pull/428/head
Alinson S. Xavier 8 years ago
parent 2904f3e2f8
commit b94d2f2fa6

@ -30,22 +30,27 @@ import org.isoron.uhabits.*;
import org.isoron.uhabits.core.preferences.*;
import org.isoron.uhabits.intents.*;
import static android.view.View.MeasureSpec.*;
import static android.view.View.MeasureSpec.makeMeasureSpec;
public abstract class BaseWidget
{
private final WidgetPreferences prefs;
private final int id;
@NonNull
private WidgetDimensions dimensions;
protected final WidgetPreferences widgetPrefs;
@NonNull
private final Context context;
protected final Preferences prefs;
@NonNull
protected final PendingIntentFactory pendingIntentFactory;
@NonNull
private final Context context;
@NonNull
private WidgetDimensions dimensions;
public BaseWidget(@NonNull Context context, int id)
{
this.id = id;
@ -54,15 +59,16 @@ public abstract class BaseWidget
HabitsApplication app =
(HabitsApplication) context.getApplicationContext();
prefs = app.getComponent().getWidgetPreferences();
widgetPrefs = app.getComponent().getWidgetPreferences();
prefs = app.getComponent().getPreferences();
pendingIntentFactory = app.getComponent().getPendingIntentFactory();
dimensions = new WidgetDimensions(getDefaultWidth(), getDefaultHeight(),
getDefaultWidth(), getDefaultHeight());
getDefaultWidth(), getDefaultHeight());
}
public void delete()
{
prefs.removeWidget(id);
widgetPrefs.removeWidget(id);
}
@NonNull
@ -80,7 +86,7 @@ public abstract class BaseWidget
public RemoteViews getLandscapeRemoteViews()
{
return getRemoteViews(dimensions.getLandscapeWidth(),
dimensions.getLandscapeHeight());
dimensions.getLandscapeHeight());
}
public abstract PendingIntent getOnClickPendingIntent(Context context);
@ -89,7 +95,7 @@ public abstract class BaseWidget
public RemoteViews getPortraitRemoteViews()
{
return getRemoteViews(dimensions.getPortraitWidth(),
dimensions.getPortraitHeight());
dimensions.getPortraitHeight());
}
public abstract void refreshData(View widgetView);
@ -139,7 +145,7 @@ public abstract class BaseWidget
int w = (int) (((float) entireWidth - imageWidth) / 2);
int h = (int) (((float) entireHeight - imageHeight) / 2);
return new int[]{ w, h, w, h };
return new int[]{w, h, w, h};
}
@NonNull
@ -183,7 +189,7 @@ public abstract class BaseWidget
entireView.measure(specWidth, specHeight);
entireView.layout(0, 0, entireView.getMeasuredWidth(),
entireView.getMeasuredHeight());
entireView.getMeasuredHeight());
View imageView = entireView.findViewById(R.id.imageView);
width = imageView.getMeasuredWidth();

@ -19,26 +19,19 @@
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.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;
import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT;
import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH;
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 java.util.*;
import static android.appwidget.AppWidgetManager.*;
import static org.isoron.androidbase.utils.InterfaceUtils.dpToPixels;
public abstract class BaseWidgetProvider extends AppWidgetProvider
@ -141,15 +134,18 @@ public abstract class BaseWidgetProvider extends AppWidgetProvider
}).start();
}
protected Habit getHabitFromWidgetId(int widgetId)
protected List<Habit> getHabitsFromWidgetId(int widgetId)
{
long habitId = widgetPrefs.getHabitIdFromWidgetId(widgetId);
if (habitId == WidgetPreferences.STACK_WIDGET_HABITS) {
return null;
long selectedIds[] = widgetPrefs.getHabitIdsFromWidgetId(widgetId);
ArrayList<Habit> selectedHabits = new ArrayList<>(selectedIds.length);
for (long id : selectedIds)
{
Habit h = habits.getById(id);
if (h == null) throw new HabitNotFoundException();
selectedHabits.add(h);
}
Habit habit = habits.getById(habitId);
if (habit == null) throw new HabitNotFoundException();
return habit;
return selectedHabits;
}
@NonNull

@ -18,17 +18,12 @@
*/
package org.isoron.uhabits.widgets
import android.content.Context
import android.content.*
class CheckmarkWidgetProvider : BaseWidgetProvider() {
override fun getWidgetFromId(context: Context, id: Int): BaseWidget {
// if the habit was null, but did not have a habit id associated with a stack widget,
// `getHabitFromWidgetId` will throw a HabitNotFoundException
val habit = getHabitFromWidgetId(id)
if (habit != null) {
return CheckmarkWidget(context, id, habit)
} else {
return StackWidget(context, id, StackWidgetType.CHECKMARK)
}
val habits = getHabitsFromWidgetId(id)
if (habits.size == 1) return CheckmarkWidget(context, id, habits[0])
else return StackWidget(context, id, StackWidgetType.CHECKMARK, habits)
}
}

@ -19,17 +19,12 @@
package org.isoron.uhabits.widgets
import android.content.Context
import android.content.*
class FrequencyWidgetProvider : BaseWidgetProvider() {
override fun getWidgetFromId(context: Context, id: Int): BaseWidget {
// if the habit was null, but did not have a habit id associated with a stack widget,
// `getHabitFromWidgetId` will throw a HabitNotFoundException
val habit = getHabitFromWidgetId(id)
if (habit != null) {
return FrequencyWidget(context, id, habit)
} else {
return StackWidget(context, id, StackWidgetType.FREQUENCY)
}
val habits = getHabitsFromWidgetId(id)
if (habits.size == 1) return FrequencyWidget(context, id, habits[0])
else return StackWidget(context, id, StackWidgetType.FREQUENCY, habits)
}
}

@ -23,21 +23,21 @@ import android.app.*
import android.appwidget.AppWidgetManager.*
import android.content.*
import android.os.*
import android.util.Log
import android.view.*
import android.widget.*
import android.widget.AbsListView.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.preferences.*
import java.util.*
class HabitPickerDialog : Activity(), AdapterView.OnItemClickListener {
class HabitPickerDialog : Activity() {
private var widgetId = 0
private lateinit var habitList: HabitList
private lateinit var preferences: WidgetPreferences
private lateinit var habitIds: ArrayList<Long>
private lateinit var widgetUpdater: WidgetUpdater
private lateinit var listView: ListView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -45,26 +45,35 @@ class HabitPickerDialog : Activity(), AdapterView.OnItemClickListener {
habitList = component.habitList
preferences = component.widgetPreferences
widgetUpdater = component.widgetUpdater
widgetId = intent.extras?.getInt(EXTRA_APPWIDGET_ID,
INVALID_APPWIDGET_ID) ?: 0
widgetId = intent.extras?.getInt(EXTRA_APPWIDGET_ID, INVALID_APPWIDGET_ID) ?: 0
habitIds = ArrayList<Long>()
habitIds = ArrayList()
val habitNames = ArrayList<String>()
for (h in habitList) {
if (h.isArchived) continue
habitIds.add(h.getId()!!)
habitIds.add(h.id!!)
habitNames.add(h.name)
}
setContentView(R.layout.widget_configure_activity)
with(findViewById(R.id.listView) as ListView) {
adapter = ArrayAdapter(context, android.R.layout.simple_list_item_1,
habitNames)
onItemClickListener = this@HabitPickerDialog
listView = findViewById(R.id.listView) as ListView
with(listView) {
adapter = ArrayAdapter(context, android.R.layout.simple_list_item_multiple_choice, habitNames)
choiceMode = CHOICE_MODE_MULTIPLE
itemsCanFocus = false
}
with(findViewById(R.id.createStackWidgetButton) as Button) {
setOnClickListener(View.OnClickListener {
preferences.addWidget(widgetId, WidgetPreferences.STACK_WIDGET_HABITS)
with(findViewById(R.id.buttonSave) as Button) {
setOnClickListener({
val selectedIds = mutableListOf<Long>()
for (i in 0..listView.count) {
if (listView.isItemChecked(i)) {
selectedIds.add(habitIds[i])
}
}
preferences.addWidget(widgetId, selectedIds.toLongArray())
widgetUpdater.updateWidgets()
setResult(Activity.RESULT_OK, Intent().apply {
putExtra(EXTRA_APPWIDGET_ID, widgetId)
@ -73,16 +82,4 @@ class HabitPickerDialog : Activity(), AdapterView.OnItemClickListener {
})
}
}
override fun onItemClick(parent: AdapterView<*>,
view: View,
position: Int,
id: Long) {
preferences.addWidget(widgetId, habitIds[position])
widgetUpdater.updateWidgets()
setResult(Activity.RESULT_OK, Intent().apply {
putExtra(EXTRA_APPWIDGET_ID, widgetId)
})
finish()
}
}

@ -18,17 +18,12 @@
*/
package org.isoron.uhabits.widgets
import android.content.Context
import android.content.*
class HistoryWidgetProvider : BaseWidgetProvider() {
override fun getWidgetFromId(context: Context, id: Int): BaseWidget {
// if the habit was null, but did not have a habit id associated with a stack widget,
// `getHabitFromWidgetId` will throw a HabitNotFoundException
val habit = getHabitFromWidgetId(id)
if (habit != null) {
return HistoryWidget(context, id, habit)
} else {
return StackWidget(context, id, StackWidgetType.HISTORY)
}
val habits = getHabitsFromWidgetId(id)
if (habits.size == 1) return HistoryWidget(context, id, habits[0])
else return StackWidget(context, id, StackWidgetType.HISTORY, habits)
}
}

@ -24,15 +24,13 @@ import android.view.*
import org.isoron.uhabits.activities.common.views.*
import org.isoron.uhabits.activities.habits.show.views.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.utils.*
import org.isoron.uhabits.widgets.views.*
class ScoreWidget(
context: Context,
id: Int,
private val habit: Habit,
private val prefs: Preferences
private val habit: Habit
) : BaseWidget(context, id) {
override fun getOnClickPendingIntent(context: Context) =

@ -18,19 +18,12 @@
*/
package org.isoron.uhabits.widgets
import android.content.Context
import org.isoron.uhabits.HabitsApplication
import android.content.*
class ScoreWidgetProvider : BaseWidgetProvider() {
override fun getWidgetFromId(context: Context, id: Int): BaseWidget {
// if the habit was null, but did not have a habit id associated with a stack widget,
// `getHabitFromWidgetId` will throw a HabitNotFoundException
val habit = getHabitFromWidgetId(id)
val component = (context.applicationContext as HabitsApplication).component
if (habit != null) {
return ScoreWidget(context, id, habit, component.preferences)
} else {
return StackWidget(context, id, StackWidgetType.SCORE)
}
val habits = getHabitsFromWidgetId(id)
if (habits.size == 1) return ScoreWidget(context, id, habits[0])
else return StackWidget(context, id, StackWidgetType.SCORE, habits)
}
}

@ -19,17 +19,19 @@
package org.isoron.uhabits.widgets
import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.view.View
import android.widget.RemoteViews
import android.appwidget.*
import android.content.*
import android.net.*
import android.view.*
import android.widget.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.utils.*
class StackWidget(
context: Context,
widgetId: Int,
private val widgetType: StackWidgetType
private val widgetType: StackWidgetType,
private val habits: List<Habit>
) : BaseWidget(context, widgetId) {
override fun getOnClickPendingIntent(context: Context) = null
@ -39,13 +41,17 @@ class StackWidget(
}
override fun getRemoteViews(width: Int, height: Int): RemoteViews {
val manager = AppWidgetManager.getInstance(context)
val remoteViews = RemoteViews(context.packageName, StackWidgetType.getStackWidgetLayoutId(widgetType))
val serviceIntent = Intent(context, StackWidgetService::class.java)
val habitIds = StringUtils.joinLongs(habits.map { it.id!! }.toLongArray())
serviceIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id)
serviceIntent.putExtra(StackWidgetService.WIDGET_TYPE, widgetType.value)
serviceIntent.setData(Uri.parse(serviceIntent.toUri(Intent.URI_INTENT_SCHEME)))
serviceIntent.putExtra(StackWidgetService.HABIT_IDS, habitIds)
serviceIntent.data = Uri.parse(serviceIntent.toUri(Intent.URI_INTENT_SCHEME))
remoteViews.setRemoteAdapter(StackWidgetType.getStackWidgetAdapterViewId(widgetType), serviceIntent)
AppWidgetManager.getInstance(context).notifyAppWidgetViewDataChanged(id, StackWidgetType.getStackWidgetAdapterViewId(widgetType))
manager.notifyAppWidgetViewDataChanged(id, StackWidgetType.getStackWidgetAdapterViewId(widgetType))
// TODO what should the empty view look like?
remoteViews.setEmptyView(StackWidgetType.getStackWidgetAdapterViewId(widgetType),
StackWidgetType.getStackWidgetEmptyViewId(widgetType))

@ -1,110 +1,127 @@
package org.isoron.uhabits.widgets;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
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;
import static org.isoron.uhabits.widgets.StackWidgetService.WIDGET_TYPE;
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.utils.*;
public class StackWidgetService extends RemoteViewsService {
import java.util.*;
import static android.appwidget.AppWidgetManager.*;
import static org.isoron.androidbase.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) {
public RemoteViewsFactory onGetViewFactory(Intent intent)
{
return new StackRemoteViewsFactory(this.getApplicationContext(), intent);
}
}
class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {
private Context mContext;
private int mAppWidgetId;
private ArrayList<Habit> mHabitList;
private StackWidgetType mWidgetType;
public StackRemoteViewsFactory(Context context, Intent intent) {
mContext = context;
mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory
{
private Context context;
private int widgetId;
private long[] habitIds;
private ArrayList<Habit> habits = new ArrayList<>();
private StackWidgetType widgetType;
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);
if (widgetTypeValue != -1) {
mWidgetType = StackWidgetType.getWidgetTypeFromValue(widgetTypeValue);
}
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 onCreate()
{
}
public void onDestroy() {
public void onDestroy()
{
}
public int getCount() {
return mHabitList.size();
public int getCount()
{
return habits.size();
}
@NonNull
public WidgetDimensions getDimensionsFromOptions(@NonNull Context ctx,
@NonNull Bundle options) {
@NonNull Bundle options)
{
int maxWidth =
(int) dpToPixels(ctx, options.getInt(OPTION_APPWIDGET_MAX_WIDTH));
(int) dpToPixels(ctx, options.getInt(OPTION_APPWIDGET_MAX_WIDTH));
int maxHeight =
(int) dpToPixels(ctx, options.getInt(OPTION_APPWIDGET_MAX_HEIGHT));
(int) dpToPixels(ctx, options.getInt(OPTION_APPWIDGET_MAX_HEIGHT));
int minWidth =
(int) dpToPixels(ctx, options.getInt(OPTION_APPWIDGET_MIN_WIDTH));
(int) dpToPixels(ctx, options.getInt(OPTION_APPWIDGET_MIN_WIDTH));
int minHeight =
(int) dpToPixels(ctx, options.getInt(OPTION_APPWIDGET_MIN_HEIGHT));
(int) dpToPixels(ctx, options.getInt(OPTION_APPWIDGET_MIN_HEIGHT));
return new WidgetDimensions(minWidth, maxHeight, maxWidth, minHeight);
}
public RemoteViews getViewAt(int position) {
public RemoteViews getViewAt(int position)
{
RemoteViews rv = null;
if (position < getCount()) {
Habit habit = mHabitList.get(position);
if (position < getCount())
{
Habit habit = habits.get(position);
BaseWidget widget = initializeWidget(habit);
Bundle options = AppWidgetManager.getInstance(mContext).getAppWidgetOptions(mAppWidgetId);
widget.setDimensions(getDimensionsFromOptions(mContext, options));
Bundle options =
AppWidgetManager.getInstance(context).getAppWidgetOptions(widgetId);
widget.setDimensions(getDimensionsFromOptions(context, options));
final RemoteViews[] landscape = new RemoteViews[1];
final RemoteViews[] portrait = new RemoteViews[1];
Object lock = new Object();
final boolean[] flag = {false};
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
synchronized (lock) {
landscape[0] = widget.getLandscapeRemoteViews();
portrait[0] = widget.getPortraitRemoteViews();
flag[0] = true;
lock.notifyAll();
}
}
});
synchronized (lock) {
while (!flag[0]) {
try {
new Handler(Looper.getMainLooper()).post(() ->
{
synchronized (lock)
{
landscape[0] =
widget.getLandscapeRemoteViews();
portrait[0] =
widget.getPortraitRemoteViews();
flag[0] = true;
lock.notifyAll();
}
});
synchronized (lock)
{
while (!flag[0])
{
try
{
lock.wait();
} catch (InterruptedException e) {
}
catch (InterruptedException e)
{
// ignored
}
}
}
@ -115,44 +132,55 @@ class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {
return rv;
}
private BaseWidget initializeWidget(Habit habit) {
switch (mWidgetType) {
private BaseWidget initializeWidget(Habit habit)
{
switch (widgetType)
{
case CHECKMARK:
return new CheckmarkWidget(mContext, mAppWidgetId, habit);
return new CheckmarkWidget(context, widgetId, habit);
case FREQUENCY:
return new FrequencyWidget(mContext, mAppWidgetId, habit);
return new FrequencyWidget(context, widgetId, habit);
case SCORE:
HabitsApplication app = (HabitsApplication) mContext.getApplicationContext();
return new ScoreWidget(mContext, mAppWidgetId, habit, app.getComponent().getPreferences());
return new ScoreWidget(context, widgetId, habit);
case HISTORY:
return new HistoryWidget(mContext, mAppWidgetId, habit);
return new HistoryWidget(context, widgetId, habit);
case STREAKS:
return new StreakWidget(mContext, mAppWidgetId, habit);
return new StreakWidget(context, widgetId, habit);
}
return null;
}
public RemoteViews getLoadingView() {
public RemoteViews getLoadingView()
{
return null;
}
public int getViewTypeCount() {
public int getViewTypeCount()
{
return 1;
}
public long getItemId(int position) {
public long getItemId(int position)
{
return position;
}
public boolean hasStableIds() {
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);
public void onDataSetChanged()
{
habits.clear();
HabitsApplication app = (HabitsApplication) context.getApplicationContext();
HabitList habitList = app.getComponent().getHabitList();
for (long id : habitIds)
{
Habit h = habitList.getById(id);
if (h == null) throw new HabitNotFoundException();
habits.add(h);
}
}
}

@ -18,17 +18,12 @@
*/
package org.isoron.uhabits.widgets
import android.content.Context
import android.content.*
class StreakWidgetProvider : BaseWidgetProvider() {
override fun getWidgetFromId(context: Context, id: Int): BaseWidget {
// if the habit was null, but did not have a habit id associated with a stack widget,
// `getHabitFromWidgetId` will throw a HabitNotFoundException
val habit = getHabitFromWidgetId(id)
if (habit != null) {
return StreakWidget(context, id, habit)
} else {
return StackWidget(context, id, StackWidgetType.STREAKS)
}
val habits = getHabitsFromWidgetId(id)
if (habits.size == 1) return StreakWidget(context, id, habits[0])
else return StackWidget(context, id, StackWidgetType.STREAKS, habits)
}
}

@ -22,12 +22,6 @@
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/createStackWidgetButton"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:text="@string/create_stackview_widget_button"/>
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
@ -36,4 +30,10 @@
android:layout_weight="1">
</ListView>
<Button
android:id="@+id/buttonSave"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:text="@string/save"/>
</LinearLayout>
Loading…
Cancel
Save