Add 1x1 widget to display current streak instead of checkmark

pull/600/head
NegatioN 5 years ago
parent 8fd8c2802b
commit 8bd1734f44

@ -107,6 +107,17 @@
android:name="android.appwidget.provider" android:name="android.appwidget.provider"
android:resource="@xml/widget_checkmark_info"/> android:resource="@xml/widget_checkmark_info"/>
</receiver> </receiver>
<receiver
android:name=".widgets.CurrentStreakWidgetProvider"
android:label="@string/current_streaks">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_checkmark_info"/>
</receiver>
<service android:name=".widgets.StackWidgetService" <service android:name=".widgets.StackWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS" android:permission="android.permission.BIND_REMOTEVIEWS"
android:exported="false" /> android:exported="false" />

@ -0,0 +1,58 @@
/*
* 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.content.*
import android.view.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.utils.*
import org.isoron.uhabits.widgets.views.*
class CurrentStreakWidget(
context: Context,
widgetId: Int,
private val habit: Habit
) : BaseWidget(context, widgetId) {
override fun getOnClickPendingIntent(context: Context) =
pendingIntentFactory.toggleCheckmark(habit, null)
override fun refreshData(v: View) {
val activeStreak = habit.streaks.activeStreak
val numReps = if (activeStreak != null) {
habit.repetitions.getByInterval(activeStreak.start, activeStreak.end).size.toString()
} else {
"0"
}
(v as CurrentStreakWidgetView).apply {
setBackgroundAlpha(preferedBackgroundAlpha)
setCurrentStreak(numReps)
setPercentage(habit.scores.todayValue.toFloat())
setActiveColor(PaletteUtils.getColor(context, habit.color))
setName(habit.name)
setCheckmarkValue(habit.checkmarks.todayValue)
refresh()
}
}
override fun buildView() = CurrentStreakWidgetView(context)
override fun getDefaultHeight() = 125
override fun getDefaultWidth() = 125
}

@ -0,0 +1,29 @@
/*
* 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.content.*
class CurrentStreakWidgetProvider : BaseWidgetProvider() {
override fun getWidgetFromId(context: Context, id: Int): BaseWidget {
val habits = getHabitsFromWidgetId(id)
if (habits.size == 1) return CurrentStreakWidget(context, id, habits[0])
else return StackWidget(context, id, StackWidgetType.CHECKMARK, habits)
}
}

@ -96,6 +96,8 @@ class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory
{ {
case CHECKMARK: case CHECKMARK:
return new CheckmarkWidget(context, widgetId, habit); return new CheckmarkWidget(context, widgetId, habit);
case CURRENTSTREAK:
return new CurrentStreakWidget(context, widgetId, habit);
case FREQUENCY: case FREQUENCY:
return new FrequencyWidget(context, widgetId, habit, prefs.getFirstWeekday()); return new FrequencyWidget(context, widgetId, habit, prefs.getFirstWeekday());
case SCORE: case SCORE:

@ -12,7 +12,8 @@ public enum StackWidgetType {
FREQUENCY(1), FREQUENCY(1),
SCORE(2), // habit strength widget SCORE(2), // habit strength widget
HISTORY(3), HISTORY(3),
STREAKS(4); STREAKS(4),
CURRENTSTREAK(5);
private int value; private int value;
StackWidgetType(int value) { StackWidgetType(int value) {
@ -26,6 +27,8 @@ public enum StackWidgetType {
public static StackWidgetType getWidgetTypeFromValue(int value) { public static StackWidgetType getWidgetTypeFromValue(int value) {
if (CHECKMARK.getValue() == value) { if (CHECKMARK.getValue() == value) {
return CHECKMARK; return CHECKMARK;
} else if (CURRENTSTREAK.getValue() == value) {
return CURRENTSTREAK;
} else if (FREQUENCY.getValue() == value) { } else if (FREQUENCY.getValue() == value) {
return FREQUENCY; return FREQUENCY;
} else if (SCORE.getValue() == value) { } else if (SCORE.getValue() == value) {
@ -42,6 +45,8 @@ public enum StackWidgetType {
switch (type) { switch (type) {
case CHECKMARK: case CHECKMARK:
return R.layout.checkmark_stackview_widget; return R.layout.checkmark_stackview_widget;
case CURRENTSTREAK:
return R.layout.currentstreak_stackview_widget;
case FREQUENCY: case FREQUENCY:
return R.layout.frequency_stackview_widget; return R.layout.frequency_stackview_widget;
case SCORE: case SCORE:
@ -58,6 +63,8 @@ public enum StackWidgetType {
switch (type) { switch (type) {
case CHECKMARK: case CHECKMARK:
return R.id.checkmarkStackWidgetView; return R.id.checkmarkStackWidgetView;
case CURRENTSTREAK:
return R.id.currentStreakkWidgetEmptyView;
case FREQUENCY: case FREQUENCY:
return R.id.frequencyStackWidgetView; return R.id.frequencyStackWidgetView;
case SCORE: case SCORE:
@ -74,6 +81,8 @@ public enum StackWidgetType {
switch (type) { switch (type) {
case CHECKMARK: case CHECKMARK:
return R.id.checkmarkStackWidgetEmptyView; return R.id.checkmarkStackWidgetEmptyView;
case CURRENTSTREAK:
return R.id.currentStreakkWidgetEmptyView;
case FREQUENCY: case FREQUENCY:
return R.id.frequencyStackWidgetEmptyView; return R.id.frequencyStackWidgetEmptyView;
case SCORE: case SCORE:

@ -63,6 +63,7 @@ class WidgetUpdater
fun updateWidgets(modifiedHabitId: Long?) { fun updateWidgets(modifiedHabitId: Long?) {
taskRunner.execute { taskRunner.execute {
updateWidgets(modifiedHabitId, CheckmarkWidgetProvider::class.java) updateWidgets(modifiedHabitId, CheckmarkWidgetProvider::class.java)
updateWidgets(modifiedHabitId, CurrentStreakWidgetProvider::class.java)
updateWidgets(modifiedHabitId, HistoryWidgetProvider::class.java) updateWidgets(modifiedHabitId, HistoryWidgetProvider::class.java)
updateWidgets(modifiedHabitId, ScoreWidgetProvider::class.java) updateWidgets(modifiedHabitId, ScoreWidgetProvider::class.java)
updateWidgets(modifiedHabitId, StreakWidgetProvider::class.java) updateWidgets(modifiedHabitId, StreakWidgetProvider::class.java)

@ -0,0 +1,189 @@
/*
* 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.views;
import android.content.Context;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.isoron.androidbase.utils.StyledResources;
import org.isoron.uhabits.R;
import org.isoron.uhabits.activities.common.views.RingView;
import org.isoron.uhabits.core.models.Checkmark;
import org.isoron.uhabits.utils.PaletteUtils;
import static org.isoron.androidbase.utils.InterfaceUtils.getDimension;
public class CurrentStreakWidgetView extends HabitWidgetView
{
private int activeColor;
private float percentage;
@Nullable
private String name;
private RingView ring;
private TextView label;
private int checkmarkValue;
private String currentStreak;
public CurrentStreakWidgetView(Context context)
{
super(context);
init();
}
public CurrentStreakWidgetView(Context context, AttributeSet attrs)
{
super(context, attrs);
init();
}
public void refresh()
{
if (backgroundPaint == null || frame == null || ring == null) return;
StyledResources res = new StyledResources(getContext());
String text;
int bgColor;
int fgColor;
switch (checkmarkValue)
{
case Checkmark.CHECKED_EXPLICITLY:
bgColor = activeColor;
fgColor = res.getColor(R.attr.highContrastReverseTextColor);
setShadowAlpha(0x4f);
backgroundPaint.setColor(bgColor);
frame.setBackgroundDrawable(background);
break;
case Checkmark.CHECKED_IMPLICITLY:
case Checkmark.UNCHECKED:
default:
bgColor = res.getColor(R.attr.cardBgColor);
fgColor = res.getColor(R.attr.mediumContrastTextColor);
setShadowAlpha(0x00);
break;
}
text = currentStreak;
ring.setPercentage(percentage);
ring.setColor(fgColor);
ring.setBackgroundColor(bgColor);
ring.setText(text);
label.setText(name);
label.setTextColor(fgColor);
requestLayout();
postInvalidate();
}
public void setActiveColor(int activeColor)
{
this.activeColor = activeColor;
}
public void setCurrentStreak(String currentStreak)
{
this.currentStreak = currentStreak;
}
public void setCheckmarkValue(int checkmarkValue)
{
this.checkmarkValue = checkmarkValue;
}
public void setName(@NonNull String name)
{
this.name = name;
}
public void setPercentage(float percentage)
{
this.percentage = percentage;
}
@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()
{
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);
checkmarkValue = Checkmark.CHECKED_EXPLICITLY;
refresh();
}
}
}

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<StackView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/currentStreakStackWidgetView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:loopViews="true" />
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/currentStreakkWidgetEmptyView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/checkmark_stack_widget"
android:gravity="center"
android:textColor="#ffffff"
android:textStyle="bold"
android:textSize="16sp" />
</FrameLayout>

@ -174,6 +174,8 @@
<string name="night_mode">Dark theme</string> <string name="night_mode">Dark theme</string>
<string name="use_pure_black">Use pure black in dark theme</string> <string name="use_pure_black">Use pure black in dark theme</string>
<string name="pure_black_description">Replaces gray backgrounds with pure black in dark theme. Reduces battery usage in phones with AMOLED display.</string> <string name="pure_black_description">Replaces gray backgrounds with pure black in dark theme. Reduces battery usage in phones with AMOLED display.</string>
<string name="use_widget_numbers">Widget numbers instead of checkmarks</string>
<string name="use_widget_numbers_description">Replaces the checkmark in the 1x1 habit widget with the number of times you\'ve currently chained the habit.</string>
<string name="interface_preferences">Interface</string> <string name="interface_preferences">Interface</string>
<string name="reverse_days">Reverse order of days</string> <string name="reverse_days">Reverse order of days</string>
<string name="reverse_days_description">Show days in reverse order on the main screen.</string> <string name="reverse_days_description">Show days in reverse order on the main screen.</string>

@ -59,6 +59,14 @@
android:key="pref_first_weekday" android:key="pref_first_weekday"
android:title="@string/first_day_of_the_week" android:title="@string/first_day_of_the_week"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<CheckBoxPreference
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:defaultValue="false"
android:key="pref_widget_numbers"
android:summary="@string/use_widget_numbers_description"
android:title="@string/use_widget_numbers"
app:iconSpaceReserved="false" />
</PreferenceCategory> </PreferenceCategory>

@ -0,0 +1,30 @@
<?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/>.
-->
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minHeight="40dp"
android:minWidth="40dp"
android:initialLayout="@layout/widget_wrapper"
android:previewImage="@drawable/widget_preview_checkmark"
android:resizeMode="none"
android:updatePeriodMillis="3600000"
android:configure="org.isoron.uhabits.widgets.HabitPickerDialog"
android:widgetCategory="home_screen">
</appwidget-provider>

@ -20,6 +20,7 @@
package org.isoron.uhabits.core.models; package org.isoron.uhabits.core.models;
import org.apache.commons.lang3.builder.*; import org.apache.commons.lang3.builder.*;
import org.isoron.uhabits.core.utils.DateUtils;
import static org.isoron.uhabits.core.utils.StringUtils.defaultToStringStyle; import static org.isoron.uhabits.core.utils.StringUtils.defaultToStringStyle;
@ -29,6 +30,8 @@ public final class Streak
private final Timestamp end; private final Timestamp end;
//TODO define the "End" of a streak to follow the rules of the given streak?
//ex if 2 times in a week, define end at end of that week. or by other rules ofc
public Streak(Timestamp start, Timestamp end) public Streak(Timestamp start, Timestamp end)
{ {
this.start = start; this.start = start;
@ -53,6 +56,12 @@ public final class Streak
return end; return end;
} }
public boolean isActive()
{
Timestamp now = DateUtils.getToday();
return (start.daysUntil(now) >= 0) & (end.daysUntil(now) <= 1);
}
public int getLength() public int getLength()
{ {
return start.daysUntil(end) + 1; return start.daysUntil(end) + 1;

@ -78,6 +78,18 @@ public abstract class StreakList
add(streaks); add(streaks);
} }
public synchronized Streak getActiveStreak()
{
List<Streak> streaks = getAll();
if (streaks.size() > 0){
Streak s = streaks.get(0);
if (s.isActive()){
return s;
}
}
return null;
}
/** /**
* Converts a list of checkmark values to a list of streaks. * Converts a list of checkmark values to a list of streaks.
* *

@ -333,6 +333,11 @@ public class Preferences
return Integer.parseInt(storage.getString("pref_widget_opacity", "102")); return Integer.parseInt(storage.getString("pref_widget_opacity", "102"));
} }
public boolean getWidgetNumberMode()
{
return storage.getBoolean("pref_widget_numbers", false);
}
/** /**
* @return An integer representing the first day of the week. Sunday * @return An integer representing the first day of the week. Sunday
* corresponds to 1, Monday to 2, and so on, until Saturday, which is * corresponds to 1, Monday to 2, and so on, until Saturday, which is

Loading…
Cancel
Save