Start refactoring widgets

pull/145/head
Alinson S. Xavier 9 years ago
parent a90e26691f
commit 52701666bc

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

@ -19,21 +19,25 @@
package org.isoron.uhabits;
import android.content.Context;
import android.os.Build;
import android.os.Looper;
import android.support.test.InstrumentationRegistry;
import android.appwidget.*;
import android.content.*;
import android.os.*;
import android.support.test.*;
import org.isoron.uhabits.models.HabitList;
import org.isoron.uhabits.tasks.BaseTask;
import org.isoron.uhabits.utils.DateUtils;
import org.isoron.uhabits.utils.InterfaceUtils;
import org.isoron.uhabits.utils.Preferences;
import org.junit.Before;
import org.isoron.uhabits.commands.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.tasks.*;
import org.isoron.uhabits.utils.*;
import org.junit.*;
import java.util.concurrent.TimeoutException;
import java.util.*;
import java.util.concurrent.*;
import javax.inject.Inject;
import javax.inject.*;
import static junit.framework.Assert.*;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
public class BaseAndroidTest
{
@ -52,6 +56,9 @@ public class BaseAndroidTest
@Inject
protected HabitList habitList;
@Inject
protected CommandRunner commandRunner;
protected AndroidTestComponent androidTestComponent;
protected HabitFixtures fixtures;
@ -78,6 +85,18 @@ public class BaseAndroidTest
fixtures = new HabitFixtures(habitList);
}
protected void sleep(int time)
{
try
{
Thread.sleep(time);
}
catch (InterruptedException e)
{
fail();
}
}
protected void waitForAsyncTasks()
throws InterruptedException, TimeoutException
{
@ -89,4 +108,19 @@ public class BaseAndroidTest
BaseTask.waitForTasks(10000);
}
protected void assertWidgetProviderIsInstalled(ComponentName desiredProvider)
{
AppWidgetManager manager = AppWidgetManager.getInstance(targetContext);
List<AppWidgetProviderInfo> providerInfoList;
List<ComponentName> installedProviders;
providerInfoList = manager.getInstalledProviders();
installedProviders = new LinkedList<>();
for (AppWidgetProviderInfo info : providerInfoList)
installedProviders.add(info.provider);
assertThat(installedProviders, hasItems(desiredProvider));
}
}

@ -21,10 +21,11 @@ package org.isoron.uhabits;
import android.graphics.*;
import android.os.*;
import android.support.annotation.*;
import android.view.*;
import android.widget.*;
import org.isoron.uhabits.tasks.*;
import org.isoron.uhabits.ui.common.views.*;
import org.isoron.uhabits.ui.widgets.*;
import org.isoron.uhabits.utils.*;
import java.io.*;
@ -34,7 +35,9 @@ import static junit.framework.Assert.*;
public class BaseViewTest extends BaseAndroidTest
{
protected static final double DEFAULT_SIMILARITY_CUTOFF = 0.09;
public static final int HISTOGRAM_BIN_SIZE = 8;
private double similarityCutoff;
@Override
@ -44,21 +47,8 @@ public class BaseViewTest extends BaseAndroidTest
similarityCutoff = DEFAULT_SIMILARITY_CUTOFF;
}
protected void setSimilarityCutoff(double similarityCutoff)
{
this.similarityCutoff = similarityCutoff;
}
protected void measureView(int width, int height, View view)
{
int specWidth = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
int specHeight = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
view.measure(specWidth, specHeight);
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
}
protected void assertRenders(View view, String expectedImagePath) throws IOException
protected void assertRenders(View view, String expectedImagePath)
throws IOException
{
StringBuilder errorMessage = new StringBuilder();
expectedImagePath = getVersionedViewAssetPath(expectedImagePath);
@ -70,98 +60,122 @@ public class BaseViewTest extends BaseAndroidTest
int width = actual.getWidth();
int height = actual.getHeight();
Bitmap scaledExpected = Bitmap.createScaledBitmap(expected, width, height, true);
Bitmap scaledExpected =
Bitmap.createScaledBitmap(expected, width, height, true);
double distance;
boolean similarEnough = true;
if ((distance = compareHistograms(getHistogram(actual), getHistogram(scaledExpected))) >
similarityCutoff)
if ((distance = compareHistograms(getHistogram(actual),
getHistogram(scaledExpected))) > similarityCutoff)
{
similarEnough = false;
errorMessage.append(String.format(
"Rendered image has wrong histogram (distance=%f). ",
distance));
"Rendered image has wrong histogram (distance=%f). ",
distance));
}
if(!similarEnough)
if (!similarEnough)
{
saveBitmap(expectedImagePath, ".expected", scaledExpected);
String path = saveBitmap(expectedImagePath, "", actual);
errorMessage.append(String.format("Actual rendered image " + "saved to %s", path));
errorMessage.append(
String.format("Actual rendered image " + "saved to %s", path));
fail(errorMessage.toString());
}
actual.recycle();
expected.recycle();
scaledExpected.recycle();
}
private Bitmap getBitmapFromAssets(String path) throws IOException
protected int dpToPixels(int dp)
{
InputStream stream = testContext.getAssets().open(path);
return BitmapFactory.decodeStream(stream);
return (int) InterfaceUtils.dpToPixels(targetContext, dp);
}
private String getVersionedViewAssetPath(String path)
protected void measureView(View view, int width, int height)
{
String result = null;
int specWidth =
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
int specHeight =
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
if (android.os.Build.VERSION.SDK_INT >= 21)
{
try
{
String vpath = "views-v21/" + path;
testContext.getAssets().open(vpath);
result = vpath;
}
catch (IOException e)
{
// ignored
}
}
view.measure(specWidth, specHeight);
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
}
if(result == null)
result = "views/" + path;
protected void setSimilarityCutoff(double similarityCutoff)
{
this.similarityCutoff = similarityCutoff;
}
return result;
protected void tap(GestureDetector.OnGestureListener view, int x, int y)
throws InterruptedException
{
long now = SystemClock.uptimeMillis();
MotionEvent e =
MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, dpToPixels(x),
dpToPixels(y), 0);
view.onSingleTapUp(e);
e.recycle();
}
private String saveBitmap(String filename, String suffix, Bitmap bitmap)
throws IOException
private double compareHistograms(int[][] actualHistogram,
int[][] expectedHistogram)
{
File dir = FileUtils.getSDCardDir("test-screenshots");
if(dir == null) dir = FileUtils.getFilesDir("test-screenshots");
if(dir == null) throw new RuntimeException("Could not find suitable dir for screenshots");
long diff = 0;
long total = 0;
filename = filename.replaceAll("\\.png$", suffix + ".png");
String absolutePath = String.format("%s/%s", dir.getAbsolutePath(), filename);
for (int i = 0; i < 256 / HISTOGRAM_BIN_SIZE; i++)
{
diff += Math.abs(actualHistogram[0][i] - expectedHistogram[0][i]);
diff += Math.abs(actualHistogram[1][i] - expectedHistogram[1][i]);
diff += Math.abs(actualHistogram[2][i] - expectedHistogram[2][i]);
diff += Math.abs(actualHistogram[3][i] - expectedHistogram[3][i]);
File parent = new File(absolutePath).getParentFile();
if(!parent.exists() && !parent.mkdirs())
throw new RuntimeException(String.format("Could not create dir: %s",
parent.getAbsolutePath()));
total += actualHistogram[0][i];
total += actualHistogram[1][i];
total += actualHistogram[2][i];
total += actualHistogram[3][i];
}
FileOutputStream out = new FileOutputStream(absolutePath);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
return (double) diff / total / 2;
}
return absolutePath;
@NonNull
protected FrameLayout convertToView(BaseWidget widget,
int width,
int height)
{
widget.setDimensions(
new WidgetDimensions(width, height, width, height));
FrameLayout view = new FrameLayout(targetContext);
RemoteViews remoteViews = widget.getPortraitRemoteViews();
view.addView(remoteViews.apply(targetContext, view));
measureView(view, width, height);
return view;
}
private Bitmap getBitmapFromAssets(String path) throws IOException
{
InputStream stream = testContext.getAssets().open(path);
return BitmapFactory.decodeStream(stream);
}
private int[][] getHistogram(Bitmap bitmap)
{
int histogram[][] = new int[4][256 / HISTOGRAM_BIN_SIZE];
for(int x = 0; x < bitmap.getWidth(); x++)
for (int x = 0; x < bitmap.getWidth(); x++)
{
for(int y = 0; y < bitmap.getHeight(); y++)
for (int y = 0; y < bitmap.getHeight(); y++)
{
int color = bitmap.getPixel(x, y);
int[] argb = new int[]{
(color >> 24) & 0xff, //alpha
(color >> 16) & 0xff, //red
(color >> 8) & 0xff, //green
(color ) & 0xff //blue
(color >> 24) & 0xff, //alpha
(color >> 16) & 0xff, //red
(color >> 8) & 0xff, //green
(color) & 0xff //blue
};
histogram[0][argb[0] / HISTOGRAM_BIN_SIZE]++;
@ -174,59 +188,49 @@ public class BaseViewTest extends BaseAndroidTest
return histogram;
}
private double compareHistograms(int[][] actualHistogram, int[][] expectedHistogram)
private String getVersionedViewAssetPath(String path)
{
long diff = 0;
long total = 0;
String result = null;
for(int i = 0; i < 256 / HISTOGRAM_BIN_SIZE; i ++)
if (android.os.Build.VERSION.SDK_INT >= 21)
{
diff += Math.abs(actualHistogram[0][i] - expectedHistogram[0][i]);
diff += Math.abs(actualHistogram[1][i] - expectedHistogram[1][i]);
diff += Math.abs(actualHistogram[2][i] - expectedHistogram[2][i]);
diff += Math.abs(actualHistogram[3][i] - expectedHistogram[3][i]);
total += actualHistogram[0][i];
total += actualHistogram[1][i];
total += actualHistogram[2][i];
total += actualHistogram[3][i];
try
{
String vpath = "views-v21/" + path;
testContext.getAssets().open(vpath);
result = vpath;
}
catch (IOException e)
{
// ignored
}
}
return (double) diff / total / 2;
}
if (result == null) result = "views/" + path;
protected int dpToPixels(int dp)
{
return (int) InterfaceUtils.dpToPixels(targetContext, dp);
return result;
}
protected void tap(GestureDetector.OnGestureListener view, int x, int y) throws InterruptedException
private String saveBitmap(String filename, String suffix, Bitmap bitmap)
throws IOException
{
long now = SystemClock.uptimeMillis();
MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, dpToPixels(x),
dpToPixels(y), 0);
view.onSingleTapUp(e);
e.recycle();
}
File dir = FileUtils.getSDCardDir("test-screenshots");
if (dir == null) dir = FileUtils.getFilesDir("test-screenshots");
if (dir == null) throw new RuntimeException(
"Could not find suitable dir for screenshots");
protected void refreshData(final HabitChart view)
{
new BaseTask()
{
@Override
protected void doInBackground()
{
view.refreshData();
}
}.execute();
filename = filename.replaceAll("\\.png$", suffix + ".png");
String absolutePath =
String.format("%s/%s", dir.getAbsolutePath(), filename);
try
{
waitForAsyncTasks();
}
catch (Exception e)
{
throw new RuntimeException("Time out");
}
File parent = new File(absolutePath).getParentFile();
if (!parent.exists() && !parent.mkdirs()) throw new RuntimeException(
String.format("Could not create dir: %s",
parent.getAbsolutePath()));
FileOutputStream out = new FileOutputStream(absolutePath);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
return absolutePath;
}
}

