Merge branch 'release/1.5.0'

pull/114/merge v1.5.0
Alinson S. Xavier 10 years ago
commit 097a5be864

@ -1,5 +1,19 @@
# Changelog
### 1.5.0 (May 15, 2016)
* Add night mode, with AMOLED support
* Backport material design to older devices
* Display more information on statistics screen
* Display score on main screen and checkmark widget
* Make widgets react immediately to touch
* Reschedule reminders after reboot
* Pick first day of the week according to country
* Add option to reverse order of days on main screen
* Add option to change notification sounds
* Add Catalan, Indonesian, Turkish, Ukrainian translations
* Switch between Simplified/Traditional Chinese according to country
### 1.4.1 (April 9, 2016)
* Show error message on widgets, instead of crashing

@ -54,6 +54,7 @@ source.
[![Habit strength][screen3th]][screen3]
[![Habit history and streaks][screen4th]][screen4]
[![Widgets][screen5th]][screen5]
[![Night mode][screen6th]][screen6]
## Installing
@ -71,7 +72,8 @@ contribute, even if you are not a software developer.
* **Report bugs, suggest features.** The easiest way to contribute is to simply
use the app and let us know if you find any problems or have any suggestions
to improve it. You can either use the link inside the app, or open an issue
at GitHub.
at GitHub. If you would like to receive the newest versions of the app
earlier than everyone else, [join our open beta on Google Play][beta].
* **Spread the word.** If you like the app, share it with your family, friends
and colleagues. You can also rate and review the app on Google Play Store, to help
@ -105,14 +107,17 @@ contribute, even if you are not a software developer.
[screen3]: screenshots/original/uhabits3.png
[screen4]: screenshots/original/uhabits4.png
[screen5]: screenshots/original/uhabits5.png
[screen6]: screenshots/original/uhabits6.png
[screen1th]: screenshots/thumbs/uhabits1.png
[screen2th]: screenshots/thumbs/uhabits2.png
[screen3th]: screenshots/thumbs/uhabits3.png
[screen4th]: screenshots/thumbs/uhabits4.png
[screen5th]: screenshots/thumbs/uhabits5.png
[screen6th]: screenshots/thumbs/uhabits6.png
[poedit]: https://poeditor.com/join/project/8DWX5pfjS0
[playstore]: https://play.google.com/store/apps/details?id=org.isoron.uhabits
[releases]: https://github.com/iSoron/uhabits/releases
[fdroid]: http://f-droid.org/app/org.isoron.uhabits
[dev-guide]: https://github.com/iSoron/uhabits/wiki/Developer-guidelines
[build]: https://github.com/iSoron/uhabits/wiki/Developer-guidelines#building
[beta]: https://play.google.com/apps/testing/org.isoron.uhabits

@ -2,14 +2,14 @@ apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
buildToolsVersion "23.0.3"
defaultConfig {
applicationId "org.isoron.uhabits"
minSdkVersion 15
targetSdkVersion 23
buildConfigField "Integer", "databaseVersion", "13"
buildConfigField "Integer", "databaseVersion", "14"
buildConfigField "String", "databaseFilename", "\"uhabits.db\""
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
@ -32,18 +32,31 @@ android {
}
dependencies {
compile 'com.android.support:support-v4:23.1.1'
compile 'com.android.support:support-v4:23.3.0'
compile 'com.android.support:appcompat-v7:23.3.0'
compile 'com.android.support:design:23.3.0'
compile 'com.android.support:preference-v14:23.3.0'
compile 'com.github.paolorotolo:appintro:3.4.0'
compile 'org.apmem.tools:layouts:1.10@aar'
compile 'com.opencsv:opencsv:3.7'
compile project(':libs:drag-sort-listview:library')
compile files('libs/ActiveAndroid.jar')
androidTestCompile 'com.android.support:support-annotations:23.1.1'
androidTestCompile 'com.android.support.test:runner:0.4.1'
androidTestCompile 'com.android.support.test:rules:0.4.1'
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.1'
androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.1'
androidTestCompile 'com.android.support:support-annotations:23.3.0'
androidTestCompile 'com.android.support.test:runner:0.5'
androidTestCompile 'com.android.support.test:rules:0.5'
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.1') {
exclude group: 'com.android.support'
}
androidTestCompile('com.android.support.test.espresso:espresso-intents:2.2.1') {
exclude group: 'com.android.support'
}
androidTestCompile('com.android.support.test.espresso:espresso-contrib:2.2.1') {
exclude group: 'com.android.support'
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 8.5 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: 5.5 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

@ -25,6 +25,7 @@ import android.os.Looper;
import android.support.test.InstrumentationRegistry;
import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.helpers.UIHelper;
import org.isoron.uhabits.tasks.BaseTask;
import org.junit.Before;
@ -50,6 +51,7 @@ public class BaseTest
targetContext = InstrumentationRegistry.getTargetContext();
testContext = InstrumentationRegistry.getContext();
UIHelper.setFixedTheme(R.style.AppBaseTheme);
DateHelper.setFixedLocalTime(FIXED_LOCAL_TIME);
}

@ -20,6 +20,7 @@
package org.isoron.uhabits.ui;
import android.support.test.espresso.NoMatchingViewException;
import android.support.test.espresso.contrib.RecyclerViewActions;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
@ -31,7 +32,6 @@ import java.util.Random;
import static android.support.test.espresso.Espresso.onData;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.Espresso.openContextualActionModeOverflowMenu;
import static android.support.test.espresso.Espresso.pressBack;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.longClick;
@ -39,12 +39,16 @@ import static android.support.test.espresso.action.ViewActions.replaceText;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.RootMatchers.isPlatformPopup;
import static android.support.test.espresso.matcher.ViewMatchers.Visibility.VISIBLE;
import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withClassName;
import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription;
import static android.support.test.espresso.matcher.ViewMatchers.withEffectiveVisibility;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withParent;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
@ -168,13 +172,13 @@ public class MainActivityActions
public static void deleteHabits(List<String> names)
{
selectHabits(names);
clickActionModeMenuItem(R.string.delete);
clickMenuItem(R.string.delete);
onView(withText("OK"))
.perform(click());
assertHabitsDontExist(names);
}
public static void clickActionModeMenuItem(int stringId)
public static void clickMenuItem(int stringId)
{
try
{
@ -188,9 +192,34 @@ public class MainActivityActions
}
catch(Exception e2)
{
openContextualActionModeOverflowMenu();
onView(withText(stringId)).perform(click());
clickHiddenMenuItem(stringId);
}
}
}
private static void clickHiddenMenuItem(int stringId)
{
try
{
// Try the ActionMode overflow menu first
onView(allOf(withContentDescription("More options"), withParent(withParent(
withClassName(containsString("Action")))))).perform(click());
}
catch (Exception e1)
{
// Try the toolbar overflow menu
onView(allOf(withContentDescription("More options"), withParent(withParent(
withClassName(containsString("Toolbar")))))).perform(click());
}
onView(withText(stringId)).perform(click());
}
public static void clickSettingsItem(String text)
{
onView(withClassName(containsString("RecyclerView")))
.perform(RecyclerViewActions.actionOnItem(
hasDescendant(withText(containsString(text))),
click()));
}
}

