Implemented ability to choose multiple habits for stackview

pull/346/head
Victor Yu 8 years ago
parent 82972d6e47
commit 5a78de5a25

@ -84,6 +84,13 @@
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/> <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".widgets.HabitGroupPickerDialog"
android:theme="@style/Theme.AppCompat.Light.Dialog">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
</intent-filter>
</activity>
<activity <activity
android:name=".activities.about.AboutActivity" android:name=".activities.about.AboutActivity"
android:label="@string/about"> android:label="@string/about">

@ -0,0 +1,126 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.widgets
import android.app.Activity
import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID
import android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import android.widget.CompoundButton
import org.isoron.uhabits.HabitsApplication
import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.HabitList
import org.isoron.uhabits.core.preferences.WidgetPreferences
class HabitGroupPickerDialog : Activity(), AdapterView.OnItemClickListener {
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 habitIdsSelected: ArrayList<Long>
private lateinit var habitListView: ListView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val component = (applicationContext as HabitsApplication).component
habitList = component.habitList
preferences = component.widgetPreferences
widgetUpdater = component.widgetUpdater
widgetId = intent.extras?.getInt(EXTRA_APPWIDGET_ID, INVALID_APPWIDGET_ID) ?: 0
habitIdsSelected = ArrayList<Long>()
habitIds = ArrayList<Long>()
val habitNames = ArrayList<String>()
for (h in habitList) {
if (h.isArchived) continue
habitIds.add(h.getId()!!)
habitNames.add(h.name)
}
setContentView(R.layout.stack_widget_configure_activity)
habitListView = findViewById(R.id.stackWidgetListView) as ListView
with(habitListView) {
adapter = ListAdapter(context, R.layout.habit_checkbox_list_item,
R.id.listItemHabitName, R.id.listItemHabitCheckbox, habitNames)
onItemClickListener = this@HabitGroupPickerDialog
}
with(findViewById(R.id.doneConfigureButton) as Button) {
setOnClickListener {
if (!habitIdsSelected.isEmpty()) {
preferences.addWidget(widgetId, habitIdsSelected.toString())
widgetUpdater.updateWidgets()
setResult(Activity.RESULT_OK, Intent().apply {
putExtra(EXTRA_APPWIDGET_ID, widgetId)
})
finish()
} else {
Toast.makeText(context, getString(R.string.select_habit_requirement_prompt),
Toast.LENGTH_SHORT).show()
}
}
}
}
override fun onItemClick(parent: AdapterView<*>,
view: View,
position: Int,
id: Long) {
val checkbox = view.findViewById(R.id.listItemHabitCheckbox) as CheckBox
checkbox.isChecked = !checkbox.isChecked
}
private inner class ListAdapter(context: Context,
private var layoutResource: Int,
private var textViewResourceId: Int,
private var checkBoxResourceId: Int,
private var habitNames: List<String>) :
ArrayAdapter<String>(context, layoutResource, textViewResourceId, habitNames) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val layoutInflater: LayoutInflater = LayoutInflater.from(context)
val view = layoutInflater.inflate(layoutResource, null)
val item = getItem(position)
if (item != null) {
val tv = view.findViewById(textViewResourceId) as TextView
tv.text = habitNames.get(position)
val cb = view.findViewById(checkBoxResourceId) as CheckBox
cb.setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
if (isChecked) {
habitIdsSelected.add(habitIds.get(position))
} else {
habitIdsSelected.remove(habitIds.get(position))
}
})
}
return view
}
}
}