@ -48,7 +48,7 @@ public class FrequencyChartTest extends BaseViewTest
view = new FrequencyChart(targetContext);
view.setFrequency(habit.getRepetitions().getWeekdayFrequency());
view.setColor(ColorUtils.getAndroidTestColor(habit.getColor()));
measureView(dpToPixels(300), dpToPixels(100), view);
measureView(view, dpToPixels(300), dpToPixels(100));
}
@Test
@ -69,7 +69,7 @@ public class FrequencyChartTest extends BaseViewTest
@Test
public void testRender_withDifferentSize() throws Throwable
{
measureView(dpToPixels(200), dpToPixels(200), view);
measureView(view, dpToPixels(200), dpToPixels(200));
assertRenders(view, BASE_PATH + "renderDifferentSize.png");
}

@ -48,7 +48,7 @@ public class HistoryChartTest extends BaseViewTest
chart = new HistoryChart(targetContext);
chart.setCheckmarks(habit.getCheckmarks().getAllValues());
chart.setColor(ColorUtils.getAndroidTestColor(habit.getColor()));
measureView(dpToPixels(400), dpToPixels(200), chart);
measureView(chart, dpToPixels(400), dpToPixels(200));
}
// @Test
@ -106,7 +106,7 @@ public class HistoryChartTest extends BaseViewTest
@Test
public void testRender_withDifferentSize() throws Throwable
{
measureView(dpToPixels(200), dpToPixels(200), chart);
measureView(chart, dpToPixels(200), dpToPixels(200));
assertRenders(chart, BASE_PATH + "renderDifferentSize.png");
}

@ -55,7 +55,7 @@ public class RingViewTest extends BaseViewTest
@Test
public void testRender_base() throws IOException
{
measureView(dpToPixels(100), dpToPixels(100), view);
measureView(view, dpToPixels(100), dpToPixels(100));
assertRenders(view, BASE_PATH + "render.png");
}
@ -65,7 +65,7 @@ public class RingViewTest extends BaseViewTest
view.setPercentage(0.25f);
view.setColor(ColorUtils.getAndroidTestColor(5));
measureView(dpToPixels(200), dpToPixels(200), view);
measureView(view, dpToPixels(200), dpToPixels(200));
assertRenders(view, BASE_PATH + "renderDifferentParams.png");
}
}

@ -50,9 +50,9 @@ public class ScoreChartTest extends BaseViewTest
view = new ScoreChart(targetContext);
view.setScores(habit.getScores().getAll());
view.setPrimaryColor(ColorUtils.getColor(targetContext, habit.getColor()));
view.setColor(ColorUtils.getColor(targetContext, habit.getColor()));
view.setBucketSize(7);
measureView(dpToPixels(300), dpToPixels(200), view);
measureView(view, dpToPixels(300), dpToPixels(200));
}
@Test
@ -75,7 +75,7 @@ public class ScoreChartTest extends BaseViewTest
@Test
public void testRender_withDifferentSize() throws Throwable
{
measureView(dpToPixels(200), dpToPixels(200), view);
measureView(view, dpToPixels(200), dpToPixels(200));
assertRenders(view, BASE_PATH + "renderDifferentSize.png");
}

@ -48,7 +48,7 @@ public class StreakChartTest extends BaseViewTest
view = new StreakChart(targetContext);
view.setColor(ColorUtils.getAndroidTestColor(habit.getColor()));
view.setStreaks(habit.getStreaks().getBest(5));
measureView(dpToPixels(300), dpToPixels(100), view);
measureView(view, dpToPixels(300), dpToPixels(100));
}
@Test
@ -60,7 +60,7 @@ public class StreakChartTest extends BaseViewTest
@Test
public void testRender_withSmallSize() throws Throwable
{
measureView(dpToPixels(100), dpToPixels(100), view);
measureView(view, dpToPixels(100), dpToPixels(100));
assertRenders(view, BASE_PATH + "renderSmallSize.png");
}

@ -54,7 +54,7 @@ public class CheckmarkButtonViewTest extends BaseViewTest
view.setValue(Checkmark.UNCHECKED);
view.setColor(ColorUtils.getAndroidTestColor(7));
measureView(dpToPixels(40), dpToPixels(40), view);
measureView(view, dpToPixels(40), dpToPixels(40));
}
@Test

@ -62,7 +62,7 @@ public class CheckmarkPanelViewTest extends BaseViewTest
view.setCheckmarkValues(checkmarks);
view.setColor(ColorUtils.getAndroidTestColor(7));
measureView(dpToPixels(200), dpToPixels(200), view);
measureView(view, dpToPixels(200), dpToPixels(200));
}
// protected void waitForLatch() throws InterruptedException

@ -0,0 +1,90 @@
/*
* 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.ui.widgets;
import android.content.*;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import android.widget.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.widgets.*;
import org.junit.*;
import org.junit.runner.*;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
import static org.isoron.uhabits.models.Checkmark.*;
@RunWith(AndroidJUnit4.class)
@MediumTest
public class CheckmarkWidgetTest extends BaseViewTest
{
private static final String PATH = "widgets/CheckmarkWidgetView/";
private Habit habit;
private CheckmarkList checkmarks;
private FrameLayout view;
@Override
public void setUp()
{
super.setUp();
habit = fixtures.createShortHabit();
checkmarks = habit.getCheckmarks();
CheckmarkWidget widget = new CheckmarkWidget(targetContext, 0, habit);
view = convertToView(widget, 200, 250);
assertThat(checkmarks.getTodayValue(), equalTo(CHECKED_EXPLICITLY));
}
@Test
public void testClick() throws Exception
{
Button button = (Button) view.findViewById(R.id.button);
assertThat(button, is(not(nullValue())));
// A better test would be to capture the intent, but it doesn't seem
// possible to capture intents sent to BroadcastReceivers.
button.performClick();
sleep(1000);
assertThat(checkmarks.getTodayValue(), equalTo(CHECKED_IMPLICITLY));
}
@Test
public void testIsInstalled()
{
ComponentName provider =
new ComponentName(targetContext, CheckmarkWidgetProvider.class);
assertWidgetProviderIsInstalled(provider);
}
@Test
public void testRender() throws Exception
{
assertRenders(view, PATH + "checked.png");
}
}

@ -17,20 +17,18 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.widgets.views;
package org.isoron.uhabits.ui.widgets.views;
import android.support.test.runner.AndroidJUnit4;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.utils.DateUtils;
import org.isoron.uhabits.utils.InterfaceUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.utils.*;
import org.junit.*;
import org.junit.runner.*;
import java.io.IOException;
import java.io.*;
@RunWith(AndroidJUnit4.class)
@MediumTest
@ -51,9 +49,16 @@ public class CheckmarkWidgetViewTest extends BaseViewTest
habit = fixtures.createShortHabit();
view = new CheckmarkWidgetView(targetContext);
view.setHabit(habit);
refreshData(view);
measureView(dpToPixels(100), dpToPixels(200), view);
int color = ColorUtils.getAndroidTestColor(habit.getColor());
int score = habit.getScores().getTodayValue();
float percentage = (float) score / Score.MAX_VALUE;
view.setActiveColor(color);
view.setCheckmarkValue(habit.getCheckmarks().getTodayValue());
view.setPercentage(percentage);
view.setName(habit.getName());
view.refresh();
measureView(view, dpToPixels(100), dpToPixels(200));
}
@Test
@ -65,29 +70,23 @@ public class CheckmarkWidgetViewTest extends BaseViewTest
@Test
public void testRender_implicitlyChecked() throws IOException
{
long today = DateUtils.getStartOfToday();
long day = DateUtils.millisecondsInOneDay;
habit.getRepetitions().toggleTimestamp(today);
habit.getRepetitions().toggleTimestamp(today - day);
habit.getRepetitions().toggleTimestamp(today - 2 * day);
view.refreshData();
view.setCheckmarkValue(Checkmark.CHECKED_IMPLICITLY);
view.refresh();
assertRenders(view, PATH + "implicitly_checked.png");
}
@Test
public void testRender_largeSize() throws IOException
{
measureView(dpToPixels(300), dpToPixels(300), view);
measureView(view, dpToPixels(300), dpToPixels(300));
assertRenders(view, PATH + "large_size.png");
}
@Test
public void testRender_unchecked() throws IOException
{
habit.getRepetitions().toggleTimestamp(DateUtils.getStartOfToday());
view.refreshData();
view.setCheckmarkValue(Checkmark.UNCHECKED);
view.refresh();
assertRenders(view, PATH + "unchecked.png");
}
}

@ -81,7 +81,7 @@
android:theme="@style/Theme.AppCompat.Light.NoActionBar"/>
<activity
android:name=".widgets.HabitPickerDialog"
android:name=".ui.widgets.HabitPickerDialog"
android:theme="@style/Theme.AppCompat.Light.Dialog">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
@ -108,53 +108,53 @@
android:resource="@xml/widget_checkmark_info"/>
</receiver>
<receiver
android:name=".widgets.HistoryWidgetProvider"
android:label="@string/history">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_history_info"/>
</receiver>
<receiver
android:name=".widgets.ScoreWidgetProvider"
android:label="@string/habit_strength">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_score_info"/>
</receiver>
<receiver
android:name=".widgets.StreakWidgetProvider"
android:label="@string/streaks">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_streak_info"/>
</receiver>
<receiver
android:name=".widgets.FrequencyWidgetProvider"
android:label="@string/frequency">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_frequency_info"/>
</receiver>
<!--<receiver-->
<!--android:name=".widgets.HistoryWidgetProvider"-->
<!--android:label="@string/history">-->
<!--<intent-filter>-->
<!--<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>-->
<!--</intent-filter>-->
<!--<meta-data-->
<!--android:name="android.appwidget.provider"-->
<!--android:resource="@xml/widget_history_info"/>-->
<!--</receiver>-->
<!--<receiver-->
<!--android:name=".widgets.ScoreWidgetProvider"-->
<!--android:label="@string/habit_strength">-->
<!--<intent-filter>-->
<!--<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>-->
<!--</intent-filter>-->
<!--<meta-data-->
<!--android:name="android.appwidget.provider"-->
<!--android:resource="@xml/widget_score_info"/>-->
<!--</receiver>-->
<!--<receiver-->
<!--android:name=".widgets.StreakWidgetProvider"-->
<!--android:label="@string/streaks">-->
<!--<intent-filter>-->
<!--<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>-->
<!--</intent-filter>-->
<!--<meta-data-->
<!--android:name="android.appwidget.provider"-->
<!--android:resource="@xml/widget_streak_info"/>-->
<!--</receiver>-->
<!--<receiver-->
<!--android:name=".widgets.FrequencyWidgetProvider"-->
<!--android:label="@string/frequency">-->
<!--<intent-filter>-->
<!--<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>-->
<!--</intent-filter>-->
<!--<meta-data-->
<!--android:name="android.appwidget.provider"-->
<!--android:resource="@xml/widget_frequency_info"/>-->
<!--</receiver>-->
<receiver android:name=".HabitBroadcastReceiver">
<intent-filter>

@ -63,4 +63,11 @@ public class AndroidModule
{
return new Preferences();
}
@Provides
@Singleton
WidgetPreferences provideWidgetPreferences()
{
return new WidgetPreferences();
}
}

@ -30,6 +30,7 @@ import org.isoron.uhabits.ui.habits.list.controllers.*;
import org.isoron.uhabits.ui.habits.list.model.*;
import org.isoron.uhabits.ui.habits.list.views.*;
import org.isoron.uhabits.ui.habits.show.*;
import org.isoron.uhabits.ui.widgets.*;
import org.isoron.uhabits.widgets.*;
/**
@ -90,4 +91,8 @@ public interface BaseComponent
void inject(BaseDialogFragment baseDialogFragment);
void inject(ShowHabitController showHabitController);
void inject(BaseWidget baseWidget);
void inject(WidgetUpdater widgetManager);
}

@ -27,14 +27,12 @@ import android.os.*;
import android.preference.*;
import android.support.v4.app.*;
import android.support.v4.app.TaskStackBuilder;
import android.support.v4.content.*;
import org.isoron.uhabits.commands.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.tasks.*;
import org.isoron.uhabits.ui.habits.show.*;
import org.isoron.uhabits.utils.*;
import org.isoron.uhabits.widgets.*;
import java.util.*;
@ -124,16 +122,6 @@ public class HabitBroadcastReceiver extends BroadcastReceiver
notificationManager.cancel(notificationId);
}
public static void sendRefreshBroadcast(Context context)
{
LocalBroadcastManager manager =
LocalBroadcastManager.getInstance(context);
Intent refreshIntent = new Intent(HabitsApplication.ACTION_REFRESH);
manager.sendBroadcast(refreshIntent);
WidgetManager.updateWidgets(context);
}
@Override
public void onReceive(final Context context, Intent intent)
{
@ -178,7 +166,6 @@ public class HabitBroadcastReceiver extends BroadcastReceiver
}
dismissNotification(context, habitId);
sendRefreshBroadcast(context);
}
private boolean checkWeekday(Intent intent, Habit habit)

@ -25,21 +25,16 @@ import android.support.annotation.*;
import com.activeandroid.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.ui.widgets.*;
import org.isoron.uhabits.utils.*;
import java.io.*;
import javax.inject.*;
/**
* The Android application for Loop Habit Tracker.
*/
public class HabitsApplication extends Application
{
public static final String ACTION_REFRESH =
"org.isoron.uhabits.ACTION_REFRESH";
public static final int RESULT_BUG_REPORT = 4;
public static final int RESULT_EXPORT_CSV = 2;
@ -56,19 +51,13 @@ public class HabitsApplication extends Application
@Nullable
private static Context context;
@Inject
HabitList habitList;
private static WidgetUpdater widgetManager;
public static BaseComponent getComponent()
{
return component;
}
public HabitList getHabitList()
{
return habitList;
}
public static void setComponent(BaseComponent component)
{
HabitsApplication.component = component;
@ -86,6 +75,15 @@ public class HabitsApplication extends Application
return application;
}
@NonNull
public static WidgetUpdater getWidgetManager()
{
if (widgetManager == null)
throw new RuntimeException("widgetManager is null");
return widgetManager;
}
public static boolean isTestMode()
{
try
@ -108,6 +106,7 @@ public class HabitsApplication extends Application
HabitsApplication.context = this;
HabitsApplication.application = this;
component = DaggerAndroidComponent.builder().build();
component.inject(this);
if (isTestMode())
{
@ -115,7 +114,9 @@ public class HabitsApplication extends Application
if (db.exists()) db.delete();
}
component.inject(this);
widgetManager = new WidgetUpdater(this);
widgetManager.startListening();
DatabaseUtils.initializeActiveAndroid();
}
@ -124,6 +125,7 @@ public class HabitsApplication extends Application
{
HabitsApplication.context = null;
ActiveAndroid.dispose();
widgetManager.stopListening();
super.onTerminate();
}
}

