diff --git a/CHANGELOG.md b/CHANGELOG.md index 37344041d..b2b072e4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index 46a9cc93b..f108eca4e 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/app/build.gradle b/app/build.gradle index d98191ed1..35eaaa0dc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' + } } diff --git a/app/src/androidTest/assets/views-v21/CheckmarkView/checked.png b/app/src/androidTest/assets/views-v21/CheckmarkView/checked.png deleted file mode 100644 index b54e5707d..000000000 Binary files a/app/src/androidTest/assets/views-v21/CheckmarkView/checked.png and /dev/null differ diff --git a/app/src/androidTest/assets/views-v21/CheckmarkView/implicitly_checked.png b/app/src/androidTest/assets/views-v21/CheckmarkView/implicitly_checked.png deleted file mode 100644 index 39d8e87f3..000000000 Binary files a/app/src/androidTest/assets/views-v21/CheckmarkView/implicitly_checked.png and /dev/null differ diff --git a/app/src/androidTest/assets/views-v21/CheckmarkView/large_size.png b/app/src/androidTest/assets/views-v21/CheckmarkView/large_size.png deleted file mode 100644 index 99e439e72..000000000 Binary files a/app/src/androidTest/assets/views-v21/CheckmarkView/large_size.png and /dev/null differ diff --git a/app/src/androidTest/assets/views-v21/CheckmarkView/unchecked.png b/app/src/androidTest/assets/views-v21/CheckmarkView/unchecked.png deleted file mode 100644 index 2dc0f531a..000000000 Binary files a/app/src/androidTest/assets/views-v21/CheckmarkView/unchecked.png and /dev/null differ diff --git a/app/src/androidTest/assets/views/CheckmarkView/checked.png b/app/src/androidTest/assets/views/CheckmarkView/checked.png index 7884c804f..69f650a0b 100644 Binary files a/app/src/androidTest/assets/views/CheckmarkView/checked.png and b/app/src/androidTest/assets/views/CheckmarkView/checked.png differ diff --git a/app/src/androidTest/assets/views/CheckmarkView/implicitly_checked.png b/app/src/androidTest/assets/views/CheckmarkView/implicitly_checked.png index 3096be180..992a9e5b2 100644 Binary files a/app/src/androidTest/assets/views/CheckmarkView/implicitly_checked.png and b/app/src/androidTest/assets/views/CheckmarkView/implicitly_checked.png differ diff --git a/app/src/androidTest/assets/views/CheckmarkView/large_size.png b/app/src/androidTest/assets/views/CheckmarkView/large_size.png index 79152fb18..8aa13d5e2 100644 Binary files a/app/src/androidTest/assets/views/CheckmarkView/large_size.png and b/app/src/androidTest/assets/views/CheckmarkView/large_size.png differ diff --git a/app/src/androidTest/assets/views/CheckmarkView/unchecked.png b/app/src/androidTest/assets/views/CheckmarkView/unchecked.png index b0d90c5c0..576d369ec 100644 Binary files a/app/src/androidTest/assets/views/CheckmarkView/unchecked.png and b/app/src/androidTest/assets/views/CheckmarkView/unchecked.png differ diff --git a/app/src/androidTest/assets/views/HabitFrequencyView/render.png b/app/src/androidTest/assets/views/HabitFrequencyView/render.png index 28c46f5a2..70ec73c84 100644 Binary files a/app/src/androidTest/assets/views/HabitFrequencyView/render.png and b/app/src/androidTest/assets/views/HabitFrequencyView/render.png differ diff --git a/app/src/androidTest/assets/views/HabitFrequencyView/renderDataOffset.png b/app/src/androidTest/assets/views/HabitFrequencyView/renderDataOffset.png index 1125e478a..66ac9f4a3 100644 Binary files a/app/src/androidTest/assets/views/HabitFrequencyView/renderDataOffset.png and b/app/src/androidTest/assets/views/HabitFrequencyView/renderDataOffset.png differ diff --git a/app/src/androidTest/assets/views/HabitFrequencyView/renderDifferentSize.png b/app/src/androidTest/assets/views/HabitFrequencyView/renderDifferentSize.png index cb5347b1c..7dd7a684c 100644 Binary files a/app/src/androidTest/assets/views/HabitFrequencyView/renderDifferentSize.png and b/app/src/androidTest/assets/views/HabitFrequencyView/renderDifferentSize.png differ diff --git a/app/src/androidTest/assets/views/HabitFrequencyView/renderTransparent.png b/app/src/androidTest/assets/views/HabitFrequencyView/renderTransparent.png index a0437b31f..70ec73c84 100644 Binary files a/app/src/androidTest/assets/views/HabitFrequencyView/renderTransparent.png and b/app/src/androidTest/assets/views/HabitFrequencyView/renderTransparent.png differ diff --git a/app/src/androidTest/assets/views/HabitHistoryView/render.png b/app/src/androidTest/assets/views/HabitHistoryView/render.png index fce176813..fabd5891e 100644 Binary files a/app/src/androidTest/assets/views/HabitHistoryView/render.png and b/app/src/androidTest/assets/views/HabitHistoryView/render.png differ diff --git a/app/src/androidTest/assets/views/HabitHistoryView/renderDataOffset.png b/app/src/androidTest/assets/views/HabitHistoryView/renderDataOffset.png index 679ad00d4..0fee4e308 100644 Binary files a/app/src/androidTest/assets/views/HabitHistoryView/renderDataOffset.png and b/app/src/androidTest/assets/views/HabitHistoryView/renderDataOffset.png differ diff --git a/app/src/androidTest/assets/views/HabitHistoryView/renderDifferentSize.png b/app/src/androidTest/assets/views/HabitHistoryView/renderDifferentSize.png index be64fb851..e2a08b0d7 100644 Binary files a/app/src/androidTest/assets/views/HabitHistoryView/renderDifferentSize.png and b/app/src/androidTest/assets/views/HabitHistoryView/renderDifferentSize.png differ diff --git a/app/src/androidTest/assets/views/HabitHistoryView/renderTransparent.png b/app/src/androidTest/assets/views/HabitHistoryView/renderTransparent.png index d52288da7..b7be74220 100644 Binary files a/app/src/androidTest/assets/views/HabitHistoryView/renderTransparent.png and b/app/src/androidTest/assets/views/HabitHistoryView/renderTransparent.png differ diff --git a/app/src/androidTest/assets/views/HabitScoreView/render.png b/app/src/androidTest/assets/views/HabitScoreView/render.png index f5c98549c..4f606e5e9 100644 Binary files a/app/src/androidTest/assets/views/HabitScoreView/render.png and b/app/src/androidTest/assets/views/HabitScoreView/render.png differ diff --git a/app/src/androidTest/assets/views/HabitScoreView/renderDataOffset.png b/app/src/androidTest/assets/views/HabitScoreView/renderDataOffset.png index c84c230a1..a6eab96e6 100644 Binary files a/app/src/androidTest/assets/views/HabitScoreView/renderDataOffset.png and b/app/src/androidTest/assets/views/HabitScoreView/renderDataOffset.png differ diff --git a/app/src/androidTest/assets/views/HabitScoreView/renderDifferentSize.png b/app/src/androidTest/assets/views/HabitScoreView/renderDifferentSize.png index 133068dc4..8eb052d73 100644 Binary files a/app/src/androidTest/assets/views/HabitScoreView/renderDifferentSize.png and b/app/src/androidTest/assets/views/HabitScoreView/renderDifferentSize.png differ diff --git a/app/src/androidTest/assets/views/HabitScoreView/renderMonthly.png b/app/src/androidTest/assets/views/HabitScoreView/renderMonthly.png index 83c31bcfe..75f74d515 100644 Binary files a/app/src/androidTest/assets/views/HabitScoreView/renderMonthly.png and b/app/src/androidTest/assets/views/HabitScoreView/renderMonthly.png differ diff --git a/app/src/androidTest/assets/views/HabitScoreView/renderTransparent.png b/app/src/androidTest/assets/views/HabitScoreView/renderTransparent.png index b6fd78857..0b7f70a7e 100644 Binary files a/app/src/androidTest/assets/views/HabitScoreView/renderTransparent.png and b/app/src/androidTest/assets/views/HabitScoreView/renderTransparent.png differ diff --git a/app/src/androidTest/assets/views/HabitScoreView/renderYearly.png b/app/src/androidTest/assets/views/HabitScoreView/renderYearly.png index 1e45b47d1..d186ceefc 100644 Binary files a/app/src/androidTest/assets/views/HabitScoreView/renderYearly.png and b/app/src/androidTest/assets/views/HabitScoreView/renderYearly.png differ diff --git a/app/src/androidTest/assets/views/HabitStreakView/render.png b/app/src/androidTest/assets/views/HabitStreakView/render.png index 365231bff..4bab05b52 100644 Binary files a/app/src/androidTest/assets/views/HabitStreakView/render.png and b/app/src/androidTest/assets/views/HabitStreakView/render.png differ diff --git a/app/src/androidTest/assets/views/HabitStreakView/renderSmallSize.png b/app/src/androidTest/assets/views/HabitStreakView/renderSmallSize.png index eaa0ca53e..00eb8daa6 100644 Binary files a/app/src/androidTest/assets/views/HabitStreakView/renderSmallSize.png and b/app/src/androidTest/assets/views/HabitStreakView/renderSmallSize.png differ diff --git a/app/src/androidTest/assets/views/HabitStreakView/renderTransparent.png b/app/src/androidTest/assets/views/HabitStreakView/renderTransparent.png index 408a215d3..4bab05b52 100644 Binary files a/app/src/androidTest/assets/views/HabitStreakView/renderTransparent.png and b/app/src/androidTest/assets/views/HabitStreakView/renderTransparent.png differ diff --git a/app/src/androidTest/assets/views/RingView/render.png b/app/src/androidTest/assets/views/RingView/render.png index c77355c7c..0bf125778 100644 Binary files a/app/src/androidTest/assets/views/RingView/render.png and b/app/src/androidTest/assets/views/RingView/render.png differ diff --git a/app/src/androidTest/assets/views/RingView/renderDifferentParams.png b/app/src/androidTest/assets/views/RingView/renderDifferentParams.png index 02dfdf803..87874ba92 100644 Binary files a/app/src/androidTest/assets/views/RingView/renderDifferentParams.png and b/app/src/androidTest/assets/views/RingView/renderDifferentParams.png differ diff --git a/app/src/androidTest/assets/views/RingView/renderLongLabel.png b/app/src/androidTest/assets/views/RingView/renderLongLabel.png deleted file mode 100644 index 48b9998ab..000000000 Binary files a/app/src/androidTest/assets/views/RingView/renderLongLabel.png and /dev/null differ diff --git a/app/src/androidTest/java/org/isoron/uhabits/BaseTest.java b/app/src/androidTest/java/org/isoron/uhabits/BaseTest.java index a73f5cd9d..5515b5121 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/BaseTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/BaseTest.java @@ -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); } diff --git a/app/src/androidTest/java/org/isoron/uhabits/ui/MainActivityActions.java b/app/src/androidTest/java/org/isoron/uhabits/ui/MainActivityActions.java index 40699aa35..a933cd3a9 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/ui/MainActivityActions.java +++ b/app/src/androidTest/java/org/isoron/uhabits/ui/MainActivityActions.java @@ -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 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())); + } } diff --git a/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java b/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java index 0ea767757..89751372f 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java @@ -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)); } } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/HabitFixtures.java b/app/src/androidTest/java/org/isoron/uhabits/unit/HabitFixtures.java index 22a425266..09298bb08 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/HabitFixtures.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/HabitFixtures.java @@ -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; diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ChangeHabitColorCommandTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ChangeHabitColorCommandTest.java index 54fd200fa..cc6c886d1 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ChangeHabitColorCommandTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ChangeHabitColorCommandTest.java @@ -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)); } } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/models/HabitTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/models/HabitTest.java index a71a0bac3..f09664031 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/models/HabitTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/models/HabitTest.java @@ -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; diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/CheckmarkViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/CheckmarkWidgetViewTest.java similarity index 88% rename from app/src/androidTest/java/org/isoron/uhabits/unit/views/CheckmarkViewTest.java rename to app/src/androidTest/java/org/isoron/uhabits/unit/views/CheckmarkWidgetViewTest.java index 637edb5ec..874c8666f 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/CheckmarkViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/CheckmarkWidgetViewTest.java @@ -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); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java index 6748dc895..ea8f0f54f 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java @@ -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(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitScoreViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitScoreViewTest.java index 89ccdcdb6..a7f1228b9 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitScoreViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitScoreViewTest.java @@ -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"); } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/NumberViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/NumberViewTest.java index 93269befa..a8f371af6 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/NumberViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/NumberViewTest.java @@ -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,10 +68,10 @@ 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); assertRenders(view, "NumberView/renderDifferentParams.png"); } -} +} \ No newline at end of file diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/RingViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/RingViewTest.java index d7fcb48d6..9f831ba49 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/RingViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/RingViewTest.java @@ -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"); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java index d5e69fa60..d87c3b9fc 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java @@ -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()); } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 23c17a53a..3bac419f5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,8 +21,8 @@ + android:versionCode="16" + android:versionName="1.5.0"> @@ -34,6 +34,8 @@ android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="18" /> + + + android:label="@string/title_activity_show_habit"> @@ -68,8 +69,7 @@ + android:label="@string/settings"> @@ -90,8 +90,10 @@ + android:label="@string/about"> + - + + + + + diff --git a/app/src/main/assets/migrations/14.sql b/app/src/main/assets/migrations/14.sql new file mode 100644 index 000000000..e267afc2c --- /dev/null +++ b/app/src/main/assets/migrations/14.sql @@ -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; \ No newline at end of file diff --git a/app/src/main/java/com/android/colorpicker/ColorPickerDialog.java b/app/src/main/java/com/android/colorpicker/ColorPickerDialog.java index a47e39d43..114c5fed5 100644 --- a/app/src/main/java/com/android/colorpicker/ColorPickerDialog.java +++ b/app/src/main/java/com/android/colorpicker/ColorPickerDialog.java @@ -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; diff --git a/app/src/main/java/com/android/datetimepicker/time/TimePickerDialog.java b/app/src/main/java/com/android/datetimepicker/time/TimePickerDialog.java index e365005f4..f36ad2780 100644 --- a/app/src/main/java/com/android/datetimepicker/time/TimePickerDialog.java +++ b/app/src/main/java/com/android/datetimepicker/time/TimePickerDialog.java @@ -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"; diff --git a/app/src/main/java/org/isoron/uhabits/AboutActivity.java b/app/src/main/java/org/isoron/uhabits/AboutActivity.java index 3f84528e1..279a96d45 100644 --- a/app/src/main/java/org/isoron/uhabits/AboutActivity.java +++ b/app/src/main/java/org/isoron/uhabits/AboutActivity.java @@ -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); diff --git a/app/src/main/java/org/isoron/uhabits/BaseActivity.java b/app/src/main/java/org/isoron/uhabits/BaseActivity.java index 4c8b2b75a..1444f228a 100644 --- a/app/src/main/java/org/isoron/uhabits/BaseActivity.java +++ b/app/src/main/java/org/isoron/uhabits/BaseActivity.java @@ -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); + } } diff --git a/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java b/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java index 057aedfba..e1a45aa5e 100644 --- a/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java +++ b/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java @@ -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); + } } diff --git a/app/src/main/java/org/isoron/uhabits/IntroActivity.java b/app/src/main/java/org/isoron/uhabits/IntroActivity.java index e7685aa87..e298d1c63 100644 --- a/app/src/main/java/org/isoron/uhabits/IntroActivity.java +++ b/app/src/main/java/org/isoron/uhabits/IntroActivity.java @@ -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"))); diff --git a/app/src/main/java/org/isoron/uhabits/MainActivity.java b/app/src/main/java/org/isoron/uhabits/MainActivity.java index 760ad134e..a8dad23b8 100644 --- a/app/src/main/java/org/isoron/uhabits/MainActivity.java +++ b/app/src/main/java/org/isoron/uhabits/MainActivity.java @@ -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); diff --git a/app/src/main/java/org/isoron/uhabits/SettingsActivity.java b/app/src/main/java/org/isoron/uhabits/SettingsActivity.java index 9e43b1bba..579e7a7a6 100644 --- a/app/src/main/java/org/isoron/uhabits/SettingsActivity.java +++ b/app/src/main/java/org/isoron/uhabits/SettingsActivity.java @@ -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); } } diff --git a/app/src/main/java/org/isoron/uhabits/ShowHabitActivity.java b/app/src/main/java/org/isoron/uhabits/ShowHabitActivity.java index 4fa707bd9..8be54b586 100644 --- a/app/src/main/java/org/isoron/uhabits/ShowHabitActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ShowHabitActivity.java @@ -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() diff --git a/app/src/main/java/org/isoron/uhabits/fragments/EditHabitFragment.java b/app/src/main/java/org/isoron/uhabits/dialogs/EditHabitDialogFragment.java similarity index 92% rename from app/src/main/java/org/isoron/uhabits/fragments/EditHabitFragment.java rename to app/src/main/java/org/isoron/uhabits/dialogs/EditHabitDialogFragment.java index 3f1a117c9..1baa97cbe 100644 --- a/app/src/main/java/org/isoron/uhabits/fragments/EditHabitFragment.java +++ b/app/src/main/java/org/isoron/uhabits/dialogs/EditHabitDialogFragment.java @@ -17,13 +17,13 @@ * with this program. If not, see . */ -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"); diff --git a/app/src/main/java/org/isoron/uhabits/dialogs/HistoryEditorDialog.java b/app/src/main/java/org/isoron/uhabits/dialogs/HistoryEditorDialog.java index 7654fb0ed..cd95e9212 100644 --- a/app/src/main/java/org/isoron/uhabits/dialogs/HistoryEditorDialog.java +++ b/app/src/main/java/org/isoron/uhabits/dialogs/HistoryEditorDialog.java @@ -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); } diff --git a/app/src/main/java/org/isoron/uhabits/dialogs/WeekdayPickerDialog.java b/app/src/main/java/org/isoron/uhabits/dialogs/WeekdayPickerDialog.java index f2d54f5a2..5297b80eb 100644 --- a/app/src/main/java/org/isoron/uhabits/dialogs/WeekdayPickerDialog.java +++ b/app/src/main/java/org/isoron/uhabits/dialogs/WeekdayPickerDialog.java @@ -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 { diff --git a/app/src/main/java/org/isoron/uhabits/fragments/HabitListAdapter.java b/app/src/main/java/org/isoron/uhabits/fragments/HabitListAdapter.java index dfbd959f2..e01ceef84 100644 --- a/app/src/main/java/org/isoron/uhabits/fragments/HabitListAdapter.java +++ b/app/src/main/java/org/isoron/uhabits/fragments/HabitListAdapter.java @@ -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; } diff --git a/app/src/main/java/org/isoron/uhabits/fragments/HabitSelectionCallback.java b/app/src/main/java/org/isoron/uhabits/fragments/HabitSelectionCallback.java index 9d124a691..bf0973aaf 100644 --- a/app/src/main/java/org/isoron/uhabits/fragments/HabitSelectionCallback.java +++ b/app/src/main/java/org/isoron/uhabits/fragments/HabitSelectionCallback.java @@ -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; } diff --git a/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java b/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java index 1e9983833..1319a69b1 100644 --- a/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java +++ b/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java @@ -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; diff --git a/app/src/main/java/org/isoron/uhabits/fragments/SettingsFragment.java b/app/src/main/java/org/isoron/uhabits/fragments/SettingsFragment.java index 0c4491b34..838527589 100644 --- a/app/src/main/java/org/isoron/uhabits/fragments/SettingsFragment.java +++ b/app/src/main/java/org/isoron/uhabits/fragments/SettingsFragment.java @@ -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); + } } \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java b/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java index 1afb5dabd..f95c83881 100644 --- a/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java +++ b/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java @@ -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; } diff --git a/app/src/main/java/org/isoron/uhabits/helpers/ColorHelper.java b/app/src/main/java/org/isoron/uhabits/helpers/ColorHelper.java index e58a73745..d64f4ac3d 100644 --- a/app/src/main/java/org/isoron/uhabits/helpers/ColorHelper.java +++ b/app/src/main/java/org/isoron/uhabits/helpers/ColorHelper.java @@ -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]; diff --git a/app/src/main/java/org/isoron/uhabits/helpers/DateHelper.java b/app/src/main/java/org/isoron/uhabits/helpers/DateHelper.java index b2c300453..4d0c02c32 100644 --- a/app/src/main/java/org/isoron/uhabits/helpers/DateHelper.java +++ b/app/src/main/java/org/isoron/uhabits/helpers/DateHelper.java @@ -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(); diff --git a/app/src/main/java/org/isoron/uhabits/helpers/ListHabitsHelper.java b/app/src/main/java/org/isoron/uhabits/helpers/ListHabitsHelper.java index 0cd6567fd..91a3a4aff 100644 --- a/app/src/main/java/org/isoron/uhabits/helpers/ListHabitsHelper.java +++ b/app/src/main/java/org/isoron/uhabits/helpers/ListHabitsHelper.java @@ -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; } } diff --git a/app/src/main/java/org/isoron/uhabits/helpers/ReminderHelper.java b/app/src/main/java/org/isoron/uhabits/helpers/ReminderHelper.java index 876c229fb..c575ffcf4 100644 --- a/app/src/main/java/org/isoron/uhabits/helpers/ReminderHelper.java +++ b/app/src/main/java/org/isoron/uhabits/helpers/ReminderHelper.java @@ -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; + } } diff --git a/app/src/main/java/org/isoron/uhabits/helpers/UIHelper.java b/app/src/main/java/org/isoron/uhabits/helpers/UIHelper.java index d545e8a51..7e6c8c968 100644 --- a/app/src/main/java/org/isoron/uhabits/helpers/UIHelper.java +++ b/app/src/main/java/org/isoron/uhabits/helpers/UIHelper.java @@ -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; + } } diff --git a/app/src/main/java/org/isoron/uhabits/models/Habit.java b/app/src/main/java/org/isoron/uhabits/models/Habit.java index a3c51e3fe..1fdb982ad 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Habit.java +++ b/app/src/main/java/org/isoron/uhabits/models/Habit.java @@ -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); } diff --git a/app/src/main/java/org/isoron/uhabits/views/CheckmarkView.java b/app/src/main/java/org/isoron/uhabits/views/CheckmarkView.java deleted file mode 100644 index 8819e8e37..000000000 --- a/app/src/main/java/org/isoron/uhabits/views/CheckmarkView.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (C) 2016 Álinson Santos Xavier - * - * 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 . - */ - -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); - } -} diff --git a/app/src/main/java/org/isoron/uhabits/views/CheckmarkWidgetView.java b/app/src/main/java/org/isoron/uhabits/views/CheckmarkWidgetView.java new file mode 100644 index 000000000..01cf98a19 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/views/CheckmarkWidgetView.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2016 Álinson Santos Xavier + * + * 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 . + */ + +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; + } +} diff --git a/app/src/main/java/org/isoron/uhabits/views/GraphWidgetView.java b/app/src/main/java/org/isoron/uhabits/views/GraphWidgetView.java new file mode 100644 index 000000000..bb142ca7d --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/views/GraphWidgetView.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2016 Álinson Santos Xavier + * + * 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 . + */ + +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; + } +} diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitFrequencyView.java b/app/src/main/java/org/isoron/uhabits/views/HabitFrequencyView.java index 55abb3959..7890757dd 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitFrequencyView.java +++ b/app/src/main/java/org/isoron/uhabits/views/HabitFrequencyView.java @@ -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 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); diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java b/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java index 0958bf275..96899585e 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java +++ b/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java @@ -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); } } diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java b/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java index 4e1ae4c7c..6258e7b13 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java +++ b/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java @@ -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); diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java b/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java index 37d2e16c9..74fc6c518 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java +++ b/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java @@ -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 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); diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitWidgetView.java b/app/src/main/java/org/isoron/uhabits/views/HabitWidgetView.java new file mode 100644 index 000000000..bab859aaa --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/views/HabitWidgetView.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2016 Álinson Santos Xavier + * + * 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 . + */ + +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; + } +} diff --git a/app/src/main/java/org/isoron/uhabits/views/NumberView.java b/app/src/main/java/org/isoron/uhabits/views/NumberView.java index 2d5ab4402..b5412a7a3 100644 --- a/app/src/main/java/org/isoron/uhabits/views/NumberView.java +++ b/app/src/main/java/org/isoron/uhabits/views/NumberView.java @@ -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(); } diff --git a/app/src/main/java/org/isoron/uhabits/views/RepetitionCountView.java b/app/src/main/java/org/isoron/uhabits/views/RepetitionCountView.java index e0f80aa90..ee850ab2f 100644 --- a/app/src/main/java/org/isoron/uhabits/views/RepetitionCountView.java +++ b/app/src/main/java/org/isoron/uhabits/views/RepetitionCountView.java @@ -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)); } } diff --git a/app/src/main/java/org/isoron/uhabits/views/RingView.java b/app/src/main/java/org/isoron/uhabits/views/RingView.java index 0d63ff958..de7ee345a 100644 --- a/app/src/main/java/org/isoron/uhabits/views/RingView.java +++ b/app/src/main/java/org/isoron/uhabits/views/RingView.java @@ -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; } } diff --git a/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java index 558a5da41..6490084f7 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java @@ -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 }; } } diff --git a/app/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidgetProvider.java index bd04e8eb0..cc392ba7e 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidgetProvider.java @@ -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; } diff --git a/app/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.java index 2cac07c06..2fdbedb71 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.java @@ -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; } } diff --git a/app/src/main/java/org/isoron/uhabits/widgets/HistoryWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/HistoryWidgetProvider.java index e02cb608c..bb8be7e25 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/HistoryWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/HistoryWidgetProvider.java @@ -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; } } diff --git a/app/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.java index 456491efe..2608887b4 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.java @@ -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; } } diff --git a/app/src/main/java/org/isoron/uhabits/widgets/StreakWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/StreakWidgetProvider.java index f5d81d4f6..f0455d00a 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/StreakWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/StreakWidgetProvider.java @@ -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; } } diff --git a/app/src/main/res/drawable-hdpi/apptheme_textfield_activated_holo_light.9.png b/app/src/main/res/drawable-hdpi/apptheme_textfield_activated_holo_light.9.png deleted file mode 100644 index 56c957fbc..000000000 Binary files a/app/src/main/res/drawable-hdpi/apptheme_textfield_activated_holo_light.9.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/apptheme_textfield_default_holo_light.9.png b/app/src/main/res/drawable-hdpi/apptheme_textfield_default_holo_light.9.png deleted file mode 100644 index d5ac69c23..000000000 Binary files a/app/src/main/res/drawable-hdpi/apptheme_textfield_default_holo_light.9.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/apptheme_textfield_disabled_focused_holo_light.9.png b/app/src/main/res/drawable-hdpi/apptheme_textfield_disabled_focused_holo_light.9.png deleted file mode 100644 index b70db4e10..000000000 Binary files a/app/src/main/res/drawable-hdpi/apptheme_textfield_disabled_focused_holo_light.9.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/apptheme_textfield_disabled_holo_light.9.png b/app/src/main/res/drawable-hdpi/apptheme_textfield_disabled_holo_light.9.png deleted file mode 100644 index a77d66d99..000000000 Binary files a/app/src/main/res/drawable-hdpi/apptheme_textfield_disabled_holo_light.9.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/apptheme_textfield_focused_holo_light.9.png b/app/src/main/res/drawable-hdpi/apptheme_textfield_focused_holo_light.9.png deleted file mode 100644 index d7e90cb12..000000000 Binary files a/app/src/main/res/drawable-hdpi/apptheme_textfield_focused_holo_light.9.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_add.png b/app/src/main/res/drawable-hdpi/ic_action_add.png deleted file mode 100644 index aa7cf4f2c..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_action_add.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_add_dark.png b/app/src/main/res/drawable-hdpi/ic_action_add_dark.png index 65fd03cb1..fb70e738f 100644 Binary files a/app/src/main/res/drawable-hdpi/ic_action_add_dark.png and b/app/src/main/res/drawable-hdpi/ic_action_add_dark.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_add_light.png b/app/src/main/res/drawable-hdpi/ic_action_add_light.png new file mode 100644 index 000000000..641e14989 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_add_light.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_edit_dark.png b/app/src/main/res/drawable-hdpi/ic_action_edit_dark.png index 4112c9047..cd5af349d 100644 Binary files a/app/src/main/res/drawable-hdpi/ic_action_edit_dark.png and b/app/src/main/res/drawable-hdpi/ic_action_edit_dark.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_edit_light.png b/app/src/main/res/drawable-hdpi/ic_action_edit_light.png index 5c8bcf881..70cb75e1f 100644 Binary files a/app/src/main/res/drawable-hdpi/ic_action_edit_light.png and b/app/src/main/res/drawable-hdpi/ic_action_edit_light.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_alarm_black.png b/app/src/main/res/drawable-hdpi/ic_alarm_black.png new file mode 100644 index 000000000..907491104 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_alarm_black.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_alarm_white.png b/app/src/main/res/drawable-hdpi/ic_alarm_white.png new file mode 100644 index 000000000..6dc94d2b5 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_alarm_white.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_repeat_black.png b/app/src/main/res/drawable-hdpi/ic_repeat_black.png new file mode 100644 index 000000000..d8b42bbe4 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_repeat_black.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_repeat_white.png b/app/src/main/res/drawable-hdpi/ic_repeat_white.png new file mode 100644 index 000000000..81c5be793 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_repeat_white.png differ diff --git a/app/src/main/res/drawable-mdpi/apptheme_textfield_activated_holo_light.9.png b/app/src/main/res/drawable-mdpi/apptheme_textfield_activated_holo_light.9.png deleted file mode 100644 index d7118fbab..000000000 Binary files a/app/src/main/res/drawable-mdpi/apptheme_textfield_activated_holo_light.9.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/apptheme_textfield_default_holo_light.9.png b/app/src/main/res/drawable-mdpi/apptheme_textfield_default_holo_light.9.png deleted file mode 100644 index 47302c93e..000000000 Binary files a/app/src/main/res/drawable-mdpi/apptheme_textfield_default_holo_light.9.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/apptheme_textfield_disabled_focused_holo_light.9.png b/app/src/main/res/drawable-mdpi/apptheme_textfield_disabled_focused_holo_light.9.png deleted file mode 100644 index 0d5ea839d..000000000 Binary files a/app/src/main/res/drawable-mdpi/apptheme_textfield_disabled_focused_holo_light.9.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/apptheme_textfield_disabled_holo_light.9.png b/app/src/main/res/drawable-mdpi/apptheme_textfield_disabled_holo_light.9.png deleted file mode 100644 index ea6d2f74b..000000000 Binary files a/app/src/main/res/drawable-mdpi/apptheme_textfield_disabled_holo_light.9.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/apptheme_textfield_focused_holo_light.9.png b/app/src/main/res/drawable-mdpi/apptheme_textfield_focused_holo_light.9.png deleted file mode 100644 index a3e40613c..000000000 Binary files a/app/src/main/res/drawable-mdpi/apptheme_textfield_focused_holo_light.9.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_add.png b/app/src/main/res/drawable-mdpi/ic_action_add.png deleted file mode 100644 index 99b189a0c..000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_action_add.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_add_dark.png b/app/src/main/res/drawable-mdpi/ic_action_add_dark.png index e6b36b2b0..278f816ac 100644 Binary files a/app/src/main/res/drawable-mdpi/ic_action_add_dark.png and b/app/src/main/res/drawable-mdpi/ic_action_add_dark.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_add_light.png b/app/src/main/res/drawable-mdpi/ic_action_add_light.png new file mode 100644 index 000000000..9a73578d5 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_add_light.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_edit_dark.png b/app/src/main/res/drawable-mdpi/ic_action_edit_dark.png index a5ab91338..ee59997a9 100644 Binary files a/app/src/main/res/drawable-mdpi/ic_action_edit_dark.png and b/app/src/main/res/drawable-mdpi/ic_action_edit_dark.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_edit_light.png b/app/src/main/res/drawable-mdpi/ic_action_edit_light.png index c6367decf..7807f449f 100644 Binary files a/app/src/main/res/drawable-mdpi/ic_action_edit_light.png and b/app/src/main/res/drawable-mdpi/ic_action_edit_light.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_alarm_black.png b/app/src/main/res/drawable-mdpi/ic_alarm_black.png new file mode 100644 index 000000000..ed2f90aee Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_alarm_black.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_alarm_white.png b/app/src/main/res/drawable-mdpi/ic_alarm_white.png new file mode 100644 index 000000000..ca4f94707 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_alarm_white.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_repeat_black.png b/app/src/main/res/drawable-mdpi/ic_repeat_black.png new file mode 100644 index 000000000..c00a24ebf Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_repeat_black.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_repeat_white.png b/app/src/main/res/drawable-mdpi/ic_repeat_white.png new file mode 100644 index 000000000..b1c2e04ab Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_repeat_white.png differ diff --git a/app/src/main/res/drawable-nodpi/widget_preview_checkmark.png b/app/src/main/res/drawable-nodpi/widget_preview_checkmark.png new file mode 100644 index 000000000..0cbac62e6 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/widget_preview_checkmark.png differ diff --git a/app/src/main/res/drawable-nodpi/widget_preview_frequency.png b/app/src/main/res/drawable-nodpi/widget_preview_frequency.png new file mode 100644 index 000000000..561afb45c Binary files /dev/null and b/app/src/main/res/drawable-nodpi/widget_preview_frequency.png differ diff --git a/app/src/main/res/drawable-nodpi/widget_preview_history.png b/app/src/main/res/drawable-nodpi/widget_preview_history.png new file mode 100644 index 000000000..95297b6ee Binary files /dev/null and b/app/src/main/res/drawable-nodpi/widget_preview_history.png differ diff --git a/app/src/main/res/drawable-nodpi/widget_preview_score.png b/app/src/main/res/drawable-nodpi/widget_preview_score.png new file mode 100644 index 000000000..8d9b3baa5 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/widget_preview_score.png differ diff --git a/app/src/main/res/drawable-nodpi/widget_preview_streaks.png b/app/src/main/res/drawable-nodpi/widget_preview_streaks.png new file mode 100644 index 000000000..b1fe031e6 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/widget_preview_streaks.png differ diff --git a/app/src/main/res/drawable-v21/ripple_white.xml b/app/src/main/res/drawable-v21/ripple.xml similarity index 93% rename from app/src/main/res/drawable-v21/ripple_white.xml rename to app/src/main/res/drawable-v21/ripple.xml index 5721b50cb..18e2c0fb9 100644 --- a/app/src/main/res/drawable-v21/ripple_white.xml +++ b/app/src/main/res/drawable-v21/ripple.xml @@ -20,5 +20,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/selected_box.xml b/app/src/main/res/drawable-v21/selected_box.xml new file mode 100644 index 000000000..9c11643e2 --- /dev/null +++ b/app/src/main/res/drawable-v21/selected_box.xml @@ -0,0 +1,24 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/widget_button_background.xml b/app/src/main/res/drawable-v21/widget_button_background.xml new file mode 100644 index 000000000..fdeb02bcd --- /dev/null +++ b/app/src/main/res/drawable-v21/widget_button_background.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable-xhdpi/apptheme_textfield_activated_holo_light.9.png b/app/src/main/res/drawable-xhdpi/apptheme_textfield_activated_holo_light.9.png deleted file mode 100644 index cf064ec12..000000000 Binary files a/app/src/main/res/drawable-xhdpi/apptheme_textfield_activated_holo_light.9.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/apptheme_textfield_default_holo_light.9.png b/app/src/main/res/drawable-xhdpi/apptheme_textfield_default_holo_light.9.png deleted file mode 100644 index f0ad55a66..000000000 Binary files a/app/src/main/res/drawable-xhdpi/apptheme_textfield_default_holo_light.9.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/apptheme_textfield_disabled_focused_holo_light.9.png b/app/src/main/res/drawable-xhdpi/apptheme_textfield_disabled_focused_holo_light.9.png deleted file mode 100644 index 40a28cf70..000000000 Binary files a/app/src/main/res/drawable-xhdpi/apptheme_textfield_disabled_focused_holo_light.9.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/apptheme_textfield_disabled_holo_light.9.png b/app/src/main/res/drawable-xhdpi/apptheme_textfield_disabled_holo_light.9.png deleted file mode 100644 index 4ffdd869e..000000000 Binary files a/app/src/main/res/drawable-xhdpi/apptheme_textfield_disabled_holo_light.9.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/apptheme_textfield_focused_holo_light.9.png b/app/src/main/res/drawable-xhdpi/apptheme_textfield_focused_holo_light.9.png deleted file mode 100644 index 9bb556e7a..000000000 Binary files a/app/src/main/res/drawable-xhdpi/apptheme_textfield_focused_holo_light.9.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_add.png b/app/src/main/res/drawable-xhdpi/ic_action_add.png deleted file mode 100644 index 9d6af0423..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_action_add.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_add_dark.png b/app/src/main/res/drawable-xhdpi/ic_action_add_dark.png index 25f4060a9..454cd4f44 100644 Binary files a/app/src/main/res/drawable-xhdpi/ic_action_add_dark.png and b/app/src/main/res/drawable-xhdpi/ic_action_add_dark.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_add_light.png b/app/src/main/res/drawable-xhdpi/ic_action_add_light.png new file mode 100644 index 000000000..efce32f5d Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_add_light.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_edit_dark.png b/app/src/main/res/drawable-xhdpi/ic_action_edit_dark.png index cb2395307..ac1f3fa8f 100644 Binary files a/app/src/main/res/drawable-xhdpi/ic_action_edit_dark.png and b/app/src/main/res/drawable-xhdpi/ic_action_edit_dark.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_edit_light.png b/app/src/main/res/drawable-xhdpi/ic_action_edit_light.png index 955a22d51..472959624 100644 Binary files a/app/src/main/res/drawable-xhdpi/ic_action_edit_light.png and b/app/src/main/res/drawable-xhdpi/ic_action_edit_light.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_alarm_black.png b/app/src/main/res/drawable-xhdpi/ic_alarm_black.png new file mode 100644 index 000000000..e37eab0e4 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_alarm_black.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_alarm_white.png b/app/src/main/res/drawable-xhdpi/ic_alarm_white.png new file mode 100644 index 000000000..280e09df3 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_alarm_white.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_repeat_black.png b/app/src/main/res/drawable-xhdpi/ic_repeat_black.png new file mode 100644 index 000000000..75ecb0462 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_repeat_black.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_repeat_white.png b/app/src/main/res/drawable-xhdpi/ic_repeat_white.png new file mode 100644 index 000000000..ad8b8c0df Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_repeat_white.png differ diff --git a/app/src/main/res/drawable-xxhdpi/apptheme_textfield_activated_holo_light.9.png b/app/src/main/res/drawable-xxhdpi/apptheme_textfield_activated_holo_light.9.png deleted file mode 100644 index bddef0f4f..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/apptheme_textfield_activated_holo_light.9.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/apptheme_textfield_default_holo_light.9.png b/app/src/main/res/drawable-xxhdpi/apptheme_textfield_default_holo_light.9.png deleted file mode 100644 index 6db1dd0aa..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/apptheme_textfield_default_holo_light.9.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/apptheme_textfield_disabled_focused_holo_light.9.png b/app/src/main/res/drawable-xxhdpi/apptheme_textfield_disabled_focused_holo_light.9.png deleted file mode 100644 index d157d7d60..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/apptheme_textfield_disabled_focused_holo_light.9.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/apptheme_textfield_disabled_holo_light.9.png b/app/src/main/res/drawable-xxhdpi/apptheme_textfield_disabled_holo_light.9.png deleted file mode 100644 index c91f7da91..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/apptheme_textfield_disabled_holo_light.9.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/apptheme_textfield_focused_holo_light.9.png b/app/src/main/res/drawable-xxhdpi/apptheme_textfield_focused_holo_light.9.png deleted file mode 100644 index 6c5c67aad..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/apptheme_textfield_focused_holo_light.9.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_add.png b/app/src/main/res/drawable-xxhdpi/ic_action_add.png deleted file mode 100644 index 3ec4d3ae8..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_action_add.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_add_dark.png b/app/src/main/res/drawable-xxhdpi/ic_action_add_dark.png index b38238286..6c8df63b2 100644 Binary files a/app/src/main/res/drawable-xxhdpi/ic_action_add_dark.png and b/app/src/main/res/drawable-xxhdpi/ic_action_add_dark.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_add_light.png b/app/src/main/res/drawable-xxhdpi/ic_action_add_light.png new file mode 100644 index 000000000..96c3ec67b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_add_light.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_edit_dark.png b/app/src/main/res/drawable-xxhdpi/ic_action_edit_dark.png index 1f0c3c081..a1647d879 100644 Binary files a/app/src/main/res/drawable-xxhdpi/ic_action_edit_dark.png and b/app/src/main/res/drawable-xxhdpi/ic_action_edit_dark.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_edit_light.png b/app/src/main/res/drawable-xxhdpi/ic_action_edit_light.png index e2a84d81f..f8e12dda3 100644 Binary files a/app/src/main/res/drawable-xxhdpi/ic_action_edit_light.png and b/app/src/main/res/drawable-xxhdpi/ic_action_edit_light.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_alarm_black.png b/app/src/main/res/drawable-xxhdpi/ic_alarm_black.png new file mode 100644 index 000000000..eb1d08d7a Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_alarm_black.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_alarm_white.png b/app/src/main/res/drawable-xxhdpi/ic_alarm_white.png new file mode 100644 index 000000000..75e5a46ee Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_alarm_white.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_repeat_black.png b/app/src/main/res/drawable-xxhdpi/ic_repeat_black.png new file mode 100644 index 000000000..965a319ff Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_repeat_black.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_repeat_white.png b/app/src/main/res/drawable-xxhdpi/ic_repeat_white.png new file mode 100644 index 000000000..5de7a2951 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_repeat_white.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_add_light.png b/app/src/main/res/drawable-xxxhdpi/ic_action_add_light.png new file mode 100644 index 000000000..3cb10924a Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_action_add_light.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_alarm_black.png b/app/src/main/res/drawable-xxxhdpi/ic_alarm_black.png new file mode 100644 index 000000000..04b6c71d7 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_alarm_black.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_alarm_white.png b/app/src/main/res/drawable-xxxhdpi/ic_alarm_white.png new file mode 100644 index 000000000..aa8102fd2 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_alarm_white.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_repeat_black.png b/app/src/main/res/drawable-xxxhdpi/ic_repeat_black.png new file mode 100644 index 000000000..fd4d0c028 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_repeat_black.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_repeat_white.png b/app/src/main/res/drawable-xxxhdpi/ic_repeat_white.png new file mode 100644 index 000000000..c7f3072ee Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_repeat_white.png differ diff --git a/app/src/main/res/drawable/apptheme_edit_text_holo_light.xml b/app/src/main/res/drawable/apptheme_edit_text_holo_light.xml deleted file mode 100644 index 26e8fbe9b..000000000 --- a/app/src/main/res/drawable/apptheme_edit_text_holo_light.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/card_amoled_background.xml b/app/src/main/res/drawable/card_amoled_background.xml new file mode 100644 index 000000000..d46bbb1e5 --- /dev/null +++ b/app/src/main/res/drawable/card_amoled_background.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/card_dark_background.xml b/app/src/main/res/drawable/card_dark_background.xml new file mode 100644 index 000000000..7bf94a482 --- /dev/null +++ b/app/src/main/res/drawable/card_dark_background.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/card_background.xml b/app/src/main/res/drawable/card_light_background.xml similarity index 100% rename from app/src/main/res/drawable/card_background.xml rename to app/src/main/res/drawable/card_light_background.xml diff --git a/app/src/main/res/drawable/habits_list_header_amoled_background.xml b/app/src/main/res/drawable/habits_list_header_amoled_background.xml new file mode 100644 index 000000000..b65fb29c3 --- /dev/null +++ b/app/src/main/res/drawable/habits_list_header_amoled_background.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/habits_list_header_dark_background.xml b/app/src/main/res/drawable/habits_list_header_dark_background.xml new file mode 100644 index 000000000..88989c254 --- /dev/null +++ b/app/src/main/res/drawable/habits_list_header_dark_background.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/habits_list_header_background.xml b/app/src/main/res/drawable/habits_list_header_light_background.xml similarity index 77% rename from app/src/main/res/drawable/habits_list_header_background.xml rename to app/src/main/res/drawable/habits_list_header_light_background.xml index 83385eaa5..66413f277 100644 --- a/app/src/main/res/drawable/habits_list_header_background.xml +++ b/app/src/main/res/drawable/habits_list_header_light_background.xml @@ -20,7 +20,7 @@ - + - - - - - - + diff --git a/app/src/main/res/drawable/intro_icon_2.png b/app/src/main/res/drawable/intro_icon_2.png index 8955a7d8a..7daa79b2a 100644 Binary files a/app/src/main/res/drawable/intro_icon_2.png and b/app/src/main/res/drawable/intro_icon_2.png differ diff --git a/app/src/main/res/drawable/intro_icon_3.png b/app/src/main/res/drawable/intro_icon_3.png deleted file mode 100644 index 8372ea904..000000000 Binary files a/app/src/main/res/drawable/intro_icon_3.png and /dev/null differ diff --git a/app/src/main/res/drawable/selected_box_amoled.xml b/app/src/main/res/drawable/selected_box_amoled.xml new file mode 100644 index 000000000..a83732fd0 --- /dev/null +++ b/app/src/main/res/drawable/selected_box_amoled.xml @@ -0,0 +1,24 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selected_box_dark.xml b/app/src/main/res/drawable/selected_box_dark.xml new file mode 100644 index 000000000..ca0fb00ab --- /dev/null +++ b/app/src/main/res/drawable/selected_box_dark.xml @@ -0,0 +1,24 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selected_box.xml b/app/src/main/res/drawable/selected_box_light.xml similarity index 100% rename from app/src/main/res/drawable/selected_box.xml rename to app/src/main/res/drawable/selected_box_light.xml diff --git a/app/src/main/res/menu-v21/list_habits_options.xml b/app/src/main/res/drawable/toolbar_shadow.xml similarity index 69% rename from app/src/main/res/menu-v21/list_habits_options.xml rename to app/src/main/res/drawable/toolbar_shadow.xml index eac148ffb..d408e202f 100644 --- a/app/src/main/res/menu-v21/list_habits_options.xml +++ b/app/src/main/res/drawable/toolbar_shadow.xml @@ -1,3 +1,4 @@ + - - - + - + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/widget_button_background.xml b/app/src/main/res/drawable/widget_button_background.xml new file mode 100644 index 000000000..a50eaa160 --- /dev/null +++ b/app/src/main/res/drawable/widget_button_background.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/widget_preview_checkmark.png b/app/src/main/res/drawable/widget_preview_checkmark.png deleted file mode 100644 index ceb7d6541..000000000 Binary files a/app/src/main/res/drawable/widget_preview_checkmark.png and /dev/null differ diff --git a/app/src/main/res/drawable/widget_preview_frequency.png b/app/src/main/res/drawable/widget_preview_frequency.png deleted file mode 100644 index a3fc86505..000000000 Binary files a/app/src/main/res/drawable/widget_preview_frequency.png and /dev/null differ diff --git a/app/src/main/res/drawable/widget_preview_history.png b/app/src/main/res/drawable/widget_preview_history.png deleted file mode 100644 index bcc829bbc..000000000 Binary files a/app/src/main/res/drawable/widget_preview_history.png and /dev/null differ diff --git a/app/src/main/res/drawable/widget_preview_score.png b/app/src/main/res/drawable/widget_preview_score.png deleted file mode 100644 index 101569dbc..000000000 Binary files a/app/src/main/res/drawable/widget_preview_score.png and /dev/null differ diff --git a/app/src/main/res/drawable/widget_preview_streaks.png b/app/src/main/res/drawable/widget_preview_streaks.png deleted file mode 100644 index b7771c2b8..000000000 Binary files a/app/src/main/res/drawable/widget_preview_streaks.png and /dev/null differ diff --git a/app/src/main/res/layout/about.xml b/app/src/main/res/layout/about.xml index 86a0f9f26..47fd5ea92 100644 --- a/app/src/main/res/layout/about.xml +++ b/app/src/main/res/layout/about.xml @@ -18,160 +18,213 @@ ~ with this program. If not, see . --> - - + - - - - - - - - - - - - - - - - - - - - - + - - + style="@style/CardList"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/app/src/main/res/layout/edit_habit.xml b/app/src/main/res/layout/edit_habit.xml index 493dc1c67..bc2f0a98c 100644 --- a/app/src/main/res/layout/edit_habit.xml +++ b/app/src/main/res/layout/edit_habit.xml @@ -22,7 +22,7 @@ style="@style/dialogForm" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" - tools:context="org.isoron.uhabits.fragments.EditHabitFragment" + tools:context=".dialogs.EditHabitDialogFragment" tools:ignore="MergeRootFrame"> + android:src="?dialogIconChangeColor"/> - diff --git a/app/src/main/res/layout/list_habits_activity.xml b/app/src/main/res/layout/list_habits_activity.xml index 054a4d84e..0bb0106cd 100644 --- a/app/src/main/res/layout/list_habits_activity.xml +++ b/app/src/main/res/layout/list_habits_activity.xml @@ -17,19 +17,30 @@ ~ with this program. If not, see . --> - + + + + android:layout_below="@id/toolbar" + tools:layout="@layout/list_habits_fragment"/> + + diff --git a/app/src/main/res/layout/list_habits_fragment.xml b/app/src/main/res/layout/list_habits_fragment.xml index 8ec48be74..bf97d95be 100644 --- a/app/src/main/res/layout/list_habits_fragment.xml +++ b/app/src/main/res/layout/list_habits_fragment.xml @@ -26,12 +26,17 @@ + android:layout_width="match_parent" + android:background="?windowBackgroundColor" + android:paddingTop="@dimen/checkmarkHeight" + android:layout_height="match_parent" + android:divider="?windowBackgroundColor" + android:listSelector="@android:color/transparent" + android:dividerHeight="1dp"/> - + + style="@style/ListHabits.Star"/> + style="@style/ListHabits.CheckmarkPanel"/> + style="@style/Card"> . --> -