diff --git a/app/build.gradle b/app/build.gradle index 8164b62ab..08067b99c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,5 +1,7 @@ apply plugin: 'com.android.application' apply plugin: 'com.neenbedankt.android-apt' +apply plugin: 'com.getkeepsafe.dexcount' +apply plugin: 'me.tatarka.retrolambda' android { compileSdkVersion 23 @@ -14,7 +16,6 @@ android { buildConfigField "String", "databaseFilename", "\"uhabits.db\"" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" - //testInstrumentationRunnerArgument "size", "small" } buildTypes { @@ -30,6 +31,10 @@ android { lintOptions { checkReleaseBuilds false } + compileOptions { + targetCompatibility 1.8 + sourceCompatibility 1.8 + } } dependencies { @@ -41,11 +46,21 @@ dependencies { compile 'org.apmem.tools:layouts:1.10@aar' compile 'com.opencsv:opencsv:3.7' compile 'com.michaelpardo:activeandroid:3.1.0-SNAPSHOT' + compile 'com.jakewharton:butterknife:8.0.1' apt 'com.jakewharton:butterknife-compiler:8.0.1' + compile 'com.google.dagger:dagger:2.2' + apt 'com.google.dagger:dagger-compiler:2.2' + testApt 'com.google.dagger:dagger-compiler:2.2' + androidTestApt 'com.google.dagger:dagger-compiler:2.2' + provided 'javax.annotation:jsr250-api:1.0' + compile project(':libs:drag-sort-listview:library') + testCompile 'junit:junit:4.12' + testCompile 'org.mockito:mockito-core:1.10.19' + androidTestCompile 'com.android.support:support-annotations:23.3.0' androidTestCompile 'com.android.support.test:runner:0.5' androidTestCompile 'com.android.support.test:rules:0.5' diff --git a/app/src/androidTest/assets/views/ui/habits/list/CheckmarkButtonView/render_explicit_check.png b/app/src/androidTest/assets/views/ui/habits/list/CheckmarkButtonView/render_explicit_check.png new file mode 100644 index 000000000..1f53b9ae5 Binary files /dev/null and b/app/src/androidTest/assets/views/ui/habits/list/CheckmarkButtonView/render_explicit_check.png differ diff --git a/app/src/androidTest/assets/views/ui/habits/list/CheckmarkButtonView/render_implicit_check.png b/app/src/androidTest/assets/views/ui/habits/list/CheckmarkButtonView/render_implicit_check.png new file mode 100644 index 000000000..2570ca857 Binary files /dev/null and b/app/src/androidTest/assets/views/ui/habits/list/CheckmarkButtonView/render_implicit_check.png differ diff --git a/app/src/androidTest/assets/views/ui/habits/list/CheckmarkButtonView/render_unchecked.png b/app/src/androidTest/assets/views/ui/habits/list/CheckmarkButtonView/render_unchecked.png new file mode 100644 index 000000000..841cc20e0 Binary files /dev/null and b/app/src/androidTest/assets/views/ui/habits/list/CheckmarkButtonView/render_unchecked.png differ diff --git a/app/src/androidTest/assets/views/ui/habits/list/CheckmarkPanelView/render.png b/app/src/androidTest/assets/views/ui/habits/list/CheckmarkPanelView/render.png new file mode 100644 index 000000000..00b87e88e Binary files /dev/null and b/app/src/androidTest/assets/views/ui/habits/list/CheckmarkPanelView/render.png differ diff --git a/app/src/androidTest/java/org/isoron/uhabits/AndroidTestComponent.java b/app/src/androidTest/java/org/isoron/uhabits/AndroidTestComponent.java new file mode 100644 index 000000000..4c73578d6 --- /dev/null +++ b/app/src/androidTest/java/org/isoron/uhabits/AndroidTestComponent.java @@ -0,0 +1,32 @@ +/* + * 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 javax.inject.Singleton; + +import dagger.Component; + +@Singleton +@Component(modules = {AndroidModule.class}) +public interface AndroidTestComponent extends BaseComponent +{ + void inject(BaseAndroidTest baseAndroidTest); +} + diff --git a/app/src/androidTest/java/org/isoron/uhabits/BaseTest.java b/app/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java similarity index 83% rename from app/src/androidTest/java/org/isoron/uhabits/BaseTest.java rename to app/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java index 2cf353d2f..8a5d6255a 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/BaseTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java @@ -27,11 +27,14 @@ import android.support.test.InstrumentationRegistry; import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.tasks.BaseTask; +import org.isoron.uhabits.utils.Preferences; import org.junit.Before; import java.util.concurrent.TimeoutException; -public class BaseTest +import javax.inject.Inject; + +public class BaseAndroidTest { protected Context testContext; protected Context targetContext; @@ -39,8 +42,12 @@ public class BaseTest public static final long FIXED_LOCAL_TIME = 1422172800000L; // 8:00am, January 25th, 2015 (UTC) + @Inject + protected Preferences prefs; + protected AndroidTestComponent androidTestComponent; + @Before - public void setup() + public void setUp() { if(!isLooperPrepared) { @@ -53,6 +60,10 @@ public class BaseTest InterfaceUtils.setFixedTheme(R.style.AppBaseTheme); DateUtils.setFixedLocalTime(FIXED_LOCAL_TIME); + + androidTestComponent = DaggerAndroidTestComponent.builder().build(); + HabitsApplication.setComponent(androidTestComponent); + androidTestComponent.inject(this); } protected void waitForAsyncTasks() throws InterruptedException, TimeoutException diff --git a/app/src/androidTest/java/org/isoron/uhabits/ui/HabitViewActions.java b/app/src/androidTest/java/org/isoron/uhabits/ui/HabitViewActions.java index afa630ea0..cf37b0708 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/ui/HabitViewActions.java +++ b/app/src/androidTest/java/org/isoron/uhabits/ui/HabitViewActions.java @@ -61,7 +61,7 @@ public class HabitViewActions @Override public void perform(UiController uiController, View view) { - if (view.getId() != R.id.llButtons) + if (view.getId() != R.id.checkmarkPanel) throw new InvalidParameterException("View must have id llButtons"); LinearLayout llButtons = (LinearLayout) view; diff --git a/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java b/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java index fe6d71d28..82732735d 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java @@ -191,7 +191,7 @@ public class MainTest String name = addHabit(true); onData(allOf(is(instanceOf(Habit.class)), withName(name))) - .onChildView(withId(R.id.llButtons)) + .onChildView(withId(R.id.checkmarkPanel)) .perform(toggleAllCheckmarks()); Thread.sleep(1200); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/HabitsApplicationTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/HabitsApplicationTest.java index 72e721d37..317b8248c 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/HabitsApplicationTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/HabitsApplicationTest.java @@ -23,8 +23,9 @@ import android.os.Build; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.HabitsApplication; +import org.isoron.uhabits.ui.BaseSystem; import org.junit.Test; import org.junit.runner.RunWith; @@ -35,7 +36,7 @@ import static org.hamcrest.Matchers.containsString; @RunWith(AndroidJUnit4.class) @SmallTest -public class HabitsApplicationTest extends BaseTest +public class HabitsApplicationTest extends BaseAndroidTest { @Test public void test_getLogcat() throws IOException @@ -49,7 +50,8 @@ public class HabitsApplicationTest extends BaseTest HabitsApplication app = HabitsApplication.getInstance(); assert(app != null); - String log = app.getLogcat(); + BaseSystem system = new BaseSystem(targetContext); + String log = system.getLogcat(); assertThat(log, containsString(msg)); } } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ArchiveHabitsCommandTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ArchiveHabitsCommandTest.java index 24be62f7e..d00881506 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ArchiveHabitsCommandTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ArchiveHabitsCommandTest.java @@ -22,7 +22,7 @@ package org.isoron.uhabits.unit.commands; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.commands.ArchiveHabitsCommand; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.unit.HabitFixtures; @@ -37,16 +37,16 @@ import static junit.framework.Assert.assertTrue; @RunWith(AndroidJUnit4.class) @SmallTest -public class ArchiveHabitsCommandTest extends BaseTest +public class ArchiveHabitsCommandTest extends BaseAndroidTest { private ArchiveHabitsCommand command; private Habit habit; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); habit = HabitFixtures.createShortHabit(); command = new ArchiveHabitsCommand(Collections.singletonList(habit)); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ChangeHabitColorCommandTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ChangeHabitColorCommandTest.java index cc6c886d1..dd9f334f4 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ChangeHabitColorCommandTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ChangeHabitColorCommandTest.java @@ -22,7 +22,7 @@ package org.isoron.uhabits.unit.commands; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.commands.ChangeHabitColorCommand; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.unit.HabitFixtures; @@ -37,15 +37,15 @@ import static org.hamcrest.Matchers.equalTo; @RunWith(AndroidJUnit4.class) @SmallTest -public class ChangeHabitColorCommandTest extends BaseTest +public class ChangeHabitColorCommandTest extends BaseAndroidTest { private ChangeHabitColorCommand command; private LinkedList habits; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); habits = new LinkedList<>(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/CreateHabitCommandTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/CreateHabitCommandTest.java index d97fa2e07..60ee83b9a 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/CreateHabitCommandTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/CreateHabitCommandTest.java @@ -22,7 +22,7 @@ package org.isoron.uhabits.unit.commands; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.commands.CreateHabitCommand; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.unit.HabitFixtures; @@ -38,16 +38,16 @@ import static org.hamcrest.Matchers.equalTo; @RunWith(AndroidJUnit4.class) @SmallTest -public class CreateHabitCommandTest extends BaseTest +public class CreateHabitCommandTest extends BaseAndroidTest { private CreateHabitCommand command; private Habit model; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); model = new Habit(); model.name = "New habit"; diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/DeleteHabitsCommandTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/DeleteHabitsCommandTest.java index 36a97198a..045529c04 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/DeleteHabitsCommandTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/DeleteHabitsCommandTest.java @@ -22,7 +22,7 @@ package org.isoron.uhabits.unit.commands; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.commands.DeleteHabitsCommand; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.unit.HabitFixtures; @@ -39,7 +39,7 @@ import static org.hamcrest.Matchers.equalTo; @RunWith(AndroidJUnit4.class) @SmallTest -public class DeleteHabitsCommandTest extends BaseTest +public class DeleteHabitsCommandTest extends BaseAndroidTest { private DeleteHabitsCommand command; private LinkedList habits; @@ -48,9 +48,9 @@ public class DeleteHabitsCommandTest extends BaseTest public ExpectedException thrown = ExpectedException.none(); @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); HabitFixtures.purgeHabits(); habits = new LinkedList<>(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/EditHabitCommandTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/EditHabitCommandTest.java index 94d765870..a3c04fd82 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/EditHabitCommandTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/EditHabitCommandTest.java @@ -22,7 +22,7 @@ package org.isoron.uhabits.unit.commands; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.commands.EditHabitCommand; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.unit.HabitFixtures; @@ -37,7 +37,7 @@ import static org.hamcrest.Matchers.greaterThan; @RunWith(AndroidJUnit4.class) @SmallTest -public class EditHabitCommandTest extends BaseTest +public class EditHabitCommandTest extends BaseAndroidTest { private EditHabitCommand command; @@ -46,9 +46,9 @@ public class EditHabitCommandTest extends BaseTest private Long id; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); habit = HabitFixtures.createShortHabit(); habit.name = "original"; diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ToggleRepetitionCommandTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ToggleRepetitionCommandTest.java index 95993fbaa..fb22aea6b 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ToggleRepetitionCommandTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ToggleRepetitionCommandTest.java @@ -22,7 +22,7 @@ package org.isoron.uhabits.unit.commands; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.commands.ToggleRepetitionCommand; import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.models.Habit; @@ -36,7 +36,7 @@ import static junit.framework.Assert.assertTrue; @RunWith(AndroidJUnit4.class) @SmallTest -public class ToggleRepetitionCommandTest extends BaseTest +public class ToggleRepetitionCommandTest extends BaseAndroidTest { private ToggleRepetitionCommand command; @@ -44,9 +44,9 @@ public class ToggleRepetitionCommandTest extends BaseTest private long today; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); habit = HabitFixtures.createShortHabit(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/UnarchiveHabitsCommandTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/UnarchiveHabitsCommandTest.java index 066ff8bc4..336ee5752 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/UnarchiveHabitsCommandTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/UnarchiveHabitsCommandTest.java @@ -22,7 +22,7 @@ package org.isoron.uhabits.unit.commands; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.commands.UnarchiveHabitsCommand; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.unit.HabitFixtures; @@ -37,16 +37,16 @@ import static junit.framework.Assert.assertTrue; @RunWith(AndroidJUnit4.class) @SmallTest -public class UnarchiveHabitsCommandTest extends BaseTest +public class UnarchiveHabitsCommandTest extends BaseAndroidTest { private UnarchiveHabitsCommand command; private Habit habit; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); habit = HabitFixtures.createShortHabit(); Habit.archive(Collections.singletonList(habit)); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/io/HabitsCSVExporterTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/io/HabitsCSVExporterTest.java index 395952075..b68ec0a71 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/io/HabitsCSVExporterTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/io/HabitsCSVExporterTest.java @@ -24,7 +24,7 @@ import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.utils.FileUtils; import org.isoron.uhabits.io.HabitsCSVExporter; import org.isoron.uhabits.models.Habit; @@ -45,14 +45,14 @@ import static junit.framework.Assert.assertTrue; @RunWith(AndroidJUnit4.class) @SmallTest -public class HabitsCSVExporterTest extends BaseTest +public class HabitsCSVExporterTest extends BaseAndroidTest { private File baseDir; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); HabitFixtures.purgeHabits(); HabitFixtures.createShortHabit(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/io/ImportTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/io/ImportTest.java index 2e1301bd2..6ca6ba60a 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/io/ImportTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/io/ImportTest.java @@ -24,7 +24,7 @@ import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.utils.FileUtils; import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.io.GenericImporter; @@ -49,15 +49,15 @@ import static org.junit.Assert.fail; @RunWith(AndroidJUnit4.class) @SmallTest -public class ImportTest extends BaseTest +public class ImportTest extends BaseAndroidTest { private File baseDir; private Context context; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); DateUtils.setFixedLocalTime(null); HabitFixtures.purgeHabits(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/models/CheckmarkListTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/models/CheckmarkListTest.java index 98b05f0d7..9af522aef 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/models/CheckmarkListTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/models/CheckmarkListTest.java @@ -22,7 +22,7 @@ package org.isoron.uhabits.unit.models; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.unit.HabitFixtures; @@ -42,15 +42,15 @@ import static org.isoron.uhabits.models.Checkmark.UNCHECKED; @RunWith(AndroidJUnit4.class) @SmallTest -public class CheckmarkListTest extends BaseTest +public class CheckmarkListTest extends BaseAndroidTest { Habit nonDailyHabit; private Habit emptyHabit; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); HabitFixtures.purgeHabits(); nonDailyHabit = HabitFixtures.createShortHabit(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/models/HabitTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/models/HabitTest.java index 518b64004..007ccd060 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/models/HabitTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/models/HabitTest.java @@ -23,7 +23,7 @@ import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import org.hamcrest.MatcherAssert; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.unit.HabitFixtures; @@ -45,12 +45,12 @@ import static org.junit.Assert.fail; @RunWith(AndroidJUnit4.class) @SmallTest -public class HabitTest extends BaseTest +public class HabitTest extends BaseAndroidTest { @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); HabitFixtures.purgeHabits(); } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/models/RepetitionListTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/models/RepetitionListTest.java index 8acf02ab7..5cfbf95e5 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/models/RepetitionListTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/models/RepetitionListTest.java @@ -22,7 +22,7 @@ package org.isoron.uhabits.unit.models; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Repetition; @@ -44,15 +44,15 @@ import static org.hamcrest.Matchers.equalTo; @RunWith(AndroidJUnit4.class) @SmallTest -public class RepetitionListTest extends BaseTest +public class RepetitionListTest extends BaseAndroidTest { private Habit habit; private Habit emptyHabit; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); HabitFixtures.purgeHabits(); habit = HabitFixtures.createShortHabit(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreListTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreListTest.java index 017dacaec..7ab145a37 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreListTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreListTest.java @@ -22,7 +22,7 @@ package org.isoron.uhabits.unit.models; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.utils.DatabaseUtils; import org.isoron.uhabits.models.Habit; @@ -41,14 +41,14 @@ import static org.hamcrest.Matchers.equalTo; @RunWith(AndroidJUnit4.class) @SmallTest -public class ScoreListTest extends BaseTest +public class ScoreListTest extends BaseAndroidTest { private Habit habit; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); HabitFixtures.purgeHabits(); habit = HabitFixtures.createEmptyHabit(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreTest.java index e6881b1b4..16f6a5297 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreTest.java @@ -22,7 +22,7 @@ package org.isoron.uhabits.unit.models; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.models.Checkmark; import org.isoron.uhabits.models.Score; import org.junit.Before; @@ -34,12 +34,12 @@ import static org.junit.Assert.assertThat; @RunWith(AndroidJUnit4.class) @SmallTest -public class ScoreTest extends BaseTest +public class ScoreTest extends BaseAndroidTest { @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); } @Test diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportCSVTaskTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportCSVTaskTest.java index 384d94b94..189dfdc96 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportCSVTaskTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportCSVTaskTest.java @@ -22,7 +22,7 @@ package org.isoron.uhabits.unit.tasks; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.tasks.ExportCSVTask; import org.isoron.uhabits.unit.HabitFixtures; @@ -41,12 +41,12 @@ import static org.hamcrest.core.IsNot.not; @RunWith(AndroidJUnit4.class) @SmallTest -public class ExportCSVTaskTest extends BaseTest +public class ExportCSVTaskTest extends BaseAndroidTest { @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); } @Test diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportDBTaskTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportDBTaskTest.java index 5dca0ec33..e811cabbb 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportDBTaskTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportDBTaskTest.java @@ -22,7 +22,7 @@ package org.isoron.uhabits.unit.tasks; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.tasks.ExportDBTask; import org.junit.Before; import org.junit.Test; @@ -38,12 +38,12 @@ import static org.hamcrest.core.IsNot.not; @RunWith(AndroidJUnit4.class) @SmallTest -public class ExportDBTaskTest extends BaseTest +public class ExportDBTaskTest extends BaseAndroidTest { @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); } @Test diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ImportDataTaskTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ImportDataTaskTest.java index 8e67cc883..26c3acd39 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ImportDataTaskTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ImportDataTaskTest.java @@ -23,7 +23,7 @@ import android.support.annotation.NonNull; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.tasks.ImportDataTask; import org.isoron.uhabits.utils.FileUtils; import org.junit.Before; @@ -40,14 +40,14 @@ import static org.junit.Assert.fail; @RunWith(AndroidJUnit4.class) @SmallTest -public class ImportDataTaskTest extends BaseTest +public class ImportDataTaskTest extends BaseAndroidTest { private File baseDir; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); baseDir = FileUtils.getFilesDir("Backups"); if(baseDir == null) fail("baseDir should not be null"); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/ui/habits/list/view/CheckmarkButtonViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/ui/habits/list/view/CheckmarkButtonViewTest.java new file mode 100644 index 000000000..599a8e5f2 --- /dev/null +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/ui/habits/list/view/CheckmarkButtonViewTest.java @@ -0,0 +1,187 @@ +/* + * 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.unit.ui.habits.list.view; + +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.SmallTest; + +import org.isoron.uhabits.models.Checkmark; +import org.isoron.uhabits.ui.habits.list.views.CheckmarkButtonView; +import org.isoron.uhabits.unit.views.ViewTest; +import org.isoron.uhabits.utils.ColorUtils; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.util.concurrent.CountDownLatch; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class CheckmarkButtonViewTest extends ViewTest +{ + public static final String PATH = "ui/habits/list/CheckmarkButtonView/"; + + private CountDownLatch latch; + private CheckmarkButtonView view; + + @Before + public void setUp() + { + super.setUp(); + setSimilarityCutoff(0.03f); + + latch = new CountDownLatch(1); + view = new CheckmarkButtonView(targetContext); + view.setValue(Checkmark.UNCHECKED); + view.setColor(ColorUtils.CSV_PALETTE[7]); + + measureView(dpToPixels(40), dpToPixels(40), view); + } + + protected void assertRendersCheckedExplicitly() throws IOException + { + assertRenders(view, PATH + "render_explicit_check.png"); + } + + protected void assertRendersUnchecked() throws IOException + { + assertRenders(view, PATH + "render_unchecked.png"); + } + + protected void assertRendersCheckedImplicitly() throws IOException + { + assertRenders(view, PATH + "render_implicit_check.png"); + } + + @Test + public void testRender_unchecked() throws Exception + { + view.setValue(Checkmark.UNCHECKED); + assertRendersUnchecked(); + } + + @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 testLongClick() throws Exception +// { +// setOnToggleListener(); +// view.performLongClick(); +// waitForLatch(); +// assertRendersCheckedExplicitly(); +// } +// +// @Test +// public void testClick_withShortToggle_fromUnchecked() throws Exception +// { +// Preferences.getInstance().setShortToggleEnabled(true); +// view.setValue(Checkmark.UNCHECKED); +// setOnToggleListenerAndPerformClick(); +// assertRendersCheckedExplicitly(); +// } +// +// @Test +// public void testClick_withShortToggle_fromChecked() throws Exception +// { +// Preferences.getInstance().setShortToggleEnabled(true); +// view.setValue(Checkmark.CHECKED_EXPLICITLY); +// setOnToggleListenerAndPerformClick(); +// assertRendersUnchecked(); +// } +// +// @Test +// public void testClick_withShortToggle_withoutListener() throws Exception +// { +// Preferences.getInstance().setShortToggleEnabled(true); +// view.setValue(Checkmark.CHECKED_EXPLICITLY); +// view.setController(null); +// view.performClick(); +// assertRendersUnchecked(); +// } +// +// protected void setOnToggleListenerAndPerformClick() throws InterruptedException +// { +// setOnToggleListener(); +// view.performClick(); +// waitForLatch(); +// } +// +// @Test +// public void testClick_withoutShortToggle() throws Exception +// { +// Preferences.getInstance().setShortToggleEnabled(false); +// setOnInvalidToggleListener(); +// view.performClick(); +// waitForLatch(); +// assertRendersUnchecked(); +// } + +// protected void setOnInvalidToggleListener() +// { +// view.setController(new CheckmarkButtonView.Controller() +// { +// @Override +// public void onToggleCheckmark(CheckmarkButtonView view, long timestamp) +// { +// fail(); +// } +// +// @Override +// public void onInvalidToggle(CheckmarkButtonView v) +// { +// assertThat(v, equalTo(view)); +// latch.countDown(); +// } +// }); +// } + +// protected void setOnToggleListener() +// { +// view.setController(new CheckmarkButtonView.Controller() +// { +// @Override +// public void onToggleCheckmark(CheckmarkButtonView v, long t) +// { +// assertThat(v, equalTo(view)); +// assertThat(t, equalTo(DateUtils.getStartOfToday())); +// latch.countDown(); +// } +// +// @Override +// public void onInvalidToggle(CheckmarkButtonView view) +// { +// fail(); +// } +// }); +// } +} \ No newline at end of file diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/ui/habits/list/view/CheckmarkPanelViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/ui/habits/list/view/CheckmarkPanelViewTest.java new file mode 100644 index 000000000..1f6edd1c4 --- /dev/null +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/ui/habits/list/view/CheckmarkPanelViewTest.java @@ -0,0 +1,97 @@ +/* + * 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.unit.ui.habits.list.view; + +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.SmallTest; + +import org.isoron.uhabits.models.Checkmark; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.ui.habits.list.views.CheckmarkPanelView; +import org.isoron.uhabits.unit.views.ViewTest; +import org.isoron.uhabits.utils.ColorUtils; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.CountDownLatch; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class CheckmarkPanelViewTest extends ViewTest +{ + public static final String PATH = "ui/habits/list/CheckmarkPanelView/"; + + private CountDownLatch latch; + private CheckmarkPanelView view; + private int checkmarks[]; + + @Override + @Before + public void setUp() + { + super.setUp(); + setSimilarityCutoff(0.03f); + prefs.setShouldReverseCheckmarks(false); + + Habit habit = new Habit(); + + latch = new CountDownLatch(1); + checkmarks = new int[]{Checkmark.CHECKED_EXPLICITLY, Checkmark.UNCHECKED, + Checkmark.CHECKED_IMPLICITLY, Checkmark.CHECKED_EXPLICITLY}; + + view = new CheckmarkPanelView(targetContext); + view.setHabit(habit); + view.setCheckmarkValues(checkmarks); + view.setColor(ColorUtils.CSV_PALETTE[7]); + + measureView(dpToPixels(200), dpToPixels(200), view); + } + +// 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/app/src/androidTest/java/org/isoron/uhabits/unit/views/CheckmarkWidgetViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/CheckmarkWidgetViewTest.java index 45c91242a..5cde3d739 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/CheckmarkWidgetViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/CheckmarkWidgetViewTest.java @@ -42,9 +42,9 @@ public class CheckmarkWidgetViewTest extends ViewTest private Habit habit; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); InterfaceUtils.setFixedTheme(R.style.TransparentWidgetTheme); habit = HabitFixtures.createShortHabit(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitFrequencyViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitFrequencyViewTest.java index 590e2c9d2..b99cb4c89 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitFrequencyViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitFrequencyViewTest.java @@ -36,9 +36,9 @@ public class HabitFrequencyViewTest extends ViewTest private HabitFrequencyView view; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); HabitFixtures.purgeHabits(); Habit habit = HabitFixtures.createLongHabit(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java index 9982fd18a..7bf667491 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java @@ -43,9 +43,9 @@ public class HabitHistoryViewTest extends ViewTest private HabitHistoryView view; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); HabitFixtures.purgeHabits(); habit = HabitFixtures.createLongHabit(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitScoreViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitScoreViewTest.java index a7f1228b9..1f5b13a2f 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitScoreViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitScoreViewTest.java @@ -38,9 +38,9 @@ public class HabitScoreViewTest extends ViewTest private HabitScoreView view; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); HabitFixtures.purgeHabits(); habit = HabitFixtures.createLongHabit(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitStreakViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitStreakViewTest.java index ececee945..3b65c5a5d 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitStreakViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitStreakViewTest.java @@ -36,9 +36,9 @@ public class HabitStreakViewTest extends ViewTest private HabitStreakView view; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); HabitFixtures.purgeHabits(); Habit habit = HabitFixtures.createLongHabit(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/NumberViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/NumberViewTest.java index b4cdb0862..ca61aac04 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/NumberViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/NumberViewTest.java @@ -38,9 +38,9 @@ public class NumberViewTest extends ViewTest private NumberView view; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); view = new NumberView(targetContext); view.setLabel("Hello world"); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/RingViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/RingViewTest.java index d59ee499e..972fae3c8 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/RingViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/RingViewTest.java @@ -38,9 +38,9 @@ public class RingViewTest extends ViewTest private RingView view; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); view = new RingView(targetContext); view.setPercentage(0.6f); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java index 8e9f527b3..691e9d50e 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java @@ -26,7 +26,7 @@ import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.utils.FileUtils; import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.tasks.BaseTask; @@ -39,10 +39,23 @@ import java.io.InputStream; import static junit.framework.Assert.fail; -public class ViewTest extends BaseTest +public class ViewTest extends BaseAndroidTest { - protected static final double SIMILARITY_CUTOFF = 0.09; + protected static final double DEFAULT_SIMILARITY_CUTOFF = 0.09; public static final int HISTOGRAM_BIN_SIZE = 8; + private double similarityCutoff; + + @Override + public void setUp() + { + super.setUp(); + similarityCutoff = DEFAULT_SIMILARITY_CUTOFF; + } + + protected void setSimilarityCutoff(double similarityCutoff) + { + this.similarityCutoff = similarityCutoff; + } protected void measureView(int width, int height, View view) { @@ -70,7 +83,8 @@ public class ViewTest extends BaseTest double distance; boolean similarEnough = true; - if ((distance = compareHistograms(getHistogram(actual), getHistogram(scaledExpected))) > SIMILARITY_CUTOFF) + if ((distance = compareHistograms(getHistogram(actual), getHistogram(scaledExpected))) > + similarityCutoff) { similarEnough = false; errorMessage.append(String.format( diff --git a/app/src/main/java/org/isoron/uhabits/AndroidComponent.java b/app/src/main/java/org/isoron/uhabits/AndroidComponent.java new file mode 100644 index 000000000..45eb998d9 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/AndroidComponent.java @@ -0,0 +1,31 @@ +/* + * 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 javax.inject.Singleton; + +import dagger.Component; + +@Singleton +@Component(modules = {AndroidModule.class}) +public interface AndroidComponent extends BaseComponent +{ +} + diff --git a/app/src/main/java/org/isoron/uhabits/AndroidModule.java b/app/src/main/java/org/isoron/uhabits/AndroidModule.java new file mode 100644 index 000000000..3bbc1d4d1 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/AndroidModule.java @@ -0,0 +1,54 @@ +/* + * 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 org.isoron.uhabits.commands.CommandRunner; +import org.isoron.uhabits.ui.habits.list.model.HabitCardListCache; +import org.isoron.uhabits.utils.Preferences; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +@Module +public class AndroidModule +{ + @Provides + @Singleton + Preferences providePreferences() + { + return new Preferences(); + } + + @Provides + @Singleton + CommandRunner provideCommandRunner() + { + return new CommandRunner(); + } + + @Provides + @Singleton + HabitCardListCache provideHabitCardListCache() + { + return new HabitCardListCache(); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/BaseComponent.java b/app/src/main/java/org/isoron/uhabits/BaseComponent.java new file mode 100644 index 000000000..ea1b6cecb --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/BaseComponent.java @@ -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 org.isoron.uhabits.tasks.ToggleRepetitionTask; +import org.isoron.uhabits.ui.habits.edit.BaseDialogFragment; +import org.isoron.uhabits.ui.habits.list.ListHabitsSelectionMenu; +import org.isoron.uhabits.ui.habits.list.model.HabitCardListAdapter; +import org.isoron.uhabits.ui.habits.list.model.HabitCardListCache; +import org.isoron.uhabits.ui.habits.list.ListHabitsController; +import org.isoron.uhabits.ui.habits.list.controllers.CheckmarkButtonController; +import org.isoron.uhabits.ui.habits.list.model.HintList; +import org.isoron.uhabits.ui.habits.list.views.CheckmarkPanelView; + +public interface BaseComponent +{ + void inject(CheckmarkButtonController checkmarkButtonController); + + void inject(ListHabitsController listHabitsController); + + void inject(CheckmarkPanelView checkmarkPanelView); + + void inject(ToggleRepetitionTask toggleRepetitionTask); + + void inject(BaseDialogFragment baseDialogFragment); + + void inject(HabitCardListCache habitCardListCache); + + void inject(HabitBroadcastReceiver habitBroadcastReceiver); + + void inject(ListHabitsSelectionMenu listHabitsSelectionMenu); + + void inject(HintList hintList); + + void inject(HabitCardListAdapter habitCardListAdapter); +} diff --git a/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java b/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java index c923b4e34..d992bda62 100644 --- a/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java +++ b/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java @@ -38,16 +38,18 @@ import android.support.v4.content.LocalBroadcastManager; import org.isoron.uhabits.commands.CommandRunner; import org.isoron.uhabits.commands.ToggleRepetitionCommand; -import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.utils.ReminderUtils; import org.isoron.uhabits.models.Checkmark; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.tasks.BaseTask; import org.isoron.uhabits.ui.habits.show.ShowHabitActivity; +import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.utils.ReminderUtils; import org.isoron.uhabits.widgets.WidgetManager; import java.util.Date; +import javax.inject.Inject; + public class HabitBroadcastReceiver extends BroadcastReceiver { public static final String ACTION_CHECK = "org.isoron.uhabits.ACTION_CHECK"; @@ -55,6 +57,15 @@ public class HabitBroadcastReceiver extends BroadcastReceiver public static final String ACTION_SHOW_REMINDER = "org.isoron.uhabits.ACTION_SHOW_REMINDER"; public static final String ACTION_SNOOZE = "org.isoron.uhabits.ACTION_SNOOZE"; + @Inject + CommandRunner commandRunner; + + public HabitBroadcastReceiver() + { + super(); + HabitsApplication.getComponent().inject(this); + } + @Override public void onReceive(final Context context, Intent intent) { @@ -85,14 +96,7 @@ public class HabitBroadcastReceiver extends BroadcastReceiver private void createReminderAlarmsDelayed(final Context context) { - new Handler().postDelayed(new Runnable() - { - @Override - public void run() - { - ReminderUtils.createReminderAlarms(context); - } - }, 5000); + new Handler().postDelayed(() -> ReminderUtils.createReminderAlarms(context), 5000); } private void snoozeHabit(Context context, Intent intent) @@ -119,7 +123,7 @@ public class HabitBroadcastReceiver extends BroadcastReceiver if(habit != null) { ToggleRepetitionCommand command = new ToggleRepetitionCommand(habit, timestamp); - CommandRunner.getInstance().execute(command, habitId); + commandRunner.execute(command, habitId); } dismissNotification(context, habitId); diff --git a/app/src/main/java/org/isoron/uhabits/HabitsApplication.java b/app/src/main/java/org/isoron/uhabits/HabitsApplication.java index 9186d5e25..19706124c 100644 --- a/app/src/main/java/org/isoron/uhabits/HabitsApplication.java +++ b/app/src/main/java/org/isoron/uhabits/HabitsApplication.java @@ -21,28 +21,15 @@ package org.isoron.uhabits; import android.app.Application; import android.content.Context; -import android.os.Environment; -import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.view.WindowManager; import com.activeandroid.ActiveAndroid; -import org.isoron.uhabits.tasks.BaseTask; import org.isoron.uhabits.utils.DatabaseUtils; -import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.utils.FileUtils; -import org.isoron.uhabits.utils.ReminderUtils; -import org.isoron.uhabits.widgets.WidgetManager; -import java.io.BufferedReader; import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.LinkedList; -public class HabitsApplication extends Application implements MainController.System +public class HabitsApplication extends Application { public static final String ACTION_REFRESH = "org.isoron.uhabits.ACTION_REFRESH"; public static final int RESULT_IMPORT_DATA = 1; @@ -55,6 +42,7 @@ public class HabitsApplication extends Application implements MainController.Sys @Nullable private static Context context; + private static BaseComponent component; public static boolean isTestMode() { @@ -82,12 +70,23 @@ public class HabitsApplication extends Application implements MainController.Sys return application; } + public static BaseComponent getComponent() + { + return component; + } + + public static void setComponent(BaseComponent component) + { + HabitsApplication.component = component; + } + @Override public void onCreate() { super.onCreate(); HabitsApplication.context = this; HabitsApplication.application = this; + component = DaggerAndroidComponent.builder().build(); if (isTestMode()) { @@ -105,108 +104,4 @@ public class HabitsApplication extends Application implements MainController.Sys ActiveAndroid.dispose(); super.onTerminate(); } - - public String getLogcat() throws IOException - { - int maxNLines = 250; - StringBuilder builder = new StringBuilder(); - - String[] command = new String[] { "logcat", "-d"}; - Process process = Runtime.getRuntime().exec(command); - - InputStreamReader in = new InputStreamReader(process.getInputStream()); - BufferedReader bufferedReader = new BufferedReader(in); - - LinkedList log = new LinkedList<>(); - - String line; - while ((line = bufferedReader.readLine()) != null) - { - log.addLast(line); - if(log.size() > maxNLines) log.removeFirst(); - } - - for(String l : log) - { - builder.append(l); - builder.append('\n'); - } - - return builder.toString(); - } - - public String getDeviceInfo() - { - if(context == null) return ""; - - StringBuilder b = new StringBuilder(); - WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - - b.append(String.format("App Version Name: %s\n", BuildConfig.VERSION_NAME)); - b.append(String.format("App Version Code: %s\n", BuildConfig.VERSION_CODE)); - b.append(String.format("OS Version: %s (%s)\n", java.lang.System.getProperty("os.version"), - android.os.Build.VERSION.INCREMENTAL)); - b.append(String.format("OS API Level: %s\n", android.os.Build.VERSION.SDK)); - b.append(String.format("Device: %s\n", android.os.Build.DEVICE)); - b.append(String.format("Model (Product): %s (%s)\n", android.os.Build.MODEL, - android.os.Build.PRODUCT)); - b.append(String.format("Manufacturer: %s\n", android.os.Build.MANUFACTURER)); - b.append(String.format("Other tags: %s\n", android.os.Build.TAGS)); - b.append(String.format("Screen Width: %s\n", wm.getDefaultDisplay().getWidth())); - b.append(String.format("Screen Height: %s\n", wm.getDefaultDisplay().getHeight())); - b.append(String.format("SD Card state: %s\n\n", Environment.getExternalStorageState())); - - return b.toString(); - } - - @NonNull - public File dumpBugReportToFile() throws IOException - { - String date = DateUtils.getBackupDateFormat().format(DateUtils.getLocalTime()); - - if(context == null) throw new RuntimeException("application context should not be null"); - File dir = FileUtils.getFilesDir("Logs"); - if (dir == null) throw new IOException("log dir should not be null"); - - File logFile = new File(String.format("%s/Log %s.txt", dir.getPath(), date)); - FileWriter output = new FileWriter(logFile); - output.write(getBugReport()); - output.close(); - - return logFile; - } - - @NonNull - public String getBugReport() throws IOException - { - String logcat = getLogcat(); - String deviceInfo = getDeviceInfo(); - return deviceInfo + "\n" + logcat; - } - - public void scheduleReminders() - { - new BaseTask() - { - - @Override - protected void doInBackground() - { - ReminderUtils.createReminderAlarms(getContext()); - } - }.execute(); - } - - public void updateWidgets() - { - new BaseTask() - { - - @Override - protected void doInBackground() - { - WidgetManager.updateWidgets(getContext()); - } - }.execute(); - } } diff --git a/app/src/main/java/org/isoron/uhabits/HabitsBackupAgent.java b/app/src/main/java/org/isoron/uhabits/HabitsBackupAgent.java index 6baa562f2..6e619ef7a 100644 --- a/app/src/main/java/org/isoron/uhabits/HabitsBackupAgent.java +++ b/app/src/main/java/org/isoron/uhabits/HabitsBackupAgent.java @@ -28,7 +28,9 @@ public class HabitsBackupAgent extends BackupAgentHelper @Override public void onCreate() { - addHelper("preferences", new SharedPreferencesBackupHelper(this, "preferences")); - addHelper("database", new FileBackupHelper(this, "../databases/uhabits.db")); + addHelper("preferences", + new SharedPreferencesBackupHelper(this, "preferences")); + addHelper("database", + new FileBackupHelper(this, "../databases/uhabits.db")); } } diff --git a/app/src/main/java/org/isoron/uhabits/MainActivity.java b/app/src/main/java/org/isoron/uhabits/MainActivity.java index 1655c4de2..0674a8035 100644 --- a/app/src/main/java/org/isoron/uhabits/MainActivity.java +++ b/app/src/main/java/org/isoron/uhabits/MainActivity.java @@ -19,222 +19,13 @@ package org.isoron.uhabits; -import android.content.Intent; -import android.graphics.drawable.ColorDrawable; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.support.v4.app.FragmentManager; -import android.support.v7.app.ActionBar; -import android.view.Menu; -import android.view.MenuItem; +import org.isoron.uhabits.ui.habits.list.ListHabitsActivity; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.tasks.ProgressBar; -import org.isoron.uhabits.ui.AndroidProgressBar; -import org.isoron.uhabits.ui.BaseActivity; -import org.isoron.uhabits.ui.about.AboutActivity; -import org.isoron.uhabits.ui.habits.list.ListHabitsFragment; -import org.isoron.uhabits.ui.habits.show.ShowHabitActivity; -import org.isoron.uhabits.ui.intro.IntroActivity; -import org.isoron.uhabits.ui.settings.FilePickerDialog; -import org.isoron.uhabits.ui.settings.SettingsActivity; -import org.isoron.uhabits.utils.FileUtils; -import org.isoron.uhabits.utils.InterfaceUtils; - -import java.io.File; - -public class MainActivity extends BaseActivity - implements ListHabitsFragment.Listener, MainController.Screen +public class MainActivity extends ListHabitsActivity { - private MainController controller; - private ListHabitsFragment listHabitsFragment; - - @Override - protected void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - setContentView(R.layout.list_habits_activity); - setupSupportActionBar(false); - - FragmentManager fragmentManager = getSupportFragmentManager(); - listHabitsFragment = (ListHabitsFragment) fragmentManager.findFragmentById(R.id.fragment1); - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) - onPreLollipopCreate(); - - controller = new MainController(); - controller.setScreen(this); - controller.setSystem((HabitsApplication) getApplication()); - controller.onStartup(); - } - - private void onPreLollipopCreate() - { - ActionBar actionBar = getSupportActionBar(); - if(actionBar == null) return; - if(InterfaceUtils.isNightMode()) return; - - int color = getResources().getColor(R.color.grey_900); - actionBar.setBackgroundDrawable(new ColorDrawable(color)); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) - { - menu.clear(); - getMenuInflater().inflate(R.menu.main_activity, menu); - MenuItem nightModeItem = menu.findItem(R.id.action_night_mode); - nightModeItem.setChecked(InterfaceUtils.isNightMode()); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) - { - switch (item.getItemId()) - { - case R.id.action_night_mode: - toggleNightMode(); - return true; - - case R.id.action_settings: - showSettingsScreen(); - return true; - - case R.id.action_about: - showAboutScreen(); - return true; - - case R.id.action_faq: - showFAQScreen(); - return true; - - default: - return super.onOptionsItemSelected(item); - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) - { - switch (resultCode) - { - case HabitsApplication.RESULT_IMPORT_DATA: - showImportScreen(); - break; - - case HabitsApplication.RESULT_EXPORT_CSV: - controller.exportCSV(); - break; - - case HabitsApplication.RESULT_EXPORT_DB: - controller.exportDB(); - break; - - case HabitsApplication.RESULT_BUG_REPORT: - controller.sendBugReport(); - break; - } - } - - private void showFAQScreen() - { - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_VIEW); - intent.setData(Uri.parse(getString(R.string.helpURL))); - startActivity(intent); - } - - private void showAboutScreen() - { - Intent intent = new Intent(this, AboutActivity.class); - startActivity(intent); - } - - private void showSettingsScreen() - { - Intent intent = new Intent(this, SettingsActivity.class); - startActivityForResult(intent, 0); - } - - private void toggleNightMode() - { - if(InterfaceUtils.isNightMode()) - InterfaceUtils.setCurrentTheme(InterfaceUtils.THEME_LIGHT); - else - InterfaceUtils.setCurrentTheme(InterfaceUtils.THEME_DARK); - - refreshTheme(); - } - - private void refreshTheme() - { - new Handler().postDelayed(new Runnable() - { - @Override - public void run() - { - Intent intent = new Intent(MainActivity.this, MainActivity.class); - - MainActivity.this.finish(); - overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); - startActivity(intent); - - } - }, 500); // Let the menu disappear first - } - - @Override - public void onHabitClick(Habit habit) - { - showHabitScreen(habit); - } - - private void showHabitScreen(Habit habit) - { - Intent intent = new Intent(this, ShowHabitActivity.class); - intent.setData(Uri.parse("content://org.isoron.uhabits/habit/" + habit.getId())); - startActivity(intent); - } - - public void showIntroScreen() - { - Intent intent = new Intent(this, IntroActivity.class); - this.startActivity(intent); - } - - public void showImportScreen() - { - File dir = FileUtils.getFilesDir(null); - if(dir == null) - { - showMessage(R.string.could_not_import); - return; - } - - FilePickerDialog picker = new FilePickerDialog(this, dir); - picker.setListener(new FilePickerDialog.OnFileSelectedListener() - { - @Override - public void onFileSelected(File file) - { - controller.importData(file); - } - }); - picker.show(); - } - - @Override - public void refresh(Long refreshKey) - { - listHabitsFragment.refresh(refreshKey); - } - - @Override - public ProgressBar getProgressBar() - { - return new AndroidProgressBar(listHabitsFragment.getProgressBar()); - } + /* + * Since changing the main activity on the manifest file causes things to + * break we always point the launcher icon to this activity instead, and + * redirect to the desired one using inheritance. + */ } diff --git a/app/src/main/java/org/isoron/uhabits/MainController.java b/app/src/main/java/org/isoron/uhabits/MainController.java deleted file mode 100644 index 56d8cfad0..000000000 --- a/app/src/main/java/org/isoron/uhabits/MainController.java +++ /dev/null @@ -1,180 +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; - -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.tasks.ExportCSVTask; -import org.isoron.uhabits.tasks.ExportDBTask; -import org.isoron.uhabits.tasks.ImportDataTask; -import org.isoron.uhabits.tasks.ProgressBar; -import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.utils.Preferences; - -import java.io.File; -import java.io.IOException; - -public class MainController implements ImportDataTask.Listener, ExportCSVTask.Listener, - ExportDBTask.Listener -{ - public interface Screen - { - void showIntroScreen(); - - void showMessage(Integer stringId); - - void refresh(Long refreshKey); - - void sendFile(String filename); - - void sendEmail(String to, String subject, String content); - - ProgressBar getProgressBar(); - } - - public interface System - { - void scheduleReminders(); - - void updateWidgets(); - - File dumpBugReportToFile() throws IOException; - - String getBugReport() throws IOException; - } - - System sys; - Screen screen; - Preferences prefs; - - public MainController() - { - prefs = Preferences.getInstance(); - } - - public void setScreen(Screen screen) - { - this.screen = screen; - } - - public void setSystem(System sys) - { - this.sys = sys; - } - - public void onStartup() - { - prefs.initialize(); - prefs.incrementLaunchCount(); - prefs.updateLastAppVersion(); - if(prefs.isFirstRun()) onFirstRun(); - - sys.updateWidgets(); - sys.scheduleReminders(); - } - - private void onFirstRun() - { - prefs.setFirstRun(false); - prefs.setLastHintTimestamp(DateUtils.getStartOfToday()); - screen.showIntroScreen(); - } - - public void importData(File file) - { - ImportDataTask task = new ImportDataTask(file, screen.getProgressBar()); - task.setListener(this); - task.execute(); - } - - @Override - public void onImportDataFinished(int result) - { - switch (result) - { - case ImportDataTask.SUCCESS: - screen.refresh(null); - 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; - } - } - - public void exportCSV() - { - ExportCSVTask task = new ExportCSVTask(Habit.getAll(true), screen.getProgressBar()); - task.setListener(this); - task.execute(); - } - - @Override - public void onExportCSVFinished(String filename) - { - if(filename != null) screen.sendFile(filename); - else screen.showMessage(R.string.could_not_export); - } - - public void exportDB() - { - ExportDBTask task = new ExportDBTask(screen.getProgressBar()); - task.setListener(this); - task.execute(); - } - - @Override - public void onExportDBFinished(String filename) - { - if(filename != null) screen.sendFile(filename); - else screen.showMessage(R.string.could_not_export); - } - - public void sendBugReport() - { - try - { - sys.dumpBugReportToFile(); - } - catch (IOException e) - { - // ignored - } - - try - { - String log = "---------- BUG REPORT BEGINS ----------\n"; - log += sys.getBugReport(); - log += "---------- BUG REPORT ENDS ------------\n"; - String to = "dev@loophabits.org"; - String subject = "Bug Report - Loop Habit Tracker"; - screen.sendEmail(log, to, subject); - } - catch (IOException e) - { - e.printStackTrace(); - screen.showMessage(R.string.bug_report_failed); - } - } -} diff --git a/app/src/main/java/org/isoron/uhabits/commands/CommandRunner.java b/app/src/main/java/org/isoron/uhabits/commands/CommandRunner.java index ab8a55181..035e55087 100644 --- a/app/src/main/java/org/isoron/uhabits/commands/CommandRunner.java +++ b/app/src/main/java/org/isoron/uhabits/commands/CommandRunner.java @@ -19,29 +19,30 @@ package org.isoron.uhabits.commands; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + import org.isoron.uhabits.tasks.BaseTask; import java.util.LinkedList; public class CommandRunner { - public interface Listener - { - void onCommandExecuted(Command command, Long refreshKey); - } - - private static CommandRunner singleton; private LinkedList listeners; - private CommandRunner() + public CommandRunner() { listeners = new LinkedList<>(); } - public static CommandRunner getInstance() + private static CommandRunner getInstance() { - if(singleton == null) singleton = new CommandRunner(); - return singleton; + return null; + } + + public void addListener(Listener l) + { + listeners.add(l); } public void execute(final Command command, final Long refreshKey) @@ -57,7 +58,7 @@ public class CommandRunner @Override protected void onPostExecute(Void aVoid) { - for(Listener l : listeners) + for (Listener l : listeners) l.onCommandExecuted(command, refreshKey); super.onPostExecute(null); @@ -65,13 +66,14 @@ public class CommandRunner }.execute(); } - public void addListener(Listener l) + public void removeListener(Listener l) { - listeners.add(l); + listeners.remove(l); } - public void removeListener(Listener l) + public interface Listener { - listeners.remove(l); + void onCommandExecuted(@NonNull Command command, + @Nullable Long refreshKey); } } diff --git a/app/src/main/java/org/isoron/uhabits/models/ModelObservable.java b/app/src/main/java/org/isoron/uhabits/models/ModelObservable.java index a8f12cab7..2ff7650f3 100644 --- a/app/src/main/java/org/isoron/uhabits/models/ModelObservable.java +++ b/app/src/main/java/org/isoron/uhabits/models/ModelObservable.java @@ -47,7 +47,7 @@ public class ModelObservable listeners.remove(l); } - void notifyListeners() + public void notifyListeners() { for(Listener l : listeners) l.onModelChange(); } diff --git a/app/src/main/java/org/isoron/uhabits/tasks/ToggleRepetitionTask.java b/app/src/main/java/org/isoron/uhabits/tasks/ToggleRepetitionTask.java index 828656eb3..56c6e170b 100644 --- a/app/src/main/java/org/isoron/uhabits/tasks/ToggleRepetitionTask.java +++ b/app/src/main/java/org/isoron/uhabits/tasks/ToggleRepetitionTask.java @@ -19,12 +19,18 @@ package org.isoron.uhabits.tasks; +import org.isoron.uhabits.HabitsApplication; import org.isoron.uhabits.commands.CommandRunner; import org.isoron.uhabits.commands.ToggleRepetitionCommand; import org.isoron.uhabits.models.Habit; +import javax.inject.Inject; + public class ToggleRepetitionTask extends BaseTask -{; +{ + @Inject + CommandRunner commandRunner; + public interface Listener { void onToggleRepetitionFinished(); } @@ -37,13 +43,14 @@ public class ToggleRepetitionTask extends BaseTask { this.timestamp = timestamp; this.habit = habit; + HabitsApplication.getComponent().inject(this); } @Override protected void doInBackground() { ToggleRepetitionCommand command = new ToggleRepetitionCommand(habit, timestamp); - CommandRunner.getInstance().execute(command, habit.getId()); + commandRunner.execute(command, habit.getId()); } @Override diff --git a/app/src/main/java/org/isoron/uhabits/ui/BaseActivity.java b/app/src/main/java/org/isoron/uhabits/ui/BaseActivity.java index 634c84183..bdc066fc9 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/BaseActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ui/BaseActivity.java @@ -19,188 +19,114 @@ package org.isoron.uhabits.ui; -import android.app.backup.BackupManager; -import android.content.Context; import android.content.Intent; -import android.graphics.Color; -import android.graphics.drawable.ColorDrawable; -import android.net.Uri; -import android.os.Build; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v7.app.ActionBar; +import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; -import android.view.View; -import android.widget.Toast; - -import org.isoron.uhabits.HabitBroadcastReceiver; -import org.isoron.uhabits.HabitsApplication; -import org.isoron.uhabits.R; -import org.isoron.uhabits.commands.Command; -import org.isoron.uhabits.commands.CommandRunner; -import org.isoron.uhabits.models.Checkmark; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.tasks.BaseTask; -import org.isoron.uhabits.utils.ColorUtils; -import org.isoron.uhabits.utils.InterfaceUtils; -import org.isoron.uhabits.widgets.WidgetManager; +import android.view.Menu; +import android.view.MenuItem; -import java.io.File; +import org.isoron.uhabits.utils.InterfaceUtils; -abstract public class BaseActivity extends AppCompatActivity implements Thread.UncaughtExceptionHandler, - CommandRunner.Listener +/** + * Base class for all activities in the application. + */ +abstract public class BaseActivity extends AppCompatActivity + implements Thread.UncaughtExceptionHandler { - private Toast toast; - - Thread.UncaughtExceptionHandler androidExceptionHandler; - - @Override - protected void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); + @Nullable + private BaseMenu baseMenu; - InterfaceUtils.applyCurrentTheme(this); + @Nullable + private Thread.UncaughtExceptionHandler androidExceptionHandler; - androidExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); - Thread.setDefaultUncaughtExceptionHandler(this); - } + @Nullable + private BaseScreen screen; @Override - protected void onResume() + public boolean onCreateOptionsMenu(@Nullable Menu menu) { - super.onResume(); - CommandRunner.getInstance().addListener(this); + if (menu == null) return false; + if (baseMenu == null) return false; + return baseMenu.onCreate(getMenuInflater(), menu); } @Override - protected void onPause() + public boolean onOptionsItemSelected(@Nullable MenuItem item) { - CommandRunner.getInstance().removeListener(this); - super.onPause(); + if (item == null) return false; + if (baseMenu == null) return false; + return baseMenu.onItemSelected(item); } - public void showMessage(Integer stringId) + public void setBaseMenu(@Nullable BaseMenu baseMenu) { - if (stringId == null) return; - if (toast == null) toast = Toast.makeText(this, stringId, Toast.LENGTH_SHORT); - else toast.setText(stringId); - toast.show(); + this.baseMenu = baseMenu; } - protected void setupSupportActionBar(boolean homeButtonEnabled) + public void setScreen(@Nullable BaseScreen screen) { - Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); - if(toolbar == null) return; - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) - toolbar.setElevation(InterfaceUtils.dpToPixels(this, 2)); - - setSupportActionBar(toolbar); - - ActionBar actionBar = getSupportActionBar(); - if(actionBar == null) return; - - if(homeButtonEnabled) - actionBar.setDisplayHomeAsUpEnabled(true); + this.screen = screen; } @Override - public void uncaughtException(Thread thread, Throwable ex) + public void uncaughtException(@Nullable Thread thread, + @Nullable Throwable ex) { + if (ex == null) return; + try { ex.printStackTrace(); - ((HabitsApplication) getApplication()).dumpBugReportToFile(); - } - catch(Exception e) + new BaseSystem(this).dumpBugReportToFile(); + } catch (Exception e) { // ignored } - if(androidExceptionHandler != null) + if (androidExceptionHandler != null) androidExceptionHandler.uncaughtException(thread, ex); - else - System.exit(1); - } - - protected void setupActionBarColor(int color) - { - ActionBar actionBar = getSupportActionBar(); - if(actionBar == null) return; - - if (!InterfaceUtils.getStyledBoolean(this, R.attr.useHabitColorAsPrimary)) return; - - ColorDrawable drawable = new ColorDrawable(color); - actionBar.setBackgroundDrawable(drawable); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) - { - int darkerColor = ColorUtils.mixColors(color, Color.BLACK, 0.75f); - getWindow().setStatusBarColor(darkerColor); - } + else System.exit(1); } @Override - protected void onPostResume() - { - super.onPostResume(); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) - hideFakeToolbarShadow(); - } - - protected void hideFakeToolbarShadow() + protected void onActivityResult(int request, int result, Intent data) { - View view = findViewById(R.id.toolbarShadow); - if(view != null) view.setVisibility(View.GONE); - - view = findViewById(R.id.headerShadow); - if(view != null) view.setVisibility(View.GONE); + if (screen == null) return; + screen.onResult(request, result, data); } - private void dismissNotifications(Context context) - { - for(Habit h : Habit.getHabitsWithReminder()) - { - if(h.checkmarks.getTodayValue() != Checkmark.UNCHECKED) - HabitBroadcastReceiver.dismissNotification(context, h); - } - } +// @Override +// public void onCommandExecuted(Command command, Long refreshKey) +// { +// window.showMessage(command.getExecuteStringId()); +// new BaseTask() +// { +// @Override +// protected void doInBackground() +// { +// dismissNotifications(BaseActivity.this); +// BackupManager.dataChanged("org.isoron.uhabits"); +// WidgetManager.updateWidgets(BaseActivity.this); +// } +// }.execute(); +// } + +// private void dismissNotifications(Context context) +// { +// for (Habit h : Habit.getHabitsWithReminder()) +// { +// if (h.checkmarks.getTodayValue() != Checkmark.UNCHECKED) +// HabitBroadcastReceiver.dismissNotification(context, h); +// } +// } @Override - public void onCommandExecuted(Command command, Long refreshKey) - { - showMessage(command.getExecuteStringId()); - new BaseTask() - { - @Override - protected void doInBackground() - { - dismissNotifications(BaseActivity.this); - BackupManager.dataChanged("org.isoron.uhabits"); - WidgetManager.updateWidgets(BaseActivity.this); - } - }.execute(); - } - - public void sendFile(@NonNull String archiveFilename) - { - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_SEND); - intent.setType("application/zip"); - intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(archiveFilename))); - startActivity(intent); - } - - public void sendEmail(String to, String subject, String content) + protected void onCreate(Bundle savedInstanceState) { - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_SEND); - intent.setType("message/rfc822"); - intent.putExtra(Intent.EXTRA_EMAIL, new String[] {to}); - intent.putExtra(Intent.EXTRA_SUBJECT, subject); - intent.putExtra(Intent.EXTRA_TEXT, content); - startActivity(intent); + super.onCreate(savedInstanceState); + InterfaceUtils.applyCurrentTheme(this); + androidExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); + Thread.setDefaultUncaughtExceptionHandler(this); } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/BaseMenu.java b/app/src/main/java/org/isoron/uhabits/ui/BaseMenu.java new file mode 100644 index 000000000..ffa30706a --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/BaseMenu.java @@ -0,0 +1,60 @@ +/* + * 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.ui; + +import android.support.annotation.NonNull; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; + +public abstract class BaseMenu +{ + private final BaseActivity activity; + + public BaseMenu(BaseActivity activity) + { + this.activity = activity; + } + + public final boolean onCreate(@NonNull MenuInflater inflater, + @NonNull Menu menu) + { + menu.clear(); + inflater.inflate(getMenuResourceId(), menu); + onCreate(menu); + return true; + } + + public void onCreate(@NonNull Menu menu) + { + } + + public boolean onItemSelected(@NonNull MenuItem item) + { + return true; + } + + protected abstract int getMenuResourceId(); + + public void invalidate() + { + activity.invalidateOptionsMenu(); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/BaseRootView.java b/app/src/main/java/org/isoron/uhabits/ui/BaseRootView.java new file mode 100644 index 000000000..4016442ab --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/BaseRootView.java @@ -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.ui; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Color; +import android.os.Build; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.Toolbar; +import android.util.AttributeSet; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ProgressBar; + +import org.isoron.uhabits.R; +import org.isoron.uhabits.utils.InterfaceUtils; + +public abstract class BaseRootView extends FrameLayout +{ + private final Context context; + + public BaseRootView(Context context) + { + super(context); + this.context = context; + } + + public BaseRootView(Context context, AttributeSet attrs) + { + super(context, attrs); + this.context = context; + } + + public BaseRootView(Context context, AttributeSet attrs, int defStyleAttr) + { + super(context, attrs, defStyleAttr); + this.context = context; + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public BaseRootView(Context context, + AttributeSet attrs, + int defStyleAttr, + int defStyleRes) + { + super(context, attrs, defStyleAttr, defStyleRes); + this.context = context; + } + + public boolean getDisplayHomeAsUp() + { + return false; + } + + @Nullable + public ProgressBar getProgressBar() + { + return null; + } + + @NonNull + public abstract Toolbar getToolbar(); + + public int getToolbarColor() + { +// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) +// { +// if (InterfaceUtils.isNightMode()) return; +// int color = activity.getResources().getColor(R.color.grey_900); +// } +// if (!InterfaceUtils.getStyledBoolean(activity, R.attr.useHabitColorAsPrimary)) return; + return Color.BLACK; + } + + private void hideFakeToolbarShadow() + { + View view = findViewById(R.id.toolbarShadow); + if (view != null) view.setVisibility(View.GONE); + + view = findViewById(R.id.headerShadow); + if (view != null) view.setVisibility(View.GONE); + } + + protected void initToolbar() + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + getToolbar().setElevation(InterfaceUtils.dpToPixels(context, 2)); + + hideFakeToolbarShadow(); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/BaseScreen.java b/app/src/main/java/org/isoron/uhabits/ui/BaseScreen.java new file mode 100644 index 000000000..d61ffae53 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/BaseScreen.java @@ -0,0 +1,217 @@ +/* + * 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.ui; + +import android.content.Intent; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.net.Uri; +import android.os.Build; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatDialogFragment; +import android.support.v7.view.ActionMode; +import android.support.v7.widget.Toolbar; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.Toast; + +import org.isoron.uhabits.tasks.ProgressBar; +import org.isoron.uhabits.utils.ColorUtils; + +import java.io.File; + +public abstract class BaseScreen +{ + protected BaseActivity activity; + + private Toast toast; + + @Nullable + private BaseRootView rootView; + + @Nullable + private BaseSelectionMenu selectionMenu; + + public BaseScreen(BaseActivity activity) + { + this.activity = activity; + } + + public void finishSelection() + { + if (selectionMenu == null) return; + selectionMenu.finish(); + } + + @Nullable + public ProgressBar getProgressBar() + { + if (rootView == null) return null; + return new ProgressBarWrapper(rootView.getProgressBar()); + } + + public void invalidate() + { + if (rootView == null) return; + rootView.invalidate(); + } + + public void onResult(int requestCode, int resultCode, Intent data) + { + } + + public void setMenu(@Nullable BaseMenu menu) + { + activity.setBaseMenu(menu); + } + + public void setRootView(@Nullable BaseRootView rootView) + { + this.rootView = rootView; + activity.setContentView(rootView); + if (rootView == null) return; + + initToolbar(); + } + + /** + * Set the menu to be shown when a selection is active on the screen. + * + * @param menu the menu to be shown during a selection + */ + public void setSelectionMenu(@Nullable BaseSelectionMenu menu) + { + this.selectionMenu = menu; + } + + public void showMessage(@Nullable Integer stringId) + { + if (stringId == null) return; + if (toast == null) + toast = Toast.makeText(activity, stringId, Toast.LENGTH_SHORT); + else toast.setText(stringId); + toast.show(); + } + + public void showSendEmailScreen(String to, String subject, String content) + { + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_SEND); + intent.setType("message/rfc822"); + intent.putExtra(Intent.EXTRA_EMAIL, new String[]{to}); + intent.putExtra(Intent.EXTRA_SUBJECT, subject); + intent.putExtra(Intent.EXTRA_TEXT, content); + activity.startActivity(intent); + } + + public void showSendFileScreen(@NonNull String archiveFilename) + { + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_SEND); + intent.setType("application/zip"); + intent.putExtra(Intent.EXTRA_STREAM, + Uri.fromFile(new File(archiveFilename))); + activity.startActivity(intent); + } + + /** + * Instructs the screen to start a selection. If a selection menu was + * provided, this menu will be shown instead of the regular one. + */ + public void startSelection() + { + activity.startSupportActionMode(new ActionModeWrapper()); + } + + private void initToolbar() + { + if (rootView == null) return; + + Toolbar toolbar = rootView.getToolbar(); + activity.setSupportActionBar(toolbar); + ActionBar actionBar = activity.getSupportActionBar(); + if (actionBar == null) return; + + actionBar.setDisplayHomeAsUpEnabled(rootView.getDisplayHomeAsUp()); + + int color = rootView.getToolbarColor(); + setActionBarColor(actionBar, color); + setStatusBarColor(color); + } + + private void setActionBarColor(@NonNull ActionBar actionBar, int color) + { + ColorDrawable drawable = new ColorDrawable(color); + actionBar.setBackgroundDrawable(drawable); + } + + private void setStatusBarColor(int baseColor) + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + { + int darkerColor = + ColorUtils.mixColors(baseColor, Color.BLACK, 0.75f); + activity.getWindow().setStatusBarColor(darkerColor); + } + } + + protected void showDialog(AppCompatDialogFragment dialog, String tag) + { + dialog.show(activity.getSupportFragmentManager(), tag); + } + + private class ActionModeWrapper implements ActionMode.Callback + { + @Override + public boolean onActionItemClicked(@Nullable ActionMode mode, + @Nullable MenuItem item) + { + if (item == null || selectionMenu == null) return false; + return selectionMenu.onItemClicked(item); + } + + @Override + public boolean onCreateActionMode(@Nullable ActionMode mode, + @Nullable Menu menu) + { + if (selectionMenu == null) return false; + if (mode == null || menu == null) return false; + selectionMenu.onCreate(activity.getMenuInflater(), mode, menu); + return true; + } + + @Override + public void onDestroyActionMode(@Nullable ActionMode mode) + { + if (selectionMenu == null) return; + selectionMenu.onDestroy(); + } + + @Override + public boolean onPrepareActionMode(@Nullable ActionMode mode, + @Nullable Menu menu) + { + if (selectionMenu == null || menu == null) return false; + return selectionMenu.onPrepare(menu); + } + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/BaseSelectionMenu.java b/app/src/main/java/org/isoron/uhabits/ui/BaseSelectionMenu.java new file mode 100644 index 000000000..960c826d2 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/BaseSelectionMenu.java @@ -0,0 +1,87 @@ +/* + * 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.ui; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.view.ActionMode; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; + +public abstract class BaseSelectionMenu +{ + @Nullable + private ActionMode actionMode; + + /** + * Finishes the selection operation. + */ + public void finish() + { + if (actionMode != null) actionMode.finish(); + } + + public void invalidate() + { + if (actionMode != null) actionMode.invalidate(); + } + + public final void onCreate(@NonNull MenuInflater menuInflater, + @NonNull ActionMode mode, + @NonNull Menu menu) + { + this.actionMode = mode; + menuInflater.inflate(getResourceId(), menu); + onCreate(menu); + } + + public void onDestroy() + { + + } + + public boolean onItemClicked(@NonNull MenuItem item) + { + return false; + } + + public boolean onPrepare(@NonNull Menu menu) + { + return false; + } + + public void setTitle(String title) + { + if (actionMode != null) actionMode.setTitle(title); + } + + protected abstract int getResourceId(); + + /** + * Called when the menu is first created, right after the menu has been + * inflated. + * + * @param menu the menu containing the buttons + */ + protected void onCreate(@NonNull Menu menu) + { + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/BaseSystem.java b/app/src/main/java/org/isoron/uhabits/ui/BaseSystem.java new file mode 100644 index 000000000..8cd9d07e4 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/BaseSystem.java @@ -0,0 +1,166 @@ +/* + * 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.ui; + +import android.content.Context; +import android.os.Environment; +import android.support.annotation.NonNull; +import android.view.WindowManager; + +import org.isoron.uhabits.BuildConfig; +import org.isoron.uhabits.tasks.BaseTask; +import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.utils.FileUtils; +import org.isoron.uhabits.utils.ReminderUtils; +import org.isoron.uhabits.widgets.WidgetManager; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.LinkedList; + +public class BaseSystem +{ + private Context context; + + public BaseSystem(Context context) + { + this.context = context; + } + + public String getLogcat() throws IOException + { + int maxNLines = 250; + StringBuilder builder = new StringBuilder(); + + String[] command = new String[]{"logcat", "-d"}; + Process process = Runtime.getRuntime().exec(command); + + InputStreamReader in = new InputStreamReader(process.getInputStream()); + BufferedReader bufferedReader = new BufferedReader(in); + + LinkedList log = new LinkedList<>(); + + String line; + while ((line = bufferedReader.readLine()) != null) + { + log.addLast(line); + if (log.size() > maxNLines) log.removeFirst(); + } + + for (String l : log) + { + builder.append(l); + builder.append('\n'); + } + + return builder.toString(); + } + + public String getDeviceInfo() + { + if (context == null) return ""; + + StringBuilder b = new StringBuilder(); + WindowManager wm = + (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + + b.append( + String.format("App Version Name: %s\n", BuildConfig.VERSION_NAME)); + b.append( + String.format("App Version Code: %s\n", BuildConfig.VERSION_CODE)); + b.append(String.format("OS Version: %s (%s)\n", + java.lang.System.getProperty("os.version"), + android.os.Build.VERSION.INCREMENTAL)); + b.append( + String.format("OS API Level: %s\n", android.os.Build.VERSION.SDK)); + b.append(String.format("Device: %s\n", android.os.Build.DEVICE)); + b.append( + String.format("Model (Product): %s (%s)\n", android.os.Build.MODEL, + android.os.Build.PRODUCT)); + b.append( + String.format("Manufacturer: %s\n", android.os.Build.MANUFACTURER)); + b.append(String.format("Other tags: %s\n", android.os.Build.TAGS)); + b.append(String.format("Screen Width: %s\n", + wm.getDefaultDisplay().getWidth())); + b.append(String.format("Screen Height: %s\n", + wm.getDefaultDisplay().getHeight())); + b.append(String.format("SD Card state: %s\n\n", + Environment.getExternalStorageState())); + + return b.toString(); + } + + @NonNull + public File dumpBugReportToFile() throws IOException + { + String date = + DateUtils.getBackupDateFormat().format(DateUtils.getLocalTime()); + + if (context == null) throw new RuntimeException( + "application context should not be null"); + File dir = FileUtils.getFilesDir("Logs"); + if (dir == null) throw new IOException("log dir should not be null"); + + File logFile = + new File(String.format("%s/Log %s.txt", dir.getPath(), date)); + FileWriter output = new FileWriter(logFile); + output.write(getBugReport()); + output.close(); + + return logFile; + } + + @NonNull + public String getBugReport() throws IOException + { + String logcat = getLogcat(); + String deviceInfo = getDeviceInfo(); + return deviceInfo + "\n" + logcat; + } + + public void scheduleReminders() + { + new BaseTask() + { + + @Override + protected void doInBackground() + { + ReminderUtils.createReminderAlarms(context); + } + }.execute(); + } + + public void updateWidgets() + { + new BaseTask() + { + + @Override + protected void doInBackground() + { + WidgetManager.updateWidgets(context); + } + }.execute(); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/HintManager.java b/app/src/main/java/org/isoron/uhabits/ui/HintManager.java deleted file mode 100644 index aa366833f..000000000 --- a/app/src/main/java/org/isoron/uhabits/ui/HintManager.java +++ /dev/null @@ -1,81 +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.ui; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.content.Context; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; -import android.view.View; -import android.widget.TextView; - -import org.isoron.uhabits.R; -import org.isoron.uhabits.utils.DateUtils; - -public class HintManager -{ - private Context context; - private SharedPreferences prefs; - private View hintView; - - public HintManager(Context context, View hintView) - { - this.context = context; - this.hintView = hintView; - prefs = PreferenceManager.getDefaultSharedPreferences(context); - } - - public void dismissHint() - { - hintView.animate().alpha(0f).setDuration(500).setListener(new AnimatorListenerAdapter() - { - @Override - public void onAnimationEnd(Animator animation) - { - hintView.setVisibility(View.GONE); - } - }); - } - - public void showHintIfAppropriate() - { - Integer lastHintNumber = prefs.getInt("last_hint_number", -1); - Long lastHintTimestamp = prefs.getLong("last_hint_timestamp", -1); - - if (DateUtils.getStartOfToday() > lastHintTimestamp) showHint(lastHintNumber + 1); - } - - private void showHint(int hintNumber) - { - String[] hints = context.getResources().getStringArray(R.array.hints); - if (hintNumber >= hints.length) return; - - prefs.edit().putInt("last_hint_number", hintNumber).apply(); - prefs.edit().putLong("last_hint_timestamp", DateUtils.getStartOfToday()).apply(); - - TextView tvContent = (TextView) hintView.findViewById(R.id.hintContent); - tvContent.setText(hints[hintNumber]); - - hintView.setAlpha(0.0f); - hintView.setVisibility(View.VISIBLE); - hintView.animate().alpha(1f).setDuration(500); - } -} diff --git a/app/src/main/java/org/isoron/uhabits/ui/AndroidProgressBar.java b/app/src/main/java/org/isoron/uhabits/ui/ProgressBarWrapper.java similarity index 91% rename from app/src/main/java/org/isoron/uhabits/ui/AndroidProgressBar.java rename to app/src/main/java/org/isoron/uhabits/ui/ProgressBarWrapper.java index c366bf88c..44339ebf6 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/AndroidProgressBar.java +++ b/app/src/main/java/org/isoron/uhabits/ui/ProgressBarWrapper.java @@ -23,11 +23,11 @@ import android.view.View; import org.isoron.uhabits.tasks.ProgressBar; -public class AndroidProgressBar implements ProgressBar +public class ProgressBarWrapper implements ProgressBar { private final android.widget.ProgressBar progressBar; - public AndroidProgressBar(android.widget.ProgressBar progressBar) + public ProgressBarWrapper(android.widget.ProgressBar progressBar) { this.progressBar = progressBar; } diff --git a/app/src/main/java/org/isoron/uhabits/ui/about/AboutActivity.java b/app/src/main/java/org/isoron/uhabits/ui/about/AboutActivity.java index dfebc387b..e1c7600a0 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/about/AboutActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ui/about/AboutActivity.java @@ -30,32 +30,12 @@ import org.isoron.uhabits.R; import org.isoron.uhabits.ui.BaseActivity; import org.isoron.uhabits.utils.InterfaceUtils; +/** + * Activity that allows the user to see information about the app itself. + * Display current version, link to Google Play and list of contributors. + */ public class AboutActivity extends BaseActivity implements View.OnClickListener { - - @Override - protected void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - - setContentView(R.layout.about); - setupSupportActionBar(true); - - int color = InterfaceUtils.getStyledColor(this, R.attr.aboutScreenColor); - setupActionBarColor(color); - - TextView tvVersion = (TextView) findViewById(R.id.tvVersion); - TextView tvRate = (TextView) findViewById(R.id.tvRate); - TextView tvFeedback = (TextView) findViewById(R.id.tvFeedback); - TextView tvSource = (TextView) findViewById(R.id.tvSource); - - tvVersion.setText(String.format(getResources().getString(R.string.version_n), - BuildConfig.VERSION_NAME)); - tvRate.setOnClickListener(this); - tvFeedback.setOnClickListener(this); - tvSource.setOnClickListener(this); - } - @Override public void onClick(View v) { @@ -89,4 +69,29 @@ public class AboutActivity extends BaseActivity implements View.OnClickListener } } } + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + setContentView(R.layout.about); +// setupSupportActionBar(true); + + int color = + InterfaceUtils.getStyledColor(this, R.attr.aboutScreenColor); +// setupActionBarColor(color); + + TextView tvVersion = (TextView) findViewById(R.id.tvVersion); + TextView tvRate = (TextView) findViewById(R.id.tvRate); + TextView tvFeedback = (TextView) findViewById(R.id.tvFeedback); + TextView tvSource = (TextView) findViewById(R.id.tvSource); + + tvVersion.setText( + String.format(getResources().getString(R.string.version_n), + BuildConfig.VERSION_NAME)); + tvRate.setOnClickListener(this); + tvFeedback.setOnClickListener(this); + tvSource.setOnClickListener(this); + } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/about/package-info.java b/app/src/main/java/org/isoron/uhabits/ui/about/package-info.java new file mode 100644 index 000000000..bfdb10c80 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/about/package-info.java @@ -0,0 +1,23 @@ +/* + * 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 classes for AboutActivity + */ +package org.isoron.uhabits.ui.about; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogFragment.java index 96182853c..7dfb86e31 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogFragment.java @@ -32,7 +32,9 @@ import com.android.colorpicker.ColorPickerSwatch; import com.android.datetimepicker.time.RadialPickerLayout; import com.android.datetimepicker.time.TimePickerDialog; +import org.isoron.uhabits.HabitsApplication; import org.isoron.uhabits.R; +import org.isoron.uhabits.commands.CommandRunner; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.utils.ColorUtils; import org.isoron.uhabits.utils.DateUtils; @@ -40,6 +42,8 @@ import org.isoron.uhabits.utils.Preferences; import java.util.Arrays; +import javax.inject.Inject; + import butterknife.ButterKnife; import butterknife.OnClick; import butterknife.OnItemSelected; @@ -47,15 +51,24 @@ import butterknife.OnItemSelected; public abstract class BaseDialogFragment extends AppCompatDialogFragment { protected Habit originalHabit; + protected Habit modifiedHabit; - protected Preferences prefs = Preferences.getInstance(); + protected BaseDialogHelper helper; - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, + @Inject + Preferences prefs; + + @Inject + CommandRunner commandRunner; + + @Override + public View onCreateView(LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.edit_habit, container, false); + HabitsApplication.getComponent().inject(this); ButterKnife.bind(this, view); helper = new BaseDialogHelper(this, view); @@ -66,11 +79,16 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment return view; } - protected abstract void initializeHabits(); - - protected abstract void saveHabit(); - - protected abstract int getTitle(); + @OnItemSelected(R.id.sFrequency) + public void onFrequencySelected(int position) + { + if (position < 0 || position > 4) throw new IllegalArgumentException(); + int freqNums[] = {1, 1, 2, 5, 3}; + int freqDens[] = {1, 7, 7, 7, 7}; + modifiedHabit.freqNum = freqNums[position]; + modifiedHabit.freqDen = freqDens[position]; + helper.populateFrequencyFields(modifiedHabit); + } @Override @SuppressWarnings("ConstantConditions") @@ -78,7 +96,7 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment { super.onSaveInstanceState(outState); outState.putInt("color", modifiedHabit.color); - if(modifiedHabit.hasReminder()) + if (modifiedHabit.hasReminder()) { outState.putInt("reminderMin", modifiedHabit.reminderMin); outState.putInt("reminderHour", modifiedHabit.reminderHour); @@ -86,15 +104,9 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment } } - protected void restoreSavedInstance(@Nullable Bundle bundle) - { - if(bundle == null) return; - modifiedHabit.color = bundle.getInt("color", modifiedHabit.color); - modifiedHabit.reminderMin = bundle.getInt("reminderMin", -1); - modifiedHabit.reminderHour = bundle.getInt("reminderHour", -1); - modifiedHabit.reminderDays = bundle.getInt("reminderDays", -1); - if(modifiedHabit.reminderMin < 0) modifiedHabit.clearReminder(); - } + protected abstract int getTitle(); + + protected abstract void initializeHabits(); @OnClick(R.id.buttonDiscard) void onButtonDiscardClick() @@ -102,15 +114,6 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment dismiss(); } - @OnClick(R.id.buttonSave) - void onSaveButtonClick() - { - helper.parseFormIntoHabit(modifiedHabit); - if (!helper.validate(modifiedHabit)) return; - saveHabit(); - dismiss(); - } - @OnClick(R.id.tvReminderTime) @SuppressWarnings("ConstantConditions") void onDateSpinnerClick() @@ -127,36 +130,49 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment showTimePicker(defaultHour, defaultMin); } + @OnClick(R.id.buttonSave) + void onSaveButtonClick() + { + helper.parseFormIntoHabit(modifiedHabit); + if (!helper.validate(modifiedHabit)) return; + saveHabit(); + dismiss(); + } + @OnClick(R.id.tvReminderDays) @SuppressWarnings("ConstantConditions") void onWeekdayClick() { - if(!modifiedHabit.hasReminder()) return; + if (!modifiedHabit.hasReminder()) return; WeekdayPickerDialog dialog = new WeekdayPickerDialog(); dialog.setListener(new OnWeekdaysPickedListener()); - dialog.setSelectedDays(DateUtils.unpackWeekdayList(modifiedHabit.reminderDays)); + dialog.setSelectedDays( + DateUtils.unpackWeekdayList(modifiedHabit.reminderDays)); dialog.show(getFragmentManager(), "weekdayPicker"); } - @OnItemSelected(R.id.sFrequency) - public void onFrequencySelected(int position) + protected void restoreSavedInstance(@Nullable Bundle bundle) { - if(position < 0 || position > 4) throw new IllegalArgumentException(); - int freqNums[] = { 1, 1, 2, 5, 3 }; - int freqDens[] = { 1, 7, 7, 7, 7 }; - modifiedHabit.freqNum = freqNums[position]; - modifiedHabit.freqDen = freqDens[position]; - helper.populateFrequencyFields(modifiedHabit); + if (bundle == null) return; + modifiedHabit.color = bundle.getInt("color", modifiedHabit.color); + modifiedHabit.reminderMin = bundle.getInt("reminderMin", -1); + modifiedHabit.reminderHour = bundle.getInt("reminderHour", -1); + modifiedHabit.reminderDays = bundle.getInt("reminderDays", -1); + if (modifiedHabit.reminderMin < 0) modifiedHabit.clearReminder(); } + protected abstract void saveHabit(); + @OnClick(R.id.buttonPickColor) void showColorPicker() { - int androidColor = ColorUtils.getColor(getContext(), modifiedHabit.color); + int androidColor = + ColorUtils.getColor(getContext(), modifiedHabit.color); - ColorPickerDialog picker = ColorPickerDialog.newInstance( - R.string.color_picker_default_title, ColorUtils.getPalette(getContext()), - androidColor, 4, ColorPickerDialog.SIZE_SMALL); + ColorPickerDialog picker = + ColorPickerDialog.newInstance(R.string.color_picker_default_title, + ColorUtils.getPalette(getContext()), androidColor, 4, + ColorPickerDialog.SIZE_SMALL); picker.setOnColorSelectedListener(new OnColorSelectedListener()); picker.show(getFragmentManager(), "picker"); @@ -165,43 +181,36 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment private void showTimePicker(int defaultHour, int defaultMin) { boolean is24HourMode = DateFormat.is24HourFormat(getContext()); - TimePickerDialog timePicker = TimePickerDialog.newInstance(new OnTimeSetListener(), - defaultHour, defaultMin, is24HourMode); + TimePickerDialog timePicker = + TimePickerDialog.newInstance(new OnTimeSetListener(), defaultHour, + defaultMin, is24HourMode); timePicker.show(getFragmentManager(), "timePicker"); } - private class OnColorSelectedListener implements ColorPickerSwatch.OnColorSelectedListener + private class OnColorSelectedListener + implements ColorPickerSwatch.OnColorSelectedListener { + @Override public void onColorSelected(int androidColor) { - int paletteColor = ColorUtils.colorToPaletteIndex(getActivity(), androidColor); + int paletteColor = + ColorUtils.colorToPaletteIndex(getActivity(), androidColor); prefs.setDefaultHabitColor(paletteColor); modifiedHabit.color = paletteColor; helper.populateColor(paletteColor); } } - private class OnWeekdaysPickedListener implements WeekdayPickerDialog.OnWeekdaysPickedListener + private class OnTimeSetListener + implements TimePickerDialog.OnTimeSetListener { @Override - public void onWeekdaysPicked(boolean[] selectedDays) + public void onTimeCleared(RadialPickerLayout view) { - if(isSelectionEmpty(selectedDays)) - Arrays.fill(selectedDays, true); - - modifiedHabit.reminderDays = DateUtils.packWeekdayList(selectedDays); + modifiedHabit.clearReminder(); helper.populateReminderFields(modifiedHabit); } - private boolean isSelectionEmpty(boolean[] selectedDays) - { - for (boolean d : selectedDays) if (d) return false; - return true; - } - } - - private class OnTimeSetListener implements TimePickerDialog.OnTimeSetListener - { @Override public void onTimeSet(RadialPickerLayout view, int hour, int minute) { @@ -210,12 +219,25 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment modifiedHabit.reminderDays = DateUtils.ALL_WEEK_DAYS; helper.populateReminderFields(modifiedHabit); } + } + private class OnWeekdaysPickedListener + implements WeekdayPickerDialog.OnWeekdaysPickedListener + { @Override - public void onTimeCleared(RadialPickerLayout view) + public void onWeekdaysPicked(boolean[] selectedDays) { - modifiedHabit.clearReminder(); + if (isSelectionEmpty(selectedDays)) Arrays.fill(selectedDays, true); + + modifiedHabit.reminderDays = + DateUtils.packWeekdayList(selectedDays); helper.populateReminderFields(modifiedHabit); } + + private boolean isSelectionEmpty(boolean[] selectedDays) + { + for (boolean d : selectedDays) if (d) return false; + return true; + } } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogHelper.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogHelper.java index 7605976c2..a9f518b48 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogHelper.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogHelper.java @@ -37,15 +37,33 @@ import butterknife.ButterKnife; public class BaseDialogHelper { private DialogFragment frag; - @BindView(R.id.tvName) TextView tvName; - @BindView(R.id.tvDescription) TextView tvDescription; - @BindView(R.id.tvFreqNum) TextView tvFreqNum; - @BindView(R.id.tvFreqDen) TextView tvFreqDen; - @BindView(R.id.tvReminderTime) TextView tvReminderTime; - @BindView(R.id.tvReminderDays) TextView tvReminderDays; - @BindView(R.id.sFrequency) Spinner sFrequency; - @BindView(R.id.llCustomFrequency) ViewGroup llCustomFrequency; - @BindView(R.id.llReminderDays) ViewGroup llReminderDays; + + @BindView(R.id.tvName) + TextView tvName; + + @BindView(R.id.tvDescription) + TextView tvDescription; + + @BindView(R.id.tvFreqNum) + TextView tvFreqNum; + + @BindView(R.id.tvFreqDen) + TextView tvFreqDen; + + @BindView(R.id.tvReminderTime) + TextView tvReminderTime; + + @BindView(R.id.tvReminderDays) + TextView tvReminderDays; + + @BindView(R.id.sFrequency) + Spinner sFrequency; + + @BindView(R.id.llCustomFrequency) + ViewGroup llCustomFrequency; + + @BindView(R.id.llReminderDays) + ViewGroup llReminderDays; public BaseDialogHelper(DialogFragment frag, View view) { @@ -53,37 +71,30 @@ public class BaseDialogHelper ButterKnife.bind(this, view); } - protected void populateForm(final Habit habit) + void parseFormIntoHabit(Habit habit) { - if(habit.name != null) tvName.setText(habit.name); - if(habit.description != null) tvDescription.setText(habit.description); - - populateColor(habit.color); - populateFrequencyFields(habit); - populateReminderFields(habit); + habit.name = tvName.getText().toString().trim(); + habit.description = tvDescription.getText().toString().trim(); + String freqNum = tvFreqNum.getText().toString(); + String freqDen = tvFreqDen.getText().toString(); + if (!freqNum.isEmpty()) habit.freqNum = Integer.parseInt(freqNum); + if (!freqDen.isEmpty()) habit.freqDen = Integer.parseInt(freqDen); } void populateColor(int paletteColor) { - tvName.setTextColor(ColorUtils.getColor(frag.getContext(), paletteColor)); + tvName.setTextColor( + ColorUtils.getColor(frag.getContext(), paletteColor)); } - @SuppressWarnings("ConstantConditions") - void populateReminderFields(Habit habit) + protected void populateForm(final Habit habit) { - if (!habit.hasReminder()) - { - tvReminderTime.setText(R.string.reminder_off); - llReminderDays.setVisibility(View.GONE); - return; - } - - String time = DateUtils.formatTime(frag.getContext(), habit.reminderHour, habit.reminderMin); - tvReminderTime.setText(time); - llReminderDays.setVisibility(View.VISIBLE); + if (habit.name != null) tvName.setText(habit.name); + if (habit.description != null) tvDescription.setText(habit.description); - boolean weekdays[] = DateUtils.unpackWeekdayList(habit.reminderDays); - tvReminderDays.setText(DateUtils.formatWeekdayList(frag.getContext(), weekdays)); + populateColor(habit.color); + populateFrequencyFields(habit); + populateReminderFields(habit); } @SuppressLint("SetTextI18n") @@ -91,25 +102,47 @@ public class BaseDialogHelper { int quickSelectPosition = -1; - if(habit.freqNum.equals(habit.freqDen)) - quickSelectPosition = 0; + if (habit.freqNum.equals(habit.freqDen)) quickSelectPosition = 0; - else if(habit.freqNum == 1 && habit.freqDen == 7) + else if (habit.freqNum == 1 && habit.freqDen == 7) quickSelectPosition = 1; - else if(habit.freqNum == 2 && habit.freqDen == 7) + else if (habit.freqNum == 2 && habit.freqDen == 7) quickSelectPosition = 2; - else if(habit.freqNum == 5 && habit.freqDen == 7) + else if (habit.freqNum == 5 && habit.freqDen == 7) quickSelectPosition = 3; - if(quickSelectPosition >= 0) showSimplifiedFrequency(quickSelectPosition); + if (quickSelectPosition >= 0) + showSimplifiedFrequency(quickSelectPosition); + else showCustomFrequency(); tvFreqNum.setText(habit.freqNum.toString()); tvFreqDen.setText(habit.freqDen.toString()); } + @SuppressWarnings("ConstantConditions") + void populateReminderFields(Habit habit) + { + if (!habit.hasReminder()) + { + tvReminderTime.setText(R.string.reminder_off); + llReminderDays.setVisibility(View.GONE); + return; + } + + String time = + DateUtils.formatTime(frag.getContext(), habit.reminderHour, + habit.reminderMin); + tvReminderTime.setText(time); + llReminderDays.setVisibility(View.VISIBLE); + + boolean weekdays[] = DateUtils.unpackWeekdayList(habit.reminderDays); + tvReminderDays.setText( + DateUtils.formatWeekdayList(frag.getContext(), weekdays)); + } + private void showCustomFrequency() { sFrequency.setVisibility(View.GONE); @@ -130,32 +163,25 @@ public class BaseDialogHelper if (habit.name.length() == 0) { - tvName.setError(frag.getString(R.string.validation_name_should_not_be_blank)); + tvName.setError( + frag.getString(R.string.validation_name_should_not_be_blank)); valid = false; } if (habit.freqNum <= 0) { - tvFreqNum.setError(frag.getString(R.string.validation_number_should_be_positive)); + tvFreqNum.setError( + frag.getString(R.string.validation_number_should_be_positive)); valid = false; } if (habit.freqNum > habit.freqDen) { - tvFreqNum.setError(frag.getString(R.string.validation_at_most_one_rep_per_day)); + tvFreqNum.setError( + frag.getString(R.string.validation_at_most_one_rep_per_day)); valid = false; } return valid; } - - void parseFormIntoHabit(Habit habit) - { - habit.name = tvName.getText().toString().trim(); - habit.description = tvDescription.getText().toString().trim(); - String freqNum = tvFreqNum.getText().toString(); - String freqDen = tvFreqDen.getText().toString(); - if(!freqNum.isEmpty()) habit.freqNum = Integer.parseInt(freqNum); - if(!freqDen.isEmpty()) habit.freqDen = Integer.parseInt(freqDen); - } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/CreateHabitDialogFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/CreateHabitDialogFragment.java index 077a2c750..b63a95f24 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/CreateHabitDialogFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/CreateHabitDialogFragment.java @@ -21,7 +21,6 @@ package org.isoron.uhabits.ui.habits.edit; import org.isoron.uhabits.R; import org.isoron.uhabits.commands.Command; -import org.isoron.uhabits.commands.CommandRunner; import org.isoron.uhabits.commands.CreateHabitCommand; import org.isoron.uhabits.models.Habit; @@ -45,6 +44,6 @@ public class CreateHabitDialogFragment extends BaseDialogFragment protected void saveHabit() { Command command = new CreateHabitCommand(modifiedHabit); - CommandRunner.getInstance().execute(command, null); + commandRunner.execute(command, null); } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/EditHabitDialogFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/EditHabitDialogFragment.java index 7ffff0aac..7adf8f3e8 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/EditHabitDialogFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/EditHabitDialogFragment.java @@ -23,7 +23,6 @@ import android.os.Bundle; import org.isoron.uhabits.R; import org.isoron.uhabits.commands.Command; -import org.isoron.uhabits.commands.CommandRunner; import org.isoron.uhabits.commands.EditHabitCommand; import org.isoron.uhabits.models.Habit; @@ -48,7 +47,8 @@ public class EditHabitDialogFragment extends BaseDialogFragment protected void initializeHabits() { Long habitId = (Long) getArguments().get("habitId"); - if(habitId == null) throw new IllegalArgumentException("habitId must be specified"); + if (habitId == null) + throw new IllegalArgumentException("habitId must be specified"); originalHabit = Habit.get(habitId); modifiedHabit = new Habit(originalHabit); @@ -57,6 +57,6 @@ public class EditHabitDialogFragment extends BaseDialogFragment protected void saveHabit() { Command command = new EditHabitCommand(originalHabit, modifiedHabit); - CommandRunner.getInstance().execute(command, originalHabit.getId()); + commandRunner.execute(command, originalHabit.getId()); } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/HistoryEditorDialog.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/HistoryEditorDialog.java index 147d62f63..f1bd5c1f8 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/HistoryEditorDialog.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/HistoryEditorDialog.java @@ -36,7 +36,9 @@ public class HistoryEditorDialog extends AppCompatDialogFragment implements DialogInterface.OnClickListener { private Habit habit; + private Listener listener; + HabitHistoryView historyView; @Override @@ -45,21 +47,23 @@ public class HistoryEditorDialog extends AppCompatDialogFragment Context context = getActivity(); historyView = new HabitHistoryView(context, null); - if(savedInstanceState != null) + if (savedInstanceState != null) { long id = savedInstanceState.getLong("habit", -1); - if(id > 0) this.habit = Habit.get(id); + if (id > 0) this.habit = Habit.get(id); } - int padding = (int) getResources().getDimension(R.dimen.history_editor_padding); + int padding = + (int) getResources().getDimension(R.dimen.history_editor_padding); historyView.setPadding(padding, 0, padding, 0); historyView.setHabit(habit); historyView.setIsEditable(true); AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(R.string.history) - .setView(historyView) - .setPositiveButton(android.R.string.ok, this); + builder + .setTitle(R.string.history) + .setView(historyView) + .setPositiveButton(android.R.string.ok, this); refreshData(); @@ -84,7 +88,8 @@ public class HistoryEditorDialog extends AppCompatDialogFragment super.onResume(); DisplayMetrics metrics = getResources().getDisplayMetrics(); - int maxHeight = getResources().getDimensionPixelSize(R.dimen.history_editor_max_height); + int maxHeight = getResources().getDimensionPixelSize( + R.dimen.history_editor_max_height); int width = metrics.widthPixels; int height = Math.min(metrics.heightPixels, maxHeight); @@ -100,14 +105,14 @@ public class HistoryEditorDialog extends AppCompatDialogFragment public void setHabit(Habit habit) { this.habit = habit; - if(historyView != null) historyView.setHabit(habit); + if (historyView != null) historyView.setHabit(habit); } @Override public void onPause() { super.onPause(); - if(listener != null) listener.onHistoryEditorClosed(); + if (listener != null) listener.onHistoryEditorClosed(); } @Override @@ -121,7 +126,8 @@ public class HistoryEditorDialog extends AppCompatDialogFragment this.listener = listener; } - public interface Listener { + public interface Listener + { void onHistoryEditorClosed(); } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/WeekdayPickerDialog.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/WeekdayPickerDialog.java index b6af16b01..477c736f0 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/WeekdayPickerDialog.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/WeekdayPickerDialog.java @@ -28,8 +28,9 @@ import android.support.v7.app.AppCompatDialogFragment; import org.isoron.uhabits.R; import org.isoron.uhabits.utils.DateUtils; -public class WeekdayPickerDialog extends AppCompatDialogFragment - implements DialogInterface.OnMultiChoiceClickListener, DialogInterface.OnClickListener +public class WeekdayPickerDialog extends AppCompatDialogFragment implements + DialogInterface.OnMultiChoiceClickListener, + DialogInterface.OnClickListener { public interface OnWeekdaysPickedListener @@ -38,6 +39,7 @@ public class WeekdayPickerDialog extends AppCompatDialogFragment } private boolean[] selectedDays; + private OnWeekdaysPickedListener listener; public void setListener(OnWeekdaysPickedListener listener) @@ -54,10 +56,13 @@ public class WeekdayPickerDialog extends AppCompatDialogFragment public Dialog onCreateDialog(Bundle savedInstanceState) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - builder.setTitle(R.string.select_weekdays) - .setMultiChoiceItems(DateUtils.getLongDayNames(), selectedDays, this) - .setPositiveButton(android.R.string.yes, this) - .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() + builder + .setTitle(R.string.select_weekdays) + .setMultiChoiceItems(DateUtils.getLongDayNames(), selectedDays, + this) + .setPositiveButton(android.R.string.yes, this) + .setNegativeButton(android.R.string.cancel, + new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) @@ -78,6 +83,6 @@ public class WeekdayPickerDialog extends AppCompatDialogFragment @Override public void onClick(DialogInterface dialog, int which) { - if(listener != null) listener.onWeekdaysPicked(selectedDays); + if (listener != null) listener.onWeekdaysPicked(selectedDays); } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListAdapter.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListAdapter.java deleted file mode 100644 index e95aa3d11..000000000 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListAdapter.java +++ /dev/null @@ -1,99 +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.ui.habits.list; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; - -import org.isoron.uhabits.R; -import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.models.Habit; - -import java.util.List; - -class HabitListAdapter extends BaseAdapter -{ - private LayoutInflater inflater; - private HabitListLoader loader; - private ListHabitsHelper helper; - private List selectedPositions; - private View.OnLongClickListener onCheckmarkLongClickListener; - private View.OnClickListener onCheckmarkClickListener; - - public HabitListAdapter(Context context, HabitListLoader loader) - { - this.loader = loader; - - inflater = LayoutInflater.from(context); - helper = new ListHabitsHelper(context, loader); - } - - @Override - public int getCount() - { - return loader.habits.size(); - } - - @Override - public Habit getItem(int position) - { - return loader.habitsList.get(position); - } - - @Override - public long getItemId(int position) - { - return (getItem(position)).getId(); - } - - @Override - public View getView(int position, View view, ViewGroup parent) - { - final Habit habit = loader.habitsList.get(position); - boolean selected = selectedPositions.contains(position); - - if (view == null || (Long) view.getTag(R.id.timestamp_key) != DateUtils.getStartOfToday()) - { - view = helper.inflateHabitCard(inflater, onCheckmarkLongClickListener, - onCheckmarkClickListener); - } - - helper.updateHabitCard(view, habit, selected); - return view; - } - - public void setSelectedPositions(List selectedPositions) - { - this.selectedPositions = selectedPositions; - } - - public void setOnCheckmarkLongClickListener(View.OnLongClickListener listener) - { - this.onCheckmarkLongClickListener = listener; - } - - public void setOnCheckmarkClickListener(View.OnClickListener listener) - { - this.onCheckmarkClickListener = listener; - } -} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListLoader.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListLoader.java deleted file mode 100644 index d80b178fa..000000000 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListLoader.java +++ /dev/null @@ -1,223 +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.ui.habits.list; - -import android.support.annotation.Nullable; - -import org.isoron.uhabits.commands.Command; -import org.isoron.uhabits.commands.CommandRunner; -import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.tasks.BaseTask; - -import java.util.HashMap; -import java.util.List; - -public class HabitListLoader implements CommandRunner.Listener -{ - public interface Listener - { - void onLoadFinished(); - } - - private BaseTask currentFetchTask; - private int checkmarkCount; - - @Nullable - private Listener listener; - private Long lastLoadTimestamp; - - public HashMap habits; - public List habitsList; - public HashMap checkmarks; - public HashMap scores; - - boolean includeArchived; - - public void setIncludeArchived(boolean includeArchived) - { - this.includeArchived = includeArchived; - } - - public void setCheckmarkCount(int checkmarkCount) - { - this.checkmarkCount = checkmarkCount; - } - - public void setListener(@Nullable Listener listener) - { - this.listener = listener; - } - - public Long getLastLoadTimestamp() - { - return lastLoadTimestamp; - } - - public HabitListLoader() - { - habits = new HashMap<>(); - checkmarks = new HashMap<>(); - scores = new HashMap<>(); - } - - public void reorder(int from, int to) - { - Habit fromHabit = habitsList.get(from); - Habit toHabit = habitsList.get(to); - - habitsList.remove(from); - habitsList.add(to, fromHabit); - - Habit.reorder(fromHabit, toHabit); - } - - public void updateAllHabits(final boolean updateScoresAndCheckmarks) - { - if (currentFetchTask != null) currentFetchTask.cancel(true); - - currentFetchTask = new BaseTask() - { - public HashMap newHabits; - public HashMap newCheckmarks; - public HashMap newScores; - public List newHabitList; - - @Override - protected void doInBackground() - { - newHabits = new HashMap<>(); - newCheckmarks = new HashMap<>(); - newScores = new HashMap<>(); - newHabitList = Habit.getAll(includeArchived); - - long dateTo = DateUtils.getStartOfDay(DateUtils.getLocalTime()); - long dateFrom = dateTo - (checkmarkCount - 1) * DateUtils.millisecondsInOneDay; - int[] empty = new int[checkmarkCount]; - - for(Habit h : newHabitList) - { - Long id = h.getId(); - - newHabits.put(id, h); - - if(checkmarks.containsKey(id)) - newCheckmarks.put(id, checkmarks.get(id)); - else - newCheckmarks.put(id, empty); - - if(scores.containsKey(id)) - newScores.put(id, scores.get(id)); - else - newScores.put(id, 0); - } - - commit(); - - if(!updateScoresAndCheckmarks) return; - - int current = 0; - for (Habit h : newHabitList) - { - if (isCancelled()) return; - - Long id = h.getId(); - newScores.put(id, h.scores.getTodayValue()); - newCheckmarks.put(id, h.checkmarks.getValues(dateFrom, dateTo)); - - publishProgress(current++, newHabits.size()); - } - } - - private void commit() - { - habits = newHabits; - scores = newScores; - checkmarks = newCheckmarks; - habitsList = newHabitList; - } - - @Override - protected void onProgressUpdate(Integer... values) - { - if(listener != null) listener.onLoadFinished(); - } - - @Override - protected void onPostExecute(Void aVoid) - { - if (isCancelled()) return; - - lastLoadTimestamp = DateUtils.getStartOfToday(); - currentFetchTask = null; - - if(listener != null) listener.onLoadFinished(); - super.onPostExecute(null); - } - - }; - - currentFetchTask.execute(); - } - - public void updateHabit(final Long id) - { - new BaseTask() - { - @Override - protected void doInBackground() - { - long dateTo = DateUtils.getStartOfDay(DateUtils.getLocalTime()); - long dateFrom = dateTo - (checkmarkCount - 1) * DateUtils.millisecondsInOneDay; - - Habit h = Habit.get(id); - if(h == null) return; - - habits.put(id, h); - scores.put(id, h.scores.getTodayValue()); - checkmarks.put(id, h.checkmarks.getValues(dateFrom, dateTo)); - } - - @Override - protected void onPostExecute(Void aVoid) - { - if(listener != null) listener.onLoadFinished(); - super.onPostExecute(null); - } - }.execute(); - } - - public void onResume() - { - CommandRunner.getInstance().addListener(this); - } - - public void onPause() - { - CommandRunner.getInstance().removeListener(this); - } - - @Override - public void onCommandExecuted(Command command, Long refreshKey) - { - if(refreshKey == null) updateAllHabits(true); - else updateHabit(refreshKey); - } -} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListSelectionCallback.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListSelectionCallback.java deleted file mode 100644 index f292ff978..000000000 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListSelectionCallback.java +++ /dev/null @@ -1,229 +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.ui.habits.list; - -import android.content.DialogInterface; -import android.support.v7.app.AlertDialog; -import android.support.v7.view.ActionMode; -import android.view.Menu; -import android.view.MenuItem; - -import com.android.colorpicker.ColorPickerDialog; -import com.android.colorpicker.ColorPickerSwatch; - -import org.isoron.uhabits.R; -import org.isoron.uhabits.commands.ArchiveHabitsCommand; -import org.isoron.uhabits.commands.ChangeHabitColorCommand; -import org.isoron.uhabits.commands.CommandRunner; -import org.isoron.uhabits.commands.DeleteHabitsCommand; -import org.isoron.uhabits.commands.UnarchiveHabitsCommand; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.ui.BaseActivity; -import org.isoron.uhabits.ui.habits.edit.BaseDialogFragment; -import org.isoron.uhabits.ui.habits.edit.EditHabitDialogFragment; -import org.isoron.uhabits.utils.ColorUtils; - -import java.util.LinkedList; -import java.util.List; - -public class HabitListSelectionCallback implements ActionMode.Callback -{ - private HabitListLoader loader; - private List selectedPositions; - private BaseActivity activity; - private Listener listener; - - public interface Listener - { - void onActionModeDestroyed(ActionMode mode); - } - - public HabitListSelectionCallback(BaseActivity activity, HabitListLoader loader) - { - this.activity = activity; - this.loader = loader; - selectedPositions = new LinkedList<>(); - } - - public void setListener(Listener listener) - { - this.listener = listener; - } - - public void setSelectedPositions(List selectedPositions) - { - this.selectedPositions = selectedPositions; - } - - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) - { - activity.getMenuInflater().inflate(R.menu.list_habits_selection, menu); - updateTitle(mode); - updateActions(menu); - return true; - } - - @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) - { - updateTitle(mode); - updateActions(menu); - return true; - } - - private void updateActions(Menu menu) - { - boolean showEdit = (selectedPositions.size() == 1); - boolean showArchive = true; - boolean showUnarchive = true; - for (int i : selectedPositions) - { - Habit h = loader.habitsList.get(i); - if (h.isArchived()) showArchive = false; - else showUnarchive = false; - } - - 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(showEdit); - itemArchive.setVisible(showArchive); - itemUnarchive.setVisible(showUnarchive); - } - - private void updateTitle(ActionMode mode) - { - mode.setTitle("" + selectedPositions.size()); - } - - @Override - public boolean onActionItemClicked(final ActionMode mode, MenuItem item) - { - final LinkedList selectedHabits = new LinkedList<>(); - for (int i : selectedPositions) - selectedHabits.add(loader.habitsList.get(i)); - - Habit firstHabit = selectedHabits.getFirst(); - - switch (item.getItemId()) - { - case R.id.action_archive_habit: - archiveHabits(selectedHabits); - mode.finish(); - return true; - - case R.id.action_unarchive_habit: - unarchiveHabits(selectedHabits); - mode.finish(); - return true; - - case R.id.action_edit_habit: - { - editHabit(firstHabit); - mode.finish(); - return true; - } - - case R.id.action_color: - { - showColorPicker(mode, selectedHabits, firstHabit); - mode.finish(); - return true; - } - - case R.id.action_delete: - { - deleteHabits(mode, selectedHabits); - mode.finish(); - return true; - } - } - - return false; - } - - private void deleteHabits(final ActionMode mode, final LinkedList selectedHabits) - { - new AlertDialog.Builder(activity).setTitle(R.string.delete_habits) - .setMessage(R.string.delete_habits_message) - .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface dialog, int which) - { - CommandRunner.getInstance() - .execute(new DeleteHabitsCommand(selectedHabits), null); - mode.finish(); - } - }) - .setNegativeButton(android.R.string.no, null) - .show(); - } - - private void showColorPicker(final ActionMode mode, final LinkedList selectedHabits, - Habit firstHabit) - { - int originalAndroidColor = ColorUtils.getColor(activity, firstHabit.color); - - ColorPickerDialog picker = - ColorPickerDialog.newInstance(R.string.color_picker_default_title, - ColorUtils.getPalette(activity), originalAndroidColor, 4, - ColorPickerDialog.SIZE_SMALL); - - picker.setOnColorSelectedListener(new ColorPickerSwatch.OnColorSelectedListener() - { - public void onColorSelected(int androidColor) - { - int paletteColor = ColorUtils.colorToPaletteIndex(activity, androidColor); - CommandRunner.getInstance() - .execute(new ChangeHabitColorCommand(selectedHabits, paletteColor), null); - mode.finish(); - } - }); - picker.show(activity.getSupportFragmentManager(), "picker"); - } - - private void editHabit(Habit habit) - { - BaseDialogFragment - frag = EditHabitDialogFragment.newInstance(habit.getId()); - frag.show(activity.getSupportFragmentManager(), "editHabit"); - } - - private void unarchiveHabits(LinkedList selectedHabits) - { - CommandRunner.getInstance().execute(new UnarchiveHabitsCommand(selectedHabits), null); - } - - private void archiveHabits(LinkedList selectedHabits) - { - CommandRunner.getInstance().execute(new ArchiveHabitsCommand(selectedHabits), null); - } - - @Override - public void onDestroyActionMode(ActionMode mode) - { - if(listener != null) listener.onActionModeDestroyed(mode); - } -} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListView.java deleted file mode 100644 index 606ae3527..000000000 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListView.java +++ /dev/null @@ -1,273 +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.ui.habits.list; - -import android.content.Context; -import android.support.annotation.Nullable; -import android.util.AttributeSet; -import android.view.HapticFeedbackConstants; -import android.view.View; -import android.widget.AdapterView; - -import com.mobeta.android.dslv.DragSortController; -import com.mobeta.android.dslv.DragSortListView; - -import org.isoron.uhabits.R; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.utils.Preferences; - -import java.util.Date; -import java.util.LinkedList; -import java.util.List; - -public class HabitListView extends DragSortListView implements View.OnClickListener, - View.OnLongClickListener, DragSortListView.DropListener, AdapterView.OnItemClickListener, - AdapterView.OnItemLongClickListener, DragSortListView.DragListener, HabitListLoader.Listener -{ - private final HabitListLoader loader; - private final HabitListAdapter adapter; - private final ListHabitsHelper helper; - private final Preferences prefs; - private final List selectedPositions; - - @Nullable - private Listener listener; - private long lastLongClick; - private boolean showArchived; - - public HabitListView(Context context, AttributeSet attrs) - { - super(context, attrs); - - loader = new HabitListLoader(); - adapter = new HabitListAdapter(context, loader); - selectedPositions = new LinkedList<>(); - prefs = Preferences.getInstance(); - helper = new ListHabitsHelper(getContext(), loader); - - adapter.setSelectedPositions(selectedPositions); - adapter.setOnCheckmarkClickListener(this); - adapter.setOnCheckmarkLongClickListener(this); - loader.setListener(this); - loader.setCheckmarkCount(helper.getButtonCount()); - - setAdapter(adapter); - setOnItemClickListener(this); - setOnItemLongClickListener(this); - setDropListener(this); - setDragListener(this); - setFloatViewManager(new HabitsDragSortController()); - setDragEnabled(false); - setLongClickable(true); - } - - public HabitListLoader getLoader() - { - return loader; - } - - public List getSelectedPositions() - { - return selectedPositions; - } - - public void setListener(@Nullable Listener l) - { - this.listener = l; - } - - @Override - public void drop(int from, int to) - { - if(from == to) return; - cancelSelection(); - - loader.reorder(from, to); - adapter.notifyDataSetChanged(); - loader.updateAllHabits(false); - } - - @Override - public void onClick(View v) - { - if (v.getId() != R.id.tvCheck) return; - - if (prefs.isShortToggleEnabled()) toggleCheckmark(v); - else if(listener != null) listener.onInvalidToggle(); - } - - @Override - public boolean onLongClick(View v) - { - lastLongClick = new Date().getTime(); - if (v.getId() != R.id.tvCheck) return true; - if (prefs.isShortToggleEnabled()) return true; - toggleCheckmark(v); - return true; - } - - public void toggleShowArchived() - { - showArchived = !showArchived; - loader.setIncludeArchived(showArchived); - loader.updateAllHabits(true); - } - - private void toggleCheckmark(View v) - { - Long id = helper.getHabitIdFromCheckmarkView(v); - Habit habit = loader.habits.get(id); - if(habit == null) return; - - float x = v.getX() + v.getWidth() / 2.0f + ((View) v.getParent()).getX(); - float y = v.getY() + v.getHeight() / 2.0f + ((View) v.getParent()).getY(); - helper.triggerRipple((View) v.getParent().getParent(), x, y); - - performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - helper.toggleCheckmarkView(v, habit); - - long timestamp = helper.getTimestampFromCheckmarkView(v); - - if(listener != null) listener.onToggleCheckmark(habit, timestamp); - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) - { - if (new Date().getTime() - lastLongClick < 1000) return; - - if(selectedPositions.isEmpty()) - { - Habit habit = loader.habitsList.get(position); - if(listener != null) listener.onHabitClick(habit); - } - else - { - toggleItemSelected(position); - adapter.notifyDataSetChanged(); - } - } - - private void toggleItemSelected(int position) - { - int k = selectedPositions.indexOf(position); - if(k < 0) selectedPositions.add(position); - else selectedPositions.remove(k); - - if(listener != null) - { - if (selectedPositions.isEmpty()) listener.onHabitSelectionFinish(); - else listener.onHabitSelectionChange(); - } - } - - @Override - public boolean onItemLongClick(AdapterView parent, View view, int position, long id) - { - selectHabit(position); - return true; - } - - private void selectHabit(int position) - { - if(!selectedPositions.contains(position)) selectedPositions.add(position); - adapter.notifyDataSetChanged(); - - if(listener != null) - { - if (selectedPositions.size() == 1) listener.onHabitSelectionStart(); - else listener.onHabitSelectionChange(); - } - } - - @Override - public void drag(int from, int to) - { - } - - @Override - public void startDrag(int position) - { - selectHabit(position); - } - - public boolean getShowArchived() - { - return showArchived; - } - - public void cancelSelection() - { - selectedPositions.clear(); - adapter.notifyDataSetChanged(); - setDragEnabled(true); - if(listener != null) listener.onHabitSelectionFinish(); - } - - public void refreshData(Long refreshKey) - { - if (refreshKey == null) loader.updateAllHabits(true); - else loader.updateHabit(refreshKey); - } - - @Override - public void onLoadFinished() - { - adapter.notifyDataSetChanged(); - if(listener != null) listener.onDatasetChanged(); - } - - private class HabitsDragSortController extends DragSortController - { - public HabitsDragSortController() - { - super(HabitListView.this); - setRemoveEnabled(false); - } - - @Override - public View onCreateFloatView(int position) - { - return adapter.getView(position, null, null); - } - - @Override - public void onDestroyFloatView(View floatView) - { - } - } - - public interface Listener - { - void onToggleCheckmark(Habit habit, long timestamp); - - void onHabitClick(Habit habit); - - void onHabitSelectionStart(); - - void onHabitSelectionFinish(); - - void onHabitSelectionChange(); - - void onInvalidToggle(); - - void onDatasetChanged(); - } -} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsActivity.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsActivity.java new file mode 100644 index 000000000..a111df442 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsActivity.java @@ -0,0 +1,46 @@ +/* + * 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.ui.habits.list; + +import android.os.Bundle; + +import org.isoron.uhabits.ui.BaseActivity; +import org.isoron.uhabits.ui.BaseSystem; + +/** + * Activity that allows the user to see and modify the list of habits. + */ +public class ListHabitsActivity extends BaseActivity +{ + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + BaseSystem system = new BaseSystem(this); + ListHabitsScreen screen = new ListHabitsScreen(this); + ListHabitsController controller = + new ListHabitsController(screen, system); + + screen.setController(controller); + + setScreen(screen); + controller.onStartup(); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsController.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsController.java index 25557f553..b5c91775e 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsController.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsController.java @@ -19,18 +19,162 @@ package org.isoron.uhabits.ui.habits.list; +import android.support.annotation.NonNull; + +import org.isoron.uhabits.HabitsApplication; +import org.isoron.uhabits.R; +import org.isoron.uhabits.commands.CommandRunner; +import org.isoron.uhabits.commands.ToggleRepetitionCommand; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.tasks.ExportCSVTask; +import org.isoron.uhabits.tasks.ExportDBTask; +import org.isoron.uhabits.tasks.ImportDataTask; +import org.isoron.uhabits.ui.BaseSystem; +import org.isoron.uhabits.ui.habits.list.controllers.HabitCardListController; +import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.utils.Preferences; + +import java.io.File; +import java.io.IOException; + +import javax.inject.Inject; + public class ListHabitsController + implements ImportDataTask.Listener, HabitCardListController.HabitListener { - public interface Screen + @NonNull + private final ListHabitsScreen screen; + + @NonNull + private final BaseSystem system; + + @Inject + Preferences prefs; + + @Inject + CommandRunner commandRunner; + + public ListHabitsController(@NonNull ListHabitsScreen screen, + @NonNull BaseSystem system) { + this.screen = screen; + this.system = system; + HabitsApplication.getComponent().inject(this); + } + public void onExportCSV() + { + ExportCSVTask task = + new ExportCSVTask(Habit.getAll(true), screen.getProgressBar()); + task.setListener(filename -> { + if (filename != null) screen.showSendFileScreen(filename); + else screen.showMessage(R.string.could_not_export); + }); + task.execute(); } - private Screen screen; + public void onExportDB() + { + ExportDBTask task = new ExportDBTask(screen.getProgressBar()); + task.setListener(filename -> { + if (filename != null) screen.showSendFileScreen(filename); + else screen.showMessage(R.string.could_not_export); + }); + task.execute(); + } - public void setScreen(Screen screen) + @Override + public void onHabitClick(@NonNull Habit h) { - this.screen = screen; + screen.showHabitScreen(h); + } + + @Override + public void onHabitReorder(@NonNull Habit from, @NonNull Habit to) + { + Habit.reorder(from, to); } + public void onImportData(File file) + { + ImportDataTask task = new ImportDataTask(file, screen.getProgressBar()); + task.setListener(this); + task.execute(); + } + + @Override + public void onImportDataFinished(int result) + { + switch (result) + { + case ImportDataTask.SUCCESS: + screen.invalidate(); + 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; + } + } + + @Override + public void onInvalidToggle() + { + screen.showMessage(R.string.long_press_to_toggle); + } + + public void onSendBugReport() + { + try + { + system.dumpBugReportToFile(); + } catch (IOException e) + { + // ignored + } + + try + { + String log = "---------- BUG REPORT BEGINS ----------\n"; + log += system.getBugReport(); + log += "---------- BUG REPORT ENDS ------------\n"; + String to = "dev@loophabits.org"; + String subject = "Bug Report - Loop Habit Tracker"; + screen.showSendEmailScreen(log, to, subject); + } catch (IOException e) + { + e.printStackTrace(); + screen.showMessage(R.string.bug_report_failed); + } + } + + public void onStartup() + { + prefs.initialize(); + prefs.incrementLaunchCount(); + prefs.updateLastAppVersion(); + if (prefs.isFirstRun()) onFirstRun(); + + system.updateWidgets(); + system.scheduleReminders(); + } + + @Override + public void onToggle(@NonNull Habit habit, long timestamp) + { + commandRunner.execute(new ToggleRepetitionCommand(habit, timestamp), + null); + } + + private void onFirstRun() + { + prefs.setFirstRun(false); + prefs.updateLastHint(-1, DateUtils.getStartOfToday()); + screen.showIntroScreen(); + } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsFragment.java deleted file mode 100644 index ce7b6d541..000000000 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsFragment.java +++ /dev/null @@ -1,236 +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.ui.habits.list; - -import android.app.Activity; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v7.view.ActionMode; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.LinearLayout; -import android.widget.ProgressBar; -import android.widget.TextView; - -import org.isoron.uhabits.R; -import org.isoron.uhabits.commands.CommandRunner; -import org.isoron.uhabits.commands.ToggleRepetitionCommand; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.ui.BaseActivity; -import org.isoron.uhabits.ui.HintManager; -import org.isoron.uhabits.ui.habits.edit.BaseDialogFragment; -import org.isoron.uhabits.ui.habits.edit.CreateHabitDialogFragment; -import org.isoron.uhabits.utils.InterfaceUtils; - -import butterknife.BindView; -import butterknife.ButterKnife; -import butterknife.OnClick; - -public class ListHabitsFragment extends Fragment - implements HabitListSelectionCallback.Listener, ListHabitsController.Screen -{ - private ActionMode actionMode; - private HintManager hintManager; - private ListHabitsHelper helper; - private Listener habitClickListener; - private BaseActivity activity; - - @BindView(R.id.listView) HabitListView listView; - @BindView(R.id.llButtonsHeader) LinearLayout llButtonsHeader; - @BindView(R.id.progressBar) ProgressBar progressBar; - @BindView(R.id.llEmpty) View llEmpty; - @BindView(R.id.llHint) View llHint; - @BindView(R.id.tvStarEmpty) TextView tvStarEmpty; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) - { - View view = inflater.inflate(R.layout.list_habits_fragment, container, false); - ButterKnife.bind(this, view); - - helper = new ListHabitsHelper(activity, listView.getLoader()); - hintManager = new HintManager(activity, llHint); - tvStarEmpty.setTypeface(InterfaceUtils.getFontAwesome(activity)); - listView.setListener(new HabitListViewListener()); - setHasOptionsMenu(true); - - return view; - } - - @Override - @SuppressWarnings("deprecation") - public void onAttach(Activity activity) - { - super.onAttach(activity); - this.activity = (BaseActivity) activity; - habitClickListener = (Listener) activity; - } - - @Override - public void onResume() - { - super.onResume(); - listView.getLoader().onResume(); - listView.refreshData(null); - helper.updateEmptyMessage(llEmpty); - helper.updateHeader(llButtonsHeader); - hintManager.showHintIfAppropriate(); - } - - @Override - public void onPause() - { - listView.getLoader().onPause(); - super.onPause(); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) - { - super.onCreateOptionsMenu(menu, inflater); - inflater.inflate(R.menu.list_habits_fragment, menu); - MenuItem showArchivedItem = menu.findItem(R.id.action_show_archived); - showArchivedItem.setChecked(listView.getShowArchived()); - } - - - @Override - public boolean onOptionsItemSelected(MenuItem item) - { - switch (item.getItemId()) - { - case R.id.action_add: - showCreateHabitScreen(); - return true; - - case R.id.action_show_archived: - toggleShowArchived(); - return true; - - default: - return super.onOptionsItemSelected(item); - } - } - - private void toggleShowArchived() - { - listView.toggleShowArchived(); - activity.invalidateOptionsMenu(); - } - - private void showCreateHabitScreen() - { - BaseDialogFragment frag = new CreateHabitDialogFragment(); - frag.show(getFragmentManager(), "editHabit"); - } - - private void startActionMode() - { - HabitListSelectionCallback callback = - new HabitListSelectionCallback(activity, listView.getLoader()); - callback.setSelectedPositions(listView.getSelectedPositions()); - callback.setListener(this); - actionMode = activity.startSupportActionMode(callback); - } - - private void finishActionMode() - { - if(actionMode != null) actionMode.finish(); - } - - @Override - public void onActionModeDestroyed(ActionMode mode) - { - actionMode = null; - listView.cancelSelection(); - } - - @OnClick(R.id.llHint) - public void onClickHint() - { - hintManager.dismissHint(); - } - - public ProgressBar getProgressBar() - { - return progressBar; - } - - public void refresh(Long refreshKey) - { - listView.refreshData(refreshKey); - } - - public interface Listener - { - void onHabitClick(Habit habit); - } - - private class HabitListViewListener implements HabitListView.Listener - { - @Override - public void onToggleCheckmark(Habit habit, long timestamp) - { - CommandRunner.getInstance().execute(new ToggleRepetitionCommand(habit, timestamp), - habit.getId()); - } - - @Override - public void onHabitClick(Habit habit) - { - habitClickListener.onHabitClick(habit); - } - - @Override - public void onHabitSelectionStart() - { - if(actionMode == null) startActionMode(); - } - - @Override - public void onHabitSelectionFinish() - { - finishActionMode(); - } - - @Override - public void onHabitSelectionChange() - { - if(actionMode != null) actionMode.invalidate(); - } - - @Override - public void onInvalidToggle() - { - activity.showMessage(R.string.long_press_to_toggle); - } - - @Override - public void onDatasetChanged() - { - helper.updateEmptyMessage(llEmpty); - } - } -} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsHelper.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsHelper.java deleted file mode 100644 index c8ab3fcac..000000000 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsHelper.java +++ /dev/null @@ -1,307 +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.ui.habits.list; - -import android.content.Context; -import android.content.SharedPreferences; -import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.preference.PreferenceManager; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.LinearLayout; -import android.widget.TextView; - -import org.isoron.uhabits.R; -import org.isoron.uhabits.utils.ColorUtils; -import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.utils.InterfaceUtils; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.Score; -import org.isoron.uhabits.views.RingView; - -import java.util.GregorianCalendar; - -public class ListHabitsHelper -{ - private static final int CHECKMARK_LEFT_TO_RIGHT = 0; - private static final int CHECKMARK_RIGHT_TO_LEFT = 1; - - private final int lowContrastColor; - private final int mediumContrastColor; - - private final Context context; - private final HabitListLoader loader; - - public ListHabitsHelper(Context context, HabitListLoader loader) - { - this.context = context; - this.loader = loader; - - lowContrastColor = InterfaceUtils.getStyledColor(context, R.attr.lowContrastTextColor); - mediumContrastColor = InterfaceUtils.getStyledColor(context, R.attr.mediumContrastTextColor); - } - - public int getButtonCount() - { - float screenWidth = InterfaceUtils.getScreenWidth(context); - float labelWidth = context.getResources().getDimension(R.dimen.habitNameWidth); - float buttonWidth = context.getResources().getDimension(R.dimen.checkmarkWidth); - return Math.max(0, (int) ((screenWidth - labelWidth) / buttonWidth)); - } - - public int getHabitNameWidth() - { - float screenWidth = InterfaceUtils.getScreenWidth(context); - float buttonWidth = context.getResources().getDimension(R.dimen.checkmarkWidth); - float padding = InterfaceUtils.dpToPixels(context, 15); - return (int) (screenWidth - padding - getButtonCount() * buttonWidth); - } - - public void updateCheckmarkButtons(Habit habit, LinearLayout llButtons) - { - int activeColor = getActiveColor(habit); - int m = llButtons.getChildCount(); - Long habitId = habit.getId(); - - int isChecked[] = loader.checkmarks.get(habitId); - - for (int i = 0; i < m; i++) - { - int position = i; - - if(getCheckmarkOrder() == CHECKMARK_RIGHT_TO_LEFT) - position = m - i - 1; - - TextView tvCheck = (TextView) llButtons.getChildAt(position); - tvCheck.setTag(R.string.habit_key, habitId); - tvCheck.setTag(R.string.offset_key, i); - if(isChecked.length > i) - updateCheckmark(activeColor, tvCheck, isChecked[i]); - } - } - - public int getActiveColor(Habit habit) - { - int activeColor = ColorUtils.getColor(context, habit.color); - if(habit.isArchived()) activeColor = mediumContrastColor; - - return activeColor; - } - - public void initializeLabelAndIcon(View itemView) - { - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(getHabitNameWidth(), - LinearLayout.LayoutParams.WRAP_CONTENT, 1); - itemView.findViewById(R.id.label).setLayoutParams(params); - } - - public void updateNameAndIcon(Habit habit, RingView ring, TextView tvName) - { - int activeColor = getActiveColor(habit); - - tvName.setText(habit.name); - tvName.setTextColor(activeColor); - - int score = loader.scores.get(habit.getId()); - float percentage = (float) score / Score.MAX_VALUE; - - ring.setColor(activeColor); - ring.setPercentage(percentage); - ring.setPrecision(1.0f / 16); - } - - public void updateCheckmark(int activeColor, TextView tvCheck, int check) - { - switch (check) - { - case 2: - tvCheck.setText(R.string.fa_check); - tvCheck.setTextColor(activeColor); - tvCheck.setTag(R.string.toggle_key, 2); - break; - - case 1: - tvCheck.setText(R.string.fa_check); - tvCheck.setTextColor(lowContrastColor); - tvCheck.setTag(R.string.toggle_key, 1); - break; - - case 0: - tvCheck.setText(R.string.fa_times); - tvCheck.setTextColor(lowContrastColor); - tvCheck.setTag(R.string.toggle_key, 0); - break; - } - } - - public View inflateHabitCard(LayoutInflater inflater, - View.OnLongClickListener onCheckmarkLongClickListener, - View.OnClickListener onCheckmarkClickListener) - { - View view = inflater.inflate(R.layout.list_habits_item, null); - initializeLabelAndIcon(view); - inflateCheckmarkButtons(view, onCheckmarkLongClickListener, onCheckmarkClickListener, - inflater); - return view; - } - - public void updateHabitCard(View view, Habit habit, boolean selected) - { - RingView scoreRing = ((RingView) view.findViewById(R.id.scoreRing)); - TextView tvName = (TextView) view.findViewById(R.id.label); - LinearLayout llInner = (LinearLayout) view.findViewById(R.id.llInner); - LinearLayout llButtons = (LinearLayout) view.findViewById(R.id.llButtons); - - llInner.setTag(R.string.habit_key, habit.getId()); - llInner.setOnTouchListener(new HotspotTouchListener()); - - updateNameAndIcon(habit, scoreRing, tvName); - updateCheckmarkButtons(habit, llButtons); - updateHabitCardBackground(llInner, selected); - } - - - public void updateHabitCardBackground(View view, boolean isSelected) - { - if (android.os.Build.VERSION.SDK_INT >= 21) - { - if (isSelected) - view.setBackgroundResource(R.drawable.selected_box); - else - view.setBackgroundResource(R.drawable.ripple); - } - else - { - Drawable background; - - if (isSelected) - background = InterfaceUtils.getStyledDrawable(context, R.attr.selectedBackground); - else - background = InterfaceUtils.getStyledDrawable(context, R.attr.cardBackground); - - view.setBackgroundDrawable(background); - } - } - - public void inflateCheckmarkButtons(View view, View.OnLongClickListener onLongClickListener, - View.OnClickListener onClickListener, LayoutInflater inflater) - { - for (int i = 0; i < getButtonCount(); i++) - { - View check = inflater.inflate(R.layout.list_habits_item_check, null); - TextView btCheck = (TextView) check.findViewById(R.id.tvCheck); - btCheck.setTypeface(InterfaceUtils.getFontAwesome(context)); - btCheck.setOnLongClickListener(onLongClickListener); - btCheck.setOnClickListener(onClickListener); - btCheck.setHapticFeedbackEnabled(false); - ((LinearLayout) view.findViewById(R.id.llButtons)).addView(check); - } - - view.setTag(R.id.timestamp_key, DateUtils.getStartOfToday()); - } - - public void updateHeader(ViewGroup header) - { - LayoutInflater inflater = LayoutInflater.from(context); - GregorianCalendar day = DateUtils.getStartOfTodayCalendar(); - header.removeAllViews(); - - for (int i = 0; i < getButtonCount(); i++) - { - int position = 0; - - if(getCheckmarkOrder() == CHECKMARK_LEFT_TO_RIGHT) - position = i; - - View tvDay = inflater.inflate(R.layout.list_habits_header_check, null); - TextView btCheck = (TextView) tvDay.findViewById(R.id.tvCheck); - btCheck.setText(DateUtils.formatHeaderDate(day)); - header.addView(tvDay, position); - day.add(GregorianCalendar.DAY_OF_MONTH, -1); - } - } - - public void updateEmptyMessage(View view) - { - if (loader.getLastLoadTimestamp() == null) view.setVisibility(View.GONE); - else view.setVisibility(loader.habits.size() > 0 ? View.GONE : View.VISIBLE); - } - - public void toggleCheckmarkView(View v, Habit habit) - { - int androidColor = ColorUtils.getColor(context, habit.color); - - if (v.getTag(R.string.toggle_key).equals(2)) - updateCheckmark(androidColor, (TextView) v, 0); - else - updateCheckmark(androidColor, (TextView) v, 2); - } - - public Long getHabitIdFromCheckmarkView(View v) - { - return (Long) v.getTag(R.string.habit_key); - } - - public long getTimestampFromCheckmarkView(View v) - { - Integer offset = (Integer) v.getTag(R.string.offset_key); - return DateUtils.getStartOfDay(DateUtils.getLocalTime() - - offset * DateUtils.millisecondsInOneDay); - } - - public void triggerRipple(View v, final float x, final float y) - { - final Drawable background = v.getBackground(); - if (android.os.Build.VERSION.SDK_INT >= 21) - background.setHotspot(x, y); - - background.setState(new int[]{android.R.attr.state_pressed, android.R.attr.state_enabled}); - - new Handler().postDelayed(new Runnable() - { - @Override - public void run() - { - background.setState(new int[]{}); - } - }, 25); - } - - private static class HotspotTouchListener implements View.OnTouchListener - { - @Override - public boolean onTouch(View v, MotionEvent event) - { - if (android.os.Build.VERSION.SDK_INT >= 21) - v.getBackground().setHotspot(event.getX(), event.getY()); - return false; - } - } - - public int getCheckmarkOrder() - { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - boolean reverse = prefs.getBoolean("pref_checkmark_reverse_order", false); - return reverse ? CHECKMARK_RIGHT_TO_LEFT : CHECKMARK_LEFT_TO_RIGHT; - } -} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsMenu.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsMenu.java new file mode 100644 index 000000000..8349499f9 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsMenu.java @@ -0,0 +1,96 @@ +/* + * 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.ui.habits.list; + +import android.support.annotation.NonNull; +import android.view.Menu; +import android.view.MenuItem; + +import org.isoron.uhabits.R; +import org.isoron.uhabits.ui.BaseActivity; +import org.isoron.uhabits.ui.BaseMenu; +import org.isoron.uhabits.utils.InterfaceUtils; + +public class ListHabitsMenu extends BaseMenu +{ + @NonNull + private final ListHabitsScreen screen; + + private boolean showArchived; + + public ListHabitsMenu(@NonNull BaseActivity activity, + @NonNull ListHabitsScreen screen) + { + super(activity); + this.screen = screen; + } + + @Override + public void onCreate(@NonNull Menu menu) + { + MenuItem nightModeItem = menu.findItem(R.id.action_night_mode); + nightModeItem.setChecked(InterfaceUtils.isNightMode()); + + MenuItem showArchivedItem = menu.findItem(R.id.action_show_archived); + showArchivedItem.setChecked(showArchived); + } + + @Override + public boolean onItemSelected(@NonNull MenuItem item) + { + switch (item.getItemId()) + { + case R.id.action_night_mode: + screen.toggleNightMode(); + return true; + + case R.id.action_add: + screen.showCreateHabitScreen(); + return true; + + case R.id.action_faq: + screen.showFAQScreen(); + return true; + + case R.id.action_about: + screen.showAboutScreen(); + return true; + + case R.id.action_settings: + screen.showSettingsScreen(); + return true; + + case R.id.action_show_archived: + showArchived = !showArchived; + screen.getRootView().setShowArchived(showArchived); + invalidate(); + return true; + + default: + return false; + } + } + + @Override + protected int getMenuResourceId() + { + return R.menu.main_activity; + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsRootView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsRootView.java new file mode 100644 index 000000000..83c634122 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsRootView.java @@ -0,0 +1,173 @@ +/* + * 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.ui.habits.list; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.Toolbar; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ProgressBar; +import android.widget.TextView; + +import org.isoron.uhabits.R; +import org.isoron.uhabits.models.ModelObservable; +import org.isoron.uhabits.ui.BaseRootView; +import org.isoron.uhabits.ui.habits.list.controllers.HabitCardListController; +import org.isoron.uhabits.ui.habits.list.model.HabitCardListAdapter; +import org.isoron.uhabits.ui.habits.list.model.HintList; +import org.isoron.uhabits.ui.habits.list.views.HabitCardListView; +import org.isoron.uhabits.ui.habits.list.views.HintView; +import org.isoron.uhabits.utils.InterfaceUtils; + +import butterknife.BindView; +import butterknife.ButterKnife; + +public class ListHabitsRootView extends BaseRootView + implements ModelObservable.Listener +{ + @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; + + @Nullable + private HabitCardListAdapter listAdapter; + + public ListHabitsRootView(@NonNull Context context) + { + super(context); + init(); + } + + @Override + @NonNull + public ProgressBar getProgressBar() + { + return progressBar; + } + + public boolean getShowArchived() + { + if(listAdapter == null) return false; + return listAdapter.getIncludeArchived(); + } + + @NonNull + @Override + public Toolbar getToolbar() + { + return toolbar; + } + + @Override + public int getToolbarColor() + { + return InterfaceUtils.getStyledColor(getContext(), R.attr.colorPrimary); + } + + @Override + public void onModelChange() + { + updateEmptyView(); + } + + public void setShowArchived(boolean showArchived) + { + if(listAdapter == null) return; + listAdapter.setShowArchived(showArchived); + } + + private void updateEmptyView() + { + if (listAdapter == null) return; + llEmpty.setVisibility( + listAdapter.getCount() > 0 ? View.GONE : View.VISIBLE); + } + + public void setController(@Nullable ListHabitsController controller, + @Nullable ListHabitsSelectionMenu menu) + { + listView.setController(null); + if (controller == null || listAdapter == null) return; + + HabitCardListController listController = + new HabitCardListController(listAdapter, listView); + listController.setHabitListener(controller); + listController.setSelectionListener(menu); + listView.setController(listController); + } + + public void setListAdapter(@NonNull HabitCardListAdapter listAdapter) + { + if (this.listAdapter != null) + listAdapter.getObservable().removeListener(this); + + this.listAdapter = listAdapter; + listView.setAdapter(listAdapter); + listAdapter.setListView(listView); + } + + private void init() + { + addView(inflate(getContext(), R.layout.list_habits, null)); + ButterKnife.bind(this); + + tvStarEmpty.setTypeface(InterfaceUtils.getFontAwesome(getContext())); + initToolbar(); + + String hints[] = + getContext().getResources().getStringArray(R.array.hints); + HintList hintList = new HintList(hints); + hintView.setHints(hintList); + } + + @Override + protected void onAttachedToWindow() + { + super.onAttachedToWindow(); + + updateEmptyView(); + + if (listAdapter != null) listAdapter.getObservable().addListener(this); + } + + @Override + protected void onDetachedFromWindow() + { + if (listAdapter != null) + listAdapter.getObservable().removeListener(this); + super.onDetachedFromWindow(); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsScreen.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsScreen.java new file mode 100644 index 000000000..760233e37 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsScreen.java @@ -0,0 +1,236 @@ +/* + * 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.ui.habits.list; + +import android.content.Intent; +import android.net.Uri; +import android.os.Handler; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.AlertDialog; + +import com.android.colorpicker.ColorPickerDialog; + +import org.isoron.uhabits.HabitsApplication; +import org.isoron.uhabits.MainActivity; +import org.isoron.uhabits.R; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.ui.BaseActivity; +import org.isoron.uhabits.ui.BaseScreen; +import org.isoron.uhabits.ui.about.AboutActivity; +import org.isoron.uhabits.ui.habits.edit.BaseDialogFragment; +import org.isoron.uhabits.ui.habits.edit.CreateHabitDialogFragment; +import org.isoron.uhabits.ui.habits.edit.EditHabitDialogFragment; +import org.isoron.uhabits.ui.habits.list.model.HabitCardListAdapter; +import org.isoron.uhabits.ui.habits.show.ShowHabitActivity; +import org.isoron.uhabits.ui.intro.IntroActivity; +import org.isoron.uhabits.ui.settings.FilePickerDialog; +import org.isoron.uhabits.ui.settings.SettingsActivity; +import org.isoron.uhabits.utils.ColorUtils; +import org.isoron.uhabits.utils.FileUtils; +import org.isoron.uhabits.utils.InterfaceUtils; + +import java.io.File; + +public class ListHabitsScreen extends BaseScreen +{ + @Nullable + ListHabitsController controller; + + @NonNull + private final ListHabitsRootView rootView; + + @NonNull + private final ListHabitsSelectionMenu selectionMenu; + + public ListHabitsScreen(@NonNull BaseActivity activity) + { + super(activity); + rootView = new ListHabitsRootView(activity); + setRootView(rootView); + + ListHabitsMenu menu = new ListHabitsMenu(activity, this); + selectionMenu = new ListHabitsSelectionMenu(this); + setMenu(menu); + setSelectionMenu(selectionMenu); + + HabitCardListAdapter adapter = new HabitCardListAdapter(); + rootView.setListAdapter(adapter); + selectionMenu.setAdapter(adapter); + } + + @Override + public void onResult(int requestCode, int resultCode, Intent data) + { + if (controller == null) return; + + switch (resultCode) + { + case HabitsApplication.RESULT_IMPORT_DATA: + showImportScreen(); + break; + + case HabitsApplication.RESULT_EXPORT_CSV: + controller.onExportCSV(); + break; + + case HabitsApplication.RESULT_EXPORT_DB: + controller.onExportDB(); + break; + + case HabitsApplication.RESULT_BUG_REPORT: + controller.onSendBugReport(); + break; + } + } + + public void setController(@Nullable ListHabitsController controller) + { + this.controller = controller; + rootView.setController(controller, selectionMenu); + } + + public void showAboutScreen() + { + Intent intent = new Intent(activity, AboutActivity.class); + activity.startActivity(intent); + } + + public void showColorPicker(Habit habit, OnColorSelectedListener callback) + { + int color = ColorUtils.getColor(activity, habit.color); + + ColorPickerDialog picker = + ColorPickerDialog.newInstance(R.string.color_picker_default_title, + ColorUtils.getPalette(activity), color, 4, + ColorPickerDialog.SIZE_SMALL); + + picker.setOnColorSelectedListener(c -> { + c = ColorUtils.colorToPaletteIndex(activity, c); + callback.onColorSelected(c); + }); + picker.show(activity.getSupportFragmentManager(), "picker"); + } + + public void showCreateHabitScreen() + { + showDialog(new CreateHabitDialogFragment(), "editHabit"); + } + + public void showDeleteConfirmationScreen(Callback callback) + { + new AlertDialog.Builder(activity) + .setTitle(R.string.delete_habits) + .setMessage(R.string.delete_habits_message) + .setPositiveButton(android.R.string.yes, + (dialog, which) -> callback.run()) + .setNegativeButton(android.R.string.no, null) + .show(); + } + + public void showEditHabitScreen(Habit habit) + { + BaseDialogFragment frag = + EditHabitDialogFragment.newInstance(habit.getId()); + frag.show(activity.getSupportFragmentManager(), "editHabit"); + } + + public void showFAQScreen() + { + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_VIEW); + intent.setData(Uri.parse(activity.getString(R.string.helpURL))); + activity.startActivity(intent); + } + + public void showHabitScreen(@NonNull Habit habit) + { + Intent intent = new Intent(activity, ShowHabitActivity.class); + intent.setData( + Uri.parse("content://org.isoron.uhabits/habit/" + habit.getId())); + activity.startActivity(intent); + } + + public void showImportScreen() + { + if (controller == null) return; + + File dir = FileUtils.getFilesDir(null); + if (dir == null) + { + showMessage(R.string.could_not_import); + return; + } + + FilePickerDialog picker = new FilePickerDialog(activity, dir); + picker.setListener(file -> controller.onImportData(file)); + picker.show(); + } + + public void showIntroScreen() + { + Intent intent = new Intent(activity, IntroActivity.class); + activity.startActivity(intent); + } + + public void showSettingsScreen() + { + Intent intent = new Intent(activity, SettingsActivity.class); + activity.startActivityForResult(intent, 0); + } + + public void toggleNightMode() + { + if (InterfaceUtils.isNightMode()) + InterfaceUtils.setCurrentTheme(InterfaceUtils.THEME_LIGHT); + else InterfaceUtils.setCurrentTheme(InterfaceUtils.THEME_DARK); + + refreshTheme(); + } + + private void refreshTheme() + { + new Handler().postDelayed(() -> { + Intent intent = new Intent(activity, MainActivity.class); + + activity.finish(); + activity.overridePendingTransition(android.R.anim.fade_in, + android.R.anim.fade_out); + activity.startActivity(intent); + + }, 500); // HACK: Let the menu disappear first + } + + interface Callback + { + void run(); + } + + public interface OnColorSelectedListener + { + void onColorSelected(int color); + } + + @NonNull + public ListHabitsRootView getRootView() + { + return rootView; + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsSelectionMenu.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsSelectionMenu.java new file mode 100644 index 000000000..f38c2d21c --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsSelectionMenu.java @@ -0,0 +1,200 @@ +/* + * 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.ui.habits.list; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.Menu; +import android.view.MenuItem; + +import org.isoron.uhabits.HabitsApplication; +import org.isoron.uhabits.R; +import org.isoron.uhabits.commands.ArchiveHabitsCommand; +import org.isoron.uhabits.commands.ChangeHabitColorCommand; +import org.isoron.uhabits.commands.CommandRunner; +import org.isoron.uhabits.commands.DeleteHabitsCommand; +import org.isoron.uhabits.commands.UnarchiveHabitsCommand; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.ui.BaseSelectionMenu; +import org.isoron.uhabits.ui.habits.list.controllers.HabitCardListController; +import org.isoron.uhabits.ui.habits.list.model.HabitCardListAdapter; + +import java.util.List; + +import javax.inject.Inject; + +public class ListHabitsSelectionMenu extends BaseSelectionMenu + implements HabitCardListController.SelectionListener +{ + @NonNull + private final ListHabitsScreen screen; + + @Inject + CommandRunner commandRunner; + + @Nullable + private HabitCardListAdapter adapter; + + public ListHabitsSelectionMenu(@NonNull ListHabitsScreen screen) + { + this.screen = screen; + HabitsApplication.getComponent().inject(this); + } + + @Override + public void onDestroy() + { + if (adapter != null) adapter.clearSelection(); + super.onDestroy(); + } + + @Override + public boolean onItemClicked(@NonNull MenuItem item) + { + if (adapter == null) return false; + + List selected = adapter.getSelected(); + if (selected.isEmpty()) return false; + + Habit firstHabit = selected.get(0); + + switch (item.getItemId()) + { + case R.id.action_edit_habit: + edit(firstHabit); + finish(); + return true; + + case R.id.action_archive_habit: + archive(selected); + finish(); + return true; + + case R.id.action_unarchive_habit: + unarchive(selected); + finish(); + return true; + + case R.id.action_delete: + delete(selected); + return true; + + case R.id.action_color: + showColorPicker(selected, firstHabit); + return true; + + default: + return false; + } + } + + @Override + public boolean onPrepare(@NonNull Menu menu) + { + if (adapter == null) return false; + List selected = adapter.getSelected(); + + boolean showEdit = (selected.size() == 1); + boolean showArchive = true; + boolean showUnarchive = true; + for (Habit h : selected) + { + if (h.isArchived()) showArchive = false; + else showUnarchive = false; + } + + 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(showEdit); + itemArchive.setVisible(showArchive); + itemUnarchive.setVisible(showUnarchive); + + setTitle(Integer.toString(selected.size())); + + return true; + } + + @Override + public void onSelectionChange() + { + invalidate(); + } + + @Override + public void onSelectionFinish() + { + finish(); + } + + @Override + public void onSelectionStart() + { + screen.startSelection(); + } + + public void setAdapter(@Nullable HabitCardListAdapter adapter) + { + if (adapter == null) return; + this.adapter = adapter; + } + + private void archive(@NonNull List selected) + { + commandRunner.execute(new ArchiveHabitsCommand(selected), null); + } + + private void delete(@NonNull List selected) + { + screen.showDeleteConfirmationScreen(() -> { + commandRunner.execute(new DeleteHabitsCommand(selected), null); + finish(); + }); + } + + private void edit(@NonNull Habit firstHabit) + { + screen.showEditHabitScreen(firstHabit); + } + + @Override + protected int getResourceId() + { + return R.menu.list_habits_selection; + } + + private void showColorPicker(@NonNull List selected, + @NonNull Habit firstHabit) + { + screen.showColorPicker(firstHabit, color -> { + commandRunner.execute(new ChangeHabitColorCommand(selected, color), + null); + finish(); + }); + } + + private void unarchive(@NonNull List selected) + { + commandRunner.execute(new UnarchiveHabitsCommand(selected), null); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/CheckmarkButtonController.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/CheckmarkButtonController.java new file mode 100644 index 000000000..234d87f94 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/CheckmarkButtonController.java @@ -0,0 +1,98 @@ +/* + * 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.ui.habits.list.controllers; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.isoron.uhabits.HabitsApplication; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.ui.habits.list.views.CheckmarkButtonView; +import org.isoron.uhabits.utils.Preferences; + +import javax.inject.Inject; + +public class CheckmarkButtonController +{ + @Nullable + private CheckmarkButtonView view; + + @Nullable + private Listener listener; + + @Inject + Preferences prefs; + + @NonNull + private Habit habit; + + private long timestamp; + + public CheckmarkButtonController(@NonNull Habit habit, long timestamp) + { + this.habit = habit; + this.timestamp = timestamp; + HabitsApplication.getComponent().inject(this); + } + + public void onClick() + { + if (prefs.isShortToggleEnabled()) performToggle(); + else performInvalidToggle(); + } + + public boolean onLongClick() + { + performToggle(); + return true; + } + + public void performInvalidToggle() + { + if (listener != null) listener.onInvalidToggle(); + } + + public void performToggle() + { + if (view != null) view.toggle(); + if (listener != null) listener.onToggle(habit, timestamp); + } + + public void setListener(@Nullable Listener listener) + { + this.listener = listener; + } + + public void setView(@Nullable CheckmarkButtonView view) + { + this.view = view; + } + + public interface Listener + { + /** + * Called when the user's attempt to perform a toggle is rejected. + */ + void onInvalidToggle(); + + + void onToggle(Habit habit, long timestamp); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/HabitCardController.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/HabitCardController.java new file mode 100644 index 000000000..bdc65acf5 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/HabitCardController.java @@ -0,0 +1,61 @@ +/* + * 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.ui.habits.list.controllers; + +import android.support.annotation.Nullable; + +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.ui.habits.list.views.HabitCardView; + +public class HabitCardController implements HabitCardView.Controller +{ + @Nullable + private HabitCardView view; + + @Nullable + private Listener listener; + + @Override + public void onInvalidToggle() + { + if (listener != null) listener.onInvalidToggle(); + } + + @Override + public void onToggle(Habit habit, long timestamp) + { + if (view != null) view.triggerRipple(0, 0); + if (listener != null) listener.onToggle(habit, timestamp); + } + + public void setListener(@Nullable Listener listener) + { + this.listener = listener; + } + + public void setView(@Nullable HabitCardView view) + { + this.view = view; + } + + public interface Listener extends CheckmarkButtonController.Listener + { + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/HabitCardListController.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/HabitCardListController.java new file mode 100644 index 000000000..2cfb03c98 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/HabitCardListController.java @@ -0,0 +1,317 @@ +/* + * 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.ui.habits.list.controllers; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.mobeta.android.dslv.DragSortListView; + +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.ui.habits.list.model.HabitCardListAdapter; +import org.isoron.uhabits.ui.habits.list.views.HabitCardListView; + +/** + * Controller responsible for receiving and processing the events generated by a + * HabitListView. These include selecting and reordering items, toggling + * checkmarks and clicking habits. + */ +public class HabitCardListController implements DragSortListView.DropListener, + DragSortListView.DragListener, + HabitCardListView.Controller +{ + private final Mode NORMAL_MODE = new NormalMode(); + + private final Mode SELECTION_MODE = new SelectionMode(); + + @NonNull + private final HabitCardListAdapter adapter; + + @NonNull + private final HabitCardListView view; + + @Nullable + private HabitListener habitListener; + + @Nullable + private SelectionListener selectionListener; + + @NonNull + private Mode activeMode; + + public HabitCardListController(@NonNull HabitCardListAdapter adapter, + @NonNull HabitCardListView view) + { + this.adapter = adapter; + this.view = view; + this.activeMode = new NormalMode(); + } + + /** + * Called when the user is dragging a habit which was originally at position + * 'from' and is currently hovering over position 'to'. Note that the user + * has not yet finished the dragging operation. + * + * @param from the original position of the habit + * @param to the position where the habit is currently hovering + */ + @Override + public void drag(int from, int to) + { + // ignored + } + + /** + * 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.reorder(from, to); + + if (habitListener != null) + habitListener.onHabitReorder(habitFrom, habitTo); + } + + /** + * 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); + } + + /** + * 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(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); + } + + /** + * Marks all items as not selected and finishes the selection operation. + */ + private void cancelSelection() + { + adapter.clearSelection(); + view.setDragEnabled(true); + activeMode = new NormalMode(); + + if (selectionListener != null) selectionListener.onSelectionFinish(); + } + + /** + * 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; + } + + public interface HabitListener extends CheckmarkButtonController.Listener + { + /** + * Called when the user clicks a habit. + * + * @param habit the habit clicked + */ + void onHabitClick(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(Habit from, Habit to); + } + + /** + * 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 (habitListener == null) return; + + if (activeMode == SELECTION_MODE) + selectionListener.onSelectionChange(); + else + selectionListener.onSelectionFinish(); + } + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/package-info.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/package-info.java new file mode 100644 index 000000000..d0650ac2d --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/package-info.java @@ -0,0 +1,23 @@ +/* + * 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 controllers that are specific for ListHabitsActivity + */ +package org.isoron.uhabits.ui.habits.list.controllers; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListAdapter.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListAdapter.java new file mode 100644 index 000000000..6aab91a27 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListAdapter.java @@ -0,0 +1,222 @@ +/* + * 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.ui.habits.list.model; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; + +import org.isoron.uhabits.HabitsApplication; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.ModelObservable; +import org.isoron.uhabits.ui.habits.list.views.HabitCardListView; +import org.isoron.uhabits.ui.habits.list.views.HabitCardView; + +import java.util.LinkedList; +import java.util.List; + +import javax.inject.Inject; + +/** + * Provides data that backs a {@link HabitCardListView}. The data if fetched and + * cached by a {@link HabitCardListCache}. This adapter also holds a list of + * items that have been selected. + */ +public class HabitCardListAdapter extends BaseAdapter + implements HabitCardListCache.Listener +{ + @NonNull + private ModelObservable observable; + + @Inject + @NonNull + HabitCardListCache cache; + + @Nullable + private HabitCardListView listView; + + @NonNull + private final LinkedList selected; + + public HabitCardListAdapter() + { + this.selected = new LinkedList<>(); + this.observable = new ModelObservable(); + + HabitsApplication.getComponent().inject(this); + + cache.setListener(this); + cache.setCheckmarkCount(5); // TODO: make this dynamic somehow + } + + /** + * Sets all items as not selected. + */ + public void clearSelection() + { + selected.clear(); + notifyDataSetChanged(); + } + + @Override + public int getCount() + { + return cache.getHabitCount(); + } + + public boolean getIncludeArchived() + { + return cache.getIncludeArchived(); + } + + /** + * Returns the item that occupies a certain position on the list + * + * @param position position of the item + * @return the item at given position + * @throws IndexOutOfBoundsException if position is not valid + */ + @Override + @NonNull + public Habit getItem(int position) + { + return cache.getHabitByPosition(position); + } + + @Override + public long getItemId(int position) + { + return getItem(position).getId(); + } + + @NonNull + public ModelObservable getObservable() + { + return observable; + } + + @NonNull + public List getSelected() + { + return new LinkedList<>(selected); + } + + @Override + public View getView(int position, + @Nullable View view, + @Nullable ViewGroup parent) + { + if (listView == null) return null; + + Habit habit = cache.getHabitByPosition(position); + int score = cache.getScore(habit.getId()); + int checkmarks[] = cache.getCheckmarks(habit.getId()); + boolean selected = this.selected.contains(habit); + + return listView.buildCardView((HabitCardView) view, habit, score, + checkmarks, selected); + } + + /** + * Returns whether list of selected items is empty. + * + * @return true if selection is empty, false otherwise + */ + public boolean isSelectionEmpty() + { + return selected.isEmpty(); + } + + /** + * Notify the adapter that it has been attached to a ListView. + */ + public void onAttached() + { + cache.onAttached(); + } + + @Override + public void onCacheRefresh() + { + notifyDataSetChanged(); + observable.notifyListeners(); + } + + /** + * Notify the adapter that it has been detached from a ListView. + */ + public void onDetached() + { + cache.onDetached(); + } + + /** + * Changes the order of habits on the adapter. + *

+ * Note that this only has effect on the adapter cache. The database is not + * modified, and the change is lost when the cache is refreshed. This method + * is useful for making the ListView more responsive: while we wait for the + * database operation to finish, the cache can be modified to reflect the + * changes immediately. + * + * @param from the habit that should be moved + * @param to the habit that currently occupies the desired position + */ + public void reorder(int from, int to) + { + cache.reorder(from, to); + cache.refreshAllHabits(false); + notifyDataSetChanged(); + } + + /** + * Sets the HabitCardListView that this adapter will provide data for. + *

+ * This object will be used to generated new HabitCardViews, upon demand. + * + * @param listView the HabitCardListView associated with this adapter + */ + public void setListView(@Nullable HabitCardListView listView) + { + this.listView = listView; + } + + /** + * Selects or deselects the item at a given position. + * + * @param position position of the item to be toggled + */ + public void toggleSelection(int position) + { + Habit h = getItem(position); + int k = selected.indexOf(h); + if (k < 0) selected.add(h); + else selected.remove(h); + notifyDataSetChanged(); + } + + public void setShowArchived(boolean showArchived) + { + cache.setIncludeArchived(showArchived); + cache.refreshAllHabits(true); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListCache.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListCache.java new file mode 100644 index 000000000..0fb5711b1 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListCache.java @@ -0,0 +1,334 @@ +/* + * 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.ui.habits.list.model; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.isoron.uhabits.HabitsApplication; +import org.isoron.uhabits.commands.Command; +import org.isoron.uhabits.commands.CommandRunner; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.tasks.BaseTask; +import org.isoron.uhabits.utils.DateUtils; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; + +import javax.inject.Inject; + +/** + * A HabitCardListCache fetches and keeps a cache of all the data necessary to + * render a HabitCardListView. + *

+ * This is needed since performing database lookups during scrolling can make + * the ListView very slow. It also registers itself as an observer of the + * models, in order to update itself automatically. + */ +public class HabitCardListCache implements CommandRunner.Listener +{ + boolean includeArchived; + + private int checkmarkCount; + + private BaseTask currentFetchTask; + + @Nullable + private Listener listener; + + @Nullable + private Long lastLoadTimestamp; + + @NonNull + private CacheData data; + + @Inject + CommandRunner commandRunner; + + public HabitCardListCache() + { + data = new CacheData(); + HabitsApplication.getComponent().inject(this); + } + + public int[] getCheckmarks(long habitId) + { + return data.checkmarks.get(habitId); + } + + /** + * Returns the habits that occupies a certain position on the list. + * + * @param position the position of the habit + * @return the habit at given position + * @throws IndexOutOfBoundsException if position is not valid + */ + @NonNull + public Habit getHabitByPosition(int position) + { + return data.habitsList.get(position); + } + + public int getHabitCount() + { + return data.habits.size(); + } + + @Nullable + public Long getLastLoadTimestamp() + { + return lastLoadTimestamp; + } + + public int getScore(long habitId) + { + return data.scores.get(habitId); + } + + public boolean getIncludeArchived() + { + return includeArchived; + } + + public void onAttached() + { +// refreshAllHabits(true); + if (lastLoadTimestamp == null) refreshAllHabits(true); + commandRunner.addListener(this); + } + + @Override + public void onCommandExecuted(@NonNull Command command, + @Nullable Long refreshKey) + { + if (refreshKey == null) refreshAllHabits(true); + else refreshHabit(refreshKey); + } + + public void onDetached() + { +// commandRunner.removeListener(this); + } + + public void refreshAllHabits(final boolean refreshScoresAndCheckmarks) + { + if (currentFetchTask != null) currentFetchTask.cancel(true); + currentFetchTask = new RefreshAllHabitsTask(refreshScoresAndCheckmarks); + currentFetchTask.execute(); + } + + public void refreshHabit(final Long id) + { + new RefreshHabitTask(id).execute(); + } + + public void reorder(int from, int to) + { + Habit fromHabit = data.habitsList.get(from); + Habit toHabit = data.habitsList.get(to); + + data.habitsList.remove(from); + data.habitsList.add(to, fromHabit); + + Habit.reorder(fromHabit, toHabit); + } + + public void setCheckmarkCount(int checkmarkCount) + { + this.checkmarkCount = checkmarkCount; + } + + public void setIncludeArchived(boolean includeArchived) + { + this.includeArchived = includeArchived; + } + + public void setListener(@Nullable Listener listener) + { + this.listener = listener; + } + + /** + * Interface definition for a callback to be invoked when the data on the + * cache has been modified. + */ + public interface Listener + { + /** + * Called when the data on the cache has been modified. + */ + void onCacheRefresh(); + } + + private class CacheData + { + @NonNull + public HashMap habits; + + @NonNull + public List habitsList; + + @NonNull + public HashMap checkmarks; + + @NonNull + public HashMap scores; + + /** + * Creates a new CacheData without any content. + */ + public CacheData() + { + habits = new HashMap<>(); + habitsList = new LinkedList<>(); + checkmarks = new HashMap<>(); + scores = new HashMap<>(); + } + + public void copyCheckmarksFrom(@NonNull CacheData oldData) + { + int[] empty = new int[checkmarkCount]; + + for (Long id : habits.keySet()) + { + if (oldData.checkmarks.containsKey(id)) + checkmarks.put(id, oldData.checkmarks.get(id)); + else checkmarks.put(id, empty); + } + } + + public void copyScoresFrom(@NonNull CacheData oldData) + { + for (Long id : habits.keySet()) + { + if (oldData.scores.containsKey(id)) + scores.put(id, oldData.scores.get(id)); + else scores.put(id, 0); + } + } + + public void fetchHabits() + { + habitsList = Habit.getAll(includeArchived); + for (Habit h : habitsList) + habits.put(h.getId(), h); + } + } + + private class RefreshAllHabitsTask extends BaseTask + { + @NonNull + private CacheData newData; + + private final boolean refreshScoresAndCheckmarks; + + public RefreshAllHabitsTask(boolean refreshScoresAndCheckmarks) + { + this.refreshScoresAndCheckmarks = refreshScoresAndCheckmarks; + newData = new CacheData(); + } + + private void commit() + { + data = newData; + } + + @Override + protected void doInBackground() + { + newData.fetchHabits(); + newData.copyScoresFrom(data); + newData.copyCheckmarksFrom(data); + + commit(); + + if (!refreshScoresAndCheckmarks) return; + + long dateTo = DateUtils.getStartOfDay(DateUtils.getLocalTime()); + long dateFrom = + dateTo - (checkmarkCount - 1) * DateUtils.millisecondsInOneDay; + + int current = 0; + for (Habit h : newData.habitsList) + { + if (isCancelled()) return; + + Long id = h.getId(); + newData.scores.put(id, h.scores.getTodayValue()); + newData.checkmarks.put(id, + h.checkmarks.getValues(dateFrom, dateTo)); + + publishProgress(current++, newData.habits.size()); + } + } + + @Override + protected void onPostExecute(Void aVoid) + { + if (isCancelled()) return; + + lastLoadTimestamp = DateUtils.getStartOfToday(); + currentFetchTask = null; + + if (listener != null) listener.onCacheRefresh(); + super.onPostExecute(null); + } + + @Override + protected void onProgressUpdate(Integer... values) + { + if (listener != null) listener.onCacheRefresh(); + } + + } + + private class RefreshHabitTask extends BaseTask + { + private final Long id; + + public RefreshHabitTask(Long id) + { + this.id = id; + } + + @Override + protected void doInBackground() + { + long dateTo = DateUtils.getStartOfDay(DateUtils.getLocalTime()); + long dateFrom = + dateTo - (checkmarkCount - 1) * DateUtils.millisecondsInOneDay; + + Habit h = Habit.get(id); + if (h == null) return; + + data.habits.put(id, h); + data.scores.put(id, h.scores.getTodayValue()); + data.checkmarks.put(id, h.checkmarks.getValues(dateFrom, dateTo)); + } + + @Override + protected void onPostExecute(Void aVoid) + { + if (listener != null) listener.onCacheRefresh(); + super.onPostExecute(null); + } + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HintList.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HintList.java new file mode 100644 index 000000000..096ae3a16 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HintList.java @@ -0,0 +1,81 @@ +/* + * 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.ui.habits.list.model; + +import android.support.annotation.NonNull; + +import org.isoron.uhabits.HabitsApplication; +import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.utils.Preferences; + +import javax.inject.Inject; + +/** + * Provides a list of hints to be shown at the application startup, and takes + * care of deciding when a new hint should be shown. + */ +public class HintList +{ + @Inject + Preferences prefs; + + @NonNull + private final String[] hints; + + /** + * Constructs a new list containing the provided hints. + * + * @param hints initial list of hints + */ + public HintList(@NonNull String hints[]) + { + this.hints = hints; + HabitsApplication.getComponent().inject(this); + } + + /** + * Returns a new hint to be shown to the user. + *

+ * The hint returned is marked as read on the list, and will not be returned + * again. In case all hints have already been read, and there is nothing + * left, returns null. + * + * @return the next hint to be shown, or null if none + */ + public String pop() + { + int next = prefs.getLastHintNumber() + 1; + if (next >= hints.length) return null; + + prefs.updateLastHint(next, DateUtils.getStartOfToday()); + return hints[next]; + } + + /** + * Returns whether it is time to show a new hint or not. + * + * @return true if hint should be shown, false otherwise + */ + public boolean shouldShow() + { + long lastHintTimestamp = prefs.getLastHintTimestamp(); + return (DateUtils.getStartOfToday() > lastHintTimestamp); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/package-info.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/package-info.java new file mode 100644 index 000000000..06a063daf --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/package-info.java @@ -0,0 +1,23 @@ +/* + * 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 model classes that are specific for ListHabitsActivity + */ +package org.isoron.uhabits.ui.habits.list.model; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/package-info.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/package-info.java new file mode 100644 index 000000000..5ae316016 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/package-info.java @@ -0,0 +1,23 @@ +/* + * 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 classes for ListHabitsActivity. + */ +package org.isoron.uhabits.ui.habits.list; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/CheckmarkButtonView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/CheckmarkButtonView.java new file mode 100644 index 000000000..2fcd34406 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/CheckmarkButtonView.java @@ -0,0 +1,116 @@ +/* + * 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.ui.habits.list.views; + +import android.content.Context; +import android.graphics.Canvas; +import android.view.HapticFeedbackConstants; +import android.widget.FrameLayout; +import android.widget.TextView; + +import org.isoron.uhabits.R; +import org.isoron.uhabits.models.Checkmark; +import org.isoron.uhabits.ui.habits.list.controllers.CheckmarkButtonController; +import org.isoron.uhabits.utils.InterfaceUtils; + +import butterknife.BindView; +import butterknife.ButterKnife; + +public class CheckmarkButtonView extends FrameLayout +{ + private int color; + + private int value; + + @BindView(R.id.tvCheck) + TextView tvCheck; + + public CheckmarkButtonView(Context context) + { + super(context); + init(); + } + + public void setColor(int color) + { + this.color = color; + postInvalidate(); + } + + public void setController(final CheckmarkButtonController controller) + { + setOnClickListener(v -> controller.onClick()); + setOnLongClickListener(v -> controller.onLongClick()); + } + + public void setValue(int value) + { + this.value = value; + postInvalidate(); + } + + public void toggle() + { + value = (value == Checkmark.CHECKED_EXPLICITLY ? Checkmark.UNCHECKED : + Checkmark.CHECKED_EXPLICITLY); + + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + postInvalidate(); + } + + private void init() + { + addView( + inflate(getContext(), R.layout.list_habits_card_checkmark, null)); + ButterKnife.bind(this); + + setWillNotDraw(false); + setHapticFeedbackEnabled(false); + + tvCheck.setTypeface(InterfaceUtils.getFontAwesome(getContext())); + } + + @Override + protected void onDraw(Canvas canvas) + { + int lowContrastColor = InterfaceUtils.getStyledColor(getContext(), + R.attr.lowContrastTextColor); + + if (value == Checkmark.CHECKED_EXPLICITLY) + { + tvCheck.setText(R.string.fa_check); + tvCheck.setTextColor(color); + } + + if (value == Checkmark.CHECKED_IMPLICITLY) + { + tvCheck.setText(R.string.fa_check); + tvCheck.setTextColor(lowContrastColor); + } + + if (value == Checkmark.UNCHECKED) + { + tvCheck.setText(R.string.fa_times); + tvCheck.setTextColor(lowContrastColor); + } + + super.onDraw(canvas); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/CheckmarkPanelView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/CheckmarkPanelView.java new file mode 100644 index 000000000..93e22b127 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/CheckmarkPanelView.java @@ -0,0 +1,190 @@ +/* + * 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.ui.habits.list.views; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.util.AttributeSet; +import android.widget.LinearLayout; + +import org.isoron.uhabits.HabitsApplication; +import org.isoron.uhabits.R; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.ui.habits.list.controllers.CheckmarkButtonController; +import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.utils.Preferences; + +import javax.inject.Inject; + +public class CheckmarkPanelView extends LinearLayout +{ + private static final int CHECKMARK_LEFT_TO_RIGHT = 0; + + private static final int CHECKMARK_RIGHT_TO_LEFT = 1; + + @Inject + Preferences prefs; + + private int checkmarkValues[]; + + private int nButtons; + + private int color; + + private Controller controller; + + @NonNull + private Habit habit; + + public CheckmarkPanelView(Context context) + { + super(context); + init(); + } + + public CheckmarkPanelView(Context context, AttributeSet attrs) + { + super(context, attrs); + init(); + } + + public CheckmarkPanelView(Context context, + AttributeSet attrs, + int defStyleAttr) + { + super(context, attrs, defStyleAttr); + init(); + } + + public CheckmarkButtonView getButton(int position) + { + return (CheckmarkButtonView) getChildAt(position); + } + + public void setCheckmarkValues(int[] checkmarkValues) + { + this.checkmarkValues = checkmarkValues; + + if (this.nButtons != checkmarkValues.length) + { + this.nButtons = checkmarkValues.length; + addCheckmarkButtons(); + } + + setupCheckmarkButtons(); + } + + public void setColor(int color) + { + this.color = color; + setupCheckmarkButtons(); + } + + public void setController(Controller controller) + { + this.controller = controller; + } + + public void setHabit(@NonNull Habit habit) + { + this.habit = habit; + } + + private void addCheckmarkButtons() + { + removeAllViews(); + + for (int i = 0; i < nButtons; i++) + addView(new CheckmarkButtonView(getContext())); + } + + private int getCheckmarkOrder() + { + if (isInEditMode()) return CHECKMARK_LEFT_TO_RIGHT; + return prefs.shouldReverseCheckmarks() ? CHECKMARK_RIGHT_TO_LEFT : + CHECKMARK_LEFT_TO_RIGHT; + } + + private CheckmarkButtonView indexToButton(int i) + { + int position = i; + + if (getCheckmarkOrder() == CHECKMARK_RIGHT_TO_LEFT) + position = nButtons - i - 1; + + return (CheckmarkButtonView) getChildAt(position); + } + + private void init() + { + if (isInEditMode()) return; + HabitsApplication.getComponent().inject(this); + setWillNotDraw(false); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) + { + float buttonWidth = getResources().getDimension(R.dimen.checkmarkWidth); + float buttonHeight = + getResources().getDimension(R.dimen.checkmarkHeight); + + float width = buttonWidth * nButtons; + + widthMeasureSpec = + MeasureSpec.makeMeasureSpec((int) width, MeasureSpec.EXACTLY); + heightMeasureSpec = MeasureSpec.makeMeasureSpec((int) buttonHeight, + MeasureSpec.EXACTLY); + + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + private void setupButtonControllers(long timestamp, + CheckmarkButtonView buttonView) + { + if (controller == null) return; + + CheckmarkButtonController buttonController = + new CheckmarkButtonController(habit, timestamp); + + buttonController.setListener(controller); + buttonController.setView(buttonView); + buttonView.setController(buttonController); + } + + private void setupCheckmarkButtons() + { + long timestamp = DateUtils.getStartOfToday(); + long day = DateUtils.millisecondsInOneDay; + + for (int i = 0; i < nButtons; i++) + { + CheckmarkButtonView buttonView = indexToButton(i); + buttonView.setValue(checkmarkValues[i]); + buttonView.setColor(color); + setupButtonControllers(timestamp, buttonView); + timestamp -= day; + } + } + + public interface Controller extends CheckmarkButtonController.Listener + { + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HabitCardListView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HabitCardListView.java new file mode 100644 index 000000000..e1d0832a3 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HabitCardListView.java @@ -0,0 +1,165 @@ +/* + * 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.ui.habits.list.views; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ListAdapter; + +import com.mobeta.android.dslv.DragSortController; +import com.mobeta.android.dslv.DragSortListView; + +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.ui.habits.list.controllers.CheckmarkButtonController; +import org.isoron.uhabits.ui.habits.list.controllers.HabitCardController; +import org.isoron.uhabits.ui.habits.list.model.HabitCardListAdapter; + +public class HabitCardListView extends DragSortListView +{ + @Nullable + private HabitCardListAdapter adapter; + + @Nullable + private Controller controller; + + public HabitCardListView(Context context, AttributeSet attrs) + { + super(context, attrs); + setFloatViewManager(new ViewManager()); + setDragEnabled(true); + setLongClickable(true); + } + + /** + * Builds a new HabitCardView to be eventually added to this list, + * containing the given data. + * + * @param cardView an old HabitCardView that should be reused if possible, + * possibly null + * @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 buildCardView(@Nullable HabitCardView cardView, + @NonNull Habit habit, + int score, + int[] checkmarks, + boolean selected) + { + if (cardView == null) cardView = new HabitCardView(getContext()); + + cardView.setHabit(habit); + cardView.setSelected(selected); + cardView.setCheckmarkValues(checkmarks); + cardView.setScore(score); + + if (controller != null) + { + HabitCardController cardController = new HabitCardController(); + cardController.setListener(controller); + cardView.setController(cardController); + cardController.setView(cardView); + } + + return cardView; + } + + @Override + public void setAdapter(ListAdapter adapter) + { + this.adapter = (HabitCardListAdapter) adapter; + super.setAdapter(adapter); + } + + public void setController(@Nullable Controller controller) + { + this.controller = controller; + setDropListener(controller); + setDragListener(controller); + setOnItemClickListener(null); + setOnLongClickListener(null); + + if (controller == null) return; + + setOnItemClickListener((p, v, pos, id) -> controller.onItemClick(pos)); + setOnItemLongClickListener((p, v, pos, id) -> { + controller.onItemLongClick(pos); + return true; + }); + } + + public void toggleShowArchived() + { +// showArchived = !showArchived; +// cache.setIncludeArchived(showArchived); +// cache.refreshAllHabits(true); + } + + @Override + protected void onAttachedToWindow() + { + super.onAttachedToWindow(); + if (adapter != null) adapter.onAttached(); + } + + @Override + protected void onDetachedFromWindow() + { + if (adapter != null) adapter.onDetached(); + super.onDetachedFromWindow(); + } + + public interface Controller extends CheckmarkButtonController.Listener, + HabitCardController.Listener, + DropListener, + DragListener + { + void onItemClick(int pos); + + void onItemLongClick(int pos); + } + + private class ViewManager extends DragSortController + { + public ViewManager() + { + super(HabitCardListView.this); + setRemoveEnabled(false); + } + + @Override + public View onCreateFloatView(int position) + { + if (adapter == null) return null; + return adapter.getView(position, null, null); + } + + @Override + public void onDestroyFloatView(View floatView) + { + } + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HabitCardView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HabitCardView.java new file mode 100644 index 000000000..e8e758929 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HabitCardView.java @@ -0,0 +1,216 @@ +/* + * 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.ui.habits.list.views; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.util.AttributeSet; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.isoron.uhabits.R; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.Score; +import org.isoron.uhabits.utils.ColorUtils; +import org.isoron.uhabits.views.RingView; + +import java.util.Random; + +import butterknife.BindView; +import butterknife.ButterKnife; + +import static org.isoron.uhabits.utils.InterfaceUtils.getStyledColor; +import static org.isoron.uhabits.utils.InterfaceUtils.getStyledDrawable; + +public class HabitCardView extends FrameLayout +{ + private Habit habit; + + @BindView(R.id.checkmarkPanel) + CheckmarkPanelView checkmarkPanel; + + @BindView(R.id.innerFrame) + LinearLayout innerFrame; + + @BindView(R.id.label) + TextView label; + + @BindView(R.id.scoreRing) + RingView scoreRing; + + private final Context context = getContext(); + + public HabitCardView(Context context) + { + super(context); + init(); + } + + public HabitCardView(Context context, AttributeSet attrs) + { + super(context, attrs); + init(); + } + + public HabitCardView(Context context, AttributeSet attrs, int defStyleAttr) + { + super(context, attrs, defStyleAttr); + init(); + } + + public void setCheckmarkValues(int checkmarks[]) + { + checkmarkPanel.setCheckmarkValues(checkmarks); + postInvalidate(); + } + + public void setController(Controller controller) + { + checkmarkPanel.setController(null); + if (controller == null) return; + + checkmarkPanel.setController(controller); + } + + public void setHabit(Habit habit) + { + this.habit = habit; + int color = getActiveColor(habit); + + label.setText(habit.name); + label.setTextColor(color); + scoreRing.setColor(color); + checkmarkPanel.setColor(color); + checkmarkPanel.setHabit(habit); + + postInvalidate(); + } + + public void setScore(int score) + { + float percentage = (float) score / Score.MAX_VALUE; + scoreRing.setPercentage(percentage); + scoreRing.setPrecision(1.0f / 16); + postInvalidate(); + } + + @Override + public void setSelected(boolean isSelected) + { + super.setSelected(isSelected); + updateBackground(isSelected); + } + + public void triggerRipple(final float x, final float y) + { + final Drawable background = innerFrame.getBackground(); + if (android.os.Build.VERSION.SDK_INT >= 21) background.setHotspot(x, y); + background.setState(new int[]{ + android.R.attr.state_pressed, android.R.attr.state_enabled + }); + new Handler().postDelayed(() -> background.setState(new int[]{}), 25); + } + + private int getActiveColor(Habit habit) + { + int mediumContrastColor = + getStyledColor(context, R.attr.mediumContrastTextColor); + int activeColor = ColorUtils.getColor(context, habit.color); + if (habit.isArchived()) activeColor = mediumContrastColor; + + return activeColor; + } + + private void init() + { + setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + + inflate(context, R.layout.list_habits_card, this); + ButterKnife.bind(this); + + innerFrame.setOnTouchListener((v, event) -> { + if (android.os.Build.VERSION.SDK_INT >= 21) + v.getBackground().setHotspot(event.getX(), event.getY()); + return false; + }); + + if (isInEditMode()) initEditMode(); + } + + @SuppressLint("SetTextI18n") + private void initEditMode() + { + String habits[] = { + "Wake up early", + "Wash dishes", + "Exercise", + "Meditate", + "Play guitar", + "Wash clothes", + "Get a haircut" + }; + + Random rand = new Random(); + int color = ColorUtils.CSV_PALETTE[rand.nextInt(10)]; + int[] values = { + rand.nextInt(3), + rand.nextInt(3), + rand.nextInt(3), + rand.nextInt(3), + rand.nextInt(3) + }; + + label.setText(habits[rand.nextInt(habits.length)]); + label.setTextColor(color); + scoreRing.setColor(color); + scoreRing.setPercentage(rand.nextFloat()); + checkmarkPanel.setColor(color); + checkmarkPanel.setCheckmarkValues(values); + } + + private void updateBackground(boolean isSelected) + { + if (android.os.Build.VERSION.SDK_INT >= 21) + { + if (isSelected) + innerFrame.setBackgroundResource(R.drawable.selected_box); + else innerFrame.setBackgroundResource(R.drawable.ripple); + } + else + { + Drawable background; + + if (isSelected) background = + getStyledDrawable(context, R.attr.selectedBackground); + else background = getStyledDrawable(context, R.attr.cardBackground); + + innerFrame.setBackgroundDrawable(background); + } + } + + public interface Controller extends CheckmarkPanelView.Controller + { + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HeaderView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HeaderView.java new file mode 100644 index 000000000..17a361087 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HeaderView.java @@ -0,0 +1,94 @@ +/* + * 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.ui.habits.list.views; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.util.AttributeSet; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.isoron.uhabits.R; +import org.isoron.uhabits.utils.DateUtils; + +import java.util.GregorianCalendar; + +public class HeaderView extends LinearLayout +{ + private static final int CHECKMARK_LEFT_TO_RIGHT = 0; + + private static final int CHECKMARK_RIGHT_TO_LEFT = 1; + + private final Context context; + + public HeaderView(Context context, AttributeSet attrs) + { + super(context, attrs); + this.context = context; + } + + private void createButtons() + { + removeAllViews(); + GregorianCalendar day = DateUtils.getStartOfTodayCalendar(); + + for (int i = 0; i < getButtonCount(); i++) + { + int position = 0; + + if (getCheckmarkOrder() == CHECKMARK_LEFT_TO_RIGHT) position = i; + + View tvDay = + inflate(context, R.layout.list_habits_header_checkmark, null); + TextView btCheck = (TextView) tvDay.findViewById(R.id.tvCheck); + btCheck.setText(DateUtils.formatHeaderDate(day)); + addView(tvDay, position); + day.add(GregorianCalendar.DAY_OF_MONTH, -1); + } + } + + private int getButtonCount() + { + float labelWidth = getResources().getDimension(R.dimen.habitNameWidth); + float buttonWidth = getResources().getDimension(R.dimen.checkmarkWidth); + return Math.max(0, + (int) ((getMeasuredWidth() - labelWidth) / buttonWidth)); + } + + private int getCheckmarkOrder() + { + if (isInEditMode()) return CHECKMARK_LEFT_TO_RIGHT; + + SharedPreferences prefs = + PreferenceManager.getDefaultSharedPreferences(getContext()); + boolean reverse = + prefs.getBoolean("pref_checkmark_reverse_order", false); + return reverse ? CHECKMARK_RIGHT_TO_LEFT : CHECKMARK_LEFT_TO_RIGHT; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) + { + createButtons(); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HintView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HintView.java new file mode 100644 index 000000000..5fa6ba7f6 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HintView.java @@ -0,0 +1,138 @@ +/* + * 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.ui.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.ui.habits.list.model.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(); + } + + public HintView(Context context, AttributeSet attrs, int defStyleAttr) + { + super(context, attrs, defStyleAttr); + 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); + } + + private void showNext() + { + if (hintList == null) return; + if (!hintList.shouldShow()) return; + + String hint = hintList.pop(); + if (hint == null) return; + + hintContent.setText(hint); + 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/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitActivity.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitActivity.java index 1d15e642e..929e801c5 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitActivity.java @@ -25,10 +25,13 @@ import android.os.Bundle; import android.support.v7.app.ActionBar; import org.isoron.uhabits.R; -import org.isoron.uhabits.utils.ColorUtils; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.ui.BaseActivity; +/** + * Activity that allows the user to see more information about a single habit. + * Shows all the metadata for the habit, in addition to several charts. + */ public class ShowHabitActivity extends BaseActivity { private Habit habit; @@ -42,19 +45,19 @@ public class ShowHabitActivity extends BaseActivity habit = Habit.get(ContentUris.parseId(data)); setContentView(R.layout.show_habit_activity); - setupSupportActionBar(true); +// setupSupportActionBar(true); setupHabitActionBar(); } public void setupHabitActionBar() { - if(habit == null) return; + if (habit == null) return; ActionBar actionBar = getSupportActionBar(); - if(actionBar == null) return; + if (actionBar == null) return; actionBar.setTitle(habit.name); - setupActionBarColor(ColorUtils.getColor(this, habit.color)); +// setupActionBarColor(ColorUtils.getColor(this, habit.color)); } public Habit getHabit() diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java index f088b0d33..3ba99c833 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java @@ -52,29 +52,47 @@ import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; -public class ShowHabitFragment extends Fragment implements ModelObservable.Listener +public class ShowHabitFragment extends Fragment + implements ModelObservable.Listener { Habit habit; float todayScore; + float lastMonthScore; + float lastYearScore; + int activeColor; + int inactiveColor; + int previousScoreInterval; private ShowHabitHelper helper; + protected ShowHabitActivity activity; + private List dataViews; - @BindView(R.id.sStrengthInterval) Spinner sStrengthInterval; - @BindView(R.id.scoreView) HabitScoreView habitScoreView; - @BindView(R.id.historyView) HabitHistoryView habitHistoryView; - @BindView(R.id.punchcardView) HabitFrequencyView habitFrequencyView; - @BindView(R.id.streakView) HabitStreakView habitStreakView; + @BindView(R.id.sStrengthInterval) + Spinner sStrengthInterval; + + @BindView(R.id.scoreView) + HabitScoreView habitScoreView; + + @BindView(R.id.historyView) + HabitHistoryView habitHistoryView; + + @BindView(R.id.punchcardView) + HabitFrequencyView habitFrequencyView; + + @BindView(R.id.streakView) + HabitStreakView habitStreakView; @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, + public View onCreateView(LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.show_habit, container, false); @@ -87,12 +105,14 @@ public class ShowHabitFragment extends Fragment implements ModelObservable.Liste helper.updateColors(); helper.updateMainHeader(view); - int defaultScoreInterval = InterfaceUtils.getDefaultScoreInterval(getContext()); + int defaultScoreInterval = + InterfaceUtils.getDefaultScoreInterval(getContext()); previousScoreInterval = defaultScoreInterval; setScoreBucketSize(defaultScoreInterval); sStrengthInterval.setSelection(defaultScoreInterval); - sStrengthInterval.setOnItemSelectedListener(new OnItemSelectedListener()); + sStrengthInterval.setOnItemSelectedListener( + new OnItemSelectedListener()); createDataViews(); helper.updateCardHeaders(view); @@ -117,7 +137,7 @@ public class ShowHabitFragment extends Fragment implements ModelObservable.Liste dataViews.add(habitFrequencyView); dataViews.add(habitStreakView); - for(HabitDataView dataView : dataViews) + for (HabitDataView dataView : dataViews) dataView.setHabit(habit); } @@ -131,7 +151,7 @@ public class ShowHabitFragment extends Fragment implements ModelObservable.Liste @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - inflater.inflate(R.menu.show_habit_fragment, menu); +// inflater.inflate(R.menu.show_habit_fragment, menu); } @Override @@ -145,9 +165,10 @@ public class ShowHabitFragment extends Fragment implements ModelObservable.Liste private boolean showEditHabitDialog() { - if(habit == null) return false; + if (habit == null) return false; - BaseDialogFragment frag = EditHabitDialogFragment.newInstance(habit.getId()); + BaseDialogFragment frag = + EditHabitDialogFragment.newInstance(habit.getId()); frag.show(getFragmentManager(), "editHabit"); return true; } @@ -159,12 +180,12 @@ public class ShowHabitFragment extends Fragment implements ModelObservable.Liste private void setScoreBucketSize(int position) { - if(getView() == null) return; + if (getView() == null) return; - habitScoreView.setBucketSize(HabitScoreView.DEFAULT_BUCKET_SIZES[position]); + habitScoreView.setBucketSize( + HabitScoreView.DEFAULT_BUCKET_SIZES[position]); - if(position != previousScoreInterval) - refreshData(); + if (position != previousScoreInterval) refreshData(); InterfaceUtils.setDefaultScoreInterval(getContext(), position); previousScoreInterval = position; @@ -182,7 +203,7 @@ public class ShowHabitFragment extends Fragment implements ModelObservable.Liste helper.updateColors(); helper.updateMainHeader(getView()); helper.updateCardHeaders(getView()); - if(activity != null) activity.setupHabitActionBar(); + if (activity != null) activity.setupHabitActionBar(); } }); } @@ -206,8 +227,8 @@ public class ShowHabitFragment extends Fragment implements ModelObservable.Liste @Override protected void doInBackground() { - if(habit == null) return; - if(dataViews == null) return; + if (habit == null) return; + if (dataViews == null) return; long today = DateUtils.getStartOfToday(); long lastMonth = today - 30 * DateUtils.millisecondsInOneDay; @@ -226,10 +247,14 @@ public class ShowHabitFragment extends Fragment implements ModelObservable.Liste } } - private class OnItemSelectedListener implements AdapterView.OnItemSelectedListener + private class OnItemSelectedListener + implements AdapterView.OnItemSelectedListener { @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) + public void onItemSelected(AdapterView parent, + View view, + int position, + long id) { setScoreBucketSize(position); } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java index 026327d22..6a76d4f32 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java @@ -41,24 +41,26 @@ public class ShowHabitHelper String getFreqText() { - if(fragment.habit == null) return ""; + if (fragment.habit == null) return ""; Resources resources = fragment.getResources(); Integer freqNum = fragment.habit.freqNum; Integer freqDen = fragment.habit.freqDen; - if (freqNum.equals(freqDen)) return resources.getString(R.string.every_day); + if (freqNum.equals(freqDen)) + return resources.getString(R.string.every_day); if (freqNum == 1) { if (freqDen == 7) return resources.getString(R.string.every_week); - if (freqDen % 7 == 0) return resources.getString(R.string.every_x_weeks, freqDen / 7); + if (freqDen % 7 == 0) + return resources.getString(R.string.every_x_weeks, freqDen / 7); return resources.getString(R.string.every_x_days, freqDen); } String times_every = resources.getString(R.string.time_every); return String.format("%d %s %d %s", freqNum, times_every, freqDen, - resources.getString(R.string.days)); + resources.getString(R.string.days)); } void updateScore(View view) @@ -67,48 +69,62 @@ public class ShowHabitHelper if (view == null) return; float todayPercentage = fragment.todayScore / Score.MAX_VALUE; - float monthDiff = todayPercentage - (fragment.lastMonthScore / Score.MAX_VALUE); - float yearDiff = todayPercentage - (fragment.lastYearScore / Score.MAX_VALUE); + float monthDiff = + todayPercentage - (fragment.lastMonthScore / Score.MAX_VALUE); + float yearDiff = + todayPercentage - (fragment.lastYearScore / Score.MAX_VALUE); RingView scoreRing = (RingView) view.findViewById(R.id.scoreRing); - int androidColor = ColorUtils.getColor(fragment.getActivity(), fragment.habit.color); + int androidColor = + ColorUtils.getColor(fragment.getActivity(), fragment.habit.color); scoreRing.setColor(androidColor); scoreRing.setPercentage(todayPercentage); TextView scoreLabel = (TextView) view.findViewById(R.id.scoreLabel); - TextView monthDiffLabel = (TextView) view.findViewById(R.id.monthDiffLabel); - TextView yearDiffLabel = (TextView) view.findViewById(R.id.yearDiffLabel); + TextView monthDiffLabel = + (TextView) view.findViewById(R.id.monthDiffLabel); + TextView yearDiffLabel = + (TextView) view.findViewById(R.id.yearDiffLabel); scoreLabel.setText(String.format("%.0f%%", todayPercentage * 100)); String minus = "\u2212"; - monthDiffLabel.setText(String.format("%s%.0f%%", (monthDiff >= 0 ? "+" : minus), + monthDiffLabel.setText( + String.format("%s%.0f%%", (monthDiff >= 0 ? "+" : minus), Math.abs(monthDiff) * 100)); yearDiffLabel.setText( - String.format("%s%.0f%%", (yearDiff >= 0 ? "+" : minus), Math.abs(yearDiff) * 100)); + String.format("%s%.0f%%", (yearDiff >= 0 ? "+" : minus), + Math.abs(yearDiff) * 100)); - monthDiffLabel.setTextColor(monthDiff >= 0 ? fragment.activeColor : fragment.inactiveColor); - yearDiffLabel.setTextColor(yearDiff >= 0 ? fragment.activeColor : fragment.inactiveColor); + monthDiffLabel.setTextColor( + monthDiff >= 0 ? fragment.activeColor : fragment.inactiveColor); + yearDiffLabel.setTextColor( + yearDiff >= 0 ? fragment.activeColor : fragment.inactiveColor); } void updateMainHeader(View view) { if (fragment.habit == null) return; - TextView questionLabel = (TextView) view.findViewById(R.id.questionLabel); + TextView questionLabel = + (TextView) view.findViewById(R.id.questionLabel); questionLabel.setTextColor(fragment.activeColor); questionLabel.setText(fragment.habit.description); - TextView reminderLabel = (TextView) view.findViewById(R.id.reminderLabel); + TextView reminderLabel = + (TextView) view.findViewById(R.id.reminderLabel); if (fragment.habit.hasReminder()) reminderLabel.setText( - DateUtils.formatTime(fragment.getActivity(), fragment.habit.reminderHour, - fragment.habit.reminderMin)); - else reminderLabel.setText(fragment.getResources().getString(R.string.reminder_off)); + DateUtils.formatTime(fragment.getActivity(), + fragment.habit.reminderHour, fragment.habit.reminderMin)); + else reminderLabel.setText( + fragment.getResources().getString(R.string.reminder_off)); - TextView frequencyLabel = (TextView) view.findViewById(R.id.frequencyLabel); + TextView frequencyLabel = + (TextView) view.findViewById(R.id.frequencyLabel); frequencyLabel.setText(getFreqText()); - if (fragment.habit.description.isEmpty()) questionLabel.setVisibility(View.GONE); + if (fragment.habit.description.isEmpty()) + questionLabel.setVisibility(View.GONE); } void updateCardHeaders(View view) @@ -123,17 +139,20 @@ public class ShowHabitHelper void updateColor(View view, int viewId) { - if(fragment.habit == null || fragment.activity == null) return; + if (fragment.habit == null || fragment.activity == null) return; TextView textView = (TextView) view.findViewById(viewId); - int androidColor = ColorUtils.getColor(fragment.activity, fragment.habit.color); + int androidColor = + ColorUtils.getColor(fragment.activity, fragment.habit.color); textView.setTextColor(androidColor); } void updateColors() { - fragment.activeColor = ColorUtils.getColor(fragment.getContext(), fragment.habit.color); - fragment.inactiveColor = InterfaceUtils.getStyledColor(fragment.getContext(), + fragment.activeColor = + ColorUtils.getColor(fragment.getContext(), fragment.habit.color); + fragment.inactiveColor = + InterfaceUtils.getStyledColor(fragment.getContext(), R.attr.mediumContrastTextColor); } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/intro/IntroActivity.java b/app/src/main/java/org/isoron/uhabits/ui/intro/IntroActivity.java index 1b0229633..0e6bcdf45 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/intro/IntroActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ui/intro/IntroActivity.java @@ -27,6 +27,10 @@ import com.github.paolorotolo.appintro.AppIntroFragment; import org.isoron.uhabits.R; +/** + * Activity that introduces the app to the user, shown only after the app is + * launched for the first time. + */ public class IntroActivity extends AppIntro2 { @Override @@ -35,16 +39,16 @@ public class IntroActivity extends AppIntro2 showStatusBar(false); addSlide(AppIntroFragment.newInstance(getString(R.string.intro_title_1), - getString(R.string.intro_description_1), R.drawable.intro_icon_1, - Color.parseColor("#194673"))); + getString(R.string.intro_description_1), R.drawable.intro_icon_1, + Color.parseColor("#194673"))); addSlide(AppIntroFragment.newInstance(getString(R.string.intro_title_2), - getString(R.string.intro_description_2), R.drawable.intro_icon_2, - Color.parseColor("#ffa726"))); + getString(R.string.intro_description_2), R.drawable.intro_icon_2, + Color.parseColor("#ffa726"))); addSlide(AppIntroFragment.newInstance(getString(R.string.intro_title_4), - getString(R.string.intro_description_4), R.drawable.intro_icon_4, - Color.parseColor("#9575cd"))); + getString(R.string.intro_description_4), R.drawable.intro_icon_4, + Color.parseColor("#9575cd"))); } @Override diff --git a/app/src/main/java/org/isoron/uhabits/ui/intro/package-info.java b/app/src/main/java/org/isoron/uhabits/ui/intro/package-info.java new file mode 100644 index 000000000..68eee2b1e --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/intro/package-info.java @@ -0,0 +1,23 @@ +/* + * 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 classes for the IntroActivity. + */ +package org.isoron.uhabits.ui.intro; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/ui/settings/FilePickerDialog.java b/app/src/main/java/org/isoron/uhabits/ui/settings/FilePickerDialog.java index f31fad843..8d7faa1c7 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/settings/FilePickerDialog.java +++ b/app/src/main/java/org/isoron/uhabits/ui/settings/FilePickerDialog.java @@ -39,8 +39,11 @@ public class FilePickerDialog implements AdapterView.OnItemClickListener private static final String PARENT_DIR = ".."; private final Activity activity; + private ListView list; + private Dialog dialog; + private File currentPath; public interface OnFileSelectedListener @@ -59,21 +62,24 @@ public class FilePickerDialog implements AdapterView.OnItemClickListener dialog = new Dialog(activity); dialog.setContentView(list); - dialog.getWindow().setLayout(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + dialog + .getWindow() + .setLayout(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); navigateTo(initialDirectory); } @Override - public void onItemClick(AdapterView parent, View view, int which, long id) + public void onItemClick(AdapterView parent, + View view, + int which, + long id) { String filename = (String) list.getItemAtPosition(which); File file; - if (filename.equals(PARENT_DIR)) - file = currentPath.getParentFile(); - else - file = new File(currentPath, filename); + if (filename.equals(PARENT_DIR)) file = currentPath.getParentFile(); + else file = new File(currentPath, filename); if (file.isDirectory()) { @@ -102,7 +108,7 @@ public class FilePickerDialog implements AdapterView.OnItemClickListener File[] dirs = path.listFiles(new ReadableDirFilter()); File[] files = path.listFiles(new RegularReadableFileFilter()); - if(dirs == null || files == null) return; + if (dirs == null || files == null) return; this.currentPath = path; dialog.setTitle(currentPath.getPath()); @@ -142,7 +148,8 @@ public class FilePickerDialog implements AdapterView.OnItemClickListener { public FilePickerAdapter(@NonNull String[] fileList) { - super(FilePickerDialog.this.activity, android.R.layout.simple_list_item_1, fileList); + super(FilePickerDialog.this.activity, + android.R.layout.simple_list_item_1, fileList); } @Override diff --git a/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsActivity.java b/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsActivity.java index d9188b5c9..aebebc8aa 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsActivity.java @@ -22,9 +22,12 @@ package org.isoron.uhabits.ui.settings; import android.os.Bundle; import org.isoron.uhabits.R; -import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.ui.BaseActivity; +import org.isoron.uhabits.utils.InterfaceUtils; +/** + * Activity that allows the user to view and modify the app settings. + */ public class SettingsActivity extends BaseActivity { @Override @@ -32,9 +35,10 @@ public class SettingsActivity extends BaseActivity { super.onCreate(savedInstanceState); setContentView(R.layout.settings_activity); - setupSupportActionBar(true); +// setupSupportActionBar(true); - int color = InterfaceUtils.getStyledColor(this, R.attr.aboutScreenColor); - setupActionBarColor(color); + int color = + InterfaceUtils.getStyledColor(this, R.attr.aboutScreenColor); +// setupActionBarColor(color); } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsFragment.java b/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsFragment.java index bdb925c12..93aa9af80 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsFragment.java @@ -29,11 +29,11 @@ import android.support.v7.preference.PreferenceFragmentCompat; import org.isoron.uhabits.HabitsApplication; import org.isoron.uhabits.R; -import org.isoron.uhabits.utils.ReminderUtils; import org.isoron.uhabits.utils.InterfaceUtils; +import org.isoron.uhabits.utils.ReminderUtils; public class SettingsFragment extends PreferenceFragmentCompat - implements SharedPreferences.OnSharedPreferenceChangeListener + implements SharedPreferences.OnSharedPreferenceChangeListener { private static int RINGTONE_REQUEST_CODE = 1; @@ -43,14 +43,18 @@ public class SettingsFragment extends PreferenceFragmentCompat super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.preferences); - setResultOnPreferenceClick("importData", HabitsApplication.RESULT_IMPORT_DATA); - setResultOnPreferenceClick("exportCSV", HabitsApplication.RESULT_EXPORT_CSV); - setResultOnPreferenceClick("exportDB", HabitsApplication.RESULT_EXPORT_DB); - setResultOnPreferenceClick("bugReport", HabitsApplication.RESULT_BUG_REPORT); + setResultOnPreferenceClick("importData", + HabitsApplication.RESULT_IMPORT_DATA); + setResultOnPreferenceClick("exportCSV", + HabitsApplication.RESULT_EXPORT_CSV); + setResultOnPreferenceClick("exportDB", + HabitsApplication.RESULT_EXPORT_DB); + setResultOnPreferenceClick("bugReport", + HabitsApplication.RESULT_BUG_REPORT); updateRingtoneDescription(); - if(InterfaceUtils.isLocaleFullyTranslated()) + if (InterfaceUtils.isLocaleFullyTranslated()) removePreference("translate", "linksCategory"); } @@ -62,7 +66,8 @@ public class SettingsFragment extends PreferenceFragmentCompat private void removePreference(String preferenceKey, String categoryKey) { - PreferenceCategory cat = (PreferenceCategory) findPreference(categoryKey); + PreferenceCategory cat = + (PreferenceCategory) findPreference(categoryKey); Preference pref = findPreference(preferenceKey); cat.removePreference(pref); } @@ -70,16 +75,17 @@ public class SettingsFragment extends PreferenceFragmentCompat private void setResultOnPreferenceClick(String key, final int result) { Preference pref = findPreference(key); - pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() - { - @Override - public boolean onPreferenceClick(Preference preference) + pref.setOnPreferenceClickListener( + new Preference.OnPreferenceClickListener() { - getActivity().setResult(result); - getActivity().finish(); - return true; - } - }); + @Override + public boolean onPreferenceClick(Preference preference) + { + getActivity().setResult(result); + getActivity().finish(); + return true; + } + }); } @Override @@ -87,19 +93,20 @@ public class SettingsFragment extends PreferenceFragmentCompat { super.onResume(); getPreferenceManager().getSharedPreferences(). - registerOnSharedPreferenceChangeListener(this); + registerOnSharedPreferenceChangeListener(this); } @Override public void onPause() { getPreferenceManager().getSharedPreferences(). - unregisterOnSharedPreferenceChangeListener(this); + unregisterOnSharedPreferenceChangeListener(this); super.onPause(); } @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, + String key) { BackupManager.dataChanged("org.isoron.uhabits"); } @@ -107,11 +114,12 @@ public class SettingsFragment extends PreferenceFragmentCompat @Override public boolean onPreferenceTreeClick(Preference preference) { - if(preference.getKey() == null) return false; + if (preference.getKey() == null) return false; if (preference.getKey().equals("reminderSound")) { - ReminderUtils.startRingtonePickerActivity(this, RINGTONE_REQUEST_CODE); + ReminderUtils.startRingtonePickerActivity(this, + RINGTONE_REQUEST_CODE); return true; } @@ -121,7 +129,7 @@ public class SettingsFragment extends PreferenceFragmentCompat @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { - if(requestCode == RINGTONE_REQUEST_CODE) + if (requestCode == RINGTONE_REQUEST_CODE) { ReminderUtils.parseRingtoneData(getContext(), data); updateRingtoneDescription(); diff --git a/app/src/main/java/org/isoron/uhabits/ui/settings/package-info.java b/app/src/main/java/org/isoron/uhabits/ui/settings/package-info.java new file mode 100644 index 000000000..d7254b864 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/settings/package-info.java @@ -0,0 +1,23 @@ +/* + * 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 classes for the SettingsActivity. + */ +package org.isoron.uhabits.ui.settings; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/utils/Preferences.java b/app/src/main/java/org/isoron/uhabits/utils/Preferences.java index d4d318294..adb0c6086 100644 --- a/app/src/main/java/org/isoron/uhabits/utils/Preferences.java +++ b/app/src/main/java/org/isoron/uhabits/utils/Preferences.java @@ -29,28 +29,40 @@ import org.isoron.uhabits.R; public class Preferences { - private static Preferences singleton; - private Context context; + private SharedPreferences prefs; - private Preferences() + public Preferences() { this.context = HabitsApplication.getContext(); prefs = PreferenceManager.getDefaultSharedPreferences(context); } - public static Preferences getInstance() + public Integer getDefaultHabitColor(int fallbackColor) { - if(singleton == null) singleton = new Preferences(); - return singleton; + return prefs.getInt("pref_default_habit_palette_color", fallbackColor); } - public void initialize() + /** + * Returns the number of the last hint shown to the user. + * + * @return number of last hint shown + */ + public int getLastHintNumber() { - PreferenceManager.setDefaultValues(context, R.xml.preferences, false); + return prefs.getInt("last_hint_number", -1); } + /** + * Returns the time when the last hint was shown to the user. + * + * @return timestamp of the day the last hint was shown + */ + public long getLastHintTimestamp() + { + return prefs.getLong("last_hint_timestamp", -1); + } public void incrementLaunchCount() { @@ -58,9 +70,9 @@ public class Preferences prefs.edit().putInt("launch_count", count + 1).apply(); } - public void updateLastAppVersion() + public void initialize() { - prefs.edit().putInt("last_version", BuildConfig.VERSION_CODE).apply(); + PreferenceManager.setDefaultValues(context, R.xml.preferences, false); } public boolean isFirstRun() @@ -73,19 +85,14 @@ public class Preferences prefs.edit().putBoolean("pref_first_run", isFirstRun).apply(); } - public void setLastHintTimestamp(long timestamp) - { - prefs.edit().putLong("last_hint_timestamp", timestamp).apply(); - } - public boolean isShortToggleEnabled() { return prefs.getBoolean("pref_short_toggle", false); } - public Integer getDefaultHabitColor(int defaultColor) + public void setShortToggleEnabled(boolean enabled) { - return prefs.getInt("pref_default_habit_palette_color", defaultColor); + prefs.edit().putBoolean("pref_short_toggle", enabled).apply(); } public void setDefaultHabitColor(int color) @@ -93,4 +100,38 @@ public class Preferences prefs.edit().putInt("pref_default_habit_palette_color", color).apply(); } + public void setShouldReverseCheckmarks(boolean shouldReverse) + { + prefs + .edit() + .putBoolean("pref_checkmark_reverse_order", shouldReverse) + .apply(); + } + + public boolean shouldReverseCheckmarks() + { + return prefs.getBoolean("pref_checkmark_reverse_order", false); + } + + public void updateLastAppVersion() + { + prefs.edit().putInt("last_version", BuildConfig.VERSION_CODE).apply(); + } + + /** + * Sets the last hint shown to the user, and the time that it was shown. + * + * @param number number of the last hint shown + * @param timestamp timestamp for the day the last hint was shown + */ + public void updateLastHint(int number, long timestamp) + { + prefs + .edit() + .putInt("last_hint_number", number) + .putLong("last_hint_timestamp", timestamp) + .apply(); + } + + } diff --git a/app/src/main/res/layout/list_habits_fragment.xml b/app/src/main/res/layout/list_habits.xml similarity index 56% rename from app/src/main/res/layout/list_habits_fragment.xml rename to app/src/main/res/layout/list_habits.xml index 9fba9281f..a9e71fa69 100644 --- a/app/src/main/res/layout/list_habits_fragment.xml +++ b/app/src/main/res/layout/list_habits.xml @@ -19,24 +19,36 @@ --> + android:background="?windowBackgroundColor"> - + + + + + app:drag_start_mode="onLongPress" + app:sort_enabled="true" + app:track_drag_sort="false" + android:layout_below="@id/header"/> - - - - - - - - - - - - + android:layout_alignParentBottom="true"/> - - - + \ No newline at end of file diff --git a/app/src/main/res/layout/list_habits_activity.xml b/app/src/main/res/layout/list_habits_activity.xml deleted file mode 100644 index 6c0d3a643..000000000 --- a/app/src/main/res/layout/list_habits_activity.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - diff --git a/app/src/main/res/layout/list_habits_item.xml b/app/src/main/res/layout/list_habits_card.xml similarity index 63% rename from app/src/main/res/layout/list_habits_item.xml rename to app/src/main/res/layout/list_habits_card.xml index 63a766d35..3320a995c 100644 --- a/app/src/main/res/layout/list_habits_item.xml +++ b/app/src/main/res/layout/list_habits_card.xml @@ -18,31 +18,34 @@ ~ with this program. If not, see . --> - + + android:id="@+id/innerFrame" + style="@style/ListHabits.HabitCard" + android:layout_width="match_parent"> + android:layout_marginTop="0dp" + habit:thickness="3"/> + style="@style/ListHabits.Label"/> + + - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/list_habits_item_check.xml b/app/src/main/res/layout/list_habits_card_checkmark.xml similarity index 100% rename from app/src/main/res/layout/list_habits_item_check.xml rename to app/src/main/res/layout/list_habits_card_checkmark.xml diff --git a/app/src/main/res/layout/list_habits_header_check.xml b/app/src/main/res/layout/list_habits_header_checkmark.xml similarity index 100% rename from app/src/main/res/layout/list_habits_header_check.xml rename to app/src/main/res/layout/list_habits_header_checkmark.xml diff --git a/app/src/main/res/layout/list_habits_hint.xml b/app/src/main/res/layout/list_habits_hint.xml new file mode 100644 index 000000000..11d86bd72 --- /dev/null +++ b/app/src/main/res/layout/list_habits_hint.xml @@ -0,0 +1,46 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_habits_preview.xml b/app/src/main/res/layout/list_habits_preview.xml new file mode 100644 index 000000000..ff52cd785 --- /dev/null +++ b/app/src/main/res/layout/list_habits_preview.xml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/list_habits_fragment.xml b/app/src/main/res/menu/list_habits_fragment.xml deleted file mode 100644 index 80695df7a..000000000 --- a/app/src/main/res/menu/list_habits_fragment.xml +++ /dev/null @@ -1,39 +0,0 @@ - - -

- - - - - - diff --git a/app/src/main/res/menu/main_activity.xml b/app/src/main/res/menu/main_activity.xml index c80f5945f..958594485 100644 --- a/app/src/main/res/menu/main_activity.xml +++ b/app/src/main/res/menu/main_activity.xml @@ -22,6 +22,20 @@ xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity"> + + + + ?headerBackgroundColor 2dp - match_parent - wrap_content - true 4dp + end \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index d0c3dde42..94bf2a851 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -240,7 +240,6 @@ ?actionBarSize ?colorPrimary @style/ThemeOverlay.AppCompat.Dark.ActionBar - true diff --git a/app/src/main/res/values/styles_list_habits.xml b/app/src/main/res/values/styles_list_habits.xml index 10e079e0e..31bd516cf 100644 --- a/app/src/main/res/values/styles_list_habits.xml +++ b/app/src/main/res/values/styles_list_habits.xml @@ -26,9 +26,6 @@ diff --git a/app/src/test/java/org/isoron/uhabits/BaseUnitTest.java b/app/src/test/java/org/isoron/uhabits/BaseUnitTest.java new file mode 100644 index 000000000..f9d06ba8f --- /dev/null +++ b/app/src/test/java/org/isoron/uhabits/BaseUnitTest.java @@ -0,0 +1,45 @@ +/* + * 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 org.isoron.uhabits.ui.habits.list.model.HabitCardListCache; +import org.isoron.uhabits.utils.Preferences; +import org.junit.Before; + +import javax.inject.Inject; + +public class BaseUnitTest +{ + @Inject + protected Preferences prefs; + + @Inject + protected HabitCardListCache listCache; + + protected TestComponent testComponent; + + @Before + public void setUp() + { + testComponent = DaggerTestComponent.builder().build(); + HabitsApplication.setComponent(testComponent); + testComponent.inject(this); + } +} diff --git a/app/src/test/java/org/isoron/uhabits/TestComponent.java b/app/src/test/java/org/isoron/uhabits/TestComponent.java new file mode 100644 index 000000000..8d73408e6 --- /dev/null +++ b/app/src/test/java/org/isoron/uhabits/TestComponent.java @@ -0,0 +1,32 @@ +/* + * 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 javax.inject.Singleton; + +import dagger.Component; + +@Singleton +@Component(modules = {TestModule.class}) +public interface TestComponent extends BaseComponent +{ + void inject(BaseUnitTest baseUnitTest); +} + diff --git a/app/src/test/java/org/isoron/uhabits/TestModule.java b/app/src/test/java/org/isoron/uhabits/TestModule.java new file mode 100644 index 000000000..017b458f0 --- /dev/null +++ b/app/src/test/java/org/isoron/uhabits/TestModule.java @@ -0,0 +1,56 @@ +/* + * 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 org.isoron.uhabits.commands.CommandRunner; +import org.isoron.uhabits.ui.habits.list.model.HabitCardListCache; +import org.isoron.uhabits.utils.Preferences; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +import static org.mockito.Mockito.mock; + +@Module +public class TestModule +{ + @Singleton + @Provides + Preferences providePreferences() + { + return mock(Preferences.class); + } + + @Singleton + @Provides + CommandRunner provideCommandRunner() + { + return mock(CommandRunner.class); + } + + @Singleton + @Provides + HabitCardListCache provideHabitCardListCache() + { + return mock(HabitCardListCache.class); + } +} diff --git a/app/src/test/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListAdapterTest.java b/app/src/test/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListAdapterTest.java new file mode 100644 index 000000000..13e153e31 --- /dev/null +++ b/app/src/test/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListAdapterTest.java @@ -0,0 +1,112 @@ +/* + * 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.ui.habits.list.model; + +import org.isoron.uhabits.BaseUnitTest; +import org.junit.Before; +import org.junit.Test; + +public class HabitCardListAdapterTest extends BaseUnitTest +{ + @Override + @Before + public void setUp() + { + super.setUp(); + } + + @Test + public void testSelection() throws Exception + { + + } + + @Test + public void testGetCount() throws Exception + { + + } + + @Test + public void testGetItem() throws Exception + { + + } + + @Test + public void testGetItemId() throws Exception + { + + } + + @Test + public void testGetSelected() throws Exception + { + + } + + @Test + public void testGetView() throws Exception + { + + } + + @Test + public void testIsSelectionEmpty() throws Exception + { + + } + + @Test + public void testOnAttached() throws Exception + { + + } + + @Test + public void testOnCacheRefresh() throws Exception + { + + } + + @Test + public void testOnDetached() throws Exception + { + + } + + @Test + public void testReorder() throws Exception + { + + } + + @Test + public void testSetListView() throws Exception + { + + } + + @Test + public void testToggleSelection() throws Exception + { + + } +} \ No newline at end of file diff --git a/app/src/test/java/org/isoron/uhabits/ui/habits/list/views/CheckmarkButtonControllerTest.java b/app/src/test/java/org/isoron/uhabits/ui/habits/list/views/CheckmarkButtonControllerTest.java new file mode 100644 index 000000000..6346c2540 --- /dev/null +++ b/app/src/test/java/org/isoron/uhabits/ui/habits/list/views/CheckmarkButtonControllerTest.java @@ -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.ui.habits.list.views; + +import org.isoron.uhabits.BaseUnitTest; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.ui.habits.list.controllers.CheckmarkButtonController; +import org.junit.Before; +import org.junit.Test; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +public class CheckmarkButtonControllerTest extends BaseUnitTest +{ + private CheckmarkButtonController controller; + + private CheckmarkButtonView view; + + private CheckmarkButtonController.Listener listener; + + private Habit habit; + + private int timestamp; + + @Override + @Before + public void setUp() + { + super.setUp(); + + timestamp = 0; + habit = mock(Habit.class); + + this.view = mock(CheckmarkButtonView.class); + this.listener = mock(CheckmarkButtonController.Listener.class); + this.controller = new CheckmarkButtonController(habit, timestamp); + controller.setView(view); + controller.setListener(listener); + } + + @Test + public void testOnClick_withShortToggle() throws Exception + { + doReturn(true).when(prefs).isShortToggleEnabled(); + controller.onClick(); + verifyToggle(); + } + + @Test + public void testOnClick_withoutShortToggle() throws Exception + { + doReturn(false).when(prefs).isShortToggleEnabled(); + controller.onClick(); + verifyInvalidToggle(); + } + + @Test + public void testOnLongClick() throws Exception + { + controller.onLongClick(); + verifyToggle(); + } + + protected void verifyInvalidToggle() + { + verifyZeroInteractions(view); + verify(listener).onInvalidToggle(); + } + + protected void verifyToggle() + { + verify(view).toggle(); + verify(listener).onToggle(habit, timestamp); + } +} \ No newline at end of file diff --git a/app/src/test/java/org/isoron/uhabits/ui/habits/list/views/HabitCardControllerTest.java b/app/src/test/java/org/isoron/uhabits/ui/habits/list/views/HabitCardControllerTest.java new file mode 100644 index 000000000..2f7839589 --- /dev/null +++ b/app/src/test/java/org/isoron/uhabits/ui/habits/list/views/HabitCardControllerTest.java @@ -0,0 +1,74 @@ +/* + * 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.ui.habits.list.views; + +import org.isoron.uhabits.BaseUnitTest; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.ui.habits.list.controllers.HabitCardController; +import org.isoron.uhabits.utils.DateUtils; +import org.junit.Before; +import org.junit.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class HabitCardControllerTest extends BaseUnitTest +{ + + private Habit habit; + + private HabitCardController controller; + + private HabitCardController.Listener listener; + + private HabitCardView view; + + @Override + @Before + public void setUp() + { + super.setUp(); + + this.habit = mock(Habit.class); + this.listener = mock(HabitCardController.Listener.class); + this.view = mock(HabitCardView.class); + + this.controller = new HabitCardController(); + controller.setListener(listener); + controller.setView(view); + view.setController(controller); + } + + @Test + public void testOnInvalidToggle() + { + controller.onInvalidToggle(); + verify(listener).onInvalidToggle(); + } + + @Test + public void testOnToggle() + { + long timestamp = DateUtils.getStartOfToday(); + controller.onToggle(habit, timestamp); + verify(view).triggerRipple(0, 0); + verify(listener).onToggle(habit, timestamp); + } +} \ No newline at end of file diff --git a/app/src/test/java/org/isoron/uhabits/ui/habits/list/views/HabitCardListControllerTest.java b/app/src/test/java/org/isoron/uhabits/ui/habits/list/views/HabitCardListControllerTest.java new file mode 100644 index 000000000..5a577d7fd --- /dev/null +++ b/app/src/test/java/org/isoron/uhabits/ui/habits/list/views/HabitCardListControllerTest.java @@ -0,0 +1,178 @@ +/* + * 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.ui.habits.list.views; + +import org.isoron.uhabits.BaseUnitTest; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.ui.habits.list.controllers.HabitCardListController; +import org.isoron.uhabits.ui.habits.list.model.HabitCardListAdapter; +import org.junit.Before; +import org.junit.Test; + +import java.util.LinkedList; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; + +public class HabitCardListControllerTest extends BaseUnitTest +{ + + 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(); + + this.controller = new HabitCardListController(adapter, view); + 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).reorder(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).reorder(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/app/src/test/java/org/isoron/uhabits/ui/habits/list/views/package-info.java b/app/src/test/java/org/isoron/uhabits/ui/habits/list/views/package-info.java new file mode 100644 index 000000000..67a18717a --- /dev/null +++ b/app/src/test/java/org/isoron/uhabits/ui/habits/list/views/package-info.java @@ -0,0 +1,23 @@ +/* + * 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.ui.habits.list.views; \ No newline at end of file diff --git a/build.gradle b/build.gradle index d9ea1210d..0382b84b4 100644 --- a/build.gradle +++ b/build.gradle @@ -7,6 +7,9 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:2.1.0' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' + classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.5.2' + classpath 'me.tatarka:gradle-retrolambda:3.2.5' + classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' } } diff --git a/gradle.properties b/gradle.properties index 6c47b7f7d..57a1784b0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ org.gradle.parallel=true org.gradle.daemon=true -org.gradle.jvmargs=-Xms512m -Xmx2048m +org.gradle.jvmargs=-Xms1024m -Xmx4096m -XX:MaxPermSize=2048m