@ -160,7 +160,7 @@ public abstract class BaseScreen
*
* @param rootView the root view for this screen.
*/
public void setRootView(@Nullable BaseRootView rootView)
protected void setRootView(@Nullable BaseRootView rootView)
{
this.rootView = rootView;
activity.setContentView(rootView);

@ -28,7 +28,6 @@ import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.tasks.*;
import org.isoron.uhabits.utils.*;
import org.isoron.uhabits.widgets.*;
import java.io.*;
import java.lang.Process;
@ -119,22 +118,6 @@ public class BaseSystem
}.execute();
}
/**
* Refreshes all application widgets.
*/
public void updateWidgets()
{
new BaseTask()
{
@Override
protected void doInBackground()
{
WidgetManager.updateWidgets(context);
}
}.execute();
}
private String getDeviceInfo()
{
if (context == null) return "null context\n";

@ -137,7 +137,7 @@ public class ScoreChart extends ScrollableChart
requestLayout();
}
public void setPrimaryColor(int primaryColor)
public void setColor(int primaryColor)
{
this.primaryColor = primaryColor;
postInvalidate();

@ -19,14 +19,14 @@
package org.isoron.uhabits.ui.habits.list;
import android.os.Bundle;
import android.os.*;
import org.isoron.uhabits.HabitsApplication;
import org.isoron.uhabits.models.HabitList;
import org.isoron.uhabits.ui.BaseActivity;
import org.isoron.uhabits.ui.BaseSystem;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.ui.*;
import org.isoron.uhabits.ui.habits.list.model.*;
import javax.inject.Inject;
import javax.inject.*;
/**
* Activity that allows the user to see and modify the list of habits.
@ -36,19 +36,55 @@ public class ListHabitsActivity extends BaseActivity
@Inject
HabitList habitList;
private HabitCardListAdapter adapter;
private ListHabitsRootView rootView;
private ListHabitsScreen screen;
private ListHabitsMenu menu;
private ListHabitsSelectionMenu selectionMenu;
private ListHabitsController controller;
private BaseSystem system;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
HabitsApplication.getComponent().inject(this);
BaseSystem system = new BaseSystem(this);
ListHabitsScreen screen = new ListHabitsScreen(this);
ListHabitsController controller =
new ListHabitsController(screen, system, habitList);
int checkmarkCount = ListHabitsRootView.MAX_CHECKMARK_COUNT;
system = new BaseSystem(this);
adapter = new HabitCardListAdapter(checkmarkCount);
rootView = new ListHabitsRootView(this, adapter);
screen = new ListHabitsScreen(this, rootView);
menu = new ListHabitsMenu(this, screen, adapter);
selectionMenu = new ListHabitsSelectionMenu(screen, adapter);
controller = new ListHabitsController(screen, system, habitList);
screen.setMenu(menu);
screen.setSelectionMenu(selectionMenu);
rootView.setController(controller, selectionMenu);
screen.setController(controller);
setScreen(screen);
controller.onStartup();
}
@Override
protected void onPause()
{
adapter.cancelRefresh();
super.onPause();
}
@Override
protected void onResume()
{
super.onResume();
adapter.refresh();
}
}

@ -95,7 +95,7 @@ public class ListHabitsController
habitList.reorder(from, to);
}
public void onImportData(File file)
public void onImportData(@NonNull File file)
{
ImportDataTask task = new ImportDataTask(file, screen.getProgressBar());
task.setListener(this);
@ -163,7 +163,6 @@ public class ListHabitsController
if (prefs.isFirstRun()) onFirstRun();
new Handler().postDelayed(() -> {
system.updateWidgets();
system.scheduleReminders();
}, 1000);
}

@ -26,6 +26,7 @@ import android.view.MenuItem;
import org.isoron.uhabits.R;
import org.isoron.uhabits.ui.BaseActivity;
import org.isoron.uhabits.ui.BaseMenu;
import org.isoron.uhabits.ui.habits.list.model.*;
import org.isoron.uhabits.utils.InterfaceUtils;
public class ListHabitsMenu extends BaseMenu
@ -35,11 +36,15 @@ public class ListHabitsMenu extends BaseMenu
private boolean showArchived;
private final HabitCardListAdapter adapter;
public ListHabitsMenu(@NonNull BaseActivity activity,
@NonNull ListHabitsScreen screen)
@NonNull ListHabitsScreen screen,
@NonNull HabitCardListAdapter adapter)
{
super(activity);
this.screen = screen;
this.adapter = adapter;
}
@Override
@ -79,7 +84,7 @@ public class ListHabitsMenu extends BaseMenu
case R.id.action_show_archived:
showArchived = !showArchived;
screen.getRootView().setShowArchived(showArchived);
adapter.setShowArchived(showArchived);
invalidate();
return true;

@ -59,13 +59,27 @@ public class ListHabitsRootView extends BaseRootView
@BindView(R.id.hintView)
HintView hintView;
@Nullable
private HabitCardListAdapter listAdapter;
@NonNull
private final HabitCardListAdapter listAdapter;
public ListHabitsRootView(@NonNull Context context)
public ListHabitsRootView(@NonNull Context context,
@NonNull HabitCardListAdapter listAdapter)
{
super(context);
init();
addView(inflate(getContext(), R.layout.list_habits, null));
ButterKnife.bind(this);
this.listAdapter = listAdapter;
listView.setAdapter(listAdapter);
listAdapter.setListView(listView);
tvStarEmpty.setTypeface(InterfaceUtils.getFontAwesome(getContext()));
initToolbar();
String hints[] =
getContext().getResources().getStringArray(R.array.hints);
HintList hintList = new HintList(hints);
hintView.setHints(hintList);
}
public static int getCheckmarkCount(View v)
@ -84,12 +98,6 @@ public class ListHabitsRootView extends BaseRootView
return progressBar;
}
public void setShowArchived(boolean showArchived)
{
if (listAdapter == null) return;
listAdapter.setShowArchived(showArchived);
}
@NonNull
@Override
public Toolbar getToolbar()
@ -103,12 +111,9 @@ public class ListHabitsRootView extends BaseRootView
updateEmptyView();
}
public void setController(@Nullable ListHabitsController controller,
@Nullable ListHabitsSelectionMenu menu)
public void setController(@NonNull ListHabitsController controller,
@NonNull ListHabitsSelectionMenu menu)
{
listView.setController(null);
if (controller == null || menu == null || listAdapter == null) return;
HabitCardListController listController =
new HabitCardListController(listAdapter, listView);
@ -118,49 +123,23 @@ public class ListHabitsRootView extends BaseRootView
menu.setListController(listController);
}
public void setListAdapter(@NonNull HabitCardListAdapter listAdapter)
{
if (this.listAdapter != null)
listAdapter.getObservable().removeListener(this);
this.listAdapter = listAdapter;
listView.setAdapter(listAdapter);
listAdapter.setListView(listView);
}
@Override
protected void onAttachedToWindow()
{
super.onAttachedToWindow();
if (listAdapter != null) listAdapter.getObservable().addListener(this);
listAdapter.getObservable().addListener(this);
}
@Override
protected void onDetachedFromWindow()
{
if (listAdapter != null)
listAdapter.getObservable().removeListener(this);
listAdapter.getObservable().removeListener(this);
super.onDetachedFromWindow();
}
private void init()
{
addView(inflate(getContext(), R.layout.list_habits, null));
ButterKnife.bind(this);
tvStarEmpty.setTypeface(InterfaceUtils.getFontAwesome(getContext()));
initToolbar();
String hints[] =
getContext().getResources().getStringArray(R.array.hints);
HintList hintList = new HintList(hints);
hintView.setHints(hintList);
}
private void updateEmptyView()
{
if (listAdapter == null) return;
llEmpty.setVisibility(
listAdapter.getCount() > 0 ? View.GONE : View.VISIBLE);
}

@ -32,7 +32,6 @@ import org.isoron.uhabits.models.*;
import org.isoron.uhabits.ui.*;
import org.isoron.uhabits.ui.about.*;
import org.isoron.uhabits.ui.habits.edit.*;
import org.isoron.uhabits.ui.habits.list.model.*;
import org.isoron.uhabits.ui.habits.show.*;
import org.isoron.uhabits.ui.intro.*;
import org.isoron.uhabits.ui.settings.*;
@ -42,37 +41,14 @@ import java.io.*;
public class ListHabitsScreen extends BaseScreen
{
@Nullable
ListHabitsController controller;
@NonNull
private final ListHabitsRootView rootView;
@NonNull
private final ListHabitsSelectionMenu selectionMenu;
public ListHabitsScreen(@NonNull BaseActivity activity)
public ListHabitsScreen(@NonNull BaseActivity activity,
ListHabitsRootView rootView)
{
super(activity);
rootView = new ListHabitsRootView(activity);
setRootView(rootView);
ListHabitsMenu menu = new ListHabitsMenu(activity, this);
selectionMenu = new ListHabitsSelectionMenu(this);
setMenu(menu);
setSelectionMenu(selectionMenu);
HabitCardListAdapter adapter =
new HabitCardListAdapter(ListHabitsRootView.MAX_CHECKMARK_COUNT);
rootView.setListAdapter(adapter);
selectionMenu.setListAdapter(adapter);
}
@NonNull
public ListHabitsRootView getRootView()
{
return rootView;
}
@Override
@ -100,12 +76,6 @@ public class ListHabitsScreen extends BaseScreen
}
}
public void setController(@Nullable ListHabitsController controller)
{
this.controller = controller;
rootView.setController(controller, selectionMenu);
}
public void showAboutScreen()
{
Intent intent = new Intent(activity, AboutActivity.class);

@ -42,16 +42,18 @@ public class ListHabitsSelectionMenu extends BaseSelectionMenu
@Inject
CommandRunner commandRunner;
@Nullable
private HabitCardListAdapter listAdapter;
@NonNull
private final HabitCardListAdapter listAdapter;
@Nullable
private HabitCardListController listController;
public ListHabitsSelectionMenu(@NonNull ListHabitsScreen screen)
public ListHabitsSelectionMenu(@NonNull ListHabitsScreen screen,
HabitCardListAdapter listAdapter)
{
this.screen = screen;
HabitsApplication.getComponent().inject(this);
this.listAdapter = listAdapter;
}
@Override
@ -64,8 +66,6 @@ public class ListHabitsSelectionMenu extends BaseSelectionMenu
@Override
public boolean onItemClicked(@NonNull MenuItem item)
{
if (listAdapter == null) return false;
List<Habit> selected = listAdapter.getSelected();
if (selected.isEmpty()) return false;
@ -104,7 +104,6 @@ public class ListHabitsSelectionMenu extends BaseSelectionMenu
@Override
public boolean onPrepare(@NonNull Menu menu)
{
if (listAdapter == null) return false;
List<Habit> selected = listAdapter.getSelected();
boolean showEdit = (selected.size() == 1);
@ -149,12 +148,6 @@ public class ListHabitsSelectionMenu extends BaseSelectionMenu
screen.startSelection();
}
public void setListAdapter(@Nullable HabitCardListAdapter listAdapter)
{
if (listAdapter == null) return;
this.listAdapter = listAdapter;
}
public void setListController(HabitCardListController listController)
{
this.listController = listController;

@ -93,6 +93,6 @@ public class CheckmarkButtonController
void onInvalidToggle();
void onToggle(Habit habit, long timestamp);
void onToggle(@NonNull Habit habit, long timestamp);
}
}

@ -19,7 +19,7 @@
package org.isoron.uhabits.ui.habits.list.controllers;
import android.support.annotation.Nullable;
import android.support.annotation.*;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.ui.habits.list.views.HabitCardView;
@ -39,7 +39,7 @@ public class HabitCardController implements HabitCardView.Controller
}
@Override
public void onToggle(Habit habit, long timestamp)
public void onToggle(@NonNull Habit habit, long timestamp)
{
if (view != null) view.triggerRipple(timestamp);
if (listener != null) listener.onToggle(habit, timestamp);

@ -147,7 +147,7 @@ public class HabitCardListController implements DragSortListView.DropListener,
* @param timestamp the timestamps of the checkmark
*/
@Override
public void onToggle(Habit habit, long timestamp)
public void onToggle(@NonNull Habit habit, long timestamp)
{
if (habitListener != null) habitListener.onToggle(habit, timestamp);
}
@ -203,7 +203,7 @@ public class HabitCardListController implements DragSortListView.DropListener,
*
* @param habit the habit clicked
*/
void onHabitClick(Habit habit);
void onHabitClick(@NonNull Habit habit);
/**
* Called when the user wants to change the position of a habit on the
@ -212,7 +212,7 @@ public class HabitCardListController implements DragSortListView.DropListener,
* @param from habit to be moved
* @param to habit that currently occupies the desired position
*/
void onHabitReorder(Habit from, Habit to);
void onHabitReorder(@NonNull Habit from, @NonNull Habit to);
}
/**

@ -62,6 +62,11 @@ public class HabitCardListAdapter extends BaseAdapter
cache.setCheckmarkCount(checkmarkCount);
}
public void cancelRefresh()
{
cache.cancelTasks();
}
/**
* Sets all items as not selected.
*/
@ -77,11 +82,6 @@ public class HabitCardListAdapter extends BaseAdapter
return cache.getHabitCount();
}
public boolean getIncludeArchived()
{
return cache.getIncludeArchived();
}
/**
* Returns the item that occupies a certain position on the list
*
@ -163,6 +163,11 @@ public class HabitCardListAdapter extends BaseAdapter
cache.onDetached();
}
public void refresh()
{
cache.refreshAllHabits(true);
}
/**
* Changes the order of habits on the adapter.
* <p>

@ -69,6 +69,12 @@ public class HabitCardListCache implements CommandRunner.Listener
HabitsApplication.getComponent().inject(this);
}
public void cancelTasks()
{
if(currentFetchTask != null)
currentFetchTask.cancel(true);
}
public int[] getCheckmarks(long habitId)
{
return data.checkmarks.get(habitId);
@ -256,6 +262,7 @@ public class HabitCardListCache implements CommandRunner.Listener
newData.copyScoresFrom(data);
newData.copyCheckmarksFrom(data);
// sleep(1000);
commit();
if (!refreshScoresAndCheckmarks) return;
@ -274,10 +281,23 @@ public class HabitCardListCache implements CommandRunner.Listener
newData.checkmarks.put(id,
h.getCheckmarks().getValues(dateFrom, dateTo));
// sleep(1000);
publishProgress(current++, newData.habits.size());
}
}
private void sleep(int time)
{
try
{
Thread.sleep(time);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
@Override
protected void onPostExecute(Void aVoid)
{

@ -40,6 +40,14 @@ public class ShowHabitActivity extends BaseActivity
@Inject
HabitList habitList;
private ShowHabitController controller;
private ShowHabitRootView rootView;
private ShowHabitScreen screen;
private ShowHabitsMenu menu;
@Override
protected void onCreate(Bundle savedInstanceState)
{
@ -47,15 +55,15 @@ public class ShowHabitActivity extends BaseActivity
HabitsApplication.getComponent().inject(this);
Habit habit = getHabitFromIntent();
ShowHabitScreen screen = new ShowHabitScreen(this, habit);
ShowHabitRootView view = new ShowHabitRootView(this, habit);
screen.setRootView(view);
this.setScreen(screen);
rootView = new ShowHabitRootView(this, habit);
screen = new ShowHabitScreen(this, habit, rootView);
setScreen(screen);
ShowHabitsMenu menu = new ShowHabitsMenu(this, screen);
ShowHabitController controller = new ShowHabitController(screen, habit);
menu = new ShowHabitsMenu(this, screen);
screen.setMenu(menu);
view.setController(controller);
controller = new ShowHabitController(screen, habit);
rootView.setController(controller);
}
@NonNull

@ -31,10 +31,13 @@ public class ShowHabitScreen extends BaseScreen
@NonNull
private final Habit habit;
public ShowHabitScreen(@NonNull BaseActivity activity, @NonNull Habit habit)
public ShowHabitScreen(@NonNull BaseActivity activity,
@NonNull Habit habit,
ShowHabitRootView view)
{
super(activity);
this.habit = habit;
setRootView(view);
}
public void showEditHabitDialog()

@ -58,7 +58,6 @@ public class HistoryCard extends HabitCard
@OnClick(R.id.edit)
public void onClickEditButton()
{
Log.d("HistoryCard", "onClickEditButton");
controller.onEditHistoryButtonClick();
}

@ -107,7 +107,7 @@ public class ScoreCard extends HabitCard
{
spinner.setVisibility(GONE);
title.setTextColor(ColorUtils.getAndroidTestColor(1));
chart.setPrimaryColor(ColorUtils.getAndroidTestColor(1));
chart.setColor(ColorUtils.getAndroidTestColor(1));
chart.populateWithRandomData();
}
}
@ -141,7 +141,7 @@ public class ScoreCard extends HabitCard
int color =
ColorUtils.getColor(getContext(), getHabit().getColor());
title.setTextColor(color);
chart.setPrimaryColor(color);
chart.setColor(color);
}
}
}

@ -0,0 +1,196 @@
/*
* 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.ui.widgets;
import android.app.*;
import android.content.*;
import android.graphics.*;
import android.support.annotation.*;
import android.view.*;
import android.widget.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.utils.*;
import javax.inject.*;
import static android.os.Build.VERSION.*;
import static android.os.Build.VERSION_CODES.*;
import static android.view.View.MeasureSpec.*;
public abstract class BaseWidget
{
@Inject
WidgetPreferences preferences;
private final int id;
@NonNull
private WidgetDimensions dimensions;
@NonNull
private final Context context;
public BaseWidget(@NonNull Context context, int id)
{
this.id = id;
this.context = context;
HabitsApplication.getComponent().inject(this);
dimensions = new WidgetDimensions(0, 0, 0, 0);
}
public void delete()
{
preferences.removeWidget(id);
}
@NonNull
public Context getContext()
{
return context;
}
public int getId()
{
return id;
}
@NonNull
public RemoteViews getLandscapeRemoteViews()
{
return getRemoteViews(dimensions.getLandscapeWidth(),
dimensions.getLandscapeHeight());
}
public abstract int getLayoutId();
public abstract PendingIntent getOnClickPendingIntent(Context context);
@NonNull
public RemoteViews getPortraitRemoteViews()
{
return getRemoteViews(dimensions.getPortraitWidth(),
dimensions.getPortraitHeight());
}
public abstract void refreshData(View widgetView);
public void setDimensions(@NonNull WidgetDimensions dimensions)
{
this.dimensions = dimensions;
}
protected abstract View buildView();
protected abstract int getDefaultHeight();
protected abstract int getDefaultWidth();
protected abstract String getTitle();
private void adjustRemoteViewsPadding(RemoteViews remoteViews,
View view,
int width,
int height)
{
int imageWidth = view.getMeasuredWidth();
int imageHeight = view.getMeasuredHeight();
int p[] = calculatePadding(width, height, imageWidth, imageHeight);
remoteViews.setViewPadding(R.id.buttonOverlay, p[0], p[1], p[2], p[3]);
}
private void buildRemoteViews(View view,
RemoteViews remoteViews,
int width,
int height)
{
Bitmap bitmap = getBitmapFromView(view);
remoteViews.setTextViewText(R.id.label, getTitle());
remoteViews.setImageViewBitmap(R.id.imageView, bitmap);
if (SDK_INT >= JELLY_BEAN)
adjustRemoteViewsPadding(remoteViews, view, width, height);
PendingIntent onClickIntent = getOnClickPendingIntent(context);
if (onClickIntent != null)
remoteViews.setOnClickPendingIntent(R.id.button, onClickIntent);
}
private int[] calculatePadding(int entireWidth,
int entireHeight,
int imageWidth,
int imageHeight)
{
int w = (int) (((float) entireWidth - imageWidth) / 2);
int h = (int) (((float) entireHeight - imageHeight) / 2);
return new int[]{ w, h, w, h };
}
private Bitmap getBitmapFromView(View view)
{
view.invalidate();
view.setDrawingCacheEnabled(true);
view.buildDrawingCache(true);
return view.getDrawingCache();
}
@NonNull
private RemoteViews getRemoteViews(int width, int height)
{
View view = buildView();
measureView(view, width, height);
refreshData(view);
if(view.isLayoutRequested())
measureView(view, width, height);
RemoteViews remoteViews =
new RemoteViews(context.getPackageName(), getLayoutId());
buildRemoteViews(view, remoteViews, width, height);
return remoteViews;
}
private void measureView(View view, int width, int height)
{
LayoutInflater inflater = LayoutInflater.from(context);
View entireView = inflater.inflate(getLayoutId(), null);
int specWidth = makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
int specHeight = makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
entireView.measure(specWidth, specHeight);
entireView.layout(0, 0, entireView.getMeasuredWidth(),
entireView.getMeasuredHeight());
View imageView = entireView.findViewById(R.id.imageView);
width = imageView.getMeasuredWidth();
height = imageView.getMeasuredHeight();
specWidth = makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
specHeight = makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
view.measure(specWidth, specHeight);
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
}
}

@ -0,0 +1,96 @@
/*
* 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.ui.widgets;
import android.app.*;
import android.content.*;
import android.support.annotation.*;
import android.view.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.ui.widgets.views.*;
import org.isoron.uhabits.utils.*;
public class CheckmarkWidget extends BaseWidget
{
@NonNull
private final Habit habit;
public CheckmarkWidget(@NonNull Context context,
int widgetId,
@NonNull Habit habit)
{
super(context, widgetId);
this.habit = habit;
}
@Override
public int getLayoutId()
{
return R.layout.widget_wrapper;
}
@Override
public PendingIntent getOnClickPendingIntent(Context context)
{
return HabitBroadcastReceiver.buildCheckIntent(context, habit, null);
}
@Override
public void refreshData(View v)
{
CheckmarkWidgetView view = (CheckmarkWidgetView) v;
int color = ColorUtils.getColor(getContext(), habit.getColor());
int score = habit.getScores().getTodayValue();
float percentage = (float) score / Score.MAX_VALUE;
int checkmark = habit.getCheckmarks().getTodayValue();
view.setPercentage(percentage);
view.setActiveColor(color);
view.setName(habit.getName());
view.setCheckmarkValue(checkmark);
view.refresh();
}
@Override
protected View buildView()
{
return new CheckmarkWidgetView(getContext());
}
@Override
protected int getDefaultHeight()
{
return 125;
}
@Override
protected int getDefaultWidth()
{
return 125;
}
@Override
protected String getTitle()
{
return habit.getName();
}
}

@ -17,38 +17,53 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.widgets;
import android.app.Activity;
import android.appwidget.AppWidgetManager;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import org.isoron.uhabits.HabitsApplication;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.models.HabitList;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
public class HabitPickerDialog extends Activity implements AdapterView.OnItemClickListener
package org.isoron.uhabits.ui.widgets;
import android.app.*;
import android.content.*;
import android.os.*;
import android.view.*;
import android.widget.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.utils.*;
import java.util.*;
import javax.inject.*;
import static android.appwidget.AppWidgetManager.*;
public class HabitPickerDialog extends Activity
implements AdapterView.OnItemClickListener
{
@Inject
HabitList habitList;
@Inject
WidgetPreferences preferences;
private Integer widgetId;
private ArrayList<Long> habitIds;
@Override
public void onItemClick(AdapterView<?> parent,
View view,
int position,
long id)
{
Long habitId = habitIds.get(position);
preferences.addWidget(widgetId, habitId);
HabitsApplication.getWidgetManager().updateWidgets(this);
Intent resultValue = new Intent();
resultValue.putExtra(EXTRA_APPWIDGET_ID, widgetId);
setResult(RESULT_OK, resultValue);
finish();
}
@Override
protected void onCreate(Bundle savedInstanceState)
{
@ -59,8 +74,8 @@ public class HabitPickerDialog extends Activity implements AdapterView.OnItemCli
Intent intent = getIntent();
Bundle extras = intent.getExtras();
if (extras != null) widgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
if (extras != null)
widgetId = extras.getInt(EXTRA_APPWIDGET_ID, INVALID_APPWIDGET_ID);
ListView listView = (ListView) findViewById(R.id.listView);
@ -74,29 +89,11 @@ public class HabitPickerDialog extends Activity implements AdapterView.OnItemCli
habitNames.add(h.getName());
}
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1,
habitNames);
ArrayAdapter<String> adapter =
new ArrayAdapter<>(this, android.R.layout.simple_list_item_1,
habitNames);
listView.setAdapter(adapter);
listView.setOnItemClickListener(this);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
{
Long habitId = habitIds.get(position);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
getApplicationContext());
prefs
.edit()
.putLong(BaseWidgetProvider.getHabitIdKey(widgetId), habitId)
.commit();
WidgetManager.updateWidgets(this);
Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
setResult(RESULT_OK, resultValue);
finish();
}
}

@ -0,0 +1,62 @@
/*
* 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.ui.widgets;
public class WidgetDimensions
{
private final int portraitWidth;
private final int portraitHeight;
private final int landscapeWidth;
private final int landscapeHeight;
public WidgetDimensions(int portraitWidth,
int portraitHeight,
int landscapeWidth,
int landscapeHeight)
{
this.portraitWidth = portraitWidth;
this.portraitHeight = portraitHeight;
this.landscapeWidth = landscapeWidth;
this.landscapeHeight = landscapeHeight;
}
public int getLandscapeHeight()
{
return landscapeHeight;
}
public int getLandscapeWidth()
{
return landscapeWidth;
}
public int getPortraitHeight()
{
return portraitHeight;
}
public int getPortraitWidth()
{
return portraitWidth;
}
}

@ -0,0 +1,98 @@
/*
* 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.ui.widgets;
import android.appwidget.*;
import android.content.*;
import android.support.annotation.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.commands.*;
import org.isoron.uhabits.widgets.*;
import javax.inject.*;
/**
* A WidgetUpdater listens to the commands being executed by the application and
* updates the home-screen widgets accordingly.
* <p>
* There should be only one instance of this class, created when the application
* starts. To access it, call HabitApplication.getWidgetUpdater().
*/
public class WidgetUpdater implements CommandRunner.Listener
{
@Inject
CommandRunner commandRunner;
@NonNull
private final Context context;
public WidgetUpdater(@NonNull Context context)
{
this.context = context;
HabitsApplication.getComponent().inject(this);
}
@Override
public void onCommandExecuted(@NonNull Command command,
@Nullable Long refreshKey)
{
updateWidgets(context);
}
/**
* Instructs the updater to start listening to commands. If any relevant
* commands are executed after this method is called, the corresponding
* widgets will get updated.
*/
public void startListening()
{
commandRunner.addListener(this);
}
/**
* Instructs the updater to stop listening to commands. Every command
* executed after this method is called will be ignored by the updater.
*/
public void stopListening()
{
commandRunner.removeListener(this);
}
void updateWidgets(Context context)
{
updateWidgets(context, CheckmarkWidgetProvider.class);
updateWidgets(context, HistoryWidgetProvider.class);
updateWidgets(context, ScoreWidgetProvider.class);
updateWidgets(context, StreakWidgetProvider.class);
updateWidgets(context, FrequencyWidgetProvider.class);
}
private void updateWidgets(Context context, Class providerClass)
{
ComponentName provider = new ComponentName(context, providerClass);
Intent intent = new Intent(context, providerClass);
intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
int ids[] =
AppWidgetManager.getInstance(context).getAppWidgetIds(provider);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids);
context.sendBroadcast(intent);
}
}

