diff --git a/.gitignore b/.gitignore index cce506928..0b81ffa3c 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ gen/ local.properties crowdin.yaml local +tmp/ diff --git a/accept_images.sh b/accept_images.sh deleted file mode 100755 index e8e14d51c..000000000 --- a/accept_images.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -find uhabits-android/build/outputs/failed/test-screenshots -name '*.expected*' -delete -rsync -av uhabits-android/build/outputs/failed/test-screenshots/ uhabits-android/src/androidTest/assets/ diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitModule.java b/android-base/src/main/java/org/isoron/androidbase/activities/ActivityContextModule.java similarity index 68% rename from uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitModule.java rename to android-base/src/main/java/org/isoron/androidbase/activities/ActivityContextModule.java index 105b9a6b5..af7e26442 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitModule.java +++ b/android-base/src/main/java/org/isoron/androidbase/activities/ActivityContextModule.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Álinson Santos Xavier + * Copyright (C) 2016 Álinson Santos Xavier * * This file is part of Loop Habit Tracker. * @@ -17,25 +17,26 @@ * with this program. If not, see . */ -package org.isoron.uhabits.activities; +package org.isoron.androidbase.activities; -import org.isoron.uhabits.core.models.*; +import android.content.*; import dagger.*; @Module -public class HabitModule +public class ActivityContextModule { - private final Habit habit; + private Context context; - public HabitModule(Habit habit) + public ActivityContextModule(Context context) { - this.habit = habit; + this.context = context; } @Provides - public Habit getHabit() + @ActivityContext + public Context getContext() { - return habit; + return context; } } diff --git a/android-base/src/main/java/org/isoron/androidbase/activities/ActivityModule.java b/android-base/src/main/java/org/isoron/androidbase/activities/BaseActivityModule.java similarity index 79% rename from android-base/src/main/java/org/isoron/androidbase/activities/ActivityModule.java rename to android-base/src/main/java/org/isoron/androidbase/activities/BaseActivityModule.java index f3910c989..96e5ec476 100644 --- a/android-base/src/main/java/org/isoron/androidbase/activities/ActivityModule.java +++ b/android-base/src/main/java/org/isoron/androidbase/activities/BaseActivityModule.java @@ -19,29 +19,20 @@ package org.isoron.androidbase.activities; -import android.content.*; - import dagger.*; @Module -public class ActivityModule +public class BaseActivityModule { private BaseActivity activity; - public ActivityModule(BaseActivity activity) + public BaseActivityModule(BaseActivity activity) { this.activity = activity; } @Provides - public BaseActivity getActivity() - { - return activity; - } - - @Provides - @ActivityContext - public Context getContext() + public BaseActivity getBaseActivity() { return activity; } diff --git a/android-base/src/main/java/org/isoron/androidbase/utils/StyledResources.java b/android-base/src/main/java/org/isoron/androidbase/utils/StyledResources.java index c72686936..d15766f95 100644 --- a/android-base/src/main/java/org/isoron/androidbase/utils/StyledResources.java +++ b/android-base/src/main/java/org/isoron/androidbase/utils/StyledResources.java @@ -51,6 +51,15 @@ public class StyledResources return bool; } + public int getDimension(@AttrRes int attrId) + { + TypedArray ta = getTypedArray(attrId); + int dim = ta.getDimensionPixelSize(0, 0); + ta.recycle(); + + return dim; + } + public int getColor(@AttrRes int attrId) { TypedArray ta = getTypedArray(attrId); @@ -80,13 +89,13 @@ public class StyledResources public int[] getPalette() { - int resourceId = getStyleResource(R.attr.palette); + int resourceId = getResource(R.attr.palette); if (resourceId < 0) throw new RuntimeException("resource not found"); return context.getResources().getIntArray(resourceId); } - int getStyleResource(@AttrRes int attrId) + public int getResource(@AttrRes int attrId) { TypedArray ta = getTypedArray(attrId); int resourceId = ta.getResourceId(0, -1); diff --git a/build.gradle b/build.gradle index dae129a17..7d8de18e5 100644 --- a/build.gradle +++ b/build.gradle @@ -1,4 +1,5 @@ buildscript { + ext.kotlin_version = '1.1.2-4' repositories { jcenter() maven { url 'https://maven.google.com' } @@ -10,6 +11,7 @@ buildscript { classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.6.4' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' classpath 'org.jacoco:org.jacoco.core:+' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/build.sh b/build.sh index 74d4fc268..471172bb9 100755 --- a/build.sh +++ b/build.sh @@ -198,6 +198,18 @@ uninstall_test_apk() { $ADB uninstall ${PACKAGE_NAME}.test || fail } +fetch_images() { + rm -rf tmp/test-screenshots > /dev/null + mkdir -p tmp/ + adb pull /sdcard/Android/data/org.isoron.uhabits/files/test-screenshots tmp/ + adb shell rm -rf /sdcard/Android/data/org.isoron.uhabits/files/test-screenshots +} + +accept_images() { + find tmp/test-screenshots -name '*.expected*' -delete + rsync -av tmp/test-screenshots/ uhabits-android/src/androidTest/assets/ +} + run_local_tests() { clean_output_dir run_adb_as_root @@ -263,6 +275,14 @@ case "$1" in run_local_tests ;; + fetch-images) + fetch_images + ;; + + accept-images) + accept_images + ;; + install) shift; parse_opts $* build_apk @@ -278,6 +298,8 @@ case "$1" in ci-tests Start emulator silently, run tests then kill emulator local-tests Run all tests on connected device install Install app on connected device + fetch-images Fetches failed view test images from device + accept-images Copies fetched images to corresponding assets folder Options: -u --uninstall-first Uninstall existing APK first diff --git a/gradle.properties b/gradle.properties index 57a1784b0..2c6d0198f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.parallel=true +org.gradle.parallel=false org.gradle.daemon=true -org.gradle.jvmargs=-Xms1024m -Xmx4096m -XX:MaxPermSize=2048m +org.gradle.jvmargs=-Xms2048m -Xmx2048m -XX:MaxPermSize=2048m diff --git a/uhabits-android/build.gradle b/uhabits-android/build.gradle index deb171daa..9f67c7c0a 100644 --- a/uhabits-android/build.gradle +++ b/uhabits-android/build.gradle @@ -1,5 +1,7 @@ apply plugin: 'idea' apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' apply plugin: 'jacoco' android { @@ -11,6 +13,13 @@ android { minSdkVersion 19 targetSdkVersion 25 testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunnerArgument "size", "medium" + + javaCompileOptions { + annotationProcessorOptions { + includeCompileClasspath false + } + } } buildTypes { @@ -64,18 +73,16 @@ dependencies { implementation 'com.google.dagger:dagger:2.9' implementation 'com.jakewharton:butterknife:8.6.1-SNAPSHOT' implementation 'org.apmem.tools:layouts:1.10' - implementation 'org.jetbrains:annotations-java5:15.0' implementation 'com.google.code.gson:gson:2.7' implementation 'com.google.code.findbugs:jsr305:3.0.2' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" + compileOnly 'javax.annotation:jsr250-api:1.0' compileOnly 'com.google.auto.factory:auto-factory:1.0-beta3' - annotationProcessor 'com.google.dagger:dagger-compiler:2.9' - annotationProcessor 'com.jakewharton:butterknife-compiler:8.6.1-SNAPSHOT' - annotationProcessor 'com.google.auto.factory:auto-factory:1.0-beta3' + kapt 'com.google.dagger:dagger-compiler:2.9' + kapt 'com.jakewharton:butterknife-compiler:8.6.1-SNAPSHOT' + kapt 'com.google.auto.factory:auto-factory:1.0-beta3' - androidTestAnnotationProcessor 'com.google.auto.factory:auto-factory:1.0-beta3' - androidTestAnnotationProcessor 'com.google.dagger:dagger-compiler:2.9' - androidTestAnnotationProcessor 'com.jakewharton:butterknife-compiler:8.6.1-SNAPSHOT' androidTestImplementation 'com.android.support.test.espresso:espresso-contrib:2.2.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:2.2.2' androidTestImplementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.1' @@ -87,6 +94,7 @@ dependencies { androidTestImplementation 'com.android.support.test:runner:0.5' androidTestImplementation 'com.google.guava:guava:20.0' androidTestImplementation project(":uhabits-core") + kaptAndroidTest 'com.google.dagger:dagger-compiler:2.9' // mockito-android 2+ includes net.bytebuddy, which causes tests to fail. // Excluding the package net.bytebuddy on AndroidManifest.xml breaks some @@ -94,9 +102,6 @@ dependencies { androidTestImplementation "org.mockito:mockito-core:1+" androidTestImplementation "com.google.dexmaker:dexmaker-mockito:+" - testAnnotationProcessor 'com.google.auto.factory:auto-factory:1.0-beta3' - testAnnotationProcessor 'com.google.dagger:dagger-compiler:2.9' - testAnnotationProcessor 'com.jakewharton:butterknife-compiler:8.6.1-SNAPSHOT' testImplementation 'com.google.dagger:dagger:2.9' testImplementation "org.mockito:mockito-core:2.8.9" testImplementation "org.mockito:mockito-inline:2.8.9" @@ -114,6 +119,10 @@ repositories { mavenCentral() } +kapt { + correctErrorTypes = true +} + task coverageReport(type: JacocoReport) { jacocoClasspath = configurations['androidJacocoAnt'] diff --git a/uhabits-android/src/androidTest/assets/views-v19/habits/list/CheckmarkPanelView/render_different_color.png b/uhabits-android/src/androidTest/assets/views-v19/habits/list/CheckmarkPanelView/render_different_color.png new file mode 100644 index 000000000..84302fe22 Binary files /dev/null and b/uhabits-android/src/androidTest/assets/views-v19/habits/list/CheckmarkPanelView/render_different_color.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v19/habits/list/CheckmarkPanelView/render_offset.png b/uhabits-android/src/androidTest/assets/views-v19/habits/list/CheckmarkPanelView/render_offset.png new file mode 100644 index 000000000..9abe0c353 Binary files /dev/null and b/uhabits-android/src/androidTest/assets/views-v19/habits/list/CheckmarkPanelView/render_offset.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v19/habits/list/CheckmarkPanelView/render_reversed.png b/uhabits-android/src/androidTest/assets/views-v19/habits/list/CheckmarkPanelView/render_reversed.png new file mode 100644 index 000000000..cf312df77 Binary files /dev/null and b/uhabits-android/src/androidTest/assets/views-v19/habits/list/CheckmarkPanelView/render_reversed.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v19/habits/list/NumberPanelView/render.png b/uhabits-android/src/androidTest/assets/views-v19/habits/list/NumberPanelView/render.png new file mode 100644 index 000000000..e33641085 Binary files /dev/null and b/uhabits-android/src/androidTest/assets/views-v19/habits/list/NumberPanelView/render.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v19/habits/list/NumberPanelView/render_different_color.png b/uhabits-android/src/androidTest/assets/views-v19/habits/list/NumberPanelView/render_different_color.png new file mode 100644 index 000000000..172a6434c Binary files /dev/null and b/uhabits-android/src/androidTest/assets/views-v19/habits/list/NumberPanelView/render_different_color.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v19/habits/list/NumberPanelView/render_offset.png b/uhabits-android/src/androidTest/assets/views-v19/habits/list/NumberPanelView/render_offset.png new file mode 100644 index 000000000..8e86904df Binary files /dev/null and b/uhabits-android/src/androidTest/assets/views-v19/habits/list/NumberPanelView/render_offset.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v19/habits/list/NumberPanelView/render_reversed.png b/uhabits-android/src/androidTest/assets/views-v19/habits/list/NumberPanelView/render_reversed.png new file mode 100644 index 000000000..1d8338f29 Binary files /dev/null and b/uhabits-android/src/androidTest/assets/views-v19/habits/list/NumberPanelView/render_reversed.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v19/widgets/HistoryWidget/render.png b/uhabits-android/src/androidTest/assets/views-v19/widgets/HistoryWidget/render.png index 306c49a11..6b9a21f94 100644 Binary files a/uhabits-android/src/androidTest/assets/views-v19/widgets/HistoryWidget/render.png and b/uhabits-android/src/androidTest/assets/views-v19/widgets/HistoryWidget/render.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v19/widgets/ScoreWidget/render.png b/uhabits-android/src/androidTest/assets/views-v19/widgets/ScoreWidget/render.png index edc306993..0f61c79da 100644 Binary files a/uhabits-android/src/androidTest/assets/views-v19/widgets/ScoreWidget/render.png and b/uhabits-android/src/androidTest/assets/views-v19/widgets/ScoreWidget/render.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/common/RingView/renderDifferentParams.png b/uhabits-android/src/androidTest/assets/views-v21/common/RingView/renderDifferentParams.png index 87874ba92..5285c2626 100644 Binary files a/uhabits-android/src/androidTest/assets/views-v21/common/RingView/renderDifferentParams.png and b/uhabits-android/src/androidTest/assets/views-v21/common/RingView/renderDifferentParams.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/common/ScoreChart/renderYearly.png b/uhabits-android/src/androidTest/assets/views-v21/common/ScoreChart/renderYearly.png index a5aca0cf4..068864ecb 100644 Binary files a/uhabits-android/src/androidTest/assets/views-v21/common/ScoreChart/renderYearly.png and b/uhabits-android/src/androidTest/assets/views-v21/common/ScoreChart/renderYearly.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/habits/list/CheckmarkButtonView/render_explicit_check.png b/uhabits-android/src/androidTest/assets/views-v21/habits/list/CheckmarkButtonView/render_explicit_check.png index 32c5d98cb..f3e2d3bef 100644 Binary files a/uhabits-android/src/androidTest/assets/views-v21/habits/list/CheckmarkButtonView/render_explicit_check.png and b/uhabits-android/src/androidTest/assets/views-v21/habits/list/CheckmarkButtonView/render_explicit_check.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/habits/list/CheckmarkPanelView/render.png b/uhabits-android/src/androidTest/assets/views-v21/habits/list/CheckmarkPanelView/render.png index 5201b4c94..39b3ebb2c 100644 Binary files a/uhabits-android/src/androidTest/assets/views-v21/habits/list/CheckmarkPanelView/render.png and b/uhabits-android/src/androidTest/assets/views-v21/habits/list/CheckmarkPanelView/render.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/habits/list/CheckmarkPanelView/render_different_color.png b/uhabits-android/src/androidTest/assets/views-v21/habits/list/CheckmarkPanelView/render_different_color.png new file mode 100644 index 000000000..b55300ece Binary files /dev/null and b/uhabits-android/src/androidTest/assets/views-v21/habits/list/CheckmarkPanelView/render_different_color.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/habits/list/CheckmarkPanelView/render_offset.png b/uhabits-android/src/androidTest/assets/views-v21/habits/list/CheckmarkPanelView/render_offset.png new file mode 100644 index 000000000..574701417 Binary files /dev/null and b/uhabits-android/src/androidTest/assets/views-v21/habits/list/CheckmarkPanelView/render_offset.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/habits/list/CheckmarkPanelView/render_reversed.png b/uhabits-android/src/androidTest/assets/views-v21/habits/list/CheckmarkPanelView/render_reversed.png new file mode 100644 index 000000000..2965eb691 Binary files /dev/null and b/uhabits-android/src/androidTest/assets/views-v21/habits/list/CheckmarkPanelView/render_reversed.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/habits/list/HabitCardView/render.png b/uhabits-android/src/androidTest/assets/views-v21/habits/list/HabitCardView/render.png index 23c258a82..bf0e6b59d 100644 Binary files a/uhabits-android/src/androidTest/assets/views-v21/habits/list/HabitCardView/render.png and b/uhabits-android/src/androidTest/assets/views-v21/habits/list/HabitCardView/render.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/habits/list/HabitCardView/render_changed.png b/uhabits-android/src/androidTest/assets/views-v21/habits/list/HabitCardView/render_changed.png index 23c258a82..bf0e6b59d 100644 Binary files a/uhabits-android/src/androidTest/assets/views-v21/habits/list/HabitCardView/render_changed.png and b/uhabits-android/src/androidTest/assets/views-v21/habits/list/HabitCardView/render_changed.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/habits/list/HabitCardView/render_numerical.png b/uhabits-android/src/androidTest/assets/views-v21/habits/list/HabitCardView/render_numerical.png new file mode 100644 index 000000000..a35768436 Binary files /dev/null and b/uhabits-android/src/androidTest/assets/views-v21/habits/list/HabitCardView/render_numerical.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/habits/list/HabitCardView/render_selected.png b/uhabits-android/src/androidTest/assets/views-v21/habits/list/HabitCardView/render_selected.png index 63524c5fc..3cb0a817f 100644 Binary files a/uhabits-android/src/androidTest/assets/views-v21/habits/list/HabitCardView/render_selected.png and b/uhabits-android/src/androidTest/assets/views-v21/habits/list/HabitCardView/render_selected.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/habits/list/HeaderView/render.png b/uhabits-android/src/androidTest/assets/views-v21/habits/list/HeaderView/render.png index b3667220c..4273511d5 100644 Binary files a/uhabits-android/src/androidTest/assets/views-v21/habits/list/HeaderView/render.png and b/uhabits-android/src/androidTest/assets/views-v21/habits/list/HeaderView/render.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/habits/list/HeaderView/render_reverse.png b/uhabits-android/src/androidTest/assets/views-v21/habits/list/HeaderView/render_reverse.png index 09460ab18..ca045595e 100644 Binary files a/uhabits-android/src/androidTest/assets/views-v21/habits/list/HeaderView/render_reverse.png and b/uhabits-android/src/androidTest/assets/views-v21/habits/list/HeaderView/render_reverse.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/habits/list/NumberPanelView/render.png b/uhabits-android/src/androidTest/assets/views-v21/habits/list/NumberPanelView/render.png new file mode 100644 index 000000000..9e9857e18 Binary files /dev/null and b/uhabits-android/src/androidTest/assets/views-v21/habits/list/NumberPanelView/render.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/habits/list/NumberPanelView/render_different_color.png b/uhabits-android/src/androidTest/assets/views-v21/habits/list/NumberPanelView/render_different_color.png new file mode 100644 index 000000000..165226baa Binary files /dev/null and b/uhabits-android/src/androidTest/assets/views-v21/habits/list/NumberPanelView/render_different_color.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/habits/list/NumberPanelView/render_offset.png b/uhabits-android/src/androidTest/assets/views-v21/habits/list/NumberPanelView/render_offset.png new file mode 100644 index 000000000..8460f9b4f Binary files /dev/null and b/uhabits-android/src/androidTest/assets/views-v21/habits/list/NumberPanelView/render_offset.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/habits/list/NumberPanelView/render_reversed.png b/uhabits-android/src/androidTest/assets/views-v21/habits/list/NumberPanelView/render_reversed.png new file mode 100644 index 000000000..2adf58c23 Binary files /dev/null and b/uhabits-android/src/androidTest/assets/views-v21/habits/list/NumberPanelView/render_reversed.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/habits/show/FrequencyCard/render.png b/uhabits-android/src/androidTest/assets/views-v21/habits/show/FrequencyCard/render.png index 275a8d489..bc3330fb4 100644 Binary files a/uhabits-android/src/androidTest/assets/views-v21/habits/show/FrequencyCard/render.png and b/uhabits-android/src/androidTest/assets/views-v21/habits/show/FrequencyCard/render.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/habits/show/SubtitleCard/render.png b/uhabits-android/src/androidTest/assets/views-v21/habits/show/SubtitleCard/render.png index 3b8138940..96e2310e0 100644 Binary files a/uhabits-android/src/androidTest/assets/views-v21/habits/show/SubtitleCard/render.png and b/uhabits-android/src/androidTest/assets/views-v21/habits/show/SubtitleCard/render.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/widgets/HistoryWidget/render.png b/uhabits-android/src/androidTest/assets/views-v21/widgets/HistoryWidget/render.png index ce30793a7..fa7ef2110 100644 Binary files a/uhabits-android/src/androidTest/assets/views-v21/widgets/HistoryWidget/render.png and b/uhabits-android/src/androidTest/assets/views-v21/widgets/HistoryWidget/render.png differ diff --git a/uhabits-android/src/androidTest/assets/views-v21/widgets/ScoreWidget/render.png b/uhabits-android/src/androidTest/assets/views-v21/widgets/ScoreWidget/render.png index 471611d6f..59d94daa4 100644 Binary files a/uhabits-android/src/androidTest/assets/views-v21/widgets/ScoreWidget/render.png and b/uhabits-android/src/androidTest/assets/views-v21/widgets/ScoreWidget/render.png differ diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java b/uhabits-android/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java index ec6dcaa0e..de390ff7c 100644 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java @@ -31,6 +31,7 @@ import android.util.*; import junit.framework.*; import org.isoron.androidbase.*; +import org.isoron.androidbase.activities.*; import org.isoron.androidbase.utils.*; import org.isoron.uhabits.core.models.*; import org.isoron.uhabits.core.preferences.*; @@ -67,12 +68,15 @@ public class BaseAndroidTest extends TestCase protected CountDownLatch latch; - protected AndroidTestComponent component; + protected HabitsApplicationTestComponent appComponent; protected ModelFactory modelFactory; + protected HabitsActivityTestComponent component; + private boolean isDone = false; + @Override @Before public void setUp() { @@ -86,21 +90,31 @@ public class BaseAndroidTest extends TestCase setTheme(R.style.AppBaseTheme); setLocale("en", "US"); - component = DaggerAndroidTestComponent + latch = new CountDownLatch(1); + + appComponent = DaggerHabitsApplicationTestComponent .builder() .appContextModule(new AppContextModule(targetContext.getApplicationContext())) .build(); - HabitsApplication.setComponent(component); - prefs = component.getPreferences(); - habitList = component.getHabitList(); - taskRunner = component.getTaskRunner(); - logger = component.getHabitsLogger(); + HabitsApplication.setComponent(appComponent); + prefs = appComponent.getPreferences(); + habitList = appComponent.getHabitList(); + taskRunner = appComponent.getTaskRunner(); + logger = appComponent.getHabitsLogger(); + modelFactory = appComponent.getModelFactory(); + + prefs.reset(); - modelFactory = component.getModelFactory(); fixtures = new HabitFixtures(modelFactory, habitList); + fixtures.purgeHabits(appComponent.getHabitList()); + Habit habit = fixtures.createEmptyHabit(); - latch = new CountDownLatch(1); + component = DaggerHabitsActivityTestComponent + .builder() + .activityContextModule(new ActivityContextModule(targetContext)) + .habitsApplicationComponent(appComponent) + .build(); } protected void assertWidgetProviderIsInstalled(Class componentClass) @@ -118,7 +132,7 @@ public class BaseAndroidTest extends TestCase protected void awaitLatch() throws InterruptedException { - assertTrue(latch.await(5, TimeUnit.SECONDS)); + assertTrue(latch.await(1, TimeUnit.SECONDS)); } protected void setLocale(@NonNull String language, @NonNull String country) @@ -195,4 +209,10 @@ public class BaseAndroidTest extends TestCase { Debug.stopMethodTracing(); } + + protected Long day(int offset) + { + return DateUtils.getStartOfToday() - + offset * DateUtils.millisecondsInOneDay; + } } diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/BaseViewTest.java b/uhabits-android/src/androidTest/java/org/isoron/uhabits/BaseViewTest.java index 79a09fe45..c6c97ca6b 100644 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/BaseViewTest.java +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/BaseViewTest.java @@ -21,6 +21,7 @@ package org.isoron.uhabits; import android.graphics.*; import android.support.annotation.*; +import android.support.test.*; import android.view.*; import android.widget.*; @@ -31,14 +32,14 @@ import org.isoron.uhabits.widgets.*; import java.io.*; import java.util.*; -import static android.os.Build.VERSION.*; +import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP; -import static android.view.View.MeasureSpec.*; +import static android.view.View.MeasureSpec.makeMeasureSpec; public class BaseViewTest extends BaseAndroidTest { - double similarityCutoff = 0.00075; + public double similarityCutoff = 0.00015; @Override public void setUp() @@ -49,6 +50,7 @@ public class BaseViewTest extends BaseAndroidTest protected void assertRenders(View view, String expectedImagePath) throws IOException { + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); expectedImagePath = getVersionedPath(expectedImagePath); Bitmap actual = renderView(view); if(actual == null) throw new IllegalStateException("actual is null"); @@ -198,6 +200,7 @@ public class BaseViewTest extends BaseAndroidTest Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); + view.invalidate(); view.draw(canvas); return bitmap; } diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/HabitFixtures.java b/uhabits-android/src/androidTest/java/org/isoron/uhabits/HabitFixtures.java index 4eeb5f6b9..f7a7cd9cf 100644 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/HabitFixtures.java +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/HabitFixtures.java @@ -84,6 +84,9 @@ public class HabitFixtures habit.setName("Take a walk"); habit.setDescription("How many steps did you walk today?"); habit.setType(Habit.NUMBER_HABIT); + habit.setTargetType(Habit.AT_LEAST); + habit.setTargetValue(200.0); + habit.setUnit("steps"); habitList.add(habit); long timestamp = DateUtils.getStartOfToday(); diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/HabitsActivityTestComponent.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/HabitsActivityTestComponent.kt new file mode 100644 index 000000000..dde1b5a90 --- /dev/null +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/HabitsActivityTestComponent.kt @@ -0,0 +1,53 @@ +/* + * 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 + +import dagger.* +import org.isoron.androidbase.activities.* +import org.isoron.uhabits.activities.* +import org.isoron.uhabits.activities.about.* +import org.isoron.uhabits.activities.habits.list.* +import org.isoron.uhabits.activities.habits.list.views.* +import org.isoron.uhabits.activities.habits.show.* +import org.isoron.uhabits.core.ui.screens.habits.list.* +import org.mockito.Mockito.* + +@Module +class TestModule { + @Provides fun ListHabitsBehavior() = mock(ListHabitsBehavior::class.java) +} + +@ActivityScope +@Component(modules = arrayOf( + ActivityContextModule::class, + AboutModule::class, + HabitsActivityModule::class, + ListHabitsModule::class, + ShowHabitModule::class, + HabitModule::class, + TestModule::class +), dependencies = arrayOf(HabitsApplicationComponent::class)) +interface HabitsActivityTestComponent { + fun getCheckmarkPanelViewFactory(): CheckmarkPanelViewFactory + fun getHabitCardViewFactory(): HabitCardViewFactory + fun getCheckmarkButtonViewFactory(): CheckmarkButtonViewFactory + fun getNumberButtonViewFactory(): NumberButtonViewFactory + fun getNumberPanelViewFactory(): NumberPanelViewFactory +} \ No newline at end of file diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/AndroidTestComponent.java b/uhabits-android/src/androidTest/java/org/isoron/uhabits/HabitsApplicationTestComponent.java similarity index 90% rename from uhabits-android/src/androidTest/java/org/isoron/uhabits/AndroidTestComponent.java rename to uhabits-android/src/androidTest/java/org/isoron/uhabits/HabitsApplicationTestComponent.java index ffd27be59..7ff4e2e26 100644 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/AndroidTestComponent.java +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/HabitsApplicationTestComponent.java @@ -19,7 +19,6 @@ package org.isoron.uhabits; - import org.isoron.androidbase.*; import org.isoron.uhabits.core.*; import org.isoron.uhabits.core.tasks.*; @@ -32,7 +31,8 @@ import dagger.*; HabitsModule.class, SingleThreadModule.class, }) -public interface AndroidTestComponent extends HabitsApplicationComponent +public interface HabitsApplicationTestComponent + extends HabitsApplicationComponent { } @@ -42,7 +42,7 @@ class SingleThreadModule { @Provides @AppScope - public static TaskRunner provideTaskRunner() + static TaskRunner provideTaskRunner() { return new SingleThreadTaskRunner(); } diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/CommonSteps.java b/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/CommonSteps.java index b724467bf..fbbafe672 100644 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/CommonSteps.java +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/CommonSteps.java @@ -142,7 +142,8 @@ public class CommonSteps extends BaseUserInterfaceTest switch(screen) { case LIST_HABITS: - onView(withId(R.id.header)).check(matches(isDisplayed())); + onView(withClassName(endsWith("ListHabitsRootView"))) + .check(matches(isDisplayed())); break; case SHOW_HABIT: diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/ListHabitsSteps.java b/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/ListHabitsSteps.java index fbee024be..c8784138e 100644 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/ListHabitsSteps.java +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/ListHabitsSteps.java @@ -20,7 +20,6 @@ package org.isoron.uhabits.acceptance.steps; import android.support.test.espresso.*; -import android.support.test.uiautomator.*; import android.view.*; import org.hamcrest.*; @@ -29,13 +28,18 @@ import org.isoron.uhabits.activities.habits.list.views.*; import java.util.*; -import static android.support.test.InstrumentationRegistry.*; -import static android.support.test.espresso.Espresso.*; -import static android.support.test.espresso.action.ViewActions.*; -import static android.support.test.espresso.matcher.ViewMatchers.*; +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant; +import static android.support.test.espresso.matcher.ViewMatchers.isEnabled; +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.withId; +import static android.support.test.espresso.matcher.ViewMatchers.withParent; +import static android.support.test.espresso.matcher.ViewMatchers.withText; import static org.hamcrest.CoreMatchers.*; import static org.isoron.uhabits.BaseUserInterfaceTest.device; -import static org.isoron.uhabits.acceptance.steps.CommonSteps.*; +import static org.isoron.uhabits.acceptance.steps.CommonSteps.clickText; public abstract class ListHabitsSteps { @@ -89,17 +93,8 @@ public abstract class ListHabitsSteps private static void clickTextInsideOverflowMenu(int id) { - UiObject toolbar = device.findObject( - new UiSelector().resourceId("org.isoron.uhabits:id/toolbar")); - if (toolbar.exists()) - { - onView(allOf(withContentDescription("More options"), - withParent(withParent(withId(R.id.toolbar))))).perform(click()); - } - else - { - openActionBarOverflowOrOptionsMenu(getTargetContext()); - } + onView(allOf(withContentDescription("More options"), withParent( + withParent(withClassName(endsWith("Toolbar")))))).perform(click()); onView(withText(id)).perform(click()); } diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonViewTest.java b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonViewTest.java deleted file mode 100644 index 53b2f1cc8..000000000 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonViewTest.java +++ /dev/null @@ -1,87 +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.activities.habits.list.views; - -import android.support.test.runner.*; -import android.test.suitebuilder.annotation.*; - -import org.isoron.uhabits.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.utils.*; -import org.junit.*; -import org.junit.runner.*; - -import java.io.*; - -@RunWith(AndroidJUnit4.class) -@MediumTest -public class CheckmarkButtonViewTest extends BaseViewTest -{ - public static final String PATH = "habits/list/CheckmarkButtonView/"; - - private CheckmarkButtonView view; - - @Override - @Before - public void setUp() - { - super.setUp(); - view = new CheckmarkButtonView(targetContext); - view.setValue(Checkmark.UNCHECKED); - view.setColor(PaletteUtils.getAndroidTestColor(5)); - measureView(view, dpToPixels(48), dpToPixels(48)); - } - - @Test - public void testRender_explicitCheck() throws Exception - { - view.setValue(Checkmark.CHECKED_EXPLICITLY); - assertRendersCheckedExplicitly(); - } - - @Test - public void testRender_implicitCheck() throws Exception - { - view.setValue(Checkmark.CHECKED_IMPLICITLY); - assertRendersCheckedImplicitly(); - } - - @Test - public void testRender_unchecked() throws Exception - { - view.setValue(Checkmark.UNCHECKED); - assertRendersUnchecked(); - } - - protected void assertRendersCheckedExplicitly() throws IOException - { - assertRenders(view, PATH + "render_explicit_check.png"); - } - - protected void assertRendersCheckedImplicitly() throws IOException - { - assertRenders(view, PATH + "render_implicit_check.png"); - } - - protected void assertRendersUnchecked() throws IOException - { - assertRenders(view, PATH + "render_unchecked.png"); - } -} \ No newline at end of file diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonViewTest.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonViewTest.kt new file mode 100644 index 000000000..9c6e52533 --- /dev/null +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonViewTest.kt @@ -0,0 +1,99 @@ +/* + * 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.activities.habits.list.views + +import android.support.test.filters.* +import android.support.test.runner.* +import org.isoron.uhabits.* +import org.isoron.uhabits.core.models.* +import org.isoron.uhabits.utils.* +import org.junit.* +import org.junit.runner.* + +@RunWith(AndroidJUnit4::class) +@MediumTest +class CheckmarkButtonViewTest : BaseViewTest() { + + private val PATH = "habits/list/CheckmarkButtonView" + lateinit var view: CheckmarkButtonView + + var toggled = false + + @Before + override fun setUp() { + super.setUp() + view = component.getCheckmarkButtonViewFactory().create().apply { + value = Checkmark.UNCHECKED + color = PaletteUtils.getAndroidTestColor(5) + onToggle = { toggled = true } + } + measureView(view, dpToPixels(48), dpToPixels(48)) + } + + @Test + fun testRender_explicitCheck() { + view.value = Checkmark.CHECKED_EXPLICITLY + assertRendersCheckedExplicitly() + } + + @Test + fun testRender_implicitCheck() { + view.value = Checkmark.CHECKED_IMPLICITLY + assertRendersCheckedImplicitly() + } + + @Test + fun testRender_unchecked() { + view.value = Checkmark.UNCHECKED + assertRendersUnchecked() + } + + @Test + fun testClick_withShortToggleDisabled() { + prefs.isShortToggleEnabled = false + view.performClick() + assertFalse(toggled) + } + + @Test + fun testClick_withShortToggleEnabled() { + prefs.isShortToggleEnabled = true + view.performClick() + assertTrue(toggled) + } + + @Test + fun testLongClick() { + view.performLongClick() + assertTrue(toggled) + } + + private fun assertRendersCheckedExplicitly() { + assertRenders(view, "$PATH/render_explicit_check.png") + } + + private fun assertRendersCheckedImplicitly() { + assertRenders(view, "$PATH/render_implicit_check.png") + } + + private fun assertRendersUnchecked() { + assertRenders(view, "$PATH/render_unchecked.png") + } +} \ No newline at end of file diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelViewTest.java b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelViewTest.java deleted file mode 100644 index b73f71d97..000000000 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelViewTest.java +++ /dev/null @@ -1,96 +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.activities.habits.list.views; - -import android.support.test.runner.AndroidJUnit4; -import android.test.suitebuilder.annotation.*; - -import org.isoron.uhabits.core.models.Checkmark; -import org.isoron.uhabits.core.models.Habit; -import org.isoron.uhabits.BaseViewTest; -import org.isoron.uhabits.utils.*; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.concurrent.CountDownLatch; - -@RunWith(AndroidJUnit4.class) -@MediumTest -public class CheckmarkPanelViewTest extends BaseViewTest -{ - public static final String PATH = "habits/list/CheckmarkPanelView/"; - - private CountDownLatch latch; - private CheckmarkPanelView view; - private int checkmarks[]; - - @Override - @Before - public void setUp() - { - super.setUp(); - prefs.setShouldReverseCheckmarks(false); - - Habit habit = fixtures.createEmptyHabit(); - - latch = new CountDownLatch(1); - checkmarks = new int[]{ - Checkmark.CHECKED_EXPLICITLY, Checkmark.UNCHECKED, - Checkmark.CHECKED_IMPLICITLY, Checkmark.CHECKED_EXPLICITLY}; - - view = new CheckmarkPanelView(targetContext); - view.setValues(checkmarks); - view.setButtonCount(4); - view.setColor(PaletteUtils.getAndroidTestColor(7)); - - measureView(view, dpToPixels(200), dpToPixels(200)); - } - -// protected void waitForLatch() throws InterruptedException -// { -// assertTrue("Latch timeout", latch.await(1, TimeUnit.SECONDS)); -// } - - @Test - public void testRender() throws Exception - { - assertRenders(view, PATH + "render.png"); - } - -// @Test -// public void testToggleCheckmark_withLeftToRight() throws Exception -// { -// setToggleListener(); -// view.getButton(1).performToggle(); -// waitForLatch(); -// } -// -// @Test -// public void testToggleCheckmark_withReverseCheckmarks() throws Exception -// { -// prefs.setShouldReverseCheckmarks(true); -// view.setCheckmarkValues(checkmarks); // refresh after preference change -// -// setToggleListener(); -// view.getButton(2).performToggle(); -// waitForLatch(); -// } -} \ No newline at end of file diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelViewTest.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelViewTest.kt new file mode 100644 index 000000000..835259f18 --- /dev/null +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelViewTest.kt @@ -0,0 +1,110 @@ +/* + * 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.activities.habits.list.views + +import android.support.test.filters.* +import android.support.test.runner.* +import org.hamcrest.CoreMatchers.* +import org.hamcrest.MatcherAssert.* +import org.isoron.uhabits.* +import org.isoron.uhabits.core.models.Checkmark.* +import org.isoron.uhabits.utils.* +import org.junit.* +import org.junit.runner.* + +@RunWith(AndroidJUnit4::class) +@MediumTest +class CheckmarkPanelViewTest : BaseViewTest() { + + private val PATH = "habits/list/CheckmarkPanelView" + private lateinit var view: CheckmarkPanelView + + @Before + override fun setUp() { + super.setUp() + prefs.isCheckmarkSequenceReversed = false + + val checkmarks = intArrayOf(CHECKED_EXPLICITLY, + CHECKED_EXPLICITLY, + CHECKED_IMPLICITLY, + UNCHECKED, + UNCHECKED, + UNCHECKED, + CHECKED_EXPLICITLY) + + view = component.getCheckmarkPanelViewFactory().create().apply { + values = checkmarks + buttonCount = 4 + color = PaletteUtils.getAndroidTestColor(7) + } + view.onAttachedToWindow() + measureView(view, dpToPixels(200), dpToPixels(200)) + } + + @After + public override fun tearDown() { +// view.onDetachedFromWindow() + super.tearDown() + } + + @Test + fun testRender() { + assertRenders(view, "$PATH/render.png") + } + + @Test + fun testRender_withDifferentColor() { + view.color = PaletteUtils.getAndroidTestColor(1) + assertRenders(view, "$PATH/render_different_color.png") + } + + @Test + fun testRender_Reversed() { + prefs.isCheckmarkSequenceReversed = true + assertRenders(view, "$PATH/render_reversed.png") + } + + @Test + fun testRender_withOffset() { + view.dataOffset = 3 + assertRenders(view, "$PATH/render_offset.png") + } + + @Test + fun testToggle() { + var timestamps = mutableListOf() + view.onToggle = { timestamps.add(it) } + view.buttons[0].performLongClick() + view.buttons[2].performLongClick() + view.buttons[3].performLongClick() + assertThat(timestamps, equalTo(listOf(day(0), day(2), day(3)))) + } + + @Test + fun testToggle_withOffset() { + var timestamps = LongArray(0) + view.dataOffset = 3 + view.onToggle = { timestamps += it } + view.buttons[0].performLongClick() + view.buttons[2].performLongClick() + view.buttons[3].performLongClick() + assertThat(timestamps, equalTo(longArrayOf(day(3), day(5), day(6)))) + } +} \ No newline at end of file diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/HabitCardViewTest.java b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/HabitCardViewTest.java deleted file mode 100644 index 6133f36d6..000000000 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/HabitCardViewTest.java +++ /dev/null @@ -1,86 +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.activities.habits.list.views; - -import android.support.test.runner.*; -import android.test.suitebuilder.annotation.*; - -import org.isoron.uhabits.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.utils.*; -import org.junit.*; -import org.junit.runner.*; - -@RunWith(AndroidJUnit4.class) -@MediumTest -public class HabitCardViewTest extends BaseViewTest -{ - private HabitCardView view; - - public static final String PATH = "habits/list/HabitCardView/"; - - private Habit habit; - - @Override - public void setUp() - { - super.setUp(); - setTheme(R.style.AppBaseTheme); - - habit = fixtures.createLongHabit(); - CheckmarkList checkmarks = habit.getCheckmarks(); - - long today = DateUtils.getStartOfToday(); - long day = DateUtils.millisecondsInOneDay; - int[] values = checkmarks.getValues(today - 5 * day, today); - - view = new HabitCardView(targetContext); - view.setHabit(habit); - view.setValues(values); - view.setSelected(false); - view.setScore(habit.getScores().getTodayValue()); - view.setButtonCount(6); - measureView(view, dpToPixels(400), dpToPixels(50)); - } - - @Test - public void testRender() throws Exception - { - assertRenders(view, PATH + "render.png"); - } - - @Test - public void testRender_selected() throws Exception - { - view.setSelected(true); - measureView(view, dpToPixels(400), dpToPixels(50)); - assertRenders(view, PATH + "render_selected.png"); - } - - @Test - public void testChangeModel() throws Exception - { - habit.setName("Wake up early"); - habit.setColor(2); - habit.getObservable().notifyListeners(); - Thread.sleep(500); - assertRenders(view, PATH + "render_changed.png"); - } -} diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/HabitCardViewTest.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/HabitCardViewTest.kt new file mode 100644 index 000000000..5660b862e --- /dev/null +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/HabitCardViewTest.kt @@ -0,0 +1,86 @@ +/* + * 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.activities.habits.list.views + +import android.support.test.filters.* +import android.support.test.runner.* +import org.isoron.uhabits.* +import org.isoron.uhabits.core.models.* +import org.junit.* +import org.junit.runner.* + +@RunWith(AndroidJUnit4::class) +@MediumTest +class HabitCardViewTest : BaseViewTest() { + + val PATH = "habits/list/HabitCardView" + lateinit private var view: HabitCardView + lateinit private var habit1: Habit + lateinit private var habit2: Habit + + override fun setUp() { + super.setUp() + setTheme(R.style.AppBaseTheme) + + habit1 = fixtures.createLongHabit() + habit2 = fixtures.createLongNumericalHabit() + view = component.getHabitCardViewFactory().create().apply { + habit = habit1 + values = habit1.checkmarks.allValues + score = habit1.scores.todayValue + isSelected = false + buttonCount = 5 + } + latch.countDown() + + latch.await() + measureView(view, dpToPixels(400), dpToPixels(50)) + } + + @Test + fun testRender() { + assertRenders(view, "$PATH/render.png") + } + + @Test + fun testRender_selected() { + view.isSelected = true + measureView(view, dpToPixels(400), dpToPixels(50)) + assertRenders(view, "$PATH/render_selected.png") + } + + @Test + fun testRender_numerical() { + view.apply { + habit = habit2 + values = habit2.checkmarks.allValues + } + assertRenders(view, "$PATH/render_numerical.png") + } + + @Test + fun testChangeModel() { + habit1.name = "Wake up early" + habit1.color = 2 + habit1.observable.notifyListeners() + Thread.sleep(500) + assertRenders(view, "$PATH/render_changed.png") + } +} diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/HeaderViewTest.java b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/HeaderViewTest.java index 70d4ee66e..4c347c2a5 100644 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/HeaderViewTest.java +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/HeaderViewTest.java @@ -57,22 +57,22 @@ public class HeaderViewTest extends BaseViewTest @Test public void testRender() throws Exception { - when(prefs.shouldReverseCheckmarks()).thenReturn(false); + when(prefs.isCheckmarkSequenceReversed()).thenReturn(false); assertRenders(view, PATH + "render.png"); - verify(prefs).shouldReverseCheckmarks(); + verify(prefs).isCheckmarkSequenceReversed(); verifyNoMoreInteractions(prefs); } @Test public void testRender_reverse() throws Exception { - when(prefs.shouldReverseCheckmarks()).thenReturn(true); + when(prefs.isCheckmarkSequenceReversed()).thenReturn(true); assertRenders(view, PATH + "render_reverse.png"); - verify(prefs).shouldReverseCheckmarks(); + verify(prefs).isCheckmarkSequenceReversed(); verifyNoMoreInteractions(prefs); } } \ No newline at end of file diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/HintViewTest.java b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/HintViewTest.java index 9f8e5525a..44f512568 100644 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/HintViewTest.java +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/HintViewTest.java @@ -47,9 +47,8 @@ public class HintViewTest extends BaseViewTest { super.setUp(); - view = new HintView(targetContext); list = mock(HintList.class); - view.setHints(list); + view = new HintView(targetContext, list); measureView(view, 400, 200); String text = diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonViewTest.java b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonViewTest.java deleted file mode 100644 index 09343c04c..000000000 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonViewTest.java +++ /dev/null @@ -1,95 +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.activities.habits.list.views; - -import android.support.test.filters.*; -import android.support.test.runner.*; - -import org.isoron.uhabits.*; -import org.isoron.uhabits.utils.*; -import org.junit.*; -import org.junit.runner.*; - -import static org.hamcrest.CoreMatchers.*; -import static org.hamcrest.MatcherAssert.*; - -@RunWith(AndroidJUnit4.class) -@MediumTest -public class NumberButtonViewTest extends BaseViewTest -{ - public static final String PATH = "habits/list/NumberButtonView/"; - - private NumberButtonView view; - - - @Override - @Before - public void setUp() - { - super.setUp(); - - view = new NumberButtonView(targetContext); - view.setUnit("steps"); - view.setThreshold(100.0); - view.setColor(PaletteUtils.getAndroidTestColor(8)); - - measureView(view, dpToPixels(48), dpToPixels(48)); - } - - @Test - public void testFormatValue() - { - assertThat(NumberButtonView.formatValue(0.1235), equalTo("0.12")); - assertThat(NumberButtonView.formatValue(0.1000), equalTo("0.1")); - assertThat(NumberButtonView.formatValue(5.0), equalTo("5")); - assertThat(NumberButtonView.formatValue(5.25), equalTo("5.25")); - assertThat(NumberButtonView.formatValue(12.3456), equalTo("12.3")); - assertThat(NumberButtonView.formatValue(123.123), equalTo("123")); - assertThat(NumberButtonView.formatValue(321.2), equalTo("321")); - assertThat(NumberButtonView.formatValue(4321.2), equalTo("4.3k")); - assertThat(NumberButtonView.formatValue(54321.2), equalTo("54.3k")); - assertThat(NumberButtonView.formatValue(654321.2), equalTo("654k")); - assertThat(NumberButtonView.formatValue(7654321.2), equalTo("7.7M")); - assertThat(NumberButtonView.formatValue(87654321.2), equalTo("87.7M")); - assertThat(NumberButtonView.formatValue(987654321.2), equalTo("988M")); - assertThat(NumberButtonView.formatValue(1987654321.2), equalTo("2.0G")); - } - - @Test - public void testRender_aboveThreshold() throws Exception - { - view.setValue(500); - assertRenders(view, PATH + "render_above.png"); - } - - @Test - public void testRender_belowThreshold() throws Exception - { - view.setValue(99); - assertRenders(view, PATH + "render_below.png"); - } - - @Test - public void testRender_zero() throws Exception - { - view.setValue(0); - assertRenders(view, PATH + "render_zero.png"); - } -} \ No newline at end of file diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonViewTest.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonViewTest.kt new file mode 100644 index 000000000..d64830e21 --- /dev/null +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonViewTest.kt @@ -0,0 +1,106 @@ +/* + * 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.activities.habits.list.views + +import android.support.test.filters.* +import android.support.test.runner.* +import org.hamcrest.CoreMatchers.* +import org.hamcrest.MatcherAssert.* +import org.isoron.uhabits.* +import org.isoron.uhabits.utils.* +import org.junit.* +import org.junit.runner.* + +@RunWith(AndroidJUnit4::class) +@MediumTest +class NumberButtonViewTest : BaseViewTest() { + + private val PATH = "habits/list/NumberButtonView" + private lateinit var view: NumberButtonView + var edited = false + + @Before + override fun setUp() { + super.setUp() + view = component.getNumberButtonViewFactory().create().apply { + units = "steps" + threshold = 100.0 + color = PaletteUtils.getAndroidTestColor(8) + onEdit = { edited = true } + } + measureView(view, dpToPixels(48), dpToPixels(48)) + } + + @Test + fun testFormatValue() { + assertThat(0.1235.toShortString(), equalTo("0.12")) + assertThat(0.1000.toShortString(), equalTo("0.1")) + assertThat(5.0.toShortString(), equalTo("5")) + assertThat(5.25.toShortString(), equalTo("5.25")) + assertThat(12.3456.toShortString(), equalTo("12.3")) + assertThat(123.123.toShortString(), equalTo("123")) + assertThat(321.2.toShortString(), equalTo("321")) + assertThat(4321.2.toShortString(), equalTo("4.3k")) + assertThat(54321.2.toShortString(), equalTo("54.3k")) + assertThat(654321.2.toShortString(), equalTo("654k")) + assertThat(7654321.2.toShortString(), equalTo("7.7M")) + assertThat(87654321.2.toShortString(), equalTo("87.7M")) + assertThat(987654321.2.toShortString(), equalTo("988M")) + assertThat(1987654321.2.toShortString(), equalTo("2.0G")) + } + + @Test + fun testRender_aboveThreshold() { + view.value = 500.0 + assertRenders(view, "$PATH/render_above.png") + } + + @Test + fun testRender_belowThreshold() { + view.value = 99.0 + assertRenders(view, "$PATH/render_below.png") + } + + @Test + fun testRender_zero() { + view.value = 0.0 + assertRenders(view, "$PATH/render_zero.png") + } + + @Test + fun testClick_shortToggleDisabled() { + prefs.isShortToggleEnabled = false + view.performClick() + assertFalse(edited) + } + + @Test + fun testClick_shortToggleEnabled() { + prefs.isShortToggleEnabled = true + view.performClick() + assertTrue(edited) + } + + @Test + fun testLongClick() { + view.performLongClick() + assertTrue(edited) + } +} \ No newline at end of file diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelViewTest.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelViewTest.kt new file mode 100644 index 000000000..3e07e8d84 --- /dev/null +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelViewTest.kt @@ -0,0 +1,105 @@ +/* + * 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.activities.habits.list.views + +import android.support.test.filters.* +import android.support.test.runner.* +import org.hamcrest.CoreMatchers.* +import org.hamcrest.MatcherAssert.* +import org.isoron.uhabits.* +import org.isoron.uhabits.utils.* +import org.junit.* +import org.junit.runner.* + +@RunWith(AndroidJUnit4::class) +@MediumTest +class NumberPanelViewTest : BaseViewTest() { + + private val PATH = "habits/list/NumberPanelView" + private lateinit var view: NumberPanelView + + @Before + override fun setUp() { + super.setUp() + prefs.isCheckmarkSequenceReversed = false + + val checkmarks = doubleArrayOf(1400.0, 5300.0, 0.0, + 14600.0, 2500.0, 45000.0) + + view = component.getNumberPanelViewFactory().create().apply { + values = checkmarks + buttonCount = 4 + color = PaletteUtils.getAndroidTestColor(7) + units = "steps" + threshold = 5000.0 + } + view.onAttachedToWindow() + measureView(view, dpToPixels(200), dpToPixels(200)) + } + + @After + public override fun tearDown() { + view.onDetachedFromWindow() + } + + @Test + fun testRender() { + assertRenders(view, "$PATH/render.png") + } + + @Test + fun testRender_withDifferentColor() { + view.color = PaletteUtils.getAndroidTestColor(1) + assertRenders(view, "$PATH/render_different_color.png") + } + + @Test + fun testRender_Reversed() { + prefs.isCheckmarkSequenceReversed = true + assertRenders(view, "$PATH/render_reversed.png") + } + + @Test + fun testRender_withOffset() { + view.dataOffset = 3 + assertRenders(view, "$PATH/render_offset.png") + } + + @Test + fun testEdit() { + var timestamps = LongArray(0) + view.onEdit = { timestamps += it } + view.buttons[0].performLongClick() + view.buttons[2].performLongClick() + view.buttons[3].performLongClick() + assertThat(timestamps, equalTo(longArrayOf(day(0), day(2), day(3)))) + } + + @Test + fun testEdit_withOffset() { + var timestamps = LongArray(0) + view.dataOffset = 3 + view.onEdit = { timestamps += it } + view.buttons[0].performLongClick() + view.buttons[2].performLongClick() + view.buttons[3].performLongClick() + assertThat(timestamps, equalTo(longArrayOf(day(3), day(5), day(6)))) + } +} \ No newline at end of file diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/AndroidThemeSwitcher.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/AndroidThemeSwitcher.java deleted file mode 100644 index 62b04c9bd..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/AndroidThemeSwitcher.java +++ /dev/null @@ -1,63 +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.activities; - -import android.support.annotation.*; - -import org.isoron.androidbase.activities.*; -import org.isoron.uhabits.R; -import org.isoron.uhabits.core.preferences.*; -import org.isoron.uhabits.core.ui.*; - -import javax.inject.*; - -@ActivityScope -public class AndroidThemeSwitcher extends ThemeSwitcher -{ - @NonNull - private final BaseActivity activity; - - @Inject - public AndroidThemeSwitcher(@NonNull BaseActivity activity, - @NonNull Preferences preferences) - { - super(preferences); - this.activity = activity; - } - - @Override - public void applyDarkTheme() - { - activity.setTheme(R.style.AppBaseThemeDark); - } - - @Override - public void applyLightTheme() - { - activity.setTheme(R.style.AppBaseTheme); - } - - @Override - public void applyPureBlackTheme() - { - activity.setTheme(R.style.AppBaseThemeDark_PureBlack); - - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/package-info.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/AndroidThemeSwitcher.kt similarity index 53% rename from uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/package-info.java rename to uhabits-android/src/main/java/org/isoron/uhabits/activities/AndroidThemeSwitcher.kt index f9fc65e4b..9c8c8f6b3 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/package-info.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/AndroidThemeSwitcher.kt @@ -17,8 +17,30 @@ * with this program. If not, see . */ -/** - * Provides custom views that are used primarily on {@link - * org.isoron.uhabits.activities.habits.show.ShowHabitActivity}. - */ -package org.isoron.uhabits.activities.habits.show.views; \ No newline at end of file +package org.isoron.uhabits.activities + +import org.isoron.androidbase.activities.* +import org.isoron.uhabits.* +import org.isoron.uhabits.core.preferences.* +import org.isoron.uhabits.core.ui.* +import javax.inject.* + +@ActivityScope +class AndroidThemeSwitcher +@Inject constructor( + private val activity: BaseActivity, + preferences: Preferences +) : ThemeSwitcher(preferences) { + + override fun applyDarkTheme() { + activity.setTheme(R.style.AppBaseThemeDark) + } + + override fun applyLightTheme() { + activity.setTheme(R.style.AppBaseTheme) + } + + override fun applyPureBlackTheme() { + activity.setTheme(R.style.AppBaseThemeDark_PureBlack) + } +} diff --git a/uhabits-core/src/main/java/org/isoron/uhabits/core/io/package-info.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitModule.kt similarity index 80% rename from uhabits-core/src/main/java/org/isoron/uhabits/core/io/package-info.java rename to uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitModule.kt index c7a4f0a20..c4d76d03c 100644 --- a/uhabits-core/src/main/java/org/isoron/uhabits/core/io/package-info.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitModule.kt @@ -17,7 +17,12 @@ * with this program. If not, see . */ -/** - * Provides classes that deal with importing from and exporting to files. - */ -package org.isoron.uhabits.core.io; \ No newline at end of file +package org.isoron.uhabits.activities + +import dagger.* +import org.isoron.uhabits.core.models.* + +@Module +class HabitModule(private val habit: Habit) { + @Provides fun getHabit() = habit +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitsActivity.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitsActivity.java deleted file mode 100644 index 3cb8a2e31..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitsActivity.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2017 Á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.activities; - -import android.content.*; -import android.net.*; -import android.os.*; -import android.support.annotation.*; - -import org.isoron.androidbase.activities.*; -import org.isoron.uhabits.*; -import org.isoron.uhabits.core.models.*; - -public abstract class HabitsActivity extends BaseActivity -{ - private HabitsActivityComponent component; - - private HabitsApplicationComponent appComponent; - - public HabitsActivityComponent getActivityComponent() - { - return component; - } - - public HabitsApplicationComponent getAppComponent() - { - return appComponent; - } - - @Override - protected void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - - appComponent = - ((HabitsApplication) getApplicationContext()).getComponent(); - - Habit habit = getHabitFromIntent(appComponent.getHabitList()); - - component = DaggerHabitsActivityComponent - .builder() - .activityModule(new ActivityModule(this)) - .habitModule(new HabitModule(habit)) - .habitsApplicationComponent(appComponent) - .build(); - - component.getThemeSwitcher().apply(); - } - - @Nullable - private Habit getHabitFromIntent(@NonNull HabitList habitList) - { - Uri data = getIntent().getData(); - if(data == null) return null; - - Habit habit = habitList.getById(ContentUris.parseId(data)); - if (habit == null) throw new RuntimeException("habit not found"); - - return habit; - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitsActivity.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitsActivity.kt new file mode 100644 index 000000000..6b4365f04 --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitsActivity.kt @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2017 Á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.activities + +import android.content.* +import android.os.* +import org.isoron.androidbase.activities.* +import org.isoron.uhabits.* +import org.isoron.uhabits.core.models.* + +abstract class HabitsActivity : BaseActivity() { + lateinit var component: HabitsActivityComponent + lateinit var appComponent: HabitsApplicationComponent + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + appComponent = (applicationContext as HabitsApplication).component + + val habit = getHabitFromIntent(appComponent.habitList) + ?: appComponent.modelFactory.buildHabit() + + component = DaggerHabitsActivityComponent + .builder() + .activityContextModule(ActivityContextModule(this)) + .baseActivityModule(BaseActivityModule(this)) + .habitModule(HabitModule(habit)) + .habitsApplicationComponent(appComponent) + .build() + + component.themeSwitcher.apply() + } + + private fun getHabitFromIntent(habitList: HabitList): Habit? { + val data = intent.data ?: return null + val habit = habitList.getById(ContentUris.parseId(data)) + ?: throw RuntimeException("habit not found") + return habit + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitsActivityComponent.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitsActivityComponent.java deleted file mode 100644 index 3a3bd38de..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitsActivityComponent.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2017 Á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.activities; - -import org.isoron.androidbase.activities.*; -import org.isoron.uhabits.*; -import org.isoron.uhabits.activities.about.*; -import org.isoron.uhabits.activities.common.dialogs.*; -import org.isoron.uhabits.activities.habits.list.*; -import org.isoron.uhabits.activities.habits.list.views.*; -import org.isoron.uhabits.activities.habits.show.*; -import org.isoron.uhabits.core.ui.*; -import org.isoron.uhabits.core.ui.screens.habits.list.*; - -import dagger.*; - -@ActivityScope -@Component(modules = { - ActivityModule.class, - AboutModule.class, - HabitsActivityModule.class, - ListHabitsModule.class, - ShowHabitModule.class, - HabitModule.class -}, dependencies = { HabitsApplicationComponent.class }) -public interface HabitsActivityComponent -{ - AboutRootView getAboutRootView(); - - AboutScreen getAboutScreen(); - - ColorPickerDialogFactory getColorPickerDialogFactory(); - - HabitCardListAdapter getHabitCardListAdapter(); - - ListHabitsBehavior getListHabitsBehavior(); - - ListHabitsController getListHabitsController(); - - ListHabitsMenu getListHabitsMenu(); - - ListHabitsRootView getListHabitsRootView(); - - ListHabitsScreen getListHabitsScreen(); - - ListHabitsSelectionMenu getListHabitsSelectionMenu(); - - ShowHabitScreen getShowHabitScreen(); - - ThemeSwitcher getThemeSwitcher(); -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitsActivityComponent.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitsActivityComponent.kt new file mode 100644 index 000000000..ed6906b74 --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitsActivityComponent.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2017 Á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.activities + +import dagger.* +import org.isoron.androidbase.activities.* +import org.isoron.uhabits.* +import org.isoron.uhabits.activities.about.* +import org.isoron.uhabits.activities.common.dialogs.* +import org.isoron.uhabits.activities.habits.list.* +import org.isoron.uhabits.activities.habits.list.views.* +import org.isoron.uhabits.activities.habits.show.* +import org.isoron.uhabits.core.ui.* +import org.isoron.uhabits.core.ui.screens.habits.list.* + +@ActivityScope +@Component(modules = arrayOf( + ActivityContextModule::class, + BaseActivityModule::class, + AboutModule::class, + HabitsActivityModule::class, + ListHabitsModule::class, + ShowHabitModule::class, + HabitModule::class +), dependencies = arrayOf(HabitsApplicationComponent::class)) +interface HabitsActivityComponent { + val aboutRootView: AboutRootView + val aboutScreen: AboutScreen + val colorPickerDialogFactory: ColorPickerDialogFactory + val habitCardListAdapter: HabitCardListAdapter + val listHabitsBehavior: ListHabitsBehavior + val listHabitsMenu: ListHabitsMenu + val listHabitsRootView: ListHabitsRootView + val listHabitsScreen: ListHabitsScreen + val listHabitsSelectionMenu: ListHabitsSelectionMenu + val showHabitScreen: ShowHabitScreen + val themeSwitcher: ThemeSwitcher +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitsActivityModule.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitsActivityModule.kt similarity index 73% rename from uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitsActivityModule.java rename to uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitsActivityModule.kt index 8263190e2..77145afbc 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitsActivityModule.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitsActivityModule.kt @@ -17,17 +17,14 @@ * with this program. If not, see . */ -package org.isoron.uhabits.activities; +package org.isoron.uhabits.activities -import org.isoron.androidbase.activities.*; -import org.isoron.uhabits.core.ui.*; - -import dagger.*; +import dagger.* +import org.isoron.androidbase.activities.* +import org.isoron.uhabits.core.ui.* @Module -public abstract class HabitsActivityModule -{ - @Binds - @ActivityScope - abstract ThemeSwitcher getThemeSwitcher(AndroidThemeSwitcher t); +abstract class HabitsActivityModule { + @Binds @ActivityScope + internal abstract fun getThemeSwitcher(t: AndroidThemeSwitcher): ThemeSwitcher } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitsDirFinder.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitsDirFinder.java deleted file mode 100644 index 8940bda7d..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitsDirFinder.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2017 Á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.activities; - -import android.support.annotation.*; - -import org.isoron.androidbase.*; -import org.isoron.uhabits.core.ui.screens.habits.list.*; -import org.isoron.uhabits.core.ui.screens.habits.show.*; - -import java.io.*; - -import javax.inject.*; - -public class HabitsDirFinder - implements ShowHabitMenuBehavior.System, ListHabitsBehavior.DirFinder -{ - private AndroidDirFinder androidDirFinder; - - @Inject - public HabitsDirFinder(@NonNull AndroidDirFinder androidDirFinder) - { - this.androidDirFinder = androidDirFinder; - } - - @Override - public File getCSVOutputDir() - { - return androidDirFinder.getFilesDir("CSV"); - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardViewHolder.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitsDirFinder.kt similarity index 62% rename from uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardViewHolder.java rename to uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitsDirFinder.kt index a9d35b01e..54bf67390 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardViewHolder.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitsDirFinder.kt @@ -16,16 +16,20 @@ * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ +package org.isoron.uhabits.activities -package org.isoron.uhabits.activities.habits.list.views; +import org.isoron.androidbase.* +import org.isoron.uhabits.core.ui.screens.habits.list.* +import org.isoron.uhabits.core.ui.screens.habits.show.* +import java.io.* +import javax.inject.* -import android.support.v7.widget.*; -import android.view.*; +class HabitsDirFinder @Inject +constructor( + private val androidDirFinder: AndroidDirFinder +) : ShowHabitMenuBehavior.System, ListHabitsBehavior.DirFinder { -public class HabitCardViewHolder extends RecyclerView.ViewHolder -{ - public HabitCardViewHolder(View itemView) - { - super(itemView); + override fun getCSVOutputDir(): File { + return androidDirFinder.getFilesDir("CSV")!! } } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/about/AboutActivity.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/about/AboutActivity.java index 408844c7b..fc098b3c9 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/about/AboutActivity.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/about/AboutActivity.java @@ -33,8 +33,8 @@ public class AboutActivity extends HabitsActivity protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - AboutScreen screen = getActivityComponent().getAboutScreen(); - screen.setRootView(getActivityComponent().getAboutRootView()); + AboutScreen screen = getComponent().getAboutScreen(); + screen.setRootView(getComponent().getAboutRootView()); setScreen(screen); } } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/about/package-info.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/about/package-info.java deleted file mode 100644 index e519806ab..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/about/package-info.java +++ /dev/null @@ -1,23 +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 . - */ - -/** - * Provides activity that shows information about the app. - */ -package org.isoron.uhabits.activities.about; \ No newline at end of file diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/NumberPickerFactory.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/NumberPickerFactory.kt new file mode 100644 index 000000000..a14ae4dbe --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/NumberPickerFactory.kt @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2017 Á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.activities.common.dialogs + +import android.content.* +import android.support.v7.app.* +import android.text.* +import android.view.* +import android.view.inputmethod.* +import android.widget.* +import org.isoron.androidbase.activities.* +import org.isoron.androidbase.utils.* +import org.isoron.uhabits.* +import org.isoron.uhabits.core.ui.screens.habits.list.* +import javax.inject.* + +class NumberPickerFactory +@Inject constructor( + @ActivityContext private val context: Context +) { + fun create(value: Double, + unit: String, + callback: ListHabitsBehavior.NumberPickerCallback): AlertDialog { + + val inflater = LayoutInflater.from(context) + val view = inflater.inflate(R.layout.number_picker_dialog, null) + + val picker = view.findViewById(R.id.picker) as NumberPicker + val picker2 = view.findViewById(R.id.picker2) as NumberPicker + val tvUnit = view.findViewById(R.id.tvUnit) as TextView + + val intValue = Math.round(value * 100).toInt() + + picker.minValue = 0 + picker.maxValue = Integer.MAX_VALUE / 100 + picker.value = intValue / 100 + picker.wrapSelectorWheel = false + + picker2.minValue = 0 + picker2.maxValue = 19 + picker2.setFormatter { v -> String.format("%02d", 5 * v) } + picker2.value = intValue % 100 / 5 + refreshInitialValue(picker2) + + tvUnit.text = unit + + val dialog = AlertDialog.Builder(context) + .setView(view) + .setTitle(R.string.change_value) + .setPositiveButton(android.R.string.ok) { _, _ -> + picker.clearFocus() + val v = picker.value + 0.05 * picker2.value + callback.onNumberPicked(v) + } + .create() + + InterfaceUtils.setupEditorAction(picker) { _, actionId, _ -> + if (actionId == EditorInfo.IME_ACTION_DONE) + dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick() + false + } + + return dialog + } + + private fun refreshInitialValue(picker: NumberPicker) { + // Workaround for Android bug: + // https://code.google.com/p/android/issues/detail?id=35482 + val f = NumberPicker::class.java.getDeclaredField("mInputText") + f.isAccessible = true + val inputText = f.get(picker) as EditText + inputText.filters = arrayOfNulls(0) + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/BarChart.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/BarChart.java index 3d053ef5a..a81dec50a 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/BarChart.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/BarChart.java @@ -361,7 +361,7 @@ public class BarChart extends ScrollableChart if (value / 1000 >= target) activeColor = primaryColor; - String label = NumberButtonView.formatValue(value / 1000); + String label = NumberButtonViewKt.toShortString(value / 1000); Rect rText = new Rect(); pText.getTextBounds(label, 0, label.length(), rText); diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/RingView.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/RingView.java index ac3d72890..0a29ca599 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/RingView.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/RingView.java @@ -114,13 +114,18 @@ public class RingView extends View public void setBackgroundColor(int backgroundColor) { this.backgroundColor = backgroundColor; - postInvalidate(); + invalidate(); } public void setColor(int color) { this.color = color; - postInvalidate(); + invalidate(); + } + + public int getColor() + { + return color; } public void setIsTransparencyEnabled(boolean isTransparencyEnabled) @@ -131,19 +136,19 @@ public class RingView extends View public void setPercentage(float percentage) { this.percentage = percentage; - postInvalidate(); + invalidate(); } public void setPrecision(float precision) { this.precision = precision; - postInvalidate(); + invalidate(); } public void setText(String text) { this.text = text; - postInvalidate(); + invalidate(); } public void setTextSize(float textSize) @@ -154,7 +159,7 @@ public class RingView extends View public void setThickness(float thickness) { this.thickness = thickness; - postInvalidate(); + invalidate(); } @Override @@ -254,4 +259,14 @@ public class RingView extends View Bitmap.createBitmap(diameter, diameter, Bitmap.Config.ARGB_8888); cacheCanvas = new Canvas(drawingCache); } + + public float getPercentage() + { + return percentage; + } + + public float getPrecision() + { + return precision; + } } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/ScrollableChart.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/ScrollableChart.java index c488cb17a..ae880c264 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/ScrollableChart.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/ScrollableChart.java @@ -177,7 +177,7 @@ public abstract class ScrollableChart extends View return detector.onTouchEvent(event); } - public void setDirection(int direction) + public void setScrollDirection(int direction) { if (direction != 1 && direction != -1) throw new IllegalArgumentException(); diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/TaskProgressBar.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/TaskProgressBar.kt new file mode 100644 index 000000000..13a23fc07 --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/TaskProgressBar.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2017 Á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.activities.common.views + +import android.content.* +import android.widget.* +import org.isoron.androidbase.activities.* +import org.isoron.uhabits.core.tasks.* + +class TaskProgressBar( + context: Context, + private val runner: TaskRunner +) : ProgressBar( + context, + null, + android.R.attr.progressBarStyleHorizontal +), TaskRunner.Listener { + + init { + visibility = BaseRootView.GONE + isIndeterminate = true + } + + override fun onTaskStarted(task: Task?) = update() + override fun onTaskFinished(task: Task?) = update() + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + runner.addListener(this) + update() + } + + override fun onDetachedFromWindow() { + runner.removeListener(this) + super.onDetachedFromWindow() + } + + fun update() { + val callback = { + val activeTaskCount = runner.activeTaskCount + val newVisibility = when (activeTaskCount) { + 0 -> GONE + else -> VISIBLE + } + if (visibility != newVisibility) visibility = newVisibility + } + postDelayed(callback, 500) + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/package-info.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/package-info.java deleted file mode 100644 index 1d50f26f0..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/package-info.java +++ /dev/null @@ -1,23 +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 . - */ - -/** - * Provides views that are used across the app, such as RingView. - */ -package org.isoron.uhabits.activities.common.views; \ No newline at end of file diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/edit/EditHabitDialog.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/edit/EditHabitDialog.java index 5553ad816..55ea8e691 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/edit/EditHabitDialog.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/edit/EditHabitDialog.java @@ -93,7 +93,7 @@ public class EditHabitDialog extends AppCompatDialogFragment HabitsActivity activity = (HabitsActivity) getActivity(); colorPickerDialogFactory = - activity.getActivityComponent().getColorPickerDialogFactory(); + activity.getComponent().getColorPickerDialogFactory(); } @Override diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/edit/package-info.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/edit/package-info.java deleted file mode 100644 index 3d6e4e626..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/edit/package-info.java +++ /dev/null @@ -1,23 +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 . - */ - -/** - * Provides dialogs for editing habits and related classes. - */ -package org.isoron.uhabits.activities.habits.edit; \ No newline at end of file diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.java deleted file mode 100644 index e81fb13fe..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.java +++ /dev/null @@ -1,102 +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.activities.habits.list; - -import android.os.*; - -import org.isoron.uhabits.activities.*; -import org.isoron.uhabits.activities.habits.list.views.*; -import org.isoron.uhabits.core.preferences.*; -import org.isoron.uhabits.core.ui.*; -import org.isoron.uhabits.core.utils.*; - -/** - * Activity that allows the user to see and modify the list of habits. - */ -public class ListHabitsActivity extends HabitsActivity -{ - private HabitCardListAdapter adapter; - - private ListHabitsRootView rootView; - - private ListHabitsScreen screen; - - private boolean pureBlack; - - private Preferences prefs; - - private MidnightTimer midnightTimer; - - @Override - protected void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - midnightTimer = getAppComponent().getMidnightTimer(); - HabitsActivityComponent component = getActivityComponent(); - - ListHabitsMenu menu = component.getListHabitsMenu(); - ListHabitsSelectionMenu selectionMenu = component.getListHabitsSelectionMenu(); - ListHabitsController controller = component.getListHabitsController(); - - adapter = component.getHabitCardListAdapter(); - rootView = component.getListHabitsRootView(); - screen = component.getListHabitsScreen(); - - prefs = getAppComponent().getPreferences(); - pureBlack = prefs.isPureBlackEnabled(); - - screen.setMenu(menu); - screen.setController(controller); - screen.setSelectionMenu(selectionMenu); - rootView.setController(controller, selectionMenu); - -// if(prefs.isSyncEnabled()) -// startService(new Intent(this, SyncService.class)); - - setScreen(screen); - controller.onStartup(); - } - - @Override - protected void onPause() - { - midnightTimer.onPause(); - screen.onDettached(); - adapter.cancelRefresh(); - super.onPause(); - } - - @Override - protected void onResume() - { - adapter.refresh(); - screen.onAttached(); - rootView.postInvalidate(); - midnightTimer.onResume(); - - if (prefs.getTheme() == ThemeSwitcher.THEME_DARK && - prefs.isPureBlackEnabled() != pureBlack) - { - restartWithFade(ListHabitsActivity.class); - } - - super.onResume(); - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.kt new file mode 100644 index 000000000..a72b62d5b --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.kt @@ -0,0 +1,70 @@ +/* + * 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.activities.habits.list + +import android.os.* +import org.isoron.uhabits.activities.* +import org.isoron.uhabits.activities.habits.list.views.* +import org.isoron.uhabits.core.preferences.* +import org.isoron.uhabits.core.ui.ThemeSwitcher.* +import org.isoron.uhabits.core.utils.* + +class ListHabitsActivity : HabitsActivity() { + + var pureBlack: Boolean = false + lateinit var adapter: HabitCardListAdapter + lateinit var rootView: ListHabitsRootView + lateinit var screen: ListHabitsScreen + lateinit var prefs: Preferences + lateinit var midnightTimer: MidnightTimer + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + prefs = appComponent.preferences + pureBlack = prefs.isPureBlackEnabled + midnightTimer = appComponent.midnightTimer + rootView = component.listHabitsRootView + screen = component.listHabitsScreen + adapter = component.habitCardListAdapter + + setScreen(screen) + component.listHabitsBehavior.onStartup() + } + + override fun onPause() { + midnightTimer.onPause() + screen.onDettached() + adapter.cancelRefresh() + super.onPause() + } + + override fun onResume() { + adapter.refresh() + screen.onAttached() + rootView.postInvalidate() + midnightTimer.onResume() + + if (prefs.theme == THEME_DARK && prefs.isPureBlackEnabled != pureBlack) { + restartWithFade(ListHabitsActivity::class.java) + } + + super.onResume() + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java deleted file mode 100644 index f5e8fb86f..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java +++ /dev/null @@ -1,164 +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.activities.habits.list; - -import android.support.annotation.*; - -import org.isoron.androidbase.activities.*; -import org.isoron.uhabits.R; -import org.isoron.uhabits.activities.habits.list.views.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.tasks.*; -import org.isoron.uhabits.core.ui.screens.habits.list.*; -import org.isoron.uhabits.tasks.*; - -import java.io.*; - -import javax.inject.*; - -@ActivityScope -public class ListHabitsController - implements HabitCardListController.HabitListener -{ - @NonNull - private final ListHabitsBehavior behavior; - - @NonNull - private final ListHabitsScreen screen; - - @NonNull - private final HabitCardListAdapter adapter; - - @NonNull - private final TaskRunner taskRunner; - - private ImportDataTaskFactory importTaskFactory; - - private ExportDBTaskFactory exportDBFactory; - - @Inject - public ListHabitsController(@NonNull ListHabitsBehavior behavior, - @NonNull HabitCardListAdapter adapter, - @NonNull ListHabitsScreen screen, - @NonNull TaskRunner taskRunner, - @NonNull ImportDataTaskFactory importTaskFactory, - @NonNull ExportDBTaskFactory exportDBFactory) - { - this.behavior = behavior; - this.adapter = adapter; - this.screen = screen; - this.taskRunner = taskRunner; - this.importTaskFactory = importTaskFactory; - this.exportDBFactory = exportDBFactory; - } - - public void onEdit(@NonNull Habit habit, long timestamp) - { - behavior.onEdit(habit, timestamp); - } - - public void onExportCSV() - { - behavior.onExportCSV(); - } - - public void onExportDB() - { - taskRunner.execute(exportDBFactory.create(filename -> - { - if (filename != null) screen.showSendFileScreen(filename); - else screen.showMessage(R.string.could_not_export); - })); - } - - @Override - public void onHabitClick(@NonNull Habit h) - { - behavior.onClickHabit(h); - } - - @Override - public void onHabitReorder(@NonNull Habit from, @NonNull Habit to) - { - behavior.onReorderHabit(from, to); - } - - public void onImportData(@NonNull File file, - @NonNull OnFinishedListener finishedListener) - { - taskRunner.execute(importTaskFactory.create(file, result -> - { - switch (result) - { - case ImportDataTask.SUCCESS: - adapter.refresh(); - screen.showMessage(R.string.habits_imported); - break; - - case ImportDataTask.NOT_RECOGNIZED: - screen.showMessage(R.string.file_not_recognized); - break; - - default: - screen.showMessage(R.string.could_not_import); - break; - } - - finishedListener.onFinish(); - })); - } - - public void onInvalidEdit() - { - screen.showMessage(R.string.long_press_to_edit); - } - - @Override - public void onInvalidToggle() - { - screen.showMessage(R.string.long_press_to_toggle); - } - - public void onRepairDB() - { - behavior.onRepairDB(); - } - - public void onSendBugReport() - { - behavior.onSendBugReport(); - } - - public void onStartup() - { - behavior.onStartup(); - } - - @Override - public void onToggle(@NonNull Habit habit, long timestamp) - { - behavior.onToggle(habit, timestamp); - } - - public interface OnFinishedListener - { - void onFinish(); - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsMenu.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsMenu.java deleted file mode 100644 index 0c8335284..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsMenu.java +++ /dev/null @@ -1,129 +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.activities.habits.list; - -import android.support.annotation.*; -import android.view.*; - -import org.isoron.androidbase.activities.*; -import org.isoron.uhabits.R; -import org.isoron.uhabits.core.preferences.*; -import org.isoron.uhabits.core.ui.*; -import org.isoron.uhabits.core.ui.screens.habits.list.*; - -import javax.inject.*; - -@ActivityScope -public class ListHabitsMenu extends BaseMenu -{ - - @NonNull - private final ListHabitsMenuBehavior behavior; - - private final Preferences preferences; - - private ThemeSwitcher themeSwitcher; - - @Inject - public ListHabitsMenu(@NonNull BaseActivity activity, - @NonNull Preferences preferences, - @NonNull ThemeSwitcher themeSwitcher, - @NonNull ListHabitsMenuBehavior behavior) - { - super(activity); - this.preferences = preferences; - this.themeSwitcher = themeSwitcher; - this.behavior = behavior; - } - - @Override - public void onCreate(@NonNull Menu menu) - { - MenuItem nightModeItem = menu.findItem(R.id.actionToggleNightMode); - MenuItem hideArchivedItem = menu.findItem(R.id.actionHideArchived); - MenuItem hideCompletedItem = menu.findItem(R.id.actionHideCompleted); - nightModeItem.setChecked(themeSwitcher.isNightMode()); - hideArchivedItem.setChecked(!preferences.getShowArchived()); - hideCompletedItem.setChecked(!preferences.getShowCompleted()); - } - - @Override - public boolean onItemSelected(@NonNull MenuItem item) - { - switch (item.getItemId()) - { - case R.id.actionToggleNightMode: - behavior.onToggleNightMode(); - return true; - - case R.id.actionAdd: - behavior.onCreateHabit(); - return true; - - case R.id.actionFAQ: - behavior.onViewFAQ(); - return true; - - case R.id.actionAbout: - behavior.onViewAbout(); - return true; - - case R.id.actionSettings: - behavior.onViewSettings(); - return true; - - case R.id.actionHideArchived: - behavior.onToggleShowArchived(); - invalidate(); - return true; - - case R.id.actionHideCompleted: - behavior.onToggleShowCompleted(); - invalidate(); - return true; - - case R.id.actionSortColor: - behavior.onSortByColor(); - return true; - - case R.id.actionSortManual: - behavior.onSortByManually(); - return true; - - case R.id.actionSortName: - behavior.onSortByName(); - return true; - - case R.id.actionSortScore: - behavior.onSortByScore(); - return true; - - default: - return false; - } - } - - @Override - protected int getMenuResourceId() - { - return R.menu.list_habits; - } - -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsMenu.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsMenu.kt new file mode 100644 index 000000000..572ae0af1 --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsMenu.kt @@ -0,0 +1,111 @@ +/* + * 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.activities.habits.list + +import android.view.* +import org.isoron.androidbase.activities.* +import org.isoron.uhabits.* +import org.isoron.uhabits.core.preferences.* +import org.isoron.uhabits.core.ui.* +import org.isoron.uhabits.core.ui.screens.habits.list.* +import javax.inject.* + +@ActivityScope +class ListHabitsMenu @Inject constructor( + activity: BaseActivity, + private val preferences: Preferences, + private val themeSwitcher: ThemeSwitcher, + private val behavior: ListHabitsMenuBehavior +) : BaseMenu(activity) { + + override fun onCreate(menu: Menu) { + val nightModeItem = menu.findItem(R.id.actionToggleNightMode) + val hideArchivedItem = menu.findItem(R.id.actionHideArchived) + val hideCompletedItem = menu.findItem(R.id.actionHideCompleted) + nightModeItem.isChecked = themeSwitcher.isNightMode + hideArchivedItem.isChecked = !preferences.showArchived + hideCompletedItem.isChecked = !preferences.showCompleted + } + + override fun onItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.actionToggleNightMode -> { + behavior.onToggleNightMode() + return true + } + + R.id.actionAdd -> { + behavior.onCreateHabit() + return true + } + + R.id.actionFAQ -> { + behavior.onViewFAQ() + return true + } + + R.id.actionAbout -> { + behavior.onViewAbout() + return true + } + + R.id.actionSettings -> { + behavior.onViewSettings() + return true + } + + R.id.actionHideArchived -> { + behavior.onToggleShowArchived() + invalidate() + return true + } + + R.id.actionHideCompleted -> { + behavior.onToggleShowCompleted() + invalidate() + return true + } + + R.id.actionSortColor -> { + behavior.onSortByColor() + return true + } + + R.id.actionSortManual -> { + behavior.onSortByManually() + return true + } + + R.id.actionSortName -> { + behavior.onSortByName() + return true + } + + R.id.actionSortScore -> { + behavior.onSortByScore() + return true + } + + else -> return false + } + } + + override fun getMenuResourceId() = R.menu.list_habits +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsModule.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsModule.java deleted file mode 100644 index 09e8d4775..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsModule.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2017 Á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.activities.habits.list; - -import android.content.*; -import android.support.annotation.*; - -import org.isoron.androidbase.*; -import org.isoron.uhabits.activities.*; -import org.isoron.uhabits.activities.habits.list.views.*; -import org.isoron.uhabits.core.ui.screens.habits.list.*; - -import javax.inject.*; - -import dagger.*; - -class BugReporterProxy extends AndroidBugReporter - implements ListHabitsBehavior.BugReporter -{ - @Inject - public BugReporterProxy(@AppContext @NonNull Context context) - { - super(context); - } -} - -@Module -public abstract class ListHabitsModule -{ - @Binds - abstract ListHabitsMenuBehavior.Adapter getAdapter(HabitCardListAdapter adapter); - - @Binds - abstract ListHabitsBehavior.BugReporter getBugReporter(BugReporterProxy proxy); - - @Binds - abstract ListHabitsMenuBehavior.Screen getMenuScreen(ListHabitsScreen screen); - - @Binds - abstract ListHabitsBehavior.Screen getScreen(ListHabitsScreen screen); - - @Binds - abstract ListHabitsSelectionMenuBehavior.Adapter getSelMenuAdapter( - HabitCardListAdapter adapter); - - @Binds - abstract ListHabitsSelectionMenuBehavior.Screen getSelMenuScreen( - ListHabitsScreen screen); - - @Binds - abstract ListHabitsBehavior.DirFinder getSystem(HabitsDirFinder system); -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsModule.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsModule.kt new file mode 100644 index 000000000..55ffc22cf --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsModule.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2017 Á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.activities.habits.list + +import android.content.* +import dagger.* +import org.isoron.androidbase.* +import org.isoron.uhabits.activities.* +import org.isoron.uhabits.activities.habits.list.views.* +import org.isoron.uhabits.core.ui.screens.habits.list.* +import javax.inject.* + +class BugReporterProxy +@Inject constructor( + @AppContext context: Context +) : AndroidBugReporter(context), ListHabitsBehavior.BugReporter + +@Module +abstract class ListHabitsModule { + + @Binds + abstract fun getAdapter(adapter: HabitCardListAdapter): ListHabitsMenuBehavior.Adapter + + @Binds + abstract fun getBugReporter(proxy: BugReporterProxy): ListHabitsBehavior.BugReporter + + @Binds + abstract fun getMenuScreen(screen: ListHabitsScreen): ListHabitsMenuBehavior.Screen + + @Binds + abstract fun getScreen(screen: ListHabitsScreen): ListHabitsBehavior.Screen + + @Binds + abstract fun getSelMenuAdapter(adapter: HabitCardListAdapter): ListHabitsSelectionMenuBehavior.Adapter + + @Binds + abstract fun getSelMenuScreen(screen: ListHabitsScreen): ListHabitsSelectionMenuBehavior.Screen + + @Binds + abstract fun getSystem(system: HabitsDirFinder): ListHabitsBehavior.DirFinder +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsRootView.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsRootView.java deleted file mode 100644 index 13d31d1d5..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsRootView.java +++ /dev/null @@ -1,198 +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.activities.habits.list; - -import android.content.*; -import android.support.annotation.*; -import android.support.v7.widget.Toolbar; -import android.view.*; -import android.widget.*; - -import org.isoron.androidbase.activities.*; -import org.isoron.androidbase.utils.*; -import org.isoron.uhabits.R; -import org.isoron.uhabits.activities.common.views.*; -import org.isoron.uhabits.activities.habits.list.views.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.tasks.*; -import org.isoron.uhabits.core.ui.screens.habits.list.*; - -import javax.inject.*; - -import butterknife.*; - -import static org.isoron.androidbase.utils.InterfaceUtils.*; - -@ActivityScope -public class ListHabitsRootView extends BaseRootView - implements ModelObservable.Listener, TaskRunner.Listener -{ - public static final int MAX_CHECKMARK_COUNT = 60; - - @BindView(R.id.listView) - HabitCardListView listView; - - @BindView(R.id.llEmpty) - ViewGroup llEmpty; - - @BindView(R.id.tvStarEmpty) - TextView tvStarEmpty; - - @BindView(R.id.toolbar) - Toolbar toolbar; - - @BindView(R.id.progressBar) - ProgressBar progressBar; - - @BindView(R.id.hintView) - HintView hintView; - - @BindView(R.id.header) - HeaderView header; - - @NonNull - private final HabitCardListAdapter listAdapter; - - private final TaskRunner runner; - - @Inject - public ListHabitsRootView(@ActivityContext Context context, - @NonNull HintListFactory hintListFactory, - @NonNull HabitCardListAdapter listAdapter, - @NonNull TaskRunner runner) - { - super(context); - addView(inflate(getContext(), R.layout.list_habits, null)); - ButterKnife.bind(this); - - this.listAdapter = listAdapter; - listView.setAdapter(listAdapter); - listAdapter.setListView(listView); - - this.runner = runner; - progressBar.setIndeterminate(true); - tvStarEmpty.setTypeface(InterfaceUtils.getFontAwesome(getContext())); - - String hints[] = - getContext().getResources().getStringArray(R.array.hints); - HintList hintList = hintListFactory.create(hints); - hintView.setHints(hintList); - - initToolbar(); - } - - @NonNull - @Override - public Toolbar getToolbar() - { - return toolbar; - } - - @Override - public void onModelChange() - { - updateEmptyView(); - } - - @Override - public void onTaskFinished(Task task) - { - updateProgressBar(); - } - - @Override - public void onTaskStarted(Task task) - { - updateProgressBar(); - } - - public void setController(@NonNull ListHabitsController controller, - @NonNull ListHabitsSelectionMenu menu) - { - HabitCardListController listController = - new HabitCardListController(listAdapter); - - listController.setHabitListener(controller); - listController.setSelectionListener(menu); - listView.setController(listController); - menu.setListController(listController); - header.setScrollController(new ScrollableChart.ScrollController() - { - @Override - public void onDataOffsetChanged(int newDataOffset) - { - listView.setDataOffset(newDataOffset); - } - }); - } - - @Override - protected void onAttachedToWindow() - { - super.onAttachedToWindow(); - runner.addListener(this); - updateProgressBar(); - listAdapter.getObservable().addListener(this); - } - - @Override - protected void onDetachedFromWindow() - { - listAdapter.getObservable().removeListener(this); - runner.removeListener(this); - super.onDetachedFromWindow(); - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) - { - int count = getCheckmarkCount(); - header.setButtonCount(count); - header.setMaxDataOffset(Math.max(MAX_CHECKMARK_COUNT - count, 0)); - listView.setCheckmarkCount(count); - super.onSizeChanged(w, h, oldw, oldh); - } - - private int getCheckmarkCount() - { - float nameWidth = getDimension(getContext(), R.dimen.habitNameWidth); - float labelWidth = Math.max(getMeasuredWidth() / 3, nameWidth); - float buttonWidth = getDimension(getContext(), R.dimen.checkmarkWidth); - return Math.min(MAX_CHECKMARK_COUNT, Math.max(0, - (int) ((getMeasuredWidth() - labelWidth) / buttonWidth))); - } - - private void updateEmptyView() - { - llEmpty.setVisibility( - listAdapter.getItemCount() > 0 ? View.GONE : View.VISIBLE); - } - - private void updateProgressBar() - { - postDelayed(() -> - { - int activeTaskCount = runner.getActiveTaskCount(); - int newVisibility = activeTaskCount > 0 ? VISIBLE : GONE; - if (progressBar.getVisibility() != newVisibility) - progressBar.setVisibility(newVisibility); - }, 500); - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsRootView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsRootView.kt new file mode 100644 index 000000000..25377db0c --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsRootView.kt @@ -0,0 +1,135 @@ +/* + * 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.activities.habits.list + +import android.content.* +import android.os.Build.VERSION.* +import android.os.Build.VERSION_CODES.* +import android.support.v7.widget.Toolbar +import android.view.ViewGroup.LayoutParams.* +import android.widget.* +import org.isoron.androidbase.activities.* +import org.isoron.uhabits.* +import org.isoron.uhabits.activities.common.views.* +import org.isoron.uhabits.activities.habits.list.views.* +import org.isoron.uhabits.core.models.* +import org.isoron.uhabits.core.preferences.* +import org.isoron.uhabits.core.tasks.* +import org.isoron.uhabits.core.ui.screens.habits.list.* +import org.isoron.uhabits.core.utils.* +import org.isoron.uhabits.utils.* +import java.lang.Math.* +import javax.inject.* + +const val MAX_CHECKMARK_COUNT = 60 + +@ActivityScope +class ListHabitsRootView @Inject constructor( + @ActivityContext context: Context, + hintListFactory: HintListFactory, + preferences: Preferences, + midnightTimer: MidnightTimer, + runner: TaskRunner, + private val listAdapter: HabitCardListAdapter, + habitCardListViewFactory: HabitCardListViewFactory +) : BaseRootView(context), ModelObservable.Listener { + + val listView: HabitCardListView = habitCardListViewFactory.create() + val llEmpty = EmptyListView(context) + val tbar = buildToolbar() + val progressBar = TaskProgressBar(context, runner) + val hintView: HintView + val header = HeaderView(context, preferences, midnightTimer) + + init { + val hints = resources.getStringArray(R.array.hints) + val hintList = hintListFactory.create(hints) + hintView = HintView(context, hintList) + + addView(RelativeLayout(context).apply { + background = sres.getDrawable(R.attr.windowBackgroundColor) + addAtTop(tbar) + addBelow(header, tbar) + addBelow(listView, header, height = MATCH_PARENT) + addBelow(llEmpty, header, height = MATCH_PARENT) + addBelow(progressBar, header) { + it.topMargin = dp(-6.0f).toInt() + } + addAtBottom(hintView) + if (SDK_INT < LOLLIPOP) { + addBelow(ShadowView(context), tbar) + addBelow(ShadowView(context), header) + } + }, MATCH_PARENT, MATCH_PARENT) + + listAdapter.setListView(listView) + initToolbar() + } + + override fun getToolbar(): Toolbar { + return tbar + } + + override fun onModelChange() { + updateEmptyView() + } + + private fun setupControllers() { + header.setScrollController(object : ScrollableChart.ScrollController { + override fun onDataOffsetChanged(newDataOffset: Int) { + listView.dataOffset = newDataOffset + } + }) + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + setupControllers() + listAdapter.observable.addListener(this) + } + + override fun onDetachedFromWindow() { + listAdapter.observable.removeListener(this) + super.onDetachedFromWindow() + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + val count = getCheckmarkCount() + header.buttonCount = count + header.setMaxDataOffset(max(MAX_CHECKMARK_COUNT - count, 0)) + listView.checkmarkCount = count + super.onSizeChanged(w, h, oldw, oldh) + } + + private fun getCheckmarkCount(): Int { + val nameWidth = dim(R.dimen.habitNameWidth) + val buttonWidth = dim(R.dimen.checkmarkWidth) + val labelWidth = max((measuredWidth / 3).toFloat(), nameWidth) + val buttonCount = ((measuredWidth - labelWidth) / buttonWidth).toInt() + return min(MAX_CHECKMARK_COUNT, max(0, buttonCount)) + } + + private fun updateEmptyView() { + llEmpty.visibility = when (listAdapter.itemCount) { + 0 -> VISIBLE + else -> GONE + } + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java deleted file mode 100644 index 7882b9600..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java +++ /dev/null @@ -1,441 +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.activities.habits.list; - -import android.app.*; -import android.content.*; -import android.net.*; -import android.support.annotation.*; -import android.support.v7.app.AlertDialog; -import android.text.*; -import android.view.*; -import android.widget.*; - -import org.isoron.androidbase.activities.*; -import org.isoron.androidbase.utils.*; -import org.isoron.uhabits.*; -import org.isoron.uhabits.activities.common.dialogs.*; -import org.isoron.uhabits.activities.habits.edit.*; -import org.isoron.uhabits.core.commands.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.preferences.*; -import org.isoron.uhabits.core.ui.*; -import org.isoron.uhabits.core.ui.callbacks.*; -import org.isoron.uhabits.core.ui.screens.habits.list.*; -import org.isoron.uhabits.intents.*; - -import java.io.*; -import java.lang.reflect.*; -import java.util.*; - -import javax.inject.*; - -import static android.content.DialogInterface.BUTTON_POSITIVE; -import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE; - -@ActivityScope -public class ListHabitsScreen extends BaseScreen - implements CommandRunner.Listener, ListHabitsBehavior.Screen, - ListHabitsMenuBehavior.Screen, - ListHabitsSelectionMenuBehavior.Screen -{ - public static final int REQUEST_OPEN_DOCUMENT = 6; - - public static final int REQUEST_SETTINGS = 7; - - public static final int RESULT_BUG_REPORT = 4; - - public static final int RESULT_EXPORT_CSV = 2; - - public static final int RESULT_EXPORT_DB = 3; - - public static final int RESULT_IMPORT_DATA = 1; - - public static final int RESULT_REPAIR_DB = 5; - - @Nullable - private ListHabitsController controller; - - @NonNull - private final IntentFactory intentFactory; - - @NonNull - private final CommandRunner commandRunner; - - @NonNull - private final ConfirmDeleteDialogFactory confirmDeleteDialogFactory; - - @NonNull - private final ColorPickerDialogFactory colorPickerFactory; - - @NonNull - private final EditHabitDialogFactory editHabitDialogFactory; - - @NonNull - private final ThemeSwitcher themeSwitcher; - - @NonNull - private Preferences prefs; - - @Inject - public ListHabitsScreen(@NonNull BaseActivity activity, - @NonNull CommandRunner commandRunner, - @NonNull ListHabitsRootView rootView, - @NonNull IntentFactory intentFactory, - @NonNull ThemeSwitcher themeSwitcher, - @NonNull ConfirmDeleteDialogFactory confirmDeleteDialogFactory, - @NonNull ColorPickerDialogFactory colorPickerFactory, - @NonNull EditHabitDialogFactory editHabitDialogFactory, - @NonNull Preferences prefs) - { - super(activity); - setRootView(rootView); - this.prefs = prefs; - this.colorPickerFactory = colorPickerFactory; - this.commandRunner = commandRunner; - this.confirmDeleteDialogFactory = confirmDeleteDialogFactory; - this.editHabitDialogFactory = editHabitDialogFactory; - this.intentFactory = intentFactory; - this.themeSwitcher = themeSwitcher; - } - - @StringRes - private Integer getExecuteString(@NonNull Command command) - { - if(command instanceof ArchiveHabitsCommand) - return R.string.toast_habit_archived; - - if(command instanceof ChangeHabitColorCommand) - return R.string.toast_habit_changed; - - if(command instanceof CreateHabitCommand) - return R.string.toast_habit_created; - - if(command instanceof DeleteHabitsCommand) - return R.string.toast_habit_deleted; - - if(command instanceof EditHabitCommand) - return R.string.toast_habit_changed; - - if(command instanceof UnarchiveHabitsCommand) - return R.string.toast_habit_unarchived; - - return null; - } - - public void onAttached() - { - commandRunner.addListener(this); - } - - @Override - public void onCommandExecuted(@NonNull Command command, - @Nullable Long refreshKey) - { - if (command.isRemote()) return; - showMessage(getExecuteString(command)); - } - - public void onDettached() - { - commandRunner.removeListener(this); - } - - @Override - public void onResult(int requestCode, int resultCode, Intent data) - { - if (requestCode == REQUEST_OPEN_DOCUMENT) - onOpenDocumentResult(resultCode, data); - - if (requestCode == REQUEST_SETTINGS) onSettingsResult(resultCode); - } - - public void setController(@Nullable ListHabitsController controller) - { - this.controller = controller; - } - - @Override - public void applyTheme() - { - themeSwitcher.apply(); - activity.restartWithFade(ListHabitsActivity.class); - } - - @Override - public void showAboutScreen() - { - Intent intent = intentFactory.startAboutActivity(activity); - activity.startActivity(intent); - } - - @Override - public void showColorPicker(int defaultColor, - @NonNull OnColorPickedCallback callback) { - ColorPickerDialog picker = colorPickerFactory.create(defaultColor); - picker.setListener(callback); - activity.showDialog(picker, "picker"); - } - - public void showCreateBooleanHabitScreen() - { - EditHabitDialog dialog; - dialog = editHabitDialogFactory.createBoolean(); - activity.showDialog(dialog, "editHabit"); - } - - @Override - public void showCreateHabitScreen() - { - if (!prefs.isNumericalHabitsFeatureEnabled()) - { - showCreateBooleanHabitScreen(); - return; - } - - Dialog dialog = new AlertDialog.Builder(activity) - .setTitle("Type of habit") - .setItems(R.array.habitTypes, (d, which) -> - { - if (which == 0) showCreateBooleanHabitScreen(); - else showCreateNumericalHabitScreen(); - }) - .create(); - - dialog.show(); - } - - @Override - public void showDeleteConfirmationScreen( - @NonNull OnConfirmedCallback callback) - { - activity.showDialog(confirmDeleteDialogFactory.create(callback)); - } - - @Override - public void showEditHabitsScreen(List habits) - { - EditHabitDialog dialog; - dialog = editHabitDialogFactory.edit(habits.get(0)); - activity.showDialog(dialog, "editNumericalHabit"); - } - - @Override - public void showFAQScreen() - { - Intent intent = intentFactory.viewFAQ(activity); - activity.startActivity(intent); - } - - @Override - public void showHabitScreen(@NonNull Habit habit) - { - Intent intent = intentFactory.startShowHabitActivity(activity, habit); - activity.startActivity(intent); - } - - public void showImportScreen() - { - Intent intent = intentFactory.openDocument(); - activity.startActivityForResult(intent, REQUEST_OPEN_DOCUMENT); - } - - @Override - public void showIntroScreen() - { - Intent intent = intentFactory.startIntroActivity(activity); - activity.startActivity(intent); - } - - @Override - public void showMessage(@NonNull ListHabitsBehavior.Message m) - { - switch (m) - { - case COULD_NOT_EXPORT: - showMessage(R.string.could_not_export); - break; - - case IMPORT_SUCCESSFUL: - showMessage(R.string.habits_imported); - break; - - case IMPORT_FAILED: - showMessage(R.string.could_not_import); - break; - - case DATABASE_REPAIRED: - showMessage(R.string.database_repaired); - break; - - case COULD_NOT_GENERATE_BUG_REPORT: - showMessage(R.string.bug_report_failed); - break; - - case FILE_NOT_RECOGNIZED: - showMessage(R.string.file_not_recognized); - break; - } - } - - @Override - public void showNumberPicker(double value, - @NonNull String unit, - @NonNull - ListHabitsBehavior.NumberPickerCallback callback) - { - LayoutInflater inflater = activity.getLayoutInflater(); - View view = inflater.inflate(R.layout.number_picker_dialog, null); - - final NumberPicker picker; - final NumberPicker picker2; - final TextView tvUnit; - - picker = (NumberPicker) view.findViewById(R.id.picker); - picker2 = (NumberPicker) view.findViewById(R.id.picker2); - tvUnit = (TextView) view.findViewById(R.id.tvUnit); - - int intValue = (int) Math.round(value * 100); - - picker.setMinValue(0); - picker.setMaxValue(Integer.MAX_VALUE / 100); - picker.setValue(intValue / 100); - picker.setWrapSelectorWheel(false); - - picker2.setMinValue(0); - picker2.setMaxValue(19); - picker2.setFormatter(v -> String.format("%02d", 5 * v)); - picker2.setValue((intValue % 100) / 5); - refreshInitialValue(picker2); - - tvUnit.setText(unit); - - AlertDialog dialog = new AlertDialog.Builder(activity) - .setView(view) - .setTitle(R.string.change_value) - .setPositiveButton(android.R.string.ok, (d, which) -> - { - picker.clearFocus(); - double v = picker.getValue() + 0.05 * picker2.getValue(); - callback.onNumberPicked(v); - }) - .create(); - - InterfaceUtils.setupEditorAction(picker, (v, actionId, event) -> - { - if (actionId == IME_ACTION_DONE) - dialog.getButton(BUTTON_POSITIVE).performClick(); - return false; - }); - - dialog.show(); - } - - @Override - public void showSendBugReportToDeveloperScreen(String log) - { - int to = R.string.bugReportTo; - int subject = R.string.bugReportSubject; - showSendEmailScreen(to, subject, log); - } - - @Override - public void showSettingsScreen() - { - Intent intent = intentFactory.startSettingsActivity(activity); - activity.startActivityForResult(intent, REQUEST_SETTINGS); - } - - private void onOpenDocumentResult(int resultCode, Intent data) - { - if (controller == null) return; - if (resultCode != Activity.RESULT_OK) return; - - try - { - Uri uri = data.getData(); - ContentResolver cr = activity.getContentResolver(); - InputStream is = cr.openInputStream(uri); - - File cacheDir = activity.getExternalCacheDir(); - File tempFile = File.createTempFile("import", "", cacheDir); - - FileUtils.copy(is, tempFile); - controller.onImportData(tempFile, () -> tempFile.delete()); - } - catch (IOException e) - { - showMessage(R.string.could_not_import); - e.printStackTrace(); - } - } - - private void onSettingsResult(int resultCode) - { - if (controller == null) return; - - switch (resultCode) - { - case RESULT_IMPORT_DATA: - showImportScreen(); - break; - - case RESULT_EXPORT_CSV: - controller.onExportCSV(); - break; - - case RESULT_EXPORT_DB: - controller.onExportDB(); - break; - - case RESULT_BUG_REPORT: - controller.onSendBugReport(); - break; - - case RESULT_REPAIR_DB: - controller.onRepairDB(); - break; - } - } - - private void refreshInitialValue(NumberPicker picker2) - { - // Workaround for a bug on Android: - // https://code.google.com/p/android/issues/detail?id=35482 - try - { - Field f = NumberPicker.class.getDeclaredField("mInputText"); - f.setAccessible(true); - EditText inputText = (EditText) f.get(picker2); - inputText.setFilters(new InputFilter[0]); - } - catch (Exception e) - { - throw new RuntimeException(e); - } - } - - private void showCreateNumericalHabitScreen() - { - EditHabitDialog dialog; - dialog = editHabitDialogFactory.createNumerical(); - activity.showDialog(dialog, "editHabit"); - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.kt new file mode 100644 index 000000000..bda22a46e --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.kt @@ -0,0 +1,264 @@ +/* + * 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.activities.habits.list + +import android.app.* +import android.content.* +import android.support.annotation.* +import dagger.* +import org.isoron.androidbase.activities.* +import org.isoron.androidbase.utils.* +import org.isoron.uhabits.* +import org.isoron.uhabits.activities.common.dialogs.* +import org.isoron.uhabits.activities.habits.edit.* +import org.isoron.uhabits.activities.habits.list.views.* +import org.isoron.uhabits.core.commands.* +import org.isoron.uhabits.core.models.* +import org.isoron.uhabits.core.preferences.* +import org.isoron.uhabits.core.tasks.* +import org.isoron.uhabits.core.ui.* +import org.isoron.uhabits.core.ui.callbacks.* +import org.isoron.uhabits.core.ui.screens.habits.list.* +import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message.* +import org.isoron.uhabits.intents.* +import org.isoron.uhabits.tasks.* +import java.io.* +import javax.inject.* + +const val RESULT_IMPORT_DATA = 1 +const val RESULT_EXPORT_CSV = 2 +const val RESULT_EXPORT_DB = 3 +const val RESULT_BUG_REPORT = 4 +const val RESULT_REPAIR_DB = 5 +const val REQUEST_OPEN_DOCUMENT = 6 +const val REQUEST_SETTINGS = 7 + +@ActivityScope +class ListHabitsScreen +@Inject constructor( + activity: BaseActivity, + rootView: ListHabitsRootView, + private val commandRunner: CommandRunner, + private val intentFactory: IntentFactory, + private val themeSwitcher: ThemeSwitcher, + private val preferences: Preferences, + private val adapter: HabitCardListAdapter, + private val taskRunner: TaskRunner, + private val exportDBFactory: ExportDBTaskFactory, + private val importTaskFactory: ImportDataTaskFactory, + private val confirmDeleteDialogFactory: ConfirmDeleteDialogFactory, + private val colorPickerFactory: ColorPickerDialogFactory, + private val editHabitDialogFactory: EditHabitDialogFactory, + private val numberPickerFactory: NumberPickerFactory, + private val behavior: Lazy, + private val menu: Lazy, + private val selectionMenu: Lazy +) : BaseScreen(activity), + CommandRunner.Listener, + ListHabitsBehavior.Screen, + ListHabitsMenuBehavior.Screen, + ListHabitsSelectionMenuBehavior.Screen { + + init { + setRootView(rootView) + } + + fun onAttached() { + setMenu(menu.get()) + setSelectionMenu(selectionMenu.get()) + commandRunner.addListener(this) + } + + fun onDettached() { + commandRunner.removeListener(this) + } + + override fun onCommandExecuted(command: Command, refreshKey: Long?) { + if (command.isRemote) return + showMessage(getExecuteString(command)) + } + + override fun onResult(requestCode: Int, resultCode: Int, data: Intent?) { + when (requestCode) { + REQUEST_OPEN_DOCUMENT -> onOpenDocumentResult(resultCode, data) + REQUEST_SETTINGS -> onSettingsResult(resultCode) + } + } + + private fun onOpenDocumentResult(resultCode: Int, data: Intent?) { + if (data == null) return + if (resultCode != Activity.RESULT_OK) return + try { + val inStream = activity.contentResolver.openInputStream(data.data) + val cacheDir = activity.externalCacheDir + val tempFile = File.createTempFile("import", "", cacheDir) + FileUtils.copy(inStream, tempFile) + onImportData(tempFile) { tempFile.delete() } + } catch (e: IOException) { + showMessage(R.string.could_not_import) + e.printStackTrace() + } + } + + private fun onSettingsResult(resultCode: Int) { + when (resultCode) { + RESULT_IMPORT_DATA -> showImportScreen() + RESULT_EXPORT_CSV -> behavior.get().onExportCSV() + RESULT_EXPORT_DB -> onExportDB() + RESULT_BUG_REPORT -> behavior.get().onSendBugReport() + RESULT_REPAIR_DB -> behavior.get().onRepairDB() + } + } + + override fun applyTheme() { + themeSwitcher.apply() + activity.restartWithFade(ListHabitsActivity::class.java) + } + + override fun showAboutScreen() { + val intent = intentFactory.startAboutActivity(activity) + activity.startActivity(intent) + } + + fun showCreateBooleanHabitScreen() { + val dialog = editHabitDialogFactory.createBoolean() + activity.showDialog(dialog, "editHabit") + } + + override fun showCreateHabitScreen() { + if (!preferences.isNumericalHabitsFeatureEnabled) { + showCreateBooleanHabitScreen() + return + } + + val dialog = AlertDialog.Builder(activity) + .setTitle("Type of habit") + .setItems(R.array.habitTypes) { _, which -> + if (which == 0) showCreateBooleanHabitScreen() + else showCreateNumericalHabitScreen() + } + .create() + + dialog.show() + } + + override fun showDeleteConfirmationScreen(callback: OnConfirmedCallback) { + activity.showDialog(confirmDeleteDialogFactory.create(callback)) + } + + override fun showEditHabitsScreen(habits: List) { + val dialog = editHabitDialogFactory.edit(habits[0]) + activity.showDialog(dialog, "editNumericalHabit") + } + + override fun showFAQScreen() { + val intent = intentFactory.viewFAQ(activity) + activity.startActivity(intent) + } + + override fun showHabitScreen(habit: Habit) { + val intent = intentFactory.startShowHabitActivity(activity, habit) + activity.startActivity(intent) + } + + fun showImportScreen() { + val intent = intentFactory.openDocument() + activity.startActivityForResult(intent, REQUEST_OPEN_DOCUMENT) + } + + override fun showIntroScreen() { + val intent = intentFactory.startIntroActivity(activity) + activity.startActivity(intent) + } + + override fun showMessage(m: ListHabitsBehavior.Message) { + showMessage(when (m) { + COULD_NOT_EXPORT -> R.string.could_not_export + IMPORT_SUCCESSFUL -> R.string.habits_imported + IMPORT_FAILED -> R.string.could_not_import + DATABASE_REPAIRED -> R.string.database_repaired + COULD_NOT_GENERATE_BUG_REPORT -> R.string.bug_report_failed + FILE_NOT_RECOGNIZED -> R.string.file_not_recognized + }) + } + + override fun showSendBugReportToDeveloperScreen(log: String) { + val to = R.string.bugReportTo + val subject = R.string.bugReportSubject + showSendEmailScreen(to, subject, log) + } + + override fun showSettingsScreen() { + val intent = intentFactory.startSettingsActivity(activity) + activity.startActivityForResult(intent, REQUEST_SETTINGS) + } + + override fun showColorPicker(defaultColor: Int, + callback: OnColorPickedCallback) { + val picker = colorPickerFactory.create(defaultColor) + picker.setListener(callback) + activity.showDialog(picker, "picker") + } + + override fun showNumberPicker(value: Double, + unit: String, + callback: ListHabitsBehavior.NumberPickerCallback) { + numberPickerFactory.create(value, unit, callback).show() + } + + @StringRes + private fun getExecuteString(command: Command): Int? { + when (command) { + is ArchiveHabitsCommand -> return R.string.toast_habit_archived + is ChangeHabitColorCommand -> return R.string.toast_habit_changed + is CreateHabitCommand -> return R.string.toast_habit_created + is DeleteHabitsCommand -> return R.string.toast_habit_deleted + is EditHabitCommand -> return R.string.toast_habit_changed + is UnarchiveHabitsCommand -> return R.string.toast_habit_unarchived + else -> return null + } + } + + private fun showCreateNumericalHabitScreen() { + val dialog = editHabitDialogFactory.createNumerical() + activity.showDialog(dialog, "editHabit") + } + + private fun onImportData(file: File, onFinished: () -> Unit) { + taskRunner.execute(importTaskFactory.create(file) { result -> + if (result == ImportDataTask.SUCCESS) { + adapter.refresh() + showMessage(R.string.habits_imported) + } else if (result == ImportDataTask.NOT_RECOGNIZED) { + showMessage(R.string.file_not_recognized) + } else { + showMessage(R.string.could_not_import) + } + onFinished() + }) + } + + private fun onExportDB() { + taskRunner.execute(exportDBFactory.create { filename -> + if (filename != null) showSendFileScreen(filename) + else showMessage(R.string.could_not_export) + }) + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsSelectionMenu.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsSelectionMenu.java deleted file mode 100644 index ac0a53a0f..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsSelectionMenu.java +++ /dev/null @@ -1,145 +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.activities.habits.list; - -import android.support.annotation.*; -import android.view.*; - -import org.isoron.androidbase.activities.*; -import org.isoron.uhabits.R; -import org.isoron.uhabits.activities.habits.list.views.*; -import org.isoron.uhabits.core.commands.*; -import org.isoron.uhabits.core.ui.screens.habits.list.*; - -import javax.inject.*; - -@ActivityScope -public class ListHabitsSelectionMenu extends BaseSelectionMenu - implements HabitCardListController.SelectionListener -{ - @NonNull - private final ListHabitsScreen screen; - - @NonNull - CommandRunner commandRunner; - - private ListHabitsSelectionMenuBehavior behavior; - - @NonNull - private final HabitCardListAdapter listAdapter; - - @Nullable - private HabitCardListController listController; - - @Inject - public ListHabitsSelectionMenu(@NonNull ListHabitsScreen screen, - @NonNull HabitCardListAdapter listAdapter, - @NonNull CommandRunner commandRunner, - @NonNull ListHabitsSelectionMenuBehavior behavior) - { - this.screen = screen; - this.listAdapter = listAdapter; - this.commandRunner = commandRunner; - this.behavior = behavior; - } - - @Override - public void onFinish() - { - if (listController != null) listController.onSelectionFinished(); - super.onFinish(); - } - - @Override - public boolean onItemClicked(@NonNull MenuItem item) - { - switch (item.getItemId()) - { - case R.id.action_edit_habit: - behavior.onEditHabits(); - return true; - - case R.id.action_archive_habit: - behavior.onArchiveHabits(); - return true; - - case R.id.action_unarchive_habit: - behavior.onUnarchiveHabits(); - return true; - - case R.id.action_delete: - behavior.onDeleteHabits(); - return true; - - case R.id.action_color: - behavior.onChangeColor(); - return true; - - default: - return false; - } - } - - @Override - public boolean onPrepare(@NonNull Menu menu) - { - MenuItem itemEdit = menu.findItem(R.id.action_edit_habit); - MenuItem itemColor = menu.findItem(R.id.action_color); - MenuItem itemArchive = menu.findItem(R.id.action_archive_habit); - MenuItem itemUnarchive = menu.findItem(R.id.action_unarchive_habit); - - itemColor.setVisible(true); - itemEdit.setVisible(behavior.canEdit()); - itemArchive.setVisible(behavior.canArchive()); - itemUnarchive.setVisible(behavior.canUnarchive()); - setTitle(Integer.toString(listAdapter.getSelected().size())); - - return true; - } - - @Override - public void onSelectionChange() - { - invalidate(); - } - - @Override - public void onSelectionFinish() - { - finish(); - } - - @Override - public void onSelectionStart() - { - screen.startSelection(); - } - - public void setListController(HabitCardListController listController) - { - this.listController = listController; - } - - @Override - protected int getResourceId() - { - return R.menu.list_habits_selection; - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsSelectionMenu.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsSelectionMenu.kt new file mode 100644 index 000000000..9493b651d --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsSelectionMenu.kt @@ -0,0 +1,95 @@ +/* + * 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.activities.habits.list + +import android.view.* +import dagger.* +import org.isoron.androidbase.activities.* +import org.isoron.uhabits.* +import org.isoron.uhabits.activities.habits.list.views.* +import org.isoron.uhabits.core.commands.* +import org.isoron.uhabits.core.ui.screens.habits.list.* +import javax.inject.* + +@ActivityScope +class ListHabitsSelectionMenu @Inject constructor( + private val screen: ListHabitsScreen, + private val listAdapter: HabitCardListAdapter, + var commandRunner: CommandRunner, + private val behavior: ListHabitsSelectionMenuBehavior, + private val listController: Lazy +) : BaseSelectionMenu() { + + override fun onFinish() { + listController.get().onSelectionFinished() + super.onFinish() + } + + override fun onItemClicked(item: MenuItem): Boolean { + when (item.itemId) { + R.id.action_edit_habit -> { + behavior.onEditHabits() + return true + } + + R.id.action_archive_habit -> { + behavior.onArchiveHabits() + return true + } + + R.id.action_unarchive_habit -> { + behavior.onUnarchiveHabits() + return true + } + + R.id.action_delete -> { + behavior.onDeleteHabits() + return true + } + + R.id.action_color -> { + behavior.onChangeColor() + return true + } + + else -> return false + } + } + + override fun onPrepare(menu: Menu): Boolean { + val itemEdit = menu.findItem(R.id.action_edit_habit) + val itemColor = menu.findItem(R.id.action_color) + val itemArchive = menu.findItem(R.id.action_archive_habit) + val itemUnarchive = menu.findItem(R.id.action_unarchive_habit) + + itemColor.isVisible = true + itemEdit.isVisible = behavior.canEdit() + itemArchive.isVisible = behavior.canArchive() + itemUnarchive.isVisible = behavior.canUnarchive() + setTitle(Integer.toString(listAdapter.selected.size)) + + return true + } + + fun onSelectionStart() = screen.startSelection() + fun onSelectionChange() = invalidate() + fun onSelectionFinish() = finish() + override fun getResourceId() = R.menu.list_habits_selection +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/package-info.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/package-info.java deleted file mode 100644 index 1a39e29de..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/package-info.java +++ /dev/null @@ -1,23 +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 . - */ - -/** - * Provides acitivity for listing habits and related classes. - */ -package org.isoron.uhabits.activities.habits.list; \ No newline at end of file diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/ButtonPanelView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/ButtonPanelView.kt new file mode 100644 index 000000000..bea2d4149 --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/ButtonPanelView.kt @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2017 Á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.activities.habits.list.views + +import android.content.* +import android.view.* +import android.view.View.MeasureSpec.* +import android.widget.* +import org.isoron.uhabits.* +import org.isoron.uhabits.core.preferences.* +import org.isoron.uhabits.utils.* + +abstract class ButtonPanelView( + context: Context, + val preferences: Preferences +) : LinearLayout(context), + Preferences.Listener { + + var buttonCount = 0 + set(value) { + field = value + inflateButtons() + } + + var dataOffset = 0 + set(value) { + field = value + setupButtons() + } + + var buttons = mutableListOf() + + override fun onCheckmarkSequenceChanged() { + inflateButtons() + } + + @Synchronized + protected fun inflateButtons() { + val reverse = preferences.isCheckmarkSequenceReversed + + buttons.clear() + repeat(buttonCount) { buttons.add(createButton()) } + + removeAllViews() + if (reverse) buttons.reversed().forEach { addView(it) } + else buttons.forEach { addView(it) } + setupButtons() + requestLayout() + } + + public override fun onAttachedToWindow() { + super.onAttachedToWindow() + preferences.addListener(this) + } + + public override fun onDetachedFromWindow() { + preferences.removeListener(this) + super.onDetachedFromWindow() + } + + override fun onMeasure(widthSpec: Int, heightSpec: Int) { + val buttonWidth = dim(R.dimen.checkmarkWidth) + val buttonHeight = dim(R.dimen.checkmarkHeight) + val width = (buttonWidth * buttonCount) + super.onMeasure(width.toMeasureSpec(EXACTLY), + buttonHeight.toMeasureSpec(EXACTLY)) + } + + protected abstract fun setupButtons() + protected abstract fun createButton(): T +} \ No newline at end of file diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonView.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonView.java deleted file mode 100644 index 9696f805b..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonView.java +++ /dev/null @@ -1,194 +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.activities.habits.list.views; - -import android.content.*; -import android.content.res.*; -import android.graphics.*; -import android.support.annotation.*; -import android.text.*; -import android.util.*; -import android.view.*; - -import org.isoron.androidbase.utils.*; -import org.isoron.uhabits.*; -import org.isoron.uhabits.activities.*; -import org.isoron.uhabits.core.preferences.*; -import org.isoron.uhabits.utils.*; - -import static android.view.View.MeasureSpec.EXACTLY; -import static android.view.View.MeasureSpec.makeMeasureSpec; -import static org.isoron.androidbase.utils.InterfaceUtils.getDimension; -import static org.isoron.androidbase.utils.InterfaceUtils.getFontAwesome; -import static org.isoron.uhabits.core.models.Checkmark.CHECKED_EXPLICITLY; -import static org.isoron.uhabits.core.models.Checkmark.UNCHECKED; -import static org.isoron.uhabits.utils.AttributeSetUtils.getIntAttribute; - -public class CheckmarkButtonView extends View -{ - private int color; - - private int value; - - private StyledResources styledRes; - - private TextPaint paint; - - private int lowContrastColor; - - private RectF rect; - - @Nullable - private Preferences prefs; - - @NonNull - private OnToggleListener onToggleListener; - - @NonNull - private OnInvalidToggleListener onInvalidToggleListener; - - public CheckmarkButtonView(@Nullable Context context) - { - super(context); - init(); - } - - public CheckmarkButtonView(@Nullable Context ctx, @Nullable AttributeSet attrs) - { - super(ctx, attrs); - init(); - - if(ctx == null) throw new IllegalStateException(); - if(attrs == null) throw new IllegalStateException(); - - int paletteColor = getIntAttribute(ctx, attrs, "color", 0); - setColor(PaletteUtils.getAndroidTestColor(paletteColor)); - int value = getIntAttribute(ctx, attrs, "value", 0); - setValue(value); - } - - public void setColor(int color) - { - this.color = color; - postInvalidate(); - } - - public void setValue(int value) - { - this.value = value; - postInvalidate(); - } - - public void performToggle() - { - onToggleListener.onToggle(); - value = (value == CHECKED_EXPLICITLY ? UNCHECKED : CHECKED_EXPLICITLY); - performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - postInvalidate(); - } - - @Override - protected void onDraw(Canvas canvas) - { - super.onDraw(canvas); - Resources resources = getResources(); - - paint.setColor(value == CHECKED_EXPLICITLY ? color : lowContrastColor); - int id = (value == UNCHECKED ? R.string.fa_times : R.string.fa_check); - String label = resources.getString(id); - float em = paint.measureText("m"); - - rect.set(0, 0, getWidth(), getHeight()); - rect.offset(0, 0.4f * em); - canvas.drawText(label, rect.centerX(), rect.centerY(), paint); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) - { - Resources res = getResources(); - int height = res.getDimensionPixelSize(R.dimen.checkmarkHeight); - int width = res.getDimensionPixelSize(R.dimen.checkmarkWidth); - - widthMeasureSpec = makeMeasureSpec(width, EXACTLY); - heightMeasureSpec = makeMeasureSpec(height, EXACTLY); - - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - - private void init() - { - setFocusable(false); - - styledRes = new StyledResources(getContext()); - - paint = new TextPaint(); - paint.setTypeface(getFontAwesome(getContext())); - paint.setAntiAlias(true); - paint.setTextAlign(Paint.Align.CENTER); - paint.setTextSize(getDimension(getContext(), R.dimen.smallTextSize)); - - rect = new RectF(); - color = Color.BLACK; - lowContrastColor = styledRes.getColor(R.attr.lowContrastTextColor); - - onToggleListener = () -> {}; - onInvalidToggleListener = () -> {}; - - if (getContext() instanceof HabitsActivity) - { - HabitsApplicationComponent component = - ((HabitsActivity) getContext()).getAppComponent(); - prefs = component.getPreferences(); - } - - setOnClickListener((v) -> { - if (prefs == null) return; - if (prefs.isShortToggleEnabled()) performToggle(); - else onInvalidToggleListener.onInvalidToggle(); - }); - - setOnLongClickListener(v -> { - performToggle(); - return true; - }); - } - - public void setOnInvalidToggleListener( - @NonNull OnInvalidToggleListener onInvalidToggleListener) - { - this.onInvalidToggleListener = onInvalidToggleListener; - } - - public void setOnToggleListener(@NonNull OnToggleListener onToggleListener) - { - this.onToggleListener = onToggleListener; - } - - public interface OnInvalidToggleListener - { - void onInvalidToggle(); - } - - public interface OnToggleListener - { - void onToggle(); - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonView.kt new file mode 100644 index 000000000..77981995a --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonView.kt @@ -0,0 +1,123 @@ +/* + * 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.activities.habits.list.views + +import android.content.* +import android.graphics.* +import android.text.* +import android.view.* +import android.view.View.MeasureSpec.* +import com.google.auto.factory.* +import org.isoron.androidbase.activities.* +import org.isoron.uhabits.* +import org.isoron.uhabits.core.models.Checkmark.* +import org.isoron.uhabits.core.preferences.* +import org.isoron.uhabits.utils.* + +@AutoFactory +class CheckmarkButtonView( + @Provided @ActivityContext context: Context, + @Provided val preferences: Preferences +) : View(context), + View.OnClickListener, + View.OnLongClickListener { + + var color: Int = Color.BLACK + set(value) { + field = value + invalidate() + } + + var value: Int = 0 + set(value) { + field = value + invalidate() + } + + var onToggle: () -> Unit = {} + private var drawer = Drawer() + + init { + isFocusable = false + setOnClickListener(this) + setOnLongClickListener(this) + } + + fun performToggle() { + onToggle() + value = when (value) { + CHECKED_EXPLICITLY -> UNCHECKED + else -> CHECKED_EXPLICITLY + } + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) + invalidate() + } + + override fun onClick(v: View) { + if (preferences.isShortToggleEnabled) performToggle() + else showMessage(R.string.long_press_to_toggle) + } + + override fun onLongClick(v: View): Boolean { + performToggle() + return true + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + drawer.draw(canvas) + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + val height = resources.getDimensionPixelSize(R.dimen.checkmarkHeight) + val width = resources.getDimensionPixelSize(R.dimen.checkmarkWidth) + super.onMeasure(width.toMeasureSpec(EXACTLY), + height.toMeasureSpec(EXACTLY)) + } + + private inner class Drawer { + private val rect = RectF() + private val lowContrastColor = sres.getColor(R.attr.lowContrastTextColor) + + private val paint = TextPaint().apply { + typeface = getFontAwesome() + isAntiAlias = true + textAlign = Paint.Align.CENTER + textSize = dim(R.dimen.smallTextSize) + } + + fun draw(canvas: Canvas) { + paint.color = when (value) { + CHECKED_EXPLICITLY -> color + else -> lowContrastColor + } + val id = when (value) { + UNCHECKED -> R.string.fa_times + else -> R.string.fa_check + } + val label = resources.getString(id) + val em = paint.measureText("m") + + rect.set(0f, 0f, width.toFloat(), height.toFloat()) + rect.offset(0f, 0.4f * em) + canvas.drawText(label, rect.centerX(), rect.centerY(), paint) + } + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelView.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelView.java deleted file mode 100644 index afec42462..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelView.java +++ /dev/null @@ -1,243 +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.activities.habits.list.views; - -import android.content.*; -import android.support.annotation.*; -import android.util.*; -import android.widget.*; - -import org.isoron.uhabits.*; -import org.isoron.uhabits.core.preferences.*; -import org.isoron.uhabits.core.utils.*; - -import java.util.*; - -import static android.view.View.MeasureSpec.EXACTLY; -import static android.view.View.MeasureSpec.makeMeasureSpec; -import static org.isoron.androidbase.utils.InterfaceUtils.getDimension; -import static org.isoron.uhabits.utils.AttributeSetUtils.getIntAttribute; -import static org.isoron.uhabits.utils.PaletteUtils.getAndroidTestColor; - -public class CheckmarkPanelView extends LinearLayout - implements Preferences.Listener -{ - private static final int LEFT_TO_RIGHT = 0; - - private static final int RIGHT_TO_LEFT = 1; - - @Nullable - private Preferences prefs; - - private int values[]; - - private int nButtons; - - private int color; - - private int dataOffset; - - @NonNull - private OnInvalidToggleListener onInvalidToggleListener; - - @NonNull - private OnToggleListener onToggleLister; - - public CheckmarkPanelView(Context context) - { - super(context); - init(); - } - - public CheckmarkPanelView(Context ctx, AttributeSet attrs) - { - super(ctx, attrs); - init(); - - if (ctx != null && attrs != null) - { - int paletteColor = getIntAttribute(ctx, attrs, "color", 0); - setColor(getAndroidTestColor(paletteColor)); - setButtonCount(getIntAttribute(ctx, attrs, "button_count", 5)); - } - - if (isInEditMode()) initEditMode(); - } - - public CheckmarkButtonView indexToButton(int i) - { - int position = i; - - if (getCheckmarkOrder() == RIGHT_TO_LEFT) position = nButtons - i - 1; - - return (CheckmarkButtonView) getChildAt(position); - } - - @Override - public void onCheckmarkOrderChanged() - { - setupButtons(); - } - - public void setButtonCount(int newButtonCount) - { - if (nButtons != newButtonCount) - { - nButtons = newButtonCount; - addButtons(); - } - - setupButtons(); - } - - public void setColor(int color) - { - this.color = color; - setupButtons(); - } - - public void setDataOffset(int dataOffset) - { - this.dataOffset = dataOffset; - setupButtons(); - } - - public void setOnInvalidToggleListener( - @NonNull OnInvalidToggleListener onInvalidToggleListener) - { - this.onInvalidToggleListener = onInvalidToggleListener; - } - - public void setOnToggleLister(@NonNull OnToggleListener onToggleLister) - { - this.onToggleLister = onToggleLister; - } - - public void setValues(int[] values) - { - this.values = values; - setupButtons(); - } - - @Override - protected void onAttachedToWindow() - { - super.onAttachedToWindow(); - if (prefs != null) prefs.addListener(this); - } - - @Override - protected void onDetachedFromWindow() - { - if (prefs != null) prefs.removeListener(this); - super.onDetachedFromWindow(); - } - - @Override - protected void onMeasure(int widthSpec, int heightSpec) - { - float buttonWidth = getDimension(getContext(), R.dimen.checkmarkWidth); - float buttonHeight = - getDimension(getContext(), R.dimen.checkmarkHeight); - - float width = buttonWidth * nButtons; - - widthSpec = makeMeasureSpec((int) width, EXACTLY); - heightSpec = makeMeasureSpec((int) buttonHeight, EXACTLY); - - super.onMeasure(widthSpec, heightSpec); - } - - private void addButtons() - { - removeAllViews(); - - for (int i = 0; i < nButtons; i++) - addView(new CheckmarkButtonView(getContext())); - } - - private int getCheckmarkOrder() - { - if (prefs == null) return LEFT_TO_RIGHT; - return prefs.shouldReverseCheckmarks() ? RIGHT_TO_LEFT : LEFT_TO_RIGHT; - } - - private void init() - { - Context appContext = getContext().getApplicationContext(); - if (appContext instanceof HabitsApplication) - { - HabitsApplication app = (HabitsApplication) appContext; - prefs = app.getComponent().getPreferences(); - } - - onInvalidToggleListener = () -> {}; - onToggleLister = (t) -> {}; - - setWillNotDraw(false); - values = new int[0]; - } - - private void initEditMode() - { - int values[] = new int[nButtons]; - - for (int i = 0; i < nButtons; i++) - values[i] = Math.min(2, new Random().nextInt(4)); - - setValues(values); - } - - private void setupButtonControllers(long timestamp, - CheckmarkButtonView buttonView) - { - buttonView.setOnInvalidToggleListener( - () -> onInvalidToggleListener.onInvalidToggle()); - buttonView.setOnToggleListener( - () -> onToggleLister.onToggle(timestamp)); - } - - private void setupButtons() - { - long timestamp = DateUtils.getStartOfToday(); - long day = DateUtils.millisecondsInOneDay; - timestamp -= day * dataOffset; - - for (int i = 0; i < nButtons; i++) - { - CheckmarkButtonView buttonView = indexToButton(i); - if (i + dataOffset >= values.length) break; - buttonView.setValue(values[i + dataOffset]); - buttonView.setColor(color); - setupButtonControllers(timestamp, buttonView); - timestamp -= day; - } - } - - public interface OnInvalidToggleListener - { - void onInvalidToggle(); - } - - public interface OnToggleListener - { - void onToggle(long timestamp); - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelView.kt new file mode 100644 index 000000000..30d81a379 --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelView.kt @@ -0,0 +1,71 @@ +/* + * 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.activities.habits.list.views + +import android.content.* +import com.google.auto.factory.* +import org.isoron.androidbase.activities.* +import org.isoron.uhabits.core.models.Checkmark.* +import org.isoron.uhabits.core.preferences.* +import org.isoron.uhabits.core.utils.* + +@AutoFactory +class CheckmarkPanelView( + @Provided @ActivityContext context: Context, + @Provided preferences: Preferences, + @Provided private val buttonFactory: CheckmarkButtonViewFactory +) : ButtonPanelView(context, preferences) { + + var values = IntArray(0) + set(values) { + field = values + setupButtons() + } + + var color = 0 + set(value) { + field = value + setupButtons() + } + + var onToggle: (Long) -> Unit = {} + set(value) { + field = value + setupButtons() + } + + override fun createButton(): CheckmarkButtonView = buttonFactory.create() + + @Synchronized + override fun setupButtons() { + val today = DateUtils.getStartOfToday() + val day = DateUtils.millisecondsInOneDay + + buttons.forEachIndexed { index, button -> + val timestamp = today - (index + dataOffset) * day + button.value = when { + index + dataOffset < values.size -> values[index + dataOffset] + else -> UNCHECKED + } + button.color = color + button.onToggle = { onToggle(timestamp) } + } + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/EmptyListView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/EmptyListView.kt new file mode 100644 index 000000000..3d4ddafb1 --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/EmptyListView.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2017 Á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.activities.habits.list.views + +import android.content.* +import android.view.* +import android.view.Gravity.* +import android.view.ViewGroup.LayoutParams.* +import android.widget.* +import org.isoron.androidbase.activities.* +import org.isoron.uhabits.* +import org.isoron.uhabits.utils.* + +class EmptyListView(context: Context) : LinearLayout(context) { + init { + orientation = VERTICAL + gravity = Gravity.CENTER + visibility = BaseRootView.GONE + + addView(TextView(context).apply { + text = str(R.string.fa_star_half_o) + typeface = getFontAwesome() + textSize = sp(40.0f) + gravity = CENTER + setTextColor(sres.getColor(R.attr.mediumContrastTextColor)) + }, MATCH_PARENT, WRAP_CONTENT) + + addView(TextView(context).apply { + text = str(R.string.no_habits_found) + gravity = CENTER + setPadding(0, dp(20.0f).toInt(), 0, 0) + setTextColor(sres.getColor(R.attr.mediumContrastTextColor)) + }, MATCH_PARENT, WRAP_CONTENT) + } +} \ No newline at end of file diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListAdapter.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListAdapter.java index 76598a64d..6992391c1 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListAdapter.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListAdapter.java @@ -78,7 +78,8 @@ public class HabitCardListAdapter this.midnightTimer = midnightTimer; cache.setListener(this); - cache.setCheckmarkCount(ListHabitsRootView.MAX_CHECKMARK_COUNT); + cache.setCheckmarkCount( + ListHabitsRootViewKt.MAX_CHECKMARK_COUNT); cache.setOrder(preferences.getDefaultOrder()); setHasStableIds(true); @@ -202,7 +203,7 @@ public class HabitCardListAdapter int viewType) { if (listView == null) return null; - View view = listView.createCardView(); + View view = listView.createHabitCardView(); return new HabitCardViewHolder(view); } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListController.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListController.java deleted file mode 100644 index 86d1d1bd9..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListController.java +++ /dev/null @@ -1,334 +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.activities.habits.list.views; - -import android.support.annotation.*; - -import org.isoron.androidbase.activities.*; -import org.isoron.uhabits.core.models.*; - -import javax.inject.*; - -/** - * Controller responsible for receiving and processing the events generated by a - * HabitListView. These include selecting and reordering items, toggling - * checkmarks and clicking habits. - */ -@ActivityScope -public class HabitCardListController implements HabitCardListView.Controller, - ModelObservable.Listener -{ - private final Mode NORMAL_MODE = new NormalMode(); - - private final Mode SELECTION_MODE = new SelectionMode(); - - @NonNull - private final HabitCardListAdapter adapter; - - @Nullable - private HabitListener habitListener; - - @Nullable - private SelectionListener selectionListener; - - @NonNull - private Mode activeMode; - - @Inject - public HabitCardListController(@NonNull HabitCardListAdapter adapter) - { - this.adapter = adapter; - this.activeMode = new NormalMode(); - adapter.getObservable().addListener(this); - } - - /** - * Called when the user drags a habit and drops it somewhere. Note that the - * dragging operation is already complete. - * - * @param from the original position of the habit - * @param to the position where the habit was released - */ - @Override - public void drop(int from, int to) - { - if (from == to) return; - cancelSelection(); - - Habit habitFrom = adapter.getItem(from); - Habit habitTo = adapter.getItem(to); - adapter.performReorder(from, to); - - if (habitListener != null) - habitListener.onHabitReorder(habitFrom, habitTo); - } - - @Override - public void onEdit(@NonNull Habit habit, long timestamp) - { - if (habitListener != null) habitListener.onEdit(habit, timestamp); - } - - @Override - public void onInvalidEdit() - { - if (habitListener != null) habitListener.onInvalidEdit(); - } - - /** - * Called when the user attempts to perform a toggle, but attempt is - * rejected. - */ - @Override - public void onInvalidToggle() - { - if (habitListener != null) habitListener.onInvalidToggle(); - } - - /** - * Called when the user clicks at some item. - * - * @param position the position of the clicked item - */ - @Override - public void onItemClick(int position) - { - activeMode.onItemClick(position); - } - - /** - * Called when the user long clicks at some item. - * - * @param position the position of the clicked item - */ - @Override - public void onItemLongClick(int position) - { - activeMode.onItemLongClick(position); - } - - @Override - public void onModelChange() - { - if(adapter.isSelectionEmpty()) - { - activeMode = new NormalMode(); - if (selectionListener != null) selectionListener.onSelectionFinish(); - } - } - - /** - * Called when the selection operation is cancelled externally, by something - * other than this controller. This happens, for example, when the user - * presses the back button. - */ - public void onSelectionFinished() - { - cancelSelection(); - } - - /** - * Called when the user wants to toggle a checkmark. - * - * @param habit the habit of the checkmark - * @param timestamp the timestamps of the checkmark - */ - @Override - public void onToggle(@NonNull Habit habit, long timestamp) - { - if (habitListener != null) habitListener.onToggle(habit, timestamp); - } - - public void setHabitListener(@Nullable HabitListener habitListener) - { - this.habitListener = habitListener; - } - - public void setSelectionListener(@Nullable SelectionListener listener) - { - this.selectionListener = listener; - } - - /** - * Called when the user starts dragging an item. - * - * @param position the position of the habit dragged - */ - @Override - public void startDrag(int position) - { - activeMode.startDrag(position); - } - - /** - * Selects or deselects the item at a given position - * - * @param position the position of the item to be selected/deselected - */ - protected void toggleSelection(int position) - { - adapter.toggleSelection(position); - activeMode = adapter.isSelectionEmpty() ? NORMAL_MODE : SELECTION_MODE; - } - - /** - * Marks all items as not selected and finishes the selection operation. - */ - private void cancelSelection() - { - adapter.clearSelection(); - activeMode = new NormalMode(); - if (selectionListener != null) selectionListener.onSelectionFinish(); - } - - public interface HabitListener - { - void onEdit(Habit habit, long timestamp); - - /** - * Called when the user clicks a habit. - * - * @param habit the habit clicked - */ - void onHabitClick(@NonNull Habit habit); - - /** - * Called when the user wants to change the position of a habit on the - * list. - * - * @param from habit to be moved - * @param to habit that currently occupies the desired position - */ - void onHabitReorder(@NonNull Habit from, @NonNull Habit to); - - void onInvalidEdit(); - - void onInvalidToggle(); - - void onToggle(Habit habit, long timestamp); - } - - /** - * A Mode describes the behaviour of the list upon clicking, long clicking - * and dragging an item. This depends on whether some items are already - * selected or not. - */ - private interface Mode - { - void onItemClick(int position); - - boolean onItemLongClick(int position); - - void startDrag(int position); - } - - public interface SelectionListener - { - /** - * Called when the user changes the list of selected item. This is only - * called if there were previously selected items. If the selection was - * previously empty, then onHabitSelectionStart is called instead. - */ - void onSelectionChange(); - - /** - * Called when the user deselects all items or cancels the selection. - */ - void onSelectionFinish(); - - /** - * Called after the user selects the first item. - */ - void onSelectionStart(); - } - - /** - * Mode activated when there are no items selected. Clicks trigger habit - * click. Long clicks start selection. - */ - class NormalMode implements Mode - { - @Override - public void onItemClick(int position) - { - Habit habit = adapter.getItem(position); - if (habitListener != null) habitListener.onHabitClick(habit); - } - - @Override - public boolean onItemLongClick(int position) - { - startSelection(position); - return true; - } - - @Override - public void startDrag(int position) - { - startSelection(position); - } - - protected void startSelection(int position) - { - toggleSelection(position); - activeMode = SELECTION_MODE; - if (selectionListener != null) selectionListener.onSelectionStart(); - } - } - - /** - * Mode activated when some items are already selected. - *

- * Clicks toggle item selection. Long clicks select more items. - */ - class SelectionMode implements Mode - { - @Override - public void onItemClick(int position) - { - toggleSelection(position); - notifyListener(); - } - - @Override - public boolean onItemLongClick(int position) - { - toggleSelection(position); - notifyListener(); - return true; - } - - @Override - public void startDrag(int position) - { - toggleSelection(position); - notifyListener(); - } - - protected void notifyListener() - { - if (selectionListener == null) return; - - if (activeMode == SELECTION_MODE) - selectionListener.onSelectionChange(); - else selectionListener.onSelectionFinish(); - } - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListController.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListController.kt new file mode 100644 index 000000000..b68f3b24c --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListController.kt @@ -0,0 +1,167 @@ +/* + * 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.activities.habits.list.views + +import dagger.* +import org.isoron.androidbase.activities.* +import org.isoron.uhabits.activities.habits.list.* +import org.isoron.uhabits.core.models.* +import org.isoron.uhabits.core.ui.screens.habits.list.* +import javax.inject.* + +/** + * Controller responsible for receiving and processing the events generated by a + * HabitListView. These include selecting and reordering items, toggling + * checkmarks and clicking habits. + */ +@ActivityScope +class HabitCardListController @Inject constructor( + private val adapter: HabitCardListAdapter, + private val behavior: ListHabitsBehavior, + private val selectionMenu: Lazy +) : HabitCardListView.Controller, ModelObservable.Listener { + + private val NORMAL_MODE = NormalMode() + private val SELECTION_MODE = SelectionMode() + private var activeMode: Mode + + init { + this.activeMode = NORMAL_MODE + adapter.observable.addListener(this) + } + + override fun drop(from: Int, to: Int) { + if (from == to) return + cancelSelection() + + val habitFrom = adapter.getItem(from) + val habitTo = adapter.getItem(to) + if(habitFrom == null || habitTo == null) return + + adapter.performReorder(from, to) + behavior.onReorderHabit(habitFrom, habitTo) + } + + override fun onItemClick(position: Int) { + activeMode.onItemClick(position) + } + + override fun onItemLongClick(position: Int) { + activeMode.onItemLongClick(position) + } + + override fun onModelChange() { + if (adapter.isSelectionEmpty) { + activeMode = NormalMode() + selectionMenu.get().onSelectionFinish() + } + } + + fun onSelectionFinished() { + cancelSelection() + } + + override fun startDrag(position: Int) { + activeMode.startDrag(position) + } + + protected fun toggleSelection(position: Int) { + adapter.toggleSelection(position) + activeMode = if (adapter.isSelectionEmpty) NORMAL_MODE else SELECTION_MODE + } + + private fun cancelSelection() { + adapter.clearSelection() + activeMode = NormalMode() + selectionMenu.get().onSelectionFinish() + } + + interface HabitListener { + fun onHabitClick(habit: Habit) + fun onHabitReorder(from: Habit, to: Habit) + } + + /** + * A Mode describes the behavior of the list upon clicking, long clicking + * and dragging an item. This depends on whether some items are already + * selected or not. + */ + private interface Mode { + fun onItemClick(position: Int) + fun onItemLongClick(position: Int): Boolean + fun startDrag(position: Int) + } + + /** + * Mode activated when there are no items selected. Clicks trigger habit + * click. Long clicks start selection. + */ + internal inner class NormalMode : Mode { + override fun onItemClick(position: Int) { + val habit = adapter.getItem(position) + if (habit == null) return + behavior.onClickHabit(habit) + } + + override fun onItemLongClick(position: Int): Boolean { + startSelection(position) + return true + } + + override fun startDrag(position: Int) { + startSelection(position) + } + + protected fun startSelection(position: Int) { + toggleSelection(position) + activeMode = SELECTION_MODE + selectionMenu.get().onSelectionStart() + } + } + + /** + * Mode activated when some items are already selected. Clicks toggle + * item selection. Long clicks select more items. + */ + internal inner class SelectionMode : Mode { + override fun onItemClick(position: Int) { + toggleSelection(position) + notifyListener() + } + + override fun onItemLongClick(position: Int): Boolean { + toggleSelection(position) + notifyListener() + return true + } + + override fun startDrag(position: Int) { + toggleSelection(position) + notifyListener() + } + + protected fun notifyListener() { + if (activeMode === SELECTION_MODE) + selectionMenu.get().onSelectionChange() + else + selectionMenu.get().onSelectionFinish() + } + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListView.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListView.java deleted file mode 100644 index 5de5f8a0f..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListView.java +++ /dev/null @@ -1,282 +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.activities.habits.list.views; - -import android.content.*; -import android.os.*; -import android.support.annotation.*; -import android.support.v7.widget.*; -import android.support.v7.widget.helper.*; -import android.util.*; -import android.view.*; - -import org.isoron.uhabits.activities.common.views.*; -import org.isoron.uhabits.core.models.*; - -import java.util.*; - -public class HabitCardListView extends RecyclerView -{ - @Nullable - private HabitCardListAdapter adapter; - - @NonNull - private Controller controller; - - private final ItemTouchHelper touchHelper; - - private int checkmarkCount; - - private int dataOffset; - - private LinkedList attachedHolders; - - public HabitCardListView(Context context, AttributeSet attrs) - { - super(context, attrs); - setLongClickable(true); - setHasFixedSize(true); - setLayoutManager(new LinearLayoutManager(getContext())); - - TouchHelperCallback callback = new TouchHelperCallback(); - touchHelper = new ItemTouchHelper(callback); - touchHelper.attachToRecyclerView(this); - - attachedHolders = new LinkedList<>(); - controller = new Controller() {}; - } - - public void attachCardView(HabitCardViewHolder holder) - { - attachedHolders.add(holder); - } - - /** - * Builds a new HabitCardView to be eventually added to this list, - * containing the given data. - * - * @param holder the ViewHolder containing the HabitCardView that should - * be built - * @param habit the habit for this card - * @param score the current score for the habit - * @param checkmarks the list of checkmark values to be included in the - * card - * @param selected true if the card is selected, false otherwise - * @return the HabitCardView generated - */ - public View bindCardView(@NonNull HabitCardViewHolder holder, - @NonNull Habit habit, - double score, - int[] checkmarks, - boolean selected) - { - HabitCardView cardView = (HabitCardView) holder.itemView; - cardView.setHabit(habit); - cardView.setSelected(selected); - cardView.setValues(checkmarks); - cardView.setButtonCount(checkmarkCount); - cardView.setDataOffset(dataOffset); - cardView.setScore(score); - cardView.setUnit(habit.getUnit()); - cardView.setThreshold(habit.getTargetValue()); - setupCardViewListeners(holder); - return cardView; - } - - public View createCardView() - { - return new HabitCardView(getContext()); - } - - public void detachCardView(HabitCardViewHolder holder) - { - attachedHolders.remove(holder); - } - - @Override - public void setAdapter(RecyclerView.Adapter adapter) - { - this.adapter = (HabitCardListAdapter) adapter; - super.setAdapter(adapter); - } - - public void setCheckmarkCount(int checkmarkCount) - { - this.checkmarkCount = checkmarkCount; - } - - public void setController(@NonNull Controller controller) - { - this.controller = controller; - } - - public void setDataOffset(int dataOffset) - { - this.dataOffset = dataOffset; - for (HabitCardViewHolder holder : attachedHolders) - { - HabitCardView cardView = (HabitCardView) holder.itemView; - cardView.setDataOffset(dataOffset); - } - } - - @Override - protected void onAttachedToWindow() - { - super.onAttachedToWindow(); - if (adapter != null) adapter.onAttached(); - } - - @Override - protected void onDetachedFromWindow() - { - if (adapter != null) adapter.onDetached(); - super.onDetachedFromWindow(); - } - - @Override - protected void onRestoreInstanceState(Parcelable state) - { - if(!(state instanceof BundleSavedState)) - { - super.onRestoreInstanceState(state); - return; - } - - BundleSavedState bss = (BundleSavedState) state; - dataOffset = bss.bundle.getInt("dataOffset"); - super.onRestoreInstanceState(bss.getSuperState()); - } - - @Override - protected Parcelable onSaveInstanceState() - { - Parcelable superState = super.onSaveInstanceState(); - Bundle bundle = new Bundle(); - bundle.putInt("dataOffset", dataOffset); - return new BundleSavedState(superState, bundle); - } - - protected void setupCardViewListeners(@NonNull HabitCardViewHolder holder) - { - HabitCardView cardView = (HabitCardView) holder.itemView; - cardView.setOnInvalidEditListener(() -> controller.onInvalidEdit()); - cardView.setOnInvalidToggleListener(() -> controller.onInvalidToggle()); - cardView.setOnToggleListener( - (habit, timestamp) -> controller.onToggle(habit, timestamp)); - cardView.setOnEditListener( - (habit, timestamp) -> controller.onEdit(habit, timestamp)); - - GestureDetector detector = new GestureDetector(getContext(), - new CardViewGestureDetector(holder)); - - cardView.setOnTouchListener((v, ev) -> { - detector.onTouchEvent(ev); - return true; - }); - } - - public interface Controller - { - default void drop(int from, int to) {} - - default void onItemClick(int pos) {} - - default void onItemLongClick(int pos) {} - - default void startDrag(int position) {} - - default void onInvalidToggle() {} - - default void onInvalidEdit() {} - - default void onToggle(@NonNull Habit habit, long timestamp) {} - - default void onEdit(@NonNull Habit habit, long timestamp) {} - } - - private class CardViewGestureDetector - extends GestureDetector.SimpleOnGestureListener - { - @NonNull - private final HabitCardViewHolder holder; - - public CardViewGestureDetector(@NonNull HabitCardViewHolder holder) - { - this.holder = holder; - } - - @Override - public void onLongPress(MotionEvent e) - { - int position = holder.getAdapterPosition(); - if (controller != null) controller.onItemLongClick(position); - if (adapter.isSortable()) touchHelper.startDrag(holder); - } - - @Override - public boolean onSingleTapUp(MotionEvent e) - { - int position = holder.getAdapterPosition(); - if (controller != null) controller.onItemClick(position); - return true; - } - } - - class TouchHelperCallback extends ItemTouchHelper.Callback - { - @Override - public int getMovementFlags(RecyclerView recyclerView, - ViewHolder viewHolder) - { - int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN; - int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END; - return makeMovementFlags(dragFlags, swipeFlags); - } - - @Override - public boolean isItemViewSwipeEnabled() - { - return false; - } - - @Override - public boolean isLongPressDragEnabled() - { - return false; - } - - @Override - public boolean onMove(RecyclerView recyclerView, - ViewHolder from, - ViewHolder to) - { - if (controller == null) return false; - controller.drop(from.getAdapterPosition(), to.getAdapterPosition()); - return true; - } - - @Override - public void onSwiped(ViewHolder viewHolder, int direction) - { - // NOP - } - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListView.kt new file mode 100644 index 000000000..c68d56d5a --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListView.kt @@ -0,0 +1,171 @@ +/* + * 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.activities.habits.list.views + +import android.content.* +import android.os.* +import android.support.v7.widget.* +import android.support.v7.widget.helper.* +import android.support.v7.widget.helper.ItemTouchHelper.* +import android.view.* +import com.google.auto.factory.* +import dagger.* +import org.isoron.androidbase.activities.* +import org.isoron.uhabits.activities.common.views.* +import org.isoron.uhabits.core.models.* + +@AutoFactory +class HabitCardListView( + @Provided @ActivityContext context: Context, + @Provided private val adapter: HabitCardListAdapter, + @Provided private val cardViewFactory: HabitCardViewFactory, + @Provided private val controller: Lazy +) : RecyclerView(context) { + + var checkmarkCount: Int = 0 + + var dataOffset: Int = 0 + set(value) { + field = value + attachedHolders + .map { it.itemView as HabitCardView } + .forEach { it.dataOffset = value } + } + + private val attachedHolders = mutableListOf() + private val touchHelper = ItemTouchHelper(TouchHelperCallback()).apply { + attachToRecyclerView(this@HabitCardListView) + } + + init { + setHasFixedSize(true) + isLongClickable = true + layoutManager = LinearLayoutManager(context) + super.setAdapter(adapter) + } + + fun createHabitCardView(): View { + return cardViewFactory.create() + } + + fun bindCardView(holder: HabitCardViewHolder, + habit: Habit, + score: Double, + checkmarks: IntArray, + selected: Boolean): View { + val cardView = holder.itemView as HabitCardView + cardView.habit = habit + cardView.isSelected = selected + cardView.values = checkmarks + cardView.buttonCount = checkmarkCount + cardView.dataOffset = dataOffset + cardView.score = score + cardView.unit = habit.unit + cardView.threshold = habit.targetValue + + val detector = GestureDetector(context, CardViewGestureDetector(holder)) + cardView.setOnTouchListener { _, ev -> + detector.onTouchEvent(ev) + true + } + + return cardView + } + + fun attachCardView(holder: HabitCardViewHolder) { + attachedHolders.add(holder) + } + + fun detachCardView(holder: HabitCardViewHolder) { + attachedHolders.remove(holder) + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + adapter.onAttached() + } + + override fun onDetachedFromWindow() { + adapter.onDetached() + super.onDetachedFromWindow() + } + + override fun onRestoreInstanceState(state: Parcelable) { + if (state !is BundleSavedState) { + super.onRestoreInstanceState(state) + return + } + dataOffset = state.bundle.getInt("dataOffset") + super.onRestoreInstanceState(state.superState) + } + + override fun onSaveInstanceState(): Parcelable { + val superState = super.onSaveInstanceState() + val bundle = Bundle().apply { + putInt("dataOffset", dataOffset) + } + return BundleSavedState(superState, bundle) + } + + interface Controller { + fun drop(from: Int, to: Int) {} + fun onItemClick(pos: Int) {} + fun onItemLongClick(pos: Int) {} + fun startDrag(position: Int) {} + } + + private inner class CardViewGestureDetector( + private val holder: HabitCardViewHolder + ) : GestureDetector.SimpleOnGestureListener() { + + override fun onLongPress(e: MotionEvent) { + val position = holder.adapterPosition + controller.get().onItemLongClick(position) + if (adapter.isSortable) touchHelper.startDrag(holder) + } + + override fun onSingleTapUp(e: MotionEvent): Boolean { + val position = holder.adapterPosition + controller.get().onItemClick(position) + return true + } + } + + inner class TouchHelperCallback : ItemTouchHelper.Callback() { + override fun getMovementFlags(recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder): Int { + return makeMovementFlags(UP or DOWN, START or END) + } + + override fun onMove(recyclerView: RecyclerView, + from: RecyclerView.ViewHolder, + to: RecyclerView.ViewHolder): Boolean { + controller.get().drop(from.adapterPosition, to.adapterPosition) + return true + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, + direction: Int) { + } + + override fun isItemViewSwipeEnabled() = false + override fun isLongPressDragEnabled() = false + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.java deleted file mode 100644 index 4c9bbc332..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.java +++ /dev/null @@ -1,406 +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.activities.habits.list.views; - -import android.annotation.*; -import android.content.*; -import android.graphics.drawable.*; -import android.os.*; -import android.support.annotation.*; -import android.text.*; -import android.util.*; -import android.view.*; -import android.widget.*; - -import org.isoron.androidbase.utils.*; -import org.isoron.uhabits.*; -import org.isoron.uhabits.activities.common.views.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.utils.*; -import org.isoron.uhabits.utils.*; - -import java.util.*; - -import static android.os.Build.VERSION.SDK_INT; -import static android.os.Build.VERSION_CODES.LOLLIPOP; -import static android.os.Build.VERSION_CODES.M; -import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; -import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; -import static org.isoron.androidbase.utils.InterfaceUtils.dpToPixels; - -public class HabitCardView extends FrameLayout - implements ModelObservable.Listener -{ - private static final String EDIT_MODE_HABITS[] = { - "Wake up early", - "Wash dishes", - "Exercise", - "Meditate", - "Play guitar", - "Wash clothes", - "Get a haircut" - }; - - CheckmarkPanelView checkmarkPanel; - - NumberPanelView numberPanel; - - LinearLayout innerFrame; - - TextView label; - - RingView scoreRing; - - private final Context context = getContext(); - - private StyledResources res; - - @Nullable - private Habit habit; - - private int dataOffset; - - @NonNull - OnInvalidEditListener onInvalidEditListener; - - @NonNull - OnEditListener onEditListener; - - @NonNull - private OnToggleListener onToggleListener; - - @NonNull - private OnInvalidToggleListener onInvalidToggleListener; - - public HabitCardView(Context context) - { - super(context); - init(); - } - - public HabitCardView(Context context, AttributeSet attrs) - { - super(context, attrs); - init(); - } - - @Override - public void onModelChange() - { - new Handler(Looper.getMainLooper()).post(() -> refresh()); - } - - public void setButtonCount(int buttonCount) - { - checkmarkPanel.setButtonCount(buttonCount); - numberPanel.setButtonCount(buttonCount); - } - - public void setDataOffset(int dataOffset) - { - this.dataOffset = dataOffset; - checkmarkPanel.setDataOffset(dataOffset); - numberPanel.setDataOffset(dataOffset); - } - - public void setHabit(@NonNull Habit habit) - { - if (isAttachedToWindow()) stopListening(); - this.habit = habit; - refresh(); - if (isAttachedToWindow()) startListening(); - postInvalidate(); - } - - public void setScore(double score) - { - float percentage = (float) score; - scoreRing.setPercentage(percentage); - scoreRing.setPrecision(1.0f / 16); - postInvalidate(); - } - - @Override - public void setSelected(boolean isSelected) - { - super.setSelected(isSelected); - updateBackground(isSelected); - } - - public void setThreshold(double threshold) - { - numberPanel.setThreshold(threshold); - } - - public void setUnit(String unit) - { - numberPanel.setUnit(unit); - } - - public void setValues(int values[]) - { - double dvalues[] = new double[values.length]; - for (int i = 0; i < values.length; i++) - dvalues[i] = (double) values[i] / 1000; - - checkmarkPanel.setValues(values); - numberPanel.setValues(dvalues); - numberPanel.setThreshold(10); - postInvalidate(); - } - - public void triggerRipple(long timestamp) - { - long today = DateUtils.getStartOfToday(); - long day = DateUtils.millisecondsInOneDay; - int offset = (int) ((today - timestamp) / day) - dataOffset; - CheckmarkButtonView button = checkmarkPanel.indexToButton(offset); - - float y = button.getHeight() / 2.0f; - float x = checkmarkPanel.getX() + button.getX() + button.getWidth() / 2; - triggerRipple(x, y); - } - - @Override - protected void onAttachedToWindow() - { - super.onAttachedToWindow(); - stopListening(); - } - - @Override - protected void onDetachedFromWindow() - { - startListening(); - super.onDetachedFromWindow(); - } - - private int getActiveColor(Habit habit) - { - int mediumContrastColor = res.getColor(R.attr.mediumContrastTextColor); - int activeColor = PaletteUtils.getColor(context, habit.getColor()); - if (habit.isArchived()) activeColor = mediumContrastColor; - - return activeColor; - } - - private void init() - { - res = new StyledResources(getContext()); - setLayoutParams(new LayoutParams(MATCH_PARENT, WRAP_CONTENT)); - setClipToPadding(false); - - int margin = (int) dpToPixels(context, 3); - setPadding(margin, 0, margin, margin); - - initInnerFrame(); - initScoreRing(); - initLabel(); - - onInvalidEditListener = () -> {}; - onInvalidToggleListener = () -> {}; - onEditListener = ((h, t) -> {}); - onToggleListener = ((h,t) -> {}); - - checkmarkPanel = new CheckmarkPanelView(context); - checkmarkPanel.setOnToggleLister( - (t) -> onToggleListener.onToggle(habit, t)); - checkmarkPanel.setOnInvalidToggleListener( - () -> onInvalidToggleListener.onInvalidToggle()); - - numberPanel = new NumberPanelView(context); - numberPanel.setVisibility(GONE); - numberPanel.setOnInvalidEditListener(() -> - onInvalidEditListener.onInvalidEdit()); - numberPanel.setOnEditListener(((t) -> { - triggerRipple(t); - onEditListener.onEdit(habit, t); - })); - - innerFrame.addView(scoreRing); - innerFrame.addView(label); - innerFrame.addView(checkmarkPanel); - innerFrame.addView(numberPanel); - addView(innerFrame); - - innerFrame.setOnTouchListener((v, event) -> - { - if (SDK_INT >= LOLLIPOP) - v.getBackground().setHotspot(event.getX(), event.getY()); - return false; - }); - - if (isInEditMode()) initEditMode(); - } - - @SuppressLint("SetTextI18n") - private void initEditMode() - { - Random rand = new Random(); - int color = PaletteUtils.getAndroidTestColor(rand.nextInt(10)); - label.setText(EDIT_MODE_HABITS[rand.nextInt(EDIT_MODE_HABITS.length)]); - label.setTextColor(color); - scoreRing.setColor(color); - scoreRing.setPercentage(rand.nextFloat()); - checkmarkPanel.setColor(color); - numberPanel.setColor(color); - checkmarkPanel.setButtonCount(5); - } - - private void initInnerFrame() - { - LinearLayout.LayoutParams params; - params = new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT); - - innerFrame = new LinearLayout(context); - innerFrame.setLayoutParams(params); - innerFrame.setOrientation(LinearLayout.HORIZONTAL); - innerFrame.setGravity(Gravity.CENTER_VERTICAL); - - if (SDK_INT >= LOLLIPOP) - innerFrame.setElevation(dpToPixels(context, 1)); - } - - private void initLabel() - { - LinearLayout.LayoutParams params; - params = new LinearLayout.LayoutParams(0, WRAP_CONTENT, 1); - - label = new TextView(context); - label.setLayoutParams(params); - label.setMaxLines(2); - label.setEllipsize(TextUtils.TruncateAt.END); - - if (SDK_INT >= M) - label.setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED); - } - - private void initScoreRing() - { - scoreRing = new RingView(context); - int ringSize = (int) dpToPixels(context, 15); - int margin = (int) dpToPixels(context, 8); - float thickness = dpToPixels(context, 3); - - LinearLayout.LayoutParams params; - params = new LinearLayout.LayoutParams(ringSize, ringSize); - params.setMargins(margin, 0, margin, 0); - params.gravity = Gravity.CENTER; - - scoreRing.setLayoutParams(params); - scoreRing.setThickness(thickness); - } - - private void refresh() - { - int color = getActiveColor(habit); - label.setText(habit.getName()); - label.setTextColor(color); - scoreRing.setColor(color); - checkmarkPanel.setColor(color); - numberPanel.setColor(color); - - boolean isNumerical = habit.isNumerical(); - checkmarkPanel.setVisibility(isNumerical ? GONE : VISIBLE); - numberPanel.setVisibility(isNumerical ? VISIBLE : GONE); - - postInvalidate(); - } - - private void startListening() - { - if (habit != null) habit.getObservable().removeListener(this); - } - - private void stopListening() - { - if (habit != null) habit.getObservable().addListener(this); - } - - private void triggerRipple(final float x, final float y) - { - final Drawable background = innerFrame.getBackground(); - if (SDK_INT >= LOLLIPOP) background.setHotspot(x, y); - background.setState(new int[]{ - android.R.attr.state_pressed, android.R.attr.state_enabled - }); - new Handler().postDelayed(() -> background.setState(new int[]{}), 25); - } - - private void updateBackground(boolean isSelected) - { - if (SDK_INT >= LOLLIPOP) - { - if (isSelected) - innerFrame.setBackgroundResource(R.drawable.selected_box); - else innerFrame.setBackgroundResource(R.drawable.ripple); - } - else - { - Drawable background; - if (isSelected) - background = res.getDrawable(R.attr.selectedBackground); - else background = res.getDrawable(R.attr.cardBackground); - innerFrame.setBackgroundDrawable(background); - } - } - - public void setOnEditListener(@NonNull OnEditListener onEditListener) - { - this.onEditListener = onEditListener; - } - - public void setOnInvalidEditListener( - @NonNull OnInvalidEditListener onInvalidEditListener) - { - this.onInvalidEditListener = onInvalidEditListener; - } - - public void setOnToggleListener(@NonNull OnToggleListener onToggleListener) - { - this.onToggleListener = onToggleListener; - } - - public void setOnInvalidToggleListener( - @NonNull OnInvalidToggleListener onInvalidToggleListener) - { - this.onInvalidToggleListener = onInvalidToggleListener; - } - - public interface OnEditListener - { - void onEdit(@NonNull Habit habit, long timestamp); - } - - public interface OnInvalidEditListener - { - void onInvalidEdit(); - } - - public interface OnInvalidToggleListener - { - void onInvalidToggle(); - } - - public interface OnToggleListener - { - void onToggle(@NonNull Habit habit, long timestamp); - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.kt new file mode 100644 index 000000000..f948266de --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.kt @@ -0,0 +1,253 @@ +/* + * 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.activities.habits.list.views + +import android.content.* +import android.os.* +import android.os.Build.VERSION.* +import android.os.Build.VERSION_CODES.* +import android.text.* +import android.view.* +import android.view.ViewGroup.LayoutParams.* +import android.widget.* +import com.google.auto.factory.* +import org.isoron.androidbase.activities.* +import org.isoron.uhabits.* +import org.isoron.uhabits.activities.common.views.* +import org.isoron.uhabits.core.models.* +import org.isoron.uhabits.core.ui.screens.habits.list.* +import org.isoron.uhabits.core.utils.* +import org.isoron.uhabits.utils.* + +@AutoFactory +class HabitCardView( + @Provided @ActivityContext context: Context, + @Provided private val checkmarkPanelFactory: CheckmarkPanelViewFactory, + @Provided private val numberPanelFactory: NumberPanelViewFactory, + @Provided private val behavior: ListHabitsBehavior +) : FrameLayout(context), + ModelObservable.Listener { + + var buttonCount + get() = checkmarkPanel.buttonCount + set(value) { + checkmarkPanel.buttonCount = value + numberPanel.buttonCount = value + } + + var dataOffset = 0 + set(value) { + field = value + checkmarkPanel.dataOffset = value + numberPanel.dataOffset = value + } + + var habit: Habit? = null + set(newHabit) { + if (isAttachedToWindow) { + field?.observable?.removeListener(this) + newHabit?.observable?.addListener(this) + } + field = newHabit + if (newHabit != null) copyAttributesFrom(newHabit) + } + + var score + get() = scoreRing.percentage.toDouble() + set(value) { + scoreRing.percentage = value.toFloat() + scoreRing.precision = 1.0f / 16 + } + + var unit + get() = numberPanel.units + set(value) { + numberPanel.units = value + } + + var values + get() = checkmarkPanel.values + set(values) { + checkmarkPanel.values = values + numberPanel.values = values.map { it / 1000.0 }.toDoubleArray() + } + + var threshold: Double + get() = numberPanel.threshold + set(value) { + numberPanel.threshold = value + } + + private var checkmarkPanel: CheckmarkPanelView + private var numberPanel: NumberPanelView + private var innerFrame: LinearLayout + private var label: TextView + private var scoreRing: RingView + + init { + scoreRing = RingView(context).apply { + val thickness = dp(3f) + val margin = dp(8f).toInt() + val ringSize = dp(15f).toInt() + layoutParams = LinearLayout.LayoutParams(ringSize, ringSize).apply { + setMargins(margin, 0, margin, 0) + gravity = Gravity.CENTER + } + setThickness(thickness) + } + + label = TextView(context).apply { + maxLines = 2 + ellipsize = TextUtils.TruncateAt.END + layoutParams = LinearLayout.LayoutParams(0, WRAP_CONTENT, 1f) + if (SDK_INT >= M) breakStrategy = Layout.BREAK_STRATEGY_BALANCED + } + + checkmarkPanel = checkmarkPanelFactory.create().apply { + onToggle = { timestamp -> + triggerRipple(timestamp) + habit?.let { behavior.onToggle(it, timestamp) } + } + } + + numberPanel = numberPanelFactory.create().apply { + visibility = GONE + onEdit = { timestamp -> + triggerRipple(timestamp) + habit?.let { behavior.onEdit(it, timestamp) } + } + } + + innerFrame = LinearLayout(context).apply { + gravity = Gravity.CENTER_VERTICAL + orientation = LinearLayout.HORIZONTAL + layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT) + if (SDK_INT >= LOLLIPOP) elevation = dp(1f) + + addView(scoreRing) + addView(label) + addView(checkmarkPanel) + addView(numberPanel) + + setOnTouchListener { v, event -> + if (SDK_INT >= LOLLIPOP) + v.background.setHotspot(event.x, event.y) + false + } + } + + clipToPadding = false + layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT) + val margin = dp(3f).toInt() + setPadding(margin, 0, margin, margin) + addView(innerFrame) + } + + override fun onModelChange() { + Handler(Looper.getMainLooper()).post { + habit?.let { copyAttributesFrom(it) } + } + } + + override fun setSelected(isSelected: Boolean) { + super.setSelected(isSelected) + updateBackground(isSelected) + } + + fun triggerRipple(timestamp: Long) { + val today = DateUtils.getStartOfToday() + val day = DateUtils.millisecondsInOneDay + val offset = ((today - timestamp) / day).toInt() - dataOffset + val button = checkmarkPanel.buttons[offset] + val y = button.height / 2.0f + val x = checkmarkPanel.x + button.x + (button.width / 2).toFloat() + triggerRipple(x, y) + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + habit?.observable?.addListener(this) + } + + override fun onDetachedFromWindow() { + habit?.observable?.removeListener(this) + super.onDetachedFromWindow() + } + + private fun copyAttributesFrom(h: Habit) { + + fun getActiveColor(habit: Habit): Int { + return when (habit.isArchived) { + true -> sres.getColor(R.attr.mediumContrastTextColor) + false -> PaletteUtils.getColor(context, habit.color) + } + } + + val c = getActiveColor(h) + label.apply { + text = h.name + setTextColor(c) + } + scoreRing.apply { + color = c + } + checkmarkPanel.apply { + color = c + visibility = when (h.isNumerical) { + true -> View.GONE + false -> View.VISIBLE + } + } + numberPanel.apply { + color = c + units = h.unit + threshold = h.targetValue + visibility = when (h.isNumerical) { + true -> View.VISIBLE + false -> View.GONE + } + } + } + + private fun triggerRipple(x: Float, y: Float) { + val background = innerFrame.background + if (SDK_INT >= LOLLIPOP) background.setHotspot(x, y) + background.state = intArrayOf(android.R.attr.state_pressed, + android.R.attr.state_enabled) + Handler().postDelayed({ background.state = intArrayOf() }, 25) + } + + private fun updateBackground(isSelected: Boolean) { + if (SDK_INT < LOLLIPOP) { + val background = when (isSelected) { + true -> sres.getDrawable(R.attr.selectedBackground) + false -> sres.getDrawable(R.attr.cardBackground) + } + innerFrame.setBackgroundDrawable(background) + return + } + + val background = when (isSelected) { + true -> R.drawable.selected_box + false -> R.drawable.ripple + } + innerFrame.setBackgroundResource(background) + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/tasks/package-info.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardViewHolder.kt similarity index 80% rename from uhabits-android/src/main/java/org/isoron/uhabits/tasks/package-info.java rename to uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardViewHolder.kt index 5a175d814..ca820109c 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/tasks/package-info.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardViewHolder.kt @@ -17,8 +17,9 @@ * with this program. If not, see . */ -/** - * Provides async tasks for useful operations such as {@link - * org.isoron.uhabits.core.tasks.ExportCSVTask}. - */ -package org.isoron.uhabits.tasks; \ No newline at end of file +package org.isoron.uhabits.activities.habits.list.views + +import android.support.v7.widget.* +import android.view.* + +class HabitCardViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HeaderView.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HeaderView.java deleted file mode 100644 index 90bdebf39..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HeaderView.java +++ /dev/null @@ -1,192 +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.activities.habits.list.views; - -import android.content.*; -import android.graphics.*; -import android.support.annotation.*; -import android.text.*; -import android.util.*; - -import org.isoron.androidbase.utils.*; -import org.isoron.uhabits.*; -import org.isoron.uhabits.activities.common.views.*; -import org.isoron.uhabits.core.preferences.*; -import org.isoron.uhabits.core.utils.*; - -import java.util.*; - -import static org.isoron.androidbase.utils.InterfaceUtils.*; - -public class HeaderView extends ScrollableChart - implements Preferences.Listener, MidnightTimer.MidnightListener -{ - - private int buttonCount; - - @Nullable - private Preferences prefs; - - @Nullable - private MidnightTimer midnightTimer; - - private TextPaint paint; - - private RectF rect; - - public HeaderView(@NonNull Context context, - @NonNull Preferences prefs, - @NonNull MidnightTimer midnightTimer) - { - super(context); - this.prefs = prefs; - this.midnightTimer = midnightTimer; - init(); - } - - public HeaderView(Context context, @Nullable AttributeSet attrs) - { - super(context, attrs); - - Context appContext = context.getApplicationContext(); - if (appContext instanceof HabitsApplication) - { - HabitsApplication app = (HabitsApplication) appContext; - prefs = app.getComponent().getPreferences(); - midnightTimer = app.getComponent().getMidnightTimer(); - } - - init(); - } - - @Override - public void atMidnight() - { - post(() -> invalidate()); - } - - @Override - public void onCheckmarkOrderChanged() - { - updateDirection(); - postInvalidate(); - } - - public void setButtonCount(int buttonCount) - { - this.buttonCount = buttonCount; - postInvalidate(); - } - - @Override - protected void onAttachedToWindow() - { - updateDirection(); - super.onAttachedToWindow(); - if (prefs != null) prefs.addListener(this); - if (midnightTimer != null) midnightTimer.addListener(this); - } - - @Override - protected void onDetachedFromWindow() - { - if (midnightTimer != null) midnightTimer.removeListener(this); - if (prefs != null) prefs.removeListener(this); - super.onDetachedFromWindow(); - } - - @Override - protected void onDraw(Canvas canvas) - { - super.onDraw(canvas); - - GregorianCalendar day = DateUtils.getStartOfTodayCalendar(); - float width = getDimension(getContext(), R.dimen.checkmarkWidth); - float height = getDimension(getContext(), R.dimen.checkmarkHeight); - boolean reverse = shouldReverseCheckmarks(); - boolean isRtl = InterfaceUtils.isLayoutRtl(this); - - day.add(GregorianCalendar.DAY_OF_MONTH, -getDataOffset()); - float em = paint.measureText("m"); - - for (int i = 0; i < buttonCount; i++) - { - rect.set(0, 0, width, height); - rect.offset(canvas.getWidth(), 0); - - if (reverse) rect.offset(-(i + 1) * width, 0); - else rect.offset((i - buttonCount) * width, 0); - - if (isRtl) rect.set(canvas.getWidth() - rect.right, rect.top, - canvas.getWidth() - rect.left, rect.bottom); - - String text = DateUtils.formatHeaderDate(day).toUpperCase(); - String[] lines = text.split("\n"); - - int y1 = (int) (rect.centerY() - 0.25 * em); - int y2 = (int) (rect.centerY() + 1.25 * em); - - canvas.drawText(lines[0], rect.centerX(), y1, paint); - canvas.drawText(lines[1], rect.centerX(), y2, paint); - day.add(GregorianCalendar.DAY_OF_MONTH, -1); - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) - { - int width = MeasureSpec.getSize(widthMeasureSpec); - int height = (int) getDimension(getContext(), R.dimen.checkmarkHeight); - setMeasuredDimension(width, height); - } - - private void init() - { - setScrollerBucketSize( - (int) getDimension(getContext(), R.dimen.checkmarkWidth)); - - StyledResources sr = new StyledResources(getContext()); - paint = new TextPaint(); - paint.setColor(Color.BLACK); - paint.setAntiAlias(true); - paint.setTextSize(getDimension(getContext(), R.dimen.tinyTextSize)); - paint.setTextAlign(Paint.Align.CENTER); - paint.setTypeface(Typeface.DEFAULT_BOLD); - paint.setColor(sr.getColor(R.attr.mediumContrastTextColor)); - - rect = new RectF(); - - if (isInEditMode()) setButtonCount(5); - } - - private boolean shouldReverseCheckmarks() - { - if (prefs == null) return false; - return prefs.shouldReverseCheckmarks(); - } - - private void updateDirection() - { - int direction = -1; - if (shouldReverseCheckmarks()) direction *= -1; - if (InterfaceUtils.isLayoutRtl(this)) direction *= -1; - setDirection(direction); - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HeaderView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HeaderView.kt new file mode 100644 index 000000000..e5c057f55 --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HeaderView.kt @@ -0,0 +1,136 @@ +/* + * 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.activities.habits.list.views + +import android.content.* +import android.graphics.* +import android.os.Build.VERSION.* +import android.os.Build.VERSION_CODES.* +import android.text.* +import android.view.View.MeasureSpec.* +import org.isoron.uhabits.* +import org.isoron.uhabits.activities.common.views.* +import org.isoron.uhabits.core.preferences.* +import org.isoron.uhabits.core.utils.* +import org.isoron.uhabits.core.utils.DateUtils.* +import org.isoron.uhabits.utils.* +import java.util.* + +class HeaderView( + context: Context, + val prefs: Preferences, + val midnightTimer: MidnightTimer +) : ScrollableChart(context), + Preferences.Listener, + MidnightTimer.MidnightListener { + + private var drawer = Drawer() + + var buttonCount: Int = 0 + set(value) { + field = value + requestLayout() + } + + init { + setScrollerBucketSize(dim(R.dimen.checkmarkWidth).toInt()) + setBackgroundColor(sres.getColor(R.attr.headerBackgroundColor)) + if (SDK_INT >= LOLLIPOP) elevation = dp(2.0f) + } + + override fun atMidnight() { + post { invalidate() } + } + + override fun onCheckmarkSequenceChanged() { + updateScrollDirection() + postInvalidate() + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + updateScrollDirection() + prefs.addListener(this) + midnightTimer.addListener(this) + } + + override fun onDetachedFromWindow() { + midnightTimer.removeListener(this) + prefs.removeListener(this) + super.onDetachedFromWindow() + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + drawer.draw(canvas) + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + val height = dim(R.dimen.checkmarkHeight) + setMeasuredDimension(widthMeasureSpec, height.toMeasureSpec(EXACTLY)) + } + + private fun updateScrollDirection() { + var direction = -1 + if (prefs.isCheckmarkSequenceReversed) direction *= -1 + if (isRTL()) direction *= -1 + setScrollDirection(direction) + } + + private inner class Drawer { + private val rect = RectF() + private val paint = TextPaint().apply { + color = Color.BLACK + isAntiAlias = true + textSize = dim(R.dimen.tinyTextSize) + textAlign = Paint.Align.CENTER + typeface = Typeface.DEFAULT_BOLD + color = sres.getColor(R.attr.mediumContrastTextColor) + } + + fun draw(canvas: Canvas) { + val day = getStartOfTodayCalendar() + val width = dim(R.dimen.checkmarkWidth) + val height = dim(R.dimen.checkmarkHeight) + val isReversed = prefs.isCheckmarkSequenceReversed + + day.add(GregorianCalendar.DAY_OF_MONTH, -dataOffset) + val em = paint.measureText("m") + + repeat(buttonCount) { index -> + rect.set(0f, 0f, width, height) + rect.offset(canvas.width.toFloat(), 0f) + + if (isReversed) rect.offset(-(index + 1) * width, 0f) + else rect.offset((index - buttonCount) * width, 0f) + + if (isRTL()) rect.set(canvas.width - rect.right, rect.top, + canvas.width - rect.left, rect.bottom) + + val y1 = rect.centerY() - 0.25 * em + val y2 = rect.centerY() + 1.25 * em + val lines = formatHeaderDate(day).toUpperCase().split("\n") + canvas.drawText(lines[0], rect.centerX(), y1.toFloat(), paint) + canvas.drawText(lines[1], rect.centerX(), y2.toFloat(), paint) + day.add(GregorianCalendar.DAY_OF_MONTH, -1) + } + } + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HintView.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HintView.java deleted file mode 100644 index 1848b4827..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HintView.java +++ /dev/null @@ -1,134 +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.activities.habits.list.views; - -import android.animation.AnimatorListenerAdapter; -import android.annotation.SuppressLint; -import android.content.Context; -import android.support.annotation.Nullable; -import android.util.AttributeSet; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.TextView; - -import org.isoron.uhabits.R; -import org.isoron.uhabits.core.ui.screens.habits.list.HintList; - -import java.util.Random; - -import butterknife.BindView; -import butterknife.ButterKnife; - -public class HintView extends FrameLayout -{ - @BindView(R.id.hintContent) - TextView hintContent; - - @Nullable - private HintList hintList; - - public HintView(Context context) - { - super(context); - init(); - } - - public HintView(Context context, AttributeSet attrs) - { - super(context, attrs); - init(); - } - - @Override - public void onAttachedToWindow() - { - super.onAttachedToWindow(); - showNext(); - } - - /** - * Sets the list of hints to be shown - * - * @param hintList the list of hints to be shown - */ - public void setHints(@Nullable HintList hintList) - { - this.hintList = hintList; - } - - private void dismiss() - { - animate().alpha(0f).setDuration(500).setListener(new DismissAnimator()); - } - - private void init() - { - addView(inflate(getContext(), R.layout.list_habits_hint, null)); - ButterKnife.bind(this); - - setVisibility(GONE); - setClickable(true); - setOnClickListener(v -> dismiss()); - - if (isInEditMode()) initEditMode(); - } - - @SuppressLint("SetTextI18n") - private void initEditMode() - { - String hints[] = { - "Cats are the most popular pet in the United States: There " + - "are 88 million pet cats and 74 million dogs.", - "A cat has been mayor of Talkeetna, Alaska, for 15 years. " + - "His name is Stubbs.", - "Cats can’t taste sweetness." - }; - - int k = new Random().nextInt(hints.length); - hintContent.setText(hints[k]); - setVisibility(VISIBLE); - setAlpha(1.0f); - } - - protected void showNext() - { - if (hintList == null) return; - if (!hintList.shouldShow()) return; - - String hint = hintList.pop(); - if (hint == null) return; - - hintContent.setText(hint); - requestLayout(); - - setAlpha(0.0f); - setVisibility(View.VISIBLE); - animate().alpha(1f).setDuration(500); - } - - private class DismissAnimator extends AnimatorListenerAdapter - { - @Override - public void onAnimationEnd(android.animation.Animator animation) - { - setVisibility(View.GONE); - } - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HintView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HintView.kt new file mode 100644 index 000000000..8bbbf8ea2 --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HintView.kt @@ -0,0 +1,91 @@ +/* + * 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.activities.habits.list.views + +import android.animation.* +import android.content.* +import android.graphics.* +import android.graphics.Color.* +import android.view.* +import android.view.ViewGroup.LayoutParams.* +import android.widget.* +import org.isoron.uhabits.* +import org.isoron.uhabits.core.ui.screens.habits.list.* +import org.isoron.uhabits.utils.* + +class HintView( + context: Context, + private val hintList: HintList +) : LinearLayout(context) { + + val hintContent: TextView + + init { + isClickable = true + visibility = GONE + orientation = VERTICAL + val p1 = dp(16.0f).toInt() + val p2 = dp(4.0f).toInt() + setPadding(p1, p1, p2, p1) + setBackgroundColor(resources.getColor(R.color.indigo_500)) + + val hintTitle = TextView(context).apply { + setTextColor(WHITE) + setTypeface(null, Typeface.BOLD) + text = resources.getString(R.string.hint_title) + } + + hintContent = TextView(context).apply { + setTextColor(WHITE) + setPadding(0, dp(5.0f).toInt(), 0, 0) + } + + addView(hintTitle, WRAP_CONTENT, WRAP_CONTENT) + addView(hintContent, WRAP_CONTENT, WRAP_CONTENT) + setOnClickListener { dismiss() } + } + + public override fun onAttachedToWindow() { + super.onAttachedToWindow() + showNext() + } + + fun showNext() { + if (!hintList.shouldShow()) return + val hint = hintList.pop() ?: return + + hintContent.text = hint + requestLayout() + + alpha = 0.0f + visibility = View.VISIBLE + animate().alpha(1f).duration = 500 + } + + private fun dismiss() { + animate().alpha(0f).setDuration(500).setListener(DismissAnimator()) + } + + private inner class DismissAnimator : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: android.animation.Animator) { + visibility = View.GONE + } + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonView.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonView.java deleted file mode 100644 index db625cafc..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonView.java +++ /dev/null @@ -1,239 +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.activities.habits.list.views; - -import android.content.*; -import android.graphics.*; -import android.support.annotation.*; -import android.text.*; -import android.util.*; -import android.view.*; - -import org.isoron.androidbase.utils.*; -import org.isoron.uhabits.*; -import org.isoron.uhabits.activities.*; -import org.isoron.uhabits.core.preferences.*; -import org.isoron.uhabits.utils.*; - -import java.text.*; - -import static org.isoron.androidbase.utils.InterfaceUtils.getDimension; -import static org.isoron.uhabits.utils.AttributeSetUtils.getAttribute; -import static org.isoron.uhabits.utils.AttributeSetUtils.getIntAttribute; - -public class NumberButtonView extends View implements View.OnClickListener, - View.OnLongClickListener -{ - private static Typeface BOLD_TYPEFACE = - Typeface.create("sans-serif-condensed", Typeface.BOLD); - - private static Typeface NORMAL_TYPEFACE = - Typeface.create("sans-serif-condensed", Typeface.NORMAL); - - private int color; - - private double value; - - private double threshold; - - private String unit; - - private RectF rect; - - private TextPaint pRegular; - - private TextPaint pBold; - - private int lightGrey; - - private float em; - - private int darkGrey; - - @NonNull - private OnEditListener onEditListener; - - @NonNull - private OnInvalidEditListener onInvalidEditListener; - - @Nullable - private Preferences prefs; - - public NumberButtonView(@Nullable Context context) - { - super(context); - init(); - } - - public NumberButtonView(@Nullable Context ctx, @Nullable AttributeSet attrs) - { - super(ctx, attrs); - init(); - - if (ctx != null && attrs != null) - { - int color = getIntAttribute(ctx, attrs, "color", 0); - int value = getIntAttribute(ctx, attrs, "value", 0); - int threshold = getIntAttribute(ctx, attrs, "threshold", 1); - String unit = getAttribute(ctx, attrs, "unit", "min"); - setColor(PaletteUtils.getAndroidTestColor(color)); - setThreshold(threshold); - setValue(value); - setUnit(unit); - } - } - - public static String formatValue(double v) - { - if (v >= 1e9) return String.format("%.1fG", v / 1e9); - if (v >= 1e8) return String.format("%.0fM", v / 1e6); - if (v >= 1e7) return String.format("%.1fM", v / 1e6); - if (v >= 1e6) return String.format("%.1fM", v / 1e6); - if (v >= 1e5) return String.format("%.0fk", v / 1e3); - if (v >= 1e4) return String.format("%.1fk", v / 1e3); - if (v >= 1e3) return String.format("%.1fk", v / 1e3); - if (v >= 1e2) return new DecimalFormat("#").format(v); - if (v >= 1e1) return new DecimalFormat("#.#").format(v); - return new DecimalFormat("#.##").format(v); - } - - @Override - public void onClick(View v) - { - if(prefs == null) return; - if (prefs.isShortToggleEnabled()) onEditListener.onEdit(); - else onInvalidEditListener.onInvalidEdit(); - } - - @Override - public boolean onLongClick(View v) - { - onEditListener.onEdit(); - return true; - } - - public void setColor(int color) - { - this.color = color; - postInvalidate(); - } - - public void setThreshold(double threshold) - { - this.threshold = threshold; - postInvalidate(); - } - - public void setUnit(String unit) - { - this.unit = unit; - postInvalidate(); - } - - public void setValue(double value) - { - this.value = value; - postInvalidate(); - } - - public void setOnEditListener(@NonNull OnEditListener onEditListener) - { - this.onEditListener = onEditListener; - } - - public void setOnInvalidEditListener( - @NonNull OnInvalidEditListener onInvalidEditListener) - { - this.onInvalidEditListener = onInvalidEditListener; - } - - @Override - protected void onDraw(Canvas canvas) - { - int activeColor = lightGrey; - if (value > 0 && value < threshold) activeColor = darkGrey; - if (value >= threshold) activeColor = color; - - pRegular.setColor(activeColor); - pBold.setColor(activeColor); - - String fv = formatValue(value); - - rect.set(0, 0, getWidth(), getHeight()); - canvas.drawText(fv, rect.centerX(), rect.centerY(), pBold); - - rect.offset(0, 1.2f * em); - canvas.drawText(unit, rect.centerX(), rect.centerY(), pRegular); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) - { - int width = (int) getDimension(getContext(), R.dimen.checkmarkWidth); - int height = (int) getDimension(getContext(), R.dimen.checkmarkHeight); - setMeasuredDimension(width, height); - } - - private void init() - { - StyledResources sr = new StyledResources(getContext()); - - rect = new RectF(); - pRegular = new TextPaint(); - pRegular.setTextSize( - getDimension(getContext(), R.dimen.smallerTextSize)); - pRegular.setTypeface(NORMAL_TYPEFACE); - pRegular.setAntiAlias(true); - pRegular.setTextAlign(Paint.Align.CENTER); - - pBold = new TextPaint(); - pBold.setTextSize(getDimension(getContext(), R.dimen.smallTextSize)); - pBold.setTypeface(BOLD_TYPEFACE); - pBold.setAntiAlias(true); - pBold.setTextAlign(Paint.Align.CENTER); - - em = pBold.measureText("m"); - lightGrey = sr.getColor(R.attr.lowContrastTextColor); - darkGrey = sr.getColor(R.attr.mediumContrastTextColor); - - onEditListener = () -> {}; - onInvalidEditListener = () -> {}; - - setOnClickListener(this); - setOnLongClickListener(this); - - if(getContext() instanceof HabitsActivity) - { - HabitsApplicationComponent component = - ((HabitsActivity) getContext()).getAppComponent(); - prefs = component.getPreferences(); - } - } - - public interface OnEditListener - { - void onEdit(); - } - - public interface OnInvalidEditListener - { - void onInvalidEdit(); - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonView.kt new file mode 100644 index 000000000..53defd457 --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonView.kt @@ -0,0 +1,160 @@ +/* + * 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.activities.habits.list.views + +import android.content.* +import android.graphics.* +import android.text.* +import android.view.* +import android.view.View.* +import com.google.auto.factory.* +import org.isoron.androidbase.activities.* +import org.isoron.androidbase.utils.* +import org.isoron.androidbase.utils.InterfaceUtils.* +import org.isoron.uhabits.* +import org.isoron.uhabits.core.preferences.* +import org.isoron.uhabits.utils.* +import java.text.* + +private val BOLD_TYPEFACE = Typeface.create("sans-serif-condensed", Typeface.BOLD) +private val NORMAL_TYPEFACE = Typeface.create("sans-serif-condensed", Typeface.NORMAL) + +fun Double.toShortString(): String = when { + this >= 1e9 -> String.format("%.1fG", this / 1e9) + this >= 1e8 -> String.format("%.0fM", this / 1e6) + this >= 1e7 -> String.format("%.1fM", this / 1e6) + this >= 1e6 -> String.format("%.1fM", this / 1e6) + this >= 1e5 -> String.format("%.0fk", this / 1e3) + this >= 1e4 -> String.format("%.1fk", this / 1e3) + this >= 1e3 -> String.format("%.1fk", this / 1e3) + this >= 1e2 -> DecimalFormat("#").format(this) + this >= 1e1 -> DecimalFormat("#.#").format(this) + else -> DecimalFormat("#.##").format(this) +} + +@AutoFactory +class NumberButtonView( + @Provided @ActivityContext context: Context, + @Provided val preferences: Preferences +) : View(context), + OnClickListener, + OnLongClickListener { + + var color = 0 + set(value) { + field = value + invalidate() + } + + var value = 0.0 + set(value) { + field = value + invalidate() + } + + var threshold = 0.0 + set(value) { + field = value + invalidate() + } + + var units = "" + set(value) { + field = value + invalidate() + } + + var onEdit: () -> Unit = {} + private var drawer: Drawer = Drawer(context) + + init { + setOnClickListener(this) + setOnLongClickListener(this) + } + + override fun onClick(v: View) { + if (preferences.isShortToggleEnabled) onEdit() + else showMessage(R.string.long_press_to_edit) + } + + override fun onLongClick(v: View): Boolean { + onEdit() + return true + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + drawer.draw(canvas) + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + val width = getDimension(context, R.dimen.checkmarkWidth).toInt() + val height = getDimension(context, R.dimen.checkmarkHeight).toInt() + setMeasuredDimension(width, height) + } + + private inner class Drawer(context: Context) { + + private val em: Float + private val rect: RectF = RectF() + private val sr = StyledResources(context) + + private val lightGrey: Int + private val darkGrey: Int + + private val pRegular: TextPaint = TextPaint().apply { + textSize = getDimension(context, R.dimen.smallerTextSize) + typeface = NORMAL_TYPEFACE + isAntiAlias = true + textAlign = Paint.Align.CENTER + } + + private val pBold: TextPaint = TextPaint().apply { + textSize = getDimension(context, R.dimen.smallTextSize) + typeface = BOLD_TYPEFACE + isAntiAlias = true + textAlign = Paint.Align.CENTER + } + + init { + em = pBold.measureText("m") + lightGrey = sr.getColor(R.attr.lowContrastTextColor) + darkGrey = sr.getColor(R.attr.mediumContrastTextColor) + } + + fun draw(canvas: Canvas) { + val activeColor = when { + value == 0.0 -> lightGrey + value < threshold -> darkGrey + else -> color + } + + val label = value.toShortString() + pBold.color = activeColor + pRegular.color = activeColor + + rect.set(0f, 0f, width.toFloat(), height.toFloat()) + canvas.drawText(label, rect.centerX(), rect.centerY(), pBold) + + rect.offset(0f, 1.2f * em) + canvas.drawText(units, rect.centerX(), rect.centerY(), pRegular) + } + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelView.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelView.java deleted file mode 100644 index 80117bd58..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelView.java +++ /dev/null @@ -1,259 +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.activities.habits.list.views; - -import android.content.*; -import android.support.annotation.*; -import android.util.*; -import android.widget.*; - -import org.isoron.uhabits.*; -import org.isoron.uhabits.core.preferences.*; -import org.isoron.uhabits.core.utils.*; - -import java.util.*; - -import static android.view.View.MeasureSpec.EXACTLY; -import static android.view.View.MeasureSpec.makeMeasureSpec; -import static org.isoron.androidbase.utils.InterfaceUtils.getDimension; -import static org.isoron.uhabits.utils.AttributeSetUtils.getAttribute; -import static org.isoron.uhabits.utils.AttributeSetUtils.getIntAttribute; -import static org.isoron.uhabits.utils.PaletteUtils.getAndroidTestColor; - -public class NumberPanelView extends LinearLayout - implements Preferences.Listener -{ - private static final int LEFT_TO_RIGHT = 0; - - private static final int RIGHT_TO_LEFT = 1; - - @Nullable - private Preferences prefs; - - private double values[]; - - private double threshold; - - private int nButtons; - - private int color; - - private String unit; - - private int dataOffset; - - @NonNull - private OnInvalidEditListener onInvalidEditListener; - - private OnEditListener onEditListener; - - public NumberPanelView(Context context) - { - super(context); - init(); - } - - public NumberPanelView(Context ctx, AttributeSet attrs) - { - super(ctx, attrs); - init(); - - if (ctx != null && attrs != null) - { - int paletteColor = getIntAttribute(ctx, attrs, "color", 0); - setColor(getAndroidTestColor(paletteColor)); - setButtonCount(getIntAttribute(ctx, attrs, "button_count", 5)); - setThreshold(getIntAttribute(ctx, attrs, "threshold", 1)); - setUnit(getAttribute(ctx, attrs, "unit", "min")); - } - - if (isInEditMode()) initEditMode(); - } - - public NumberButtonView indexToButton(int i) - { - int position = i; - if (getCheckmarkOrder() == RIGHT_TO_LEFT) position = nButtons - i - 1; - return (NumberButtonView) getChildAt(position); - } - - public void initEditMode() - { - double values[] = new double[nButtons]; - for (int i = 0; i < nButtons; i++) - values[i] = new Random().nextDouble() * (threshold * 3); - setValues(values); - } - - @Override - public void onCheckmarkOrderChanged() - { - setupButtons(); - } - - public void setButtonCount(int newButtonCount) - { - if (nButtons != newButtonCount) - { - nButtons = newButtonCount; - addButtons(); - } - - setupButtons(); - } - - public void setColor(int color) - { - this.color = color; - setupButtons(); - } - - public void setDataOffset(int dataOffset) - { - this.dataOffset = dataOffset; - setupButtons(); - } - - public void setOnInvalidEditListener( - @NonNull OnInvalidEditListener onInvalidEditListener) - { - this.onInvalidEditListener = onInvalidEditListener; - } - - public void setOnEditListener(OnEditListener onEditListener) - { - this.onEditListener = onEditListener; - } - - public void setThreshold(double threshold) - { - this.threshold = threshold; - setupButtons(); - } - - public void setUnit(String unit) - { - this.unit = unit; - setupButtons(); - } - - public void setValues(double[] values) - { - this.values = values; - setupButtons(); - } - - @Override - protected void onAttachedToWindow() - { - super.onAttachedToWindow(); - if (prefs != null) prefs.addListener(this); - } - - @Override - protected void onDetachedFromWindow() - { - if (prefs != null) prefs.removeListener(this); - super.onDetachedFromWindow(); - } - - @Override - protected void onMeasure(int widthSpec, int heightSpec) - { - Context context = getContext(); - float buttonWidth = getDimension(context, R.dimen.checkmarkWidth); - float buttonHeight = getDimension(context, R.dimen.checkmarkHeight); - - float width = buttonWidth * nButtons; - - widthSpec = makeMeasureSpec((int) width, EXACTLY); - heightSpec = makeMeasureSpec((int) buttonHeight, EXACTLY); - - super.onMeasure(widthSpec, heightSpec); - } - - private void addButtons() - { - removeAllViews(); - for (int i = 0; i < nButtons; i++) - addView(new NumberButtonView(getContext())); - } - - private int getCheckmarkOrder() - { - if (prefs == null) return LEFT_TO_RIGHT; - return prefs.shouldReverseCheckmarks() ? RIGHT_TO_LEFT : LEFT_TO_RIGHT; - } - - private void init() - { - Context appContext = getContext().getApplicationContext(); - if (appContext instanceof HabitsApplication) - { - HabitsApplication app = (HabitsApplication) appContext; - prefs = app.getComponent().getPreferences(); - } - - setWillNotDraw(false); - values = new double[0]; - - onInvalidEditListener = () -> {}; - onEditListener = (t) -> {}; - } - - private void setupButtonControllers(long timestamp, - NumberButtonView buttonView) - { - buttonView.setOnEditListener( - () -> onEditListener.onEdit(timestamp)); - - buttonView.setOnInvalidEditListener( - () -> onInvalidEditListener.onInvalidEdit()); - } - - private void setupButtons() - { - long timestamp = DateUtils.getStartOfToday(); - long day = DateUtils.millisecondsInOneDay; - timestamp -= day * dataOffset; - - for (int i = 0; i < nButtons; i++) - { - NumberButtonView buttonView = indexToButton(i); - if (i + dataOffset >= values.length) break; - buttonView.setValue(values[i + dataOffset]); - buttonView.setColor(color); - buttonView.setThreshold(threshold); - buttonView.setUnit(unit); - setupButtonControllers(timestamp, buttonView); - timestamp -= day; - } - } - - public interface OnInvalidEditListener - { - void onInvalidEdit(); - } - - public interface OnEditListener - { - void onEdit(long timestamp); - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelView.kt new file mode 100644 index 000000000..aaaabc2a6 --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelView.kt @@ -0,0 +1,84 @@ +/* + * 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.activities.habits.list.views + +import android.content.* +import com.google.auto.factory.* +import org.isoron.androidbase.activities.* +import org.isoron.uhabits.core.preferences.* +import org.isoron.uhabits.core.utils.* + +@AutoFactory +class NumberPanelView( + @Provided @ActivityContext context: Context, + @Provided preferences: Preferences, + @Provided private val buttonFactory: NumberButtonViewFactory +) : ButtonPanelView(context, preferences) { + + var values = DoubleArray(0) + set(values) { + field = values + setupButtons() + } + + var threshold = 0.0 + set(value) { + field = value + setupButtons() + } + + var color = 0 + set(value) { + field = value + setupButtons() + } + + var units = "" + set(value) { + field = value + setupButtons() + } + + var onEdit: (Long) -> Unit = {} + set(value) { + field = value + setupButtons() + } + + override fun createButton() = buttonFactory.create()!! + + @Synchronized + override fun setupButtons() { + val day = DateUtils.millisecondsInOneDay + val today = DateUtils.getStartOfToday() + + buttons.forEachIndexed { index, button -> + val timestamp = today - (index + dataOffset) * day + button.value = when { + index + dataOffset < values.size -> values[index + dataOffset] + else -> 0.0 + } + button.color = color + button.threshold = threshold + button.units = units + button.onEdit = { onEdit(timestamp) } + } + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/ShadowView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/ShadowView.kt new file mode 100644 index 000000000..bc97fb198 --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/ShadowView.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2017 Á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.activities.habits.list.views + +import android.content.* +import android.view.* +import android.view.View.MeasureSpec.* +import org.isoron.uhabits.* +import org.isoron.uhabits.utils.* + +@Suppress("DEPRECATION") +class ShadowView(context: Context) : View(context) { + init { + alpha = 0.2f + background = resources.getDrawable(R.drawable.shadow) + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, + dp(2.0f).toInt().toMeasureSpec(EXACTLY)) + } +} \ No newline at end of file diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitActivity.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitActivity.java index 42178f0ef..104056810 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitActivity.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitActivity.java @@ -34,6 +34,6 @@ public class ShowHabitActivity extends HabitsActivity protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setScreen(getActivityComponent().getShowHabitScreen()); + setScreen(getComponent().getShowHabitScreen()); } } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/package-info.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/package-info.java deleted file mode 100644 index ca132a6c7..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/package-info.java +++ /dev/null @@ -1,24 +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 . - */ - -/** - * Provides activity that displays detailed habit information and related - * classes. - */ -package org.isoron.uhabits.activities.habits.show; \ No newline at end of file diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/intro/package-info.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/intro/package-info.java deleted file mode 100644 index 4023d1b94..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/intro/package-info.java +++ /dev/null @@ -1,23 +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 . - */ - -/** - * Provides activity that introduces app to the user and related classes. - */ -package org.isoron.uhabits.activities.intro; \ No newline at end of file diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/package-info.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/package-info.java deleted file mode 100644 index abc95467c..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/package-info.java +++ /dev/null @@ -1,23 +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 . - */ - -/** - * Provides classes for the Android activites. - */ -package org.isoron.uhabits.activities; \ No newline at end of file diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/settings/SettingsFragment.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/settings/SettingsFragment.java index 07f4e062c..3badaac52 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/settings/SettingsFragment.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/settings/SettingsFragment.java @@ -39,11 +39,11 @@ import static android.media.RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT; import static android.media.RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT; import static android.media.RingtoneManager.EXTRA_RINGTONE_TYPE; import static android.media.RingtoneManager.TYPE_NOTIFICATION; -import static org.isoron.uhabits.activities.habits.list.ListHabitsScreen.RESULT_BUG_REPORT; -import static org.isoron.uhabits.activities.habits.list.ListHabitsScreen.RESULT_EXPORT_CSV; -import static org.isoron.uhabits.activities.habits.list.ListHabitsScreen.RESULT_EXPORT_DB; -import static org.isoron.uhabits.activities.habits.list.ListHabitsScreen.RESULT_IMPORT_DATA; -import static org.isoron.uhabits.activities.habits.list.ListHabitsScreen.RESULT_REPAIR_DB; +import static org.isoron.uhabits.activities.habits.list.ListHabitsScreenKt.RESULT_BUG_REPORT; +import static org.isoron.uhabits.activities.habits.list.ListHabitsScreenKt.RESULT_EXPORT_CSV; +import static org.isoron.uhabits.activities.habits.list.ListHabitsScreenKt.RESULT_EXPORT_DB; +import static org.isoron.uhabits.activities.habits.list.ListHabitsScreenKt.RESULT_IMPORT_DATA; +import static org.isoron.uhabits.activities.habits.list.ListHabitsScreenKt.RESULT_REPAIR_DB; public class SettingsFragment extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/settings/package-info.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/settings/package-info.java deleted file mode 100644 index 73ed9c7a1..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/settings/package-info.java +++ /dev/null @@ -1,23 +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 . - */ - -/** - * Provides activity for changing the settings. - */ -package org.isoron.uhabits.activities.settings; \ No newline at end of file diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/package-info.java b/uhabits-android/src/main/java/org/isoron/uhabits/package-info.java deleted file mode 100644 index b080842fc..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/package-info.java +++ /dev/null @@ -1,23 +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 . - */ - -/** - * Provides classes for the Loop Habit Tracker app. - */ -package org.isoron.uhabits; \ No newline at end of file diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/preferences/SharedPreferencesStorage.java b/uhabits-android/src/main/java/org/isoron/uhabits/preferences/SharedPreferencesStorage.java index 76b2a4c3b..7464a6992 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/preferences/SharedPreferencesStorage.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/preferences/SharedPreferencesStorage.java @@ -93,7 +93,7 @@ public class SharedPreferencesStorage switch (key) { case "pref_checkmark_reverse_order": - preferences.setShouldReverseCheckmarks(getBoolean(key, false)); + preferences.setCheckmarkSequenceReversed(getBoolean(key, false)); break; case "pref_sticky_notifications": diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/utils/ViewExtensions.kt b/uhabits-android/src/main/java/org/isoron/uhabits/utils/ViewExtensions.kt new file mode 100644 index 000000000..427bc5ba6 --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/utils/ViewExtensions.kt @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2017 Á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.utils + +import android.graphics.* +import android.support.annotation.* +import android.support.design.widget.* +import android.support.v7.widget.Toolbar +import android.view.* +import android.view.ViewGroup.LayoutParams.* +import android.widget.* +import android.widget.RelativeLayout.* +import org.isoron.androidbase.utils.* +import org.isoron.uhabits.* + +fun RelativeLayout.addBelow(view: View, + subject: View, + width: Int = MATCH_PARENT, + height: Int = WRAP_CONTENT, + applyCustomRules: (params: RelativeLayout.LayoutParams) -> Unit = {}) { + + view.layoutParams = RelativeLayout.LayoutParams(width, height).apply { + addRule(BELOW, subject.id) + applyCustomRules(this) + } + view.id = View.generateViewId() + this.addView(view) +} + +fun RelativeLayout.addAtBottom(view: View, + width: Int = MATCH_PARENT, + height: Int = WRAP_CONTENT) { + + view.layoutParams = RelativeLayout.LayoutParams(width, height).apply { + addRule(ALIGN_PARENT_BOTTOM) + } + view.id = View.generateViewId() + this.addView(view) +} + +fun RelativeLayout.addAtTop(view: View, + width: Int = MATCH_PARENT, + height: Int = WRAP_CONTENT) { + + view.layoutParams = RelativeLayout.LayoutParams(width, height).apply { + addRule(ALIGN_PARENT_TOP) + } + view.id = View.generateViewId() + this.addView(view) +} + +fun ViewGroup.buildToolbar(): Toolbar { + val inflater = LayoutInflater.from(context) + return inflater.inflate(R.layout.toolbar, null) as Toolbar +} + +fun View.showMessage(@StringRes stringId: Int) { + try { + val snackbar = Snackbar.make(this, stringId, Snackbar.LENGTH_SHORT) + val tvId = android.support.design.R.id.snackbar_text + val tv = snackbar.view.findViewById(tvId) + if(tv is TextView) tv.setTextColor(Color.WHITE) + snackbar.show() + } catch (e: IllegalArgumentException) { + return + } +} + +fun Int.toMeasureSpec(mode: Int) = + View.MeasureSpec.makeMeasureSpec(this, mode) + +fun Float.toMeasureSpec(mode: Int) = + View.MeasureSpec.makeMeasureSpec(toInt(), mode) + +fun View.isRTL() = InterfaceUtils.isLayoutRtl(this) +fun View.getFontAwesome() = InterfaceUtils.getFontAwesome(context)!! +fun View.dim(id: Int) = InterfaceUtils.getDimension(context, id) +fun View.sp(value: Float) = InterfaceUtils.spToPixels(context, value) +fun View.dp(value: Float) = InterfaceUtils.dpToPixels(context, value) +fun View.str(id: Int) = resources.getString(id) +val View.sres: StyledResources + get() = StyledResources(context) diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/utils/package-info.java b/uhabits-android/src/main/java/org/isoron/uhabits/utils/package-info.java deleted file mode 100644 index 614727d39..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/utils/package-info.java +++ /dev/null @@ -1,23 +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 . - */ - -/** - * Provides various utilities classes, such as {@link org.isoron.androidbase.utils.ColorUtils}. - */ -package org.isoron.uhabits.utils; \ No newline at end of file diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/package-info.java b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/package-info.java deleted file mode 100644 index 5616e64ad..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/package-info.java +++ /dev/null @@ -1,23 +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 . - */ - -/** - * Provides home-screen Android widgets and related classes. - */ -package org.isoron.uhabits.widgets; \ No newline at end of file diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/package-info.java b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/package-info.java deleted file mode 100644 index 2fbf2799c..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/package-info.java +++ /dev/null @@ -1,23 +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 . - */ - -/** - * Provides views that are specific for the home-screen widgets. - */ -package org.isoron.uhabits.widgets.views; \ No newline at end of file diff --git a/uhabits-android/src/main/res/drawable/toolbar_shadow.xml b/uhabits-android/src/main/res/drawable/shadow.xml similarity index 100% rename from uhabits-android/src/main/res/drawable/toolbar_shadow.xml rename to uhabits-android/src/main/res/drawable/shadow.xml diff --git a/uhabits-android/src/main/res/layout/list_habits.xml b/uhabits-android/src/main/res/layout/list_habits.xml deleted file mode 100644 index 3515790a4..000000000 --- a/uhabits-android/src/main/res/layout/list_habits.xml +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/uhabits-android/src/main/res/layout/list_habits_button_preview.xml b/uhabits-android/src/main/res/layout/list_habits_button_preview.xml deleted file mode 100644 index 5f2724a07..000000000 --- a/uhabits-android/src/main/res/layout/list_habits_button_preview.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/uhabits-android/src/main/res/layout/list_habits_panel_preview.xml b/uhabits-android/src/main/res/layout/list_habits_panel_preview.xml deleted file mode 100644 index e20ca4438..000000000 --- a/uhabits-android/src/main/res/layout/list_habits_panel_preview.xml +++ /dev/null @@ -1,107 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/uhabits-android/src/main/res/layout/list_habits_preview.xml b/uhabits-android/src/main/res/layout/list_habits_preview.xml deleted file mode 100644 index 5ea6eb697..000000000 --- a/uhabits-android/src/main/res/layout/list_habits_preview.xml +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/uhabits-android/src/main/res/layout/toolbar.xml b/uhabits-android/src/main/res/layout/toolbar.xml new file mode 100644 index 000000000..8d263e906 --- /dev/null +++ b/uhabits-android/src/main/res/layout/toolbar.xml @@ -0,0 +1,28 @@ + + + + \ No newline at end of file diff --git a/uhabits-android/src/main/res/values/styles.xml b/uhabits-android/src/main/res/values/styles.xml index 976ddf172..622fffe3e 100644 --- a/uhabits-android/src/main/res/values/styles.xml +++ b/uhabits-android/src/main/res/values/styles.xml @@ -237,7 +237,7 @@ diff --git a/uhabits-android/src/test/java/org/isoron/uhabits/BaseAndroidUnitTest.java b/uhabits-android/src/test/java/org/isoron/uhabits/BaseAndroidJVMTest.java similarity index 98% rename from uhabits-android/src/test/java/org/isoron/uhabits/BaseAndroidUnitTest.java rename to uhabits-android/src/test/java/org/isoron/uhabits/BaseAndroidJVMTest.java index f35f77ec4..a2f2c780d 100644 --- a/uhabits-android/src/test/java/org/isoron/uhabits/BaseAndroidUnitTest.java +++ b/uhabits-android/src/test/java/org/isoron/uhabits/BaseAndroidJVMTest.java @@ -34,16 +34,12 @@ import java.util.*; import static org.mockito.Mockito.*; @RunWith(MockitoJUnitRunner.class) -public class BaseAndroidUnitTest +public class BaseAndroidJVMTest { protected HabitList habitList; - protected HabitFixtures fixtures; - protected MemoryModelFactory modelFactory; - protected SingleThreadTaskRunner taskRunner; - protected CommandRunner commandRunner; @Before diff --git a/uhabits-android/src/test/java/org/isoron/uhabits/activities/habits/list/ListHabitsMenuTest.java b/uhabits-android/src/test/java/org/isoron/uhabits/activities/habits/list/ListHabitsMenuTest.java deleted file mode 100644 index 8127343a5..000000000 --- a/uhabits-android/src/test/java/org/isoron/uhabits/activities/habits/list/ListHabitsMenuTest.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (C) 2017 Á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.activities.habits.list; - -import android.view.*; - -import org.isoron.androidbase.activities.*; -import org.isoron.uhabits.*; -import org.isoron.uhabits.R; -import org.isoron.uhabits.core.preferences.*; -import org.isoron.uhabits.core.ui.*; -import org.isoron.uhabits.core.ui.screens.habits.list.*; -import org.junit.*; - -import static org.mockito.Mockito.*; - -public class ListHabitsMenuTest extends BaseAndroidUnitTest -{ - private BaseActivity activity; - - private Preferences preferences; - - private ThemeSwitcher themeSwitcher; - - private ListHabitsMenu menu; - - private ListHabitsMenuBehavior behavior; - - @Before - @Override - public void setUp() - { - activity = mock(BaseActivity.class); - preferences = mock(Preferences.class); - themeSwitcher = mock(ThemeSwitcher.class); - behavior = mock(ListHabitsMenuBehavior.class); - - menu = new ListHabitsMenu(activity, preferences, - themeSwitcher, behavior); - } - - @Test - public void testOnCreate() - { - MenuItem nightModeItem = mock(MenuItem.class); - MenuItem hideArchivedItem = mock(MenuItem.class); - MenuItem hideCompletedItem = mock(MenuItem.class); - Menu androidMenu = mock(Menu.class); - when(androidMenu.findItem(R.id.actionToggleNightMode)).thenReturn( - nightModeItem); - when(androidMenu.findItem(R.id.actionHideArchived)).thenReturn( - hideArchivedItem); - when(androidMenu.findItem(R.id.actionHideCompleted)).thenReturn( - hideCompletedItem); - - when(preferences.getShowArchived()).thenReturn(false); - when(preferences.getShowCompleted()).thenReturn(false); - when(themeSwitcher.isNightMode()).thenReturn(false); - - menu.onCreate(androidMenu); - - verify(nightModeItem).setChecked(false); - verify(hideArchivedItem).setChecked(true); - verify(hideCompletedItem).setChecked(true); - reset(nightModeItem, hideArchivedItem, hideCompletedItem); - - when(preferences.getShowArchived()).thenReturn(true); - when(preferences.getShowCompleted()).thenReturn(true); - when(themeSwitcher.isNightMode()).thenReturn(true); - - menu.onCreate(androidMenu); - - verify(nightModeItem).setChecked(true); - verify(hideArchivedItem).setChecked(false); - verify(hideCompletedItem).setChecked(false); - } - - @Test - public void testOnSelected_about() - { - onItemSelected(R.id.actionAbout); - verify(behavior).onViewAbout(); - } - - @Test - public void testOnSelected_add() - { - onItemSelected(R.id.actionAdd); - verify(behavior).onCreateHabit(); - } - - @Test - public void testOnSelected_faq() - { - onItemSelected(R.id.actionFAQ); - verify(behavior).onViewFAQ(); - } - - @Test - public void testOnSelected_nightMode() - { - onItemSelected(R.id.actionToggleNightMode); - verify(behavior).onToggleNightMode(); - } - - @Test - public void testOnSelected_settings() - { - onItemSelected(R.id.actionSettings); - verify(behavior).onViewSettings(); - } - - @Test - public void testOnSelected_showArchived() - { - onItemSelected(R.id.actionHideArchived); - verify(behavior).onToggleShowArchived(); - } - - @Test - public void testOnSelected_showCompleted() - { - onItemSelected(R.id.actionHideCompleted); - verify(behavior).onToggleShowCompleted(); - } - - protected void onItemSelected(int actionId) - { - MenuItem item = mock(MenuItem.class); - when(item.getItemId()).thenReturn(actionId); - menu.onItemSelected(item); - } -} \ No newline at end of file diff --git a/uhabits-android/src/test/java/org/isoron/uhabits/activities/habits/list/ListHabitsMenuTest.kt b/uhabits-android/src/test/java/org/isoron/uhabits/activities/habits/list/ListHabitsMenuTest.kt new file mode 100644 index 000000000..09a6fdd94 --- /dev/null +++ b/uhabits-android/src/test/java/org/isoron/uhabits/activities/habits/list/ListHabitsMenuTest.kt @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2017 Á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.activities.habits.list + +import android.view.* +import org.isoron.androidbase.activities.* +import org.isoron.uhabits.* +import org.isoron.uhabits.core.preferences.* +import org.isoron.uhabits.core.ui.* +import org.isoron.uhabits.core.ui.screens.habits.list.* +import org.junit.* +import org.mockito.* +import org.mockito.Mockito.* + +class ListHabitsMenuTest : BaseAndroidJVMTest() { + @Mock lateinit var activity: BaseActivity + @Mock lateinit var preferences: Preferences + @Mock lateinit var themeSwitcher: ThemeSwitcher + @Mock lateinit var behavior: ListHabitsMenuBehavior + lateinit var menu: ListHabitsMenu + + @Before + override fun setUp() { + super.setUp() + menu = ListHabitsMenu(activity, preferences, + themeSwitcher, behavior) + } + + @Test + fun testOnCreate() { + val nightModeItem = mock(MenuItem::class.java) + val hideArchivedItem = mock(MenuItem::class.java) + val hideCompletedItem = mock(MenuItem::class.java) + val androidMenu = mock(Menu::class.java) + `when`(androidMenu.findItem(R.id.actionToggleNightMode)).thenReturn( + nightModeItem) + `when`(androidMenu.findItem(R.id.actionHideArchived)).thenReturn( + hideArchivedItem) + `when`(androidMenu.findItem(R.id.actionHideCompleted)).thenReturn( + hideCompletedItem) + + `when`(preferences.showArchived).thenReturn(false) + `when`(preferences.showCompleted).thenReturn(false) + `when`(themeSwitcher.isNightMode).thenReturn(false) + + menu.onCreate(androidMenu) + + verify(nightModeItem).isChecked = false + verify(hideArchivedItem).isChecked = true + verify(hideCompletedItem).isChecked = true + reset(nightModeItem, hideArchivedItem, hideCompletedItem) + + `when`(preferences.showArchived).thenReturn(true) + `when`(preferences.showCompleted).thenReturn(true) + `when`(themeSwitcher.isNightMode).thenReturn(true) + + menu.onCreate(androidMenu) + + verify(nightModeItem).isChecked = true + verify(hideArchivedItem).isChecked = false + verify(hideCompletedItem).isChecked = false + } + + @Test + fun testOnSelected_about() { + onItemSelected(R.id.actionAbout) + verify(behavior).onViewAbout() + } + + @Test + fun testOnSelected_add() { + onItemSelected(R.id.actionAdd) + verify(behavior).onCreateHabit() + } + + @Test + fun testOnSelected_faq() { + onItemSelected(R.id.actionFAQ) + verify(behavior).onViewFAQ() + } + + @Test + fun testOnSelected_nightMode() { + onItemSelected(R.id.actionToggleNightMode) + verify(behavior).onToggleNightMode() + } + + @Test + fun testOnSelected_settings() { + onItemSelected(R.id.actionSettings) + verify(behavior).onViewSettings() + } + + @Test + fun testOnSelected_showArchived() { + onItemSelected(R.id.actionHideArchived) + verify(behavior).onToggleShowArchived() + } + + @Test + fun testOnSelected_showCompleted() { + onItemSelected(R.id.actionHideCompleted) + verify(behavior).onToggleShowCompleted() + } + + private fun onItemSelected(actionId: Int) { + val item = mock(MenuItem::class.java) + `when`(item.itemId).thenReturn(actionId) + menu.onItemSelected(item) + } +} \ No newline at end of file diff --git a/uhabits-android/src/test/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreenTest.java b/uhabits-android/src/test/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreenTest.java deleted file mode 100644 index d72382f69..000000000 --- a/uhabits-android/src/test/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreenTest.java +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Copyright (C) 2017 Á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.activities.habits.list; - - -import android.content.*; - -import org.isoron.androidbase.activities.*; -import org.isoron.uhabits.R; -import org.isoron.uhabits.activities.common.dialogs.*; -import org.isoron.uhabits.activities.habits.edit.*; -import org.isoron.uhabits.core.commands.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.preferences.*; -import org.isoron.uhabits.core.ui.*; -import org.isoron.uhabits.core.ui.callbacks.*; -import org.isoron.uhabits.intents.*; -import org.junit.*; -import org.junit.runner.*; -import org.mockito.junit.*; - -import java.util.*; - -import static org.isoron.uhabits.activities.habits.list.ListHabitsScreen.*; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.*; -import static org.mockito.Mockito.eq; - -@RunWith(MockitoJUnitRunner.class) -public class ListHabitsScreenTest -{ - private BaseActivity activity; - - private ListHabitsRootView rootView; - - private ListHabitsScreen screen; - - private ListHabitsController controller; - - private Habit habit; - - private Intent intent; - - private ConfirmDeleteDialogFactory confirmDeleteDialogFactory; - - private IntentFactory intentFactory; - - private CommandRunner commandRunner; - - private ColorPickerDialogFactory colorPickerDialogFactory; - - private EditHabitDialogFactory dialogFactory; - - private ThemeSwitcher themeSwitcher; - - private Preferences prefs; - - private CommandParser commandParser; - - @Before - public void setUp() - { - activity = mock(ListHabitsActivity.class); - commandRunner = mock(CommandRunner.class); - rootView = mock(ListHabitsRootView.class); - intentFactory = mock(IntentFactory.class); - themeSwitcher = mock(ThemeSwitcher.class); - confirmDeleteDialogFactory = mock(ConfirmDeleteDialogFactory.class); - colorPickerDialogFactory = mock(ColorPickerDialogFactory.class); - dialogFactory = mock(EditHabitDialogFactory.class); - prefs = mock(Preferences.class); - commandParser = mock(CommandParser.class); - - screen = spy(new ListHabitsScreen(activity, commandRunner, rootView, - intentFactory, themeSwitcher, confirmDeleteDialogFactory, - colorPickerDialogFactory, dialogFactory, prefs)); - - doNothing().when(screen).showMessage(anyInt()); - - controller = mock(ListHabitsController.class); - screen.setController(controller); - - habit = mock(Habit.class); - intent = mock(Intent.class); - } - -// @Test -// public void testCreateHabitScreen() -// { -// CreateBooleanHabitDialog dialog = mock(CreateBooleanHabitDialog.class); -// when(createHabitDialogFactory.create()).thenReturn(dialog); -// -// screen.showCreateHabitScreen(); -// -// verify(activity).showDialog(eq(dialog), any()); -// } - - @Test - public void testOnAttached() - { - screen.onAttached(); - verify(commandRunner).addListener(screen); - } - - @Test - public void testOnCommand() - { - Command c = mock(DeleteHabitsCommand.class); - screen.onCommandExecuted(c, null); - verify(screen).showMessage(R.string.toast_habit_deleted); - } - - @Test - public void testOnDetach() - { - screen.onDettached(); - verify(commandRunner).removeListener(screen); - } - - @Test - public void testOnResult_bugReport() - { - screen.onResult(REQUEST_SETTINGS, RESULT_BUG_REPORT, null); - verify(controller).onSendBugReport(); - } - - @Test - public void testOnResult_exportCSV() - { - screen.onResult(REQUEST_SETTINGS, RESULT_EXPORT_CSV, null); - verify(controller).onExportCSV(); - } - - @Test - public void testOnResult_exportDB() - { - screen.onResult(REQUEST_SETTINGS, RESULT_EXPORT_DB, null); - verify(controller).onExportDB(); - } - - @Test - public void testOnResult_importData() - { - screen.onResult(REQUEST_SETTINGS, RESULT_IMPORT_DATA, null); - testShowImportScreen(); - } - - @Test - public void testShowAboutScreen() throws Exception - { - when(intentFactory.startAboutActivity(activity)).thenReturn(intent); - screen.showAboutScreen(); - verify(activity).startActivity(eq(intent)); - } - - @Test - public void testShowColorPicker() - { - ColorPickerDialog picker = mock(ColorPickerDialog.class); - when(colorPickerDialogFactory.create(999)).thenReturn(picker); - OnColorPickedCallback callback = mock(OnColorPickedCallback.class); - - screen.showColorPicker(999, callback); - - verify(activity).showDialog(eq(picker), any()); - verify(picker).setListener(callback); - } - - @Test - public void testShowDeleteConfirmationScreen() - { - OnConfirmedCallback callback = mock(OnConfirmedCallback.class); - ConfirmDeleteDialog dialog = mock(ConfirmDeleteDialog.class); - when(confirmDeleteDialogFactory.create(callback)).thenReturn(dialog); - - screen.showDeleteConfirmationScreen(callback); - - verify(activity).showDialog(dialog); - } - - @Test - public void testShowEditHabitScreen() - { - EditHabitDialog dialog = mock(EditHabitDialog.class); - when(dialogFactory.edit(habit)).thenReturn(dialog); - screen.showEditHabitsScreen(Collections.singletonList(habit)); - verify(activity).showDialog(eq(dialog), any()); - } - - @Test - public void testShowFAQScreen() - { - when(intentFactory.viewFAQ(activity)).thenReturn(intent); - screen.showFAQScreen(); - verify(activity).startActivity(intent); - } - - @Test - public void testShowHabitScreen() - { - when(intentFactory.startShowHabitActivity(activity, habit)).thenReturn( - intent); - screen.showHabitScreen(habit); - verify(activity).startActivity(intent); - } - - @Test - public void testShowImportScreen() - { - when(intentFactory.openDocument()).thenReturn(intent); - screen.showImportScreen(); - verify(activity).startActivityForResult(intent, REQUEST_OPEN_DOCUMENT); - } - - @Test - public void testShowIntroScreen() - { - when(intentFactory.startIntroActivity(activity)).thenReturn(intent); - screen.showIntroScreen(); - verify(activity).startActivity(intent); - } - - @Test - public void testShowSettingsScreen() - { - when(intentFactory.startSettingsActivity(activity)).thenReturn(intent); - screen.showSettingsScreen(); - verify(activity).startActivityForResult(eq(intent), anyInt()); - } - - @Test - public void testApplyTheme() - { - screen.applyTheme(); - verify(activity).restartWithFade(ListHabitsActivity.class); - } -} \ No newline at end of file diff --git a/uhabits-android/src/test/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreenTest.kt b/uhabits-android/src/test/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreenTest.kt new file mode 100644 index 000000000..ea206264f --- /dev/null +++ b/uhabits-android/src/test/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreenTest.kt @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2017 Á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.activities.habits.list + +import android.content.* +import dagger.* +import org.isoron.androidbase.activities.* +import org.isoron.uhabits.* +import org.isoron.uhabits.activities.common.dialogs.* +import org.isoron.uhabits.activities.habits.edit.* +import org.isoron.uhabits.activities.habits.list.views.* +import org.isoron.uhabits.core.commands.* +import org.isoron.uhabits.core.models.* +import org.isoron.uhabits.core.preferences.* +import org.isoron.uhabits.core.ui.* +import org.isoron.uhabits.core.ui.callbacks.* +import org.isoron.uhabits.core.ui.screens.habits.list.* +import org.isoron.uhabits.intents.* +import org.isoron.uhabits.tasks.* +import org.junit.* +import org.junit.runner.* +import org.mockito.* +import org.mockito.ArgumentMatchers.* +import org.mockito.Mockito.`when` +import org.mockito.Mockito.doNothing +import org.mockito.Mockito.mock +import org.mockito.Mockito.spy +import org.mockito.Mockito.verify +import org.mockito.junit.* + +@RunWith(MockitoJUnitRunner::class) +class ListHabitsScreenTest : BaseAndroidJVMTest() { + @Mock lateinit var activity: BaseActivity + @Mock lateinit var habit: Habit + @Mock lateinit var intent: Intent + @Mock lateinit var rootView: ListHabitsRootView + @Mock lateinit var confirmDeleteDialogFactory: ConfirmDeleteDialogFactory + @Mock lateinit var intentFactory: IntentFactory + @Mock lateinit var colorPickerDialogFactory: ColorPickerDialogFactory + @Mock lateinit var editHabitDialogFactory: EditHabitDialogFactory + @Mock lateinit var themeSwitcher: ThemeSwitcher + @Mock lateinit var prefs: Preferences + @Mock lateinit var menu: ListHabitsMenu + @Mock lateinit var selectionMenu: ListHabitsSelectionMenu + @Mock lateinit var adapter: HabitCardListAdapter + @Mock lateinit var behavior: ListHabitsBehavior + @Mock lateinit var exportDBFactory: ExportDBTaskFactory + @Mock lateinit var importTaskFactory: ImportDataTaskFactory + @Mock lateinit var numberPickerFactory: NumberPickerFactory + + lateinit var screen: ListHabitsScreen + + @Before + override fun setUp() { + super.setUp() + commandRunner = mock(CommandRunner::class.java) + screen = spy(ListHabitsScreen( + activity = activity, + rootView = rootView, + commandRunner = commandRunner, + intentFactory = intentFactory, + themeSwitcher = themeSwitcher, + preferences = prefs, + confirmDeleteDialogFactory = confirmDeleteDialogFactory, + colorPickerFactory = colorPickerDialogFactory, + editHabitDialogFactory = editHabitDialogFactory, + menu = Lazy { menu }, + selectionMenu = Lazy { selectionMenu }, + adapter = adapter, + behavior = Lazy { behavior }, + taskRunner = taskRunner, + exportDBFactory = exportDBFactory, + importTaskFactory = importTaskFactory, + numberPickerFactory = numberPickerFactory)) + + doNothing().`when`(screen).showMessage(anyInt()) + } + + @Test + fun testApplyTheme() { + screen.applyTheme() + verify(activity).restartWithFade(ListHabitsActivity::class.java) + } + + @Test + fun testOnAttached() { + screen.onAttached() + verify(commandRunner).addListener(screen) + } + + @Test + fun testOnCommand() { + val c = mock(DeleteHabitsCommand::class.java) + screen.onCommandExecuted(c, null) + verify(screen).showMessage(R.string.toast_habit_deleted) + } + + @Test + fun testOnDetach() { + screen.onDettached() + verify(commandRunner).removeListener(screen) + } + + @Test + fun testOnResult_bugReport() { + screen.onResult(REQUEST_SETTINGS, RESULT_BUG_REPORT, null) + verify(behavior).onSendBugReport() + } + + @Test + fun testOnResult_exportCSV() { + screen.onResult(REQUEST_SETTINGS, RESULT_EXPORT_CSV, null) + verify(behavior).onExportCSV() + } + + @Test + fun testOnResult_importData() { + screen.onResult(REQUEST_SETTINGS, RESULT_IMPORT_DATA, null) + testShowImportScreen() + } + + @Test + @Throws(Exception::class) + fun testShowAboutScreen() { + `when`(intentFactory.startAboutActivity(activity)).thenReturn(intent) + screen.showAboutScreen() + verify(activity).startActivity(eq(intent)) + } + + @Test + fun testShowColorPicker() { + val picker = mock(ColorPickerDialog::class.java) + `when`(colorPickerDialogFactory.create(999)).thenReturn(picker) + val callback = mock(OnColorPickedCallback::class.java) + + screen.showColorPicker(999, callback) + + verify(activity).showDialog(eq(picker), any()) + verify(picker).setListener(callback) + } + + @Test + fun testShowDeleteConfirmationScreen() { + val callback = mock(OnConfirmedCallback::class.java) + val dialog = mock(ConfirmDeleteDialog::class.java) + `when`(confirmDeleteDialogFactory.create(callback)).thenReturn(dialog) + + screen.showDeleteConfirmationScreen(callback) + + verify(activity).showDialog(dialog) + } + + @Test + fun testShowEditHabitScreen() { + val dialog = mock(EditHabitDialog::class.java) + `when`(editHabitDialogFactory.edit(habit)).thenReturn(dialog) + screen.showEditHabitsScreen(listOf(habit)) + verify(activity).showDialog(eq(dialog), any()) + } + + @Test + fun testShowFAQScreen() { + `when`(intentFactory.viewFAQ(activity)).thenReturn(intent) + screen.showFAQScreen() + verify(activity).startActivity(intent) + } + + @Test + fun testShowHabitScreen() { + `when`(intentFactory.startShowHabitActivity(activity, habit)) + .thenReturn(intent) + screen.showHabitScreen(habit) + verify(activity).startActivity(intent) + } + + @Test + fun testShowImportScreen() { + `when`(intentFactory.openDocument()).thenReturn(intent) + screen.showImportScreen() + verify(activity).startActivityForResult(intent, REQUEST_OPEN_DOCUMENT) + } + + @Test + fun testShowIntroScreen() { + `when`(intentFactory.startIntroActivity(activity)).thenReturn(intent) + screen.showIntroScreen() + verify(activity).startActivity(intent) + } + + @Test + fun testShowSettingsScreen() { + `when`(intentFactory.startSettingsActivity(activity)).thenReturn(intent) + screen.showSettingsScreen() + verify(activity).startActivityForResult(eq(intent), anyInt()) + } +} \ No newline at end of file diff --git a/uhabits-android/src/test/java/org/isoron/uhabits/activities/habits/list/controllers/HabitCardListControllerTest.java b/uhabits-android/src/test/java/org/isoron/uhabits/activities/habits/list/controllers/HabitCardListControllerTest.java deleted file mode 100644 index b56e138c1..000000000 --- a/uhabits-android/src/test/java/org/isoron/uhabits/activities/habits/list/controllers/HabitCardListControllerTest.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (C) 2017 Á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.activities.habits.list.controllers; - -import org.isoron.uhabits.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.activities.habits.list.views.*; -import org.junit.*; - -import java.util.*; - -import static org.mockito.Mockito.*; - -public class HabitCardListControllerTest extends BaseAndroidUnitTest -{ - - private LinkedList habits; - - private HabitCardListView view; - - private HabitCardListAdapter adapter; - - private HabitCardListController controller; - - private HabitCardListController.HabitListener habitListener; - - private HabitCardListController.SelectionListener selectionListener; - - @Override - @Before - public void setUp() - { - super.setUp(); - - this.view = mock(HabitCardListView.class); - this.adapter = mock(HabitCardListAdapter.class); - this.habitListener = mock(HabitCardListController.HabitListener.class); - this.selectionListener = - mock(HabitCardListController.SelectionListener.class); - - habits = new LinkedList<>(); - for (int i = 0; i < 10; i++) - { - Habit mock = mock(Habit.class); - habits.add(mock); - - } - - resetMocks(); - - when(adapter.getObservable()).thenReturn(new ModelObservable()); - this.controller = new HabitCardListController(adapter); - controller.setHabitListener(habitListener); - controller.setSelectionListener(selectionListener); - view.setController(controller); - } - - @Test - public void testClick_withSelection() - { - controller.onItemLongClick(0); - verify(adapter).toggleSelection(0); - verify(selectionListener).onSelectionStart(); - resetMocks(); - - controller.onItemClick(1); - verify(adapter).toggleSelection(1); - verify(selectionListener).onSelectionChange(); - resetMocks(); - - controller.onItemClick(1); - verify(adapter).toggleSelection(1); - verify(selectionListener).onSelectionChange(); - resetMocks(); - - doReturn(true).when(adapter).isSelectionEmpty(); - controller.onItemClick(0); - verify(adapter).toggleSelection(0); - verify(selectionListener).onSelectionFinish(); - } - - @Test - public void testClick_withoutSelection() - { - controller.onItemClick(0); - verify(habitListener).onHabitClick(habits.get(0)); - } - - @Test - public void testDragAndDrop_withSelection() - { - controller.onItemLongClick(0); - verify(adapter).toggleSelection(0); - verify(selectionListener).onSelectionStart(); - resetMocks(); - - controller.startDrag(1); - verify(selectionListener).onSelectionChange(); - verify(adapter).toggleSelection(1); - resetMocks(); - - controller.drop(1, 3); - verify(habitListener).onHabitReorder(habits.get(1), habits.get(3)); - verify(selectionListener).onSelectionFinish(); - verify(adapter).performReorder(1, 3); - resetMocks(); - } - - @Test - public void testDragAndDrop_withoutSelection_distinctPlace() - { - controller.startDrag(0); - verify(selectionListener).onSelectionStart(); - verify(adapter).toggleSelection(0); - resetMocks(); - - controller.drop(0, 3); - verify(habitListener).onHabitReorder(habits.get(0), habits.get(3)); - verify(selectionListener).onSelectionFinish(); - verify(adapter).performReorder(0, 3); - verify(adapter).clearSelection(); - } - - @Test - public void testInvalidToggle() - { - controller.onInvalidToggle(); - verify(habitListener).onInvalidToggle(); - } - - @Test - public void testLongClick_withSelection() - { - controller.onItemLongClick(0); - verify(adapter).toggleSelection(0); - verify(selectionListener).onSelectionStart(); - resetMocks(); - - controller.onItemLongClick(1); - verify(adapter).toggleSelection(1); - verify(selectionListener).onSelectionChange(); - } - - @Test - public void testToggle() - { - controller.onToggle(habits.getFirst(), 0); - verify(habitListener).onToggle(habits.getFirst(), 0); - } - - protected void resetMocks() - { - reset(adapter, habitListener, selectionListener); - for (int i = 0; i < habits.size(); i++) - doReturn(habits.get(i)).when(adapter).getItem(i); - } -} \ No newline at end of file diff --git a/uhabits-android/src/test/java/org/isoron/uhabits/activities/habits/list/controllers/HabitCardListControllerTest.kt b/uhabits-android/src/test/java/org/isoron/uhabits/activities/habits/list/controllers/HabitCardListControllerTest.kt new file mode 100644 index 000000000..4dec267d0 --- /dev/null +++ b/uhabits-android/src/test/java/org/isoron/uhabits/activities/habits/list/controllers/HabitCardListControllerTest.kt @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2017 Á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.activities.habits.list.controllers + +import dagger.* +import org.isoron.uhabits.* +import org.isoron.uhabits.activities.habits.list.* +import org.isoron.uhabits.activities.habits.list.views.* +import org.isoron.uhabits.core.models.* +import org.isoron.uhabits.core.ui.screens.habits.list.* +import org.junit.* +import org.mockito.* +import org.mockito.Mockito.* +import java.util.* + +class HabitCardListControllerTest : BaseAndroidJVMTest() { + private val habits = LinkedList() + private lateinit var controller: HabitCardListController + + @Mock lateinit var adapter: HabitCardListAdapter + @Mock lateinit var behavior: ListHabitsBehavior + @Mock lateinit var selectionMenu: ListHabitsSelectionMenu + + @Before + override fun setUp() { + super.setUp() + `when`(adapter.observable).thenReturn(ModelObservable()) + controller = HabitCardListController(adapter, + behavior, + Lazy { selectionMenu }) + + repeat(10) { habits.add(fixtures.createEmptyHabit()) } + for(i in 0..9) `when`(adapter.getItem(i)).thenReturn(habits[i]) + + } + + @Test + fun testClick_withSelection() { + controller.onItemLongClick(0) + verify(adapter).toggleSelection(0) + verify(selectionMenu).onSelectionStart() + reset(adapter, selectionMenu) + + controller.onItemClick(1) + verify(adapter).toggleSelection(1) + verify(selectionMenu).onSelectionChange() + reset(adapter, selectionMenu) + + controller.onItemClick(1) + verify(adapter).toggleSelection(1) + verify(selectionMenu).onSelectionChange() + reset(adapter, selectionMenu) + + doReturn(true).`when`(adapter).isSelectionEmpty + controller.onItemClick(0) + verify(adapter).toggleSelection(0) + verify(selectionMenu).onSelectionFinish() + } + + @Test + fun testClick_withoutSelection() { + controller.onItemClick(0) + verify(behavior).onClickHabit(habits[0]) + } + + @Test + fun testDragAndDrop_withSelection() { + controller.onItemLongClick(0) + verify(adapter).toggleSelection(0) + verify(selectionMenu).onSelectionStart() + + controller.startDrag(1) + verify(selectionMenu).onSelectionChange() + verify(adapter).toggleSelection(1) + + controller.drop(1, 3) + verify(behavior).onReorderHabit(habits[1], habits[3]) + verify(selectionMenu).onSelectionFinish() + verify(adapter).performReorder(1, 3) + } + + @Test + fun testDragAndDrop_withoutSelection_distinctPlace() { + controller.startDrag(0) + verify(selectionMenu).onSelectionStart() + verify(adapter).toggleSelection(0) + + controller.drop(0, 3) + verify(behavior).onReorderHabit(habits[0], habits[3]) + verify(selectionMenu).onSelectionFinish() + verify(adapter).performReorder(0, 3) + verify(adapter).clearSelection() + } + + @Test + fun testLongClick_withSelection() { + controller.onItemLongClick(0) + verify(adapter).toggleSelection(0) + verify(selectionMenu).onSelectionStart() + + controller.onItemLongClick(1) + verify(adapter).toggleSelection(1) + verify(selectionMenu).onSelectionChange() + } +} \ No newline at end of file diff --git a/uhabits-android/src/test/java/org/isoron/uhabits/activities/habits/list/controllers/package-info.java b/uhabits-android/src/test/java/org/isoron/uhabits/activities/habits/list/controllers/package-info.java deleted file mode 100644 index 90ef83893..000000000 --- a/uhabits-android/src/test/java/org/isoron/uhabits/activities/habits/list/controllers/package-info.java +++ /dev/null @@ -1,23 +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 . - */ - -/** - * Contains views for ListHabitsActivity - */ -package org.isoron.uhabits.activities.habits.list.controllers; \ No newline at end of file diff --git a/uhabits-android/src/test/java/org/isoron/uhabits/receivers/ReminderControllerTest.java b/uhabits-android/src/test/java/org/isoron/uhabits/receivers/ReminderControllerTest.java index cbd161b8e..50b7711d6 100644 --- a/uhabits-android/src/test/java/org/isoron/uhabits/receivers/ReminderControllerTest.java +++ b/uhabits-android/src/test/java/org/isoron/uhabits/receivers/ReminderControllerTest.java @@ -29,7 +29,7 @@ import org.junit.*; import static org.mockito.Mockito.*; -public class ReminderControllerTest extends BaseAndroidUnitTest +public class ReminderControllerTest extends BaseAndroidJVMTest { private ReminderController controller; diff --git a/uhabits-android/src/test/java/org/isoron/uhabits/receivers/WidgetControllerTest.java b/uhabits-android/src/test/java/org/isoron/uhabits/receivers/WidgetControllerTest.java index 8a3966b57..73364c80f 100644 --- a/uhabits-android/src/test/java/org/isoron/uhabits/receivers/WidgetControllerTest.java +++ b/uhabits-android/src/test/java/org/isoron/uhabits/receivers/WidgetControllerTest.java @@ -32,7 +32,7 @@ import static org.hamcrest.core.IsEqual.*; import static org.isoron.uhabits.core.models.Checkmark.*; import static org.mockito.Mockito.*; -public class WidgetControllerTest extends BaseAndroidUnitTest +public class WidgetControllerTest extends BaseAndroidJVMTest { private WidgetBehavior controller; diff --git a/uhabits-core/src/main/java/org/isoron/uhabits/core/models/sqlite/records/package-info.java b/uhabits-core/src/main/java/org/isoron/uhabits/core/models/sqlite/records/package-info.java deleted file mode 100644 index 74a9ac796..000000000 --- a/uhabits-core/src/main/java/org/isoron/uhabits/core/models/sqlite/records/package-info.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2017 Á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 . - * - * - */ - -/** - * Provides classes that represent rows in the SQLite database. - */ -package org.isoron.uhabits.core.models.sqlite.records; \ No newline at end of file diff --git a/uhabits-core/src/main/java/org/isoron/uhabits/core/preferences/Preferences.java b/uhabits-core/src/main/java/org/isoron/uhabits/core/preferences/Preferences.java index cd1d140c4..50985bdd4 100644 --- a/uhabits-core/src/main/java/org/isoron/uhabits/core/preferences/Preferences.java +++ b/uhabits-core/src/main/java/org/isoron/uhabits/core/preferences/Preferences.java @@ -212,6 +212,11 @@ public class Preferences return storage.getBoolean("pref_short_toggle", false); } + public void setShortToggleEnabled(boolean enabled) + { + storage.putBoolean("pref_short_toggle", enabled); + } + public boolean isSyncEnabled() { return storage.getBoolean("pref_feature_sync", false); @@ -243,11 +248,11 @@ public class Preferences for (Listener l : listeners) l.onNotificationsChanged(); } - public void setShouldReverseCheckmarks(boolean reverse) + public void setCheckmarkSequenceReversed(boolean reverse) { shouldReverseCheckmarks = reverse; storage.putBoolean("pref_checkmark_reverse_order", reverse); - for (Listener l : listeners) l.onCheckmarkOrderChanged(); + for (Listener l : listeners) l.onCheckmarkSequenceChanged(); } public void setSyncEnabled(boolean isEnabled) @@ -261,7 +266,7 @@ public class Preferences return storage.getBoolean("pref_sticky_notifications", false); } - public boolean shouldReverseCheckmarks() + public boolean isCheckmarkSequenceReversed() { if (shouldReverseCheckmarks == null) shouldReverseCheckmarks = storage.getBoolean("pref_checkmark_reverse_order", false); @@ -277,7 +282,7 @@ public class Preferences public interface Listener { - default void onCheckmarkOrderChanged() {} + default void onCheckmarkSequenceChanged() {} default void onNotificationsChanged() {} diff --git a/uhabits-core/src/main/java/org/isoron/uhabits/core/tasks/SingleThreadTaskRunner.java b/uhabits-core/src/main/java/org/isoron/uhabits/core/tasks/SingleThreadTaskRunner.java index 748b8521d..f01257839 100644 --- a/uhabits-core/src/main/java/org/isoron/uhabits/core/tasks/SingleThreadTaskRunner.java +++ b/uhabits-core/src/main/java/org/isoron/uhabits/core/tasks/SingleThreadTaskRunner.java @@ -19,21 +19,27 @@ package org.isoron.uhabits.core.tasks; +import java.util.*; + public class SingleThreadTaskRunner implements TaskRunner { + private List listeners = new LinkedList<>(); + @Override public void addListener(Listener listener) { - throw new UnsupportedOperationException(); + listeners.add(listener); } @Override public void execute(Task task) { + for(Listener l : listeners) l.onTaskStarted(task); task.onAttached(this); task.onPreExecute(); task.doInBackground(); task.onPostExecute(); + for(Listener l : listeners) l.onTaskFinished(task); } @Override @@ -51,6 +57,6 @@ public class SingleThreadTaskRunner implements TaskRunner @Override public void removeListener(Listener listener) { - throw new UnsupportedOperationException(); + listeners.remove(listener); } } diff --git a/uhabits-core/src/test/java/org/isoron/uhabits/core/models/sqlite/SQLiteHabitListTest.java b/uhabits-core/src/test/java/org/isoron/uhabits/core/models/sqlite/SQLiteHabitListTest.java index a1d79372d..0c7ba02f2 100644 --- a/uhabits-core/src/test/java/org/isoron/uhabits/core/models/sqlite/SQLiteHabitListTest.java +++ b/uhabits-core/src/test/java/org/isoron/uhabits/core/models/sqlite/SQLiteHabitListTest.java @@ -15,8 +15,6 @@ * * You should have received a copy of the GNU General Public License along * with this program. If not, see . - * - * */ package org.isoron.uhabits.core.models.sqlite;