Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 534e6c2d9d | |||
| b6501c9a29 | |||
| 238a1c724d | |||
| 34ca9d17a2 | |||
| e844390614 | |||
| 5e00d07b73 | |||
| 28b6ae7014 | |||
| 2a1bf5fc2e | |||
| ef7483f9dc | |||
| bbb9ed8f99 | |||
| c49d576871 | |||
| bc66ae4f7a | |||
| fa416adbb9 | |||
| 8b835b9918 | |||
| 471c5d341f | |||
| 57296745b3 | |||
| 140ab34a76 | |||
| 0d6ad26505 | |||
| 65cc99dbf7 | |||
| 6855ef9d5e | |||
| 4c58b084c6 | |||
| 5c8e522646 | |||
| 55da0759d4 | |||
| 692fe3218f | |||
| 387930c08d | |||
| 6bd31f9607 | |||
| 9aafe7160c | |||
| 5cc4aac67a | |||
| 831421bc98 | |||
| 161d8f2517 | |||
| bfe4b822b3 | |||
| 19e79a8559 | |||
| 876d4f0ac7 | |||
| f4f7faf3a4 | |||
| 56f2ae57fe | |||
| 3fe09efe9b | |||
| f0de29fbfe | |||
| 324facfffd | |||
| 03e58f9ef2 | |||
| e6c9f7f0c9 | |||
| 42bdedb86a | |||
| ab0c510fda | |||
| e46fd58664 | |||
| 8532bd402e | |||
| 2c599b18ef | |||
| 0d78ba4ba9 | |||
|
|
611dfa00a5 | ||
|
|
54a195243d | ||
|
|
4fc30fae53 | ||
| b3fe9c65d2 | |||
| 09f1ae8765 | |||
| 0a8b763ece | |||
| edd5f25529 | |||
| d81fdb41dc | |||
| 02c8810e46 | |||
| 6adf8061d3 | |||
| d19d57e5df | |||
| fd82e6c24b | |||
| 56263efa39 | |||
| d5eacba303 | |||
| 222261c674 | |||
| b1a06df7f8 | |||
| a1fc7dd0d1 | |||
|
|
10131d5124 | ||
| aa94959ad2 | |||
| 45fd8a29e1 | |||
| 8c4fab28aa |
2
.gitignore
vendored
@@ -20,3 +20,5 @@ captures/
|
|||||||
docs/
|
docs/
|
||||||
gen/
|
gen/
|
||||||
local.properties
|
local.properties
|
||||||
|
crowdin.yaml
|
||||||
|
local
|
||||||
|
|||||||
32
CHANGELOG.md
@@ -1,5 +1,37 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
### 1.7.3 (May 30, 2017)
|
||||||
|
|
||||||
|
* Improve performance of 'sort by score'
|
||||||
|
* Other minor bug fixes
|
||||||
|
|
||||||
|
### 1.7.2 (May 27, 2017)
|
||||||
|
|
||||||
|
* Fix crash at startup
|
||||||
|
|
||||||
|
### 1.7.1 (May 21, 2017)
|
||||||
|
|
||||||
|
* Fix crash (BadParcelableException)
|
||||||
|
* Fix layout for RTL languages such as Arabic
|
||||||
|
* Automatically detect and reject invalid database files
|
||||||
|
* Add Hebrew translation
|
||||||
|
|
||||||
|
### 1.7.0 (Mar 31, 2017)
|
||||||
|
|
||||||
|
* Sort habits automatically
|
||||||
|
* Allow swiping the header to see previous days
|
||||||
|
* Import backups directly from Google Drive or Dropbox
|
||||||
|
* Refresh data automatically at midnight
|
||||||
|
* Other minor bug fixes and enhancements
|
||||||
|
|
||||||
|
### 1.6.2 (Oct 13, 2016)
|
||||||
|
|
||||||
|
* Fix crash on Android 4.1
|
||||||
|
|
||||||
|
### 1.6.1 (Oct 10, 2016)
|
||||||
|
|
||||||
|
* Fix a crash at startup when database is corrupted
|
||||||
|
|
||||||
### 1.6.0 (Oct 10, 2016)
|
### 1.6.0 (Oct 10, 2016)
|
||||||
|
|
||||||
* Add option to make notifications sticky
|
* Add option to make notifications sticky
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ apply plugin: 'me.tatarka.retrolambda'
|
|||||||
apply plugin: 'jacoco'
|
apply plugin: 'jacoco'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 23
|
compileSdkVersion 25
|
||||||
buildToolsVersion "23.0.3"
|
buildToolsVersion "25.0.2"
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "org.isoron.uhabits"
|
applicationId "org.isoron.uhabits"
|
||||||
minSdkVersion 15
|
minSdkVersion 15
|
||||||
targetSdkVersion 23
|
targetSdkVersion 25
|
||||||
|
|
||||||
buildConfigField "Integer", "databaseVersion", "15"
|
buildConfigField "Integer", "databaseVersion", "15"
|
||||||
buildConfigField "String", "databaseFilename", "\"uhabits.db\""
|
buildConfigField "String", "databaseFilename", "\"uhabits.db\""
|
||||||
@@ -25,7 +25,7 @@ android {
|
|||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||||
}
|
}
|
||||||
debug {
|
debug {
|
||||||
testCoverageEnabled = true
|
testCoverageEnabled = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ dependencies {
|
|||||||
|
|
||||||
androidTestApt 'com.google.dagger:dagger-compiler:2.2'
|
androidTestApt 'com.google.dagger:dagger-compiler:2.2'
|
||||||
|
|
||||||
androidTestCompile 'com.android.support:support-annotations:23.3.0'
|
androidTestCompile 'com.android.support:support-annotations:25.3.0'
|
||||||
androidTestCompile 'com.android.support.test:rules:0.5'
|
androidTestCompile 'com.android.support.test:rules:0.5'
|
||||||
androidTestCompile 'com.android.support.test:runner:0.5'
|
androidTestCompile 'com.android.support.test:runner:0.5'
|
||||||
androidTestCompile 'com.google.auto.factory:auto-factory:1.0-beta3'
|
androidTestCompile 'com.google.auto.factory:auto-factory:1.0-beta3'
|
||||||
@@ -64,10 +64,10 @@ dependencies {
|
|||||||
apt 'com.google.dagger:dagger-compiler:2.2'
|
apt 'com.google.dagger:dagger-compiler:2.2'
|
||||||
apt 'com.jakewharton:butterknife-compiler:8.0.1'
|
apt 'com.jakewharton:butterknife-compiler:8.0.1'
|
||||||
|
|
||||||
compile 'com.android.support:appcompat-v7:23.3.0'
|
compile 'com.android.support:appcompat-v7:25.3.0'
|
||||||
compile 'com.android.support:design:23.3.0'
|
compile 'com.android.support:design:25.3.0'
|
||||||
compile 'com.android.support:preference-v14:23.3.0'
|
compile 'com.android.support:preference-v14:25.3.0'
|
||||||
compile 'com.android.support:support-v4:23.3.0'
|
compile 'com.android.support:support-v4:25.3.0'
|
||||||
compile 'com.getpebble:pebblekit:3.0.0'
|
compile 'com.getpebble:pebblekit:3.0.0'
|
||||||
compile 'com.github.paolorotolo:appintro:3.4.0'
|
compile 'com.github.paolorotolo:appintro:3.4.0'
|
||||||
compile 'com.google.auto.factory:auto-factory:1.0-beta3'
|
compile 'com.google.auto.factory:auto-factory:1.0-beta3'
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import android.content.*;
|
|||||||
import android.os.*;
|
import android.os.*;
|
||||||
import android.support.annotation.*;
|
import android.support.annotation.*;
|
||||||
import android.support.test.*;
|
import android.support.test.*;
|
||||||
|
import android.util.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.models.*;
|
import org.isoron.uhabits.models.*;
|
||||||
import org.isoron.uhabits.preferences.*;
|
import org.isoron.uhabits.preferences.*;
|
||||||
@@ -31,6 +32,7 @@ import org.isoron.uhabits.tasks.*;
|
|||||||
import org.isoron.uhabits.utils.*;
|
import org.isoron.uhabits.utils.*;
|
||||||
import org.junit.*;
|
import org.junit.*;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
@@ -63,6 +65,8 @@ public class BaseAndroidTest
|
|||||||
|
|
||||||
protected AndroidTestComponent component;
|
protected AndroidTestComponent component;
|
||||||
|
|
||||||
|
protected ModelFactory modelFactory;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp()
|
public void setUp()
|
||||||
{
|
{
|
||||||
@@ -89,7 +93,7 @@ public class BaseAndroidTest
|
|||||||
taskRunner = component.getTaskRunner();
|
taskRunner = component.getTaskRunner();
|
||||||
logger = component.getHabitsLogger();
|
logger = component.getHabitsLogger();
|
||||||
|
|
||||||
ModelFactory modelFactory = component.getModelFactory();
|
modelFactory = component.getModelFactory();
|
||||||
fixtures = new HabitFixtures(modelFactory, habitList);
|
fixtures = new HabitFixtures(modelFactory, habitList);
|
||||||
|
|
||||||
latch = new CountDownLatch(1);
|
latch = new CountDownLatch(1);
|
||||||
@@ -130,4 +134,18 @@ public class BaseAndroidTest
|
|||||||
fail();
|
fail();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void startTracing()
|
||||||
|
{
|
||||||
|
File dir = FileUtils.getFilesDir(targetContext, "Profile");
|
||||||
|
assertNotNull(dir);
|
||||||
|
String tracePath = dir.getAbsolutePath() + "/performance.trace";
|
||||||
|
Log.d("PerformanceTest", String.format("Saving trace file to %s", tracePath));
|
||||||
|
Debug.startMethodTracingSampling(tracePath, 0, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void stopTracing()
|
||||||
|
{
|
||||||
|
Debug.stopMethodTracing();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -225,7 +225,7 @@ public class BaseViewTest extends BaseAndroidTest
|
|||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
File dir = FileUtils.getSDCardDir("test-screenshots");
|
File dir = FileUtils.getSDCardDir("test-screenshots");
|
||||||
if (dir == null) dir = FileUtils.getFilesDir("test-screenshots");
|
if (dir == null) dir = FileUtils.getFilesDir(targetContext,"test-screenshots");
|
||||||
if (dir == null) throw new RuntimeException(
|
if (dir == null) throw new RuntimeException(
|
||||||
"Could not find suitable dir for screenshots");
|
"Could not find suitable dir for screenshots");
|
||||||
|
|
||||||
|
|||||||
@@ -39,12 +39,18 @@ public class HabitFixtures
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Habit createEmptyHabit()
|
public Habit createEmptyHabit()
|
||||||
|
{
|
||||||
|
return createEmptyHabit(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Habit createEmptyHabit(Long id)
|
||||||
{
|
{
|
||||||
Habit habit = modelFactory.buildHabit();
|
Habit habit = modelFactory.buildHabit();
|
||||||
habit.setName("Meditate");
|
habit.setName("Meditate");
|
||||||
habit.setDescription("Did you meditate this morning?");
|
habit.setDescription("Did you meditate this morning?");
|
||||||
habit.setColor(3);
|
habit.setColor(3);
|
||||||
habit.setFrequency(Frequency.DAILY);
|
habit.setFrequency(Frequency.DAILY);
|
||||||
|
habit.setId(id);
|
||||||
habitList.add(habit);
|
habitList.add(habit);
|
||||||
return habit;
|
return habit;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,12 +46,12 @@ public class CheckmarkButtonViewTest extends BaseViewTest
|
|||||||
public void setUp()
|
public void setUp()
|
||||||
{
|
{
|
||||||
super.setUp();
|
super.setUp();
|
||||||
setSimilarityCutoff(0.03f);
|
setSimilarityCutoff(0.015f);
|
||||||
|
|
||||||
latch = new CountDownLatch(1);
|
latch = new CountDownLatch(1);
|
||||||
view = new CheckmarkButtonView(targetContext);
|
view = new CheckmarkButtonView(targetContext);
|
||||||
view.setValue(Checkmark.UNCHECKED);
|
view.setValue(Checkmark.UNCHECKED);
|
||||||
view.setColor(ColorUtils.getAndroidTestColor(7));
|
view.setColor(ColorUtils.getAndroidTestColor(5));
|
||||||
|
|
||||||
measureView(view, dpToPixels(40), dpToPixels(40));
|
measureView(view, dpToPixels(40), dpToPixels(40));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ public class CheckmarkPanelViewTest extends BaseViewTest
|
|||||||
view = new CheckmarkPanelView(targetContext);
|
view = new CheckmarkPanelView(targetContext);
|
||||||
view.setHabit(habit);
|
view.setHabit(habit);
|
||||||
view.setCheckmarkValues(checkmarks);
|
view.setCheckmarkValues(checkmarks);
|
||||||
|
view.setButtonCount(4);
|
||||||
view.setColor(ColorUtils.getAndroidTestColor(7));
|
view.setColor(ColorUtils.getAndroidTestColor(7));
|
||||||
|
|
||||||
measureView(view, dpToPixels(200), dpToPixels(200));
|
measureView(view, dpToPixels(200), dpToPixels(200));
|
||||||
|
|||||||
@@ -40,8 +40,6 @@ import static org.junit.Assert.*;
|
|||||||
@MediumTest
|
@MediumTest
|
||||||
public class ImportTest extends BaseAndroidTest
|
public class ImportTest extends BaseAndroidTest
|
||||||
{
|
{
|
||||||
private File baseDir;
|
|
||||||
|
|
||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -50,11 +48,8 @@ public class ImportTest extends BaseAndroidTest
|
|||||||
{
|
{
|
||||||
super.setUp();
|
super.setUp();
|
||||||
DateUtils.setFixedLocalTime(null);
|
DateUtils.setFixedLocalTime(null);
|
||||||
|
|
||||||
fixtures.purgeHabits(habitList);
|
fixtures.purgeHabits(habitList);
|
||||||
context = InstrumentationRegistry.getInstrumentation().getContext();
|
context = InstrumentationRegistry.getInstrumentation().getContext();
|
||||||
baseDir = FileUtils.getFilesDir("Backups");
|
|
||||||
if (baseDir == null) fail("baseDir should not be null");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -149,8 +144,7 @@ public class ImportTest extends BaseAndroidTest
|
|||||||
|
|
||||||
private void importFromFile(String assetFilename) throws IOException
|
private void importFromFile(String assetFilename) throws IOException
|
||||||
{
|
{
|
||||||
File file =
|
File file = File.createTempFile("asset", "");
|
||||||
new File(String.format("%s/%s", baseDir.getPath(), assetFilename));
|
|
||||||
copyAssetToFile(assetFilename, file);
|
copyAssetToFile(assetFilename, file);
|
||||||
assertTrue(file.exists());
|
assertTrue(file.exists());
|
||||||
assertTrue(file.canRead());
|
assertTrue(file.canRead());
|
||||||
@@ -159,5 +153,7 @@ public class ImportTest extends BaseAndroidTest
|
|||||||
assertThat(importer.canHandle(file), is(true));
|
assertThat(importer.canHandle(file), is(true));
|
||||||
|
|
||||||
importer.importHabitsFromFile(file);
|
importer.importHabitsFromFile(file);
|
||||||
|
|
||||||
|
file.delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,240 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of Loop Habit Tracker.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.isoron.uhabits.models;
|
||||||
|
|
||||||
|
import android.support.test.runner.*;
|
||||||
|
import android.test.suitebuilder.annotation.*;
|
||||||
|
|
||||||
|
import org.hamcrest.*;
|
||||||
|
import org.isoron.uhabits.*;
|
||||||
|
import org.junit.*;
|
||||||
|
import org.junit.runner.*;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static junit.framework.Assert.*;
|
||||||
|
import static org.hamcrest.CoreMatchers.*;
|
||||||
|
import static org.hamcrest.MatcherAssert.*;
|
||||||
|
import static org.hamcrest.core.IsEqual.equalTo;
|
||||||
|
import static org.isoron.uhabits.models.HabitList.Order.*;
|
||||||
|
|
||||||
|
@SuppressWarnings("JavaDoc")
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@MediumTest
|
||||||
|
public class HabitListTest extends BaseAndroidTest
|
||||||
|
{
|
||||||
|
private ArrayList<Habit> habitsArray;
|
||||||
|
|
||||||
|
private HabitList activeHabits;
|
||||||
|
|
||||||
|
private HabitList reminderHabits;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUp()
|
||||||
|
{
|
||||||
|
super.setUp();
|
||||||
|
habitList.removeAll();
|
||||||
|
|
||||||
|
habitsArray = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
Habit habit = fixtures.createEmptyHabit((long) i);
|
||||||
|
habitsArray.add(habit);
|
||||||
|
|
||||||
|
if (i % 3 == 0)
|
||||||
|
habit.setReminder(new Reminder(8, 30, WeekdayList.EVERY_DAY));
|
||||||
|
|
||||||
|
habitList.update(habit);
|
||||||
|
}
|
||||||
|
|
||||||
|
habitsArray.get(0).setArchived(true);
|
||||||
|
habitsArray.get(1).setArchived(true);
|
||||||
|
habitsArray.get(4).setArchived(true);
|
||||||
|
habitsArray.get(7).setArchived(true);
|
||||||
|
|
||||||
|
activeHabits = habitList.getFiltered(new HabitMatcherBuilder().build());
|
||||||
|
|
||||||
|
reminderHabits = habitList.getFiltered(new HabitMatcherBuilder()
|
||||||
|
.setArchivedAllowed(true)
|
||||||
|
.setReminderRequired(true)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_size()
|
||||||
|
{
|
||||||
|
assertThat(habitList.size(), equalTo(10));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_countActive()
|
||||||
|
{
|
||||||
|
assertThat(activeHabits.size(), equalTo(6));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_getByPosition()
|
||||||
|
{
|
||||||
|
assertThat(habitList.getByPosition(0), equalTo(habitsArray.get(0)));
|
||||||
|
assertThat(habitList.getByPosition(3), equalTo(habitsArray.get(3)));
|
||||||
|
assertThat(habitList.getByPosition(9), equalTo(habitsArray.get(9)));
|
||||||
|
|
||||||
|
assertThat(activeHabits.getByPosition(0), equalTo(habitsArray.get(2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_getHabitsWithReminder()
|
||||||
|
{
|
||||||
|
assertThat(reminderHabits.size(), equalTo(4));
|
||||||
|
assertThat(reminderHabits.getByPosition(1),
|
||||||
|
equalTo(habitsArray.get(3)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_get_withInvalidId()
|
||||||
|
{
|
||||||
|
assertThat(habitList.getById(100L), is(nullValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_get_withValidId()
|
||||||
|
{
|
||||||
|
Habit habit1 = habitsArray.get(0);
|
||||||
|
Habit habit2 = habitList.getById(habit1.getId());
|
||||||
|
assertThat(habit1, equalTo(habit2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_reorder()
|
||||||
|
{
|
||||||
|
int operations[][] = {
|
||||||
|
{ 5, 2 }, { 3, 7 }, { 4, 4 }, { 3, 2 }
|
||||||
|
};
|
||||||
|
|
||||||
|
int expectedPosition[][] = {
|
||||||
|
{ 0, 1, 3, 4, 5, 2, 6, 7, 8, 9 },
|
||||||
|
{ 0, 1, 7, 3, 4, 2, 5, 6, 8, 9 },
|
||||||
|
{ 0, 1, 7, 3, 4, 2, 5, 6, 8, 9 },
|
||||||
|
{ 0, 1, 7, 2, 4, 3, 5, 6, 8, 9 },
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < operations.length; i++)
|
||||||
|
{
|
||||||
|
int from = operations[i][0];
|
||||||
|
int to = operations[i][1];
|
||||||
|
|
||||||
|
Habit fromHabit = habitList.getByPosition(from);
|
||||||
|
Habit toHabit = habitList.getByPosition(to);
|
||||||
|
habitList.reorder(fromHabit, toHabit);
|
||||||
|
|
||||||
|
int actualPositions[] = new int[10];
|
||||||
|
|
||||||
|
for (int j = 0; j < 10; j++)
|
||||||
|
{
|
||||||
|
Habit h = habitList.getById(j);
|
||||||
|
assertNotNull(h);
|
||||||
|
actualPositions[j] = habitList.indexOf(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(actualPositions, equalTo(expectedPosition[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_writeCSV() throws IOException
|
||||||
|
{
|
||||||
|
habitList.removeAll();
|
||||||
|
|
||||||
|
Habit h1 = fixtures.createEmptyHabit();
|
||||||
|
h1.setName("Meditate");
|
||||||
|
h1.setDescription("Did you meditate this morning?");
|
||||||
|
h1.setFrequency(Frequency.DAILY);
|
||||||
|
h1.setColor(3);
|
||||||
|
|
||||||
|
Habit h2 = fixtures.createEmptyHabit();
|
||||||
|
h2.setName("Wake up early");
|
||||||
|
h2.setDescription("Did you wake up before 6am?");
|
||||||
|
h2.setFrequency(new Frequency(2, 3));
|
||||||
|
h2.setColor(5);
|
||||||
|
|
||||||
|
habitList.update(h1);
|
||||||
|
habitList.update(h2);
|
||||||
|
|
||||||
|
String expectedCSV =
|
||||||
|
"Position,Name,Description,NumRepetitions,Interval,Color\n" +
|
||||||
|
"001,Meditate,Did you meditate this morning?,1,1,#AFB42B\n" +
|
||||||
|
"002,Wake up early,Did you wake up before 6am?,2,3,#00897B\n";
|
||||||
|
|
||||||
|
StringWriter writer = new StringWriter();
|
||||||
|
habitList.writeCSV(writer);
|
||||||
|
|
||||||
|
MatcherAssert.assertThat(writer.toString(), equalTo(expectedCSV));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_ordering()
|
||||||
|
{
|
||||||
|
habitList.removeAll();
|
||||||
|
|
||||||
|
Habit h3 = fixtures.createEmptyHabit();
|
||||||
|
h3.setName("C Habit");
|
||||||
|
h3.setColor(0);
|
||||||
|
habitList.update(h3);
|
||||||
|
|
||||||
|
Habit h1 = fixtures.createEmptyHabit();
|
||||||
|
h1.setName("A Habit");
|
||||||
|
h1.setColor(2);
|
||||||
|
habitList.update(h1);
|
||||||
|
|
||||||
|
Habit h4 = fixtures.createEmptyHabit();
|
||||||
|
h4.setName("D Habit");
|
||||||
|
h4.setColor(1);
|
||||||
|
habitList.update(h4);
|
||||||
|
|
||||||
|
Habit h2 = fixtures.createEmptyHabit();
|
||||||
|
h2.setName("B Habit");
|
||||||
|
h2.setColor(2);
|
||||||
|
habitList.update(h2);
|
||||||
|
|
||||||
|
habitList.setOrder(BY_POSITION);
|
||||||
|
assertThat(habitList.getByPosition(0), equalTo(h3));
|
||||||
|
assertThat(habitList.getByPosition(1), equalTo(h1));
|
||||||
|
assertThat(habitList.getByPosition(2), equalTo(h4));
|
||||||
|
assertThat(habitList.getByPosition(3), equalTo(h2));
|
||||||
|
|
||||||
|
habitList.setOrder(BY_NAME);
|
||||||
|
assertThat(habitList.getByPosition(0), equalTo(h1));
|
||||||
|
assertThat(habitList.getByPosition(1), equalTo(h2));
|
||||||
|
assertThat(habitList.getByPosition(2), equalTo(h3));
|
||||||
|
assertThat(habitList.getByPosition(3), equalTo(h4));
|
||||||
|
|
||||||
|
habitList.remove(h1);
|
||||||
|
habitList.add(h1);
|
||||||
|
assertThat(habitList.getByPosition(0), equalTo(h1));
|
||||||
|
|
||||||
|
habitList.setOrder(BY_COLOR);
|
||||||
|
assertThat(habitList.getByPosition(0), equalTo(h3));
|
||||||
|
assertThat(habitList.getByPosition(1), equalTo(h4));
|
||||||
|
assertThat(habitList.getByPosition(2), equalTo(h1));
|
||||||
|
assertThat(habitList.getByPosition(3), equalTo(h2));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -125,17 +125,6 @@ public class SQLiteHabitListTest extends BaseAndroidTest
|
|||||||
assertThat(habits.get(3).getName(), equalTo("habit 3"));
|
assertThat(habits.get(3).getName(), equalTo("habit 3"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Test
|
|
||||||
// public void testGetAll_withoutArchived()
|
|
||||||
// {
|
|
||||||
// List<Habit> habits = habitList.toList();
|
|
||||||
// assertThat(habits.size(), equalTo(5));
|
|
||||||
// assertThat(habits.get(3).getName(), equalTo("habit 7"));
|
|
||||||
//
|
|
||||||
// List<Habit> another = habitList.toList();
|
|
||||||
// assertThat(habits, equalTo(another));
|
|
||||||
// }
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetById()
|
public void testGetById()
|
||||||
{
|
{
|
||||||
@@ -178,45 +167,6 @@ public class SQLiteHabitListTest extends BaseAndroidTest
|
|||||||
assertThat(habitList.indexOf(h2), equalTo(-1));
|
assertThat(habitList.indexOf(h2), equalTo(-1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_reorder()
|
|
||||||
{
|
|
||||||
// Same as HabitListTest.java
|
|
||||||
// TODO: remove duplication
|
|
||||||
|
|
||||||
int operations[][] = {
|
|
||||||
{5, 2}, {3, 7}, {4, 4}, {3, 2}
|
|
||||||
};
|
|
||||||
|
|
||||||
int expectedPosition[][] = {
|
|
||||||
{0, 1, 3, 4, 5, 2, 6, 7, 8, 9},
|
|
||||||
{0, 1, 7, 3, 4, 2, 5, 6, 8, 9},
|
|
||||||
{0, 1, 7, 3, 4, 2, 5, 6, 8, 9},
|
|
||||||
{0, 1, 7, 2, 4, 3, 5, 6, 8, 9},
|
|
||||||
};
|
|
||||||
|
|
||||||
for (int i = 0; i < operations.length; i++)
|
|
||||||
{
|
|
||||||
int from = operations[i][0];
|
|
||||||
int to = operations[i][1];
|
|
||||||
|
|
||||||
Habit fromHabit = habitList.getByPosition(from);
|
|
||||||
Habit toHabit = habitList.getByPosition(to);
|
|
||||||
habitList.reorder(fromHabit, toHabit);
|
|
||||||
|
|
||||||
int actualPositions[] = new int[10];
|
|
||||||
|
|
||||||
for (int j = 0; j < 10; j++)
|
|
||||||
{
|
|
||||||
Habit h = habitList.getById(j);
|
|
||||||
assertNotNull(h);
|
|
||||||
actualPositions[j] = habitList.indexOf(h);
|
|
||||||
}
|
|
||||||
|
|
||||||
assertThat(actualPositions, equalTo(expectedPosition[i]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private HabitRecord getRecord(long id)
|
private HabitRecord getRecord(long id)
|
||||||
{
|
{
|
||||||
return new Select()
|
return new Select()
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of Loop Habit Tracker.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.isoron.uhabits.performance;
|
||||||
|
|
||||||
|
import android.support.test.filters.*;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.*;
|
||||||
|
import org.isoron.uhabits.models.*;
|
||||||
|
import org.junit.*;
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
public class PerformanceTest extends BaseAndroidTest
|
||||||
|
{
|
||||||
|
private Habit habit;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUp()
|
||||||
|
{
|
||||||
|
super.setUp();
|
||||||
|
habit = fixtures.createLongHabit();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 1000)
|
||||||
|
public void testRepeatedGetTodayValue()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 100000; i++)
|
||||||
|
{
|
||||||
|
habit.getScores().getTodayValue();
|
||||||
|
habit.getCheckmarks().getTodayValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -56,7 +56,7 @@ public class ExportCSVTaskTest extends BaseAndroidTest
|
|||||||
for (Habit h : habitList) selected.add(h);
|
for (Habit h : habitList) selected.add(h);
|
||||||
|
|
||||||
taskRunner.execute(
|
taskRunner.execute(
|
||||||
new ExportCSVTask(habitList, selected, archiveFilename -> {
|
new ExportCSVTask(targetContext,habitList, selected, archiveFilename -> {
|
||||||
assertThat(archiveFilename, is(not(nullValue())));
|
assertThat(archiveFilename, is(not(nullValue())));
|
||||||
File f = new File(archiveFilename);
|
File f = new File(archiveFilename);
|
||||||
assertTrue(f.exists());
|
assertTrue(f.exists());
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ public class ExportDBTaskTest extends BaseAndroidTest
|
|||||||
@Test
|
@Test
|
||||||
public void testExportCSV() throws Throwable
|
public void testExportCSV() throws Throwable
|
||||||
{
|
{
|
||||||
ExportDBTask task = new ExportDBTask(filename -> {
|
ExportDBTask task = new ExportDBTask(targetContext, filename -> {
|
||||||
assertThat(filename, is(not(nullValue())));
|
assertThat(filename, is(not(nullValue())));
|
||||||
|
|
||||||
File f = new File(filename);
|
File f = new File(filename);
|
||||||
|
|||||||
@@ -21,8 +21,8 @@
|
|||||||
<manifest
|
<manifest
|
||||||
package="org.isoron.uhabits"
|
package="org.isoron.uhabits"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:versionCode="25"
|
android:versionCode="30"
|
||||||
android:versionName="1.6.2">
|
android:versionName="1.7.3">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||||
|
|
||||||
@@ -223,5 +223,15 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name="android.support.v4.content.FileProvider"
|
||||||
|
android:authorities="org.isoron.uhabits"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/file_paths" />
|
||||||
|
</provider>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
@@ -21,10 +21,10 @@ package org.isoron.uhabits;
|
|||||||
|
|
||||||
import android.app.*;
|
import android.app.*;
|
||||||
import android.content.*;
|
import android.content.*;
|
||||||
import android.support.annotation.*;
|
|
||||||
|
|
||||||
import com.activeandroid.*;
|
import com.activeandroid.*;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.models.sqlite.*;
|
||||||
import org.isoron.uhabits.notifications.*;
|
import org.isoron.uhabits.notifications.*;
|
||||||
import org.isoron.uhabits.preferences.*;
|
import org.isoron.uhabits.preferences.*;
|
||||||
import org.isoron.uhabits.tasks.*;
|
import org.isoron.uhabits.tasks.*;
|
||||||
@@ -38,7 +38,7 @@ import java.io.*;
|
|||||||
*/
|
*/
|
||||||
public class HabitsApplication extends Application
|
public class HabitsApplication extends Application
|
||||||
{
|
{
|
||||||
private static Context context;
|
private Context context;
|
||||||
|
|
||||||
private static AppComponent component;
|
private static AppComponent component;
|
||||||
|
|
||||||
@@ -58,26 +58,14 @@ public class HabitsApplication extends Application
|
|||||||
HabitsApplication.component = component;
|
HabitsApplication.component = component;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Deprecated
|
|
||||||
public static Context getContext()
|
|
||||||
{
|
|
||||||
if (context == null) throw new RuntimeException("context is null");
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isTestMode()
|
public static boolean isTestMode()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (context != null)
|
Class.forName ("org.isoron.uhabits.BaseAndroidTest");
|
||||||
{
|
|
||||||
String testClass = "org.isoron.uhabits.BaseAndroidTest";
|
|
||||||
context.getClassLoader().loadClass(testClass);
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (final Exception e)
|
catch (final ClassNotFoundException e)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -87,7 +75,7 @@ public class HabitsApplication extends Application
|
|||||||
public void onCreate()
|
public void onCreate()
|
||||||
{
|
{
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
HabitsApplication.context = this;
|
context = this;
|
||||||
|
|
||||||
component = DaggerAppComponent
|
component = DaggerAppComponent
|
||||||
.builder()
|
.builder()
|
||||||
@@ -96,11 +84,20 @@ public class HabitsApplication extends Application
|
|||||||
|
|
||||||
if (isTestMode())
|
if (isTestMode())
|
||||||
{
|
{
|
||||||
File db = DatabaseUtils.getDatabaseFile();
|
File db = DatabaseUtils.getDatabaseFile(context);
|
||||||
if (db.exists()) db.delete();
|
if (db.exists()) db.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
DatabaseUtils.initializeActiveAndroid();
|
try
|
||||||
|
{
|
||||||
|
DatabaseUtils.initializeActiveAndroid(context);
|
||||||
|
}
|
||||||
|
catch (InvalidDatabaseVersionException e)
|
||||||
|
{
|
||||||
|
File db = DatabaseUtils.getDatabaseFile(context);
|
||||||
|
db.renameTo(new File(db.getAbsolutePath() + ".invalid"));
|
||||||
|
DatabaseUtils.initializeActiveAndroid(context);
|
||||||
|
}
|
||||||
|
|
||||||
widgetUpdater = component.getWidgetUpdater();
|
widgetUpdater = component.getWidgetUpdater();
|
||||||
widgetUpdater.startListening();
|
widgetUpdater.startListening();
|
||||||
@@ -125,7 +122,7 @@ public class HabitsApplication extends Application
|
|||||||
@Override
|
@Override
|
||||||
public void onTerminate()
|
public void onTerminate()
|
||||||
{
|
{
|
||||||
HabitsApplication.context = null;
|
context = null;
|
||||||
ActiveAndroid.dispose();
|
ActiveAndroid.dispose();
|
||||||
|
|
||||||
reminderScheduler.stopListening();
|
reminderScheduler.stopListening();
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import java.io.*;
|
|||||||
|
|
||||||
import static android.os.Build.VERSION.*;
|
import static android.os.Build.VERSION.*;
|
||||||
import static android.os.Build.VERSION_CODES.*;
|
import static android.os.Build.VERSION_CODES.*;
|
||||||
|
import static android.support.v4.content.FileProvider.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for all screens in the application.
|
* Base class for all screens in the application.
|
||||||
@@ -50,6 +51,8 @@ import static android.os.Build.VERSION_CODES.*;
|
|||||||
*/
|
*/
|
||||||
public class BaseScreen
|
public class BaseScreen
|
||||||
{
|
{
|
||||||
|
public static final int REQUEST_CREATE_DOCUMENT = 1;
|
||||||
|
|
||||||
protected BaseActivity activity;
|
protected BaseActivity activity;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -230,11 +233,14 @@ public class BaseScreen
|
|||||||
|
|
||||||
public void showSendFileScreen(@NonNull String archiveFilename)
|
public void showSendFileScreen(@NonNull String archiveFilename)
|
||||||
{
|
{
|
||||||
|
File file = new File(archiveFilename);
|
||||||
|
Uri fileUri = getUriForFile(activity, "org.isoron.uhabits", file);
|
||||||
|
|
||||||
Intent intent = new Intent();
|
Intent intent = new Intent();
|
||||||
intent.setAction(Intent.ACTION_SEND);
|
intent.setAction(Intent.ACTION_SEND);
|
||||||
intent.setType("application/zip");
|
intent.setType("application/zip");
|
||||||
intent.putExtra(Intent.EXTRA_STREAM,
|
intent.putExtra(Intent.EXTRA_STREAM, fileUri);
|
||||||
Uri.fromFile(new File(archiveFilename)));
|
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
activity.startActivity(intent);
|
activity.startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ public class BaseSystem
|
|||||||
|
|
||||||
if (context == null) throw new RuntimeException(
|
if (context == null) throw new RuntimeException(
|
||||||
"application context should not be null");
|
"application context should not be null");
|
||||||
File dir = FileUtils.getFilesDir("Logs");
|
File dir = FileUtils.getFilesDir(context, "Logs");
|
||||||
if (dir == null) throw new IOException("log dir should not be null");
|
if (dir == null) throw new IOException("log dir should not be null");
|
||||||
|
|
||||||
File logFile =
|
File logFile =
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import android.support.v7.widget.Toolbar;
|
|||||||
import android.widget.*;
|
import android.widget.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.BuildConfig;
|
import org.isoron.uhabits.BuildConfig;
|
||||||
import org.isoron.uhabits.*;
|
import org.isoron.uhabits.R;
|
||||||
import org.isoron.uhabits.activities.*;
|
import org.isoron.uhabits.activities.*;
|
||||||
import org.isoron.uhabits.intents.*;
|
import org.isoron.uhabits.intents.*;
|
||||||
import org.isoron.uhabits.utils.*;
|
import org.isoron.uhabits.utils.*;
|
||||||
@@ -94,6 +94,13 @@ public class AboutRootView extends BaseRootView
|
|||||||
getContext().startActivity(intent);
|
getContext().startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.tvTranslate)
|
||||||
|
public void onClickTranslate()
|
||||||
|
{
|
||||||
|
Intent intent = intents.helpTranslate(getContext());
|
||||||
|
getContext().startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
@OnClick(R.id.tvRate)
|
@OnClick(R.id.tvRate)
|
||||||
public void onClickRate()
|
public void onClickRate()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import android.support.v7.app.*;
|
|||||||
|
|
||||||
import com.google.auto.factory.*;
|
import com.google.auto.factory.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
import org.isoron.uhabits.R;
|
||||||
import org.isoron.uhabits.activities.*;
|
import org.isoron.uhabits.activities.*;
|
||||||
|
|
||||||
import butterknife.*;
|
import butterknife.*;
|
||||||
|
|||||||
@@ -78,6 +78,8 @@ public class HistoryEditorDialog extends AppCompatDialogFragment
|
|||||||
{
|
{
|
||||||
long id = savedInstanceState.getLong("habit", -1);
|
long id = savedInstanceState.getLong("habit", -1);
|
||||||
if (id > 0) this.habit = habitList.getById(id);
|
if (id > 0) this.habit = habitList.getById(id);
|
||||||
|
historyChart.onRestoreInstanceState(
|
||||||
|
savedInstanceState.getParcelable("historyChart"));
|
||||||
}
|
}
|
||||||
|
|
||||||
int padding =
|
int padding =
|
||||||
@@ -129,6 +131,7 @@ public class HistoryEditorDialog extends AppCompatDialogFragment
|
|||||||
public void onSaveInstanceState(Bundle outState)
|
public void onSaveInstanceState(Bundle outState)
|
||||||
{
|
{
|
||||||
outState.putLong("habit", habit.getId());
|
outState.putLong("habit", habit.getId());
|
||||||
|
outState.putParcelable("historyChart", historyChart.onSaveInstanceState());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setController(@NonNull Controller controller)
|
public void setController(@NonNull Controller controller)
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of Loop Habit Tracker.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.isoron.uhabits.activities.common.views;
|
||||||
|
|
||||||
|
import android.os.*;
|
||||||
|
import android.support.v4.os.*;
|
||||||
|
|
||||||
|
public class BundleSavedState extends android.support.v4.view.AbsSavedState
|
||||||
|
{
|
||||||
|
public static final Parcelable.Creator<BundleSavedState> CREATOR =
|
||||||
|
ParcelableCompat.newCreator(
|
||||||
|
new ParcelableCompatCreatorCallbacks<BundleSavedState>()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public BundleSavedState createFromParcel(Parcel source,
|
||||||
|
ClassLoader loader)
|
||||||
|
{
|
||||||
|
return new BundleSavedState(source, loader);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BundleSavedState[] newArray(int size)
|
||||||
|
{
|
||||||
|
return new BundleSavedState[size];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
public final Bundle bundle;
|
||||||
|
|
||||||
|
public BundleSavedState(Parcelable superState, Bundle bundle)
|
||||||
|
{
|
||||||
|
super(superState);
|
||||||
|
this.bundle = bundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BundleSavedState(Parcel source, ClassLoader loader)
|
||||||
|
{
|
||||||
|
super(source, loader);
|
||||||
|
this.bundle = source.readBundle(loader);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel out, int flags)
|
||||||
|
{
|
||||||
|
super.writeToParcel(out, flags);
|
||||||
|
out.writeBundle(bundle);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ package org.isoron.uhabits.activities.common.views;
|
|||||||
|
|
||||||
import android.animation.*;
|
import android.animation.*;
|
||||||
import android.content.*;
|
import android.content.*;
|
||||||
|
import android.os.*;
|
||||||
import android.util.*;
|
import android.util.*;
|
||||||
import android.view.*;
|
import android.view.*;
|
||||||
import android.widget.*;
|
import android.widget.*;
|
||||||
@@ -32,7 +33,9 @@ public abstract class ScrollableChart extends View
|
|||||||
|
|
||||||
private int dataOffset;
|
private int dataOffset;
|
||||||
|
|
||||||
private int scrollerBucketSize;
|
private int scrollerBucketSize = 1;
|
||||||
|
|
||||||
|
private int direction = 1;
|
||||||
|
|
||||||
private GestureDetector detector;
|
private GestureDetector detector;
|
||||||
|
|
||||||
@@ -40,6 +43,10 @@ public abstract class ScrollableChart extends View
|
|||||||
|
|
||||||
private ValueAnimator scrollAnimator;
|
private ValueAnimator scrollAnimator;
|
||||||
|
|
||||||
|
private ScrollController scrollController;
|
||||||
|
|
||||||
|
private int maxDataOffset = 10000;
|
||||||
|
|
||||||
public ScrollableChart(Context context)
|
public ScrollableChart(Context context)
|
||||||
{
|
{
|
||||||
super(context);
|
super(context);
|
||||||
@@ -63,8 +70,7 @@ public abstract class ScrollableChart extends View
|
|||||||
if (!scroller.isFinished())
|
if (!scroller.isFinished())
|
||||||
{
|
{
|
||||||
scroller.computeScrollOffset();
|
scroller.computeScrollOffset();
|
||||||
dataOffset = Math.max(0, scroller.getCurrX() / scrollerBucketSize);
|
updateDataOffset();
|
||||||
postInvalidate();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -85,19 +91,50 @@ public abstract class ScrollableChart extends View
|
|||||||
float velocityY)
|
float velocityY)
|
||||||
{
|
{
|
||||||
scroller.fling(scroller.getCurrX(), scroller.getCurrY(),
|
scroller.fling(scroller.getCurrX(), scroller.getCurrY(),
|
||||||
(int) velocityX / 2, 0, 0, 100000, 0, 0);
|
direction * ((int) velocityX) / 2, 0, 0, getMaxX(), 0, 0);
|
||||||
invalidate();
|
invalidate();
|
||||||
|
|
||||||
scrollAnimator.setDuration(scroller.getDuration());
|
scrollAnimator.setDuration(scroller.getDuration());
|
||||||
scrollAnimator.start();
|
scrollAnimator.start();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private int getMaxX()
|
||||||
public void onLongPress(MotionEvent e)
|
|
||||||
{
|
{
|
||||||
|
return maxDataOffset * scrollerBucketSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRestoreInstanceState(Parcelable state)
|
||||||
|
{
|
||||||
|
if(!(state instanceof BundleSavedState))
|
||||||
|
{
|
||||||
|
super.onRestoreInstanceState(state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BundleSavedState bss = (BundleSavedState) state;
|
||||||
|
int x = bss.bundle.getInt("x");
|
||||||
|
int y = bss.bundle.getInt("y");
|
||||||
|
direction = bss.bundle.getInt("direction");
|
||||||
|
dataOffset = bss.bundle.getInt("dataOffset");
|
||||||
|
maxDataOffset = bss.bundle.getInt("maxDataOffset");
|
||||||
|
scroller.startScroll(0, 0, x, y, 0);
|
||||||
|
scroller.computeScrollOffset();
|
||||||
|
super.onRestoreInstanceState(bss.getSuperState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Parcelable onSaveInstanceState()
|
||||||
|
{
|
||||||
|
Parcelable superState = super.onSaveInstanceState();
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putInt("x", scroller.getCurrX());
|
||||||
|
bundle.putInt("y", scroller.getCurrY());
|
||||||
|
bundle.putInt("dataOffset", dataOffset);
|
||||||
|
bundle.putInt("direction", direction);
|
||||||
|
bundle.putInt("maxDataOffset", maxDataOffset);
|
||||||
|
return new BundleSavedState(superState, bundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -111,12 +148,14 @@ public abstract class ScrollableChart extends View
|
|||||||
if (parent != null) parent.requestDisallowInterceptTouchEvent(true);
|
if (parent != null) parent.requestDisallowInterceptTouchEvent(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
scroller.startScroll(scroller.getCurrX(), scroller.getCurrY(),
|
|
||||||
(int) -dx, (int) dy, 0);
|
|
||||||
scroller.computeScrollOffset();
|
|
||||||
dataOffset = Math.max(0, scroller.getCurrX() / scrollerBucketSize);
|
|
||||||
postInvalidate();
|
|
||||||
|
|
||||||
|
dx = - direction * dx;
|
||||||
|
dx = Math.min(dx, getMaxX() - scroller.getCurrX());
|
||||||
|
scroller.startScroll(scroller.getCurrX(), scroller.getCurrY(), (int) dx,
|
||||||
|
(int) dy, 0);
|
||||||
|
|
||||||
|
scroller.computeScrollOffset();
|
||||||
|
updateDataOffset();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,6 +177,32 @@ public abstract class ScrollableChart extends View
|
|||||||
return detector.onTouchEvent(event);
|
return detector.onTouchEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDirection(int direction)
|
||||||
|
{
|
||||||
|
if (direction != 1 && direction != -1)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
this.direction = direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLongPress(MotionEvent e)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxDataOffset(int maxDataOffset)
|
||||||
|
{
|
||||||
|
this.maxDataOffset = maxDataOffset;
|
||||||
|
this.dataOffset = Math.min(dataOffset, maxDataOffset);
|
||||||
|
scrollController.onDataOffsetChanged(this.dataOffset);
|
||||||
|
postInvalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScrollController(ScrollController scrollController)
|
||||||
|
{
|
||||||
|
this.scrollController = scrollController;
|
||||||
|
}
|
||||||
|
|
||||||
public void setScrollerBucketSize(int scrollerBucketSize)
|
public void setScrollerBucketSize(int scrollerBucketSize)
|
||||||
{
|
{
|
||||||
this.scrollerBucketSize = scrollerBucketSize;
|
this.scrollerBucketSize = scrollerBucketSize;
|
||||||
@@ -149,5 +214,25 @@ public abstract class ScrollableChart extends View
|
|||||||
scroller = new Scroller(context, null, true);
|
scroller = new Scroller(context, null, true);
|
||||||
scrollAnimator = ValueAnimator.ofFloat(0, 1);
|
scrollAnimator = ValueAnimator.ofFloat(0, 1);
|
||||||
scrollAnimator.addUpdateListener(this);
|
scrollAnimator.addUpdateListener(this);
|
||||||
|
scrollController = new ScrollController() {};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateDataOffset()
|
||||||
|
{
|
||||||
|
int newDataOffset = scroller.getCurrX() / scrollerBucketSize;
|
||||||
|
newDataOffset = Math.max(0, newDataOffset);
|
||||||
|
newDataOffset = Math.min(maxDataOffset, newDataOffset);
|
||||||
|
|
||||||
|
if (newDataOffset != dataOffset)
|
||||||
|
{
|
||||||
|
dataOffset = newDataOffset;
|
||||||
|
scrollController.onDataOffsetChanged(dataOffset);
|
||||||
|
postInvalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ScrollController
|
||||||
|
{
|
||||||
|
default void onDataOffsetChanged(int newDataOffset) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import android.view.*;
|
|||||||
import com.android.datetimepicker.time.*;
|
import com.android.datetimepicker.time.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
import org.isoron.uhabits.*;
|
||||||
|
import org.isoron.uhabits.R;
|
||||||
import org.isoron.uhabits.activities.*;
|
import org.isoron.uhabits.activities.*;
|
||||||
import org.isoron.uhabits.activities.common.dialogs.*;
|
import org.isoron.uhabits.activities.common.dialogs.*;
|
||||||
import org.isoron.uhabits.commands.*;
|
import org.isoron.uhabits.commands.*;
|
||||||
@@ -38,6 +39,8 @@ import java.util.*;
|
|||||||
|
|
||||||
import butterknife.*;
|
import butterknife.*;
|
||||||
|
|
||||||
|
import static org.isoron.uhabits.activities.ThemeSwitcher.*;
|
||||||
|
|
||||||
public abstract class BaseDialog extends AppCompatDialogFragment
|
public abstract class BaseDialog extends AppCompatDialogFragment
|
||||||
{
|
{
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -61,6 +64,18 @@ public abstract class BaseDialog extends AppCompatDialogFragment
|
|||||||
|
|
||||||
private ColorPickerDialogFactory colorPickerDialogFactory;
|
private ColorPickerDialogFactory colorPickerDialogFactory;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTheme()
|
||||||
|
{
|
||||||
|
AppComponent component =
|
||||||
|
((HabitsApplication) getContext().getApplicationContext()).getComponent();
|
||||||
|
|
||||||
|
if(component.getPreferences().getTheme() == THEME_LIGHT)
|
||||||
|
return R.style.DialogWithTitle;
|
||||||
|
else
|
||||||
|
return R.style.DarkDialogWithTitle;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityCreated(Bundle savedInstanceState)
|
public void onActivityCreated(Bundle savedInstanceState)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import android.support.v4.app.*;
|
|||||||
import android.view.*;
|
import android.view.*;
|
||||||
import android.widget.*;
|
import android.widget.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
import org.isoron.uhabits.R;
|
||||||
import org.isoron.uhabits.models.*;
|
import org.isoron.uhabits.models.*;
|
||||||
import org.isoron.uhabits.utils.*;
|
import org.isoron.uhabits.utils.*;
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import org.isoron.uhabits.*;
|
|||||||
import org.isoron.uhabits.activities.*;
|
import org.isoron.uhabits.activities.*;
|
||||||
import org.isoron.uhabits.activities.habits.list.model.*;
|
import org.isoron.uhabits.activities.habits.list.model.*;
|
||||||
import org.isoron.uhabits.preferences.*;
|
import org.isoron.uhabits.preferences.*;
|
||||||
|
import org.isoron.uhabits.utils.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activity that allows the user to see and modify the list of habits.
|
* Activity that allows the user to see and modify the list of habits.
|
||||||
@@ -43,6 +44,8 @@ public class ListHabitsActivity extends BaseActivity
|
|||||||
|
|
||||||
private Preferences prefs;
|
private Preferences prefs;
|
||||||
|
|
||||||
|
private MidnightTimer midnightTimer;
|
||||||
|
|
||||||
public ListHabitsComponent getListHabitsComponent()
|
public ListHabitsComponent getListHabitsComponent()
|
||||||
{
|
{
|
||||||
return component;
|
return component;
|
||||||
@@ -77,6 +80,8 @@ public class ListHabitsActivity extends BaseActivity
|
|||||||
screen.setSelectionMenu(selectionMenu);
|
screen.setSelectionMenu(selectionMenu);
|
||||||
rootView.setController(controller, selectionMenu);
|
rootView.setController(controller, selectionMenu);
|
||||||
|
|
||||||
|
midnightTimer = component.getMidnightTimer();
|
||||||
|
|
||||||
setScreen(screen);
|
setScreen(screen);
|
||||||
controller.onStartup();
|
controller.onStartup();
|
||||||
}
|
}
|
||||||
@@ -84,6 +89,7 @@ public class ListHabitsActivity extends BaseActivity
|
|||||||
@Override
|
@Override
|
||||||
protected void onPause()
|
protected void onPause()
|
||||||
{
|
{
|
||||||
|
midnightTimer.onPause();
|
||||||
screen.onDettached();
|
screen.onDettached();
|
||||||
adapter.cancelRefresh();
|
adapter.cancelRefresh();
|
||||||
super.onPause();
|
super.onPause();
|
||||||
@@ -95,6 +101,7 @@ public class ListHabitsActivity extends BaseActivity
|
|||||||
adapter.refresh();
|
adapter.refresh();
|
||||||
screen.onAttached();
|
screen.onAttached();
|
||||||
rootView.postInvalidate();
|
rootView.postInvalidate();
|
||||||
|
midnightTimer.onResume();
|
||||||
|
|
||||||
if (prefs.getTheme() == ThemeSwitcher.THEME_DARK &&
|
if (prefs.getTheme() == ThemeSwitcher.THEME_DARK &&
|
||||||
prefs.isPureBlackEnabled() != pureBlack)
|
prefs.isPureBlackEnabled() != pureBlack)
|
||||||
|
|||||||
@@ -23,13 +23,14 @@ import org.isoron.uhabits.*;
|
|||||||
import org.isoron.uhabits.activities.*;
|
import org.isoron.uhabits.activities.*;
|
||||||
import org.isoron.uhabits.activities.habits.list.controllers.*;
|
import org.isoron.uhabits.activities.habits.list.controllers.*;
|
||||||
import org.isoron.uhabits.activities.habits.list.model.*;
|
import org.isoron.uhabits.activities.habits.list.model.*;
|
||||||
|
import org.isoron.uhabits.utils.*;
|
||||||
|
|
||||||
import dagger.*;
|
import dagger.*;
|
||||||
|
|
||||||
@ActivityScope
|
@ActivityScope
|
||||||
@Component(modules = { ActivityModule.class },
|
@Component(modules = { ActivityModule.class },
|
||||||
dependencies = { AppComponent.class })
|
dependencies = { AppComponent.class })
|
||||||
public interface ListHabitsComponent extends ActivityComponent
|
public interface ListHabitsComponent
|
||||||
{
|
{
|
||||||
CheckmarkButtonControllerFactory getCheckmarkButtonControllerFactory();
|
CheckmarkButtonControllerFactory getCheckmarkButtonControllerFactory();
|
||||||
|
|
||||||
@@ -44,4 +45,6 @@ public interface ListHabitsComponent extends ActivityComponent
|
|||||||
ListHabitsScreen getScreen();
|
ListHabitsScreen getScreen();
|
||||||
|
|
||||||
ListHabitsSelectionMenu getSelectionMenu();
|
ListHabitsSelectionMenu getSelectionMenu();
|
||||||
|
|
||||||
|
MidnightTimer getMidnightTimer();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import javax.inject.*;
|
|||||||
public class ListHabitsController
|
public class ListHabitsController
|
||||||
implements HabitCardListController.HabitListener
|
implements HabitCardListController.HabitListener
|
||||||
{
|
{
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private final ListHabitsScreen screen;
|
private final ListHabitsScreen screen;
|
||||||
|
|
||||||
@@ -70,6 +71,8 @@ public class ListHabitsController
|
|||||||
|
|
||||||
private ExportCSVTaskFactory exportCSVFactory;
|
private ExportCSVTaskFactory exportCSVFactory;
|
||||||
|
|
||||||
|
private ExportDBTaskFactory exportDBFactory;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ListHabitsController(@NonNull BaseSystem system,
|
public ListHabitsController(@NonNull BaseSystem system,
|
||||||
@NonNull CommandRunner commandRunner,
|
@NonNull CommandRunner commandRunner,
|
||||||
@@ -82,7 +85,8 @@ public class ListHabitsController
|
|||||||
@NonNull WidgetUpdater widgetUpdater,
|
@NonNull WidgetUpdater widgetUpdater,
|
||||||
@NonNull
|
@NonNull
|
||||||
ImportDataTaskFactory importTaskFactory,
|
ImportDataTaskFactory importTaskFactory,
|
||||||
@NonNull ExportCSVTaskFactory exportCSVFactory)
|
@NonNull ExportCSVTaskFactory exportCSVFactory,
|
||||||
|
@NonNull ExportDBTaskFactory exportDBFactory)
|
||||||
{
|
{
|
||||||
this.adapter = adapter;
|
this.adapter = adapter;
|
||||||
this.commandRunner = commandRunner;
|
this.commandRunner = commandRunner;
|
||||||
@@ -95,6 +99,7 @@ public class ListHabitsController
|
|||||||
this.widgetUpdater = widgetUpdater;
|
this.widgetUpdater = widgetUpdater;
|
||||||
this.importTaskFactory = importTaskFactory;
|
this.importTaskFactory = importTaskFactory;
|
||||||
this.exportCSVFactory = exportCSVFactory;
|
this.exportCSVFactory = exportCSVFactory;
|
||||||
|
this.exportDBFactory = exportDBFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onExportCSV()
|
public void onExportCSV()
|
||||||
@@ -110,7 +115,7 @@ public class ListHabitsController
|
|||||||
|
|
||||||
public void onExportDB()
|
public void onExportDB()
|
||||||
{
|
{
|
||||||
taskRunner.execute(new ExportDBTask(filename -> {
|
taskRunner.execute(exportDBFactory.create(filename -> {
|
||||||
if (filename != null) screen.showSendFileScreen(filename);
|
if (filename != null) screen.showSendFileScreen(filename);
|
||||||
else screen.showMessage(R.string.could_not_export);
|
else screen.showMessage(R.string.could_not_export);
|
||||||
}));
|
}));
|
||||||
@@ -128,7 +133,8 @@ public class ListHabitsController
|
|||||||
taskRunner.execute(() -> habitList.reorder(from, to));
|
taskRunner.execute(() -> habitList.reorder(from, to));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onImportData(@NonNull File file)
|
public void onImportData(@NonNull File file,
|
||||||
|
@NonNull OnFinishedListener finishedListener)
|
||||||
{
|
{
|
||||||
taskRunner.execute(importTaskFactory.create(file, result -> {
|
taskRunner.execute(importTaskFactory.create(file, result -> {
|
||||||
switch (result)
|
switch (result)
|
||||||
@@ -146,6 +152,8 @@ public class ListHabitsController
|
|||||||
screen.showMessage(R.string.could_not_import);
|
screen.showMessage(R.string.could_not_import);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
finishedListener.onFinish();
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,4 +216,9 @@ public class ListHabitsController
|
|||||||
prefs.updateLastHint(-1, DateUtils.getStartOfToday());
|
prefs.updateLastHint(-1, DateUtils.getStartOfToday());
|
||||||
screen.showIntroScreen();
|
screen.showIntroScreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface OnFinishedListener
|
||||||
|
{
|
||||||
|
void onFinish();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,6 +112,22 @@ public class ListHabitsMenu extends BaseMenu
|
|||||||
invalidate();
|
invalidate();
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
case R.id.actionSortColor:
|
||||||
|
adapter.setOrder(HabitList.Order.BY_COLOR);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case R.id.actionSortManual:
|
||||||
|
adapter.setOrder(HabitList.Order.BY_POSITION);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case R.id.actionSortName:
|
||||||
|
adapter.setOrder(HabitList.Order.BY_NAME);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case R.id.actionSortScore:
|
||||||
|
adapter.setOrder(HabitList.Order.BY_SCORE);
|
||||||
|
return true;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,9 @@ import android.support.v7.widget.Toolbar;
|
|||||||
import android.view.*;
|
import android.view.*;
|
||||||
import android.widget.*;
|
import android.widget.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
import org.isoron.uhabits.R;
|
||||||
import org.isoron.uhabits.activities.*;
|
import org.isoron.uhabits.activities.*;
|
||||||
|
import org.isoron.uhabits.activities.common.views.*;
|
||||||
import org.isoron.uhabits.activities.habits.list.controllers.*;
|
import org.isoron.uhabits.activities.habits.list.controllers.*;
|
||||||
import org.isoron.uhabits.activities.habits.list.model.*;
|
import org.isoron.uhabits.activities.habits.list.model.*;
|
||||||
import org.isoron.uhabits.activities.habits.list.views.*;
|
import org.isoron.uhabits.activities.habits.list.views.*;
|
||||||
@@ -43,7 +44,7 @@ import butterknife.*;
|
|||||||
public class ListHabitsRootView extends BaseRootView
|
public class ListHabitsRootView extends BaseRootView
|
||||||
implements ModelObservable.Listener, TaskRunner.Listener
|
implements ModelObservable.Listener, TaskRunner.Listener
|
||||||
{
|
{
|
||||||
public static final int MAX_CHECKMARK_COUNT = 21;
|
public static final int MAX_CHECKMARK_COUNT = 60;
|
||||||
|
|
||||||
@BindView(R.id.listView)
|
@BindView(R.id.listView)
|
||||||
HabitCardListView listView;
|
HabitCardListView listView;
|
||||||
@@ -132,6 +133,13 @@ public class ListHabitsRootView extends BaseRootView
|
|||||||
listController.setSelectionListener(menu);
|
listController.setSelectionListener(menu);
|
||||||
listView.setController(listController);
|
listView.setController(listController);
|
||||||
menu.setListController(listController);
|
menu.setListController(listController);
|
||||||
|
header.setScrollController(new ScrollableChart.ScrollController() {
|
||||||
|
@Override
|
||||||
|
public void onDataOffsetChanged(int newDataOffset)
|
||||||
|
{
|
||||||
|
listView.setDataOffset(newDataOffset);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -156,6 +164,7 @@ public class ListHabitsRootView extends BaseRootView
|
|||||||
{
|
{
|
||||||
int count = getCheckmarkCount();
|
int count = getCheckmarkCount();
|
||||||
header.setButtonCount(count);
|
header.setButtonCount(count);
|
||||||
|
header.setMaxDataOffset(Math.max(MAX_CHECKMARK_COUNT - count, 0));
|
||||||
listView.setCheckmarkCount(count);
|
listView.setCheckmarkCount(count);
|
||||||
super.onSizeChanged(w, h, oldw, oldh);
|
super.onSizeChanged(w, h, oldw, oldh);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,9 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.activities.habits.list;
|
package org.isoron.uhabits.activities.habits.list;
|
||||||
|
|
||||||
|
import android.app.*;
|
||||||
import android.content.*;
|
import android.content.*;
|
||||||
|
import android.net.*;
|
||||||
import android.support.annotation.*;
|
import android.support.annotation.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
import org.isoron.uhabits.*;
|
||||||
@@ -31,24 +33,32 @@ import org.isoron.uhabits.commands.*;
|
|||||||
import org.isoron.uhabits.intents.*;
|
import org.isoron.uhabits.intents.*;
|
||||||
import org.isoron.uhabits.io.*;
|
import org.isoron.uhabits.io.*;
|
||||||
import org.isoron.uhabits.models.*;
|
import org.isoron.uhabits.models.*;
|
||||||
|
import org.isoron.uhabits.utils.*;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
|
||||||
import javax.inject.*;
|
import javax.inject.*;
|
||||||
|
|
||||||
|
import static android.os.Build.VERSION.*;
|
||||||
|
import static android.os.Build.VERSION_CODES.*;
|
||||||
|
|
||||||
@ActivityScope
|
@ActivityScope
|
||||||
public class ListHabitsScreen extends BaseScreen
|
public class ListHabitsScreen extends BaseScreen
|
||||||
implements CommandRunner.Listener
|
implements CommandRunner.Listener
|
||||||
{
|
{
|
||||||
public static final int RESULT_BUG_REPORT = 4;
|
public static final int RESULT_IMPORT_DATA = 1;
|
||||||
|
|
||||||
public static final int RESULT_EXPORT_CSV = 2;
|
public static final int RESULT_EXPORT_CSV = 2;
|
||||||
|
|
||||||
public static final int RESULT_EXPORT_DB = 3;
|
public static final int RESULT_EXPORT_DB = 3;
|
||||||
|
|
||||||
|
public static final int RESULT_BUG_REPORT = 4;
|
||||||
|
|
||||||
public static final int RESULT_REPAIR_DB = 5;
|
public static final int RESULT_REPAIR_DB = 5;
|
||||||
|
|
||||||
public static final int RESULT_IMPORT_DATA = 1;
|
public static final int REQUEST_OPEN_DOCUMENT = 6;
|
||||||
|
|
||||||
|
public static final int REQUEST_SETTINGS = 7;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private ListHabitsController controller;
|
private ListHabitsController controller;
|
||||||
@@ -125,6 +135,15 @@ public class ListHabitsScreen extends BaseScreen
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResult(int requestCode, int resultCode, Intent data)
|
public void onResult(int requestCode, int resultCode, Intent data)
|
||||||
|
{
|
||||||
|
if (requestCode == REQUEST_OPEN_DOCUMENT)
|
||||||
|
onOpenDocumentResult(resultCode, data);
|
||||||
|
|
||||||
|
if (requestCode == REQUEST_SETTINGS)
|
||||||
|
onSettingsResult(resultCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSettingsResult(int resultCode)
|
||||||
{
|
{
|
||||||
if (controller == null) return;
|
if (controller == null) return;
|
||||||
|
|
||||||
@@ -152,6 +171,30 @@ public class ListHabitsScreen extends BaseScreen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onOpenDocumentResult(int resultCode, Intent data)
|
||||||
|
{
|
||||||
|
if (controller == null) return;
|
||||||
|
if (resultCode != Activity.RESULT_OK) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Uri uri = data.getData();
|
||||||
|
ContentResolver cr = activity.getContentResolver();
|
||||||
|
InputStream is = cr.openInputStream(uri);
|
||||||
|
|
||||||
|
File cacheDir = activity.getExternalCacheDir();
|
||||||
|
File tempFile = File.createTempFile("import", "", cacheDir);
|
||||||
|
|
||||||
|
FileUtils.copy(is, tempFile);
|
||||||
|
controller.onImportData(tempFile, () -> tempFile.delete());
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
showMessage(R.string.could_not_import);
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void setController(@Nullable ListHabitsController controller)
|
public void setController(@Nullable ListHabitsController controller)
|
||||||
{
|
{
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
@@ -208,6 +251,21 @@ public class ListHabitsScreen extends BaseScreen
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void showImportScreen()
|
public void showImportScreen()
|
||||||
|
{
|
||||||
|
if (SDK_INT < KITKAT)
|
||||||
|
{
|
||||||
|
showImportScreenPreKitKat();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
||||||
|
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
|
intent.setType("*/*");
|
||||||
|
|
||||||
|
activity.startActivityForResult(intent, REQUEST_OPEN_DOCUMENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showImportScreenPreKitKat()
|
||||||
{
|
{
|
||||||
File dir = dirFinder.findStorageDir(null);
|
File dir = dirFinder.findStorageDir(null);
|
||||||
|
|
||||||
@@ -220,7 +278,8 @@ public class ListHabitsScreen extends BaseScreen
|
|||||||
FilePickerDialog picker = filePickerDialogFactory.create(dir);
|
FilePickerDialog picker = filePickerDialogFactory.create(dir);
|
||||||
|
|
||||||
if (controller != null)
|
if (controller != null)
|
||||||
picker.setListener(file -> controller.onImportData(file));
|
picker.setListener(file -> controller.onImportData(file, () -> {}));
|
||||||
|
|
||||||
activity.showDialog(picker.getDialog());
|
activity.showDialog(picker.getDialog());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,7 +292,7 @@ public class ListHabitsScreen extends BaseScreen
|
|||||||
public void showSettingsScreen()
|
public void showSettingsScreen()
|
||||||
{
|
{
|
||||||
Intent intent = intentFactory.startSettingsActivity(activity);
|
Intent intent = intentFactory.startSettingsActivity(activity);
|
||||||
activity.startActivityForResult(intent, 0);
|
activity.startActivityForResult(intent, REQUEST_SETTINGS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void toggleNightMode()
|
public void toggleNightMode()
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ import org.isoron.uhabits.activities.*;
|
|||||||
import org.isoron.uhabits.activities.habits.list.*;
|
import org.isoron.uhabits.activities.habits.list.*;
|
||||||
import org.isoron.uhabits.activities.habits.list.views.*;
|
import org.isoron.uhabits.activities.habits.list.views.*;
|
||||||
import org.isoron.uhabits.models.*;
|
import org.isoron.uhabits.models.*;
|
||||||
|
import org.isoron.uhabits.preferences.*;
|
||||||
|
import org.isoron.uhabits.utils.*;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@@ -41,7 +43,7 @@ import javax.inject.*;
|
|||||||
@ActivityScope
|
@ActivityScope
|
||||||
public class HabitCardListAdapter
|
public class HabitCardListAdapter
|
||||||
extends RecyclerView.Adapter<HabitCardViewHolder>
|
extends RecyclerView.Adapter<HabitCardViewHolder>
|
||||||
implements HabitCardListCache.Listener
|
implements HabitCardListCache.Listener, MidnightTimer.MidnightListener
|
||||||
{
|
{
|
||||||
@NonNull
|
@NonNull
|
||||||
private ModelObservable observable;
|
private ModelObservable observable;
|
||||||
@@ -55,19 +57,36 @@ public class HabitCardListAdapter
|
|||||||
@NonNull
|
@NonNull
|
||||||
private final HabitCardListCache cache;
|
private final HabitCardListCache cache;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private Preferences preferences;
|
||||||
|
|
||||||
|
private final MidnightTimer midnightTimer;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public HabitCardListAdapter(@NonNull HabitCardListCache cache)
|
public HabitCardListAdapter(@NonNull HabitCardListCache cache,
|
||||||
|
@NonNull Preferences preferences,
|
||||||
|
@NonNull MidnightTimer midnightTimer)
|
||||||
{
|
{
|
||||||
|
this.preferences = preferences;
|
||||||
this.selected = new LinkedList<>();
|
this.selected = new LinkedList<>();
|
||||||
this.observable = new ModelObservable();
|
this.observable = new ModelObservable();
|
||||||
this.cache = cache;
|
this.cache = cache;
|
||||||
|
|
||||||
|
this.midnightTimer = midnightTimer;
|
||||||
|
|
||||||
cache.setListener(this);
|
cache.setListener(this);
|
||||||
cache.setCheckmarkCount(ListHabitsRootView.MAX_CHECKMARK_COUNT);
|
cache.setCheckmarkCount(ListHabitsRootView.MAX_CHECKMARK_COUNT);
|
||||||
|
cache.setOrder(preferences.getDefaultOrder());
|
||||||
|
|
||||||
setHasStableIds(true);
|
setHasStableIds(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void atMidnight()
|
||||||
|
{
|
||||||
|
cache.refreshAllHabits();
|
||||||
|
}
|
||||||
|
|
||||||
public void cancelRefresh()
|
public void cancelRefresh()
|
||||||
{
|
{
|
||||||
cache.cancelTasks();
|
cache.cancelTasks();
|
||||||
@@ -130,12 +149,18 @@ public class HabitCardListAdapter
|
|||||||
return selected.isEmpty();
|
return selected.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isSortable()
|
||||||
|
{
|
||||||
|
return cache.getOrder() == HabitList.Order.BY_POSITION;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify the adapter that it has been attached to a ListView.
|
* Notify the adapter that it has been attached to a ListView.
|
||||||
*/
|
*/
|
||||||
public void onAttached()
|
public void onAttached()
|
||||||
{
|
{
|
||||||
cache.onAttached();
|
cache.onAttached();
|
||||||
|
midnightTimer.addListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -153,6 +178,20 @@ public class HabitCardListAdapter
|
|||||||
listView.bindCardView(holder, habit, score, checkmarks, selected);
|
listView.bindCardView(holder, habit, score, checkmarks, selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewAttachedToWindow(@Nullable HabitCardViewHolder holder)
|
||||||
|
{
|
||||||
|
if (listView == null) return;
|
||||||
|
listView.attachCardView(holder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewDetachedFromWindow(@Nullable HabitCardViewHolder holder)
|
||||||
|
{
|
||||||
|
if (listView == null) return;
|
||||||
|
listView.detachCardView(holder);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HabitCardViewHolder onCreateViewHolder(ViewGroup parent,
|
public HabitCardViewHolder onCreateViewHolder(ViewGroup parent,
|
||||||
int viewType)
|
int viewType)
|
||||||
@@ -168,6 +207,7 @@ public class HabitCardListAdapter
|
|||||||
public void onDetached()
|
public void onDetached()
|
||||||
{
|
{
|
||||||
cache.onDetached();
|
cache.onDetached();
|
||||||
|
midnightTimer.removeListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -260,6 +300,12 @@ public class HabitCardListAdapter
|
|||||||
this.listView = listView;
|
this.listView = listView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setOrder(HabitList.Order order)
|
||||||
|
{
|
||||||
|
cache.setOrder(order);
|
||||||
|
preferences.setDefaultOrder(order);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Selects or deselects the item at a given position.
|
* Selects or deselects the item at a given position.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -107,6 +107,11 @@ public class HabitCardListCache implements CommandRunner.Listener
|
|||||||
return data.habits.size();
|
return data.habits.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HabitList.Order getOrder()
|
||||||
|
{
|
||||||
|
return filteredHabits.getOrder();
|
||||||
|
}
|
||||||
|
|
||||||
public int getScore(long habitId)
|
public int getScore(long habitId)
|
||||||
{
|
{
|
||||||
return data.scores.get(habitId);
|
return data.scores.get(habitId);
|
||||||
@@ -180,6 +185,13 @@ public class HabitCardListCache implements CommandRunner.Listener
|
|||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setOrder(HabitList.Order order)
|
||||||
|
{
|
||||||
|
allHabits.setOrder(order);
|
||||||
|
filteredHabits.setOrder(order);
|
||||||
|
refreshAllHabits();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface definition for a callback to be invoked when the data on the
|
* Interface definition for a callback to be invoked when the data on the
|
||||||
* cache has been modified.
|
* cache has been modified.
|
||||||
|
|||||||
@@ -20,21 +20,34 @@
|
|||||||
package org.isoron.uhabits.activities.habits.list.views;
|
package org.isoron.uhabits.activities.habits.list.views;
|
||||||
|
|
||||||
import android.content.*;
|
import android.content.*;
|
||||||
|
import android.content.res.*;
|
||||||
|
import android.graphics.*;
|
||||||
|
import android.support.annotation.*;
|
||||||
|
import android.text.*;
|
||||||
|
import android.util.*;
|
||||||
import android.view.*;
|
import android.view.*;
|
||||||
import android.widget.*;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
import org.isoron.uhabits.*;
|
||||||
import org.isoron.uhabits.activities.habits.list.controllers.*;
|
import org.isoron.uhabits.activities.habits.list.controllers.*;
|
||||||
import org.isoron.uhabits.models.*;
|
|
||||||
import org.isoron.uhabits.utils.*;
|
import org.isoron.uhabits.utils.*;
|
||||||
|
|
||||||
public class CheckmarkButtonView extends TextView
|
import static android.view.View.MeasureSpec.*;
|
||||||
|
import static org.isoron.uhabits.models.Checkmark.*;
|
||||||
|
import static org.isoron.uhabits.utils.AttributeSetUtils.*;
|
||||||
|
|
||||||
|
public class CheckmarkButtonView extends View
|
||||||
{
|
{
|
||||||
private int color;
|
private int color;
|
||||||
|
|
||||||
private int value;
|
private int value;
|
||||||
|
|
||||||
private StyledResources res;
|
private StyledResources styledRes;
|
||||||
|
|
||||||
|
private TextPaint paint;
|
||||||
|
|
||||||
|
private int lowContrastColor;
|
||||||
|
|
||||||
|
private RectF rect;
|
||||||
|
|
||||||
public CheckmarkButtonView(Context context)
|
public CheckmarkButtonView(Context context)
|
||||||
{
|
{
|
||||||
@@ -42,6 +55,21 @@ public class CheckmarkButtonView extends TextView
|
|||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CheckmarkButtonView(@Nullable Context ctx, @Nullable AttributeSet attrs)
|
||||||
|
{
|
||||||
|
super(ctx, attrs);
|
||||||
|
init();
|
||||||
|
|
||||||
|
if(ctx == null) throw new IllegalStateException();
|
||||||
|
if(attrs == null) throw new IllegalStateException();
|
||||||
|
|
||||||
|
int paletteColor = getIntAttribute(ctx, attrs, "color", 0);
|
||||||
|
setColor(ColorUtils.getAndroidTestColor(paletteColor));
|
||||||
|
|
||||||
|
int value = getIntAttribute(ctx, attrs, "value", 0);
|
||||||
|
setValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
public void setColor(int color)
|
public void setColor(int color)
|
||||||
{
|
{
|
||||||
this.color = color;
|
this.color = color;
|
||||||
@@ -57,55 +85,60 @@ public class CheckmarkButtonView extends TextView
|
|||||||
public void setValue(int value)
|
public void setValue(int value)
|
||||||
{
|
{
|
||||||
this.value = value;
|
this.value = value;
|
||||||
updateText();
|
postInvalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void toggle()
|
public void toggle()
|
||||||
{
|
{
|
||||||
value = (value == Checkmark.CHECKED_EXPLICITLY ? Checkmark.UNCHECKED :
|
value = (value == CHECKED_EXPLICITLY ? UNCHECKED : CHECKED_EXPLICITLY);
|
||||||
Checkmark.CHECKED_EXPLICITLY);
|
|
||||||
|
|
||||||
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||||
updateText();
|
postInvalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDraw(Canvas canvas)
|
||||||
|
{
|
||||||
|
super.onDraw(canvas);
|
||||||
|
Resources resources = getResources();
|
||||||
|
|
||||||
|
paint.setColor(value == CHECKED_EXPLICITLY ? color : lowContrastColor);
|
||||||
|
int id = (value == UNCHECKED ? R.string.fa_times : R.string.fa_check);
|
||||||
|
String label = resources.getString(id);
|
||||||
|
float em = paint.measureText("m");
|
||||||
|
|
||||||
|
rect.set(0, 0, getWidth(), getHeight());
|
||||||
|
rect.offset(0, 0.4f * em);
|
||||||
|
canvas.drawText(label, rect.centerX(), rect.centerY(), paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
||||||
|
{
|
||||||
|
Resources res = getResources();
|
||||||
|
int height = res.getDimensionPixelSize(R.dimen.checkmarkHeight);
|
||||||
|
int width = res.getDimensionPixelSize(R.dimen.checkmarkWidth);
|
||||||
|
|
||||||
|
widthMeasureSpec = makeMeasureSpec(width, EXACTLY);
|
||||||
|
heightMeasureSpec = makeMeasureSpec(height, EXACTLY);
|
||||||
|
|
||||||
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void init()
|
private void init()
|
||||||
{
|
{
|
||||||
res = new StyledResources(getContext());
|
|
||||||
|
|
||||||
setWillNotDraw(false);
|
|
||||||
setHapticFeedbackEnabled(false);
|
|
||||||
|
|
||||||
setMinHeight(
|
|
||||||
getResources().getDimensionPixelSize(R.dimen.checkmarkHeight));
|
|
||||||
setMinWidth(
|
|
||||||
getResources().getDimensionPixelSize(R.dimen.checkmarkWidth));
|
|
||||||
|
|
||||||
setFocusable(false);
|
setFocusable(false);
|
||||||
setGravity(Gravity.CENTER);
|
|
||||||
setTypeface(InterfaceUtils.getFontAwesome(getContext()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateText()
|
Resources res = getResources();
|
||||||
{
|
styledRes = new StyledResources(getContext());
|
||||||
int lowContrastColor = res.getColor(R.attr.lowContrastTextColor);
|
|
||||||
|
|
||||||
if (value == Checkmark.CHECKED_EXPLICITLY)
|
paint = new TextPaint();
|
||||||
{
|
paint.setTypeface(InterfaceUtils.getFontAwesome(getContext()));
|
||||||
setText(R.string.fa_check);
|
paint.setAntiAlias(true);
|
||||||
setTextColor(color);
|
paint.setTextAlign(Paint.Align.CENTER);
|
||||||
}
|
paint.setTextSize(res.getDimension(R.dimen.regularTextSize));
|
||||||
|
|
||||||
if (value == Checkmark.CHECKED_IMPLICITLY)
|
rect = new RectF();
|
||||||
{
|
color = ColorUtils.getAndroidTestColor(0);
|
||||||
setText(R.string.fa_check);
|
lowContrastColor = styledRes.getColor(R.attr.lowContrastTextColor);
|
||||||
setTextColor(lowContrastColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value == Checkmark.UNCHECKED)
|
|
||||||
{
|
|
||||||
setText(R.string.fa_times);
|
|
||||||
setTextColor(lowContrastColor);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ public class CheckmarkPanelView extends LinearLayout implements Preferences.List
|
|||||||
@NonNull
|
@NonNull
|
||||||
private Habit habit;
|
private Habit habit;
|
||||||
|
|
||||||
|
private int dataOffset;
|
||||||
|
|
||||||
public CheckmarkPanelView(Context context)
|
public CheckmarkPanelView(Context context)
|
||||||
{
|
{
|
||||||
super(context);
|
super(context);
|
||||||
@@ -75,19 +77,23 @@ public class CheckmarkPanelView extends LinearLayout implements Preferences.List
|
|||||||
return (CheckmarkButtonView) getChildAt(position);
|
return (CheckmarkButtonView) getChildAt(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCheckmarkValues(int[] checkmarkValues)
|
public void setButtonCount(int newButtonCount)
|
||||||
{
|
{
|
||||||
this.checkmarkValues = checkmarkValues;
|
if(nButtons != newButtonCount)
|
||||||
|
|
||||||
if (this.nButtons != checkmarkValues.length)
|
|
||||||
{
|
{
|
||||||
this.nButtons = checkmarkValues.length;
|
nButtons = newButtonCount;
|
||||||
addCheckmarkButtons();
|
addCheckmarkButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
setupCheckmarkButtons();
|
setupCheckmarkButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setCheckmarkValues(int[] checkmarkValues)
|
||||||
|
{
|
||||||
|
this.checkmarkValues = checkmarkValues;
|
||||||
|
setupCheckmarkButtons();
|
||||||
|
}
|
||||||
|
|
||||||
public void setColor(int color)
|
public void setColor(int color)
|
||||||
{
|
{
|
||||||
this.color = color;
|
this.color = color;
|
||||||
@@ -100,6 +106,12 @@ public class CheckmarkPanelView extends LinearLayout implements Preferences.List
|
|||||||
setupCheckmarkButtons();
|
setupCheckmarkButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDataOffset(int dataOffset)
|
||||||
|
{
|
||||||
|
this.dataOffset = dataOffset;
|
||||||
|
setupCheckmarkButtons();
|
||||||
|
}
|
||||||
|
|
||||||
public void setHabit(@NonNull Habit habit)
|
public void setHabit(@NonNull Habit habit)
|
||||||
{
|
{
|
||||||
this.habit = habit;
|
this.habit = habit;
|
||||||
@@ -170,11 +182,13 @@ public class CheckmarkPanelView extends LinearLayout implements Preferences.List
|
|||||||
{
|
{
|
||||||
long timestamp = DateUtils.getStartOfToday();
|
long timestamp = DateUtils.getStartOfToday();
|
||||||
long day = DateUtils.millisecondsInOneDay;
|
long day = DateUtils.millisecondsInOneDay;
|
||||||
|
timestamp -= day * dataOffset;
|
||||||
|
|
||||||
for (int i = 0; i < nButtons; i++)
|
for (int i = 0; i < nButtons; i++)
|
||||||
{
|
{
|
||||||
CheckmarkButtonView buttonView = indexToButton(i);
|
CheckmarkButtonView buttonView = indexToButton(i);
|
||||||
buttonView.setValue(checkmarkValues[i]);
|
if(i + dataOffset >= checkmarkValues.length) break;
|
||||||
|
buttonView.setValue(checkmarkValues[i + dataOffset]);
|
||||||
buttonView.setColor(color);
|
buttonView.setColor(color);
|
||||||
setupButtonControllers(timestamp, buttonView);
|
setupButtonControllers(timestamp, buttonView);
|
||||||
timestamp -= day;
|
timestamp -= day;
|
||||||
|
|||||||
@@ -20,15 +20,17 @@
|
|||||||
package org.isoron.uhabits.activities.habits.list.views;
|
package org.isoron.uhabits.activities.habits.list.views;
|
||||||
|
|
||||||
import android.content.*;
|
import android.content.*;
|
||||||
|
import android.os.*;
|
||||||
import android.support.annotation.*;
|
import android.support.annotation.*;
|
||||||
import android.support.v7.widget.*;
|
import android.support.v7.widget.*;
|
||||||
import android.support.v7.widget.helper.*;
|
import android.support.v7.widget.helper.*;
|
||||||
import android.util.*;
|
import android.util.*;
|
||||||
import android.view.*;
|
import android.view.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.models.*;
|
import org.isoron.uhabits.activities.common.views.*;
|
||||||
import org.isoron.uhabits.activities.habits.list.controllers.*;
|
import org.isoron.uhabits.activities.habits.list.controllers.*;
|
||||||
import org.isoron.uhabits.activities.habits.list.model.*;
|
import org.isoron.uhabits.activities.habits.list.model.*;
|
||||||
|
import org.isoron.uhabits.models.*;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@@ -44,6 +46,10 @@ public class HabitCardListView extends RecyclerView
|
|||||||
|
|
||||||
private int checkmarkCount;
|
private int checkmarkCount;
|
||||||
|
|
||||||
|
private int dataOffset;
|
||||||
|
|
||||||
|
private LinkedList<HabitCardViewHolder> attachedHolders;
|
||||||
|
|
||||||
public HabitCardListView(Context context, AttributeSet attrs)
|
public HabitCardListView(Context context, AttributeSet attrs)
|
||||||
{
|
{
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
@@ -54,6 +60,13 @@ public class HabitCardListView extends RecyclerView
|
|||||||
TouchHelperCallback callback = new TouchHelperCallback();
|
TouchHelperCallback callback = new TouchHelperCallback();
|
||||||
touchHelper = new ItemTouchHelper(callback);
|
touchHelper = new ItemTouchHelper(callback);
|
||||||
touchHelper.attachToRecyclerView(this);
|
touchHelper.attachToRecyclerView(this);
|
||||||
|
|
||||||
|
attachedHolders = new LinkedList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void attachCardView(HabitCardViewHolder holder)
|
||||||
|
{
|
||||||
|
attachedHolders.add(holder);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -75,13 +88,12 @@ public class HabitCardListView extends RecyclerView
|
|||||||
int[] checkmarks,
|
int[] checkmarks,
|
||||||
boolean selected)
|
boolean selected)
|
||||||
{
|
{
|
||||||
int visibleCheckmarks[] =
|
|
||||||
Arrays.copyOfRange(checkmarks, 0, checkmarkCount);
|
|
||||||
|
|
||||||
HabitCardView cardView = (HabitCardView) holder.itemView;
|
HabitCardView cardView = (HabitCardView) holder.itemView;
|
||||||
cardView.setHabit(habit);
|
cardView.setHabit(habit);
|
||||||
cardView.setSelected(selected);
|
cardView.setSelected(selected);
|
||||||
cardView.setCheckmarkValues(visibleCheckmarks);
|
cardView.setCheckmarkValues(checkmarks);
|
||||||
|
cardView.setCheckmarkCount(checkmarkCount);
|
||||||
|
cardView.setDataOffset(dataOffset);
|
||||||
cardView.setScore(score);
|
cardView.setScore(score);
|
||||||
if (controller != null) setupCardViewController(holder);
|
if (controller != null) setupCardViewController(holder);
|
||||||
return cardView;
|
return cardView;
|
||||||
@@ -92,6 +104,11 @@ public class HabitCardListView extends RecyclerView
|
|||||||
return new HabitCardView(getContext());
|
return new HabitCardView(getContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void detachCardView(HabitCardViewHolder holder)
|
||||||
|
{
|
||||||
|
attachedHolders.remove(holder);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setAdapter(RecyclerView.Adapter adapter)
|
public void setAdapter(RecyclerView.Adapter adapter)
|
||||||
{
|
{
|
||||||
@@ -109,6 +126,16 @@ public class HabitCardListView extends RecyclerView
|
|||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDataOffset(int dataOffset)
|
||||||
|
{
|
||||||
|
this.dataOffset = dataOffset;
|
||||||
|
for (HabitCardViewHolder holder : attachedHolders)
|
||||||
|
{
|
||||||
|
HabitCardView cardView = (HabitCardView) holder.itemView;
|
||||||
|
cardView.setDataOffset(dataOffset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onAttachedToWindow()
|
protected void onAttachedToWindow()
|
||||||
{
|
{
|
||||||
@@ -123,6 +150,29 @@ public class HabitCardListView extends RecyclerView
|
|||||||
super.onDetachedFromWindow();
|
super.onDetachedFromWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onRestoreInstanceState(Parcelable state)
|
||||||
|
{
|
||||||
|
if(!(state instanceof BundleSavedState))
|
||||||
|
{
|
||||||
|
super.onRestoreInstanceState(state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BundleSavedState bss = (BundleSavedState) state;
|
||||||
|
dataOffset = bss.bundle.getInt("dataOffset");
|
||||||
|
super.onRestoreInstanceState(bss.getSuperState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Parcelable onSaveInstanceState()
|
||||||
|
{
|
||||||
|
Parcelable superState = super.onSaveInstanceState();
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putInt("dataOffset", dataOffset);
|
||||||
|
return new BundleSavedState(superState, bundle);
|
||||||
|
}
|
||||||
|
|
||||||
protected void setupCardViewController(@NonNull HabitCardViewHolder holder)
|
protected void setupCardViewController(@NonNull HabitCardViewHolder holder)
|
||||||
{
|
{
|
||||||
HabitCardView cardView = (HabitCardView) holder.itemView;
|
HabitCardView cardView = (HabitCardView) holder.itemView;
|
||||||
@@ -168,7 +218,7 @@ public class HabitCardListView extends RecyclerView
|
|||||||
{
|
{
|
||||||
int position = holder.getAdapterPosition();
|
int position = holder.getAdapterPosition();
|
||||||
if (controller != null) controller.onItemLongClick(position);
|
if (controller != null) controller.onItemLongClick(position);
|
||||||
touchHelper.startDrag(holder);
|
if (adapter.isSortable()) touchHelper.startDrag(holder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import android.support.annotation.*;
|
|||||||
import android.util.*;
|
import android.util.*;
|
||||||
import android.widget.*;
|
import android.widget.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
import org.isoron.uhabits.R;
|
||||||
import org.isoron.uhabits.activities.common.views.*;
|
import org.isoron.uhabits.activities.common.views.*;
|
||||||
import org.isoron.uhabits.models.*;
|
import org.isoron.uhabits.models.*;
|
||||||
import org.isoron.uhabits.utils.*;
|
import org.isoron.uhabits.utils.*;
|
||||||
@@ -72,6 +72,8 @@ public class HabitCardView extends FrameLayout
|
|||||||
@Nullable
|
@Nullable
|
||||||
private Habit habit;
|
private Habit habit;
|
||||||
|
|
||||||
|
private int dataOffset;
|
||||||
|
|
||||||
public HabitCardView(Context context)
|
public HabitCardView(Context context)
|
||||||
{
|
{
|
||||||
super(context);
|
super(context);
|
||||||
@@ -90,6 +92,11 @@ public class HabitCardView extends FrameLayout
|
|||||||
new Handler(Looper.getMainLooper()).post(() -> refresh());
|
new Handler(Looper.getMainLooper()).post(() -> refresh());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setCheckmarkCount(int checkmarkCount)
|
||||||
|
{
|
||||||
|
checkmarkPanel.setButtonCount(checkmarkCount);
|
||||||
|
}
|
||||||
|
|
||||||
public void setCheckmarkValues(int checkmarks[])
|
public void setCheckmarkValues(int checkmarks[])
|
||||||
{
|
{
|
||||||
checkmarkPanel.setCheckmarkValues(checkmarks);
|
checkmarkPanel.setCheckmarkValues(checkmarks);
|
||||||
@@ -103,6 +110,12 @@ public class HabitCardView extends FrameLayout
|
|||||||
checkmarkPanel.setController(controller);
|
checkmarkPanel.setController(controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDataOffset(int dataOffset)
|
||||||
|
{
|
||||||
|
this.dataOffset = dataOffset;
|
||||||
|
checkmarkPanel.setDataOffset(dataOffset);
|
||||||
|
}
|
||||||
|
|
||||||
public void setHabit(@NonNull Habit habit)
|
public void setHabit(@NonNull Habit habit)
|
||||||
{
|
{
|
||||||
if (this.habit != null) detachFromHabit();
|
if (this.habit != null) detachFromHabit();
|
||||||
@@ -134,7 +147,7 @@ public class HabitCardView extends FrameLayout
|
|||||||
{
|
{
|
||||||
long today = DateUtils.getStartOfToday();
|
long today = DateUtils.getStartOfToday();
|
||||||
long day = DateUtils.millisecondsInOneDay;
|
long day = DateUtils.millisecondsInOneDay;
|
||||||
int offset = (int) ((today - timestamp) / day);
|
int offset = (int) ((today - timestamp) / day) - dataOffset;
|
||||||
CheckmarkButtonView button = checkmarkPanel.indexToButton(offset);
|
CheckmarkButtonView button = checkmarkPanel.indexToButton(offset);
|
||||||
|
|
||||||
float y = button.getHeight() / 2.0f;
|
float y = button.getHeight() / 2.0f;
|
||||||
@@ -201,6 +214,7 @@ public class HabitCardView extends FrameLayout
|
|||||||
scoreRing.setPercentage(rand.nextFloat());
|
scoreRing.setPercentage(rand.nextFloat());
|
||||||
checkmarkPanel.setColor(color);
|
checkmarkPanel.setColor(color);
|
||||||
checkmarkPanel.setCheckmarkValues(values);
|
checkmarkPanel.setCheckmarkValues(values);
|
||||||
|
checkmarkPanel.setButtonCount(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refresh()
|
private void refresh()
|
||||||
|
|||||||
@@ -20,30 +20,39 @@
|
|||||||
package org.isoron.uhabits.activities.habits.list.views;
|
package org.isoron.uhabits.activities.habits.list.views;
|
||||||
|
|
||||||
import android.content.*;
|
import android.content.*;
|
||||||
|
import android.content.res.*;
|
||||||
|
import android.graphics.*;
|
||||||
import android.support.annotation.*;
|
import android.support.annotation.*;
|
||||||
|
import android.text.*;
|
||||||
import android.util.*;
|
import android.util.*;
|
||||||
import android.view.*;
|
|
||||||
import android.widget.*;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
import org.isoron.uhabits.*;
|
||||||
|
import org.isoron.uhabits.activities.common.views.*;
|
||||||
|
import org.isoron.uhabits.activities.habits.list.*;
|
||||||
import org.isoron.uhabits.preferences.*;
|
import org.isoron.uhabits.preferences.*;
|
||||||
import org.isoron.uhabits.utils.*;
|
import org.isoron.uhabits.utils.*;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public class HeaderView extends LinearLayout implements Preferences.Listener
|
public class HeaderView extends ScrollableChart
|
||||||
|
implements Preferences.Listener, MidnightTimer.MidnightListener
|
||||||
{
|
{
|
||||||
private final Context context;
|
|
||||||
|
|
||||||
private int buttonCount;
|
private int buttonCount;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private Preferences prefs;
|
private Preferences prefs;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private MidnightTimer midnightTimer;
|
||||||
|
|
||||||
|
private final TextPaint paint;
|
||||||
|
|
||||||
|
private RectF rect;
|
||||||
|
|
||||||
public HeaderView(Context context, AttributeSet attrs)
|
public HeaderView(Context context, AttributeSet attrs)
|
||||||
{
|
{
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
this.context = context;
|
|
||||||
|
|
||||||
if (isInEditMode())
|
if (isInEditMode())
|
||||||
{
|
{
|
||||||
@@ -56,51 +65,116 @@ public class HeaderView extends LinearLayout implements Preferences.Listener
|
|||||||
HabitsApplication app = (HabitsApplication) appContext;
|
HabitsApplication app = (HabitsApplication) appContext;
|
||||||
prefs = app.getComponent().getPreferences();
|
prefs = app.getComponent().getPreferences();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (context instanceof ListHabitsActivity)
|
||||||
|
{
|
||||||
|
ListHabitsActivity activity = (ListHabitsActivity) context;
|
||||||
|
midnightTimer = activity.getListHabitsComponent().getMidnightTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
Resources res = context.getResources();
|
||||||
|
setScrollerBucketSize((int) res.getDimension(R.dimen.checkmarkWidth));
|
||||||
|
|
||||||
|
StyledResources sr = new StyledResources(context);
|
||||||
|
paint = new TextPaint();
|
||||||
|
paint.setColor(Color.BLACK);
|
||||||
|
paint.setAntiAlias(true);
|
||||||
|
paint.setTextSize(getResources().getDimension(R.dimen.tinyTextSize));
|
||||||
|
paint.setTextAlign(Paint.Align.CENTER);
|
||||||
|
paint.setTypeface(Typeface.DEFAULT_BOLD);
|
||||||
|
paint.setColor(sr.getColor(R.attr.mediumContrastTextColor));
|
||||||
|
|
||||||
|
rect = new RectF();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void atMidnight()
|
||||||
|
{
|
||||||
|
post(() -> invalidate());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCheckmarkOrderChanged()
|
public void onCheckmarkOrderChanged()
|
||||||
{
|
{
|
||||||
createButtons();
|
updateDirection();
|
||||||
|
postInvalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setButtonCount(int buttonCount)
|
public void setButtonCount(int buttonCount)
|
||||||
{
|
{
|
||||||
this.buttonCount = buttonCount;
|
this.buttonCount = buttonCount;
|
||||||
createButtons();
|
postInvalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onAttachedToWindow()
|
protected void onAttachedToWindow()
|
||||||
{
|
{
|
||||||
|
updateDirection();
|
||||||
super.onAttachedToWindow();
|
super.onAttachedToWindow();
|
||||||
if (prefs != null) prefs.addListener(this);
|
if (prefs != null) prefs.addListener(this);
|
||||||
|
if (midnightTimer != null) midnightTimer.addListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateDirection()
|
||||||
|
{
|
||||||
|
int direction = -1;
|
||||||
|
if (shouldReverseCheckmarks()) direction *= -1;
|
||||||
|
if (InterfaceUtils.isLayoutRtl(this)) direction *= -1;
|
||||||
|
setDirection(direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDetachedFromWindow()
|
protected void onDetachedFromWindow()
|
||||||
{
|
{
|
||||||
|
if (midnightTimer != null) midnightTimer.removeListener(this);
|
||||||
if (prefs != null) prefs.removeListener(this);
|
if (prefs != null) prefs.removeListener(this);
|
||||||
super.onDetachedFromWindow();
|
super.onDetachedFromWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createButtons()
|
@Override
|
||||||
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
||||||
{
|
{
|
||||||
removeAllViews();
|
int width = MeasureSpec.getSize(widthMeasureSpec);
|
||||||
|
int height = (int) getContext()
|
||||||
|
.getResources()
|
||||||
|
.getDimension(R.dimen.checkmarkHeight);
|
||||||
|
setMeasuredDimension(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDraw(Canvas canvas)
|
||||||
|
{
|
||||||
|
super.onDraw(canvas);
|
||||||
|
|
||||||
GregorianCalendar day = DateUtils.getStartOfTodayCalendar();
|
GregorianCalendar day = DateUtils.getStartOfTodayCalendar();
|
||||||
|
Resources res = getContext().getResources();
|
||||||
|
float width = res.getDimension(R.dimen.checkmarkWidth);
|
||||||
|
float height = res.getDimension(R.dimen.checkmarkHeight);
|
||||||
|
boolean reverse = shouldReverseCheckmarks();
|
||||||
|
boolean isRtl = InterfaceUtils.isLayoutRtl(this);
|
||||||
|
|
||||||
|
day.add(GregorianCalendar.DAY_OF_MONTH, -getDataOffset());
|
||||||
|
float em = paint.measureText("m");
|
||||||
|
|
||||||
for (int i = 0; i < buttonCount; i++)
|
for (int i = 0; i < buttonCount; i++)
|
||||||
addView(
|
|
||||||
inflate(context, R.layout.list_habits_header_checkmark, null));
|
|
||||||
|
|
||||||
for (int i = 0; i < getChildCount(); i++)
|
|
||||||
{
|
{
|
||||||
int position = i;
|
rect.set(0, 0, width, height);
|
||||||
if (shouldReverseCheckmarks()) position = getChildCount() - i - 1;
|
rect.offset(canvas.getWidth(), 0);
|
||||||
|
|
||||||
View button = getChildAt(position);
|
if(reverse) rect.offset(- (i + 1) * width, 0);
|
||||||
TextView label = (TextView) button.findViewById(R.id.tvCheck);
|
else rect.offset((i - buttonCount) * width, 0);
|
||||||
label.setText(DateUtils.formatHeaderDate(day));
|
|
||||||
|
if (isRtl) rect.set(canvas.getWidth() - rect.right, rect.top,
|
||||||
|
canvas.getWidth() - rect.left, rect.bottom);
|
||||||
|
|
||||||
|
String text = DateUtils.formatHeaderDate(day).toUpperCase();
|
||||||
|
String[] lines = text.split("\n");
|
||||||
|
|
||||||
|
int y1 = (int)(rect.centerY() - 0.25 * em);
|
||||||
|
int y2 = (int)(rect.centerY() + 1.25 * em);
|
||||||
|
|
||||||
|
canvas.drawText(lines[0], rect.centerX(), y1, paint);
|
||||||
|
canvas.drawText(lines[1], rect.centerX(), y2, paint);
|
||||||
day.add(GregorianCalendar.DAY_OF_MONTH, -1);
|
day.add(GregorianCalendar.DAY_OF_MONTH, -1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import android.os.*;
|
|||||||
import android.support.annotation.*;
|
import android.support.annotation.*;
|
||||||
import android.support.v7.widget.*;
|
import android.support.v7.widget.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
import org.isoron.uhabits.R;
|
||||||
import org.isoron.uhabits.activities.*;
|
import org.isoron.uhabits.activities.*;
|
||||||
import org.isoron.uhabits.activities.habits.show.views.*;
|
import org.isoron.uhabits.activities.habits.show.views.*;
|
||||||
import org.isoron.uhabits.models.*;
|
import org.isoron.uhabits.models.*;
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ import android.view.*;
|
|||||||
|
|
||||||
import org.isoron.uhabits.*;
|
import org.isoron.uhabits.*;
|
||||||
import org.isoron.uhabits.activities.*;
|
import org.isoron.uhabits.activities.*;
|
||||||
|
import org.isoron.uhabits.models.*;
|
||||||
|
import org.isoron.uhabits.tasks.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
import javax.inject.*;
|
import javax.inject.*;
|
||||||
|
|
||||||
@@ -33,12 +37,38 @@ public class ShowHabitsMenu extends BaseMenu
|
|||||||
@NonNull
|
@NonNull
|
||||||
private final ShowHabitScreen screen;
|
private final ShowHabitScreen screen;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final Habit habit;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final TaskRunner taskRunner;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private ExportCSVTaskFactory exportCSVFactory;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ShowHabitsMenu(@NonNull BaseActivity activity,
|
public ShowHabitsMenu(@NonNull BaseActivity activity,
|
||||||
@NonNull ShowHabitScreen screen)
|
@NonNull ShowHabitScreen screen,
|
||||||
|
@NonNull Habit habit,
|
||||||
|
@NonNull ExportCSVTaskFactory exportCSVFactory,
|
||||||
|
@NonNull TaskRunner taskRunner)
|
||||||
{
|
{
|
||||||
super(activity);
|
super(activity);
|
||||||
this.screen = screen;
|
this.screen = screen;
|
||||||
|
this.habit = habit;
|
||||||
|
this.taskRunner = taskRunner;
|
||||||
|
this.exportCSVFactory = exportCSVFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void exportHabit()
|
||||||
|
{
|
||||||
|
List<Habit> selected = new LinkedList<>();
|
||||||
|
selected.add(habit);
|
||||||
|
ExportCSVTask task = exportCSVFactory.create(selected, filename -> {
|
||||||
|
if (filename != null) screen.showSendFileScreen(filename);
|
||||||
|
else screen.showMessage(R.string.could_not_export);
|
||||||
|
});
|
||||||
|
taskRunner.execute(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -50,6 +80,10 @@ public class ShowHabitsMenu extends BaseMenu
|
|||||||
screen.showEditHabitDialog();
|
screen.showEditHabitDialog();
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
case R.id.export:
|
||||||
|
this.exportHabit();
|
||||||
|
return true;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import android.util.*;
|
|||||||
import android.widget.*;
|
import android.widget.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
import org.isoron.uhabits.*;
|
||||||
|
import org.isoron.uhabits.R;
|
||||||
import org.isoron.uhabits.activities.common.views.*;
|
import org.isoron.uhabits.activities.common.views.*;
|
||||||
import org.isoron.uhabits.models.*;
|
import org.isoron.uhabits.models.*;
|
||||||
import org.isoron.uhabits.tasks.*;
|
import org.isoron.uhabits.tasks.*;
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import android.util.*;
|
|||||||
import android.widget.*;
|
import android.widget.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
import org.isoron.uhabits.*;
|
||||||
|
import org.isoron.uhabits.R;
|
||||||
import org.isoron.uhabits.activities.common.views.*;
|
import org.isoron.uhabits.activities.common.views.*;
|
||||||
import org.isoron.uhabits.models.*;
|
import org.isoron.uhabits.models.*;
|
||||||
import org.isoron.uhabits.tasks.*;
|
import org.isoron.uhabits.tasks.*;
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import android.util.*;
|
|||||||
import android.widget.*;
|
import android.widget.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
import org.isoron.uhabits.*;
|
||||||
|
import org.isoron.uhabits.R;
|
||||||
import org.isoron.uhabits.activities.common.views.*;
|
import org.isoron.uhabits.activities.common.views.*;
|
||||||
import org.isoron.uhabits.models.*;
|
import org.isoron.uhabits.models.*;
|
||||||
import org.isoron.uhabits.tasks.*;
|
import org.isoron.uhabits.tasks.*;
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import android.util.*;
|
|||||||
import android.widget.*;
|
import android.widget.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
import org.isoron.uhabits.*;
|
||||||
|
import org.isoron.uhabits.R;
|
||||||
import org.isoron.uhabits.activities.common.views.*;
|
import org.isoron.uhabits.activities.common.views.*;
|
||||||
import org.isoron.uhabits.models.*;
|
import org.isoron.uhabits.models.*;
|
||||||
import org.isoron.uhabits.preferences.*;
|
import org.isoron.uhabits.preferences.*;
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import android.util.*;
|
|||||||
import android.widget.*;
|
import android.widget.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
import org.isoron.uhabits.*;
|
||||||
|
import org.isoron.uhabits.R;
|
||||||
import org.isoron.uhabits.activities.common.views.*;
|
import org.isoron.uhabits.activities.common.views.*;
|
||||||
import org.isoron.uhabits.models.*;
|
import org.isoron.uhabits.models.*;
|
||||||
import org.isoron.uhabits.tasks.*;
|
import org.isoron.uhabits.tasks.*;
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import android.content.res.*;
|
|||||||
import android.util.*;
|
import android.util.*;
|
||||||
import android.widget.*;
|
import android.widget.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
import org.isoron.uhabits.R;
|
||||||
import org.isoron.uhabits.models.*;
|
import org.isoron.uhabits.models.*;
|
||||||
import org.isoron.uhabits.utils.*;
|
import org.isoron.uhabits.utils.*;
|
||||||
|
|
||||||
|
|||||||
@@ -61,9 +61,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
|||||||
setResultOnPreferenceClick("bugReport", ListHabitsScreen.RESULT_BUG_REPORT);
|
setResultOnPreferenceClick("bugReport", ListHabitsScreen.RESULT_BUG_REPORT);
|
||||||
|
|
||||||
updateRingtoneDescription();
|
updateRingtoneDescription();
|
||||||
|
|
||||||
if (InterfaceUtils.isLocaleFullyTranslated())
|
|
||||||
removePreference("translate", "linksCategory");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -110,14 +107,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
|||||||
BackupManager.dataChanged("org.isoron.uhabits");
|
BackupManager.dataChanged("org.isoron.uhabits");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removePreference(String preferenceKey, String categoryKey)
|
|
||||||
{
|
|
||||||
PreferenceCategory cat =
|
|
||||||
(PreferenceCategory) findPreference(categoryKey);
|
|
||||||
Preference pref = findPreference(preferenceKey);
|
|
||||||
cat.removePreference(pref);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setResultOnPreferenceClick(String key, final int result)
|
private void setResultOnPreferenceClick(String key, final int result)
|
||||||
{
|
{
|
||||||
Preference pref = findPreference(key);
|
Preference pref = findPreference(key);
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import android.support.v7.widget.*;
|
|||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.widget.*;
|
import android.widget.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
import org.isoron.uhabits.R;
|
||||||
import org.isoron.uhabits.activities.*;
|
import org.isoron.uhabits.activities.*;
|
||||||
import org.isoron.uhabits.models.*;
|
import org.isoron.uhabits.models.*;
|
||||||
import org.isoron.uhabits.utils.*;
|
import org.isoron.uhabits.utils.*;
|
||||||
|
|||||||
@@ -39,6 +39,12 @@ public class IntentFactory
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Intent helpTranslate(Context context)
|
||||||
|
{
|
||||||
|
String url = context.getString(R.string.translateURL);
|
||||||
|
return buildViewIntent(url);
|
||||||
|
}
|
||||||
|
|
||||||
public Intent rateApp(Context context)
|
public Intent rateApp(Context context)
|
||||||
{
|
{
|
||||||
String url = context.getString(R.string.playStoreURL);
|
String url = context.getString(R.string.playStoreURL);
|
||||||
|
|||||||
@@ -19,18 +19,20 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.io;
|
package org.isoron.uhabits.io;
|
||||||
|
|
||||||
import android.database.Cursor;
|
import android.content.*;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.*;
|
||||||
import android.support.annotation.NonNull;
|
import android.database.sqlite.*;
|
||||||
|
import android.support.annotation.*;
|
||||||
|
import android.util.*;
|
||||||
|
|
||||||
import com.activeandroid.ActiveAndroid;
|
import com.activeandroid.*;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.*;
|
||||||
import org.isoron.uhabits.models.*;
|
import org.isoron.uhabits.models.*;
|
||||||
import org.isoron.uhabits.utils.DatabaseUtils;
|
import org.isoron.uhabits.utils.DatabaseUtils;
|
||||||
import org.isoron.uhabits.utils.FileUtils;
|
import org.isoron.uhabits.utils.*;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.*;
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import javax.inject.*;
|
import javax.inject.*;
|
||||||
|
|
||||||
@@ -39,10 +41,15 @@ import javax.inject.*;
|
|||||||
*/
|
*/
|
||||||
public class LoopDBImporter extends AbstractImporter
|
public class LoopDBImporter extends AbstractImporter
|
||||||
{
|
{
|
||||||
|
@NonNull
|
||||||
|
private Context context;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public LoopDBImporter(@NonNull HabitList habits)
|
public LoopDBImporter(@NonNull @AppContext Context context,
|
||||||
|
@NonNull HabitList habits)
|
||||||
{
|
{
|
||||||
super(habits);
|
super(habits);
|
||||||
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -53,23 +60,37 @@ public class LoopDBImporter extends AbstractImporter
|
|||||||
SQLiteDatabase db = SQLiteDatabase.openDatabase(file.getPath(), null,
|
SQLiteDatabase db = SQLiteDatabase.openDatabase(file.getPath(), null,
|
||||||
SQLiteDatabase.OPEN_READONLY);
|
SQLiteDatabase.OPEN_READONLY);
|
||||||
|
|
||||||
|
boolean canHandle = true;
|
||||||
|
|
||||||
Cursor c = db.rawQuery(
|
Cursor c = db.rawQuery(
|
||||||
"select count(*) from SQLITE_MASTER where name=? or name=?",
|
"select count(*) from SQLITE_MASTER where name=? or name=?",
|
||||||
new String[]{"Checkmarks", "Repetitions"});
|
new String[]{ "Checkmarks", "Repetitions" });
|
||||||
|
|
||||||
boolean result = (c.moveToFirst() && c.getInt(0) == 2);
|
if (!c.moveToFirst() || c.getInt(0) != 2)
|
||||||
|
{
|
||||||
|
Log.w("LoopDBImporter", "Cannot handle file: tables not found");
|
||||||
|
canHandle = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (db.getVersion() > BuildConfig.databaseVersion)
|
||||||
|
{
|
||||||
|
Log.w("LoopDBImporter", String.format(
|
||||||
|
"Cannot handle file: incompatible version: %d > %d",
|
||||||
|
db.getVersion(), BuildConfig.databaseVersion));
|
||||||
|
canHandle = false;
|
||||||
|
}
|
||||||
|
|
||||||
c.close();
|
c.close();
|
||||||
db.close();
|
db.close();
|
||||||
return result;
|
return canHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void importHabitsFromFile(@NonNull File file) throws IOException
|
public void importHabitsFromFile(@NonNull File file) throws IOException
|
||||||
{
|
{
|
||||||
ActiveAndroid.dispose();
|
ActiveAndroid.dispose();
|
||||||
File originalDB = DatabaseUtils.getDatabaseFile();
|
File originalDB = DatabaseUtils.getDatabaseFile(context);
|
||||||
FileUtils.copy(file, originalDB);
|
FileUtils.copy(file, originalDB);
|
||||||
DatabaseUtils.initializeActiveAndroid();
|
DatabaseUtils.initializeActiveAndroid(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ public abstract class CheckmarkList
|
|||||||
*
|
*
|
||||||
* @return value of today's checkmark
|
* @return value of today's checkmark
|
||||||
*/
|
*/
|
||||||
public final int getTodayValue()
|
public int getTodayValue()
|
||||||
{
|
{
|
||||||
Checkmark today = getToday();
|
Checkmark today = getToday();
|
||||||
if (today != null) return today.getValue();
|
if (today != null) return today.getValue();
|
||||||
@@ -192,7 +192,7 @@ public abstract class CheckmarkList
|
|||||||
Checkmark newest = getNewestComputed();
|
Checkmark newest = getNewestComputed();
|
||||||
Checkmark oldest = getOldestComputed();
|
Checkmark oldest = getOldestComputed();
|
||||||
|
|
||||||
if (newest == null)
|
if (newest == null || oldest == null)
|
||||||
{
|
{
|
||||||
forceRecompute(from, to);
|
forceRecompute(from, to);
|
||||||
}
|
}
|
||||||
@@ -208,6 +208,7 @@ public abstract class CheckmarkList
|
|||||||
*
|
*
|
||||||
* @return oldest checkmark already computed
|
* @return oldest checkmark already computed
|
||||||
*/
|
*/
|
||||||
|
@Nullable
|
||||||
protected abstract Checkmark getOldestComputed();
|
protected abstract Checkmark getOldestComputed();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -285,5 +286,6 @@ public abstract class CheckmarkList
|
|||||||
*
|
*
|
||||||
* @return newest checkmark already computed
|
* @return newest checkmark already computed
|
||||||
*/
|
*/
|
||||||
|
@Nullable
|
||||||
protected abstract Checkmark getNewestComputed();
|
protected abstract Checkmark getNewestComputed();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,9 +48,7 @@ public abstract class HabitList implements Iterable<Habit>
|
|||||||
public HabitList()
|
public HabitList()
|
||||||
{
|
{
|
||||||
observable = new ModelObservable();
|
observable = new ModelObservable();
|
||||||
filter = new HabitMatcherBuilder()
|
filter = new HabitMatcherBuilder().setArchivedAllowed(true).build();
|
||||||
.setArchivedAllowed(true)
|
|
||||||
.build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected HabitList(@NonNull HabitMatcher filter)
|
protected HabitList(@NonNull HabitMatcher filter)
|
||||||
@@ -106,6 +104,15 @@ public abstract class HabitList implements Iterable<Habit>
|
|||||||
return observable;
|
return observable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract Order getOrder();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the order of the elements on the list.
|
||||||
|
*
|
||||||
|
* @param order the new order criterea
|
||||||
|
*/
|
||||||
|
public abstract void setOrder(@NonNull Order order);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the index of the given habit in the list, or -1 if the list does
|
* Returns the index of the given habit in the list, or -1 if the list does
|
||||||
* not contain the habit.
|
* not contain the habit.
|
||||||
@@ -149,7 +156,7 @@ public abstract class HabitList implements Iterable<Habit>
|
|||||||
|
|
||||||
public void repair()
|
public void repair()
|
||||||
{
|
{
|
||||||
for(Habit h : this)
|
for (Habit h : this)
|
||||||
{
|
{
|
||||||
h.getCheckmarks().invalidateNewerThan(0);
|
h.getCheckmarks().invalidateNewerThan(0);
|
||||||
h.getStreaks().invalidateNewerThan(0);
|
h.getStreaks().invalidateNewerThan(0);
|
||||||
@@ -228,4 +235,12 @@ public abstract class HabitList implements Iterable<Habit>
|
|||||||
|
|
||||||
csv.close();
|
csv.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum Order
|
||||||
|
{
|
||||||
|
BY_NAME,
|
||||||
|
BY_COLOR,
|
||||||
|
BY_SCORE,
|
||||||
|
BY_POSITION
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ public class MemoryCheckmarkList extends CheckmarkList
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Nullable
|
||||||
protected Checkmark getOldestComputed()
|
protected Checkmark getOldestComputed()
|
||||||
{
|
{
|
||||||
if(list.isEmpty()) return null;
|
if(list.isEmpty()) return null;
|
||||||
@@ -79,6 +80,7 @@ public class MemoryCheckmarkList extends CheckmarkList
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Nullable
|
||||||
protected Checkmark getNewestComputed()
|
protected Checkmark getNewestComputed()
|
||||||
{
|
{
|
||||||
if(list.isEmpty()) return null;
|
if(list.isEmpty()) return null;
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ import org.isoron.uhabits.models.*;
|
|||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import static org.isoron.uhabits.models.HabitList.Order.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In-memory implementation of {@link HabitList}.
|
* In-memory implementation of {@link HabitList}.
|
||||||
*/
|
*/
|
||||||
@@ -33,16 +35,23 @@ public class MemoryHabitList extends HabitList
|
|||||||
@NonNull
|
@NonNull
|
||||||
private LinkedList<Habit> list;
|
private LinkedList<Habit> list;
|
||||||
|
|
||||||
|
private Comparator<Habit> comparator = null;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private Order order;
|
||||||
|
|
||||||
public MemoryHabitList()
|
public MemoryHabitList()
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
list = new LinkedList<>();
|
list = new LinkedList<>();
|
||||||
|
order = Order.BY_POSITION;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected MemoryHabitList(@NonNull HabitMatcher matcher)
|
protected MemoryHabitList(@NonNull HabitMatcher matcher)
|
||||||
{
|
{
|
||||||
super(matcher);
|
super(matcher);
|
||||||
list = new LinkedList<>();
|
list = new LinkedList<>();
|
||||||
|
order = Order.BY_POSITION;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -57,6 +66,7 @@ public class MemoryHabitList extends HabitList
|
|||||||
|
|
||||||
if (id == null) habit.setId((long) list.size());
|
if (id == null) habit.setId((long) list.size());
|
||||||
list.addLast(habit);
|
list.addLast(habit);
|
||||||
|
resort();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -82,10 +92,17 @@ public class MemoryHabitList extends HabitList
|
|||||||
public HabitList getFiltered(HabitMatcher matcher)
|
public HabitList getFiltered(HabitMatcher matcher)
|
||||||
{
|
{
|
||||||
MemoryHabitList habits = new MemoryHabitList(matcher);
|
MemoryHabitList habits = new MemoryHabitList(matcher);
|
||||||
for(Habit h : this) if (matcher.matches(h)) habits.add(h);
|
habits.comparator = comparator;
|
||||||
|
for (Habit h : this) if (matcher.matches(h)) habits.add(h);
|
||||||
return habits;
|
return habits;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Order getOrder()
|
||||||
|
{
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int indexOf(@NonNull Habit h)
|
public int indexOf(@NonNull Habit h)
|
||||||
{
|
{
|
||||||
@@ -112,6 +129,14 @@ public class MemoryHabitList extends HabitList
|
|||||||
list.add(toPos, from);
|
list.add(toPos, from);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOrder(@NonNull Order order)
|
||||||
|
{
|
||||||
|
this.order = order;
|
||||||
|
this.comparator = getComparatorByOrder(order);
|
||||||
|
resort();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int size()
|
public int size()
|
||||||
{
|
{
|
||||||
@@ -123,4 +148,34 @@ public class MemoryHabitList extends HabitList
|
|||||||
{
|
{
|
||||||
// NOP
|
// NOP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Comparator<Habit> getComparatorByOrder(Order order)
|
||||||
|
{
|
||||||
|
Comparator<Habit> nameComparator =
|
||||||
|
(h1, h2) -> h1.getName().compareTo(h2.getName());
|
||||||
|
|
||||||
|
Comparator<Habit> colorComparator = (h1, h2) -> {
|
||||||
|
Integer c1 = h1.getColor();
|
||||||
|
Integer c2 = h2.getColor();
|
||||||
|
if (c1.equals(c2)) return nameComparator.compare(h1, h2);
|
||||||
|
else return c1.compareTo(c2);
|
||||||
|
};
|
||||||
|
|
||||||
|
Comparator<Habit> scoreComparator = (h1, h2) -> {
|
||||||
|
int s1 = h1.getScores().getTodayValue();
|
||||||
|
int s2 = h2.getScores().getTodayValue();
|
||||||
|
return Integer.compare(s2, s1);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (order == BY_POSITION) return null;
|
||||||
|
if (order == BY_NAME) return nameComparator;
|
||||||
|
if (order == BY_COLOR) return colorComparator;
|
||||||
|
if (order == BY_SCORE) return scoreComparator;
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resort()
|
||||||
|
{
|
||||||
|
if (comparator != null) Collections.sort(list, comparator);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of Loop Habit Tracker.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.isoron.uhabits.models.sqlite;
|
||||||
|
|
||||||
|
public class InvalidDatabaseVersionException extends RuntimeException
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -24,7 +24,6 @@ import android.support.annotation.*;
|
|||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import com.activeandroid.*;
|
import com.activeandroid.*;
|
||||||
import com.activeandroid.query.*;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.models.*;
|
import org.isoron.uhabits.models.*;
|
||||||
import org.isoron.uhabits.models.sqlite.records.*;
|
import org.isoron.uhabits.models.sqlite.records.*;
|
||||||
@@ -38,38 +37,54 @@ import java.util.*;
|
|||||||
*/
|
*/
|
||||||
public class SQLiteCheckmarkList extends CheckmarkList
|
public class SQLiteCheckmarkList extends CheckmarkList
|
||||||
{
|
{
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private HabitRecord habitRecord;
|
private HabitRecord habitRecord;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private final SQLiteUtils<CheckmarkRecord> sqlite;
|
private final SQLiteUtils<CheckmarkRecord> sqlite;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Integer todayValue;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final SQLiteStatement invalidateStatement;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final SQLiteStatement addStatement;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final SQLiteDatabase db;
|
||||||
|
|
||||||
|
private static final String ADD_QUERY =
|
||||||
|
"insert into Checkmarks(habit, timestamp, value) values (?,?,?)";
|
||||||
|
|
||||||
|
private static final String INVALIDATE_QUERY =
|
||||||
|
"delete from Checkmarks where habit = ? and timestamp >= ?";
|
||||||
|
|
||||||
public SQLiteCheckmarkList(Habit habit)
|
public SQLiteCheckmarkList(Habit habit)
|
||||||
{
|
{
|
||||||
super(habit);
|
super(habit);
|
||||||
sqlite = new SQLiteUtils<>(CheckmarkRecord.class);
|
sqlite = new SQLiteUtils<>(CheckmarkRecord.class);
|
||||||
|
|
||||||
|
db = Cache.openDatabase();
|
||||||
|
addStatement = db.compileStatement(ADD_QUERY);
|
||||||
|
invalidateStatement = db.compileStatement(INVALIDATE_QUERY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void add(List<Checkmark> checkmarks)
|
public void add(List<Checkmark> checkmarks)
|
||||||
{
|
{
|
||||||
check(habit.getId());
|
check(habit.getId());
|
||||||
|
|
||||||
String query =
|
|
||||||
"insert into Checkmarks(habit, timestamp, value) values (?,?,?)";
|
|
||||||
|
|
||||||
SQLiteDatabase db = Cache.openDatabase();
|
|
||||||
db.beginTransaction();
|
db.beginTransaction();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
SQLiteStatement statement = db.compileStatement(query);
|
|
||||||
|
|
||||||
for (Checkmark c : checkmarks)
|
for (Checkmark c : checkmarks)
|
||||||
{
|
{
|
||||||
statement.bindLong(1, habit.getId());
|
addStatement.bindLong(1, habit.getId());
|
||||||
statement.bindLong(2, c.getTimestamp());
|
addStatement.bindLong(2, c.getTimestamp());
|
||||||
statement.bindLong(3, c.getValue());
|
addStatement.bindLong(3, c.getValue());
|
||||||
statement.execute();
|
addStatement.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
db.setTransactionSuccessful();
|
db.setTransactionSuccessful();
|
||||||
@@ -115,12 +130,10 @@ public class SQLiteCheckmarkList extends CheckmarkList
|
|||||||
@Override
|
@Override
|
||||||
public void invalidateNewerThan(long timestamp)
|
public void invalidateNewerThan(long timestamp)
|
||||||
{
|
{
|
||||||
new Delete()
|
todayValue = null;
|
||||||
.from(CheckmarkRecord.class)
|
invalidateStatement.bindLong(1, habit.getId());
|
||||||
.where("habit = ?", habit.getId())
|
invalidateStatement.bindLong(2, timestamp);
|
||||||
.and("timestamp >= ?", timestamp)
|
invalidateStatement.execute();
|
||||||
.execute();
|
|
||||||
|
|
||||||
observable.notifyListeners();
|
observable.notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,6 +153,7 @@ public class SQLiteCheckmarkList extends CheckmarkList
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Nullable
|
||||||
protected Checkmark getOldestComputed()
|
protected Checkmark getOldestComputed()
|
||||||
{
|
{
|
||||||
check(habit.getId());
|
check(habit.getId());
|
||||||
@@ -179,4 +193,11 @@ public class SQLiteCheckmarkList extends CheckmarkList
|
|||||||
for (CheckmarkRecord r : records) checkmarks.add(r.toCheckmark());
|
for (CheckmarkRecord r : records) checkmarks.add(r.toCheckmark());
|
||||||
return checkmarks;
|
return checkmarks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTodayValue()
|
||||||
|
{
|
||||||
|
if(todayValue == null) todayValue = super.getTodayValue();
|
||||||
|
return todayValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,10 +39,15 @@ public class SQLiteHabitList extends HabitList
|
|||||||
|
|
||||||
private static SQLiteHabitList instance;
|
private static SQLiteHabitList instance;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
private final SQLiteUtils<HabitRecord> sqlite;
|
private final SQLiteUtils<HabitRecord> sqlite;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
private final ModelFactory modelFactory;
|
private final ModelFactory modelFactory;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private Order order;
|
||||||
|
|
||||||
public SQLiteHabitList(@NonNull ModelFactory modelFactory)
|
public SQLiteHabitList(@NonNull ModelFactory modelFactory)
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
@@ -50,16 +55,19 @@ public class SQLiteHabitList extends HabitList
|
|||||||
|
|
||||||
if (cache == null) cache = new HashMap<>();
|
if (cache == null) cache = new HashMap<>();
|
||||||
sqlite = new SQLiteUtils<>(HabitRecord.class);
|
sqlite = new SQLiteUtils<>(HabitRecord.class);
|
||||||
|
order = Order.BY_POSITION;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected SQLiteHabitList(@NonNull ModelFactory modelFactory,
|
protected SQLiteHabitList(@NonNull ModelFactory modelFactory,
|
||||||
@NonNull HabitMatcher filter)
|
@NonNull HabitMatcher filter,
|
||||||
|
@NonNull Order order)
|
||||||
{
|
{
|
||||||
super(filter);
|
super(filter);
|
||||||
this.modelFactory = modelFactory;
|
this.modelFactory = modelFactory;
|
||||||
|
|
||||||
if (cache == null) cache = new HashMap<>();
|
if (cache == null) cache = new HashMap<>();
|
||||||
sqlite = new SQLiteUtils<>(HabitRecord.class);
|
sqlite = new SQLiteUtils<>(HabitRecord.class);
|
||||||
|
this.order = order;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SQLiteHabitList getInstance(
|
public static SQLiteHabitList getInstance(
|
||||||
@@ -118,7 +126,20 @@ public class SQLiteHabitList extends HabitList
|
|||||||
@Override
|
@Override
|
||||||
public HabitList getFiltered(HabitMatcher filter)
|
public HabitList getFiltered(HabitMatcher filter)
|
||||||
{
|
{
|
||||||
return new SQLiteHabitList(modelFactory, filter);
|
return new SQLiteHabitList(modelFactory, filter, order);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
public Order getOrder()
|
||||||
|
{
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOrder(@NonNull Order order)
|
||||||
|
{
|
||||||
|
this.order = order;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -214,6 +235,13 @@ public class SQLiteHabitList extends HabitList
|
|||||||
getObservable().notifyListeners();
|
getObservable().notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void repair()
|
||||||
|
{
|
||||||
|
super.repair();
|
||||||
|
rebuildOrder();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int size()
|
public int size()
|
||||||
{
|
{
|
||||||
@@ -233,7 +261,7 @@ public class SQLiteHabitList extends HabitList
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected List<Habit> toList()
|
protected synchronized List<Habit> toList()
|
||||||
{
|
{
|
||||||
String query = buildSelectQuery();
|
String query = buildSelectQuery();
|
||||||
List<HabitRecord> recordList = sqlite.query(query, null);
|
List<HabitRecord> recordList = sqlite.query(query, null);
|
||||||
@@ -249,12 +277,38 @@ public class SQLiteHabitList extends HabitList
|
|||||||
habits.add(habit);
|
habits.add(habit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(order == Order.BY_SCORE)
|
||||||
|
{
|
||||||
|
Collections.sort(habits, (lhs, rhs) -> {
|
||||||
|
int s1 = lhs.getScores().getTodayValue();
|
||||||
|
int s2 = rhs.getScores().getTodayValue();
|
||||||
|
return Integer.compare(s2, s1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return habits;
|
return habits;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void appendOrderBy(StringBuilder query)
|
private void appendOrderBy(StringBuilder query)
|
||||||
{
|
{
|
||||||
query.append("order by position ");
|
switch (order)
|
||||||
|
{
|
||||||
|
case BY_POSITION:
|
||||||
|
query.append("order by position ");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BY_NAME:
|
||||||
|
case BY_SCORE:
|
||||||
|
query.append("order by name ");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BY_COLOR:
|
||||||
|
query.append("order by color, name ");
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void appendSelect(StringBuilder query)
|
private void appendSelect(StringBuilder query)
|
||||||
@@ -282,11 +336,4 @@ public class SQLiteHabitList extends HabitList
|
|||||||
appendOrderBy(query);
|
appendOrderBy(query);
|
||||||
return query.toString();
|
return query.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void repair()
|
|
||||||
{
|
|
||||||
super.repair();
|
|
||||||
rebuildOrder();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,12 +19,12 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.models.sqlite;
|
package org.isoron.uhabits.models.sqlite;
|
||||||
|
|
||||||
import android.database.DatabaseUtils;
|
import android.database.*;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.*;
|
||||||
import android.support.annotation.*;
|
import android.support.annotation.*;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import com.activeandroid.Cache;
|
import com.activeandroid.*;
|
||||||
import com.activeandroid.query.*;
|
import com.activeandroid.query.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.models.*;
|
import org.isoron.uhabits.models.*;
|
||||||
@@ -43,10 +43,16 @@ public class SQLiteRepetitionList extends RepetitionList
|
|||||||
@Nullable
|
@Nullable
|
||||||
private HabitRecord habitRecord;
|
private HabitRecord habitRecord;
|
||||||
|
|
||||||
|
private SQLiteStatement addStatement;
|
||||||
|
|
||||||
public SQLiteRepetitionList(@NonNull Habit habit)
|
public SQLiteRepetitionList(@NonNull Habit habit)
|
||||||
{
|
{
|
||||||
super(habit);
|
super(habit);
|
||||||
sqlite = new SQLiteUtils<>(RepetitionRecord.class);
|
sqlite = new SQLiteUtils<>(RepetitionRecord.class);
|
||||||
|
|
||||||
|
SQLiteDatabase db = Cache.openDatabase();
|
||||||
|
String addQuery = "insert into repetitions(habit, timestamp) values (?,?)";
|
||||||
|
addStatement = db.compileStatement(addQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -61,11 +67,9 @@ public class SQLiteRepetitionList extends RepetitionList
|
|||||||
public void add(Repetition rep)
|
public void add(Repetition rep)
|
||||||
{
|
{
|
||||||
check(habit.getId());
|
check(habit.getId());
|
||||||
|
addStatement.bindLong(1, habit.getId());
|
||||||
RepetitionRecord record = new RepetitionRecord();
|
addStatement.bindLong(2, rep.getTimestamp());
|
||||||
record.copyFrom(rep);
|
addStatement.execute();
|
||||||
record.habit = habitRecord;
|
|
||||||
record.save();
|
|
||||||
observable.notifyListeners();
|
observable.notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import android.support.annotation.*;
|
|||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import com.activeandroid.*;
|
import com.activeandroid.*;
|
||||||
import com.activeandroid.query.*;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.models.*;
|
import org.isoron.uhabits.models.*;
|
||||||
import org.isoron.uhabits.models.sqlite.records.*;
|
import org.isoron.uhabits.models.sqlite.records.*;
|
||||||
@@ -37,12 +36,30 @@ import java.util.*;
|
|||||||
*/
|
*/
|
||||||
public class SQLiteScoreList extends ScoreList
|
public class SQLiteScoreList extends ScoreList
|
||||||
{
|
{
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private HabitRecord habitRecord;
|
private HabitRecord habitRecord;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private final SQLiteUtils<ScoreRecord> sqlite;
|
private final SQLiteUtils<ScoreRecord> sqlite;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Integer todayValue;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final SQLiteStatement invalidateStatement;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final SQLiteStatement addStatement;
|
||||||
|
|
||||||
|
public static final String ADD_QUERY =
|
||||||
|
"insert into Score(habit, timestamp, score) values (?,?,?)";
|
||||||
|
|
||||||
|
public static final String INVALIDATE_QUERY =
|
||||||
|
"delete from Score where habit = ? " + "and timestamp >= ?";
|
||||||
|
|
||||||
|
private final SQLiteDatabase db;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new ScoreList associated with the given habit.
|
* Constructs a new ScoreList associated with the given habit.
|
||||||
*
|
*
|
||||||
@@ -52,28 +69,25 @@ public class SQLiteScoreList extends ScoreList
|
|||||||
{
|
{
|
||||||
super(habit);
|
super(habit);
|
||||||
sqlite = new SQLiteUtils<>(ScoreRecord.class);
|
sqlite = new SQLiteUtils<>(ScoreRecord.class);
|
||||||
|
|
||||||
|
db = Cache.openDatabase();
|
||||||
|
addStatement = db.compileStatement(ADD_QUERY);
|
||||||
|
invalidateStatement = db.compileStatement(INVALIDATE_QUERY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void add(List<Score> scores)
|
public void add(List<Score> scores)
|
||||||
{
|
{
|
||||||
check(habit.getId());
|
check(habit.getId());
|
||||||
String query =
|
|
||||||
"insert into Score(habit, timestamp, score) values (?,?,?)";
|
|
||||||
|
|
||||||
SQLiteDatabase db = Cache.openDatabase();
|
|
||||||
db.beginTransaction();
|
db.beginTransaction();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
SQLiteStatement statement = db.compileStatement(query);
|
|
||||||
|
|
||||||
for (Score s : scores)
|
for (Score s : scores)
|
||||||
{
|
{
|
||||||
statement.bindLong(1, habit.getId());
|
addStatement.bindLong(1, habit.getId());
|
||||||
statement.bindLong(2, s.getTimestamp());
|
addStatement.bindLong(2, s.getTimestamp());
|
||||||
statement.bindLong(3, s.getValue());
|
addStatement.bindLong(3, s.getValue());
|
||||||
statement.execute();
|
addStatement.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
db.setTransactionSuccessful();
|
db.setTransactionSuccessful();
|
||||||
@@ -126,12 +140,10 @@ public class SQLiteScoreList extends ScoreList
|
|||||||
@Override
|
@Override
|
||||||
public void invalidateNewerThan(long timestamp)
|
public void invalidateNewerThan(long timestamp)
|
||||||
{
|
{
|
||||||
new Delete()
|
todayValue = null;
|
||||||
.from(ScoreRecord.class)
|
invalidateStatement.bindLong(1, habit.getId());
|
||||||
.where("habit = ?", habit.getId())
|
invalidateStatement.bindLong(2, timestamp);
|
||||||
.and("timestamp >= ?", timestamp)
|
invalidateStatement.execute();
|
||||||
.execute();
|
|
||||||
|
|
||||||
getObservable().notifyListeners();
|
getObservable().notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,4 +216,11 @@ public class SQLiteScoreList extends ScoreList
|
|||||||
for (ScoreRecord r : records) scores.add(r.toScore());
|
for (ScoreRecord r : records) scores.add(r.toScore());
|
||||||
return scores;
|
return scores;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTodayValue()
|
||||||
|
{
|
||||||
|
if (todayValue == null) todayValue = super.getTodayValue();
|
||||||
|
return todayValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,10 +19,11 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.models.sqlite;
|
package org.isoron.uhabits.models.sqlite;
|
||||||
|
|
||||||
|
import android.database.sqlite.*;
|
||||||
import android.support.annotation.*;
|
import android.support.annotation.*;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import com.activeandroid.query.*;
|
import com.activeandroid.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.models.*;
|
import org.isoron.uhabits.models.*;
|
||||||
import org.isoron.uhabits.models.sqlite.records.*;
|
import org.isoron.uhabits.models.sqlite.records.*;
|
||||||
@@ -41,10 +42,17 @@ public class SQLiteStreakList extends StreakList
|
|||||||
@NonNull
|
@NonNull
|
||||||
private final SQLiteUtils<StreakRecord> sqlite;
|
private final SQLiteUtils<StreakRecord> sqlite;
|
||||||
|
|
||||||
|
private final SQLiteStatement invalidateStatement;
|
||||||
|
|
||||||
public SQLiteStreakList(Habit habit)
|
public SQLiteStreakList(Habit habit)
|
||||||
{
|
{
|
||||||
super(habit);
|
super(habit);
|
||||||
sqlite = new SQLiteUtils<>(StreakRecord.class);
|
sqlite = new SQLiteUtils<>(StreakRecord.class);
|
||||||
|
|
||||||
|
SQLiteDatabase db = Cache.openDatabase();
|
||||||
|
String invalidateQuery = "delete from Streak where habit = ? " +
|
||||||
|
"and end >= ?";
|
||||||
|
invalidateStatement = db.compileStatement(invalidateQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -73,12 +81,9 @@ public class SQLiteStreakList extends StreakList
|
|||||||
@Override
|
@Override
|
||||||
public void invalidateNewerThan(long timestamp)
|
public void invalidateNewerThan(long timestamp)
|
||||||
{
|
{
|
||||||
new Delete()
|
invalidateStatement.bindLong(1, habit.getId());
|
||||||
.from(StreakRecord.class)
|
invalidateStatement.bindLong(2, timestamp - DateUtils.millisecondsInOneDay);
|
||||||
.where("habit = ?", habit.getId())
|
invalidateStatement.execute();
|
||||||
.and("end >= ?", timestamp - DateUtils.millisecondsInOneDay)
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
observable.notifyListeners();
|
observable.notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ package org.isoron.uhabits.models.sqlite.records;
|
|||||||
import android.annotation.*;
|
import android.annotation.*;
|
||||||
import android.database.*;
|
import android.database.*;
|
||||||
import android.support.annotation.*;
|
import android.support.annotation.*;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import com.activeandroid.*;
|
import com.activeandroid.*;
|
||||||
import com.activeandroid.annotation.*;
|
import com.activeandroid.annotation.*;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import android.preference.*;
|
|||||||
|
|
||||||
import org.isoron.uhabits.*;
|
import org.isoron.uhabits.*;
|
||||||
import org.isoron.uhabits.activities.*;
|
import org.isoron.uhabits.activities.*;
|
||||||
|
import org.isoron.uhabits.models.*;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@@ -61,6 +62,21 @@ public class Preferences
|
|||||||
return prefs.getInt("pref_default_habit_palette_color", fallbackColor);
|
return prefs.getInt("pref_default_habit_palette_color", fallbackColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HabitList.Order getDefaultOrder()
|
||||||
|
{
|
||||||
|
String name = prefs.getString("pref_default_order", "BY_POSITION");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return HabitList.Order.valueOf(name);
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException e)
|
||||||
|
{
|
||||||
|
setDefaultOrder(HabitList.Order.BY_POSITION);
|
||||||
|
return HabitList.Order.BY_POSITION;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public int getDefaultScoreSpinnerPosition()
|
public int getDefaultScoreSpinnerPosition()
|
||||||
{
|
{
|
||||||
int defaultScoreInterval = prefs.getInt("pref_score_view_interval", 1);
|
int defaultScoreInterval = prefs.getInt("pref_score_view_interval", 1);
|
||||||
@@ -69,6 +85,11 @@ public class Preferences
|
|||||||
return defaultScoreInterval;
|
return defaultScoreInterval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDefaultOrder(HabitList.Order order)
|
||||||
|
{
|
||||||
|
prefs.edit().putString("pref_default_order", order.name()).apply();
|
||||||
|
}
|
||||||
|
|
||||||
public void setDefaultScoreSpinnerPosition(int position)
|
public void setDefaultScoreSpinnerPosition(int position)
|
||||||
{
|
{
|
||||||
prefs.edit().putInt("pref_score_view_interval", position).apply();
|
prefs.edit().putInt("pref_score_view_interval", position).apply();
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import android.content.*;
|
|||||||
import android.preference.*;
|
import android.preference.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
import org.isoron.uhabits.*;
|
||||||
|
import org.isoron.uhabits.models.*;
|
||||||
|
|
||||||
import javax.inject.*;
|
import javax.inject.*;
|
||||||
|
|
||||||
@@ -48,7 +49,7 @@ public class WidgetPreferences
|
|||||||
public long getHabitIdFromWidgetId(int widgetId)
|
public long getHabitIdFromWidgetId(int widgetId)
|
||||||
{
|
{
|
||||||
Long habitId = prefs.getLong(getHabitIdKey(widgetId), -1);
|
Long habitId = prefs.getLong(getHabitIdKey(widgetId), -1);
|
||||||
if (habitId < 0) throw new RuntimeException("widget not found");
|
if (habitId < 0) throw new HabitNotFoundException();
|
||||||
|
|
||||||
return habitId;
|
return habitId;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ public class PebbleReceiver extends PebbleDataReceiver
|
|||||||
public static final UUID WATCHAPP_UUID =
|
public static final UUID WATCHAPP_UUID =
|
||||||
UUID.fromString("82629d99-8ea6-4631-a022-9ca77a12a058");
|
UUID.fromString("82629d99-8ea6-4631-a022-9ca77a12a058");
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private Context context;
|
||||||
|
|
||||||
private HabitList allHabits;
|
private HabitList allHabits;
|
||||||
|
|
||||||
private CommandRunner commandRunner;
|
private CommandRunner commandRunner;
|
||||||
@@ -61,6 +64,8 @@ public class PebbleReceiver extends PebbleDataReceiver
|
|||||||
if (context == null) throw new RuntimeException("context is null");
|
if (context == null) throw new RuntimeException("context is null");
|
||||||
if (data == null) throw new RuntimeException("data is null");
|
if (data == null) throw new RuntimeException("data is null");
|
||||||
|
|
||||||
|
this.context = context;
|
||||||
|
|
||||||
HabitsApplication app =
|
HabitsApplication app =
|
||||||
(HabitsApplication) context.getApplicationContext();
|
(HabitsApplication) context.getApplicationContext();
|
||||||
|
|
||||||
@@ -136,7 +141,7 @@ public class PebbleReceiver extends PebbleDataReceiver
|
|||||||
|
|
||||||
private void sendDict(@NonNull PebbleDictionary dict)
|
private void sendDict(@NonNull PebbleDictionary dict)
|
||||||
{
|
{
|
||||||
PebbleKit.sendDataToPebble(HabitsApplication.getContext(),
|
PebbleKit.sendDataToPebble(context,
|
||||||
PebbleReceiver.WATCHAPP_UUID, dict);
|
PebbleReceiver.WATCHAPP_UUID, dict);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,10 +19,13 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.tasks;
|
package org.isoron.uhabits.tasks;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.support.annotation.*;
|
import android.support.annotation.*;
|
||||||
|
|
||||||
import com.google.auto.factory.*;
|
import com.google.auto.factory.*;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.AppContext;
|
||||||
|
import org.isoron.uhabits.activities.ActivityContext;
|
||||||
import org.isoron.uhabits.io.*;
|
import org.isoron.uhabits.io.*;
|
||||||
import org.isoron.uhabits.models.*;
|
import org.isoron.uhabits.models.*;
|
||||||
import org.isoron.uhabits.utils.*;
|
import org.isoron.uhabits.utils.*;
|
||||||
@@ -35,6 +38,9 @@ public class ExportCSVTask implements Task
|
|||||||
{
|
{
|
||||||
private String archiveFilename;
|
private String archiveFilename;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private final List<Habit> selectedHabits;
|
private final List<Habit> selectedHabits;
|
||||||
|
|
||||||
@@ -44,10 +50,12 @@ public class ExportCSVTask implements Task
|
|||||||
@NonNull
|
@NonNull
|
||||||
private final HabitList habitList;
|
private final HabitList habitList;
|
||||||
|
|
||||||
public ExportCSVTask(@Provided @NonNull HabitList habitList,
|
public ExportCSVTask(@Provided @AppContext @NonNull Context context,
|
||||||
|
@Provided @NonNull HabitList habitList,
|
||||||
@NonNull List<Habit> selectedHabits,
|
@NonNull List<Habit> selectedHabits,
|
||||||
@NonNull Listener listener)
|
@NonNull Listener listener)
|
||||||
{
|
{
|
||||||
|
this.context = context;
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
this.habitList = habitList;
|
this.habitList = habitList;
|
||||||
this.selectedHabits = selectedHabits;
|
this.selectedHabits = selectedHabits;
|
||||||
@@ -58,7 +66,7 @@ public class ExportCSVTask implements Task
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
File dir = FileUtils.getFilesDir("CSV");
|
File dir = FileUtils.getFilesDir(context, "CSV");
|
||||||
if (dir == null) return;
|
if (dir == null) return;
|
||||||
|
|
||||||
HabitsCSVExporter exporter;
|
HabitsCSVExporter exporter;
|
||||||
|
|||||||
@@ -19,22 +19,33 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.tasks;
|
package org.isoron.uhabits.tasks;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.support.annotation.*;
|
import android.support.annotation.*;
|
||||||
|
|
||||||
|
import com.google.auto.factory.AutoFactory;
|
||||||
|
import com.google.auto.factory.Provided;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.AppContext;
|
||||||
|
import org.isoron.uhabits.activities.ActivityContext;
|
||||||
import org.isoron.uhabits.utils.*;
|
import org.isoron.uhabits.utils.*;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
|
||||||
|
@AutoFactory(allowSubclasses = true)
|
||||||
public class ExportDBTask implements Task
|
public class ExportDBTask implements Task
|
||||||
{
|
{
|
||||||
private String filename;
|
private String filename;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private Context context;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private final Listener listener;
|
private final Listener listener;
|
||||||
|
|
||||||
public ExportDBTask(@NonNull Listener listener)
|
public ExportDBTask(@Provided @AppContext @NonNull Context context, @NonNull Listener listener)
|
||||||
{
|
{
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -44,10 +55,10 @@ public class ExportDBTask implements Task
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
File dir = FileUtils.getFilesDir("Backups");
|
File dir = FileUtils.getFilesDir(context, "Backups");
|
||||||
if (dir == null) return;
|
if (dir == null) return;
|
||||||
|
|
||||||
filename = DatabaseUtils.saveDatabaseCopy(dir);
|
filename = DatabaseUtils.saveDatabaseCopy(context, dir);
|
||||||
}
|
}
|
||||||
catch (IOException e)
|
catch (IOException e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -74,4 +74,14 @@ public class AttributeSetUtils
|
|||||||
if (number != null) return Float.parseFloat(number);
|
if (number != null) return Float.parseFloat(number);
|
||||||
else return defaultValue;
|
else return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int getIntAttribute(@NonNull Context context,
|
||||||
|
@NonNull AttributeSet attrs,
|
||||||
|
@NonNull String name,
|
||||||
|
int defaultValue)
|
||||||
|
{
|
||||||
|
String number = getAttribute(context, attrs, name, null);
|
||||||
|
if (number != null) return Integer.parseInt(number);
|
||||||
|
else return defaultValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import android.support.annotation.*;
|
|||||||
import com.activeandroid.*;
|
import com.activeandroid.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
import org.isoron.uhabits.*;
|
||||||
|
import org.isoron.uhabits.models.sqlite.*;
|
||||||
import org.isoron.uhabits.models.sqlite.records.*;
|
import org.isoron.uhabits.models.sqlite.records.*;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
@@ -47,9 +48,8 @@ public abstract class DatabaseUtils
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public static File getDatabaseFile()
|
public static File getDatabaseFile(Context context)
|
||||||
{
|
{
|
||||||
Context context = HabitsApplication.getContext();
|
|
||||||
String databaseFilename = getDatabaseFilename();
|
String databaseFilename = getDatabaseFilename();
|
||||||
String root = context.getFilesDir().getPath();
|
String root = context.getFilesDir().getPath();
|
||||||
|
|
||||||
@@ -68,9 +68,8 @@ public abstract class DatabaseUtils
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public static void initializeActiveAndroid()
|
public static void initializeActiveAndroid(Context context)
|
||||||
{
|
{
|
||||||
Context context = HabitsApplication.getContext();
|
|
||||||
Configuration dbConfig = new Configuration.Builder(context)
|
Configuration dbConfig = new Configuration.Builder(context)
|
||||||
.setDatabaseName(getDatabaseFilename())
|
.setDatabaseName(getDatabaseFilename())
|
||||||
.setDatabaseVersion(BuildConfig.databaseVersion)
|
.setDatabaseVersion(BuildConfig.databaseVersion)
|
||||||
@@ -78,18 +77,27 @@ public abstract class DatabaseUtils
|
|||||||
RepetitionRecord.class, ScoreRecord.class, StreakRecord.class)
|
RepetitionRecord.class, ScoreRecord.class, StreakRecord.class)
|
||||||
.create();
|
.create();
|
||||||
|
|
||||||
ActiveAndroid.initialize(dbConfig);
|
try
|
||||||
|
{
|
||||||
|
ActiveAndroid.initialize(dbConfig);
|
||||||
|
}
|
||||||
|
catch (RuntimeException e)
|
||||||
|
{
|
||||||
|
if(e.getMessage().contains("downgrade"))
|
||||||
|
throw new InvalidDatabaseVersionException();
|
||||||
|
else throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||||
public static String saveDatabaseCopy(File dir) throws IOException
|
public static String saveDatabaseCopy(Context context, File dir) throws IOException
|
||||||
{
|
{
|
||||||
SimpleDateFormat dateFormat = DateFormats.getBackupDateFormat();
|
SimpleDateFormat dateFormat = DateFormats.getBackupDateFormat();
|
||||||
String date = dateFormat.format(DateUtils.getLocalTime());
|
String date = dateFormat.format(DateUtils.getLocalTime());
|
||||||
String format = "%s/Loop Habits Backup %s.db";
|
String format = "%s/Loop Habits Backup %s.db";
|
||||||
String filename = String.format(format, dir.getAbsolutePath(), date);
|
String filename = String.format(format, dir.getAbsolutePath(), date);
|
||||||
|
|
||||||
File db = getDatabaseFile();
|
File db = getDatabaseFile(context);
|
||||||
File dbCopy = new File(filename);
|
File dbCopy = new File(filename);
|
||||||
FileUtils.copy(db, dbCopy);
|
FileUtils.copy(db, dbCopy);
|
||||||
|
|
||||||
|
|||||||
@@ -187,6 +187,11 @@ public abstract class DateUtils
|
|||||||
return getStartOfDay(DateUtils.getLocalTime());
|
return getStartOfDay(DateUtils.getLocalTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static long millisecondsUntilTomorrow()
|
||||||
|
{
|
||||||
|
return getStartOfToday() + millisecondsInOneDay - getLocalTime();
|
||||||
|
}
|
||||||
|
|
||||||
public static GregorianCalendar getStartOfTodayCalendar()
|
public static GregorianCalendar getStartOfTodayCalendar()
|
||||||
{
|
{
|
||||||
return getCalendar(getStartOfToday());
|
return getCalendar(getStartOfToday());
|
||||||
|
|||||||
@@ -87,9 +87,8 @@ public abstract class FileUtils
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static File getFilesDir(@Nullable String relativePath)
|
public static File getFilesDir(@NonNull Context context, @Nullable String relativePath)
|
||||||
{
|
{
|
||||||
Context context = HabitsApplication.getContext();
|
|
||||||
File externalFilesDirs[] =
|
File externalFilesDirs[] =
|
||||||
ContextCompat.getExternalFilesDirs(context, null);
|
ContextCompat.getExternalFilesDirs(context, null);
|
||||||
|
|
||||||
|
|||||||
@@ -22,19 +22,12 @@ package org.isoron.uhabits.utils;
|
|||||||
import android.content.*;
|
import android.content.*;
|
||||||
import android.content.res.*;
|
import android.content.res.*;
|
||||||
import android.graphics.*;
|
import android.graphics.*;
|
||||||
|
import android.support.v4.view.*;
|
||||||
import android.util.*;
|
import android.util.*;
|
||||||
|
import android.view.*;
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
public abstract class InterfaceUtils
|
public abstract class InterfaceUtils
|
||||||
{
|
{
|
||||||
|
|
||||||
// TODO: Move this to another place, or detect automatically
|
|
||||||
private static String fullyTranslatedLanguages[] = {
|
|
||||||
"ca", "zh", "en", "de", "in", "it", "ko", "pl", "pt", "es", "tk", "uk",
|
|
||||||
"ja", "fr", "hr", "sl"
|
|
||||||
};
|
|
||||||
|
|
||||||
private static Typeface fontAwesome;
|
private static Typeface fontAwesome;
|
||||||
|
|
||||||
public static Typeface getFontAwesome(Context context)
|
public static Typeface getFontAwesome(Context context)
|
||||||
@@ -59,14 +52,9 @@ public abstract class InterfaceUtils
|
|||||||
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, metrics);
|
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, metrics);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isLocaleFullyTranslated()
|
public static boolean isLayoutRtl(View view)
|
||||||
{
|
{
|
||||||
final String currentLanguage = Locale.getDefault().getLanguage();
|
return ViewCompat.getLayoutDirection(view) ==
|
||||||
|
ViewCompat.LAYOUT_DIRECTION_RTL;
|
||||||
for(String lang : fullyTranslatedLanguages)
|
|
||||||
if(currentLanguage.equals(lang)) return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of Loop Habit Tracker.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.isoron.uhabits.utils;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.activities.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
import javax.inject.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class that emits events when a new day starts.
|
||||||
|
*/
|
||||||
|
@ActivityScope
|
||||||
|
public class MidnightTimer
|
||||||
|
{
|
||||||
|
private final List<MidnightListener> listeners;
|
||||||
|
|
||||||
|
private ScheduledExecutorService executor;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public MidnightTimer()
|
||||||
|
{
|
||||||
|
this.listeners = new LinkedList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void addListener(MidnightListener listener)
|
||||||
|
{
|
||||||
|
this.listeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void onPause()
|
||||||
|
{
|
||||||
|
executor.shutdownNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void onResume()
|
||||||
|
{
|
||||||
|
executor = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
executor.scheduleAtFixedRate(() -> notifyListeners(),
|
||||||
|
DateUtils.millisecondsUntilTomorrow() + 1000,
|
||||||
|
DateUtils.millisecondsInOneDay, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void removeListener(MidnightListener listener)
|
||||||
|
{
|
||||||
|
this.listeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void notifyListeners()
|
||||||
|
{
|
||||||
|
for (MidnightListener l : listeners) l.atMidnight();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface MidnightListener
|
||||||
|
{
|
||||||
|
void atMidnight();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,8 @@ import android.os.*;
|
|||||||
import android.support.annotation.*;
|
import android.support.annotation.*;
|
||||||
import android.widget.*;
|
import android.widget.*;
|
||||||
|
|
||||||
|
import com.activeandroid.util.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
import org.isoron.uhabits.*;
|
||||||
import org.isoron.uhabits.models.*;
|
import org.isoron.uhabits.models.*;
|
||||||
import org.isoron.uhabits.preferences.*;
|
import org.isoron.uhabits.preferences.*;
|
||||||
@@ -76,8 +78,15 @@ public abstract class BaseWidgetProvider extends AppWidgetProvider
|
|||||||
|
|
||||||
for (int id : ids)
|
for (int id : ids)
|
||||||
{
|
{
|
||||||
BaseWidget widget = getWidgetFromId(context, id);
|
try
|
||||||
widget.delete();
|
{
|
||||||
|
BaseWidget widget = getWidgetFromId(context, id);
|
||||||
|
widget.delete();
|
||||||
|
}
|
||||||
|
catch (HabitNotFoundException e)
|
||||||
|
{
|
||||||
|
Log.e("BaseWidgetProvider", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
app/src/main/res/drawable-hdpi/ic_action_download_dark.png
Normal file
|
After Width: | Height: | Size: 249 B |
BIN
app/src/main/res/drawable-hdpi/ic_action_download_light.png
Normal file
|
After Width: | Height: | Size: 280 B |
BIN
app/src/main/res/drawable-mdpi/ic_action_download_dark.png
Normal file
|
After Width: | Height: | Size: 198 B |
BIN
app/src/main/res/drawable-mdpi/ic_action_download_light.png
Normal file
|
After Width: | Height: | Size: 236 B |
BIN
app/src/main/res/drawable-xhdpi/ic_action_download_dark.png
Normal file
|
After Width: | Height: | Size: 279 B |
BIN
app/src/main/res/drawable-xhdpi/ic_action_download_light.png
Normal file
|
After Width: | Height: | Size: 319 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_action_download_dark.png
Normal file
|
After Width: | Height: | Size: 366 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_action_download_light.png
Normal file
|
After Width: | Height: | Size: 397 B |
@@ -31,6 +31,7 @@
|
|||||||
style="@style/Toolbar"/>
|
style="@style/Toolbar"/>
|
||||||
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
|
android:id="@+id/scrollView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/toolbar">
|
android:layout_below="@id/toolbar">
|
||||||
@@ -86,6 +87,11 @@
|
|||||||
style="@style/About.Item.Clickable"
|
style="@style/About.Item.Clickable"
|
||||||
android:text="@string/pref_send_feedback"/>
|
android:text="@string/pref_send_feedback"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvTranslate"
|
||||||
|
style="@style/About.Item.Clickable"
|
||||||
|
android:text="@string/help_translate"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvSource"
|
android:id="@+id/tvSource"
|
||||||
style="@style/About.Item.Clickable"
|
style="@style/About.Item.Clickable"
|
||||||
@@ -117,6 +123,9 @@
|
|||||||
<TextView
|
<TextView
|
||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
android:text="Nikhil (regularcoder)"/>
|
android:text="Nikhil (regularcoder)"/>
|
||||||
|
<TextView
|
||||||
|
style="@style/About.Item"
|
||||||
|
android:text="JanetQC"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
@@ -129,17 +138,33 @@
|
|||||||
android:text="@string/translators"
|
android:text="@string/translators"
|
||||||
android:textColor="?aboutScreenColor"/>
|
android:textColor="?aboutScreenColor"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/About.Item"
|
||||||
|
android:text="Mihail Stefanov (Bǎlgarski)"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
android:text="Angga Rifandi (Bahasa Indonesia)"/>
|
android:text="Angga Rifandi (Bahasa Indonesia)"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/About.Item"
|
||||||
|
android:text="David Nos (Català)"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
android:text="Tomáš Borovec (Čeština)"/>
|
android:text="Tomáš Borovec (Čeština)"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
android:text="David Nos (Català)"/>
|
android:text="Rancher (Cрпски)"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/About.Item"
|
||||||
|
android:text="Yussuf (Dansk)"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/About.Item"
|
||||||
|
android:text="Sølv Ræven (Dansk)"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
@@ -157,6 +182,22 @@
|
|||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
android:text="Ander Raso Vazquez (Español)"/>
|
android:text="Ander Raso Vazquez (Español)"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/About.Item"
|
||||||
|
android:text="Beriain (Euskara)"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/About.Item"
|
||||||
|
android:text="Andreas Michelakis (Ελληνικά)"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/About.Item"
|
||||||
|
android:text="Eman (Fārsi)"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/About.Item"
|
||||||
|
android:text="Saeed Esmaili (Fārsi)"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
android:text="François Mahé (Français)"/>
|
android:text="François Mahé (Français)"/>
|
||||||
@@ -205,6 +246,10 @@
|
|||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
android:text="Dmitriy Bogdanov (Русский)"/>
|
android:text="Dmitriy Bogdanov (Русский)"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/About.Item"
|
||||||
|
android:text="Andrei Pleș (Română)"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
android:text="Dušan Strgar (Slovenščina)"/>
|
android:text="Dušan Strgar (Slovenščina)"/>
|
||||||
@@ -275,7 +320,12 @@
|
|||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
android:text="Andreas Michelakis (Ελληνικά)"/>
|
android:text="Niraj Yadav (हिन्दी)"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/About.Item"
|
||||||
|
android:text="Yoav Argov (עברית)"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|||||||
@@ -79,7 +79,7 @@
|
|||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/tvFreqNum"
|
android:id="@+id/tvFreqNum"
|
||||||
style="@style/dialogFormInputSmallNumber"/>
|
style="@style/dialogFormInputLargeNumber"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/textView3"
|
android:id="@+id/textView3"
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/tvFreqDen"
|
android:id="@+id/tvFreqDen"
|
||||||
style="@style/dialogFormInputSmallNumber"/>
|
style="@style/dialogFormInputLargeNumber"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/textView5"
|
android:id="@+id/textView5"
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
<ScrollView
|
<ScrollView
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/scrollView"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/toolbar"
|
android:layout_below="@id/toolbar"
|
||||||
|
|||||||
@@ -45,6 +45,26 @@
|
|||||||
android:checkable="true"
|
android:checkable="true"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:title="@string/hide_completed"/>
|
android:title="@string/hide_completed"/>
|
||||||
|
|
||||||
|
<item android:title="@string/sort">
|
||||||
|
<menu>
|
||||||
|
<item
|
||||||
|
android:id="@+id/actionSortManual"
|
||||||
|
android:title="@string/manually"/>
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/actionSortName"
|
||||||
|
android:title="@string/by_name"/>
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/actionSortColor"
|
||||||
|
android:title="@string/by_color"/>
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/actionSortScore"
|
||||||
|
android:title="@string/by_score"/>
|
||||||
|
</menu>
|
||||||
|
</item>
|
||||||
</menu>
|
</menu>
|
||||||
</item>
|
</item>
|
||||||
|
|
||||||
@@ -73,5 +93,4 @@
|
|||||||
android:orderInCategory="100"
|
android:orderInCategory="100"
|
||||||
android:title="@string/about"
|
android:title="@string/about"
|
||||||
app:showAsAction="never"/>
|
app:showAsAction="never"/>
|
||||||
|
|
||||||
</menu>
|
</menu>
|
||||||
|
|||||||
@@ -21,6 +21,11 @@
|
|||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/export"
|
||||||
|
android:title="@string/export"
|
||||||
|
app:showAsAction="never"/>
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_edit_habit"
|
android:id="@+id/action_edit_habit"
|
||||||
android:icon="?iconEdit"
|
android:icon="?iconEdit"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 8.5 KiB |
24
app/src/main/res/values-af/strings.xml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--Generated by crowdin.com-->
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||||
|
~
|
||||||
|
~ This file is part of Loop Habit Tracker.
|
||||||
|
~
|
||||||
|
~ Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||||
|
~ it under the terms of the GNU General Public License as published by the
|
||||||
|
~ Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
~ option) any later version.
|
||||||
|
~
|
||||||
|
~ Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||||
|
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
~ more details.
|
||||||
|
~
|
||||||
|
~ You should have received a copy of the GNU General Public License along
|
||||||
|
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
<resources>
|
||||||
|
<!-- App introduction -->
|
||||||
|
<!-- Middle part of the sentence '1 time in xx days' -->
|
||||||
|
</resources>
|
||||||
@@ -1,190 +1,162 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--Generated by crowdin.com-->
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||||
|
~
|
||||||
|
~ This file is part of Loop Habit Tracker.
|
||||||
|
~
|
||||||
|
~ Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||||
|
~ it under the terms of the GNU General Public License as published by the
|
||||||
|
~ Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
~ option) any later version.
|
||||||
|
~
|
||||||
|
~ Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||||
|
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
~ more details.
|
||||||
|
~
|
||||||
|
~ You should have received a copy of the GNU General Public License along
|
||||||
|
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
<resources>
|
<resources>
|
||||||
|
<string name="app_name">لوب ملاحق العادة </string>
|
||||||
<!-- There is no letter P in arabic so the name is unfortunately translated to "Loob" -->
|
<string name="main_activity_title">عادات</string>
|
||||||
<string name="app_name">"لوب ملاحق العادة "</string>
|
<string name="action_settings">إعدادات</string>
|
||||||
<string name="main_activity_title">"عادات"</string>
|
<string name="edit">تعديل</string>
|
||||||
<string name="action_settings">"إعدادات"</string>
|
<string name="delete">حذف</string>
|
||||||
<string name="edit">"تعديل"</string>
|
<string name="archive">أرشيف</string>
|
||||||
<string name="delete">"حذف"</string>
|
<string name="unarchive">إزالة من الأرشيف</string>
|
||||||
<string name="archive">"أرشيف"</string>
|
<string name="add_habit">إضافة العادة</string>
|
||||||
<string name="unarchive">"إزالة من الأرشيف"</string>
|
<string name="color_picker_default_title">غير اللون</string>
|
||||||
<string name="add_habit">"إضافة العادة"</string>
|
<string name="toast_habit_created">تم صنع عادة </string>
|
||||||
<string name="color_picker_default_title">"غير اللون"</string>
|
<string name="toast_habit_deleted">تم حذف عادة </string>
|
||||||
<string name="toast_habit_created">"تم صنع عادة "</string>
|
<string name="toast_habit_restored">تم ترجيع عادة</string>
|
||||||
<string name="toast_habit_deleted">"تم حذف عادة "</string>
|
<string name="toast_nothing_to_undo">لا شيء للتراجع</string>
|
||||||
<string name="toast_habit_restored">"تم ترجيع عادة"</string>
|
<string name="toast_nothing_to_redo">لا شيء لتكرار</string>
|
||||||
<string name="toast_nothing_to_undo">"لا شيء للتراجع"</string>
|
<string name="toast_habit_changed">تم تغييرعادة</string>
|
||||||
<string name="toast_nothing_to_redo">"لا شيء لتكرار"</string>
|
<string name="toast_habit_changed_back">تم ترجيع العادة إلى أصلها</string>
|
||||||
<string name="toast_habit_changed">"تم تغييرعادة"</string>
|
<string name="toast_habit_archived">تم أرشيف العادات</string>
|
||||||
|
<string name="toast_habit_unarchived">تم إزالة العادة من الأرشيف </string>
|
||||||
<!-- This appears when the user edits a habit, and then undoes the action. The habit is "changed back" to what is was before. Alternatively, "Habit restored". -->
|
<string name="overview">نظرة عامة</string>
|
||||||
<string name="toast_habit_changed_back">"تم ترجيع العادة إلى أصلها"</string>
|
<string name="habit_strength">قوة العادة</string>
|
||||||
<string name="toast_habit_archived">"تم أرشيف العادات"</string>
|
<string name="history">التاريخ</string>
|
||||||
<string name="toast_habit_unarchived">"تم إزالة العادة من الأرشيف "</string>
|
<string name="clear">مسح</string>
|
||||||
<string name="overview">"نظرة عامة"</string>
|
<string name="description_hint">السؤال (هل ... اليوم؟)</string>
|
||||||
<string name="habit_strength">"قوة العادة"</string>
|
<string name="repeat">كرر</string>
|
||||||
<string name="history">"التاريخ"</string>
|
<string name="times_every">مرات في</string>
|
||||||
<string name="clear">"مسح"</string>
|
<string name="days">أيام</string>
|
||||||
<string name="description_hint">"السؤال (هل ... اليوم؟)"</string>
|
<string name="reminder">تذكير</string>
|
||||||
|
<string name="discard">حذف</string>
|
||||||
<!-- This and the next two terms form the sentence "Repeat 3 times in 7 days" that you see when you create a habit. Let me know if you have trouble adapting this into your language. -->
|
<string name="save">حفظ</string>
|
||||||
<string name="repeat">"كرر"</string>
|
<string name="streaks">تقدم متتالية</string>
|
||||||
<string name="times_every">"مرات في"</string>
|
<string name="no_habits_found"> لا يوجد لديك عادات مفعله</string>
|
||||||
<string name="days">"أيام"</string>
|
<string name="long_press_to_toggle">أضغط و إستمر لتحقق أو ازل</string>
|
||||||
<string name="reminder">"تذكير"</string>
|
<string name="reminder_off">أوقف</string>
|
||||||
<string name="discard">"حذف"</string>
|
<string name="validation_name_should_not_be_blank">لا يمكن أن يكون الإسم فارغ</string>
|
||||||
<string name="save">"حفظ"</string>
|
<string name="validation_number_should_be_positive">يجب أن يكون الرقم إيجابي</string>
|
||||||
|
<string name="validation_at_most_one_rep_per_day">يمكن أن يكون التكرار واحدة فقط كل يوم </string>
|
||||||
<!-- Streak as in "winning streak". That is, the number of times a user has performed a habit consecutively. Similar terms are "chains" or "series". -->
|
<string name="create_habit">اخلق عادة</string>
|
||||||
<string name="streaks">"تقدم متتالية"</string>
|
<string name="edit_habit">تعديل العادة</string>
|
||||||
<string name="no_habits_found">" لا يوجد لديك عادات مفعله"</string>
|
<string name="check">حقق</string>
|
||||||
<string name="long_press_to_toggle">"أضغط و إستمر لتحقق أو ازل"</string>
|
<string name="snooze">لاحقا</string>
|
||||||
<string name="reminder_off">"أوقف"</string>
|
<!-- App introduction -->
|
||||||
<string name="validation_name_should_not_be_blank">"لا يمكن أن يكون الإسم فارغ"</string>
|
<string name="intro_title_1">أهلا بك</string>
|
||||||
<string name="validation_number_should_be_positive">"يجب أن يكون الرقم إيجابي"</string>
|
<string name="intro_description_1">لوب يساعدك على خلق والحفاظ على العادات الجيدة.</string>
|
||||||
<string name="validation_at_most_one_rep_per_day">"يمكن أن يكون التكرار واحدة فقط كل يوم "</string>
|
<string name="intro_title_2">إنشاء بعض عادات جديدة</string>
|
||||||
<string name="create_habit">"اخلق عادة"</string>
|
<string name="intro_description_2">كل يوم، بعد أداء عادتك، وضع علامة على التطبيق.</string>
|
||||||
<string name="edit_habit">"تعديل العادة"</string>
|
<string name="intro_title_3">حافظ على القيام بذلك</string>
|
||||||
<string name="check">"حقق"</string>
|
<string name="intro_description_3">العادة المستمرة لفترات طويلة تكسب نجمة كامله</string>
|
||||||
<string name="snooze">"لاحقا"</string>
|
<string name="intro_title_4">تتبع تقدمك</string>
|
||||||
|
<string name="intro_description_4">رسوم بيانية مفصلة تبين لكم كيف تحسن عاداتك مع مرور الوقت.</string>
|
||||||
<!-- App introduction -->
|
<string name="interval_15_minutes">15 دقيقة</string>
|
||||||
<string name="intro_title_1">"أهلا بك"</string>
|
<string name="interval_30_minutes">30 دقيقة</string>
|
||||||
<string name="intro_description_1">"لوب يساعدك على خلق والحفاظ على العادات الجيدة."</string>
|
<string name="interval_1_hour">ساعة واحدة</string>
|
||||||
<string name="intro_title_2">"إنشاء بعض عادات جديدة"</string>
|
<string name="interval_2_hour">ساعتين</string>
|
||||||
<string name="intro_description_2">"كل يوم، بعد أداء عادتك، وضع علامة على التطبيق."</string>
|
<string name="interval_4_hour">أربع ساعات</string>
|
||||||
<string name="intro_title_3">"حافظ على القيام بذلك"</string>
|
<string name="interval_8_hour">ثماني ساعات</string>
|
||||||
<string name="intro_description_3">"العادة المستمرة لفترات طويلة تكسب نجمة كامله"</string>
|
<string name="interval_24_hour">24 ساعة</string>
|
||||||
<string name="intro_title_4">"تتبع تقدمك"</string>
|
<string name="pref_toggle_title">تبديل بكبسه</string>
|
||||||
<string name="intro_description_4">"رسوم بيانية مفصلة تبين لكم كيف تحسن عاداتك مع مرور الوقت."</string>
|
<string name="pref_toggle_description">أكثر سهولة، لكنه ممكن يسبب كبسات غير مقصوده</string>
|
||||||
<string name="interval_15_minutes">"15 دقيقة"</string>
|
<string name="pref_snooze_interval_title">فترتي الغفوى على التذكير</string>
|
||||||
<string name="interval_30_minutes">"30 دقيقة"</string>
|
<string name="pref_rate_this_app">تقييم هذا التطبيق على جوجل بلاي</string>
|
||||||
<string name="interval_1_hour">"ساعة واحدة"</string>
|
<string name="pref_send_feedback">أرسل الملاحظات إلى المطور</string>
|
||||||
<string name="interval_2_hour">"ساعتين"</string>
|
<string name="pref_view_source_code">إفحص التعليمات البرمجية على GitHub</string>
|
||||||
<string name="interval_4_hour">"أربع ساعات"</string>
|
<string name="pref_view_app_introduction">عرض المقدمه</string>
|
||||||
<string name="interval_8_hour">"ثماني ساعات"</string>
|
<string name="links">روابط</string>
|
||||||
<string name="pref_toggle_title">"تبديل بكبسه"</string>
|
<string name="behavior">سلوك</string>
|
||||||
<string name="pref_toggle_description">"أكثر سهولة، لكنه ممكن يسبب كبسات غير مقصوده"</string>
|
<string name="name">اسم</string>
|
||||||
<string name="pref_snooze_interval_title">"فترتي الغفوى على التذكير"</string>
|
<string name="settings">إعدادات</string>
|
||||||
<string name="pref_rate_this_app">"تقييم هذا التطبيق على جوجل بلاي"</string>
|
<string name="snooze_interval">فترتي الغفوه</string>
|
||||||
<string name="pref_send_feedback">"أرسل الملاحظات إلى المطور"</string>
|
<string name="hint_title">هل كنت تعلم؟</string>
|
||||||
<string name="pref_view_source_code">"إفحص التعليمات البرمجية على GitHub"</string>
|
<string name="hint_drag">لإعادة ترتيب القوائم، أضغط اسم من هذه العادة، ثم اسحبه إلى المكان الصحيح.</string>
|
||||||
<string name="pref_view_app_introduction">"عرض المقدمه"</string>
|
<string name="hint_landscape">يمكنك ان ترى المزيد أيام عن طريق وضع الهاتف في وضع أفقي.</string>
|
||||||
<string name="links">"روابط"</string>
|
<string name="delete_habits">حذف عادات</string>
|
||||||
<string name="behavior">"سلوك"</string>
|
<string name="delete_habits_message">سيتم حذف عادات بشكل دائم. هذا العمل لا يمكن التراجع عنه.</string>
|
||||||
<string name="name">"اسم"</string>
|
<string name="weekends">عطلة نهاية الأسبوع</string>
|
||||||
<string name="show_archived">"عرض أرشفة"</string>
|
<string name="any_weekday">أيام الأسبوع</string>
|
||||||
<string name="settings">"إعدادات"</string>
|
<string name="any_day">أي يوم</string>
|
||||||
<string name="snooze_interval">"فترتي الغفوه"</string>
|
<string name="select_weekdays">إختار أيام </string>
|
||||||
<string name="hint_title">"هل كنت تعلم؟"</string>
|
<string name="export_to_csv">تصدير البيانات (CSV)</string>
|
||||||
<string name="hint_drag">"لإعادة ترتيب القوائم، أضغط اسم من هذه العادة، ثم اسحبه إلى المكان الصحيح."</string>
|
<string name="done_label">منجز</string>
|
||||||
<string name="hint_landscape">"يمكنك ان ترى المزيد أيام عن طريق وضع الهاتف في وضع أفقي."</string>
|
<string name="clear_label">نظف</string>
|
||||||
<string name="delete_habits">"حذف عادات"</string>
|
<string name="select_hours">تحديد ساعات</string>
|
||||||
<string name="delete_habits_message">"سيتم حذف عادات بشكل دائم. هذا العمل لا يمكن التراجع عنه."</string>
|
<string name="select_minutes">تحديد دقائق </string>
|
||||||
<string name="weekends">"عطلة نهاية الأسبوع"</string>
|
<string name="about">معلومات حول</string>
|
||||||
|
<string name="translators">المترجمين</string>
|
||||||
<!-- Fuzzy -->
|
<string name="developers">المطورين</string>
|
||||||
<string name="any_weekday">"أيام الأسبوع"</string>
|
<string name="version_n">الإصدار %s</string>
|
||||||
|
<string name="frequency">تردد</string>
|
||||||
<!-- Fuzzy -->
|
<string name="checkmark">علامة الاختيار</string>
|
||||||
<string name="any_day">"أي يوم"</string>
|
<string name="strength">القوة</string>
|
||||||
|
<string name="best_streaks">أكثر تقدم</string>
|
||||||
<!-- This is a bit unclear to me. It is like a prompt asking the user to select specific days, or is it more like an alarm mode where it will only activate on select days? -->
|
<string name="current_streaks">تقدم الحالي</string>
|
||||||
<string name="select_weekdays">"إختار أيام "</string>
|
<string name="number_of_repetitions">عدد من حالات التكرار</string>
|
||||||
|
<string name="last_x_days">آخر %d أيام</string>
|
||||||
<!-- Fuzzy -->
|
<string name="last_x_weeks">آخر %d أسابيع</string>
|
||||||
<string name="export_to_csv">"تصدير البيانات (CSV)"</string>
|
<string name="last_x_months">آخر %d أشهر</string>
|
||||||
<string name="done_label">"منجز"</string>
|
<string name="last_x_years">آخر %d سنين</string>
|
||||||
<string name="clear_label">"نظف"</string>
|
<string name="all_time">كل الوقت</string>
|
||||||
<string name="select_hours">"تحديد ساعات"</string>
|
<string name="every_day">كل يوم</string>
|
||||||
<string name="select_minutes">"تحديد دقائق "</string>
|
<string name="every_week">كل اسبوع</string>
|
||||||
|
<string name="two_times_per_week">مرتين في الأسبوع</string>
|
||||||
<!-- Short description used on the Google Play store. There is an 80-character limit. -->
|
<string name="five_times_per_week">خمس مرات في الأسبوع</string>
|
||||||
<string name="store_short_description">"خلق عادات جيدة وتتبع تقدمك على مر الزمن"</string>
|
<string name="custom_frequency">مخصص...</string>
|
||||||
<string name="store_description_1">"لب يساعدك على خلق والحفاظ على العادات الجيدة، مما يسمح لك لتحقيق أهدافكة. الرسوم البيانية والإحصاءات التفصيلية تبين لكم كيف تحسن عاداتك مع مرور الوقت. هو تماما خالية من الاعلانات ومفتوحة المصدر."</string>
|
<string name="help">مساعدة والأسئلة المتداولة</string>
|
||||||
<string name="store_feature_interface">"<b>واجهة بسيطة، جميلة وحديثة </b>
|
<string name="could_not_export">فشل في تصدير البيانات.</string>
|
||||||
لوب يحتوي على واجهة بسيطة وهي سهلة الاستخدام و تتابع نظام تصميم الماتريل دسيجن."</string>
|
<string name="could_not_import">فشل في استيراد البيانات.</string>
|
||||||
<string name="store_feature_score">"<b>نتيجة العادات</b>
|
<string name="file_not_recognized">الملف غير المعترف.</string>
|
||||||
بالإضافة إلى عرض التقدم الحالي، لوب ديه خوارزمية متقدمة لحساب قوة عاداتك. كل التكرار يجعل هذه العادة أقوى، وفي كل يوم غاب يجعلها أضعف. مع ذلك غيب أيام قليلة بعد تقدم طويلة ، لن تدمر تماما تقدمك ."</string>
|
<string name="habits_imported">نجح إستيراد العادات.</string>
|
||||||
<string name="store_feature_statistics">"<b>الرسوم البيانية والإحصاءات المفصلة</b>
|
<string name="full_backup_success">نجح تصدير النسخ الاحتياطي الكامل.</string>
|
||||||
نرى بوضوح كيف كنت قد تحسنت عاداتك بمرور الوقت مع الرسوم البيانية الجميله ومفصلة. انتقل إلى الوراء لنرى التاريخ الكامل لعاداتك."</string>
|
<string name="import_data">استيراد بيانات.</string>
|
||||||
<string name="store_feature_schedules">"<b>جداول مرنة</b>
|
<string name="export_full_backup">صدر نسخة احتياطية كاملة.</string>
|
||||||
تؤيد كل من العادات اليومية والعادات مع جداول أكثر تعقيدا، مثل 3 مرات كل أسبوع، مرة واحدة كل أسبوعين، أو مرة كل يومين."</string>
|
<string name="import_data_summary">تدعم النسخ الاحتياطي الكامل المصدرة من هذا التطبيق، فضلا عن الملفات التي تم إنشاؤها من Tickmate, HabitBull و Rewire. انظر التعليمات لمزيد من المعلومات.</string>
|
||||||
<string name="store_feature_reminders">"<b>تذكير</b>
|
<string name="export_as_csv_summary">صدر ملف التي يمكن فتحها ببرنامج جداول البيانات مثل إكسل أو وبينوفيس. لا يمكن إستيراد هذا الملف.</string>
|
||||||
إنشاء تذكير لكل فرد من عاداتك، في ساعة اختيار من اليوم. تحقق بسهولة، رفض أو غفوة عادتك مباشرة من الإخطار، دون الحاجة إلى فتح التطبيق."</string>
|
<string name="export_full_backup_summary">إنشاء ملف يحتوي على كافة البيانات. يمكن استيراد هذا الملف نفسه.</string>
|
||||||
<string name="store_feature_opensource">"<b>خالية تماما من الإعلانات و المصدر المفتوح</b>
|
<string name="bug_report_failed">فشل في توليد تقرير الاعطال</string>
|
||||||
لا توجد على الاطلاق الإعلانات والشعارات المزعجة أو أذونات إضافية في هذا التطبيق، و سوف يكون هناك أبدا."</string>
|
<string name="generate_bug_report">توليد تقرير الاعطال</string>
|
||||||
<string name="store_feature_wear">"<b>الأمثل للساعات الذكية</b>
|
<string name="troubleshooting">استكشاف الأخطاء وإصلاحها</string>
|
||||||
يمكن التحقق من رسائل التذكير، رفض أو غفوة عادتك مباشرة من ساعتك الاندرويد وير. "</string>
|
<string name="help_translate">المساعدة في ترجمة هذا البرنامج</string>
|
||||||
<string name="about">"معلومات حول"</string>
|
<string name="night_mode">الوضع الليلي</string>
|
||||||
<string name="translators">"المترجمين"</string>
|
<string name="use_pure_black">استخدام أسود نقي في الوضع الليلي</string>
|
||||||
<string name="developers">"المطورين"</string>
|
<string name="pure_black_description">يستبدل خلفيات رمادية مع أسود نقي في الوضع الليلي. يقلل من استهلاك البطارية في الهواتف مع شاشة AMOLED.</string>
|
||||||
|
<string name="interface_preferences">السطح البيني</string>
|
||||||
<!-- %s will get replaced by the version number. For example, "Versão %d" will become "Versão 1.2.0". -->
|
<string name="reverse_days">ترتيب عكسي أيام</string>
|
||||||
<string name="version_n">"الإصدار %s"</string>
|
<string name="reverse_days_description">عرض أيام في ترتيب عكسي على الشاشة الرئيسية</string>
|
||||||
<string name="frequency">"تردد"</string>
|
<string name="day">يوم</string>
|
||||||
<string name="checkmark">"علامة الاختيار"</string>
|
<string name="week">أسبوع</string>
|
||||||
|
<string name="month">شهر</string>
|
||||||
<!-- This is a shorter version of "Habit Strength" -->
|
<string name="quarter">ربع سنه</string>
|
||||||
<string name="strength">"القوة"</string>
|
<string name="year">عام</string>
|
||||||
<string name="best_streaks">"أكثر تقدم"</string>
|
<string name="total">المجموع</string>
|
||||||
<string name="current_streaks">"تقدم الحالي"</string>
|
<!-- Middle part of the sentence '1 time in xx days' -->
|
||||||
<string name="number_of_repetitions">"عدد من حالات التكرار"</string>
|
<string name="time_every">مرات في</string>
|
||||||
<string name="last_x_days">"آخر %d أيام"</string>
|
<string name="every_x_days">كل %d أيام</string>
|
||||||
<string name="last_x_weeks">"آخر %d أسابيع"</string>
|
<string name="every_x_weeks">كل %d أسابيع</string>
|
||||||
<string name="last_x_months">"آخر %d أشهر"</string>
|
<string name="every_x_months">كل %d أشهر</string>
|
||||||
<string name="last_x_years">"آخر %d سنين"</string>
|
<string name="score">النقاط</string>
|
||||||
|
<string name="reminder_sound">صوت تذكير</string>
|
||||||
<!-- "All time" number of repetitions. Or number of repetitions "since the beginning". -->
|
<string name="none">صامت</string>
|
||||||
<string name="all_time">"كل الوقت"</string>
|
<string name="action">عمل</string>
|
||||||
<string name="every_day">"كل يوم"</string>
|
<string name="download">تحميل</string>
|
||||||
<string name="every_week">"كل اسبوع"</string>
|
<string name="export">استخراج</string>
|
||||||
<string name="two_times_per_week">"مرتين في الأسبوع"</string>
|
</resources>
|
||||||
<string name="five_times_per_week">"خمس مرات في الأسبوع"</string>
|
|
||||||
<string name="custom_frequency">"مخصص..."</string>
|
|
||||||
<string name="help">"مساعدة والأسئلة المتداولة"</string>
|
|
||||||
<string name="could_not_export">"فشل في تصدير البيانات."</string>
|
|
||||||
<string name="could_not_import">"فشل في استيراد البيانات."</string>
|
|
||||||
|
|
||||||
<!-- Appears when the user tries to import a file which we do not support or recognize. -->
|
|
||||||
<string name="file_not_recognized">"الملف غير المعترف."</string>
|
|
||||||
<string name="habits_imported">"نجح إستيراد العادات."</string>
|
|
||||||
<string name="full_backup_success">"نجح تصدير النسخ الاحتياطي الكامل."</string>
|
|
||||||
<string name="import_data">"استيراد بيانات."</string>
|
|
||||||
<string name="export_full_backup">"صدر نسخة احتياطية كاملة."</string>
|
|
||||||
<string name="import_data_summary">"تدعم النسخ الاحتياطي الكامل المصدرة من هذا التطبيق، فضلا عن الملفات التي تم إنشاؤها من Tickmate, HabitBull و Rewire. انظر التعليمات لمزيد من المعلومات."</string>
|
|
||||||
<string name="export_as_csv_summary">"صدر ملف التي يمكن فتحها ببرنامج جداول البيانات مثل إكسل أو وبينوفيس. لا يمكن إستيراد هذا الملف."</string>
|
|
||||||
<string name="export_full_backup_summary">"إنشاء ملف يحتوي على كافة البيانات. يمكن استيراد هذا الملف نفسه."</string>
|
|
||||||
<string name="bug_report_failed">"فشل في توليد تقرير الاعطال"</string>
|
|
||||||
<string name="generate_bug_report">"توليد تقرير الاعطال"</string>
|
|
||||||
<string name="troubleshooting">"استكشاف الأخطاء وإصلاحها"</string>
|
|
||||||
<string name="help_translate">"المساعدة في ترجمة هذا البرنامج"</string>
|
|
||||||
<string name="night_mode">"الوضع الليلي"</string>
|
|
||||||
<string name="use_pure_black">"استخدام أسود نقي في الوضع الليلي"</string>
|
|
||||||
<string name="pure_black_description">"يستبدل خلفيات رمادية مع أسود نقي في الوضع الليلي. يقلل من استهلاك البطارية في الهواتف مع شاشة AMOLED."</string>
|
|
||||||
<string name="interface_preferences">"السطح البيني"</string>
|
|
||||||
<string name="reverse_days">"ترتيب عكسي أيام"</string>
|
|
||||||
<string name="reverse_days_description">"عرض أيام في ترتيب عكسي على الشاشة الرئيسية"</string>
|
|
||||||
<string name="day">"يوم"</string>
|
|
||||||
<string name="week">"أسبوع"</string>
|
|
||||||
<string name="month">"شهر"</string>
|
|
||||||
|
|
||||||
<!-- Three-month period -->
|
|
||||||
<string name="quarter">"ربع سنه"</string>
|
|
||||||
<string name="year">"عام"</string>
|
|
||||||
|
|
||||||
<!-- Middle part of the sentence '1 time in xx days' -->
|
|
||||||
<!-- Middle part of the sentence '1 time in xx days' -->
|
|
||||||
<string name="time_every">"مرات في"</string>
|
|
||||||
<string name="every_x_days">"كل %d أيام"</string>
|
|
||||||
<string name="every_x_weeks">"كل %d أسابيع"</string>
|
|
||||||
<string name="every_x_months">"كل %d أشهر"</string>
|
|
||||||
|
|
||||||
<!-- The old "habit strength" has been replaced by "score". Feel free to translate "score" as "strength" or "stability" if it sounds more natural in your language. -->
|
|
||||||
<string name="score">"النقاط"</string>
|
|
||||||
<string name="reminder_sound">"صوت تذكير"</string>
|
|
||||||
|
|
||||||
<!-- Appears when the user disables the reminder sound. Could also be "no sound", "mute" or "silent". -->
|
|
||||||
<string name="none">"صامت"</string>
|
|
||||||
</resources>
|
|
||||||
|
|||||||
178
app/src/main/res/values-bg/strings.xml
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--Generated by crowdin.com-->
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||||
|
~
|
||||||
|
~ This file is part of Loop Habit Tracker.
|
||||||
|
~
|
||||||
|
~ Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||||
|
~ it under the terms of the GNU General Public License as published by the
|
||||||
|
~ Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
~ option) any later version.
|
||||||
|
~
|
||||||
|
~ Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||||
|
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
~ more details.
|
||||||
|
~
|
||||||
|
~ You should have received a copy of the GNU General Public License along
|
||||||
|
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">Loop Следене на навици</string>
|
||||||
|
<string name="main_activity_title">Навици</string>
|
||||||
|
<string name="action_settings">Настройки</string>
|
||||||
|
<string name="edit">Редактиране</string>
|
||||||
|
<string name="delete">Изтриване</string>
|
||||||
|
<string name="archive">Архивиране</string>
|
||||||
|
<string name="unarchive">Разархивиране</string>
|
||||||
|
<string name="add_habit">Добавяне на навик</string>
|
||||||
|
<string name="color_picker_default_title">Промяна на цвят</string>
|
||||||
|
<string name="toast_habit_created">Навикът е създаден</string>
|
||||||
|
<string name="toast_habit_deleted">Навиците са изтрити</string>
|
||||||
|
<string name="toast_habit_restored">Навиците са възстановени</string>
|
||||||
|
<string name="toast_nothing_to_undo">Нищо за отмяна</string>
|
||||||
|
<string name="toast_nothing_to_redo">Нищо за възстановяване</string>
|
||||||
|
<string name="toast_habit_changed">Навикът е променен</string>
|
||||||
|
<string name="toast_habit_changed_back">Промяната на навика е отменена.</string>
|
||||||
|
<string name="toast_habit_archived">Навиците са архивирани</string>
|
||||||
|
<string name="toast_habit_unarchived">Навиците са разархивирани</string>
|
||||||
|
<string name="overview">Обобщение</string>
|
||||||
|
<string name="habit_strength">Сила на навика</string>
|
||||||
|
<string name="history">История</string>
|
||||||
|
<string name="clear">Изчистване</string>
|
||||||
|
<string name="description_hint">Въпрос (Днес, ... ли?)</string>
|
||||||
|
<string name="repeat">Повтори</string>
|
||||||
|
<string name="times_every">пъти в период от</string>
|
||||||
|
<string name="days">дни</string>
|
||||||
|
<string name="reminder">Напомняне</string>
|
||||||
|
<string name="discard">Отказ</string>
|
||||||
|
<string name="save">Запазване</string>
|
||||||
|
<string name="streaks">Поредици</string>
|
||||||
|
<string name="no_habits_found">Нямате активни навици</string>
|
||||||
|
<string name="long_press_to_toggle">Натиснете и задръжте за да добавите или премахнете отметка</string>
|
||||||
|
<string name="reminder_off">Изключено</string>
|
||||||
|
<string name="validation_name_should_not_be_blank">Името не може да бъде празно.</string>
|
||||||
|
<string name="validation_number_should_be_positive">Числото трябва да е положително.</string>
|
||||||
|
<string name="validation_at_most_one_rep_per_day">Позволено е до едно повторение на ден.</string>
|
||||||
|
<string name="create_habit">Създаване на навик</string>
|
||||||
|
<string name="edit_habit">Редактиране на навик</string>
|
||||||
|
<string name="check">Поставяне на отметка</string>
|
||||||
|
<string name="snooze">По-късно</string>
|
||||||
|
<!-- App introduction -->
|
||||||
|
<string name="intro_title_1">Добре дошли</string>
|
||||||
|
<string name="intro_description_1">Loop Следене на навици ви помага да създавате и поддържате добри навици.</string>
|
||||||
|
<string name="intro_title_2">Създайте нови навици</string>
|
||||||
|
<string name="intro_description_2">Всеки ден, след изпълнението на навика, поставете отметка в приложението.</string>
|
||||||
|
<string name="intro_title_3">Продължавайте да го изпълнявате</string>
|
||||||
|
<string name="intro_description_3">Навици изпълнявани редовно за дълго време ще получат пълна звезда.</string>
|
||||||
|
<string name="intro_title_4">Следете прогреса си</string>
|
||||||
|
<string name="intro_description_4">Подробни диаграми ви показват как вашите навици са се подобрили с времето.</string>
|
||||||
|
<string name="interval_15_minutes">15 минути</string>
|
||||||
|
<string name="interval_30_minutes">30 минути</string>
|
||||||
|
<string name="interval_1_hour">1 час</string>
|
||||||
|
<string name="interval_2_hour">2 часа</string>
|
||||||
|
<string name="interval_4_hour">4 часа</string>
|
||||||
|
<string name="interval_8_hour">8 часа</string>
|
||||||
|
<string name="interval_24_hour">24 часа</string>
|
||||||
|
<string name="pref_toggle_title">Маркиране с кратко натискане</string>
|
||||||
|
<string name="pref_toggle_description">Поставяне на отметки с кратко натискане вместо с натискане и задържане. По-удобно, но може да доведе до неволно маркиране.</string>
|
||||||
|
<string name="pref_snooze_interval_title">Интервал на напомняне след отлагане</string>
|
||||||
|
<string name="pref_rate_this_app">Оценяване на това приложение в Google Play</string>
|
||||||
|
<string name="pref_send_feedback">Изпращане на отзиви към разработчика</string>
|
||||||
|
<string name="pref_view_source_code">Преглед на програмния код в GitHub</string>
|
||||||
|
<string name="pref_view_app_introduction">Преглед на въведение в приложението</string>
|
||||||
|
<string name="links">Препратки</string>
|
||||||
|
<string name="behavior">Поведение</string>
|
||||||
|
<string name="name">Име</string>
|
||||||
|
<string name="settings">Настройки</string>
|
||||||
|
<string name="snooze_interval">Интервал на отлагане</string>
|
||||||
|
<string name="hint_title">Знаете ли че?</string>
|
||||||
|
<string name="hint_drag">За да пренаредите записите, натиснете и задръжте върху името на навика и го придърпайте до правилното място.</string>
|
||||||
|
<string name="hint_landscape">Може да виждате повече дни като обърнете телефона си в хоризонтално положение.</string>
|
||||||
|
<string name="delete_habits">Изтриване на навици</string>
|
||||||
|
<string name="delete_habits_message">Навиците ще се изтрият перманентно. Това действие не може да бъде отменено.</string>
|
||||||
|
<string name="habit_not_found">Навикът е изтрит / не е намерен</string>
|
||||||
|
<string name="weekends">Събота и неделя</string>
|
||||||
|
<string name="any_weekday">От понеделник до петък</string>
|
||||||
|
<string name="any_day">Всеки ден от седмицата</string>
|
||||||
|
<string name="select_weekdays">Избор на дни</string>
|
||||||
|
<string name="export_to_csv">Експортиране като CSV</string>
|
||||||
|
<string name="done_label">Готово</string>
|
||||||
|
<string name="clear_label">Изчистване</string>
|
||||||
|
<string name="select_hours">Избиране на час</string>
|
||||||
|
<string name="select_minutes">Избиране на минута</string>
|
||||||
|
<string name="about">За приложението</string>
|
||||||
|
<string name="translators">Преводачи</string>
|
||||||
|
<string name="developers">Разработчици</string>
|
||||||
|
<string name="version_n">Версия %s</string>
|
||||||
|
<string name="frequency">Честота</string>
|
||||||
|
<string name="checkmark">Отметка</string>
|
||||||
|
<string name="strength">Сила</string>
|
||||||
|
<string name="best_streaks">Най-добри поредици</string>
|
||||||
|
<string name="current_streaks">Текуща поредица</string>
|
||||||
|
<string name="number_of_repetitions">Брой повторения</string>
|
||||||
|
<string name="last_x_days">Последните %d дни</string>
|
||||||
|
<string name="last_x_weeks">Последните %d седмици</string>
|
||||||
|
<string name="last_x_months">Последните %d месеци</string>
|
||||||
|
<string name="last_x_years">Последните %d години</string>
|
||||||
|
<string name="all_time">От началото</string>
|
||||||
|
<string name="every_day">Всеки ден</string>
|
||||||
|
<string name="every_week">Всяка седмица</string>
|
||||||
|
<string name="two_times_per_week">2 пъти седмично</string>
|
||||||
|
<string name="five_times_per_week">5 пъти седмично</string>
|
||||||
|
<string name="custom_frequency">Друго ...</string>
|
||||||
|
<string name="help">Помощ & ЧЗВ</string>
|
||||||
|
<string name="could_not_export">Неуспешно експортиране на данни.</string>
|
||||||
|
<string name="could_not_import">Неуспешно импортиране на данни.</string>
|
||||||
|
<string name="file_not_recognized">Файлът не е разпознат.</string>
|
||||||
|
<string name="habits_imported">Навиците са импортирани успешно.</string>
|
||||||
|
<string name="full_backup_success">Пълно резервно копие е експортирано успешно.</string>
|
||||||
|
<string name="import_data">Импортиране на данни</string>
|
||||||
|
<string name="export_full_backup">Експортиране на пълно резервно копие</string>
|
||||||
|
<string name="import_data_summary">Поддържа пълни резервни копия експортирани чрез това приложение, както и файлове генерирани чрез Tickmate, HabitBull или Rewire. Вижте ЧЗВ за повече информация.</string>
|
||||||
|
<string name="export_as_csv_summary">Генерира файлове, които могат да се отварят със софтуер за електронни таблици като Microsoft Excel или OpenOffice Calc. Този файл не може да се импортира обратно.</string>
|
||||||
|
<string name="export_full_backup_summary">Генерира файл, който съдържа всичките ви данни. Този файл може да бъде импортиран обратно.</string>
|
||||||
|
<string name="bug_report_failed">Неуспешно генериране на доклад за грешки.</string>
|
||||||
|
<string name="generate_bug_report">Генериране на доклад за грешки</string>
|
||||||
|
<string name="troubleshooting">Отстраняване на проблеми</string>
|
||||||
|
<string name="help_translate">Помагане за превода на това приложение</string>
|
||||||
|
<string name="night_mode">Нощен режим</string>
|
||||||
|
<string name="use_pure_black">Използване на чисто черно при нощен режим</string>
|
||||||
|
<string name="pure_black_description">Заменя сивите фонове с чисто черни при нощен режим. Намаля разхода на батерията при телефони с AMOLED дисплеи.</string>
|
||||||
|
<string name="interface_preferences">Интерфейс</string>
|
||||||
|
<string name="reverse_days">Обратен ред на дните</string>
|
||||||
|
<string name="reverse_days_description">Показва дните на основния екран в обратен ред</string>
|
||||||
|
<string name="day">Ден</string>
|
||||||
|
<string name="week">Седмица</string>
|
||||||
|
<string name="month">Месец</string>
|
||||||
|
<string name="quarter">Тримесечие</string>
|
||||||
|
<string name="year">Година</string>
|
||||||
|
<string name="total">Общо</string>
|
||||||
|
<!-- Middle part of the sentence '1 time in xx days' -->
|
||||||
|
<string name="time_every">път в период от</string>
|
||||||
|
<string name="every_x_days">На всеки %d дни</string>
|
||||||
|
<string name="every_x_weeks">На всеки %d седмици</string>
|
||||||
|
<string name="every_x_months">На всеки %d месеца</string>
|
||||||
|
<string name="score">Сила</string>
|
||||||
|
<string name="reminder_sound">Звук за напомняне</string>
|
||||||
|
<string name="none">Няма</string>
|
||||||
|
<string name="filter">Филтър</string>
|
||||||
|
<string name="hide_completed">Скриване на завършените</string>
|
||||||
|
<string name="hide_archived">Скриване на архивираните</string>
|
||||||
|
<string name="sticky_notifications">Направи нотификациите постоянни</string>
|
||||||
|
<string name="sticky_notifications_description">Предотвратява изчистването на нотификацията с плъзване настрани.</string>
|
||||||
|
<string name="repair_database">Поправка на базата данни</string>
|
||||||
|
<string name="database_repaired">Базата данни е поправена.</string>
|
||||||
|
<string name="uncheck">Премахване на отметка</string>
|
||||||
|
<string name="toggle">Смяна</string>
|
||||||
|
<string name="action">Действие</string>
|
||||||
|
<string name="habit">Навик</string>
|
||||||
|
<string name="sort">Сортиране</string>
|
||||||
|
<string name="manually">Ръчно</string>
|
||||||
|
<string name="by_name">По име</string>
|
||||||
|
<string name="by_color">По цвят</string>
|
||||||
|
<string name="by_score">По сила</string>
|
||||||
|
<string name="download">Изтегляне</string>
|
||||||
|
<string name="export">Експортиране</string>
|
||||||
|
</resources>
|
||||||