@ -17,26 +17,19 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.widgets.views;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Checkmark;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.models.Score;
import org.isoron.uhabits.ui.common.views.HabitChart;
import org.isoron.uhabits.ui.common.views.RingView;
import org.isoron.uhabits.utils.ColorUtils;
import org.isoron.uhabits.utils.InterfaceUtils;
package org.isoron.uhabits.ui.widgets.views;
import android.content.*;
import android.support.annotation.*;
import android.util.*;
import android.widget.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.ui.common.views.*;
import org.isoron.uhabits.utils.*;
public class CheckmarkWidgetView extends HabitWidgetView
implements HabitChart
{
private int activeColor;
@ -94,6 +87,10 @@ public class CheckmarkWidgetView extends HabitWidgetView
R.attr.cardBackgroundColor);
foregroundColor = InterfaceUtils.getStyledColor(context,
R.attr.mediumContrastTextColor);
setShadowAlpha(0x00);
rebuildBackground();
break;
case Checkmark.UNCHECKED:
@ -103,6 +100,10 @@ public class CheckmarkWidgetView extends HabitWidgetView
R.attr.cardBackgroundColor);
foregroundColor = InterfaceUtils.getStyledColor(context,
R.attr.mediumContrastTextColor);
setShadowAlpha(0x00);
rebuildBackground();
break;
}
@ -118,47 +119,31 @@ public class CheckmarkWidgetView extends HabitWidgetView
postInvalidate();
}
@Override
public void refreshData()
public void setCheckmarkValue(int checkmarkValue)
{
if (habit == null) return;
this.percentage =
(float) habit.getScores().getTodayValue() / Score.MAX_VALUE;
this.checkmarkValue = habit.getCheckmarks().getTodayValue();
refresh();
this.checkmarkValue = checkmarkValue;
}
@Override
public void setHabit(@NonNull Habit habit)
public void setName(@NonNull String name)
{
super.setHabit(habit);
this.name = habit.getName();
this.activeColor = ColorUtils.getColor(getContext(), habit.getColor());
refresh();
this.name = name;
}
@Override
@NonNull
protected Integer getInnerLayoutId()
public void setPercentage(float percentage)
{
return R.layout.widget_checkmark;
this.percentage = percentage;
}
private void init()
public void setActiveColor(int activeColor)
{
ring = (RingView) findViewById(R.id.scoreRing);
label = (TextView) findViewById(R.id.label);
if (ring != null) ring.setIsTransparencyEnabled(true);
this.activeColor = activeColor;
}
if (isInEditMode())
{
percentage = 0.75f;
name = "Wake up early";
activeColor = ColorUtils.getAndroidTestColor(6);
checkmarkValue = Checkmark.CHECKED_EXPLICITLY;
refresh();
}
@Override
@NonNull
protected Integer getInnerLayoutId()
{
return R.layout.widget_checkmark;
}
@Override
@ -194,4 +179,21 @@ public class CheckmarkWidgetView extends HabitWidgetView
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 = ColorUtils.getAndroidTestColor(6);
checkmarkValue = Checkmark.CHECKED_EXPLICITLY;
refresh();
}
}
}