@ -45,7 +45,6 @@ import java.util.Random;
import static android.support.test.espresso.Espresso.onData;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu;
import static android.support.test.espresso.Espresso.pressBack;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.longClick;
@ -61,7 +60,6 @@ import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.isRoot;
import static android.support.test.espresso.matcher.ViewMatchers.withClassName;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.endsWith;
@ -69,7 +67,6 @@ import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith;
import static org.isoron.uhabits.ui.HabitMatchers.isPreferenceWithText;
import static org.isoron.uhabits.ui.HabitMatchers.withName;
import static org.isoron.uhabits.ui.HabitViewActions.clickAtRandomLocations;
import static org.isoron.uhabits.ui.HabitViewActions.toggleAllCheckmarks;
@ -77,7 +74,8 @@ import static org.isoron.uhabits.ui.MainActivityActions.addHabit;
import static org.isoron.uhabits.ui.MainActivityActions.assertHabitExists;
import static org.isoron.uhabits.ui.MainActivityActions.assertHabitsDontExist;
import static org.isoron.uhabits.ui.MainActivityActions.assertHabitsExist;
import static org.isoron.uhabits.ui.MainActivityActions.clickActionModeMenuItem;
import static org.isoron.uhabits.ui.MainActivityActions.clickMenuItem;
import static org.isoron.uhabits.ui.MainActivityActions.clickSettingsItem;
import static org.isoron.uhabits.ui.MainActivityActions.deleteHabit;
import static org.isoron.uhabits.ui.MainActivityActions.deleteHabits;
import static org.isoron.uhabits.ui.MainActivityActions.selectHabit;
@ -152,20 +150,16 @@ public class MainTest
selectHabits(names);
clickActionModeMenuItem(R.string.archive);
clickMenuItem(R.string.archive);
assertHabitsDontExist(names);
openActionBarOverflowOrOptionsMenu(targetContext);
onView(withText(R.string.show_archived))
.perform(click());
clickMenuItem(R.string.show_archived);
assertHabitsExist(names);
selectHabits(names);
clickActionModeMenuItem(R.string.unarchive);
clickMenuItem(R.string.unarchive);
openActionBarOverflowOrOptionsMenu(targetContext);
onView(withText(R.string.show_archived))
.perform(click());
clickMenuItem(R.string.show_archived);
assertHabitsExist(names);
deleteHabits(names);
@ -227,7 +221,7 @@ public class MainTest
.onChildView(withId(R.id.label))
.perform(longClick());
clickActionModeMenuItem(R.string.edit);
clickMenuItem(R.string.edit);
String modifiedName = "Modified " + new Random().nextInt(10000);
typeHabitData(modifiedName, "", "1", "1");
@ -238,7 +232,7 @@ public class MainTest
assertHabitExists(modifiedName);
selectHabit(modifiedName);
clickActionModeMenuItem(R.string.color_picker_default_title);
clickMenuItem(R.string.color_picker_default_title);
pressBack();
deleteHabit(modifiedName);
@ -272,8 +266,7 @@ public class MainTest
@Test
public void testSettings()
{
openActionBarOverflowOrOptionsMenu(targetContext);
onView(withText(R.string.settings)).perform(click());
clickMenuItem(R.string.settings);
}
/**
@ -282,8 +275,7 @@ public class MainTest
@Test
public void testAbout()
{
openActionBarOverflowOrOptionsMenu(targetContext);
onView(withText(R.string.about)).perform(click());
clickMenuItem(R.string.about);
onView(isRoot()).perform(swipeUp());
}
@ -293,8 +285,7 @@ public class MainTest
@Test
public void testHelp()
{
openActionBarOverflowOrOptionsMenu(targetContext);
onView(withText(R.string.help)).perform(click());
clickMenuItem(R.string.help);
intended(hasAction(Intent.ACTION_VIEW));
}
@ -307,20 +298,18 @@ public class MainTest
{
String name = addHabit();
openActionBarOverflowOrOptionsMenu(targetContext);
onView(withText(R.string.settings)).perform(click());
clickMenuItem(R.string.settings);
String date = DateHelper.getBackupDateFormat().format(DateHelper.getLocalTime());
date = date.substring(0, date.length() - 2);
onData(isPreferenceWithText("Export full backup")).perform(click());
clickSettingsItem("Export full backup");
intended(hasAction(Intent.ACTION_SEND));
deleteHabit(name);
openActionBarOverflowOrOptionsMenu(targetContext);
onView(withText(R.string.settings)).perform(click());
onData(isPreferenceWithText("Import data")).perform(click());
clickMenuItem(R.string.settings);
clickSettingsItem("Import data");
onData(allOf(is(instanceOf(String.class)), startsWith("Backups")))
.perform(click());
@ -339,9 +328,8 @@ public class MainTest
public void testExportCSV()
{
addHabit();
openActionBarOverflowOrOptionsMenu(targetContext);
onView(withText(R.string.settings)).perform(click());
onData(isPreferenceWithText("Export as CSV")).perform(click());
clickMenuItem(R.string.settings);
clickSettingsItem("Export as CSV");
intended(hasAction(Intent.ACTION_SEND));
}
@ -351,9 +339,8 @@ public class MainTest
@Test
public void testGenerateBugReport()
{
openActionBarOverflowOrOptionsMenu(targetContext);
onView(withText(R.string.settings)).perform(click());
onData(isPreferenceWithText("Generate bug report")).perform(click());
clickMenuItem(R.string.settings);
clickSettingsItem("Generate bug report");
intended(hasAction(Intent.ACTION_SENDTO));
}
}

@ -23,7 +23,6 @@ import android.content.Context;
import android.support.annotation.Nullable;
import android.util.Log;
import org.isoron.uhabits.helpers.ColorHelper;
import org.isoron.uhabits.helpers.DatabaseHelper;
import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.models.Habit;
@ -66,7 +65,7 @@ public class HabitFixtures
Habit habit = new Habit();
habit.name = "Meditate";
habit.description = "Did you meditate this morning?";
habit.color = ColorHelper.palette[3];
habit.color = 3;
habit.freqNum = 1;
habit.freqDen = 1;
habit.save();
@ -78,7 +77,7 @@ public class HabitFixtures
Habit habit = createEmptyHabit();
habit.freqNum = 3;
habit.freqDen = 7;
habit.color = ColorHelper.palette[4];
habit.color = 4;
habit.save();
long day = DateHelper.millisecondsInOneDay;

@ -24,7 +24,6 @@ import android.test.suitebuilder.annotation.SmallTest;
import org.isoron.uhabits.BaseTest;
import org.isoron.uhabits.commands.ChangeHabitColorCommand;
import org.isoron.uhabits.helpers.ColorHelper;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.unit.HabitFixtures;
import org.junit.Before;
@ -53,12 +52,12 @@ public class ChangeHabitColorCommandTest extends BaseTest
for(int i = 0; i < 3; i ++)
{
Habit habit = HabitFixtures.createShortHabit();
habit.color = ColorHelper.palette[i+1];
habit.color = i+1;
habit.save();
habits.add(habit);
}
command = new ChangeHabitColorCommand(habits, ColorHelper.palette[0]);
command = new ChangeHabitColorCommand(habits, 0);
}
@Test
@ -80,12 +79,12 @@ public class ChangeHabitColorCommandTest extends BaseTest
{
int k = 0;
for(Habit h : habits)
assertThat(h.color, equalTo(ColorHelper.palette[++k]));
assertThat(h.color, equalTo(++k));
}
private void checkNewColors()
{
for(Habit h : habits)
assertThat(h.color, equalTo(ColorHelper.palette[0]));
assertThat(h.color, equalTo(0));
}
}

@ -19,7 +19,6 @@
package org.isoron.uhabits.unit.models;
import android.graphics.Color;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
@ -78,7 +77,7 @@ public class HabitTest extends BaseTest
Habit model = new Habit();
model.archived = 1;
model.highlight = 1;
model.color = Color.BLACK;
model.color = 0;
model.freqNum = 10;
model.freqDen = 20;
model.reminderDays = 1;

@ -22,10 +22,12 @@ package org.isoron.uhabits.unit.views;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import org.isoron.uhabits.R;
import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.helpers.UIHelper;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.unit.HabitFixtures;
import org.isoron.uhabits.views.CheckmarkView;
import org.isoron.uhabits.views.CheckmarkWidgetView;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -34,18 +36,19 @@ import java.io.IOException;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class CheckmarkViewTest extends ViewTest
public class CheckmarkWidgetViewTest extends ViewTest
{
private CheckmarkView view;
private CheckmarkWidgetView view;
private Habit habit;
@Before
public void setup()
{
super.setup();
UIHelper.setFixedTheme(R.style.TransparentWidgetTheme);
habit = HabitFixtures.createShortHabit();
view = new CheckmarkView(targetContext);
view = new CheckmarkWidgetView(targetContext);
view.setHabit(habit);
refreshData(view);
measureView(dpToPixels(100), dpToPixels(200), view);

@ -52,7 +52,7 @@ public class HabitHistoryViewTest extends ViewTest
view = new HabitHistoryView(targetContext);
view.setHabit(habit);
measureView(dpToPixels(300), dpToPixels(100), view);
measureView(dpToPixels(400), dpToPixels(200), view);
refreshData(view);
}
@ -89,7 +89,7 @@ public class HabitHistoryViewTest extends ViewTest
public void tapDate_withEditableView() throws Throwable
{
view.setIsEditable(true);
tap(view, 270, 30);
tap(view, 340, 40); // today's square
waitForAsyncTasks();
long today = DateHelper.getStartOfToday();
@ -102,9 +102,9 @@ public class HabitHistoryViewTest extends ViewTest
int expectedCheckmarkValues[] = habit.checkmarks.getAllValues();
view.setIsEditable(true);
tap(view, 45, 5); // header
tap(view, 270, 43); // tomorrow's square
tap(view, 280, 30); // right axis
tap(view, 118, 13); // header
tap(view, 336, 60); // tomorrow's square
tap(view, 370, 60); // right axis
waitForAsyncTasks();
int actualCheckmarkValues[] = habit.checkmarks.getAllValues();
@ -115,7 +115,7 @@ public class HabitHistoryViewTest extends ViewTest
public void tapDate_withReadOnlyView() throws Throwable
{
view.setIsEditable(false);
tap(view, 270, 30);
tap(view, 340, 40); // today's square
waitForAsyncTasks();
long today = DateHelper.getStartOfToday();

@ -49,7 +49,7 @@ public class HabitScoreViewTest extends ViewTest
view.setHabit(habit);
view.setBucketSize(7);
refreshData(view);
measureView(dpToPixels(300), dpToPixels(100), view);
measureView(dpToPixels(300), dpToPixels(200), view);
}
@Test
@ -62,7 +62,7 @@ public class HabitScoreViewTest extends ViewTest
@Test
public void testRender_withTransparentBackground() throws Throwable
{
view.setIsBackgroundTransparent(true);
view.setIsTransparencyEnabled(true);
assertRenders(view, "HabitScoreView/renderTransparent.png");
}

@ -45,7 +45,7 @@ public class NumberViewTest extends ViewTest
view = new NumberView(targetContext);
view.setLabel("Hello world");
view.setNumber(31);
view.setColor(ColorHelper.palette[0]);
view.setColor(ColorHelper.CSV_PALETTE[0]);
measureView(dpToPixels(100), dpToPixels(100), view);
}
@ -68,7 +68,7 @@ public class NumberViewTest extends ViewTest
public void testRender_withDifferentParams() throws IOException
{
view.setNumber(500);
view.setColor(ColorHelper.palette[5]);
view.setColor(ColorHelper.CSV_PALETTE[5]);
view.setTextSize(targetContext.getResources().getDimension(R.dimen.tinyTextSize));
measureView(dpToPixels(200), dpToPixels(200), view);

@ -19,6 +19,7 @@
package org.isoron.uhabits.unit.views;
import android.graphics.Color;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
@ -42,10 +43,11 @@ public class RingViewTest extends ViewTest
super.setup();
view = new RingView(targetContext);
view.setLabel("Hello world");
view.setPercentage(0.6f);
view.setColor(ColorHelper.palette[0]);
view.setMaxDiameter(dpToPixels(100));
view.setText("60%");
view.setColor(ColorHelper.CSV_PALETTE[0]);
view.setBackgroundColor(Color.WHITE);
view.setThickness(dpToPixels(3));
}
@Test
@ -55,22 +57,11 @@ public class RingViewTest extends ViewTest
assertRenders(view, "RingView/render.png");
}
@Test
public void testRender_withLongLabel() throws IOException
{
view.setLabel("The quick brown fox jumps over the lazy fox");
measureView(dpToPixels(100), dpToPixels(100), view);
assertRenders(view, "RingView/renderLongLabel.png");
}
@Test
public void testRender_withDifferentParams() throws IOException
{
view.setLabel("Habit Strength");
view.setPercentage(0.25f);
view.setMaxDiameter(dpToPixels(50));
view.setColor(ColorHelper.palette[5]);
view.setColor(ColorHelper.CSV_PALETTE[5]);
measureView(dpToPixels(200), dpToPixels(200), view);
assertRenders(view, "RingView/renderDifferentParams.png");

@ -80,8 +80,8 @@ public class ViewTest extends BaseTest
if(!similarEnough)
{
saveBitmap(expectedImagePath, ".scaledExpected", scaledExpected);
String path = saveBitmap(expectedImagePath, ".actual", actual);
saveBitmap(expectedImagePath, ".expected", scaledExpected);
String path = saveBitmap(expectedImagePath, "", actual);
errorMessage.append(String.format("Actual rendered image " + "saved to %s", path));
fail(errorMessage.toString());
}

@ -21,8 +21,8 @@
<manifest
package="org.isoron.uhabits"
xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="15"
android:versionName="1.4.1">
android:versionCode="16"
android:versionName="1.5.0">
<uses-permission android:name="android.permission.VIBRATE"/>
@ -34,6 +34,8 @@
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="18" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:name="HabitsApplication"
android:allowBackup="true"
@ -59,8 +61,7 @@
<activity
android:name=".ShowHabitActivity"
android:label="@string/title_activity_show_habit"
android:parentActivityName=".MainActivity">
android:label="@string/title_activity_show_habit">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.isoron.uhabits.MainActivity"/>
@ -68,8 +69,7 @@
<activity
android:name=".SettingsActivity"
android:label="@string/settings"
android:parentActivityName=".MainActivity">
android:label="@string/settings">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.isoron.uhabits.MainActivity"/>
@ -90,8 +90,10 @@
<activity
android:name=".AboutActivity"
android:label="@string/about"
android:parentActivityName=".MainActivity">
android:label="@string/about">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity" />
</activity>
<receiver
@ -154,7 +156,11 @@
android:resource="@xml/widget_frequency_info"/>
</receiver>
<receiver android:name=".HabitBroadcastReceiver"/>
<receiver android:name=".HabitBroadcastReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
</application>

@ -0,0 +1,14 @@
update habits set color=0 where color=-2937041;
update habits set color=1 where color=-1684967;
update habits set color=2 where color=-415707;
update habits set color=3 where color=-5262293;
update habits set color=4 where color=-13070788;
update habits set color=5 where color=-16742021;
update habits set color=6 where color=-16732991;
update habits set color=7 where color=-16540699;
update habits set color=8 where color=-10603087;
update habits set color=9 where color=-7461718;
update habits set color=10 where color=-2614432;
update habits set color=11 where color=-13619152;
update habits set color=12 where color=-5592406;
update habits set color=0 where color<0 or color>12;

@ -16,24 +16,24 @@
package com.android.colorpicker;
import org.isoron.uhabits.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatDialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ProgressBar;
import com.android.colorpicker.ColorPickerSwatch.OnColorSelectedListener;
import org.isoron.uhabits.R;
/**
* A dialog which takes in as input an array of palette and creates a palette allowing the user to
* select a specific color swatch, which invokes a listener.
*/
public class ColorPickerDialog extends DialogFragment implements OnColorSelectedListener {
public class ColorPickerDialog extends AppCompatDialogFragment implements OnColorSelectedListener {
public static final int SIZE_LARGE = 1;
public static final int SIZE_SMALL = 2;

@ -30,6 +30,7 @@ import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.os.Bundle;
import android.support.v7.app.AppCompatDialogFragment;
import android.util.Log;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
@ -49,7 +50,7 @@ import com.android.datetimepicker.time.RadialPickerLayout.OnValueSelectedListene
/**
* Dialog to set a time.
*/
public class TimePickerDialog extends DialogFragment implements OnValueSelectedListener{
public class TimePickerDialog extends AppCompatDialogFragment implements OnValueSelectedListener{
private static final String TAG = "TimePickerDialog";
private static final String KEY_HOUR_OF_DAY = "hour_of_day";

@ -19,33 +19,27 @@
package org.isoron.uhabits;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import org.isoron.uhabits.helpers.ColorHelper;
import org.isoron.uhabits.helpers.UIHelper;
public class AboutActivity extends Activity implements View.OnClickListener
public class AboutActivity extends BaseActivity implements View.OnClickListener
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.about);
setupSupportActionBar(true);
if (android.os.Build.VERSION.SDK_INT >= 21)
{
int color = getResources().getColor(R.color.blue_700);
int darkerColor = ColorHelper.mixColors(color, Color.BLACK, 0.75f);
getActionBar().setBackgroundDrawable(new ColorDrawable(color));
getWindow().setStatusBarColor(darkerColor);
}
int color = UIHelper.getStyledColor(this, R.attr.aboutScreenColor);
setupActionBarColor(color);
TextView tvVersion = (TextView) findViewById(R.id.tvVersion);
TextView tvRate = (TextView) findViewById(R.id.tvRate);

@ -19,17 +19,25 @@
package org.isoron.uhabits;
import android.app.Activity;
import android.app.backup.BackupManager;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.Toast;
import org.isoron.uhabits.commands.Command;
import org.isoron.uhabits.helpers.ColorHelper;
import org.isoron.uhabits.helpers.UIHelper;
import java.util.LinkedList;
abstract public class BaseActivity extends Activity implements Thread.UncaughtExceptionHandler
abstract public class BaseActivity extends AppCompatActivity implements Thread.UncaughtExceptionHandler
{
private static int MAX_UNDO_LEVEL = 15;
@ -44,6 +52,8 @@ abstract public class BaseActivity extends Activity implements Thread.UncaughtEx
{
super.onCreate(savedInstanceState);
UIHelper.applyCurrentTheme(this);
androidExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
@ -117,6 +127,23 @@ abstract public class BaseActivity extends Activity implements Thread.UncaughtEx
showToast(command.getExecuteStringId());
}
protected void setupSupportActionBar(boolean homeButtonEnabled)
{
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
if(toolbar == null) return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
toolbar.setElevation(UIHelper.dpToPixels(this, 2));
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if(actionBar == null) return;
if(homeButtonEnabled)
actionBar.setDisplayHomeAsUpEnabled(true);
}
public void onPostExecuteCommand(Long refreshKey)
{
}
@ -139,4 +166,39 @@ abstract public class BaseActivity extends Activity implements Thread.UncaughtEx
else
System.exit(1);
}
protected void setupActionBarColor(int color)
{
ActionBar actionBar = getSupportActionBar();
if(actionBar == null) return;
if (!UIHelper.getStyledBoolean(this, R.attr.useHabitColorAsPrimary)) return;
ColorDrawable drawable = new ColorDrawable(color);
actionBar.setBackgroundDrawable(drawable);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
{
int darkerColor = ColorHelper.mixColors(color, Color.BLACK, 0.75f);
getWindow().setStatusBarColor(darkerColor);
}
}
@Override
protected void onPostResume()
{
super.onPostResume();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
hideFakeToolbarShadow();
}
protected void hideFakeToolbarShadow()
{
View view = findViewById(R.id.toolbarShadow);
if(view != null) view.setVisibility(View.GONE);
view = findViewById(R.id.headerShadow);
if(view != null) view.setVisibility(View.GONE);
}
}

@ -29,11 +29,11 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.BitmapFactory;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.support.v4.content.LocalBroadcastManager;
import org.isoron.uhabits.helpers.DateHelper;
@ -58,7 +58,7 @@ public class HabitBroadcastReceiver extends BroadcastReceiver
{
case ACTION_SHOW_REMINDER:
createNotification(context, intent);
createReminderAlarms(context);
createReminderAlarmsDelayed(context);
break;
case ACTION_DISMISS:
@ -72,10 +72,14 @@ public class HabitBroadcastReceiver extends BroadcastReceiver
case ACTION_SNOOZE:
snoozeHabit(context, intent);
break;
case Intent.ACTION_BOOT_COMPLETED:
ReminderHelper.createReminderAlarms(context);
break;
}
}
private void createReminderAlarms(final Context context)
private void createReminderAlarmsDelayed(final Context context)
{
new Handler().postDelayed(new Runnable()
{
@ -163,9 +167,7 @@ public class HabitBroadcastReceiver extends BroadcastReceiver
{
if (todayValue != Checkmark.UNCHECKED) return;
if (!checkWeekday(intent, habit)) return;
// Check if reminder has been turned off after alarm was scheduled
if (habit.reminderHour == null) return;
if (!habit.hasReminder()) return;
Intent contentIntent = new Intent(context, MainActivity.class);
contentIntent.setData(data);
@ -176,14 +178,16 @@ public class HabitBroadcastReceiver extends BroadcastReceiver
PendingIntent checkIntentPending = buildCheckIntent(context, habit, timestamp);
PendingIntent snoozeIntentPending = buildSnoozeIntent(context, habit);
Uri soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
Uri ringtoneUri = ReminderHelper.getRingtoneUri(context);
NotificationCompat.WearableExtender wearableExtender =
new NotificationCompat.WearableExtender().setBackground(
BitmapFactory.decodeResource(context.getResources(), R.drawable.stripe));
BitmapFactory.decodeResource(context.getResources(),
R.drawable.stripe));
Notification notification =
new NotificationCompat.Builder(context).setSmallIcon(R.drawable.ic_notification)
new NotificationCompat.Builder(context)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(habit.name)
.setContentText(habit.description)
.setContentIntent(contentPendingIntent)
@ -192,7 +196,7 @@ public class HabitBroadcastReceiver extends BroadcastReceiver
context.getString(R.string.check), checkIntentPending)
.addAction(R.drawable.ic_action_snooze,
context.getString(R.string.snooze), snoozeIntentPending)
.setSound(soundUri)
.setSound(ringtoneUri)
.extend(wearableExtender)
.setWhen(reminderTime)
.setShowWhen(true)
@ -201,7 +205,8 @@ public class HabitBroadcastReceiver extends BroadcastReceiver
notification.flags |= Notification.FLAG_AUTO_CANCEL;
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Activity.NOTIFICATION_SERVICE);
(NotificationManager) context.getSystemService(
Activity.NOTIFICATION_SERVICE);
int notificationId = (int) (habit.getId() % Integer.MAX_VALUE);
notificationManager.notify(notificationId, notification);
@ -241,7 +246,10 @@ public class HabitBroadcastReceiver extends BroadcastReceiver
{
Intent intent = new Intent(context, ShowHabitActivity.class);
intent.setData(Uri.parse("content://org.isoron.uhabits/habit/" + habit.getId()));
return PendingIntent.getActivity(context, 0, intent, 0);
return TaskStackBuilder.create(context.getApplicationContext())
.addNextIntentWithParentStack(intent)
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
}
private boolean checkWeekday(Intent intent, Habit habit)
@ -254,4 +262,13 @@ public class HabitBroadcastReceiver extends BroadcastReceiver
return reminderDays[weekday];
}
public static void dismissNotification(Context context, Habit habit)
{
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(
Activity.NOTIFICATION_SERVICE);
int notificationId = (int) (habit.getId() % Integer.MAX_VALUE);
notificationManager.cancel(notificationId);
}
}

@ -40,10 +40,6 @@ public class IntroActivity extends AppIntro2
getString(R.string.intro_description_2), R.drawable.intro_icon_2,
Color.parseColor("#ffa726")));
addSlide(AppIntroFragment.newInstance(getString(R.string.intro_title_3),
getString(R.string.intro_description_3), R.drawable.intro_icon_3,
Color.parseColor("#7cb342")));
addSlide(AppIntroFragment.newInstance(getString(R.string.intro_title_4),
getString(R.string.intro_description_4), R.drawable.intro_icon_4,
Color.parseColor("#9575cd")));

@ -27,19 +27,24 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.ActionBar;
import android.view.Menu;
import android.view.MenuItem;
import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.helpers.UIHelper;
import org.isoron.uhabits.fragments.ListHabitsFragment;
import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.helpers.ReminderHelper;
import org.isoron.uhabits.helpers.UIHelper;
import org.isoron.uhabits.models.Checkmark;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.tasks.BaseTask;
import org.isoron.uhabits.widgets.CheckmarkWidgetProvider;
@ -70,19 +75,35 @@ public class MainActivity extends BaseActivity
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.list_habits_activity);
setupSupportActionBar(false);
prefs = PreferenceManager.getDefaultSharedPreferences(this);
listHabitsFragment =
(ListHabitsFragment) getFragmentManager().findFragmentById(R.id.fragment1);
(ListHabitsFragment) getSupportFragmentManager().findFragmentById(R.id.fragment1);
receiver = new Receiver();
localBroadcastManager = LocalBroadcastManager.getInstance(this);
localBroadcastManager.registerReceiver(receiver, new IntentFilter(ACTION_REFRESH));
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
onPreLollipopStartup();
onStartup();
}
private void onPreLollipopStartup()
{
ActionBar actionBar = getSupportActionBar();
if(actionBar == null) return;
if(UIHelper.isNightMode()) return;
int color = getResources().getColor(R.color.grey_900);
actionBar.setBackgroundDrawable(new ColorDrawable(color));
}
private void onStartup()
{
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
@ -121,7 +142,12 @@ public class MainActivity extends BaseActivity
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
menu.clear();
getMenuInflater().inflate(R.menu.list_habits_menu, menu);
MenuItem nightModeItem = menu.findItem(R.id.action_night_mode);
nightModeItem.setChecked(UIHelper.isNightMode());
return true;
}
@ -130,6 +156,17 @@ public class MainActivity extends BaseActivity
{
switch (item.getItemId())
{
case R.id.action_night_mode:
{
if(UIHelper.isNightMode())
UIHelper.setCurrentTheme(UIHelper.THEME_LIGHT);
else
UIHelper.setCurrentTheme(UIHelper.THEME_DARK);
refreshTheme();
return true;
}
case R.id.action_settings:
{
Intent intent = new Intent(this, SettingsActivity.class);
@ -158,6 +195,23 @@ public class MainActivity extends BaseActivity
}
}
private void refreshTheme()
{
new Handler().postDelayed(new Runnable()
{
@Override
public void run()
{
Intent intent = new Intent(MainActivity.this, MainActivity.class);
MainActivity.this.finish();
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
startActivity(intent);
}
}, 500); // Let the menu disappear first
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
@ -218,11 +272,21 @@ public class MainActivity extends BaseActivity
@Override
protected void doInBackground()
{
dismissNotifications(MainActivity.this);
updateWidgets(MainActivity.this);
}
}.execute();
}
private void dismissNotifications(Context context)
{
for(Habit h : Habit.getHabitsWithReminder())
{
if(h.checkmarks.getTodayValue() != Checkmark.UNCHECKED)
HabitBroadcastReceiver.dismissNotification(context, h);
}
}
public static void updateWidgets(Context context)
{
updateWidgets(context, CheckmarkWidgetProvider.class);

@ -19,19 +19,20 @@
package org.isoron.uhabits;
import android.app.Activity;
import android.os.Bundle;
import org.isoron.uhabits.fragments.SettingsFragment;
import org.isoron.uhabits.helpers.UIHelper;
public class SettingsActivity extends Activity
public class SettingsActivity extends BaseActivity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
getFragmentManager().beginTransaction()
.replace(android.R.id.content, new SettingsFragment())
.commit();
setContentView(R.layout.settings_activity);
setupSupportActionBar(true);
int color = UIHelper.getStyledColor(this, R.attr.aboutScreenColor);
setupActionBarColor(color);
}
}