@ -25,11 +25,11 @@ import android.content.Intent
import android.view.View import android.view.View
import android.widget.RemoteViews import android.widget.RemoteViews
import org.isoron.uhabits.R import org.isoron.uhabits.R
import org.isoron.uhabits.widgets.views.CheckmarkWidgetView
class StackGroupWidget( class StackGroupWidget(
context: Context, context: Context,
widgetId: Int widgetId: Int,
private val habitIds: List<Long>
) : BaseWidget(context, widgetId) { ) : BaseWidget(context, widgetId) {
override fun getOnClickPendingIntent(context: Context) = null override fun getOnClickPendingIntent(context: Context) = null
@ -42,8 +42,7 @@ class StackGroupWidget(
val remoteViews = RemoteViews(context.packageName, R.layout.widget_stackview) val remoteViews = RemoteViews(context.packageName, R.layout.widget_stackview)
val serviceIntent = Intent(context, StackWidgetService::class.java) val serviceIntent = Intent(context, StackWidgetService::class.java)
serviceIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id) 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.putExtra(StackWidgetService.HABIT_IDS_SELECTED, habitIds.toLongArray())
// serviceIntent.data = Uri.parse(serviceIntent.toUri(Intent.URI_INTENT_SCHEME)) // embed extras so they don't get ignored
remoteViews.setRemoteAdapter(R.id.stackWidgetView, serviceIntent) remoteViews.setRemoteAdapter(R.id.stackWidgetView, serviceIntent)
AppWidgetManager.getInstance(context).notifyAppWidgetViewDataChanged(id, R.id.stackWidgetView) AppWidgetManager.getInstance(context).notifyAppWidgetViewDataChanged(id, R.id.stackWidgetView)
// TODO what should the empty view look like? // TODO what should the empty view look like?

@ -1,17 +1,19 @@
package org.isoron.uhabits.widgets package org.isoron.uhabits.widgets
import android.appwidget.AppWidgetManager
import android.content.Context import android.content.Context
import android.content.Intent import org.isoron.uhabits.HabitsApplication
import android.util.Log
import org.isoron.uhabits.R
/**
* Created by victoryu on 9/30/17.
*/
class StackWidgetProvider : BaseWidgetProvider() { class StackWidgetProvider : BaseWidgetProvider() {
override fun getWidgetFromId(context: Context, id: Int): StackGroupWidget { override fun getWidgetFromId(context: Context, id: Int): StackGroupWidget {
return StackGroupWidget(context, id) val habitIds = getHabitGroupFromWidget(context, id)
return StackGroupWidget(context, id, habitIds)
}
private fun getHabitGroupFromWidget(context: Context, widgetId: Int) : List<Long> {
val app = context.getApplicationContext() as HabitsApplication
val widgetPrefs = app.component.widgetPreferences
val habitIds = widgetPrefs.getHabitIdsGroupFromWidgetId(widgetId)
return habitIds
} }
} }

@ -12,19 +12,19 @@ import org.isoron.uhabits.HabitsApplication;
import org.isoron.uhabits.core.models.Habit; import org.isoron.uhabits.core.models.Habit;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT; 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_MAX_WIDTH;
import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT; import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT;
import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH; import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH;
import static org.isoron.androidbase.utils.InterfaceUtils.dpToPixels; import static org.isoron.androidbase.utils.InterfaceUtils.dpToPixels;
import static org.isoron.uhabits.widgets.StackWidgetService.HABIT_IDS_SELECTED;
/**
* Created by victoryu on 9/30/17.
*/
public class StackWidgetService extends RemoteViewsService { public class StackWidgetService extends RemoteViewsService {
public static final String HABIT_IDS_SELECTED = "HABIT_IDS_SELECTED";
@Override @Override
public RemoteViewsFactory onGetViewFactory(Intent intent) { public RemoteViewsFactory onGetViewFactory(Intent intent) {
return new StackRemoteViewsFactory(this.getApplicationContext(), intent); return new StackRemoteViewsFactory(this.getApplicationContext(), intent);
@ -35,10 +35,15 @@ class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {
private Context mContext; private Context mContext;
private int mAppWidgetId; private int mAppWidgetId;
private ArrayList<Habit> mHabitList; private ArrayList<Habit> mHabitList;
private List<Long> mHabitsSelected;
public StackRemoteViewsFactory(Context context, Intent intent) { public StackRemoteViewsFactory(Context context, Intent intent) {
mContext = context; mContext = context;
mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
mHabitsSelected = new ArrayList<>();
for (long id : intent.getLongArrayExtra(HABIT_IDS_SELECTED)) {
mHabitsSelected.add(id);
}
} }
public void onCreate() { public void onCreate() {
@ -104,7 +109,9 @@ class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {
mHabitList = new ArrayList<>(); mHabitList = new ArrayList<>();
HabitsApplication app = (HabitsApplication) mContext.getApplicationContext(); HabitsApplication app = (HabitsApplication) mContext.getApplicationContext();
for (Habit h : app.getComponent().getHabitList()) { for (Habit h : app.getComponent().getHabitList()) {
mHabitList.add(h); if (mHabitsSelected.contains(h.getId())) {
mHabitList.add(h);
}
} }
} }
} }

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/listItemHabitName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItemSmall"
android:gravity="center_vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:layout_weight="1"/>
<CheckBox
android:id="@+id/listItemHabitCheckbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="false" />
</LinearLayout>

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
~
~ 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 <http://www.gnu.org/licenses/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ListView
android:id="@+id/stackWidgetListView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="vertical"
android:layout_weight="1" >
</ListView>
<Button
android:id="@+id/doneConfigureButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/done_label" />
</LinearLayout>

@ -112,6 +112,7 @@
<string name="clear_label">Clear</string> <string name="clear_label">Clear</string>
<string name="select_hours">Select hours</string> <string name="select_hours">Select hours</string>
<string name="select_minutes">Select minutes</string> <string name="select_minutes">Select minutes</string>
<string name="select_habit_requirement_prompt">Please select at least one habit</string>
<string-array name="hints"> <string-array name="hints">
<item>@string/hint_drag</item> <item>@string/hint_drag</item>

@ -27,7 +27,7 @@
android:previewImage="@drawable/widget_preview_checkmark" android:previewImage="@drawable/widget_preview_checkmark"
android:resizeMode="none" android:resizeMode="none"
android:updatePeriodMillis="3600000" android:updatePeriodMillis="3600000"
android:configure="org.isoron.uhabits.widgets.HabitPickerDialog" android:configure="org.isoron.uhabits.widgets.HabitGroupPickerDialog"
android:widgetCategory="home_screen"> android:widgetCategory="home_screen">
</appwidget-provider> </appwidget-provider>

@ -19,43 +19,66 @@
package org.isoron.uhabits.core.preferences; package org.isoron.uhabits.core.preferences;
import org.isoron.uhabits.core.*; import org.isoron.uhabits.core.AppScope;
import org.isoron.uhabits.core.models.*; import org.isoron.uhabits.core.models.HabitNotFoundException;
import org.jetbrains.annotations.NotNull;
import javax.inject.*; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.inject.Inject;
@AppScope @AppScope
public class WidgetPreferences public class WidgetPreferences {
{
private Preferences.Storage storage; private Preferences.Storage storage;
@Inject @Inject
public WidgetPreferences(Preferences.Storage storage) public WidgetPreferences(Preferences.Storage storage) {
{
this.storage = storage; this.storage = storage;
} }
public void addWidget(int widgetId, long habitId) public void addWidget(int widgetId, long habitId) {
{
storage.putLong(getHabitIdKey(widgetId), habitId); storage.putLong(getHabitIdKey(widgetId), habitId);
} }
public long getHabitIdFromWidgetId(int widgetId) /**
{ * @param habitIds the string should be in the format: [habitId1, habitId2, habitId3...]
*/
public void addWidget(int widgetId, String habitIds) {
storage.putString(getHabitIdKey(widgetId), habitIds);
}
public long getHabitIdFromWidgetId(int widgetId) {
Long habitId = storage.getLong(getHabitIdKey(widgetId), -1); Long habitId = storage.getLong(getHabitIdKey(widgetId), -1);
if (habitId < 0) throw new HabitNotFoundException(); if (habitId < 0) throw new HabitNotFoundException();
return habitId; return habitId;
} }
public void removeWidget(int id) public List<Long> getHabitIdsGroupFromWidgetId(int widgetId) {
{ String habitIdsGroup = storage.getString(getHabitIdKey(widgetId), "");
if (habitIdsGroup.isEmpty()) throw new HabitNotFoundException();
ArrayList<Long> habitIdList = new ArrayList<>();
for (String s : parseStringToList(habitIdsGroup)) {
habitIdList.add(Long.parseLong(s.trim()));
}
return habitIdList;
}
public void removeWidget(int id) {
String habitIdKey = getHabitIdKey(id); String habitIdKey = getHabitIdKey(id);
storage.remove(habitIdKey); storage.remove(habitIdKey);
} }
private String getHabitIdKey(int id) private String getHabitIdKey(int id) {
{
return String.format("widget-%06d-habit", id); return String.format("widget-%06d-habit", id);
} }
private List<String> parseStringToList(@NotNull String str) {
return Arrays.asList(str.replace("[", "")
.replace("]", "").split(","));
}
} }

Loading…
Cancel
Save