@ -17,63 +17,57 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.widgets.views;
package org.isoron.uhabits.ui.widgets.views;
import android.content.Context;
import android.support.annotation.NonNull;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.content.*;
import android.support.annotation.*;
import android.view.*;
import android.widget.*;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.ui.common.views.HabitChart;
import org.isoron.uhabits.*;
public class GraphWidgetView extends HabitWidgetView implements HabitChart
public class GraphWidgetView extends HabitWidgetView
{
private final HabitChart dataView;
private final View dataView;
private TextView title;
public GraphWidgetView(Context context, HabitChart dataView)
public GraphWidgetView(Context context, View dataView)
{
super(context);
this.dataView = dataView;
init();
}
private void init()
public View getDataView()
{
ViewGroup.LayoutParams params =
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
((View) dataView).setLayoutParams(params);
ViewGroup innerFrame = (ViewGroup) findViewById(R.id.innerFrame);
innerFrame.addView(((View) dataView));
title = (TextView) findViewById(R.id.title);
title.setVisibility(VISIBLE);
return dataView;
}
@Override
public void setHabit(@NonNull Habit habit)
public void setTitle(String text)
{
super.setHabit(habit);
dataView.setHabit(habit);
title.setText(habit.getName());
title.setText(text);
}
@Override
public void refreshData()
{
if(habit == null) return;
dataView.refreshData();
}
@NonNull
protected Integer getInnerLayoutId()
{
return R.layout.widget_graph;
}
private void init()
{
ViewGroup.LayoutParams params =
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
dataView.setLayoutParams(params);
ViewGroup innerFrame = (ViewGroup) findViewById(R.id.innerFrame);
innerFrame.addView(dataView);
title = (TextView) findViewById(R.id.title);
title.setVisibility(VISIBLE);
}
}