@ -19,12 +19,12 @@
package org.isoron.uhabits;
import android.app.ActionBar;
import android.content.ContentUris;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import org.isoron.uhabits.helpers.ColorHelper;
import org.isoron.uhabits.models.Habit;
public class ShowHabitActivity extends BaseActivity
@ -38,16 +38,23 @@ public class ShowHabitActivity extends BaseActivity
Uri data = getIntent().getData();
habit = Habit.get(ContentUris.parseId(data));
ActionBar actionBar = getActionBar();
if(actionBar != null && getHabit() != null)
{
actionBar.setTitle(getHabit().name);
if (android.os.Build.VERSION.SDK_INT >= 21)
actionBar.setBackgroundDrawable(new ColorDrawable(getHabit().color));
}
setContentView(R.layout.show_habit_activity);
setupSupportActionBar(true);
setupHabitActionBar();
}
private void setupHabitActionBar()
{
if(habit == null) return;
ActionBar actionBar = getSupportActionBar();
if(actionBar == null) return;
actionBar.setTitle(habit.name);
setupActionBarColor(ColorHelper.getColor(this, habit.color));
}
public Habit getHabit()

@ -17,13 +17,13 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.fragments;
package org.isoron.uhabits.dialogs;
import android.annotation.SuppressLint;
import android.app.DialogFragment;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatDialogFragment;
import android.text.format.DateFormat;
import android.view.LayoutInflater;
import android.view.View;
@ -40,19 +40,18 @@ import com.android.colorpicker.ColorPickerSwatch;
import com.android.datetimepicker.time.RadialPickerLayout;
import com.android.datetimepicker.time.TimePickerDialog;
import org.isoron.uhabits.helpers.ColorHelper;
import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.helpers.UIHelper.OnSavedListener;
import org.isoron.uhabits.R;
import org.isoron.uhabits.commands.Command;
import org.isoron.uhabits.commands.CreateHabitCommand;
import org.isoron.uhabits.commands.EditHabitCommand;
import org.isoron.uhabits.dialogs.WeekdayPickerDialog;
import org.isoron.uhabits.helpers.ColorHelper;
import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.helpers.UIHelper.OnSavedListener;
import org.isoron.uhabits.models.Habit;
import java.util.Arrays;
public class EditHabitFragment extends DialogFragment
public class EditHabitDialogFragment extends AppCompatDialogFragment
implements OnClickListener, WeekdayPickerDialog.OnWeekdaysPickedListener,
TimePickerDialog.OnTimeSetListener, Spinner.OnItemSelectedListener
{
@ -79,9 +78,9 @@ public class EditHabitFragment extends DialogFragment
private SharedPreferences prefs;
private boolean is24HourMode;
public static EditHabitFragment editSingleHabitFragment(long id)
public static EditHabitDialogFragment editSingleHabitFragment(long id)
{
EditHabitFragment frag = new EditHabitFragment();
EditHabitDialogFragment frag = new EditHabitDialogFragment();
Bundle args = new Bundle();
args.putLong("habitId", id);
args.putInt("editMode", EDIT_MODE);
@ -89,9 +88,9 @@ public class EditHabitFragment extends DialogFragment
return frag;
}
public static EditHabitFragment createHabitFragment()
public static EditHabitDialogFragment createHabitFragment()
{
EditHabitFragment frag = new EditHabitFragment();
EditHabitDialogFragment frag = new EditHabitDialogFragment();
Bundle args = new Bundle();
args.putInt("editMode", CREATE_MODE);
frag.setArguments(args);
@ -138,7 +137,7 @@ public class EditHabitFragment extends DialogFragment
modifiedHabit = new Habit();
modifiedHabit.freqNum = 1;
modifiedHabit.freqDen = 1;
modifiedHabit.color = prefs.getInt("pref_default_habit_color", modifiedHabit.color);
modifiedHabit.color = prefs.getInt("pref_default_habit_palette_color", modifiedHabit.color);
}
else if (mode == EDIT_MODE)
{
@ -174,13 +173,13 @@ public class EditHabitFragment extends DialogFragment
return view;
}
private void changeColor(Integer color)
private void changeColor(int paletteColor)
{
modifiedHabit.color = color;
tvName.setTextColor(color);
modifiedHabit.color = paletteColor;
tvName.setTextColor(ColorHelper.getColor(getActivity(), paletteColor));
SharedPreferences.Editor editor = prefs.edit();
editor.putInt("pref_default_habit_color", color);
editor.putInt("pref_default_habit_palette_color", paletteColor);
editor.apply();
}
@ -237,15 +236,18 @@ public class EditHabitFragment extends DialogFragment
private void onColorButtonClick()
{
int originalAndroidColor = ColorHelper.getColor(getActivity(), modifiedHabit.color);
ColorPickerDialog picker = ColorPickerDialog.newInstance(
R.string.color_picker_default_title, ColorHelper.palette, modifiedHabit.color, 4,
ColorPickerDialog.SIZE_SMALL);
R.string.color_picker_default_title, ColorHelper.getPalette(getActivity()),
originalAndroidColor, 4, ColorPickerDialog.SIZE_SMALL);
picker.setOnColorSelectedListener(new ColorPickerSwatch.OnColorSelectedListener()
{
public void onColorSelected(int color)
public void onColorSelected(int androidColor)
{
changeColor(color);
int paletteColor = ColorHelper.colorToPaletteIndex(getActivity(), androidColor);
changeColor(paletteColor);
}
});
picker.show(getFragmentManager(), "picker");

@ -19,21 +19,20 @@
package org.isoron.uhabits.dialogs;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatDialogFragment;
import android.util.DisplayMetrics;
import android.util.Log;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.tasks.BaseTask;
import org.isoron.uhabits.views.HabitHistoryView;
public class HistoryEditorDialog extends DialogFragment
public class HistoryEditorDialog extends AppCompatDialogFragment
implements DialogInterface.OnClickListener
{
private Habit habit;
@ -89,8 +88,6 @@ public class HistoryEditorDialog extends DialogFragment
int width = metrics.widthPixels;
int height = Math.min(metrics.heightPixels, maxHeight);
Log.d("HistoryEditorDialog", String.format("h=%d max_h=%d", height, maxHeight));
getDialog().getWindow().setLayout(width, height);
}

@ -19,16 +19,16 @@
package org.isoron.uhabits.dialogs;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatDialogFragment;
import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.R;
import org.isoron.uhabits.helpers.DateHelper;
public class WeekdayPickerDialog extends DialogFragment
public class WeekdayPickerDialog extends AppCompatDialogFragment
implements DialogInterface.OnMultiChoiceClickListener, DialogInterface.OnClickListener
{

@ -24,11 +24,9 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.R;
import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.helpers.ListHabitsHelper;
import org.isoron.uhabits.loaders.HabitListLoader;
import org.isoron.uhabits.models.Habit;
@ -74,28 +72,15 @@ class HabitListAdapter extends BaseAdapter
public View getView(int position, View view, ViewGroup parent)
{
final Habit habit = loader.habitsList.get(position);
boolean selected = selectedPositions.contains(position);
if (view == null || (Long) view.getTag(R.id.timestamp_key) != DateHelper.getStartOfToday())
{
view = inflater.inflate(R.layout.list_habits_item, null);
helper.initializeLabelAndIcon(view);
helper.inflateCheckmarkButtons(view, onCheckmarkLongClickListener,
onCheckmarkClickListener, inflater);
view = helper.inflateHabitCard(inflater, onCheckmarkLongClickListener,
onCheckmarkClickListener);
}
TextView tvStar = ((TextView) view.findViewById(R.id.tvStar));
TextView tvName = (TextView) view.findViewById(R.id.label);
LinearLayout llInner = (LinearLayout) view.findViewById(R.id.llInner);
LinearLayout llButtons = (LinearLayout) view.findViewById(R.id.llButtons);
llInner.setTag(R.string.habit_key, habit.getId());
helper.updateNameAndIcon(habit, tvStar, tvName);
helper.updateCheckmarkButtons(habit, llButtons);
boolean selected = selectedPositions.contains(position);
helper.updateHabitBackground(llInner, selected);
helper.updateHabitCard(view, habit, selected);
return view;
}

@ -19,9 +19,9 @@
package org.isoron.uhabits.fragments;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.view.ActionMode;
import android.support.v7.app.AlertDialog;
import android.support.v7.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ProgressBar;
@ -29,12 +29,13 @@ import android.widget.ProgressBar;
import com.android.colorpicker.ColorPickerDialog;
import com.android.colorpicker.ColorPickerSwatch;
import org.isoron.uhabits.R;
import org.isoron.uhabits.BaseActivity;
import org.isoron.uhabits.R;
import org.isoron.uhabits.commands.ArchiveHabitsCommand;
import org.isoron.uhabits.commands.ChangeHabitColorCommand;
import org.isoron.uhabits.commands.DeleteHabitsCommand;
import org.isoron.uhabits.commands.UnarchiveHabitsCommand;
import org.isoron.uhabits.dialogs.EditHabitDialogFragment;
import org.isoron.uhabits.helpers.ColorHelper;
import org.isoron.uhabits.helpers.UIHelper;
import org.isoron.uhabits.loaders.HabitListLoader;
@ -155,27 +156,33 @@ public class HabitSelectionCallback implements ActionMode.Callback
case R.id.action_edit_habit:
{
EditHabitFragment frag = EditHabitFragment.editSingleHabitFragment(firstHabit.getId());
EditHabitDialogFragment
frag = EditHabitDialogFragment.editSingleHabitFragment(firstHabit.getId());
frag.setOnSavedListener(onSavedListener);
frag.show(activity.getFragmentManager(), "editHabit");
frag.show(activity.getSupportFragmentManager(), "editHabit");
return true;
}
case R.id.action_color:
{
ColorPickerDialog picker = ColorPickerDialog.newInstance(R.string.color_picker_default_title,
ColorHelper.palette, firstHabit.color, 4, ColorPickerDialog.SIZE_SMALL);
int originalAndroidColor = ColorHelper.getColor(activity, firstHabit.color);
ColorPickerDialog picker = ColorPickerDialog.newInstance(
R.string.color_picker_default_title, ColorHelper.getPalette(activity),
originalAndroidColor, 4, ColorPickerDialog.SIZE_SMALL);
picker.setOnColorSelectedListener(new ColorPickerSwatch.OnColorSelectedListener()
{
public void onColorSelected(int color)
public void onColorSelected(int androidColor)
{
activity.executeCommand(
new ChangeHabitColorCommand(selectedHabits, color), null);
int paletteColor = ColorHelper.colorToPaletteIndex(activity,
androidColor);
activity.executeCommand(new ChangeHabitColorCommand(selectedHabits,
paletteColor), null);
mode.finish();
}
});
picker.show(activity.getFragmentManager(), "picker");
picker.show(activity.getSupportFragmentManager(), "picker");
return true;
}

@ -20,14 +20,14 @@
package org.isoron.uhabits.fragments;
import android.app.Activity;
import android.app.Fragment;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.view.ActionMode;
import android.support.v4.app.Fragment;
import android.support.v7.view.ActionMode;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.HapticFeedbackConstants;
@ -50,17 +50,19 @@ import com.mobeta.android.dslv.DragSortController;
import com.mobeta.android.dslv.DragSortListView;
import com.mobeta.android.dslv.DragSortListView.DropListener;
import org.isoron.uhabits.R;
import org.isoron.uhabits.BaseActivity;
import org.isoron.uhabits.R;
import org.isoron.uhabits.commands.Command;
import org.isoron.uhabits.commands.ToggleRepetitionCommand;
import org.isoron.uhabits.dialogs.EditHabitDialogFragment;
import org.isoron.uhabits.dialogs.FilePickerDialog;
import org.isoron.uhabits.helpers.DatabaseHelper;
import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.helpers.UIHelper.OnSavedListener;
import org.isoron.uhabits.helpers.HintManager;
import org.isoron.uhabits.helpers.ListHabitsHelper;
import org.isoron.uhabits.helpers.ReminderHelper;
import org.isoron.uhabits.helpers.UIHelper;
import org.isoron.uhabits.helpers.UIHelper.OnSavedListener;
import org.isoron.uhabits.loaders.HabitListLoader;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.tasks.ExportCSVTask;
@ -120,7 +122,7 @@ public class ListHabitsFragment extends Fragment
loader.setCheckmarkCount(helper.getButtonCount());
llHint.setOnClickListener(this);
tvStarEmpty.setTypeface(helper.getFontawesome());
tvStarEmpty.setTypeface(UIHelper.getFontAwesome(activity));
adapter = new HabitListAdapter(getActivity(), loader);
adapter.setSelectedPositions(selectedPositions);
@ -141,7 +143,7 @@ public class ListHabitsFragment extends Fragment
if(savedInstanceState != null)
{
EditHabitFragment frag = (EditHabitFragment) getFragmentManager()
EditHabitDialogFragment frag = (EditHabitDialogFragment) getFragmentManager()
.findFragmentByTag("editHabit");
if(frag != null) frag.setOnSavedListener(this);
}
@ -217,7 +219,7 @@ public class ListHabitsFragment extends Fragment
{
case R.id.action_add:
{
EditHabitFragment frag = EditHabitFragment.createHabitFragment();
EditHabitDialogFragment frag = EditHabitDialogFragment.createHabitFragment();
frag.setOnSavedListener(this);
frag.show(getFragmentManager(), "editHabit");
return true;
@ -284,7 +286,7 @@ public class ListHabitsFragment extends Fragment
callback.setOnSavedListener(this);
callback.setListener(this);
actionMode = getActivity().startActionMode(callback);
actionMode = activity.startSupportActionMode(callback);
}
if(actionMode != null) actionMode.invalidate();
@ -328,17 +330,18 @@ public class ListHabitsFragment extends Fragment
private void toggleCheck(View v)
{
Long tag = (Long) v.getTag(R.string.habit_key);
Integer offset = (Integer) v.getTag(R.string.offset_key);
long timestamp = DateHelper.getStartOfDay(
DateHelper.getLocalTime() - offset * DateHelper.millisecondsInOneDay);
Habit habit = loader.habits.get(tag);
Long id = helper.getHabitIdFromCheckmarkView(v);
Habit habit = loader.habits.get(id);
if(habit == null) return;
listView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
float x = v.getX() + v.getWidth() / 2.0f + ((View) v.getParent()).getX();
float y = v.getY() + v.getHeight() / 2.0f + ((View) v.getParent()).getY();
helper.triggerRipple((View) v.getParent().getParent(), x, y);
listView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
helper.toggleCheckmarkView(v, habit);
long timestamp = helper.getTimestampFromCheckmarkView(v);
executeCommand(new ToggleRepetitionCommand(habit, timestamp), habit.getId());
}
@ -380,6 +383,7 @@ public class ListHabitsFragment extends Fragment
else loader.updateHabit(refreshKey);
}
@Override
public void onActionModeDestroyed(ActionMode mode)
{
actionMode = null;

@ -20,19 +20,23 @@
package org.isoron.uhabits.fragments;
import android.app.backup.BackupManager;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.preference.PreferenceFragment;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceCategory;
import android.support.v7.preference.PreferenceFragmentCompat;
import org.isoron.uhabits.MainActivity;
import org.isoron.uhabits.R;
import org.isoron.uhabits.helpers.ReminderHelper;
import org.isoron.uhabits.helpers.UIHelper;
public class SettingsFragment extends PreferenceFragment
public class SettingsFragment extends PreferenceFragmentCompat
implements SharedPreferences.OnSharedPreferenceChangeListener
{
private static int RINGTONE_REQUEST_CODE = 1;
@Override
public void onCreate(Bundle savedInstanceState)
{
@ -44,10 +48,18 @@ public class SettingsFragment extends PreferenceFragment
setResultOnPreferenceClick("exportDB", MainActivity.RESULT_EXPORT_DB);
setResultOnPreferenceClick("bugReport", MainActivity.RESULT_BUG_REPORT);
updateRingtoneDescription();
if(UIHelper.isLocaleFullyTranslated())
removePreference("translate", "linksCategory");
}
@Override
public void onCreatePreferences(Bundle bundle, String s)
{
}
private void removePreference(String preferenceKey, String categoryKey)
{
PreferenceCategory cat = (PreferenceCategory) findPreference(categoryKey);
@ -91,4 +103,38 @@ public class SettingsFragment extends PreferenceFragment
{
BackupManager.dataChanged("org.isoron.uhabits");
}
@Override
public boolean onPreferenceTreeClick(Preference preference)
{
if(preference.getKey() == null) return false;
if (preference.getKey().equals("reminderSound"))
{
ReminderHelper.startRingtonePickerActivity(this, RINGTONE_REQUEST_CODE);
return true;
}
return super.onPreferenceTreeClick(preference);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
if(requestCode == RINGTONE_REQUEST_CODE)
{
ReminderHelper.parseRingtoneData(getContext(), data);
updateRingtoneDescription();
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
private void updateRingtoneDescription()
{
String ringtoneName = ReminderHelper.getRingtoneName(getContext());
Preference ringtonePreference = findPreference("reminderSound");
ringtonePreference.setSummary(ringtoneName);
}
}

@ -19,12 +19,9 @@
package org.isoron.uhabits.fragments;
import android.app.Fragment;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@ -40,8 +37,10 @@ import org.isoron.uhabits.HabitBroadcastReceiver;
import org.isoron.uhabits.R;
import org.isoron.uhabits.ShowHabitActivity;
import org.isoron.uhabits.commands.Command;
import org.isoron.uhabits.dialogs.EditHabitDialogFragment;
import org.isoron.uhabits.dialogs.HistoryEditorDialog;
import org.isoron.uhabits.helpers.ColorHelper;
import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.helpers.ReminderHelper;
import org.isoron.uhabits.helpers.UIHelper;
import org.isoron.uhabits.models.Habit;
@ -73,11 +72,15 @@ public class ShowHabitFragment extends Fragment
@Nullable
private HabitScoreView scoreView;
@Nullable
private SharedPreferences prefs;
private int previousScoreInterval;
private float todayScore;
private float lastMonthScore;
private float lastYearScore;
private int activeColor;
private int inactiveColor;
@Override
public void onStart()
{
@ -90,7 +93,12 @@ public class ShowHabitFragment extends Fragment
{
View view = inflater.inflate(R.layout.show_habit, container, false);
activity = (ShowHabitActivity) getActivity();
habit = activity.getHabit();
activeColor = ColorHelper.getColor(getContext(), habit.color);
inactiveColor = UIHelper.getStyledColor(getContext(), R.attr.mediumContrastTextColor);
updateHeader(view);
dataViews = new LinkedList<>();
@ -99,13 +107,10 @@ public class ShowHabitFragment extends Fragment
scoreView = (HabitScoreView) view.findViewById(R.id.scoreView);
prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
int defaultScoreInterval = prefs.getInt("pref_score_view_interval", 1);
if(defaultScoreInterval > 5 || defaultScoreInterval < 0) defaultScoreInterval = 1;
int defaultScoreInterval = UIHelper.getDefaultScoreInterval(getContext());
previousScoreInterval = defaultScoreInterval;
setScoreBucketSize(defaultScoreInterval);
sStrengthInterval.setSelection(defaultScoreInterval);
sStrengthInterval.setOnItemSelectedListener(this);
@ -133,7 +138,7 @@ public class ShowHabitFragment extends Fragment
if(savedInstanceState != null)
{
EditHabitFragment fragEdit = (EditHabitFragment) getFragmentManager()
EditHabitDialogFragment fragEdit = (EditHabitDialogFragment) getFragmentManager()
.findFragmentByTag("editHabit");
HistoryEditorDialog fragEditor = (HistoryEditorDialog) getFragmentManager()
.findFragmentByTag("historyEditor");
@ -147,6 +152,56 @@ public class ShowHabitFragment extends Fragment
return view;
}
private void updateHeader(View view)
{
if(habit == null) return;
TextView questionLabel = (TextView) view.findViewById(R.id.questionLabel);
questionLabel.setTextColor(activeColor);
questionLabel.setText(habit.description);
TextView reminderLabel = (TextView) view.findViewById(R.id.reminderLabel);
if(habit.hasReminder())
reminderLabel.setText(DateHelper.formatTime(getActivity(), habit.reminderHour,
habit.reminderMin));
else
reminderLabel.setText(getResources().getString(R.string.reminder_off));
TextView frequencyLabel = (TextView) view.findViewById(R.id.frequencyLabel);
frequencyLabel.setText(getFreqText());
if(habit.description.isEmpty())
questionLabel.setVisibility(View.GONE);
}
private String getFreqText()
{
if(habit == null)
return "";
if(habit.freqNum.equals(habit.freqDen))
return getResources().getString(R.string.every_day);
if(habit.freqNum == 1)
{
if (habit.freqDen == 7)
return getResources().getString(R.string.every_week);
if (habit.freqDen % 7 == 0)
return getResources().getString(R.string.every_x_weeks, habit.freqDen / 7);
return getResources().getString(R.string.every_x_days, habit.freqDen);
}
String times_every = getResources().getString(R.string.times_every);
if(habit.freqNum == 1)
times_every = getResources().getString(R.string.time_every);
return String.format("%d %s %d %s", habit.freqNum, times_every, habit.freqDen,
getResources().getString(R.string.days));
}
@Override
public void onResume()
{
@ -154,42 +209,53 @@ public class ShowHabitFragment extends Fragment
refreshData();
}
private void updateScoreRing(View view)
private void updateScore(View view)
{
if(habit == null) return;
if(view == null) return;
float todayValue = (float) habit.scores.getTodayValue();
float percentage = todayValue / Score.MAX_VALUE;
float todayPercentage = todayScore / Score.MAX_VALUE;
float monthDiff = todayPercentage - (lastMonthScore / Score.MAX_VALUE);
float yearDiff = todayPercentage - (lastYearScore / Score.MAX_VALUE);
RingView scoreRing = (RingView) view.findViewById(R.id.scoreRing);
scoreRing.setColor(habit.color);
scoreRing.setPercentage(percentage);
int androidColor = ColorHelper.getColor(getActivity(), habit.color);
scoreRing.setColor(androidColor);
scoreRing.setPercentage(todayPercentage);
TextView scoreLabel = (TextView) view.findViewById(R.id.scoreLabel);
TextView monthDiffLabel = (TextView) view.findViewById(R.id.monthDiffLabel);
TextView yearDiffLabel = (TextView) view.findViewById(R.id.yearDiffLabel);
scoreLabel.setText(String.format("%.0f%%", todayPercentage * 100));
String minus = "\u2212";
monthDiffLabel.setText(String.format("%s%.0f%%", (monthDiff >= 0 ? "+" : minus),
Math.abs(monthDiff) * 100));
yearDiffLabel.setText(
String.format("%s%.0f%%", (yearDiff >= 0 ? "+" : minus), Math.abs(yearDiff) * 100));
monthDiffLabel.setTextColor(monthDiff >= 0 ? activeColor : inactiveColor);
yearDiffLabel.setTextColor(yearDiff >= 0 ? activeColor : inactiveColor);
}
private void updateHeaders(View view)
{
if(habit == null | activity == null) return;
if (android.os.Build.VERSION.SDK_INT >= 21)
{
int darkerHabitColor = ColorHelper.mixColors(habit.color, Color.BLACK, 0.75f);
activity.getWindow().setStatusBarColor(darkerHabitColor);
}
updateColor(view, R.id.tvHistory);
updateColor(view, R.id.tvOverview);
updateColor(view, R.id.tvStrength);
updateColor(view, R.id.tvStreaks);
updateColor(view, R.id.tvWeekdayFreq);
updateColor(view, R.id.scoreLabel);
}
private void updateColor(View view, int viewId)
{
if(habit == null) return;
if(habit == null || activity == null) return;
TextView textView = (TextView) view.findViewById(viewId);
textView.setTextColor(habit.color);
int androidColor = ColorHelper.getColor(activity, habit.color);
textView.setTextColor(androidColor);
}
@Override
@ -207,7 +273,8 @@ public class ShowHabitFragment extends Fragment
{
case R.id.action_edit_habit:
{
EditHabitFragment frag = EditHabitFragment.editSingleHabitFragment(habit.getId());
EditHabitDialogFragment
frag = EditHabitDialogFragment.editSingleHabitFragment(habit.getId());
frag.setOnSavedListener(this);
frag.show(getFragmentManager(), "editHabit");
return true;
@ -227,6 +294,8 @@ public class ShowHabitFragment extends Fragment
else activity.executeCommand(command, h.getId());
ReminderHelper.createReminderAlarms(activity);
HabitBroadcastReceiver.sendRefreshBroadcast(getActivity());
activity.recreate();
}
@ -241,25 +310,32 @@ public class ShowHabitFragment extends Fragment
{
new BaseTask()
{
float percentage;
@Override
protected void doInBackground()
{
if(habit == null) return;
if(dataViews == null) return;
long today = DateHelper.getStartOfToday();
long lastMonth = today - 30 * DateHelper.millisecondsInOneDay;
long lastYear = today - 365 * DateHelper.millisecondsInOneDay;
todayScore = (float) habit.scores.getTodayValue();
lastMonthScore = (float) habit.scores.getValue(lastMonth);
lastYearScore = (float) habit.scores.getValue(lastYear);
int count = 0;
for(HabitDataView view : dataViews)
{
view.refreshData();
onProgressUpdate(count++);
publishProgress(count++);
}
}
@Override
protected void onProgressUpdate(Integer... values)
{
updateScoreRing(getView());
updateScore(getView());
if(dataViews == null) return;
dataViews.get(values[0]).postInvalidate();
}
@ -278,15 +354,15 @@ public class ShowHabitFragment extends Fragment
{
if(scoreView == null) return;
int sizes[] = { 1, 7, 31, 92, 365 };
int size = sizes[position];
scoreView.setBucketSize(size);
if(position != previousScoreInterval) refreshData();
scoreView.setBucketSize(HabitScoreView.DEFAULT_BUCKET_SIZES[position]);
if(prefs != null)
prefs.edit().putInt("pref_score_view_interval", position).apply();
if(position != previousScoreInterval)
{
refreshData();
HabitBroadcastReceiver.sendRefreshBroadcast(getActivity());
}
UIHelper.setDefaultScoreInterval(getContext(), position);
previousScoreInterval = position;
}

@ -19,27 +19,63 @@
package org.isoron.uhabits.helpers;
import android.content.Context;
import android.graphics.Color;
import android.util.Log;
import org.isoron.uhabits.R;
public class ColorHelper
{
public static final int[] palette =
public static int CSV_PALETTE[] =
{
Color.parseColor("#D32F2F"), // red
Color.parseColor("#E64A19"), // orange
Color.parseColor("#F9A825"), // yellow
Color.parseColor("#AFB42B"), // light green
Color.parseColor("#388E3C"), // dark green
Color.parseColor("#00897B"), // teal
Color.parseColor("#00ACC1"), // cyan
Color.parseColor("#039BE5"), // blue
Color.parseColor("#5E35B1"), // deep purple
Color.parseColor("#8E24AA"), // purple
Color.parseColor("#D81B60"), // pink
Color.parseColor("#303030"), // dark grey
Color.parseColor("#aaaaaa") // light grey
Color.parseColor("#D32F2F"), // 0 red
Color.parseColor("#E64A19"), // 1 orange
Color.parseColor("#F9A825"), // 2 yellow
Color.parseColor("#AFB42B"), // 3 light green
Color.parseColor("#388E3C"), // 4 dark green
Color.parseColor("#00897B"), // 5 teal
Color.parseColor("#00ACC1"), // 6 cyan
Color.parseColor("#039BE5"), // 7 blue
Color.parseColor("#5E35B1"), // 8 deep purple
Color.parseColor("#8E24AA"), // 9 purple
Color.parseColor("#D81B60"), // 10 pink
Color.parseColor("#303030"), // 11 dark grey
Color.parseColor("#aaaaaa") // 12 light grey
};
public static int colorToPaletteIndex(Context context, int color)
{
int[] palette = getPalette(context);
for(int k = 0; k < palette.length; k++)
if(palette[k] == color) return k;
return -1;
}
public static int[] getPalette(Context context)
{
int resourceId = UIHelper.getStyleResource(context, R.attr.palette);
if(resourceId < 0) return CSV_PALETTE;
return context.getResources().getIntArray(resourceId);
}
public static int getColor(Context context, int paletteColor)
{
if(context == null) throw new IllegalArgumentException("Context is null");
int palette[] = getPalette(context);
if(paletteColor < 0 || paletteColor >= palette.length)
{
Log.w("ColorHelper", String.format("Invalid color: %d. Returning default.", paletteColor));
paletteColor = 0;
}
return palette[paletteColor];
}
public static int mixColors(int color1, int color2, float amount)
{
final byte ALPHA_CHANNEL = 24;
@ -76,6 +112,12 @@ public class ColorHelper
return setHSVParameter(color, newValue, 2);
}
public static int setAlpha(int color, float newAlpha)
{
int intAlpha = (int) (newAlpha * 255);
return Color.argb(intAlpha, Color.red(color), Color.green(color), Color.blue(color));
}
public static int setMinValue(int color, float newValue)
{
float hsv[] = new float[3];

@ -25,6 +25,7 @@ import android.text.format.DateFormat;
import org.isoron.uhabits.R;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
@ -155,14 +156,26 @@ public class DateHelper
}
/**
* Throughout the code, it is assumed that the weekdays are numbered from 0 (Saturday) to 6
* (Friday). In the Java Calendar they are numbered from 1 (Sunday) to 7 (Saturday). This
* function converts from Java to our internal representation.
*
* @return weekday number in the internal interpretation
*/
public static int javaWeekdayToLoopWeekday(int number)
{
return number % 7;
}
public static String[] getDayNames(int format)
{
String[] wdays = new String[7];
GregorianCalendar day = new GregorianCalendar();
day.set(GregorianCalendar.DAY_OF_WEEK, 0);
Calendar day = new GregorianCalendar();
day.set(GregorianCalendar.DAY_OF_WEEK, Calendar.SATURDAY);
for (int i = 0; i < 7; i++)
for (int i = 0; i < wdays.length; i++)
{
wdays[i] = day.getDisplayName(GregorianCalendar.DAY_OF_WEEK, format,
Locale.getDefault());
@ -172,6 +185,43 @@ public class DateHelper
return wdays;
}
/**
* @return array with weekday names starting according to locale settings,
* e.g. [Mo,Di,Mi,Do,Fr,Sa,So] in Europe
*/
public static String[] getLocaleDayNames(int format)
{
String[] days = new String[7];
Calendar calendar = new GregorianCalendar();
calendar.set(GregorianCalendar.DAY_OF_WEEK, calendar.getFirstDayOfWeek());
for (int i = 0; i < days.length; i++)
{
days[i] = calendar.getDisplayName(GregorianCalendar.DAY_OF_WEEK, format,
Locale.getDefault());
calendar.add(GregorianCalendar.DAY_OF_MONTH, 1);
}
return days;
}
/**
* @return array with week days numbers starting according to locale settings,
* e.g. [2,3,4,5,6,7,1] in Europe
*/
public static Integer[] getLocaleWeekdayList()
{
Integer[] dayNumbers = new Integer[7];
Calendar calendar = new GregorianCalendar();
calendar.set(GregorianCalendar.DAY_OF_WEEK, calendar.getFirstDayOfWeek());
for (int i = 0; i < dayNumbers.length; i++)
{
dayNumbers[i] = calendar.get(GregorianCalendar.DAY_OF_WEEK);
calendar.add(GregorianCalendar.DAY_OF_MONTH, 1);
}
return dayNumbers;
}
public static String formatWeekdayList(Context context, boolean weekday[])
{
String shortDayNames[] = getShortDayNames();

@ -20,13 +20,14 @@
package org.isoron.uhabits.helpers;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Typeface;
import android.util.DisplayMetrics;
import android.content.SharedPreferences;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
@ -34,43 +35,44 @@ import org.isoron.uhabits.R;
import org.isoron.uhabits.loaders.HabitListLoader;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.models.Score;
import org.isoron.uhabits.views.RingView;
import java.util.GregorianCalendar;
public class ListHabitsHelper
{
public static final int INACTIVE_COLOR = Color.rgb(200, 200, 200);
public static final int INACTIVE_CHECKMARK_COLOR = Color.rgb(230, 230, 230);
private static final int CHECKMARK_LEFT_TO_RIGHT = 0;
private static final int CHECKMARK_RIGHT_TO_LEFT = 1;
private final int lowContrastColor;
private final int mediumContrastColor;
private final Context context;
private final HabitListLoader loader;
private Typeface fontawesome;
public ListHabitsHelper(Context context, HabitListLoader loader)
{
this.context = context;
this.loader = loader;
fontawesome = Typeface.createFromAsset(context.getAssets(), "fontawesome-webfont.ttf");
}
public Typeface getFontawesome()
{
return fontawesome;
lowContrastColor = UIHelper.getStyledColor(context, R.attr.lowContrastTextColor);
mediumContrastColor = UIHelper.getStyledColor(context, R.attr.mediumContrastTextColor);
}
public int getButtonCount()
{
DisplayMetrics dm = context.getResources().getDisplayMetrics();
int width = (int) (dm.widthPixels / dm.density);
return Math.max(0, (int) ((width - 160) / 42.0));
float screenWidth = UIHelper.getScreenWidth(context);
float labelWidth = context.getResources().getDimension(R.dimen.habitNameWidth);
float buttonWidth = context.getResources().getDimension(R.dimen.checkmarkWidth);
return Math.max(0, (int) ((screenWidth - labelWidth) / buttonWidth));
}
public int getHabitNameWidth()
{
DisplayMetrics dm = context.getResources().getDisplayMetrics();
int width = (int) (dm.widthPixels / dm.density);
return (int) ((width - 30 - getButtonCount() * 42) * dm.density);
float screenWidth = UIHelper.getScreenWidth(context);
float buttonWidth = context.getResources().getDimension(R.dimen.checkmarkWidth);
float padding = UIHelper.dpToPixels(context, 15);
return (int) (screenWidth - padding - getButtonCount() * buttonWidth);
}
public void updateCheckmarkButtons(Habit habit, LinearLayout llButtons)
@ -83,8 +85,12 @@ public class ListHabitsHelper
for (int i = 0; i < m; i++)
{
int position = i;
TextView tvCheck = (TextView) llButtons.getChildAt(i);
if(getCheckmarkOrder() == CHECKMARK_RIGHT_TO_LEFT)
position = m - i - 1;
TextView tvCheck = (TextView) llButtons.getChildAt(position);
tvCheck.setTag(R.string.habit_key, habitId);
tvCheck.setTag(R.string.offset_key, i);
if(isChecked.length > i)
@ -94,54 +100,32 @@ public class ListHabitsHelper
public int getActiveColor(Habit habit)
{
int activeColor = habit.color;
if(habit.isArchived()) activeColor = INACTIVE_COLOR;
int activeColor = ColorHelper.getColor(context, habit.color);
if(habit.isArchived()) activeColor = mediumContrastColor;
return activeColor;
}
public void initializeLabelAndIcon(View itemView)
{
TextView tvStar = (TextView) itemView.findViewById(R.id.tvStar);
tvStar.setTypeface(getFontawesome());
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(getHabitNameWidth(),
LinearLayout.LayoutParams.WRAP_CONTENT, 1);
itemView.findViewById(R.id.label).setLayoutParams(params);
}
public void updateNameAndIcon(Habit habit, TextView tvStar, TextView tvName)
public void updateNameAndIcon(Habit habit, RingView ring, TextView tvName)
{
int activeColor = getActiveColor(habit);
tvName.setText(habit.name);
tvName.setTextColor(activeColor);
if (habit.isArchived())
{
tvStar.setText(context.getString(R.string.fa_archive));
tvStar.setTextColor(activeColor);
}
else
{
int score = loader.scores.get(habit.getId());
int score = loader.scores.get(habit.getId());
float percentage = (float) score / Score.MAX_VALUE;
if (score < Score.HALF_STAR_CUTOFF)
{
tvStar.setText(context.getString(R.string.fa_star_o));
tvStar.setTextColor(INACTIVE_COLOR);
}
else if (score < Score.FULL_STAR_CUTOFF)
{
tvStar.setText(context.getString(R.string.fa_star_half_o));
tvStar.setTextColor(INACTIVE_COLOR);
}
else
{
tvStar.setText(context.getString(R.string.fa_star));
tvStar.setTextColor(activeColor);
}
}
ring.setColor(activeColor);
ring.setPercentage(percentage);
ring.setPrecision(1.0f / 16);
}
public void updateCheckmark(int activeColor, TextView tvCheck, int check)
@ -156,27 +140,64 @@ public class ListHabitsHelper
case 1:
tvCheck.setText(R.string.fa_check);
tvCheck.setTextColor(INACTIVE_CHECKMARK_COLOR);
tvCheck.setTextColor(lowContrastColor);
tvCheck.setTag(R.string.toggle_key, 1);
break;
case 0:
tvCheck.setText(R.string.fa_times);
tvCheck.setTextColor(INACTIVE_CHECKMARK_COLOR);
tvCheck.setTextColor(lowContrastColor);
tvCheck.setTag(R.string.toggle_key, 0);
break;
}
}
public void updateHabitBackground(View view, boolean isSelected)
public View inflateHabitCard(LayoutInflater inflater,
View.OnLongClickListener onCheckmarkLongClickListener,
View.OnClickListener onCheckmarkClickListener)
{
View view = inflater.inflate(R.layout.list_habits_item, null);
initializeLabelAndIcon(view);
inflateCheckmarkButtons(view, onCheckmarkLongClickListener, onCheckmarkClickListener,
inflater);
return view;
}
public void updateHabitCard(View view, Habit habit, boolean selected)
{
if (isSelected)
view.setBackgroundResource(R.drawable.selected_box);
RingView scoreRing = ((RingView) view.findViewById(R.id.scoreRing));
TextView tvName = (TextView) view.findViewById(R.id.label);
LinearLayout llInner = (LinearLayout) view.findViewById(R.id.llInner);
LinearLayout llButtons = (LinearLayout) view.findViewById(R.id.llButtons);
llInner.setTag(R.string.habit_key, habit.getId());
llInner.setOnTouchListener(new HotspotTouchListener());
updateNameAndIcon(habit, scoreRing, tvName);
updateCheckmarkButtons(habit, llButtons);
updateHabitCardBackground(llInner, selected);
}
public void updateHabitCardBackground(View view, boolean isSelected)
{
if (android.os.Build.VERSION.SDK_INT >= 21)
{
if (isSelected)
view.setBackgroundResource(R.drawable.selected_box);
else
view.setBackgroundResource(R.drawable.ripple);
}
else
{
if (android.os.Build.VERSION.SDK_INT >= 21)
view.setBackgroundResource(R.drawable.ripple_white);
else view.setBackgroundResource(R.drawable.card_background);
Drawable background;
if (isSelected)
background = UIHelper.getStyledDrawable(context, R.attr.selectedBackground);
else
background = UIHelper.getStyledDrawable(context, R.attr.cardBackground);
view.setBackgroundDrawable(background);
}
}
@ -187,7 +208,7 @@ public class ListHabitsHelper
{
View check = inflater.inflate(R.layout.list_habits_item_check, null);
TextView btCheck = (TextView) check.findViewById(R.id.tvCheck);
btCheck.setTypeface(fontawesome);
btCheck.setTypeface(UIHelper.getFontAwesome(context));
btCheck.setOnLongClickListener(onLongClickListener);
btCheck.setOnClickListener(onClickListener);
btCheck.setHapticFeedbackEnabled(false);
@ -205,11 +226,15 @@ public class ListHabitsHelper
for (int i = 0; i < getButtonCount(); i++)
{
int position = 0;
if(getCheckmarkOrder() == CHECKMARK_LEFT_TO_RIGHT)
position = i;
View tvDay = inflater.inflate(R.layout.list_habits_header_check, null);
Button btCheck = (Button) tvDay.findViewById(R.id.tvCheck);
TextView btCheck = (TextView) tvDay.findViewById(R.id.tvCheck);
btCheck.setText(DateHelper.formatHeaderDate(day));
header.addView(tvDay);
header.addView(tvDay, position);
day.add(GregorianCalendar.DAY_OF_MONTH, -1);
}
}
@ -222,9 +247,59 @@ public class ListHabitsHelper
public void toggleCheckmarkView(View v, Habit habit)
{
int androidColor = ColorHelper.getColor(context, habit.color);
if (v.getTag(R.string.toggle_key).equals(2))
updateCheckmark(habit.color, (TextView) v, 0);
updateCheckmark(androidColor, (TextView) v, 0);
else
updateCheckmark(habit.color, (TextView) v, 2);
updateCheckmark(androidColor, (TextView) v, 2);
}
public Long getHabitIdFromCheckmarkView(View v)
{
return (Long) v.getTag(R.string.habit_key);
}
public long getTimestampFromCheckmarkView(View v)
{
Integer offset = (Integer) v.getTag(R.string.offset_key);
return DateHelper.getStartOfDay(DateHelper.getLocalTime() -
offset * DateHelper.millisecondsInOneDay);
}
public void triggerRipple(View v, final float x, final float y)
{
final Drawable background = v.getBackground();
if (android.os.Build.VERSION.SDK_INT >= 21)
background.setHotspot(x, y);
background.setState(new int[]{android.R.attr.state_pressed, android.R.attr.state_enabled});
new Handler().postDelayed(new Runnable()
{
@Override
public void run()
{
background.setState(new int[]{});
}
}, 25);
}
private static class HotspotTouchListener implements View.OnTouchListener
{
@Override
public boolean onTouch(View v, MotionEvent event)
{
if (android.os.Build.VERSION.SDK_INT >= 21)
v.getBackground().setHotspot(event.getX(), event.getY());
return false;
}
}
public int getCheckmarkOrder()
{
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean reverse = prefs.getBoolean("pref_checkmark_reverse_order", false);
return reverse ? CHECKMARK_RIGHT_TO_LEFT : CHECKMARK_LEFT_TO_RIGHT;
}
}

@ -23,12 +23,19 @@ import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.util.Log;
import org.isoron.uhabits.HabitBroadcastReceiver;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
import java.text.DateFormat;
@ -89,4 +96,68 @@ public class ReminderHelper
Log.d("ReminderHelper", String.format("Setting alarm (%s): %s",
DateFormat.getDateTimeInstance().format(new Date(reminderTime)), habit.name));
}
@Nullable
public static Uri getRingtoneUri(Context context)
{
Uri ringtoneUri = null;
Uri defaultRingtoneUri = Settings.System.DEFAULT_NOTIFICATION_URI;
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
String prefRingtoneUri = prefs.getString("pref_ringtone_uri", defaultRingtoneUri.toString());
if (prefRingtoneUri.length() > 0) ringtoneUri = Uri.parse(prefRingtoneUri);
return ringtoneUri;
}
public static void parseRingtoneData(Context context, @Nullable Intent data)
{
if(data == null) return;
Uri ringtoneUri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
if (ringtoneUri != null)
{
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
prefs.edit().putString("pref_ringtone_uri", ringtoneUri.toString()).apply();
}
else
{
String off = context.getResources().getString(R.string.none);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
prefs.edit().putString("pref_ringtone_uri", "").apply();
}
}
public static void startRingtonePickerActivity(Fragment fragment, int requestCode)
{
Uri existingRingtoneUri = ReminderHelper.getRingtoneUri(fragment.getContext());
Uri defaultRingtoneUri = Settings.System.DEFAULT_NOTIFICATION_URI;
Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, defaultRingtoneUri);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, existingRingtoneUri);
fragment.startActivityForResult(intent, requestCode);
}
public static String getRingtoneName(Context context)
{
Uri ringtoneUri = getRingtoneUri(context);
String ringtoneName = context.getResources().getString(R.string.none);
if(ringtoneUri != null)
{
Ringtone ringtone = RingtoneManager.getRingtone(context, ringtoneUri);
if(ringtone != null)
{
ringtoneName = ringtone.getTitle(context);
ringtone.stop();
}
}
return ringtoneName;
}
}

@ -19,37 +19,58 @@
package org.isoron.uhabits.helpers;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Debug;
import android.os.Looper;
import android.preference.PreferenceManager;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import org.isoron.uhabits.BuildConfig;
import org.isoron.uhabits.HabitsApplication;
import org.isoron.uhabits.R;
import org.isoron.uhabits.commands.Command;
import java.util.Locale;
public abstract class UIHelper
{
public static final String ISORON_NAMESPACE = "http://isoron.org/android";
private static Typeface fontawesome;
public static final int THEME_LIGHT = 0;
public static final int THEME_DARK = 1;
private static Typeface fontAwesome;
private static Integer fixedTheme;
public static void setFixedTheme(Integer fixedTheme)
{
UIHelper.fixedTheme = fixedTheme;
}
public interface OnSavedListener
{
void onSaved(Command command, Object savedObject);
}
public static Typeface getFontAwesome(Context context)
{
if(fontAwesome == null)
fontAwesome = Typeface.createFromAsset(context.getAssets(), "fontawesome-webfont.ttf");
return fontAwesome;
}
public static void showSoftKeyboard(View view)
{
InputMethodManager imm = (InputMethodManager) view.getContext()
@ -87,6 +108,14 @@ public abstract class UIHelper
else return defaultValue;
}
public static Integer getColorAttribute(Context context, AttributeSet attrs, String name,
Integer defaultValue)
{
int resId = attrs.getAttributeResourceValue(ISORON_NAMESPACE, name, 0);
if (resId != 0) return context.getResources().getColor(resId);
else return defaultValue;
}
public static int getIntAttribute(Context context, AttributeSet attrs, String name,
int defaultValue)
{
@ -95,6 +124,14 @@ public abstract class UIHelper
else return defaultValue;
}
public static boolean getBooleanAttribute(Context context, AttributeSet attrs, String name,
boolean defaultValue)
{
String boolText = getAttribute(context, attrs, name, null);
if(boolText != null) return Boolean.parseBoolean(boolText);
else return defaultValue;
}
public static float getFloatAttribute(Context context, AttributeSet attrs, String name,
float defaultValue)
{
@ -153,16 +190,132 @@ public abstract class UIHelper
public static boolean isLocaleFullyTranslated()
{
// TODO: Move this to another place, or detect automatically
String fullyTranslatedLanguages[] = { "en", "ar", "cs", "de", "it", "ja", "ko", "po", "pl",
"pt", "ru", "sv", "zh", "es", "fr" };
String fullyTranslatedLanguages[] = { "ca", "zh", "en", "de", "in", "it", "ko", "pl", "pt",
"es", "tk", "uk"};
final String currentLanguage = Locale.getDefault().getLanguage();
Log.d("UIHelper", String.format("lang=%s", currentLanguage));
for(String lang : fullyTranslatedLanguages)
if(currentLanguage.equals(lang)) return true;
return false;
}
public static float getScreenWidth(Context context)
{
return context.getResources().getDisplayMetrics().widthPixels;
}
public static int getStyledColor(Context context, int attrId)
{
TypedArray ta = getTypedArray(context, attrId);
int color = ta.getColor(0, 0);
ta.recycle();
return color;
}
private static TypedArray getTypedArray(Context context, int attrId)
{
int[] attrs = new int[]{ attrId };
if(fixedTheme != null)
return context.getTheme().obtainStyledAttributes(fixedTheme, attrs);
else
return context.obtainStyledAttributes(attrs);
}
public static Drawable getStyledDrawable(Context context, int attrId)
{
TypedArray ta = getTypedArray(context, attrId);
Drawable drawable = ta.getDrawable(0);
ta.recycle();
return drawable;
}
public static boolean getStyledBoolean(Context context, int attrId)
{
TypedArray ta = getTypedArray(context, attrId);
boolean bool = ta.getBoolean(0, false);
ta.recycle();
return bool;
}
public static float getStyledFloat(Context context, int attrId)
{
TypedArray ta = getTypedArray(context, attrId);
float f = ta.getFloat(0, 0);
ta.recycle();
return f;
}
static int getStyleResource(Context context, int attrId)
{
TypedArray ta = getTypedArray(context, attrId);
int resourceId = ta.getResourceId(0, -1);
ta.recycle();
return resourceId;
}
public static void applyCurrentTheme(Activity activity)
{
switch(getCurrentTheme())
{
case THEME_DARK:
{
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
boolean pureBlackEnabled = prefs.getBoolean("pref_pure_black", false);
if(pureBlackEnabled)
activity.setTheme(R.style.AppBaseThemeDark_PureBlack);
else
activity.setTheme(R.style.AppBaseThemeDark);
break;
}
case THEME_LIGHT:
default:
activity.setTheme(R.style.AppBaseTheme);
break;
}
}
private static int getCurrentTheme()
{
Context appContext = HabitsApplication.getContext();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(appContext);
return prefs.getInt("pref_theme", THEME_LIGHT);
}
public static void setCurrentTheme(int theme)
{
Context appContext = HabitsApplication.getContext();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(appContext);
prefs.edit().putInt("pref_theme", theme).apply();
}
public static boolean isNightMode()
{
return getCurrentTheme() == THEME_DARK;
}
public static void setDefaultScoreInterval(Context context, int position)
{
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
prefs.edit().putInt("pref_score_view_interval", position).apply();
}
public static int getDefaultScoreInterval(Context context)
{
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
int defaultScoreInterval = prefs.getInt("pref_score_view_interval", 1);
if(defaultScoreInterval > 5 || defaultScoreInterval < 0) defaultScoreInterval = 1;
return defaultScoreInterval;
}
}

@ -71,7 +71,11 @@ public class Habit extends Model
public Integer freqDen;
/**
* Color of the habit. The format is the same as android.graphics.Color.
* Color of the habit.
*
* This number is not an android.graphics.Color, but an index to the activity color palette,
* which changes according to the theme. To convert this color into an android.graphics.Color,
* use ColorHelper.getColor(context, habit.color).
*/
@Column(name = "color")
public Integer color;
@ -166,7 +170,7 @@ public class Habit extends Model
*/
public Habit()
{
this.color = ColorHelper.palette[5];
this.color = 5;
this.position = Habit.countWithArchived();
this.highlight = 0;
this.archived = 0;
@ -492,8 +496,15 @@ public class Habit extends Model
for(Habit habit : habits)
{
String[] cols = { habit.name, habit.description, Integer.toString(habit.freqNum),
Integer.toString(habit.freqDen), ColorHelper.toHTML(habit.color) };
String[] cols =
{
habit.name,
habit.description,
Integer.toString(habit.freqNum),
Integer.toString(habit.freqDen),
ColorHelper.toHTML(ColorHelper.CSV_PALETTE[habit.color])
};
csv.writeNext(cols, false);
}

@ -1,202 +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.views;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.os.Build;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.View;
import org.isoron.uhabits.R;
import org.isoron.uhabits.helpers.ColorHelper;
import org.isoron.uhabits.models.Habit;
public class CheckmarkView extends View implements HabitDataView
{
private Paint pCard;
private Paint pIcon;
private int primaryColor;
private int timesColor;
private int darkGrey;
private int width;
private int height;
private float leftMargin;
private float topMargin;
private float padding;
private String label;
private String fa_check;
private String fa_times;
private int check_status;
private Rect rect;
private TextPaint textPaint;
private StaticLayout labelLayout;
private Habit habit;
public CheckmarkView(Context context)
{
super(context);
init(context);
}
public CheckmarkView(Context context, AttributeSet attrs)
{
super(context, attrs);
init(context);
}
private void init(Context context)
{
Typeface fontawesome =
Typeface.createFromAsset(context.getAssets(), "fontawesome-webfont.ttf");
pCard = new Paint();
pCard.setAntiAlias(true);
pIcon = new Paint();
pIcon.setAntiAlias(true);
pIcon.setTypeface(fontawesome);
pIcon.setTextAlign(Paint.Align.CENTER);
textPaint = new TextPaint();
textPaint.setColor(Color.WHITE);
textPaint.setAntiAlias(true);
fa_check = context.getString(R.string.fa_check);
fa_times = context.getString(R.string.fa_times);
primaryColor = ColorHelper.palette[10];
timesColor = Color.argb(128, 255, 255, 255);
darkGrey = Color.argb(64, 0, 0, 0);
rect = new Rect();
check_status = 0;
label = "Habit";
}
public void setHabit(Habit habit)
{
this.habit = habit;
}
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
drawBackground(canvas);
drawCheckmark(canvas);
drawLabel(canvas);
}
private void drawBackground(Canvas canvas)
{
int color = (check_status == 2 ? primaryColor : darkGrey);
pCard.setColor(color);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
canvas.drawRoundRect(leftMargin, topMargin, width - leftMargin, height - topMargin, padding,
padding, pCard);
else
canvas.drawRect(leftMargin, topMargin, width - leftMargin, height - topMargin, pCard);
}
private void drawCheckmark(Canvas canvas)
{
String text = (check_status == 0 ? fa_times : fa_check);
int color = (check_status == 2 ? Color.WHITE : timesColor);
pIcon.setColor(color);
pIcon.setTextSize(width * 0.5f);
pIcon.getTextBounds(text, 0, 1, rect);
int y = (int) ((0.67f * height - rect.bottom - rect.top) / 2);
canvas.drawText(text, width / 2, y, pIcon);
}
private void drawLabel(Canvas canvas)
{
canvas.save();
float y;
int nLines = labelLayout.getLineCount();
if(nLines == 1)
y = height * 0.8f - padding;
else
y = height * 0.7f - padding;
canvas.translate(leftMargin + padding, y);
labelLayout.draw(canvas);
canvas.restore();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int width = MeasureSpec.getSize(widthMeasureSpec);
setMeasuredDimension(width, (int) (width * 1.25));
}
@Override
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight)
{
this.width = getMeasuredWidth();
this.height = getMeasuredHeight();
leftMargin = (width * 0.015f);
topMargin = (height * 0.015f);
padding = 8 * leftMargin;
textPaint.setTextSize(0.15f * width);
updateLabel();
}
public void refreshData()
{
this.check_status = habit.checkmarks.getTodayValue();
this.primaryColor = Color.argb(230, Color.red(habit.color), Color.green(habit.color),
Color.blue(habit.color));
this.label = habit.name;
updateLabel();
postInvalidate();
}
private void updateLabel()
{
textPaint.setColor(Color.WHITE);
labelLayout = new StaticLayout(label, textPaint,
(int) (width - 2 * leftMargin - 2 * padding),
Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false);
}
}

@ -0,0 +1,169 @@
/*
* 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.views;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.widget.TextView;
import org.isoron.uhabits.R;
import org.isoron.uhabits.helpers.ColorHelper;
import org.isoron.uhabits.helpers.UIHelper;
import org.isoron.uhabits.models.Checkmark;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.models.Score;
public class CheckmarkWidgetView extends HabitWidgetView implements HabitDataView
{
private int activeColor;
private float percentage;
@Nullable
private String name;
@Nullable
private RingView ring;
private TextView label;
private int checkmarkValue;
public CheckmarkWidgetView(Context context)
{
super(context);
init();
}
public CheckmarkWidgetView(Context context, AttributeSet attrs)
{
super(context, attrs);
init();
}
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 = ColorHelper.CSV_PALETTE[6];
checkmarkValue = Checkmark.CHECKED_EXPLICITLY;
refresh();
}
}
@Override
public void setHabit(@NonNull Habit habit)
{
super.setHabit(habit);
this.name = habit.name;
this.activeColor = ColorHelper.getColor(getContext(), habit.color);
refresh();
}
public void refresh()
{
if (backgroundPaint == null || frame == null || ring == null) return;
Context context = getContext();
String text;
int backgroundColor;
int foregroundColor;
switch (checkmarkValue)
{
case Checkmark.CHECKED_EXPLICITLY:
text = getResources().getString(R.string.fa_check);
backgroundColor = activeColor;
foregroundColor =
UIHelper.getStyledColor(context, R.attr.highContrastReverseTextColor);
setShadowAlpha(0x4f);
rebuildBackground();
backgroundPaint.setColor(backgroundColor);
frame.setBackgroundDrawable(background);
break;
case Checkmark.CHECKED_IMPLICITLY:
text = getResources().getString(R.string.fa_check);
backgroundColor = UIHelper.getStyledColor(context, R.attr.cardBackgroundColor);
foregroundColor = UIHelper.getStyledColor(context, R.attr.mediumContrastTextColor);
break;
case Checkmark.UNCHECKED:
default:
text = getResources().getString(R.string.fa_times);
backgroundColor = UIHelper.getStyledColor(context, R.attr.cardBackgroundColor);
foregroundColor = UIHelper.getStyledColor(context, R.attr.mediumContrastTextColor);
break;
}
ring.setPercentage(percentage);
ring.setColor(foregroundColor);
ring.setBackgroundColor(backgroundColor);
ring.setText(text);
label.setText(name);
label.setTextColor(foregroundColor);
requestLayout();
postInvalidate();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
float w = width;
float h = width * 1.25f;
float scale = Math.min(width / w, height / h);
w *= scale;
h *= scale;
widthMeasureSpec = MeasureSpec.makeMeasureSpec((int) w, MeasureSpec.EXACTLY);
heightMeasureSpec = MeasureSpec.makeMeasureSpec((int) h, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
public void refreshData()
{
if(habit == null) return;
this.percentage = (float) habit.scores.getTodayValue() / Score.MAX_VALUE;
this.checkmarkValue = habit.checkmarks.getTodayValue();
refresh();
}
@NonNull
protected Integer getInnerLayoutId()
{
return R.layout.widget_checkmark;
}
}

@ -0,0 +1,78 @@
/*
* 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.views;
import android.content.Context;
import android.support.annotation.NonNull;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
public class GraphWidgetView extends HabitWidgetView implements HabitDataView
{
private final HabitDataView dataView;
private TextView title;
public GraphWidgetView(Context context, HabitDataView dataView)
{
super(context);
this.dataView = dataView;
init();
}
private void init()
{
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);
}
@Override
public void setHabit(@NonNull Habit habit)
{
super.setHabit(habit);
dataView.setHabit(habit);
title.setText(habit.name);
}
@Override
public void refreshData()
{
if(habit == null) return;
dataView.refreshData();
}
@NonNull
protected Integer getInnerLayoutId()
{
return R.layout.widget_graph;
}
}

@ -21,13 +21,14 @@ package org.isoron.uhabits.views;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import org.isoron.uhabits.R;
import org.isoron.uhabits.helpers.ColorHelper;
import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.helpers.UIHelper;
import org.isoron.uhabits.models.Habit;
import java.text.SimpleDateFormat;
@ -56,13 +57,12 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV
private int nColumns;
private int textColor;
private int dimmedTextColor;
private int gridColor;
private int[] colors;
private int primaryColor;
private boolean isBackgroundTransparent;
private HashMap<Long, Integer[]> frequency;
private String wdays[];
public HabitFrequencyView(Context context)
{
@ -73,7 +73,7 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV
public HabitFrequencyView(Context context, AttributeSet attrs)
{
super(context, attrs);
this.primaryColor = ColorHelper.palette[7];
this.primaryColor = ColorHelper.getColor(getContext(), 7);
this.frequency = new HashMap<>();
init();
}
@ -89,8 +89,6 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV
createPaints();
createColors();
wdays = DateHelper.getShortDayNames();
dfMonth = DateHelper.getDateFormat("MMM");
dfYear = DateHelper.getDateFormat("yyyy");
@ -101,25 +99,15 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV
private void createColors()
{
if(habit != null)
this.primaryColor = habit.color;
if (isBackgroundTransparent)
{
primaryColor = ColorHelper.setSaturation(primaryColor, 0.75f);
primaryColor = ColorHelper.setValue(primaryColor, 1.0f);
textColor = Color.argb(192, 255, 255, 255);
dimmedTextColor = Color.argb(128, 255, 255, 255);
}
else
{
textColor = Color.argb(64, 0, 0, 0);
dimmedTextColor = Color.argb(16, 0, 0, 0);
this.primaryColor = ColorHelper.getColor(getContext(), habit.color);
}
colors = new int[4];
textColor = UIHelper.getStyledColor(getContext(), R.attr.mediumContrastTextColor);
gridColor = UIHelper.getStyledColor(getContext(), R.attr.lowContrastTextColor);
colors[0] = Color.rgb(230, 230, 230);
colors = new int[4];
colors[0] = gridColor;
colors[3] = primaryColor;
colors[1] = ColorHelper.mixColors(colors[0], colors[3], 0.66f);
colors[2] = ColorHelper.mixColors(colors[0], colors[3], 0.33f);
@ -247,11 +235,13 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV
float rowHeight = rect.height() / 8.0f;
prevRect.set(rect);
for (int i = 0; i < 7; i++)
Integer[] localeWeekdayList = DateHelper.getLocaleWeekdayList();
for (int j = 0; j < localeWeekdayList.length; j++)
{
rect.set(0, 0, baseSize, baseSize);
rect.offset(prevRect.left, prevRect.top + baseSize * i);
rect.offset(prevRect.left, prevRect.top + baseSize * j);
int i = DateHelper.javaWeekdayToLoopWeekday(localeWeekdayList[j]);
if(values != null)
drawMarker(canvas, rect, values[i]);
@ -287,11 +277,10 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV
pText.setTextAlign(Paint.Align.LEFT);
pText.setColor(textColor);
pGrid.setColor(dimmedTextColor);
pGrid.setColor(gridColor);
for (int i = 0; i < nRows; i++)
{
canvas.drawText(wdays[i], rGrid.right - columnWidth,
for (String day : DateHelper.getLocaleDayNames(Calendar.SHORT)) {
canvas.drawText(day, rGrid.right - columnWidth,
rGrid.top + rowHeight / 2 + 0.25f * em, pText);
pGrid.setStrokeWidth(1f);

@ -57,19 +57,20 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie
private float columnHeight;
private int nColumns;
private String wdays[];
private SimpleDateFormat dfMonth;
private SimpleDateFormat dfYear;
private Calendar baseDate;
private int nDays;
private int todayWeekday;
/** 0-based-position of today in the column */
private int todayPositionInColumn;
private int colors[];
private RectF baseLocation;
private int primaryColor;
private boolean isBackgroundTransparent;
private int textColor;
private int reverseTextColor;
private boolean isEditable;
public HabitHistoryView(Context context)
@ -92,13 +93,12 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie
private void init()
{
createPaints();
createColors();
createPaints();
isEditable = false;
checkmarks = new int[0];
primaryColor = ColorHelper.palette[7];
wdays = DateHelper.getShortDayNames();
primaryColor = ColorHelper.getColor(getContext(), 7);
dfMonth = DateHelper.getDateFormat("MMM");
dfYear = DateHelper.getDateFormat("yyyy");
@ -111,10 +111,11 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie
baseDate.add(Calendar.DAY_OF_YEAR, -(getDataOffset() - 1) * 7);
nDays = (nColumns - 1) * 7;
todayWeekday = DateHelper.getStartOfTodayCalendar().get(Calendar.DAY_OF_WEEK) % 7;
int realWeekday = DateHelper.getStartOfTodayCalendar().get(Calendar.DAY_OF_WEEK);
todayPositionInColumn = (7 + realWeekday - baseDate.getFirstDayOfWeek()) % 7;
baseDate.add(Calendar.DAY_OF_YEAR, -nDays);
baseDate.add(Calendar.DAY_OF_YEAR, -todayWeekday);
baseDate.add(Calendar.DAY_OF_YEAR, -todayPositionInColumn);
}
@Override
@ -133,8 +134,9 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie
setScrollerBucketSize((int) baseSize);
squareSpacing = UIHelper.dpToPixels(getContext(), 1.0f);
float maxTextSize = getResources().getDimensionPixelSize(R.dimen.regularTextSize);
float textSize = Math.min(baseSize * 0.5f, maxTextSize);
float maxTextSize = getResources().getDimension(R.dimen.regularTextSize);
float textSize = height * 0.06f;
textSize = Math.min(textSize, maxTextSize);
pSquareFg.setTextSize(textSize);
pTextHeader.setTextSize(textSize);
@ -155,7 +157,7 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie
{
float width = 0;
for(String w : wdays)
for(String w : DateHelper.getLocaleDayNames(Calendar.SHORT))
width = Math.max(width, pSquareFg.measureText(w));
return width;
@ -164,7 +166,7 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie
private void createColors()
{
if(habit != null)
this.primaryColor = habit.color;
this.primaryColor = ColorHelper.getColor(getContext(), habit.color);
if(isBackgroundTransparent)
primaryColor = ColorHelper.setMinValue(primaryColor, 0.75f);
@ -179,15 +181,17 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie
colors[0] = Color.argb(16, 255, 255, 255);
colors[1] = Color.argb(128, red, green, blue);
colors[2] = primaryColor;
textColor = Color.rgb(255, 255, 255);
textColor = Color.WHITE;
reverseTextColor = Color.WHITE;
}
else
{
colors = new int[3];
colors[0] = Color.argb(25, 0, 0, 0);
colors[0] = UIHelper.getStyledColor(getContext(), R.attr.lowContrastTextColor);
colors[1] = Color.argb(127, red, green, blue);
colors[2] = primaryColor;
textColor = Color.argb(64, 0, 0, 0);
textColor = UIHelper.getStyledColor(getContext(), R.attr.mediumContrastTextColor);
reverseTextColor = UIHelper.getStyledColor(getContext(), R.attr.highContrastReverseTextColor);
}
}
@ -198,10 +202,8 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie
pTextHeader.setAntiAlias(true);
pSquareBg = new Paint();
pSquareBg.setColor(primaryColor);
pSquareFg = new Paint();
pSquareFg.setColor(Color.WHITE);
pSquareFg.setAntiAlias(true);
pSquareFg.setTextAlign(Align.CENTER);
}
@ -274,9 +276,10 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie
for (int j = 0; j < 7; j++)
{
if (!(column == nColumns - 2 && getDataOffset() == 0 && j > todayWeekday))
if (!(column == nColumns - 2 && getDataOffset() == 0 && j > todayPositionInColumn))
{
int checkmarkOffset = getDataOffset() * 7 + nDays - 7 * (column + 1) + todayWeekday - j;
int checkmarkOffset = getDataOffset() * 7 + nDays - 7 * (column + 1) +
todayPositionInColumn - j;
drawSquare(canvas, location, date, checkmarkOffset);
}
@ -291,6 +294,7 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie
if (checkmarkOffset >= checkmarks.length) pSquareBg.setColor(colors[0]);
else pSquareBg.setColor(colors[checkmarks[checkmarkOffset]]);
pSquareFg.setColor(reverseTextColor);
canvas.drawRect(location, pSquareBg);
String text = Integer.toString(date.get(Calendar.DAY_OF_MONTH));
canvas.drawText(text, location.centerX(), location.centerY() + squareTextOffset, pSquareFg);
@ -298,11 +302,13 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie
private void drawAxis(Canvas canvas, RectF location)
{
for (int i = 0; i < 7; i++)
float verticalOffset = pTextHeader.getFontSpacing() * 0.4f;
for (String day : DateHelper.getLocaleDayNames(Calendar.SHORT))
{
location.offset(0, columnWidth);
canvas.drawText(wdays[i], location.left + headerTextOffset,
location.bottom - headerTextOffset, pTextHeader);
canvas.drawText(day, location.left + headerTextOffset,
location.centerY() + verticalOffset, pTextHeader);
}
}

@ -20,6 +20,7 @@
package org.isoron.uhabits.views;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
@ -32,6 +33,7 @@ import android.util.AttributeSet;
import org.isoron.uhabits.R;
import org.isoron.uhabits.helpers.ColorHelper;
import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.helpers.UIHelper;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.models.Score;
@ -47,6 +49,8 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView
public static final PorterDuffXfermode XFERMODE_SRC =
new PorterDuffXfermode(PorterDuff.Mode.SRC);
public static int DEFAULT_BUCKET_SIZES[] = { 1, 7, 31, 92, 365 };
private Paint pGrid;
private float em;
private Habit habit;
@ -65,15 +69,19 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView
private int nColumns;
private int textColor;
private int dimmedTextColor;
private int gridColor;
@Nullable
private int[] scores;
private int primaryColor;
private boolean isBackgroundTransparent;
private int bucketSize = 7;
private int footerHeight;
private int backgroundColor;
private Bitmap drawingCache;
private Canvas cacheCanvas;
private boolean isTransparencyEnabled;
public HabitScoreView(Context context)
{
@ -84,7 +92,7 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView
public HabitScoreView(Context context, AttributeSet attrs)
{
super(context, attrs);
this.primaryColor = ColorHelper.palette[7];
this.primaryColor = ColorHelper.getColor(getContext(), 7);
init();
}
@ -110,21 +118,11 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView
private void createColors()
{
if(habit != null)
this.primaryColor = habit.color;
if (isBackgroundTransparent)
{
primaryColor = ColorHelper.setSaturation(primaryColor, 0.75f);
primaryColor = ColorHelper.setValue(primaryColor, 1.0f);
this.primaryColor = ColorHelper.getColor(getContext(), habit.color);
textColor = Color.argb(192, 255, 255, 255);
dimmedTextColor = Color.argb(128, 255, 255, 255);
}
else
{
textColor = Color.argb(64, 0, 0, 0);
dimmedTextColor = Color.argb(16, 0, 0, 0);
}
textColor = UIHelper.getStyledColor(getContext(), R.attr.mediumContrastTextColor);
gridColor = UIHelper.getStyledColor(getContext(), R.attr.lowContrastTextColor);
backgroundColor = UIHelper.getStyledColor(getContext(), R.attr.cardBackgroundColor);
}
protected void createPaints()
@ -153,8 +151,9 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView
{
if(height < 9) height = 200;
int maxTextSize = getResources().getDimensionPixelSize(R.dimen.regularTextSize);
pText.setTextSize(Math.min(height * 0.047f, maxTextSize));
float maxTextSize = getResources().getDimension(R.dimen.tinyTextSize);
float textSize = height * 0.06f;
pText.setTextSize(Math.min(textSize, maxTextSize));
em = pText.getFontSpacing();
footerHeight = (int)(3 * em);
@ -167,12 +166,25 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView
columnWidth = Math.max(columnWidth, getMaxDayWidth() * 1.5f);
columnWidth = Math.max(columnWidth, getMaxMonthWidth() * 1.2f);
columnHeight = 8 * baseSize;
nColumns = (int) (width / columnWidth);
columnWidth = (float) width / nColumns;
columnHeight = 8 * baseSize;
float minStrokeWidth = UIHelper.dpToPixels(getContext(), 1);
pGraph.setTextSize(baseSize * 0.5f);
pGraph.setStrokeWidth(baseSize * 0.1f);
pGrid.setStrokeWidth(baseSize * 0.05f);
pGrid.setStrokeWidth(Math.min(minStrokeWidth, baseSize * 0.05f));
if(isTransparencyEnabled)
initCache(width, height);
}
private void initCache(int width, int height)
{
if (drawingCache != null) drawingCache.recycle();
drawingCache = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
cacheCanvas = new Canvas(drawingCache);
}
public void refreshData()
@ -211,12 +223,26 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
Canvas activeCanvas;
if(isTransparencyEnabled)
{
if(drawingCache == null) initCache(getWidth(), getHeight());
activeCanvas = cacheCanvas;
drawingCache.eraseColor(Color.TRANSPARENT);
}
else
{
activeCanvas = canvas;
}
if (habit == null || scores == null) return;
rect.set(0, 0, nColumns * columnWidth, columnHeight);
rect.offset(0, paddingTop);
drawGrid(canvas, rect);
drawGrid(activeCanvas, rect);
pText.setColor(textColor);
pGraph.setColor(primaryColor);
@ -241,24 +267,28 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView
int height = (int) (columnHeight * relativeScore);
rect.set(0, 0, baseSize, baseSize);
rect.offset(k * columnWidth, paddingTop + columnHeight - height - columnWidth / 2);
rect.offset(k * columnWidth + (columnWidth - baseSize) / 2,
paddingTop + columnHeight - height - baseSize / 2);
if (!prevRect.isEmpty())
{
drawLine(canvas, prevRect, rect);
drawMarker(canvas, prevRect);
drawLine(activeCanvas, prevRect, rect);
drawMarker(activeCanvas, prevRect);
}
if (k == nColumns - 1) drawMarker(canvas, rect);
if (k == nColumns - 1) drawMarker(activeCanvas, rect);
prevRect.set(rect);
rect.set(0, 0, columnWidth, columnHeight);
rect.offset(k * columnWidth, paddingTop);
drawFooter(canvas, rect, currentDate);
drawFooter(activeCanvas, rect, currentDate);
currentDate += bucketSize * DateHelper.millisecondsInOneDay;
}
if(activeCanvas != canvas)
canvas.drawBitmap(drawingCache, 0, 0, null);
}
private int skipYear = 0;
@ -322,7 +352,7 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView
pText.setTextAlign(Paint.Align.LEFT);
pText.setColor(textColor);
pGrid.setColor(dimmedTextColor);
pGrid.setColor(gridColor);
for (int i = 0; i < nRows; i++)
{
@ -345,7 +375,7 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView
private void drawMarker(Canvas canvas, RectF rect)
{
rect.inset(baseSize * 0.15f, baseSize * 0.15f);
setModeOrColor(pGraph, XFERMODE_CLEAR, Color.WHITE);
setModeOrColor(pGraph, XFERMODE_CLEAR, backgroundColor);
canvas.drawOval(rect, pGraph);
rect.inset(baseSize * 0.1f, baseSize * 0.1f);
@ -353,22 +383,23 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView
canvas.drawOval(rect, pGraph);
rect.inset(baseSize * 0.1f, baseSize * 0.1f);
setModeOrColor(pGraph, XFERMODE_CLEAR, Color.WHITE);
setModeOrColor(pGraph, XFERMODE_CLEAR, backgroundColor);
canvas.drawOval(rect, pGraph);
if(isBackgroundTransparent)
if(isTransparencyEnabled)
pGraph.setXfermode(XFERMODE_SRC);
}
public void setIsBackgroundTransparent(boolean isBackgroundTransparent)
public void setIsTransparencyEnabled(boolean enabled)
{
this.isBackgroundTransparent = isBackgroundTransparent;
this.isTransparencyEnabled = enabled;
createColors();
requestLayout();
}
private void setModeOrColor(Paint p, PorterDuffXfermode mode, int color)
{
if(isBackgroundTransparent)
if(isTransparencyEnabled)
p.setXfermode(mode);
else
p.setColor(color);

@ -29,6 +29,7 @@ import android.view.View;
import org.isoron.uhabits.R;
import org.isoron.uhabits.helpers.ColorHelper;
import org.isoron.uhabits.helpers.UIHelper;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.models.Streak;
@ -53,7 +54,6 @@ public class HabitStreakView extends View implements HabitDataView
private List<Streak> streaks;
private boolean isBackgroundTransparent;
private int textColor;
private DateFormat dateFormat;
private int width;
private float em;
@ -61,6 +61,8 @@ public class HabitStreakView extends View implements HabitDataView
private float textMargin;
private boolean shouldShowLabels;
private int maxStreakCount;
private int textColor;
private int reverseTextColor;
public HabitStreakView(Context context)
{
@ -71,7 +73,7 @@ public class HabitStreakView extends View implements HabitDataView
public HabitStreakView(Context context, AttributeSet attrs)
{
super(context, attrs);
this.primaryColor = ColorHelper.palette[7];
this.primaryColor = ColorHelper.getColor(getContext(), 7);
init();
}
@ -109,10 +111,11 @@ public class HabitStreakView extends View implements HabitDataView
maxStreakCount = height / baseSize;
this.width = width;
int maxTextSize = getResources().getDimensionPixelSize(R.dimen.regularTextSize);
float regularTextSize = Math.min(baseSize * 0.56f, maxTextSize);
float minTextSize = getResources().getDimension(R.dimen.tinyTextSize);
float maxTextSize = getResources().getDimension(R.dimen.regularTextSize);
float textSize = baseSize * 0.5f;
paint.setTextSize(regularTextSize);
paint.setTextSize(Math.max(Math.min(textSize, maxTextSize), minTextSize));
em = paint.getFontSpacing();
textMargin = 0.5f * em;
@ -122,36 +125,19 @@ public class HabitStreakView extends View implements HabitDataView
private void createColors()
{
if(habit != null)
this.primaryColor = habit.color;
if(isBackgroundTransparent)
{
primaryColor = ColorHelper.setSaturation(primaryColor, 0.75f);
primaryColor = ColorHelper.setValue(primaryColor, 1.0f);
}
this.primaryColor = ColorHelper.getColor(getContext(), habit.color);
int red = Color.red(primaryColor);
int green = Color.green(primaryColor);
int blue = Color.blue(primaryColor);
if(isBackgroundTransparent)
{
colors = new int[4];
colors[3] = primaryColor;
colors[2] = Color.argb(213, red, green, blue);
colors[1] = Color.argb(170, red, green, blue);
colors[0] = Color.argb(128, red, green, blue);
textColor = Color.rgb(255, 255, 255);
}
else
{
colors = new int[4];
colors[3] = primaryColor;
colors[2] = Color.argb(192, red, green, blue);
colors[1] = Color.argb(96, red, green, blue);
colors[0] = Color.argb(32, 0, 0, 0);
textColor = Color.argb(64, 0, 0, 0);
}
colors = new int[4];
colors[3] = primaryColor;
colors[2] = Color.argb(192, red, green, blue);
colors[1] = Color.argb(96, red, green, blue);
colors[0] = UIHelper.getStyledColor(getContext(), R.attr.lowContrastTextColor);
textColor = UIHelper.getStyledColor(getContext(), R.attr.mediumContrastTextColor);
reverseTextColor = UIHelper.getStyledColor(getContext(), R.attr.highContrastReverseTextColor);
}
protected void createPaints()
@ -216,7 +202,7 @@ public class HabitStreakView extends View implements HabitDataView
if(shouldShowLabels) availableWidth -= 2 * textMargin;
float barWidth = percentage * availableWidth;
float minBarWidth = paint.measureText(streak.length.toString());
float minBarWidth = paint.measureText(streak.length.toString()) + em;
barWidth = Math.max(barWidth, minBarWidth);
float gap = (width - barWidth) / 2;
@ -229,7 +215,7 @@ public class HabitStreakView extends View implements HabitDataView
float yOffset = rect.centerY() + 0.3f * em;
paint.setColor(Color.WHITE);
paint.setColor(reverseTextColor);
paint.setTextAlign(Paint.Align.CENTER);
canvas.drawText(streak.length.toString(), rect.centerX(), yOffset, paint);

@ -0,0 +1,117 @@
/*
* 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.views;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.InsetDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RoundRectShape;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import org.isoron.uhabits.R;
import org.isoron.uhabits.helpers.UIHelper;
import org.isoron.uhabits.models.Habit;
import java.util.Arrays;
public abstract class HabitWidgetView extends FrameLayout implements HabitDataView
{
@Nullable
protected InsetDrawable background;
@Nullable
protected Paint backgroundPaint;
@Nullable
protected Habit habit;
protected ViewGroup frame;
public void setShadowAlpha(int shadowAlpha)
{
this.shadowAlpha = shadowAlpha;
}
private int shadowAlpha;
public HabitWidgetView(Context context)
{
super(context);
init();
}
public HabitWidgetView(Context context, AttributeSet attrs)
{
super(context, attrs);
init();
}
private void init()
{
inflate(getContext(), getInnerLayoutId(), this);
shadowAlpha = (int) (255 * UIHelper.getStyledFloat(getContext(), R.attr.widgetShadowAlpha));
rebuildBackground();
}
protected abstract @NonNull Integer getInnerLayoutId();
protected void rebuildBackground()
{
Context context = getContext();
int backgroundAlpha =
(int) (255 * UIHelper.getStyledFloat(context, R.attr.widgetBackgroundAlpha));
int shadowRadius = (int) UIHelper.dpToPixels(context, 2);
int shadowOffset = (int) UIHelper.dpToPixels(context, 1);
int shadowColor = Color.argb(shadowAlpha, 0, 0, 0);
float cornerRadius = UIHelper.dpToPixels(context, 5);
float[] radii = new float[8];
Arrays.fill(radii, cornerRadius);
RoundRectShape shape = new RoundRectShape(radii, null, null);
ShapeDrawable innerDrawable = new ShapeDrawable(shape);
int insetLeftTop = Math.max(shadowRadius - shadowOffset, 0);
int insetRightBottom = shadowRadius + shadowOffset;
background = new InsetDrawable(innerDrawable, insetLeftTop, insetLeftTop, insetRightBottom,
insetRightBottom);
backgroundPaint = innerDrawable.getPaint();
backgroundPaint.setShadowLayer(shadowRadius, shadowOffset, shadowOffset, shadowColor);
backgroundPaint.setColor(UIHelper.getStyledColor(context, R.attr.cardBackgroundColor));
backgroundPaint.setAlpha(backgroundAlpha);
frame = (ViewGroup) findViewById(R.id.frame);
if(frame != null) frame.setBackgroundDrawable(background);
}
@Override
public void setHabit(@NonNull Habit habit)
{
this.habit = habit;
}
}

@ -71,7 +71,7 @@ public class NumberView extends View
this.textSize = UIHelper.getFloatAttribute(context, attrs, "textSize",
getResources().getDimension(R.dimen.regularTextSize));
this.color = ColorHelper.palette[7];
this.color = ColorHelper.getColor(getContext(), 7);
init();
}

@ -23,6 +23,7 @@ import android.content.Context;
import android.util.AttributeSet;
import org.isoron.uhabits.R;
import org.isoron.uhabits.helpers.ColorHelper;
import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.helpers.UIHelper;
import org.isoron.uhabits.models.Habit;
@ -79,6 +80,6 @@ public class RepetitionCountView extends NumberView implements HabitDataView
public void setHabit(Habit habit)
{
this.habit = habit;
setColor(habit.color);
setColor(ColorHelper.getColor(getContext(), habit.color));
}
}

@ -19,42 +19,59 @@
package org.isoron.uhabits.views;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.View;
import org.isoron.uhabits.R;
import org.isoron.uhabits.helpers.ColorHelper;
import org.isoron.uhabits.helpers.UIHelper;
public class RingView extends View
{
public static final PorterDuffXfermode XFERMODE_CLEAR =
new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
private int color;
private float precision;
private float percentage;
private float labelMarginTop;
private TextPaint pRing;
private String label;
private int diameter;
private float thickness;
private RectF rect;
private StaticLayout labelLayout;
private TextPaint pRing;
private int width;
private int height;
private float diameter;
private Integer backgroundColor;
private Integer inactiveColor;
private float maxDiameter;
private float em;
private String text;
private float textSize;
private int fadedTextColor;
private boolean enableFontAwesome;
private Bitmap drawingCache;
private Canvas cacheCanvas;
private boolean isTransparencyEnabled;
public RingView(Context context)
{
super(context);
percentage = 0.0f;
precision = 0.01f;
color = ColorHelper.CSV_PALETTE[0];
thickness = UIHelper.dpToPixels(getContext(), 2);
text = "";
textSize = context.getResources().getDimension(R.dimen.smallTextSize);
init();
}
@ -62,9 +79,24 @@ public class RingView extends View
{
super(context, attrs);
this.label = UIHelper.getAttribute(context, attrs, "label", "Label");
this.maxDiameter = UIHelper.getFloatAttribute(context, attrs, "maxDiameter", 100);
this.maxDiameter = UIHelper.dpToPixels(context, maxDiameter);
percentage = UIHelper.getFloatAttribute(context, attrs, "percentage", 0);
precision = UIHelper.getFloatAttribute(context, attrs, "precision", 0.01f);
color = UIHelper.getColorAttribute(context, attrs, "color", 0);
backgroundColor = UIHelper.getColorAttribute(context, attrs, "backgroundColor", null);
inactiveColor = UIHelper.getColorAttribute(context, attrs, "inactiveColor", null);
thickness = UIHelper.getFloatAttribute(context, attrs, "thickness", 0);
thickness = UIHelper.dpToPixels(context, thickness);
float defaultTextSize = context.getResources().getDimension(R.dimen.smallTextSize);
textSize = UIHelper.getFloatAttribute(context, attrs, "textSize", defaultTextSize);
textSize = UIHelper.spToPixels(context, textSize);
text = UIHelper.getAttribute(context, attrs, "text", "");
enableFontAwesome = UIHelper.getBooleanAttribute(context, attrs, "enableFontAwesome", false);
init();
}
@ -74,19 +106,34 @@ public class RingView extends View
postInvalidate();
}
public void setMaxDiameter(float maxDiameter)
@Override
public void setBackgroundColor(int backgroundColor)
{
this.maxDiameter = maxDiameter;
this.backgroundColor = backgroundColor;
postInvalidate();
}
public void setLabel(String label)
public void setPercentage(float percentage)
{
this.label = label;
this.percentage = percentage;
postInvalidate();
}
public void setPercentage(float percentage)
public void setPrecision(float precision)
{
this.percentage = percentage;
this.precision = precision;
postInvalidate();
}
public void setThickness(float thickness)
{
this.thickness = thickness;
postInvalidate();
}
public void setText(String text)
{
this.text = text;
postInvalidate();
}
@ -97,59 +144,94 @@ public class RingView extends View
pRing.setColor(color);
pRing.setTextAlign(Paint.Align.CENTER);
fadedTextColor = getResources().getColor(R.color.fadedTextColor);
textSize = getResources().getDimension(R.dimen.smallTextSize);
if(backgroundColor == null)
backgroundColor = UIHelper.getStyledColor(getContext(), R.attr.cardBackgroundColor);
if(inactiveColor == null)
inactiveColor = UIHelper.getStyledColor(getContext(), R.attr.highContrastTextColor);
inactiveColor = ColorHelper.setAlpha(inactiveColor, 0.1f);
rect = new RectF();
}
@Override
@SuppressLint("DrawAllocation")
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = MeasureSpec.getSize(widthMeasureSpec);
height = MeasureSpec.getSize(heightMeasureSpec);
diameter = Math.min(maxDiameter, width);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
diameter = Math.min(height, width);
pRing.setTextSize(textSize);
labelMarginTop = textSize * 0.80f;
labelLayout = new StaticLayout(label, pRing, width, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0f,
false);
em = pRing.measureText("M");
width = Math.max(width, labelLayout.getWidth());
height = (int) (diameter + labelLayout.getHeight() + labelMarginTop);
setMeasuredDimension(diameter, diameter);
}
setMeasuredDimension(width, height);
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
super.onSizeChanged(w, h, oldw, oldh);
if(isTransparencyEnabled)
{
if (drawingCache != null) drawingCache.recycle();
drawingCache = Bitmap.createBitmap(diameter, diameter, Bitmap.Config.ARGB_8888);
cacheCanvas = new Canvas(drawingCache);
}
}
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
float thickness = diameter * 0.15f;
Canvas activeCanvas;
if(isTransparencyEnabled)
{
activeCanvas = cacheCanvas;
drawingCache.eraseColor(Color.TRANSPARENT);
}
else
{
activeCanvas = canvas;
}
pRing.setColor(color);
rect.set(0, 0, diameter, diameter);
rect.offset((width - diameter) / 2, 0);
canvas.drawArc(rect, -90, 360 * percentage, true, pRing);
pRing.setColor(Color.argb(255, 230, 230, 230));
canvas.drawArc(rect, 360 * percentage - 90 + 2, 360 * (1 - percentage) - 4, true, pRing);
float angle = 360 * Math.round(percentage / precision) * precision;
pRing.setColor(Color.WHITE);
rect.inset(thickness, thickness);
canvas.drawArc(rect, -90, 360, true, pRing);
activeCanvas.drawArc(rect, -90, angle, true, pRing);
pRing.setColor(fadedTextColor);
pRing.setTextSize(textSize);
float lineHeight = pRing.getFontSpacing();
canvas.drawText(String.format("%.0f%%", percentage * 100), rect.centerX(),
rect.centerY() + lineHeight / 3, pRing);
pRing.setTextSize(textSize);
canvas.translate(width / 2, diameter + labelMarginTop);
labelLayout.draw(canvas);
pRing.setColor(inactiveColor);
activeCanvas.drawArc(rect, angle - 90, 360 - angle, true, pRing);
if(thickness > 0)
{
if(isTransparencyEnabled)
pRing.setXfermode(XFERMODE_CLEAR);
else
pRing.setColor(backgroundColor);
rect.inset(thickness, thickness);
activeCanvas.drawArc(rect, 0, 360, true, pRing);
pRing.setXfermode(null);
pRing.setColor(color);
pRing.setTextSize(textSize);
if(enableFontAwesome) pRing.setTypeface(UIHelper.getFontAwesome(getContext()));
activeCanvas.drawText(text, rect.centerX(), rect.centerY() + 0.4f * em, pRing);
}
if(activeCanvas != canvas)
canvas.drawBitmap(drawingCache, 0, 0, null);
}
public void setIsTransparencyEnabled(boolean isTransparencyEnabled)
{
this.isTransparencyEnabled = isTransparencyEnabled;
}
}

@ -33,6 +33,7 @@ import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.RemoteViews;
import android.widget.TextView;
import org.isoron.uhabits.R;
import org.isoron.uhabits.helpers.UIHelper;
@ -45,7 +46,8 @@ import java.io.IOException;
public abstract class BaseWidgetProvider extends AppWidgetProvider
{
private int width, height;
private int portraitWidth, portraitHeight;
private int landscapeWidth, landscapeHeight;
protected abstract int getDefaultHeight();
@ -122,15 +124,19 @@ public abstract class BaseWidgetProvider extends AppWidgetProvider
protected abstract void refreshCustomViewData(View widgetView);
private void savePreview(Context context, int widgetId, Bitmap widgetCache)
private void savePreview(Context context, int widgetId, Bitmap widgetCache, int width,
int height, String label)
{
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);
iv.setImageBitmap(widgetCache);
if(iv != null) iv.setImageBitmap(widgetCache);
view.measure(width, height);
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
@ -138,7 +144,7 @@ public abstract class BaseWidgetProvider extends AppWidgetProvider
view.buildDrawingCache();
Bitmap previewCache = view.getDrawingCache();
String filename = String.format("%s/%d.png", context.getExternalCacheDir(), widgetId);
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);
@ -172,8 +178,11 @@ public abstract class BaseWidgetProvider extends AppWidgetProvider
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT));
}
width = maxWidth;
height = maxHeight;
portraitWidth = minWidth;
portraitHeight = maxHeight;
landscapeWidth = maxWidth;
landscapeHeight = minHeight;
}
private void measureCustomView(Context context, int w, int h, View customView)
@ -203,8 +212,8 @@ public abstract class BaseWidgetProvider extends AppWidgetProvider
private final Context context;
private final Habit habit;
private final AppWidgetManager manager;
public RemoteViews remoteViews;
public View widgetView;
public RemoteViews portraitRemoteViews, landscapeRemoteViews;
public View portraitWidgetView, landscapeWidgetView;
public RenderWidgetTask(int widgetId, Context context, Habit habit,
AppWidgetManager manager)
@ -219,17 +228,31 @@ public abstract class BaseWidgetProvider extends AppWidgetProvider
protected void onPreExecute()
{
super.onPreExecute();
context.setTheme(R.style.TransparentWidgetTheme);
portraitRemoteViews = new RemoteViews(context.getPackageName(), getLayoutId());
portraitWidgetView = buildCustomView(context, habit);
measureCustomView(context, portraitWidth, portraitHeight, portraitWidgetView);
remoteViews = new RemoteViews(context.getPackageName(), getLayoutId());
widgetView = buildCustomView(context, habit);
measureCustomView(context, width, height, widgetView);
manager.updateAppWidget(widgetId, remoteViews);
landscapeRemoteViews = new RemoteViews(context.getPackageName(), getLayoutId());
landscapeWidgetView = buildCustomView(context, habit);
measureCustomView(context, landscapeWidth, 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()
{
refreshCustomViewData(widgetView);
refreshCustomViewData(portraitWidgetView);
refreshCustomViewData(landscapeWidgetView);
}
@Override
@ -237,20 +260,9 @@ public abstract class BaseWidgetProvider extends AppWidgetProvider
{
try
{
widgetView.invalidate();
widgetView.setDrawingCacheEnabled(true);
widgetView.buildDrawingCache(true);
Bitmap drawingCache = widgetView.getDrawingCache();
remoteViews.setTextViewText(R.id.label, habit.name);
remoteViews.setImageViewBitmap(R.id.imageView, drawingCache);
//savePreview(context, widgetId, drawingCache);
PendingIntent onClickIntent = getOnClickPendingIntent(context, habit);
if (onClickIntent != null) remoteViews.setOnClickPendingIntent(R.id.imageView,
onClickIntent);
manager.updateAppWidget(widgetId, remoteViews);
buildRemoteViews(portraitWidgetView, portraitRemoteViews, portraitWidth, portraitHeight);
buildRemoteViews(landscapeWidgetView, landscapeRemoteViews, landscapeWidth, landscapeHeight);
updateAppWidget();
}
catch (Exception e)
{
@ -260,5 +272,39 @@ public abstract class BaseWidgetProvider extends AppWidgetProvider
super.onPostExecute(aVoid);
}
private void buildRemoteViews(View widgetView, RemoteViews remoteViews, int width, int height)
{
widgetView.invalidate();
widgetView.setDrawingCacheEnabled(true);
widgetView.buildDrawingCache(true);
Bitmap drawingCache = widgetView.getDrawingCache();
remoteViews.setTextViewText(R.id.label, habit.name);
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);
}
}
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 };
}
}

@ -25,7 +25,7 @@ import android.view.View;
import org.isoron.uhabits.HabitBroadcastReceiver;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.views.CheckmarkView;
import org.isoron.uhabits.views.CheckmarkWidgetView;
import org.isoron.uhabits.views.HabitDataView;
public class CheckmarkWidgetProvider extends BaseWidgetProvider
@ -33,7 +33,7 @@ public class CheckmarkWidgetProvider extends BaseWidgetProvider
@Override
protected View buildCustomView(Context context, Habit habit)
{
CheckmarkView view = new CheckmarkView(context);
CheckmarkWidgetView view = new CheckmarkWidgetView(context);
view.setHabit(habit);
return view;
}
@ -53,19 +53,19 @@ public class CheckmarkWidgetProvider extends BaseWidgetProvider
@Override
protected int getDefaultHeight()
{
return 200;
return 125;
}
@Override
protected int getDefaultWidth()
{
return 200;
return 125;
}
@Override
protected int getLayoutId()
{
return R.layout.widget_checkmark;
return R.layout.widget_wrapper;
}

@ -26,6 +26,7 @@ import android.view.View;
import org.isoron.uhabits.HabitBroadcastReceiver;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.views.GraphWidgetView;
import org.isoron.uhabits.views.HabitDataView;
import org.isoron.uhabits.views.HabitFrequencyView;
@ -34,8 +35,8 @@ public class FrequencyWidgetProvider extends BaseWidgetProvider
@Override
protected View buildCustomView(Context context, Habit habit)
{
HabitFrequencyView view = new HabitFrequencyView(context, null);
view.setIsBackgroundTransparent(true);
HabitFrequencyView dataView = new HabitFrequencyView(context);
GraphWidgetView view = new GraphWidgetView(context, dataView);
view.setHabit(habit);
return view;
}
@ -67,6 +68,6 @@ public class FrequencyWidgetProvider extends BaseWidgetProvider
@Override
protected int getLayoutId()
{
return R.layout.widget_graph;
return R.layout.widget_wrapper;
}
}

@ -25,6 +25,7 @@ import android.view.View;
import org.isoron.uhabits.HabitBroadcastReceiver;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.views.GraphWidgetView;
import org.isoron.uhabits.views.HabitDataView;
import org.isoron.uhabits.views.HabitHistoryView;
@ -33,9 +34,9 @@ public class HistoryWidgetProvider extends BaseWidgetProvider
@Override
protected View buildCustomView(Context context, Habit habit)
{
HabitHistoryView view = new HabitHistoryView(context, null);
HabitHistoryView dataView = new HabitHistoryView(context);
GraphWidgetView view = new GraphWidgetView(context, dataView);
view.setHabit(habit);
view.setIsBackgroundTransparent(true);
return view;
}
@ -54,18 +55,18 @@ public class HistoryWidgetProvider extends BaseWidgetProvider
@Override
protected int getDefaultHeight()
{
return 200;
return 250;
}
@Override
protected int getDefaultWidth()
{
return 200;
return 250;
}
@Override
protected int getLayoutId()
{
return R.layout.widget_graph;
return R.layout.widget_wrapper;
}
}

@ -24,17 +24,25 @@ import android.view.View;
import org.isoron.uhabits.HabitBroadcastReceiver;
import org.isoron.uhabits.R;
import org.isoron.uhabits.helpers.UIHelper;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.views.GraphWidgetView;
import org.isoron.uhabits.views.HabitDataView;
import org.isoron.uhabits.views.HabitScoreView;
public class ScoreWidgetProvider extends BaseWidgetProvider
public class ScoreWidgetProvider extends BaseWidgetProvider
{
@Override
protected View buildCustomView(Context context, Habit habit)
{
HabitScoreView view = new HabitScoreView(context, null);
view.setIsBackgroundTransparent(true);
int defaultScoreInterval = UIHelper.getDefaultScoreInterval(context);
int size = HabitScoreView.DEFAULT_BUCKET_SIZES[defaultScoreInterval];
HabitScoreView dataView = new HabitScoreView(context);
dataView.setIsTransparencyEnabled(true);
dataView.setBucketSize(size);
GraphWidgetView view = new GraphWidgetView(context, dataView);
view.setHabit(habit);
return view;
}
@ -54,18 +62,18 @@ public class ScoreWidgetProvider extends BaseWidgetProvider
@Override
protected int getDefaultHeight()
{
return 200;
return 300;
}
@Override
protected int getDefaultWidth()
{
return 200;
return 300;
}
@Override
protected int getLayoutId()
{
return R.layout.widget_graph;
return R.layout.widget_wrapper;
}
}

@ -25,6 +25,7 @@ import android.view.View;
import org.isoron.uhabits.HabitBroadcastReceiver;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.views.GraphWidgetView;
import org.isoron.uhabits.views.HabitDataView;
import org.isoron.uhabits.views.HabitStreakView;
@ -33,8 +34,8 @@ public class StreakWidgetProvider extends BaseWidgetProvider
@Override
protected View buildCustomView(Context context, Habit habit)
{
HabitStreakView view = new HabitStreakView(context, null);
view.setIsBackgroundTransparent(true);
HabitStreakView dataView = new HabitStreakView(context);
GraphWidgetView view = new GraphWidgetView(context, dataView);
view.setHabit(habit);
return view;
}
@ -66,6 +67,6 @@ public class StreakWidgetProvider extends BaseWidgetProvider
@Override
protected int getLayoutId()
{
return R.layout.widget_graph;
return R.layout.widget_wrapper;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 181 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 B

After

Width:  |  Height:  |  Size: 155 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 715 B

After

Width:  |  Height:  |  Size: 304 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 822 B

After

Width:  |  Height:  |  Size: 354 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 B

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save