@ -17,7 +17,7 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.widgets.views;
package org.isoron.uhabits.ui.widgets.views;
import android.content.*;
import android.graphics.*;
@ -29,14 +29,11 @@ import android.view.*;
import android.widget.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.ui.common.views.*;
import org.isoron.uhabits.utils.*;
import java.util.*;
public abstract class HabitWidgetView extends FrameLayout
implements HabitChart
{
@Nullable
protected InsetDrawable background;
@ -44,9 +41,6 @@ public abstract class HabitWidgetView extends FrameLayout
@Nullable
protected Paint backgroundPaint;
@Nullable
protected Habit habit;
protected ViewGroup frame;
private int shadowAlpha;
@ -63,12 +57,6 @@ public abstract class HabitWidgetView extends FrameLayout
init();
}
@Override
public void setHabit(@NonNull Habit habit)
{
this.habit = habit;
}
public void setShadowAlpha(int shadowAlpha)
{
this.shadowAlpha = shadowAlpha;

@ -20,4 +20,4 @@
/**
* Provides views that are specific for the home-screen widgets.
*/
package org.isoron.uhabits.widgets.views;
package org.isoron.uhabits.ui.widgets.views;

@ -19,13 +19,10 @@
package org.isoron.uhabits.utils;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.content.*;
import android.preference.*;
import org.isoron.uhabits.BuildConfig;
import org.isoron.uhabits.HabitsApplication;
import org.isoron.uhabits.R;
import org.isoron.uhabits.*;
public class Preferences
{
@ -103,9 +100,9 @@ public class Preferences
public void setShouldReverseCheckmarks(boolean shouldReverse)
{
prefs
.edit()
.putBoolean("pref_checkmark_reverse_order", shouldReverse)
.apply();
.edit()
.putBoolean("pref_checkmark_reverse_order", shouldReverse)
.apply();
}
public boolean shouldReverseCheckmarks()
@ -127,11 +124,9 @@ public class Preferences
public void updateLastHint(int number, long timestamp)
{
prefs
.edit()
.putInt("last_hint_number", number)
.putLong("last_hint_timestamp", timestamp)
.apply();
.edit()
.putInt("last_hint_number", number)
.putLong("last_hint_timestamp", timestamp)
.apply();
}
}

@ -0,0 +1,65 @@
/*
* 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.utils;
import android.content.*;
import android.preference.*;
import org.isoron.uhabits.*;
public class WidgetPreferences
{
private Context context;
private SharedPreferences prefs;
public WidgetPreferences()
{
this.context = HabitsApplication.getContext();
prefs = PreferenceManager.getDefaultSharedPreferences(context);
}
public void addWidget(int widgetId, long habitId)
{
prefs
.edit()
.putLong(getHabitIdKey(widgetId), habitId)
.commit();
}
public long getHabitIdFromWidgetId(int widgetId)
{
Long habitId = prefs.getLong(getHabitIdKey(widgetId), -1);
if (habitId < 0) throw new RuntimeException("widget not found");
return habitId;
}
public void removeWidget(int id)
{
String habitIdKey = getHabitIdKey(id);
prefs.edit().remove(habitIdKey).apply();
}
private String getHabitIdKey(int id)
{
return String.format("widget-%06d-habit", id);
}
}

@ -0,0 +1,70 @@
/*
* 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.utils;
import android.appwidget.*;
import android.content.*;
import android.os.*;
import android.support.annotation.*;
import android.widget.*;
import org.isoron.uhabits.ui.widgets.*;
import static android.os.Build.VERSION.*;
import static android.os.Build.VERSION_CODES.*;
public abstract class WidgetUtils
{
@NonNull
public static WidgetDimensions getDimensionsFromOptions(
@NonNull Context context, @NonNull Bundle options)
{
if (SDK_INT < JELLY_BEAN)
throw new AssertionError("method requires jelly-bean");
int maxWidth = (int) InterfaceUtils.dpToPixels(context,
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH));
int maxHeight = (int) InterfaceUtils.dpToPixels(context,
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT));
int minWidth = (int) InterfaceUtils.dpToPixels(context,
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH));
int minHeight = (int) InterfaceUtils.dpToPixels(context,
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT));
return new WidgetDimensions(minWidth, maxHeight, maxWidth, minHeight);
}
public static void updateAppWidget(@NonNull AppWidgetManager manager,
@NonNull BaseWidget widget)
{
if (SDK_INT < JELLY_BEAN)
{
RemoteViews portrait = widget.getPortraitRemoteViews();
manager.updateAppWidget(widget.getId(), portrait);
}
else
{
RemoteViews landscape = widget.getLandscapeRemoteViews();
RemoteViews portrait = widget.getPortraitRemoteViews();
RemoteViews views = new RemoteViews(landscape, portrait);
manager.updateAppWidget(widget.getId(), views);
}
}
}

@ -19,308 +19,131 @@
package org.isoron.uhabits.widgets;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.RemoteViews;
import android.widget.TextView;
import android.appwidget.*;
import android.content.*;
import android.os.*;
import android.support.annotation.*;
import android.widget.*;
import org.isoron.uhabits.HabitsApplication;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.models.HabitList;
import org.isoron.uhabits.tasks.BaseTask;
import org.isoron.uhabits.utils.InterfaceUtils;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.ui.widgets.*;
import org.isoron.uhabits.utils.*;
import java.io.FileOutputStream;
import java.io.IOException;
import javax.inject.*;
import javax.inject.Inject;
import static android.os.Build.VERSION.*;
import static android.os.Build.VERSION_CODES.*;
import static org.isoron.uhabits.utils.WidgetUtils.*;
public abstract class BaseWidgetProvider extends AppWidgetProvider
{
@Inject
HabitList habitList;
private class WidgetDimensions
{
public int portraitWidth, portraitHeight;
public int landscapeWidth, landscapeHeight;
}
protected abstract int getDefaultHeight();
protected abstract int getDefaultWidth();
protected abstract PendingIntent getOnClickPendingIntent(Context context, Habit habit);
protected abstract int getLayoutId();
protected abstract View buildCustomView(Context context, Habit habit);
public static String getHabitIdKey(long widgetId)
{
return String.format("widget-%06d-habit", widgetId);
}
@Override
public void onDeleted(Context context, int[] appWidgetIds)
{
Context appContext = context.getApplicationContext();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(appContext);
for(Integer id : appWidgetIds)
prefs.edit().remove(getHabitIdKey(id)).apply();
}
@Inject
WidgetPreferences widgetPrefs;
@Override
public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager,
int appWidgetId, Bundle newOptions)
public BaseWidgetProvider()
{
updateWidget(context, appWidgetManager, appWidgetId, newOptions);
HabitsApplication.getComponent().inject(this);
}
@Override
public void onUpdate(Context context, AppWidgetManager manager, int[] appWidgetIds)
public void onAppWidgetOptionsChanged(@Nullable Context context,
@Nullable AppWidgetManager manager,
int widgetId,
@Nullable Bundle options)
{
for(int id : appWidgetIds)
try
{
Bundle options = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN)
options = manager.getAppWidgetOptions(id);
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);
updateWidget(context, manager, id, options);
BaseWidget widget = getWidgetFromId(context, widgetId);
WidgetDimensions dims = getDimensionsFromOptions(context, options);
widget.setDimensions(dims);
updateAppWidget(manager, widget);
}
}
private void updateWidget(Context context, AppWidgetManager manager,
int widgetId, Bundle options)
{
HabitsApplication.getComponent().inject(this);
WidgetDimensions dim = getWidgetDimensions(context, options);
Context appContext = context.getApplicationContext();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(appContext);
Long habitId = prefs.getLong(getHabitIdKey(widgetId), -1L);
if(habitId < 0) return;
Habit habit = habitList.getById(habitId);
if(habit == null)
catch (RuntimeException e)
{
drawErrorWidget(context, manager, widgetId);
return;
e.printStackTrace();
}
new RenderWidgetTask(widgetId, context, habit, dim, manager).execute();
}
private void drawErrorWidget(Context context, AppWidgetManager manager, int widgetId)
{
RemoteViews errorView = new RemoteViews(context.getPackageName(), R.layout.widget_error);
manager.updateAppWidget(widgetId, errorView);
}
protected abstract void refreshCustomViewData(View widgetView);
private void savePreview(Context context, int widgetId, Bitmap widgetCache, int width,
int height, String label)
@Override
public void onDeleted(@Nullable Context context, @Nullable int[] ids)
{
try
{
LayoutInflater inflater = LayoutInflater.from(context);
View view = inflater.inflate(getLayoutId(), null);
TextView tvLabel = (TextView) view.findViewById(R.id.label);
if(tvLabel != null) tvLabel.setText(label);
ImageView iv = (ImageView) view.findViewById(R.id.imageView);
if(iv != null) iv.setImageBitmap(widgetCache);
view.measure(width, height);
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
view.setDrawingCacheEnabled(true);
view.buildDrawingCache();
Bitmap previewCache = view.getDrawingCache();
String filename = String.format("%s/%d_%d.png", context.getExternalCacheDir(), widgetId, width);
Log.d("BaseWidgetProvider", String.format("Writing %s", filename));
FileOutputStream out = new FileOutputStream(filename);
if (context == null) throw new RuntimeException("context is null");
if (ids == null) throw new RuntimeException("ids is null");
if(previewCache != null)
previewCache.compress(Bitmap.CompressFormat.PNG, 100, out);
out.close();
}
catch (IOException e)
for (int id : ids)
{
e.printStackTrace();
BaseWidget widget = getWidgetFromId(context, id);
widget.delete();
}
}
private WidgetDimensions getWidgetDimensions(Context context, Bundle options)
@Override
public void onUpdate(@Nullable Context context,
@Nullable AppWidgetManager manager,
@Nullable int[] widgetIds)
{
int maxWidth = getDefaultWidth();
int minWidth = getDefaultWidth();
int maxHeight = getDefaultHeight();
int minHeight = getDefaultHeight();
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);
if (options != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
{
maxWidth = (int) InterfaceUtils.dpToPixels(context,
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH));
maxHeight = (int) InterfaceUtils.dpToPixels(context,
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT));
minWidth = (int) InterfaceUtils.dpToPixels(context,
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH));
minHeight = (int) InterfaceUtils.dpToPixels(context,
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT));
}
WidgetDimensions ws = new WidgetDimensions();
ws.portraitWidth = minWidth;
ws.portraitHeight = maxHeight;
ws.landscapeWidth = maxWidth;
ws.landscapeHeight = minHeight;
return ws;
for (int id : widgetIds)
update(context, manager, id);
}
private void measureCustomView(Context context, int w, int h, View customView)
@NonNull
protected Habit getHabitFromWidgetId(int widgetId)
{
LayoutInflater inflater = LayoutInflater.from(context);
View entireView = inflater.inflate(getLayoutId(), null);
int specWidth = View.MeasureSpec.makeMeasureSpec(w, View.MeasureSpec.EXACTLY);
int specHeight = View.MeasureSpec.makeMeasureSpec(h, View.MeasureSpec.EXACTLY);
entireView.measure(specWidth, specHeight);
entireView.layout(0, 0, entireView.getMeasuredWidth(), entireView.getMeasuredHeight());
long habitId = widgetPrefs.getHabitIdFromWidgetId(widgetId);
Habit habit = habitList.getById(habitId);
if (habit == null) throw new RuntimeException("habit not found");
return habit;
}
View imageView = entireView.findViewById(R.id.imageView);
w = imageView.getMeasuredWidth();
h = imageView.getMeasuredHeight();
@NonNull
protected abstract BaseWidget getWidgetFromId(@NonNull Context context,
int id);
specWidth = View.MeasureSpec.makeMeasureSpec(w, View.MeasureSpec.EXACTLY);
specHeight = View.MeasureSpec.makeMeasureSpec(h, View.MeasureSpec.EXACTLY);
customView.measure(specWidth, specHeight);
customView.layout(0, 0, customView.getMeasuredWidth(), customView.getMeasuredHeight());
private void drawErrorWidget(Context context,
AppWidgetManager manager,
int widgetId)
{
RemoteViews errorView =
new RemoteViews(context.getPackageName(), R.layout.widget_error);
manager.updateAppWidget(widgetId, errorView);
}
private class RenderWidgetTask extends BaseTask
private void update(@NonNull Context context,
@NonNull AppWidgetManager manager,
int widgetId)
{
private final int widgetId;
private final Context context;
private final Habit habit;
private final AppWidgetManager manager;
private RemoteViews portraitRemoteViews, landscapeRemoteViews;
private View portraitWidgetView, landscapeWidgetView;
private WidgetDimensions dim;
public RenderWidgetTask(int widgetId, Context context, Habit habit, WidgetDimensions ws,
AppWidgetManager manager)
{
this.widgetId = widgetId;
this.context = context;
this.habit = habit;
this.manager = manager;
this.dim = ws;
}
@Override
protected void onPreExecute()
{
super.onPreExecute();
context.setTheme(R.style.TransparentWidgetTheme);
portraitRemoteViews = new RemoteViews(context.getPackageName(), getLayoutId());
portraitWidgetView = buildCustomView(context, habit);
measureCustomView(context, dim.portraitWidth, dim.portraitHeight, portraitWidgetView);
landscapeRemoteViews = new RemoteViews(context.getPackageName(), getLayoutId());
landscapeWidgetView = buildCustomView(context, habit);
measureCustomView(context, dim.landscapeWidth, dim.landscapeHeight,
landscapeWidgetView);
}
private void updateAppWidget()
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
manager.updateAppWidget(widgetId, new RemoteViews(landscapeRemoteViews,
portraitRemoteViews));
else
manager.updateAppWidget(widgetId, portraitRemoteViews);
}
@Override
protected void doInBackground()
try
{
refreshCustomViewData(portraitWidgetView);
refreshCustomViewData(landscapeWidgetView);
}
BaseWidget widget = getWidgetFromId(context, widgetId);
@Override
protected void onPostExecute(Void aVoid)
{
try
{
buildRemoteViews(portraitWidgetView, portraitRemoteViews,
dim.portraitWidth, dim.portraitHeight);
buildRemoteViews(landscapeWidgetView, landscapeRemoteViews,
dim.landscapeWidth, dim.landscapeHeight);
updateAppWidget();
}
catch (Exception e)
if (SDK_INT > JELLY_BEAN)
{
drawErrorWidget(context, manager, widgetId);
e.printStackTrace();
Bundle options = manager.getAppWidgetOptions(widgetId);
widget.setDimensions(
getDimensionsFromOptions(context, options));
}
super.onPostExecute(aVoid);
updateAppWidget(manager, widget);
}
private void buildRemoteViews(View widgetView, RemoteViews remoteViews, int width,
int height)
catch (RuntimeException e)
{
widgetView.invalidate();
widgetView.setDrawingCacheEnabled(true);
widgetView.buildDrawingCache(true);
Bitmap drawingCache = widgetView.getDrawingCache();
remoteViews.setTextViewText(R.id.label, habit.getName());
remoteViews.setImageViewBitmap(R.id.imageView, drawingCache);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
{
int imageWidth = widgetView.getMeasuredWidth();
int imageHeight = widgetView.getMeasuredHeight();
int p[] = getPadding(width, height, imageWidth, imageHeight);
remoteViews.setViewPadding(R.id.buttonOverlay, p[0], p[1], p[2], p[3]);
}
//savePreview(context, widgetId, drawingCache, width, height, habit.name);
PendingIntent onClickIntent = getOnClickPendingIntent(context, habit);
if (onClickIntent != null) remoteViews.setOnClickPendingIntent(R.id.button,
onClickIntent);
drawErrorWidget(context, manager, widgetId);
e.printStackTrace();
}
}
private int[] getPadding(int entireWidth, int entireHeight, int imageWidth,
int imageHeight)
{
int w = (int) (((float) entireWidth - imageWidth) / 2);
int h = (int) (((float) entireHeight - imageHeight) / 2);
return new int[]{ w, h, w, h };
}
}

@ -18,55 +18,19 @@
*/
package org.isoron.uhabits.widgets;
import android.app.*;
import android.content.*;
import android.view.*;
import android.support.annotation.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.ui.common.views.*;
import org.isoron.uhabits.widgets.views.*;
import org.isoron.uhabits.ui.widgets.*;
public class CheckmarkWidgetProvider extends BaseWidgetProvider
{
@NonNull
@Override
protected View buildCustomView(Context context, Habit habit)
protected CheckmarkWidget getWidgetFromId(@NonNull Context context, int id)
{
CheckmarkWidgetView view = new CheckmarkWidgetView(context);
view.setHabit(habit);
return view;
Habit habit = getHabitFromWidgetId(id);
return new CheckmarkWidget(context, id, habit);
}
@Override
protected int getDefaultHeight()
{
return 125;
}
@Override
protected int getDefaultWidth()
{
return 125;
}
@Override
protected int getLayoutId()
{
return R.layout.widget_wrapper;
}
@Override
protected PendingIntent getOnClickPendingIntent(Context context,
Habit habit)
{
return HabitBroadcastReceiver.buildCheckIntent(context, habit, null);
}
@Override
protected void refreshCustomViewData(View view)
{
((HabitChart) view).refreshData();
}
}

@ -19,54 +19,73 @@
package org.isoron.uhabits.widgets;
import android.app.*;
import android.content.*;
import android.view.*;
import android.support.annotation.*;
import org.apache.commons.lang3.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.ui.common.views.*;
import org.isoron.uhabits.ui.widgets.*;
public class FrequencyWidgetProvider extends BaseWidgetProvider
{
@NonNull
@Override
protected View buildCustomView(Context context, Habit habit)
protected BaseWidget getWidgetFromId(@NonNull Context context, int id)
{
FrequencyChart dataView = new FrequencyChart(context);
throw new NotImplementedException("");
// GraphWidgetView view = new GraphWidgetView(context, dataView);
// view.setHabit(habit);
// return view;
}
@Override
protected void refreshCustomViewData(View view)
{
((HabitChart) view).refreshData();
}
@Override
protected PendingIntent getOnClickPendingIntent(Context context, Habit habit)
{
return HabitBroadcastReceiver.buildViewHabitIntent(context, habit);
}
@Override
protected int getDefaultHeight()
{
return 200;
}
@Override
protected int getDefaultWidth()
{
return 200;
}
@Override
protected int getLayoutId()
{
return R.layout.widget_wrapper;
}
// @NonNull
// @Override
// protected BaseWidget getWidgetFromId(int id)
// {
// throw new NotImplementedException("");
// }
//
// @Override
// protected View buildCustomView(Context context, Habit habit)
// {
// FrequencyChart chart = new FrequencyChart(context);
// GraphWidgetView view = new GraphWidgetView(context, chart);
// view.setTitle(habit.getName());
// return view;
// }
//
// @Override
// protected int getDefaultHeight()
// {
// return 200;
// }
//
// @Override
// protected int getDefaultWidth()
// {
// return 200;
// }
//
// @Override
// protected int getLayoutId()
// {
// return R.layout.widget_wrapper;
// }
//
// @Override
// protected PendingIntent getOnClickPendingIntent(Context context,
// Habit habit)
// {
// return HabitBroadcastReceiver.buildViewHabitIntent(context, habit);
// }
//
// @Override
// protected void refreshCustomViewData(Context context,
// View view,
// Habit habit)
// {
// GraphWidgetView widgetView = (GraphWidgetView) view;
// FrequencyChart chart = (FrequencyChart) widgetView.getDataView();
//
// int color = ColorUtils.getColor(context, habit.getColor());
//
// chart.setColor(color);
// chart.setFrequency(habit.getRepetitions().getWeekdayFrequency());
// }
}

@ -18,54 +18,74 @@
*/
package org.isoron.uhabits.widgets;
import android.app.*;
import android.content.*;
import android.view.*;
import android.support.annotation.*;
import org.apache.commons.lang3.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.ui.common.views.*;
import org.isoron.uhabits.ui.widgets.*;
public class HistoryWidgetProvider extends BaseWidgetProvider
public class HistoryWidgetProvider extends BaseWidgetProvider
{
@NonNull
@Override
protected View buildCustomView(Context context, Habit habit)
protected BaseWidget getWidgetFromId(@NonNull Context context, int id)
{
throw new NotImplementedException("");
// HistoryChart dataView = new HistoryChart(context);
// GraphWidgetView view = new GraphWidgetView(context, dataView);
// view.setHabit(habit);
// return view;
}
@Override
protected void refreshCustomViewData(View view)
{
((HabitChart) view).refreshData();
}
@Override
protected PendingIntent getOnClickPendingIntent(Context context, Habit habit)
{
return HabitBroadcastReceiver.buildViewHabitIntent(context, habit);
}
@Override
protected int getDefaultHeight()
{
return 250;
}
@Override
protected int getDefaultWidth()
{
return 250;
}
@Override
protected int getLayoutId()
{
return R.layout.widget_wrapper;
}
// @NonNull
// @Override
// protected BaseWidget getWidgetFromId(int id)
// {
// throw new NotImplementedException("");
// }
//
// @Override
// protected View buildCustomView(Context context, Habit habit)
// {
// HistoryChart dataView = new HistoryChart(context);
// GraphWidgetView widgetView = new GraphWidgetView(context, dataView);
// widgetView.setTitle(habit.getName());
// return widgetView;
// }
//
// @Override
// protected int getDefaultHeight()
// {
// return 250;
// }
//
// @Override
// protected int getDefaultWidth()
// {
// return 250;
// }
//
// @Override
// protected int getLayoutId()
// {
// return R.layout.widget_wrapper;
// }
//
// @Override
// protected PendingIntent getOnClickPendingIntent(Context context,
// Habit habit)
// {
// return HabitBroadcastReceiver.buildViewHabitIntent(context, habit);
// }
//
// @Override
// protected void refreshCustomViewData(Context context,
// View view,
// Habit habit)
// {
// GraphWidgetView widgetView = (GraphWidgetView) view;
// HistoryChart chart = (HistoryChart) widgetView.getDataView();
//
// int color = ColorUtils.getColor(context, habit.getColor());
// int[] values = habit.getCheckmarks().getAllValues();
//
// chart.setColor(color);
// chart.setCheckmarks(values);
// }
}

@ -18,63 +18,73 @@
*/
package org.isoron.uhabits.widgets;
import android.app.*;
import android.content.*;
import android.view.*;
import android.support.annotation.*;
import org.apache.commons.lang3.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.ui.common.views.*;
import org.isoron.uhabits.ui.habits.show.views.*;
import org.isoron.uhabits.utils.*;
import org.isoron.uhabits.ui.widgets.*;
public class ScoreWidgetProvider extends BaseWidgetProvider
{
@NonNull
@Override
protected View buildCustomView(Context context, Habit habit)
protected BaseWidget getWidgetFromId(@NonNull Context context, int id)
{
int defaultScoreInterval = InterfaceUtils.getDefaultScoreSpinnerPosition(context);
int size = ScoreCard.BUCKET_SIZES[defaultScoreInterval];
ScoreChart dataView = new ScoreChart(context);
dataView.setIsTransparencyEnabled(true);
dataView.setBucketSize(size);
// GraphWidgetView view = new GraphWidgetView(context, dataView);
// view.setHabit(habit);
// return view;
throw new NotImplementedException("");
}
@Override
protected void refreshCustomViewData(View view)
{
((HabitChart) view).refreshData();
}
@Override
protected PendingIntent getOnClickPendingIntent(Context context, Habit habit)
{
return HabitBroadcastReceiver.buildViewHabitIntent(context, habit);
}
@Override
protected int getDefaultHeight()
{
return 300;
}
@Override
protected int getDefaultWidth()
{
return 300;
}
@Override
protected int getLayoutId()
{
return R.layout.widget_wrapper;
}
// @Override
// protected View buildCustomView(Context context, Habit habit)
// {
// ScoreChart dataView = new ScoreChart(context);
// GraphWidgetView view = new GraphWidgetView(context, dataView);
// view.setTitle(habit.getName());
// return view;
// }
//
// @Override
// protected int getDefaultHeight()
// {
// return 300;
// }
//
// @Override
// protected int getDefaultWidth()
// {
// return 300;
// }
//
// @Override
// protected int getLayoutId()
// {
// return R.layout.widget_wrapper;
// }
//
// @Override
// protected PendingIntent getOnClickPendingIntent(Context context,
// Habit habit)
// {
// return HabitBroadcastReceiver.buildViewHabitIntent(context, habit);
// }
//
// @Override
// protected void refreshCustomViewData(Context context,
// View view,
// Habit habit)
// {
// int defaultScoreInterval =
// InterfaceUtils.getDefaultScoreSpinnerPosition(context);
// int size = ScoreCard.BUCKET_SIZES[defaultScoreInterval];
//
// GraphWidgetView widgetView = (GraphWidgetView) view;
// ScoreChart chart = (ScoreChart) widgetView.getDataView();
//
// int color = ColorUtils.getColor(context, habit.getColor());
// List<Score> scores = habit.getScores().getAll();
//
// chart.setIsTransparencyEnabled(true);
// chart.setBucketSize(size);
// chart.setColor(color);
// chart.setScores(scores);
// }
}

@ -18,54 +18,69 @@
*/
package org.isoron.uhabits.widgets;
import android.app.*;
import android.content.*;
import android.view.*;
import android.support.annotation.*;
import org.apache.commons.lang3.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.ui.common.views.*;
import org.isoron.uhabits.ui.widgets.*;
public class StreakWidgetProvider extends BaseWidgetProvider
public class StreakWidgetProvider extends BaseWidgetProvider
{
@NonNull
@Override
protected View buildCustomView(Context context, Habit habit)
protected BaseWidget getWidgetFromId(@NonNull Context context, int id)
{
StreakChart dataView = new StreakChart(context);
throw new NotImplementedException("");
// GraphWidgetView view = new GraphWidgetView(context, dataView);
// view.setHabit(habit);
// return view;
}
@Override
protected void refreshCustomViewData(View view)
{
((HabitChart) view).refreshData();
}
@Override
protected PendingIntent getOnClickPendingIntent(Context context, Habit habit)
{
return HabitBroadcastReceiver.buildViewHabitIntent(context, habit);
}
@Override
protected int getDefaultHeight()
{
return 200;
}
@Override
protected int getDefaultWidth()
{
return 200;
}
@Override
protected int getLayoutId()
{
return R.layout.widget_wrapper;
}
// @Override
// protected View buildCustomView(Context context, Habit habit)
// {
// StreakChart dataView = new StreakChart(context);
// GraphWidgetView view = new GraphWidgetView(context, dataView);
// view.setTitle(habit.getName());
// return view;
// }
//
// @Override
// protected int getDefaultHeight()
// {
// return 200;
// }
//
// @Override
// protected int getDefaultWidth()
// {
// return 200;
// }
//
// @Override
// protected int getLayoutId()
// {
// return R.layout.widget_wrapper;
// }
//
// @Override
// protected PendingIntent getOnClickPendingIntent(Context context,
// Habit habit)
// {
// return HabitBroadcastReceiver.buildViewHabitIntent(context, habit);
// }
//
// @Override
// protected void refreshCustomViewData(Context context,
// View view,
// Habit habit)
// {
// GraphWidgetView widgetView = (GraphWidgetView) view;
// StreakChart chart = (StreakChart) widgetView.getDataView();
//
// int color = ColorUtils.getColor(context, habit.getColor());
//
// // TODO: make this dynamic
// List<Streak> streaks = habit.getStreaks().getBest(10);
//
// chart.setColor(color);
// chart.setStreaks(streaks);
// }
}

@ -1,47 +0,0 @@
/*
* 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.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
public class WidgetManager
{
public static void updateWidgets(Context context)
{
updateWidgets(context, CheckmarkWidgetProvider.class);
updateWidgets(context, HistoryWidgetProvider.class);
updateWidgets(context, ScoreWidgetProvider.class);
updateWidgets(context, StreakWidgetProvider.class);
updateWidgets(context, FrequencyWidgetProvider.class);
}
private static void updateWidgets(Context context, Class providerClass)
{
ComponentName provider = new ComponentName(context, providerClass);
Intent intent = new Intent(context, providerClass);
intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
int ids[] = AppWidgetManager.getInstance(context).getAppWidgetIds(provider);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids);
context.sendBroadcast(intent);
}
}

@ -25,7 +25,7 @@
android:previewImage="@drawable/widget_preview_checkmark"
android:resizeMode="none"
android:updatePeriodMillis="3600000"
android:configure="org.isoron.uhabits.widgets.HabitPickerDialog"
android:configure="org.isoron.uhabits.ui.widgets.HabitPickerDialog"
android:widgetCategory="home_screen">

@ -27,7 +27,7 @@
android:previewImage="@drawable/widget_preview_frequency"
android:resizeMode="vertical|horizontal"
android:updatePeriodMillis="3600000"
android:configure="org.isoron.uhabits.widgets.HabitPickerDialog"
android:configure="org.isoron.uhabits.ui.widgets.HabitPickerDialog"
android:widgetCategory="home_screen">
</appwidget-provider>

@ -27,7 +27,7 @@
android:previewImage="@drawable/widget_preview_history"
android:resizeMode="vertical|horizontal"
android:updatePeriodMillis="3600000"
android:configure="org.isoron.uhabits.widgets.HabitPickerDialog"
android:configure="org.isoron.uhabits.ui.widgets.HabitPickerDialog"
android:widgetCategory="home_screen">
</appwidget-provider>

@ -27,7 +27,7 @@
android:previewImage="@drawable/widget_preview_score"
android:resizeMode="vertical|horizontal"
android:updatePeriodMillis="3600000"
android:configure="org.isoron.uhabits.widgets.HabitPickerDialog"
android:configure="org.isoron.uhabits.ui.widgets.HabitPickerDialog"
android:widgetCategory="home_screen">
</appwidget-provider>

@ -27,7 +27,7 @@
android:previewImage="@drawable/widget_preview_streaks"
android:resizeMode="vertical|horizontal"
android:updatePeriodMillis="3600000"
android:configure="org.isoron.uhabits.widgets.HabitPickerDialog"
android:configure="org.isoron.uhabits.ui.widgets.HabitPickerDialog"
android:widgetCategory="home_screen">
</appwidget-provider>

@ -35,16 +35,15 @@ public class TestModule
{
@Singleton
@Provides
Preferences providePreferences()
CommandRunner provideCommandRunner()
{
return mock(Preferences.class);
return mock(CommandRunner.class);
}
@Singleton
@Provides
CommandRunner provideCommandRunner()
Habit provideHabit()
{
return mock(CommandRunner.class);
return mock(Habit.class);
}
@Singleton
@ -55,15 +54,23 @@ public class TestModule
}
@Provides
Habit provideHabit()
@Singleton
ModelFactory provideModelFactory()
{
return mock(Habit.class);
return new MemoryModelFactory();
}
@Singleton
@Provides
Preferences providePreferences()
{
return mock(Preferences.class);
}
@Provides
@Singleton
ModelFactory provideModelFactory()
WidgetPreferences provideWidgetPreferences()
{
return new MemoryModelFactory();
return mock(WidgetPreferences.class);
}
}

Loading…
Cancel
Save