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/
|
||||
gen/
|
||||
local.properties
|
||||
crowdin.yaml
|
||||
local
|
||||
|
||||
32
CHANGELOG.md
@@ -1,5 +1,37 @@
|
||||
# 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)
|
||||
|
||||
* Add option to make notifications sticky
|
||||
|
||||
@@ -4,13 +4,13 @@ apply plugin: 'me.tatarka.retrolambda'
|
||||
apply plugin: 'jacoco'
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion "23.0.3"
|
||||
compileSdkVersion 25
|
||||
buildToolsVersion "25.0.2"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "org.isoron.uhabits"
|
||||
minSdkVersion 15
|
||||
targetSdkVersion 23
|
||||
targetSdkVersion 25
|
||||
|
||||
buildConfigField "Integer", "databaseVersion", "15"
|
||||
buildConfigField "String", "databaseFilename", "\"uhabits.db\""
|
||||
@@ -25,7 +25,7 @@ android {
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||
}
|
||||
debug {
|
||||
testCoverageEnabled = true
|
||||
testCoverageEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ dependencies {
|
||||
|
||||
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:runner:0.5'
|
||||
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.jakewharton:butterknife-compiler:8.0.1'
|
||||
|
||||
compile 'com.android.support:appcompat-v7:23.3.0'
|
||||
compile 'com.android.support:design:23.3.0'
|
||||
compile 'com.android.support:preference-v14:23.3.0'
|
||||
compile 'com.android.support:support-v4:23.3.0'
|
||||
compile 'com.android.support:appcompat-v7:25.3.0'
|
||||
compile 'com.android.support:design:25.3.0'
|
||||
compile 'com.android.support:preference-v14:25.3.0'
|
||||
compile 'com.android.support:support-v4:25.3.0'
|
||||
compile 'com.getpebble:pebblekit:3.0.0'
|
||||
compile 'com.github.paolorotolo:appintro:3.4.0'
|
||||
compile 'com.google.auto.factory:auto-factory:1.0-beta3'
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.content.*;
|
||||
import android.os.*;
|
||||
import android.support.annotation.*;
|
||||
import android.support.test.*;
|
||||
import android.util.*;
|
||||
|
||||
import org.isoron.uhabits.models.*;
|
||||
import org.isoron.uhabits.preferences.*;
|
||||
@@ -31,6 +32,7 @@ import org.isoron.uhabits.tasks.*;
|
||||
import org.isoron.uhabits.utils.*;
|
||||
import org.junit.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
@@ -63,6 +65,8 @@ public class BaseAndroidTest
|
||||
|
||||
protected AndroidTestComponent component;
|
||||
|
||||
protected ModelFactory modelFactory;
|
||||
|
||||
@Before
|
||||
public void setUp()
|
||||
{
|
||||
@@ -89,7 +93,7 @@ public class BaseAndroidTest
|
||||
taskRunner = component.getTaskRunner();
|
||||
logger = component.getHabitsLogger();
|
||||
|
||||
ModelFactory modelFactory = component.getModelFactory();
|
||||
modelFactory = component.getModelFactory();
|
||||
fixtures = new HabitFixtures(modelFactory, habitList);
|
||||
|
||||
latch = new CountDownLatch(1);
|
||||
@@ -130,4 +134,18 @@ public class BaseAndroidTest
|
||||
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
|
||||
{
|
||||
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(
|
||||
"Could not find suitable dir for screenshots");
|
||||
|
||||
|
||||
@@ -39,12 +39,18 @@ public class HabitFixtures
|
||||
}
|
||||
|
||||
public Habit createEmptyHabit()
|
||||
{
|
||||
return createEmptyHabit(null);
|
||||
}
|
||||
|
||||
public Habit createEmptyHabit(Long id)
|
||||
{
|
||||
Habit habit = modelFactory.buildHabit();
|
||||
habit.setName("Meditate");
|
||||
habit.setDescription("Did you meditate this morning?");
|
||||
habit.setColor(3);
|
||||
habit.setFrequency(Frequency.DAILY);
|
||||
habit.setId(id);
|
||||
habitList.add(habit);
|
||||
return habit;
|
||||
}
|
||||
|
||||
@@ -46,12 +46,12 @@ public class CheckmarkButtonViewTest extends BaseViewTest
|
||||
public void setUp()
|
||||
{
|
||||
super.setUp();
|
||||
setSimilarityCutoff(0.03f);
|
||||
setSimilarityCutoff(0.015f);
|
||||
|
||||
latch = new CountDownLatch(1);
|
||||
view = new CheckmarkButtonView(targetContext);
|
||||
view.setValue(Checkmark.UNCHECKED);
|
||||
view.setColor(ColorUtils.getAndroidTestColor(7));
|
||||
view.setColor(ColorUtils.getAndroidTestColor(5));
|
||||
|
||||
measureView(view, dpToPixels(40), dpToPixels(40));
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ public class CheckmarkPanelViewTest extends BaseViewTest
|
||||
view = new CheckmarkPanelView(targetContext);
|
||||
view.setHabit(habit);
|
||||
view.setCheckmarkValues(checkmarks);
|
||||
view.setButtonCount(4);
|
||||
view.setColor(ColorUtils.getAndroidTestColor(7));
|
||||
|
||||
measureView(view, dpToPixels(200), dpToPixels(200));
|
||||
|
||||
@@ -40,8 +40,6 @@ import static org.junit.Assert.*;
|
||||
@MediumTest
|
||||
public class ImportTest extends BaseAndroidTest
|
||||
{
|
||||
private File baseDir;
|
||||
|
||||
private Context context;
|
||||
|
||||
@Override
|
||||
@@ -50,11 +48,8 @@ public class ImportTest extends BaseAndroidTest
|
||||
{
|
||||
super.setUp();
|
||||
DateUtils.setFixedLocalTime(null);
|
||||
|
||||
fixtures.purgeHabits(habitList);
|
||||
context = InstrumentationRegistry.getInstrumentation().getContext();
|
||||
baseDir = FileUtils.getFilesDir("Backups");
|
||||
if (baseDir == null) fail("baseDir should not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -149,8 +144,7 @@ public class ImportTest extends BaseAndroidTest
|
||||
|
||||
private void importFromFile(String assetFilename) throws IOException
|
||||
{
|
||||
File file =
|
||||
new File(String.format("%s/%s", baseDir.getPath(), assetFilename));
|
||||
File file = File.createTempFile("asset", "");
|
||||
copyAssetToFile(assetFilename, file);
|
||||
assertTrue(file.exists());
|
||||
assertTrue(file.canRead());
|
||||
@@ -159,5 +153,7 @@ public class ImportTest extends BaseAndroidTest
|
||||
assertThat(importer.canHandle(file), is(true));
|
||||
|
||||
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"));
|
||||
}
|
||||
|
||||
// @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
|
||||
public void testGetById()
|
||||
{
|
||||
@@ -178,45 +167,6 @@ public class SQLiteHabitListTest extends BaseAndroidTest
|
||||
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)
|
||||
{
|
||||
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);
|
||||
|
||||
taskRunner.execute(
|
||||
new ExportCSVTask(habitList, selected, archiveFilename -> {
|
||||
new ExportCSVTask(targetContext,habitList, selected, archiveFilename -> {
|
||||
assertThat(archiveFilename, is(not(nullValue())));
|
||||
File f = new File(archiveFilename);
|
||||
assertTrue(f.exists());
|
||||
|
||||
@@ -46,7 +46,7 @@ public class ExportDBTaskTest extends BaseAndroidTest
|
||||
@Test
|
||||
public void testExportCSV() throws Throwable
|
||||
{
|
||||
ExportDBTask task = new ExportDBTask(filename -> {
|
||||
ExportDBTask task = new ExportDBTask(targetContext, filename -> {
|
||||
assertThat(filename, is(not(nullValue())));
|
||||
|
||||
File f = new File(filename);
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
<manifest
|
||||
package="org.isoron.uhabits"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:versionCode="25"
|
||||
android:versionName="1.6.2">
|
||||
android:versionCode="30"
|
||||
android:versionName="1.7.3">
|
||||
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
|
||||
@@ -223,5 +223,15 @@
|
||||
</intent-filter>
|
||||
</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>
|
||||
</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.content.*;
|
||||
import android.support.annotation.*;
|
||||
|
||||
import com.activeandroid.*;
|
||||
|
||||
import org.isoron.uhabits.models.sqlite.*;
|
||||
import org.isoron.uhabits.notifications.*;
|
||||
import org.isoron.uhabits.preferences.*;
|
||||
import org.isoron.uhabits.tasks.*;
|
||||
@@ -38,7 +38,7 @@ import java.io.*;
|
||||
*/
|
||||
public class HabitsApplication extends Application
|
||||
{
|
||||
private static Context context;
|
||||
private Context context;
|
||||
|
||||
private static AppComponent component;
|
||||
|
||||
@@ -58,26 +58,14 @@ public class HabitsApplication extends Application
|
||||
HabitsApplication.component = component;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Deprecated
|
||||
public static Context getContext()
|
||||
{
|
||||
if (context == null) throw new RuntimeException("context is null");
|
||||
return context;
|
||||
}
|
||||
|
||||
public static boolean isTestMode()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (context != null)
|
||||
{
|
||||
String testClass = "org.isoron.uhabits.BaseAndroidTest";
|
||||
context.getClassLoader().loadClass(testClass);
|
||||
}
|
||||
Class.forName ("org.isoron.uhabits.BaseAndroidTest");
|
||||
return true;
|
||||
}
|
||||
catch (final Exception e)
|
||||
catch (final ClassNotFoundException e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -87,7 +75,7 @@ public class HabitsApplication extends Application
|
||||
public void onCreate()
|
||||
{
|
||||
super.onCreate();
|
||||
HabitsApplication.context = this;
|
||||
context = this;
|
||||
|
||||
component = DaggerAppComponent
|
||||
.builder()
|
||||
@@ -96,11 +84,20 @@ public class HabitsApplication extends Application
|
||||
|
||||
if (isTestMode())
|
||||
{
|
||||
File db = DatabaseUtils.getDatabaseFile();
|
||||
File db = DatabaseUtils.getDatabaseFile(context);
|
||||
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.startListening();
|
||||
@@ -125,7 +122,7 @@ public class HabitsApplication extends Application
|
||||
@Override
|
||||
public void onTerminate()
|
||||
{
|
||||
HabitsApplication.context = null;
|
||||
context = null;
|
||||
ActiveAndroid.dispose();
|
||||
|
||||
reminderScheduler.stopListening();
|
||||
|
||||
@@ -40,6 +40,7 @@ import java.io.*;
|
||||
|
||||
import static android.os.Build.VERSION.*;
|
||||
import static android.os.Build.VERSION_CODES.*;
|
||||
import static android.support.v4.content.FileProvider.*;
|
||||
|
||||
/**
|
||||
* Base class for all screens in the application.
|
||||
@@ -50,6 +51,8 @@ import static android.os.Build.VERSION_CODES.*;
|
||||
*/
|
||||
public class BaseScreen
|
||||
{
|
||||
public static final int REQUEST_CREATE_DOCUMENT = 1;
|
||||
|
||||
protected BaseActivity activity;
|
||||
|
||||
@Nullable
|
||||
@@ -230,11 +233,14 @@ public class BaseScreen
|
||||
|
||||
public void showSendFileScreen(@NonNull String archiveFilename)
|
||||
{
|
||||
File file = new File(archiveFilename);
|
||||
Uri fileUri = getUriForFile(activity, "org.isoron.uhabits", file);
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_SEND);
|
||||
intent.setType("application/zip");
|
||||
intent.putExtra(Intent.EXTRA_STREAM,
|
||||
Uri.fromFile(new File(archiveFilename)));
|
||||
intent.putExtra(Intent.EXTRA_STREAM, fileUri);
|
||||
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ public class BaseSystem
|
||||
|
||||
if (context == null) throw new RuntimeException(
|
||||
"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");
|
||||
|
||||
File logFile =
|
||||
|
||||
@@ -25,7 +25,7 @@ import android.support.v7.widget.Toolbar;
|
||||
import android.widget.*;
|
||||
|
||||
import org.isoron.uhabits.BuildConfig;
|
||||
import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.activities.*;
|
||||
import org.isoron.uhabits.intents.*;
|
||||
import org.isoron.uhabits.utils.*;
|
||||
@@ -94,6 +94,13 @@ public class AboutRootView extends BaseRootView
|
||||
getContext().startActivity(intent);
|
||||
}
|
||||
|
||||
@OnClick(R.id.tvTranslate)
|
||||
public void onClickTranslate()
|
||||
{
|
||||
Intent intent = intents.helpTranslate(getContext());
|
||||
getContext().startActivity(intent);
|
||||
}
|
||||
|
||||
@OnClick(R.id.tvRate)
|
||||
public void onClickRate()
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ import android.support.v7.app.*;
|
||||
|
||||
import com.google.auto.factory.*;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.activities.*;
|
||||
|
||||
import butterknife.*;
|
||||
|
||||
@@ -78,6 +78,8 @@ public class HistoryEditorDialog extends AppCompatDialogFragment
|
||||
{
|
||||
long id = savedInstanceState.getLong("habit", -1);
|
||||
if (id > 0) this.habit = habitList.getById(id);
|
||||
historyChart.onRestoreInstanceState(
|
||||
savedInstanceState.getParcelable("historyChart"));
|
||||
}
|
||||
|
||||
int padding =
|
||||
@@ -129,6 +131,7 @@ public class HistoryEditorDialog extends AppCompatDialogFragment
|
||||
public void onSaveInstanceState(Bundle outState)
|
||||
{
|
||||
outState.putLong("habit", habit.getId());
|
||||
outState.putParcelable("historyChart", historyChart.onSaveInstanceState());
|
||||
}
|
||||
|
||||
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.content.*;
|
||||
import android.os.*;
|
||||
import android.util.*;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
@@ -32,7 +33,9 @@ public abstract class ScrollableChart extends View
|
||||
|
||||
private int dataOffset;
|
||||
|
||||
private int scrollerBucketSize;
|
||||
private int scrollerBucketSize = 1;
|
||||
|
||||
private int direction = 1;
|
||||
|
||||
private GestureDetector detector;
|
||||
|
||||
@@ -40,6 +43,10 @@ public abstract class ScrollableChart extends View
|
||||
|
||||
private ValueAnimator scrollAnimator;
|
||||
|
||||
private ScrollController scrollController;
|
||||
|
||||
private int maxDataOffset = 10000;
|
||||
|
||||
public ScrollableChart(Context context)
|
||||
{
|
||||
super(context);
|
||||
@@ -63,8 +70,7 @@ public abstract class ScrollableChart extends View
|
||||
if (!scroller.isFinished())
|
||||
{
|
||||
scroller.computeScrollOffset();
|
||||
dataOffset = Math.max(0, scroller.getCurrX() / scrollerBucketSize);
|
||||
postInvalidate();
|
||||
updateDataOffset();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -85,19 +91,50 @@ public abstract class ScrollableChart extends View
|
||||
float velocityY)
|
||||
{
|
||||
scroller.fling(scroller.getCurrX(), scroller.getCurrY(),
|
||||
(int) velocityX / 2, 0, 0, 100000, 0, 0);
|
||||
direction * ((int) velocityX) / 2, 0, 0, getMaxX(), 0, 0);
|
||||
invalidate();
|
||||
|
||||
scrollAnimator.setDuration(scroller.getDuration());
|
||||
scrollAnimator.start();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLongPress(MotionEvent e)
|
||||
private int getMaxX()
|
||||
{
|
||||
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
|
||||
@@ -111,12 +148,14 @@ public abstract class ScrollableChart extends View
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -138,6 +177,32 @@ public abstract class ScrollableChart extends View
|
||||
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)
|
||||
{
|
||||
this.scrollerBucketSize = scrollerBucketSize;
|
||||
@@ -149,5 +214,25 @@ public abstract class ScrollableChart extends View
|
||||
scroller = new Scroller(context, null, true);
|
||||
scrollAnimator = ValueAnimator.ofFloat(0, 1);
|
||||
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 org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.activities.*;
|
||||
import org.isoron.uhabits.activities.common.dialogs.*;
|
||||
import org.isoron.uhabits.commands.*;
|
||||
@@ -38,6 +39,8 @@ import java.util.*;
|
||||
|
||||
import butterknife.*;
|
||||
|
||||
import static org.isoron.uhabits.activities.ThemeSwitcher.*;
|
||||
|
||||
public abstract class BaseDialog extends AppCompatDialogFragment
|
||||
{
|
||||
@Nullable
|
||||
@@ -61,6 +64,18 @@ public abstract class BaseDialog extends AppCompatDialogFragment
|
||||
|
||||
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
|
||||
public void onActivityCreated(Bundle savedInstanceState)
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ import android.support.v4.app.*;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.*;
|
||||
import org.isoron.uhabits.utils.*;
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.activities.*;
|
||||
import org.isoron.uhabits.activities.habits.list.model.*;
|
||||
import org.isoron.uhabits.preferences.*;
|
||||
import org.isoron.uhabits.utils.*;
|
||||
|
||||
/**
|
||||
* 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 MidnightTimer midnightTimer;
|
||||
|
||||
public ListHabitsComponent getListHabitsComponent()
|
||||
{
|
||||
return component;
|
||||
@@ -77,6 +80,8 @@ public class ListHabitsActivity extends BaseActivity
|
||||
screen.setSelectionMenu(selectionMenu);
|
||||
rootView.setController(controller, selectionMenu);
|
||||
|
||||
midnightTimer = component.getMidnightTimer();
|
||||
|
||||
setScreen(screen);
|
||||
controller.onStartup();
|
||||
}
|
||||
@@ -84,6 +89,7 @@ public class ListHabitsActivity extends BaseActivity
|
||||
@Override
|
||||
protected void onPause()
|
||||
{
|
||||
midnightTimer.onPause();
|
||||
screen.onDettached();
|
||||
adapter.cancelRefresh();
|
||||
super.onPause();
|
||||
@@ -95,6 +101,7 @@ public class ListHabitsActivity extends BaseActivity
|
||||
adapter.refresh();
|
||||
screen.onAttached();
|
||||
rootView.postInvalidate();
|
||||
midnightTimer.onResume();
|
||||
|
||||
if (prefs.getTheme() == ThemeSwitcher.THEME_DARK &&
|
||||
prefs.isPureBlackEnabled() != pureBlack)
|
||||
|
||||
@@ -23,13 +23,14 @@ import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.activities.*;
|
||||
import org.isoron.uhabits.activities.habits.list.controllers.*;
|
||||
import org.isoron.uhabits.activities.habits.list.model.*;
|
||||
import org.isoron.uhabits.utils.*;
|
||||
|
||||
import dagger.*;
|
||||
|
||||
@ActivityScope
|
||||
@Component(modules = { ActivityModule.class },
|
||||
dependencies = { AppComponent.class })
|
||||
public interface ListHabitsComponent extends ActivityComponent
|
||||
public interface ListHabitsComponent
|
||||
{
|
||||
CheckmarkButtonControllerFactory getCheckmarkButtonControllerFactory();
|
||||
|
||||
@@ -44,4 +45,6 @@ public interface ListHabitsComponent extends ActivityComponent
|
||||
ListHabitsScreen getScreen();
|
||||
|
||||
ListHabitsSelectionMenu getSelectionMenu();
|
||||
|
||||
MidnightTimer getMidnightTimer();
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ import javax.inject.*;
|
||||
public class ListHabitsController
|
||||
implements HabitCardListController.HabitListener
|
||||
{
|
||||
|
||||
@NonNull
|
||||
private final ListHabitsScreen screen;
|
||||
|
||||
@@ -70,6 +71,8 @@ public class ListHabitsController
|
||||
|
||||
private ExportCSVTaskFactory exportCSVFactory;
|
||||
|
||||
private ExportDBTaskFactory exportDBFactory;
|
||||
|
||||
@Inject
|
||||
public ListHabitsController(@NonNull BaseSystem system,
|
||||
@NonNull CommandRunner commandRunner,
|
||||
@@ -82,7 +85,8 @@ public class ListHabitsController
|
||||
@NonNull WidgetUpdater widgetUpdater,
|
||||
@NonNull
|
||||
ImportDataTaskFactory importTaskFactory,
|
||||
@NonNull ExportCSVTaskFactory exportCSVFactory)
|
||||
@NonNull ExportCSVTaskFactory exportCSVFactory,
|
||||
@NonNull ExportDBTaskFactory exportDBFactory)
|
||||
{
|
||||
this.adapter = adapter;
|
||||
this.commandRunner = commandRunner;
|
||||
@@ -95,6 +99,7 @@ public class ListHabitsController
|
||||
this.widgetUpdater = widgetUpdater;
|
||||
this.importTaskFactory = importTaskFactory;
|
||||
this.exportCSVFactory = exportCSVFactory;
|
||||
this.exportDBFactory = exportDBFactory;
|
||||
}
|
||||
|
||||
public void onExportCSV()
|
||||
@@ -110,7 +115,7 @@ public class ListHabitsController
|
||||
|
||||
public void onExportDB()
|
||||
{
|
||||
taskRunner.execute(new ExportDBTask(filename -> {
|
||||
taskRunner.execute(exportDBFactory.create(filename -> {
|
||||
if (filename != null) screen.showSendFileScreen(filename);
|
||||
else screen.showMessage(R.string.could_not_export);
|
||||
}));
|
||||
@@ -128,7 +133,8 @@ public class ListHabitsController
|
||||
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 -> {
|
||||
switch (result)
|
||||
@@ -146,6 +152,8 @@ public class ListHabitsController
|
||||
screen.showMessage(R.string.could_not_import);
|
||||
break;
|
||||
}
|
||||
|
||||
finishedListener.onFinish();
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -208,4 +216,9 @@ public class ListHabitsController
|
||||
prefs.updateLastHint(-1, DateUtils.getStartOfToday());
|
||||
screen.showIntroScreen();
|
||||
}
|
||||
|
||||
public interface OnFinishedListener
|
||||
{
|
||||
void onFinish();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +112,22 @@ public class ListHabitsMenu extends BaseMenu
|
||||
invalidate();
|
||||
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:
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -26,8 +26,9 @@ import android.support.v7.widget.Toolbar;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.R;
|
||||
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.model.*;
|
||||
import org.isoron.uhabits.activities.habits.list.views.*;
|
||||
@@ -43,7 +44,7 @@ import butterknife.*;
|
||||
public class ListHabitsRootView extends BaseRootView
|
||||
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)
|
||||
HabitCardListView listView;
|
||||
@@ -132,6 +133,13 @@ public class ListHabitsRootView extends BaseRootView
|
||||
listController.setSelectionListener(menu);
|
||||
listView.setController(listController);
|
||||
menu.setListController(listController);
|
||||
header.setScrollController(new ScrollableChart.ScrollController() {
|
||||
@Override
|
||||
public void onDataOffsetChanged(int newDataOffset)
|
||||
{
|
||||
listView.setDataOffset(newDataOffset);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -156,6 +164,7 @@ public class ListHabitsRootView extends BaseRootView
|
||||
{
|
||||
int count = getCheckmarkCount();
|
||||
header.setButtonCount(count);
|
||||
header.setMaxDataOffset(Math.max(MAX_CHECKMARK_COUNT - count, 0));
|
||||
listView.setCheckmarkCount(count);
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,9 @@
|
||||
|
||||
package org.isoron.uhabits.activities.habits.list;
|
||||
|
||||
import android.app.*;
|
||||
import android.content.*;
|
||||
import android.net.*;
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
@@ -31,24 +33,32 @@ import org.isoron.uhabits.commands.*;
|
||||
import org.isoron.uhabits.intents.*;
|
||||
import org.isoron.uhabits.io.*;
|
||||
import org.isoron.uhabits.models.*;
|
||||
import org.isoron.uhabits.utils.*;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import javax.inject.*;
|
||||
|
||||
import static android.os.Build.VERSION.*;
|
||||
import static android.os.Build.VERSION_CODES.*;
|
||||
|
||||
@ActivityScope
|
||||
public class ListHabitsScreen extends BaseScreen
|
||||
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_DB = 3;
|
||||
|
||||
public static final int RESULT_BUG_REPORT = 4;
|
||||
|
||||
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
|
||||
private ListHabitsController controller;
|
||||
@@ -125,6 +135,15 @@ public class ListHabitsScreen extends BaseScreen
|
||||
|
||||
@Override
|
||||
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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
this.controller = controller;
|
||||
@@ -208,6 +251,21 @@ public class ListHabitsScreen extends BaseScreen
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -220,7 +278,8 @@ public class ListHabitsScreen extends BaseScreen
|
||||
FilePickerDialog picker = filePickerDialogFactory.create(dir);
|
||||
|
||||
if (controller != null)
|
||||
picker.setListener(file -> controller.onImportData(file));
|
||||
picker.setListener(file -> controller.onImportData(file, () -> {}));
|
||||
|
||||
activity.showDialog(picker.getDialog());
|
||||
}
|
||||
|
||||
@@ -233,7 +292,7 @@ public class ListHabitsScreen extends BaseScreen
|
||||
public void showSettingsScreen()
|
||||
{
|
||||
Intent intent = intentFactory.startSettingsActivity(activity);
|
||||
activity.startActivityForResult(intent, 0);
|
||||
activity.startActivityForResult(intent, REQUEST_SETTINGS);
|
||||
}
|
||||
|
||||
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.views.*;
|
||||
import org.isoron.uhabits.models.*;
|
||||
import org.isoron.uhabits.preferences.*;
|
||||
import org.isoron.uhabits.utils.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -41,7 +43,7 @@ import javax.inject.*;
|
||||
@ActivityScope
|
||||
public class HabitCardListAdapter
|
||||
extends RecyclerView.Adapter<HabitCardViewHolder>
|
||||
implements HabitCardListCache.Listener
|
||||
implements HabitCardListCache.Listener, MidnightTimer.MidnightListener
|
||||
{
|
||||
@NonNull
|
||||
private ModelObservable observable;
|
||||
@@ -55,19 +57,36 @@ public class HabitCardListAdapter
|
||||
@NonNull
|
||||
private final HabitCardListCache cache;
|
||||
|
||||
@NonNull
|
||||
private Preferences preferences;
|
||||
|
||||
private final MidnightTimer midnightTimer;
|
||||
|
||||
@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.observable = new ModelObservable();
|
||||
this.cache = cache;
|
||||
|
||||
this.midnightTimer = midnightTimer;
|
||||
|
||||
cache.setListener(this);
|
||||
cache.setCheckmarkCount(ListHabitsRootView.MAX_CHECKMARK_COUNT);
|
||||
cache.setOrder(preferences.getDefaultOrder());
|
||||
|
||||
setHasStableIds(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void atMidnight()
|
||||
{
|
||||
cache.refreshAllHabits();
|
||||
}
|
||||
|
||||
public void cancelRefresh()
|
||||
{
|
||||
cache.cancelTasks();
|
||||
@@ -130,12 +149,18 @@ public class HabitCardListAdapter
|
||||
return selected.isEmpty();
|
||||
}
|
||||
|
||||
public boolean isSortable()
|
||||
{
|
||||
return cache.getOrder() == HabitList.Order.BY_POSITION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the adapter that it has been attached to a ListView.
|
||||
*/
|
||||
public void onAttached()
|
||||
{
|
||||
cache.onAttached();
|
||||
midnightTimer.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -153,6 +178,20 @@ public class HabitCardListAdapter
|
||||
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
|
||||
public HabitCardViewHolder onCreateViewHolder(ViewGroup parent,
|
||||
int viewType)
|
||||
@@ -168,6 +207,7 @@ public class HabitCardListAdapter
|
||||
public void onDetached()
|
||||
{
|
||||
cache.onDetached();
|
||||
midnightTimer.removeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -260,6 +300,12 @@ public class HabitCardListAdapter
|
||||
this.listView = listView;
|
||||
}
|
||||
|
||||
public void setOrder(HabitList.Order order)
|
||||
{
|
||||
cache.setOrder(order);
|
||||
preferences.setDefaultOrder(order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects or deselects the item at a given position.
|
||||
*
|
||||
|
||||
@@ -107,6 +107,11 @@ public class HabitCardListCache implements CommandRunner.Listener
|
||||
return data.habits.size();
|
||||
}
|
||||
|
||||
public HabitList.Order getOrder()
|
||||
{
|
||||
return filteredHabits.getOrder();
|
||||
}
|
||||
|
||||
public int getScore(long habitId)
|
||||
{
|
||||
return data.scores.get(habitId);
|
||||
@@ -180,6 +185,13 @@ public class HabitCardListCache implements CommandRunner.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
|
||||
* cache has been modified.
|
||||
|
||||
@@ -20,21 +20,34 @@
|
||||
package org.isoron.uhabits.activities.habits.list.views;
|
||||
|
||||
import android.content.*;
|
||||
import android.content.res.*;
|
||||
import android.graphics.*;
|
||||
import android.support.annotation.*;
|
||||
import android.text.*;
|
||||
import android.util.*;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.activities.habits.list.controllers.*;
|
||||
import org.isoron.uhabits.models.*;
|
||||
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 value;
|
||||
|
||||
private StyledResources res;
|
||||
private StyledResources styledRes;
|
||||
|
||||
private TextPaint paint;
|
||||
|
||||
private int lowContrastColor;
|
||||
|
||||
private RectF rect;
|
||||
|
||||
public CheckmarkButtonView(Context context)
|
||||
{
|
||||
@@ -42,6 +55,21 @@ public class CheckmarkButtonView extends TextView
|
||||
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)
|
||||
{
|
||||
this.color = color;
|
||||
@@ -57,55 +85,60 @@ public class CheckmarkButtonView extends TextView
|
||||
public void setValue(int value)
|
||||
{
|
||||
this.value = value;
|
||||
updateText();
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
public void toggle()
|
||||
{
|
||||
value = (value == Checkmark.CHECKED_EXPLICITLY ? Checkmark.UNCHECKED :
|
||||
Checkmark.CHECKED_EXPLICITLY);
|
||||
|
||||
value = (value == CHECKED_EXPLICITLY ? UNCHECKED : CHECKED_EXPLICITLY);
|
||||
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()
|
||||
{
|
||||
res = new StyledResources(getContext());
|
||||
|
||||
setWillNotDraw(false);
|
||||
setHapticFeedbackEnabled(false);
|
||||
|
||||
setMinHeight(
|
||||
getResources().getDimensionPixelSize(R.dimen.checkmarkHeight));
|
||||
setMinWidth(
|
||||
getResources().getDimensionPixelSize(R.dimen.checkmarkWidth));
|
||||
|
||||
setFocusable(false);
|
||||
setGravity(Gravity.CENTER);
|
||||
setTypeface(InterfaceUtils.getFontAwesome(getContext()));
|
||||
}
|
||||
|
||||
private void updateText()
|
||||
{
|
||||
int lowContrastColor = res.getColor(R.attr.lowContrastTextColor);
|
||||
Resources res = getResources();
|
||||
styledRes = new StyledResources(getContext());
|
||||
|
||||
if (value == Checkmark.CHECKED_EXPLICITLY)
|
||||
{
|
||||
setText(R.string.fa_check);
|
||||
setTextColor(color);
|
||||
}
|
||||
paint = new TextPaint();
|
||||
paint.setTypeface(InterfaceUtils.getFontAwesome(getContext()));
|
||||
paint.setAntiAlias(true);
|
||||
paint.setTextAlign(Paint.Align.CENTER);
|
||||
paint.setTextSize(res.getDimension(R.dimen.regularTextSize));
|
||||
|
||||
if (value == Checkmark.CHECKED_IMPLICITLY)
|
||||
{
|
||||
setText(R.string.fa_check);
|
||||
setTextColor(lowContrastColor);
|
||||
}
|
||||
|
||||
if (value == Checkmark.UNCHECKED)
|
||||
{
|
||||
setText(R.string.fa_times);
|
||||
setTextColor(lowContrastColor);
|
||||
}
|
||||
rect = new RectF();
|
||||
color = ColorUtils.getAndroidTestColor(0);
|
||||
lowContrastColor = styledRes.getColor(R.attr.lowContrastTextColor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@ public class CheckmarkPanelView extends LinearLayout implements Preferences.List
|
||||
@NonNull
|
||||
private Habit habit;
|
||||
|
||||
private int dataOffset;
|
||||
|
||||
public CheckmarkPanelView(Context context)
|
||||
{
|
||||
super(context);
|
||||
@@ -75,19 +77,23 @@ public class CheckmarkPanelView extends LinearLayout implements Preferences.List
|
||||
return (CheckmarkButtonView) getChildAt(position);
|
||||
}
|
||||
|
||||
public void setCheckmarkValues(int[] checkmarkValues)
|
||||
public void setButtonCount(int newButtonCount)
|
||||
{
|
||||
this.checkmarkValues = checkmarkValues;
|
||||
|
||||
if (this.nButtons != checkmarkValues.length)
|
||||
if(nButtons != newButtonCount)
|
||||
{
|
||||
this.nButtons = checkmarkValues.length;
|
||||
nButtons = newButtonCount;
|
||||
addCheckmarkButtons();
|
||||
}
|
||||
|
||||
setupCheckmarkButtons();
|
||||
}
|
||||
|
||||
public void setCheckmarkValues(int[] checkmarkValues)
|
||||
{
|
||||
this.checkmarkValues = checkmarkValues;
|
||||
setupCheckmarkButtons();
|
||||
}
|
||||
|
||||
public void setColor(int color)
|
||||
{
|
||||
this.color = color;
|
||||
@@ -100,6 +106,12 @@ public class CheckmarkPanelView extends LinearLayout implements Preferences.List
|
||||
setupCheckmarkButtons();
|
||||
}
|
||||
|
||||
public void setDataOffset(int dataOffset)
|
||||
{
|
||||
this.dataOffset = dataOffset;
|
||||
setupCheckmarkButtons();
|
||||
}
|
||||
|
||||
public void setHabit(@NonNull Habit habit)
|
||||
{
|
||||
this.habit = habit;
|
||||
@@ -170,11 +182,13 @@ public class CheckmarkPanelView extends LinearLayout implements Preferences.List
|
||||
{
|
||||
long timestamp = DateUtils.getStartOfToday();
|
||||
long day = DateUtils.millisecondsInOneDay;
|
||||
timestamp -= day * dataOffset;
|
||||
|
||||
for (int i = 0; i < nButtons; i++)
|
||||
{
|
||||
CheckmarkButtonView buttonView = indexToButton(i);
|
||||
buttonView.setValue(checkmarkValues[i]);
|
||||
if(i + dataOffset >= checkmarkValues.length) break;
|
||||
buttonView.setValue(checkmarkValues[i + dataOffset]);
|
||||
buttonView.setColor(color);
|
||||
setupButtonControllers(timestamp, buttonView);
|
||||
timestamp -= day;
|
||||
|
||||
@@ -20,15 +20,17 @@
|
||||
package org.isoron.uhabits.activities.habits.list.views;
|
||||
|
||||
import android.content.*;
|
||||
import android.os.*;
|
||||
import android.support.annotation.*;
|
||||
import android.support.v7.widget.*;
|
||||
import android.support.v7.widget.helper.*;
|
||||
import android.util.*;
|
||||
import android.view.*;
|
||||
|
||||
import org.isoron.uhabits.models.*;
|
||||
import org.isoron.uhabits.activities.common.views.*;
|
||||
import org.isoron.uhabits.activities.habits.list.controllers.*;
|
||||
import org.isoron.uhabits.activities.habits.list.model.*;
|
||||
import org.isoron.uhabits.models.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -44,6 +46,10 @@ public class HabitCardListView extends RecyclerView
|
||||
|
||||
private int checkmarkCount;
|
||||
|
||||
private int dataOffset;
|
||||
|
||||
private LinkedList<HabitCardViewHolder> attachedHolders;
|
||||
|
||||
public HabitCardListView(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
@@ -54,6 +60,13 @@ public class HabitCardListView extends RecyclerView
|
||||
TouchHelperCallback callback = new TouchHelperCallback();
|
||||
touchHelper = new ItemTouchHelper(callback);
|
||||
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,
|
||||
boolean selected)
|
||||
{
|
||||
int visibleCheckmarks[] =
|
||||
Arrays.copyOfRange(checkmarks, 0, checkmarkCount);
|
||||
|
||||
HabitCardView cardView = (HabitCardView) holder.itemView;
|
||||
cardView.setHabit(habit);
|
||||
cardView.setSelected(selected);
|
||||
cardView.setCheckmarkValues(visibleCheckmarks);
|
||||
cardView.setCheckmarkValues(checkmarks);
|
||||
cardView.setCheckmarkCount(checkmarkCount);
|
||||
cardView.setDataOffset(dataOffset);
|
||||
cardView.setScore(score);
|
||||
if (controller != null) setupCardViewController(holder);
|
||||
return cardView;
|
||||
@@ -92,6 +104,11 @@ public class HabitCardListView extends RecyclerView
|
||||
return new HabitCardView(getContext());
|
||||
}
|
||||
|
||||
public void detachCardView(HabitCardViewHolder holder)
|
||||
{
|
||||
attachedHolders.remove(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAdapter(RecyclerView.Adapter adapter)
|
||||
{
|
||||
@@ -109,6 +126,16 @@ public class HabitCardListView extends RecyclerView
|
||||
this.controller = controller;
|
||||
}
|
||||
|
||||
public void setDataOffset(int dataOffset)
|
||||
{
|
||||
this.dataOffset = dataOffset;
|
||||
for (HabitCardViewHolder holder : attachedHolders)
|
||||
{
|
||||
HabitCardView cardView = (HabitCardView) holder.itemView;
|
||||
cardView.setDataOffset(dataOffset);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow()
|
||||
{
|
||||
@@ -123,6 +150,29 @@ public class HabitCardListView extends RecyclerView
|
||||
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)
|
||||
{
|
||||
HabitCardView cardView = (HabitCardView) holder.itemView;
|
||||
@@ -168,7 +218,7 @@ public class HabitCardListView extends RecyclerView
|
||||
{
|
||||
int position = holder.getAdapterPosition();
|
||||
if (controller != null) controller.onItemLongClick(position);
|
||||
touchHelper.startDrag(holder);
|
||||
if (adapter.isSortable()) touchHelper.startDrag(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -27,7 +27,7 @@ import android.support.annotation.*;
|
||||
import android.util.*;
|
||||
import android.widget.*;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.activities.common.views.*;
|
||||
import org.isoron.uhabits.models.*;
|
||||
import org.isoron.uhabits.utils.*;
|
||||
@@ -72,6 +72,8 @@ public class HabitCardView extends FrameLayout
|
||||
@Nullable
|
||||
private Habit habit;
|
||||
|
||||
private int dataOffset;
|
||||
|
||||
public HabitCardView(Context context)
|
||||
{
|
||||
super(context);
|
||||
@@ -90,6 +92,11 @@ public class HabitCardView extends FrameLayout
|
||||
new Handler(Looper.getMainLooper()).post(() -> refresh());
|
||||
}
|
||||
|
||||
public void setCheckmarkCount(int checkmarkCount)
|
||||
{
|
||||
checkmarkPanel.setButtonCount(checkmarkCount);
|
||||
}
|
||||
|
||||
public void setCheckmarkValues(int checkmarks[])
|
||||
{
|
||||
checkmarkPanel.setCheckmarkValues(checkmarks);
|
||||
@@ -103,6 +110,12 @@ public class HabitCardView extends FrameLayout
|
||||
checkmarkPanel.setController(controller);
|
||||
}
|
||||
|
||||
public void setDataOffset(int dataOffset)
|
||||
{
|
||||
this.dataOffset = dataOffset;
|
||||
checkmarkPanel.setDataOffset(dataOffset);
|
||||
}
|
||||
|
||||
public void setHabit(@NonNull Habit habit)
|
||||
{
|
||||
if (this.habit != null) detachFromHabit();
|
||||
@@ -134,7 +147,7 @@ public class HabitCardView extends FrameLayout
|
||||
{
|
||||
long today = DateUtils.getStartOfToday();
|
||||
long day = DateUtils.millisecondsInOneDay;
|
||||
int offset = (int) ((today - timestamp) / day);
|
||||
int offset = (int) ((today - timestamp) / day) - dataOffset;
|
||||
CheckmarkButtonView button = checkmarkPanel.indexToButton(offset);
|
||||
|
||||
float y = button.getHeight() / 2.0f;
|
||||
@@ -201,6 +214,7 @@ public class HabitCardView extends FrameLayout
|
||||
scoreRing.setPercentage(rand.nextFloat());
|
||||
checkmarkPanel.setColor(color);
|
||||
checkmarkPanel.setCheckmarkValues(values);
|
||||
checkmarkPanel.setButtonCount(5);
|
||||
}
|
||||
|
||||
private void refresh()
|
||||
|
||||
@@ -20,30 +20,39 @@
|
||||
package org.isoron.uhabits.activities.habits.list.views;
|
||||
|
||||
import android.content.*;
|
||||
import android.content.res.*;
|
||||
import android.graphics.*;
|
||||
import android.support.annotation.*;
|
||||
import android.text.*;
|
||||
import android.util.*;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
|
||||
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.utils.*;
|
||||
|
||||
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;
|
||||
|
||||
@Nullable
|
||||
private Preferences prefs;
|
||||
|
||||
@Nullable
|
||||
private MidnightTimer midnightTimer;
|
||||
|
||||
private final TextPaint paint;
|
||||
|
||||
private RectF rect;
|
||||
|
||||
public HeaderView(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
this.context = context;
|
||||
|
||||
if (isInEditMode())
|
||||
{
|
||||
@@ -56,51 +65,116 @@ public class HeaderView extends LinearLayout implements Preferences.Listener
|
||||
HabitsApplication app = (HabitsApplication) appContext;
|
||||
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
|
||||
public void onCheckmarkOrderChanged()
|
||||
{
|
||||
createButtons();
|
||||
updateDirection();
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
public void setButtonCount(int buttonCount)
|
||||
{
|
||||
this.buttonCount = buttonCount;
|
||||
createButtons();
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow()
|
||||
{
|
||||
updateDirection();
|
||||
super.onAttachedToWindow();
|
||||
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
|
||||
protected void onDetachedFromWindow()
|
||||
{
|
||||
if (midnightTimer != null) midnightTimer.removeListener(this);
|
||||
if (prefs != null) prefs.removeListener(this);
|
||||
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();
|
||||
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++)
|
||||
addView(
|
||||
inflate(context, R.layout.list_habits_header_checkmark, null));
|
||||
|
||||
for (int i = 0; i < getChildCount(); i++)
|
||||
{
|
||||
int position = i;
|
||||
if (shouldReverseCheckmarks()) position = getChildCount() - i - 1;
|
||||
rect.set(0, 0, width, height);
|
||||
rect.offset(canvas.getWidth(), 0);
|
||||
|
||||
View button = getChildAt(position);
|
||||
TextView label = (TextView) button.findViewById(R.id.tvCheck);
|
||||
label.setText(DateUtils.formatHeaderDate(day));
|
||||
if(reverse) rect.offset(- (i + 1) * width, 0);
|
||||
else rect.offset((i - buttonCount) * width, 0);
|
||||
|
||||
if (isRtl) rect.set(canvas.getWidth() - rect.right, rect.top,
|
||||
canvas.getWidth() - rect.left, rect.bottom);
|
||||
|
||||
String text = DateUtils.formatHeaderDate(day).toUpperCase();
|
||||
String[] lines = text.split("\n");
|
||||
|
||||
int y1 = (int)(rect.centerY() - 0.25 * em);
|
||||
int y2 = (int)(rect.centerY() + 1.25 * em);
|
||||
|
||||
canvas.drawText(lines[0], rect.centerX(), y1, paint);
|
||||
canvas.drawText(lines[1], rect.centerX(), y2, paint);
|
||||
day.add(GregorianCalendar.DAY_OF_MONTH, -1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import android.os.*;
|
||||
import android.support.annotation.*;
|
||||
import android.support.v7.widget.*;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.activities.*;
|
||||
import org.isoron.uhabits.activities.habits.show.views.*;
|
||||
import org.isoron.uhabits.models.*;
|
||||
|
||||
@@ -24,6 +24,10 @@ import android.view.*;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.activities.*;
|
||||
import org.isoron.uhabits.models.*;
|
||||
import org.isoron.uhabits.tasks.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import javax.inject.*;
|
||||
|
||||
@@ -33,12 +37,38 @@ public class ShowHabitsMenu extends BaseMenu
|
||||
@NonNull
|
||||
private final ShowHabitScreen screen;
|
||||
|
||||
@NonNull
|
||||
private final Habit habit;
|
||||
|
||||
@NonNull
|
||||
private final TaskRunner taskRunner;
|
||||
|
||||
@NonNull
|
||||
private ExportCSVTaskFactory exportCSVFactory;
|
||||
|
||||
@Inject
|
||||
public ShowHabitsMenu(@NonNull BaseActivity activity,
|
||||
@NonNull ShowHabitScreen screen)
|
||||
@NonNull ShowHabitScreen screen,
|
||||
@NonNull Habit habit,
|
||||
@NonNull ExportCSVTaskFactory exportCSVFactory,
|
||||
@NonNull TaskRunner taskRunner)
|
||||
{
|
||||
super(activity);
|
||||
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
|
||||
@@ -50,6 +80,10 @@ public class ShowHabitsMenu extends BaseMenu
|
||||
screen.showEditHabitDialog();
|
||||
return true;
|
||||
|
||||
case R.id.export:
|
||||
this.exportHabit();
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import android.util.*;
|
||||
import android.widget.*;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.activities.common.views.*;
|
||||
import org.isoron.uhabits.models.*;
|
||||
import org.isoron.uhabits.tasks.*;
|
||||
|
||||
@@ -25,6 +25,7 @@ import android.util.*;
|
||||
import android.widget.*;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.activities.common.views.*;
|
||||
import org.isoron.uhabits.models.*;
|
||||
import org.isoron.uhabits.tasks.*;
|
||||
|
||||
@@ -25,6 +25,7 @@ import android.util.*;
|
||||
import android.widget.*;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.activities.common.views.*;
|
||||
import org.isoron.uhabits.models.*;
|
||||
import org.isoron.uhabits.tasks.*;
|
||||
|
||||
@@ -25,6 +25,7 @@ import android.util.*;
|
||||
import android.widget.*;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.activities.common.views.*;
|
||||
import org.isoron.uhabits.models.*;
|
||||
import org.isoron.uhabits.preferences.*;
|
||||
|
||||
@@ -25,6 +25,7 @@ import android.util.*;
|
||||
import android.widget.*;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.activities.common.views.*;
|
||||
import org.isoron.uhabits.models.*;
|
||||
import org.isoron.uhabits.tasks.*;
|
||||
|
||||
@@ -25,7 +25,7 @@ import android.content.res.*;
|
||||
import android.util.*;
|
||||
import android.widget.*;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.*;
|
||||
import org.isoron.uhabits.utils.*;
|
||||
|
||||
|
||||
@@ -61,9 +61,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
setResultOnPreferenceClick("bugReport", ListHabitsScreen.RESULT_BUG_REPORT);
|
||||
|
||||
updateRingtoneDescription();
|
||||
|
||||
if (InterfaceUtils.isLocaleFullyTranslated())
|
||||
removePreference("translate", "linksCategory");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -110,14 +107,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
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)
|
||||
{
|
||||
Preference pref = findPreference(key);
|
||||
|
||||
@@ -25,7 +25,7 @@ import android.support.v7.widget.*;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.widget.*;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.activities.*;
|
||||
import org.isoron.uhabits.models.*;
|
||||
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)
|
||||
{
|
||||
String url = context.getString(R.string.playStoreURL);
|
||||
|
||||
@@ -19,18 +19,20 @@
|
||||
|
||||
package org.isoron.uhabits.io;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.content.*;
|
||||
import android.database.*;
|
||||
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.utils.DatabaseUtils;
|
||||
import org.isoron.uhabits.utils.FileUtils;
|
||||
import org.isoron.uhabits.utils.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.*;
|
||||
|
||||
import javax.inject.*;
|
||||
|
||||
@@ -39,10 +41,15 @@ import javax.inject.*;
|
||||
*/
|
||||
public class LoopDBImporter extends AbstractImporter
|
||||
{
|
||||
@NonNull
|
||||
private Context context;
|
||||
|
||||
@Inject
|
||||
public LoopDBImporter(@NonNull HabitList habits)
|
||||
public LoopDBImporter(@NonNull @AppContext Context context,
|
||||
@NonNull HabitList habits)
|
||||
{
|
||||
super(habits);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -53,23 +60,37 @@ public class LoopDBImporter extends AbstractImporter
|
||||
SQLiteDatabase db = SQLiteDatabase.openDatabase(file.getPath(), null,
|
||||
SQLiteDatabase.OPEN_READONLY);
|
||||
|
||||
boolean canHandle = true;
|
||||
|
||||
Cursor c = db.rawQuery(
|
||||
"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();
|
||||
db.close();
|
||||
return result;
|
||||
return canHandle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importHabitsFromFile(@NonNull File file) throws IOException
|
||||
{
|
||||
ActiveAndroid.dispose();
|
||||
File originalDB = DatabaseUtils.getDatabaseFile();
|
||||
File originalDB = DatabaseUtils.getDatabaseFile(context);
|
||||
FileUtils.copy(file, originalDB);
|
||||
DatabaseUtils.initializeActiveAndroid();
|
||||
DatabaseUtils.initializeActiveAndroid(context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ public abstract class CheckmarkList
|
||||
*
|
||||
* @return value of today's checkmark
|
||||
*/
|
||||
public final int getTodayValue()
|
||||
public int getTodayValue()
|
||||
{
|
||||
Checkmark today = getToday();
|
||||
if (today != null) return today.getValue();
|
||||
@@ -192,7 +192,7 @@ public abstract class CheckmarkList
|
||||
Checkmark newest = getNewestComputed();
|
||||
Checkmark oldest = getOldestComputed();
|
||||
|
||||
if (newest == null)
|
||||
if (newest == null || oldest == null)
|
||||
{
|
||||
forceRecompute(from, to);
|
||||
}
|
||||
@@ -208,6 +208,7 @@ public abstract class CheckmarkList
|
||||
*
|
||||
* @return oldest checkmark already computed
|
||||
*/
|
||||
@Nullable
|
||||
protected abstract Checkmark getOldestComputed();
|
||||
|
||||
/**
|
||||
@@ -285,5 +286,6 @@ public abstract class CheckmarkList
|
||||
*
|
||||
* @return newest checkmark already computed
|
||||
*/
|
||||
@Nullable
|
||||
protected abstract Checkmark getNewestComputed();
|
||||
}
|
||||
|
||||
@@ -48,9 +48,7 @@ public abstract class HabitList implements Iterable<Habit>
|
||||
public HabitList()
|
||||
{
|
||||
observable = new ModelObservable();
|
||||
filter = new HabitMatcherBuilder()
|
||||
.setArchivedAllowed(true)
|
||||
.build();
|
||||
filter = new HabitMatcherBuilder().setArchivedAllowed(true).build();
|
||||
}
|
||||
|
||||
protected HabitList(@NonNull HabitMatcher filter)
|
||||
@@ -106,6 +104,15 @@ public abstract class HabitList implements Iterable<Habit>
|
||||
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
|
||||
* not contain the habit.
|
||||
@@ -149,7 +156,7 @@ public abstract class HabitList implements Iterable<Habit>
|
||||
|
||||
public void repair()
|
||||
{
|
||||
for(Habit h : this)
|
||||
for (Habit h : this)
|
||||
{
|
||||
h.getCheckmarks().invalidateNewerThan(0);
|
||||
h.getStreaks().invalidateNewerThan(0);
|
||||
@@ -228,4 +235,12 @@ public abstract class HabitList implements Iterable<Habit>
|
||||
|
||||
csv.close();
|
||||
}
|
||||
|
||||
public enum Order
|
||||
{
|
||||
BY_NAME,
|
||||
BY_COLOR,
|
||||
BY_SCORE,
|
||||
BY_POSITION
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ public class MemoryCheckmarkList extends CheckmarkList
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected Checkmark getOldestComputed()
|
||||
{
|
||||
if(list.isEmpty()) return null;
|
||||
@@ -79,6 +80,7 @@ public class MemoryCheckmarkList extends CheckmarkList
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected Checkmark getNewestComputed()
|
||||
{
|
||||
if(list.isEmpty()) return null;
|
||||
|
||||
@@ -25,6 +25,8 @@ import org.isoron.uhabits.models.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static org.isoron.uhabits.models.HabitList.Order.*;
|
||||
|
||||
/**
|
||||
* In-memory implementation of {@link HabitList}.
|
||||
*/
|
||||
@@ -33,16 +35,23 @@ public class MemoryHabitList extends HabitList
|
||||
@NonNull
|
||||
private LinkedList<Habit> list;
|
||||
|
||||
private Comparator<Habit> comparator = null;
|
||||
|
||||
@NonNull
|
||||
private Order order;
|
||||
|
||||
public MemoryHabitList()
|
||||
{
|
||||
super();
|
||||
list = new LinkedList<>();
|
||||
order = Order.BY_POSITION;
|
||||
}
|
||||
|
||||
protected MemoryHabitList(@NonNull HabitMatcher matcher)
|
||||
{
|
||||
super(matcher);
|
||||
list = new LinkedList<>();
|
||||
order = Order.BY_POSITION;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -57,6 +66,7 @@ public class MemoryHabitList extends HabitList
|
||||
|
||||
if (id == null) habit.setId((long) list.size());
|
||||
list.addLast(habit);
|
||||
resort();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -82,10 +92,17 @@ public class MemoryHabitList extends HabitList
|
||||
public HabitList getFiltered(HabitMatcher 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Order getOrder()
|
||||
{
|
||||
return order;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOf(@NonNull Habit h)
|
||||
{
|
||||
@@ -112,6 +129,14 @@ public class MemoryHabitList extends HabitList
|
||||
list.add(toPos, from);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOrder(@NonNull Order order)
|
||||
{
|
||||
this.order = order;
|
||||
this.comparator = getComparatorByOrder(order);
|
||||
resort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size()
|
||||
{
|
||||
@@ -123,4 +148,34 @@ public class MemoryHabitList extends HabitList
|
||||
{
|
||||
// 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 com.activeandroid.*;
|
||||
import com.activeandroid.query.*;
|
||||
|
||||
import org.isoron.uhabits.models.*;
|
||||
import org.isoron.uhabits.models.sqlite.records.*;
|
||||
@@ -38,38 +37,54 @@ import java.util.*;
|
||||
*/
|
||||
public class SQLiteCheckmarkList extends CheckmarkList
|
||||
{
|
||||
|
||||
@Nullable
|
||||
private HabitRecord habitRecord;
|
||||
|
||||
@NonNull
|
||||
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)
|
||||
{
|
||||
super(habit);
|
||||
sqlite = new SQLiteUtils<>(CheckmarkRecord.class);
|
||||
|
||||
db = Cache.openDatabase();
|
||||
addStatement = db.compileStatement(ADD_QUERY);
|
||||
invalidateStatement = db.compileStatement(INVALIDATE_QUERY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(List<Checkmark> checkmarks)
|
||||
{
|
||||
check(habit.getId());
|
||||
|
||||
String query =
|
||||
"insert into Checkmarks(habit, timestamp, value) values (?,?,?)";
|
||||
|
||||
SQLiteDatabase db = Cache.openDatabase();
|
||||
db.beginTransaction();
|
||||
try
|
||||
{
|
||||
SQLiteStatement statement = db.compileStatement(query);
|
||||
|
||||
for (Checkmark c : checkmarks)
|
||||
{
|
||||
statement.bindLong(1, habit.getId());
|
||||
statement.bindLong(2, c.getTimestamp());
|
||||
statement.bindLong(3, c.getValue());
|
||||
statement.execute();
|
||||
addStatement.bindLong(1, habit.getId());
|
||||
addStatement.bindLong(2, c.getTimestamp());
|
||||
addStatement.bindLong(3, c.getValue());
|
||||
addStatement.execute();
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
@@ -115,12 +130,10 @@ public class SQLiteCheckmarkList extends CheckmarkList
|
||||
@Override
|
||||
public void invalidateNewerThan(long timestamp)
|
||||
{
|
||||
new Delete()
|
||||
.from(CheckmarkRecord.class)
|
||||
.where("habit = ?", habit.getId())
|
||||
.and("timestamp >= ?", timestamp)
|
||||
.execute();
|
||||
|
||||
todayValue = null;
|
||||
invalidateStatement.bindLong(1, habit.getId());
|
||||
invalidateStatement.bindLong(2, timestamp);
|
||||
invalidateStatement.execute();
|
||||
observable.notifyListeners();
|
||||
}
|
||||
|
||||
@@ -140,6 +153,7 @@ public class SQLiteCheckmarkList extends CheckmarkList
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected Checkmark getOldestComputed()
|
||||
{
|
||||
check(habit.getId());
|
||||
@@ -179,4 +193,11 @@ public class SQLiteCheckmarkList extends CheckmarkList
|
||||
for (CheckmarkRecord r : records) checkmarks.add(r.toCheckmark());
|
||||
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;
|
||||
|
||||
@NonNull
|
||||
private final SQLiteUtils<HabitRecord> sqlite;
|
||||
|
||||
@NonNull
|
||||
private final ModelFactory modelFactory;
|
||||
|
||||
@NonNull
|
||||
private Order order;
|
||||
|
||||
public SQLiteHabitList(@NonNull ModelFactory modelFactory)
|
||||
{
|
||||
super();
|
||||
@@ -50,16 +55,19 @@ public class SQLiteHabitList extends HabitList
|
||||
|
||||
if (cache == null) cache = new HashMap<>();
|
||||
sqlite = new SQLiteUtils<>(HabitRecord.class);
|
||||
order = Order.BY_POSITION;
|
||||
}
|
||||
|
||||
protected SQLiteHabitList(@NonNull ModelFactory modelFactory,
|
||||
@NonNull HabitMatcher filter)
|
||||
@NonNull HabitMatcher filter,
|
||||
@NonNull Order order)
|
||||
{
|
||||
super(filter);
|
||||
this.modelFactory = modelFactory;
|
||||
|
||||
if (cache == null) cache = new HashMap<>();
|
||||
sqlite = new SQLiteUtils<>(HabitRecord.class);
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
public static SQLiteHabitList getInstance(
|
||||
@@ -118,7 +126,20 @@ public class SQLiteHabitList extends HabitList
|
||||
@Override
|
||||
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
|
||||
@@ -214,6 +235,13 @@ public class SQLiteHabitList extends HabitList
|
||||
getObservable().notifyListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void repair()
|
||||
{
|
||||
super.repair();
|
||||
rebuildOrder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size()
|
||||
{
|
||||
@@ -233,7 +261,7 @@ public class SQLiteHabitList extends HabitList
|
||||
}
|
||||
}
|
||||
|
||||
protected List<Habit> toList()
|
||||
protected synchronized List<Habit> toList()
|
||||
{
|
||||
String query = buildSelectQuery();
|
||||
List<HabitRecord> recordList = sqlite.query(query, null);
|
||||
@@ -249,12 +277,38 @@ public class SQLiteHabitList extends HabitList
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -282,11 +336,4 @@ public class SQLiteHabitList extends HabitList
|
||||
appendOrderBy(query);
|
||||
return query.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void repair()
|
||||
{
|
||||
super.repair();
|
||||
rebuildOrder();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,12 +19,12 @@
|
||||
|
||||
package org.isoron.uhabits.models.sqlite;
|
||||
|
||||
import android.database.DatabaseUtils;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.*;
|
||||
import android.database.sqlite.*;
|
||||
import android.support.annotation.*;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.activeandroid.Cache;
|
||||
import com.activeandroid.*;
|
||||
import com.activeandroid.query.*;
|
||||
|
||||
import org.isoron.uhabits.models.*;
|
||||
@@ -43,10 +43,16 @@ public class SQLiteRepetitionList extends RepetitionList
|
||||
@Nullable
|
||||
private HabitRecord habitRecord;
|
||||
|
||||
private SQLiteStatement addStatement;
|
||||
|
||||
public SQLiteRepetitionList(@NonNull Habit habit)
|
||||
{
|
||||
super(habit);
|
||||
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)
|
||||
{
|
||||
check(habit.getId());
|
||||
|
||||
RepetitionRecord record = new RepetitionRecord();
|
||||
record.copyFrom(rep);
|
||||
record.habit = habitRecord;
|
||||
record.save();
|
||||
addStatement.bindLong(1, habit.getId());
|
||||
addStatement.bindLong(2, rep.getTimestamp());
|
||||
addStatement.execute();
|
||||
observable.notifyListeners();
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ import android.support.annotation.*;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.activeandroid.*;
|
||||
import com.activeandroid.query.*;
|
||||
|
||||
import org.isoron.uhabits.models.*;
|
||||
import org.isoron.uhabits.models.sqlite.records.*;
|
||||
@@ -37,12 +36,30 @@ import java.util.*;
|
||||
*/
|
||||
public class SQLiteScoreList extends ScoreList
|
||||
{
|
||||
|
||||
@Nullable
|
||||
private HabitRecord habitRecord;
|
||||
|
||||
@NonNull
|
||||
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.
|
||||
*
|
||||
@@ -52,28 +69,25 @@ public class SQLiteScoreList extends ScoreList
|
||||
{
|
||||
super(habit);
|
||||
sqlite = new SQLiteUtils<>(ScoreRecord.class);
|
||||
|
||||
db = Cache.openDatabase();
|
||||
addStatement = db.compileStatement(ADD_QUERY);
|
||||
invalidateStatement = db.compileStatement(INVALIDATE_QUERY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(List<Score> scores)
|
||||
{
|
||||
check(habit.getId());
|
||||
String query =
|
||||
"insert into Score(habit, timestamp, score) values (?,?,?)";
|
||||
|
||||
SQLiteDatabase db = Cache.openDatabase();
|
||||
db.beginTransaction();
|
||||
|
||||
try
|
||||
{
|
||||
SQLiteStatement statement = db.compileStatement(query);
|
||||
|
||||
for (Score s : scores)
|
||||
{
|
||||
statement.bindLong(1, habit.getId());
|
||||
statement.bindLong(2, s.getTimestamp());
|
||||
statement.bindLong(3, s.getValue());
|
||||
statement.execute();
|
||||
addStatement.bindLong(1, habit.getId());
|
||||
addStatement.bindLong(2, s.getTimestamp());
|
||||
addStatement.bindLong(3, s.getValue());
|
||||
addStatement.execute();
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
@@ -126,12 +140,10 @@ public class SQLiteScoreList extends ScoreList
|
||||
@Override
|
||||
public void invalidateNewerThan(long timestamp)
|
||||
{
|
||||
new Delete()
|
||||
.from(ScoreRecord.class)
|
||||
.where("habit = ?", habit.getId())
|
||||
.and("timestamp >= ?", timestamp)
|
||||
.execute();
|
||||
|
||||
todayValue = null;
|
||||
invalidateStatement.bindLong(1, habit.getId());
|
||||
invalidateStatement.bindLong(2, timestamp);
|
||||
invalidateStatement.execute();
|
||||
getObservable().notifyListeners();
|
||||
}
|
||||
|
||||
@@ -204,4 +216,11 @@ public class SQLiteScoreList extends ScoreList
|
||||
for (ScoreRecord r : records) scores.add(r.toScore());
|
||||
return scores;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTodayValue()
|
||||
{
|
||||
if (todayValue == null) todayValue = super.getTodayValue();
|
||||
return todayValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,10 +19,11 @@
|
||||
|
||||
package org.isoron.uhabits.models.sqlite;
|
||||
|
||||
import android.database.sqlite.*;
|
||||
import android.support.annotation.*;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.activeandroid.query.*;
|
||||
import com.activeandroid.*;
|
||||
|
||||
import org.isoron.uhabits.models.*;
|
||||
import org.isoron.uhabits.models.sqlite.records.*;
|
||||
@@ -41,10 +42,17 @@ public class SQLiteStreakList extends StreakList
|
||||
@NonNull
|
||||
private final SQLiteUtils<StreakRecord> sqlite;
|
||||
|
||||
private final SQLiteStatement invalidateStatement;
|
||||
|
||||
public SQLiteStreakList(Habit habit)
|
||||
{
|
||||
super(habit);
|
||||
sqlite = new SQLiteUtils<>(StreakRecord.class);
|
||||
|
||||
SQLiteDatabase db = Cache.openDatabase();
|
||||
String invalidateQuery = "delete from Streak where habit = ? " +
|
||||
"and end >= ?";
|
||||
invalidateStatement = db.compileStatement(invalidateQuery);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -73,12 +81,9 @@ public class SQLiteStreakList extends StreakList
|
||||
@Override
|
||||
public void invalidateNewerThan(long timestamp)
|
||||
{
|
||||
new Delete()
|
||||
.from(StreakRecord.class)
|
||||
.where("habit = ?", habit.getId())
|
||||
.and("end >= ?", timestamp - DateUtils.millisecondsInOneDay)
|
||||
.execute();
|
||||
|
||||
invalidateStatement.bindLong(1, habit.getId());
|
||||
invalidateStatement.bindLong(2, timestamp - DateUtils.millisecondsInOneDay);
|
||||
invalidateStatement.execute();
|
||||
observable.notifyListeners();
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ package org.isoron.uhabits.models.sqlite.records;
|
||||
import android.annotation.*;
|
||||
import android.database.*;
|
||||
import android.support.annotation.*;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.activeandroid.*;
|
||||
import com.activeandroid.annotation.*;
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.preference.*;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.activities.*;
|
||||
import org.isoron.uhabits.models.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -61,6 +62,21 @@ public class Preferences
|
||||
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()
|
||||
{
|
||||
int defaultScoreInterval = prefs.getInt("pref_score_view_interval", 1);
|
||||
@@ -69,6 +85,11 @@ public class Preferences
|
||||
return defaultScoreInterval;
|
||||
}
|
||||
|
||||
public void setDefaultOrder(HabitList.Order order)
|
||||
{
|
||||
prefs.edit().putString("pref_default_order", order.name()).apply();
|
||||
}
|
||||
|
||||
public void setDefaultScoreSpinnerPosition(int position)
|
||||
{
|
||||
prefs.edit().putInt("pref_score_view_interval", position).apply();
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.content.*;
|
||||
import android.preference.*;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.models.*;
|
||||
|
||||
import javax.inject.*;
|
||||
|
||||
@@ -48,7 +49,7 @@ public class WidgetPreferences
|
||||
public long getHabitIdFromWidgetId(int widgetId)
|
||||
{
|
||||
Long habitId = prefs.getLong(getHabitIdKey(widgetId), -1);
|
||||
if (habitId < 0) throw new RuntimeException("widget not found");
|
||||
if (habitId < 0) throw new HabitNotFoundException();
|
||||
|
||||
return habitId;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,9 @@ public class PebbleReceiver extends PebbleDataReceiver
|
||||
public static final UUID WATCHAPP_UUID =
|
||||
UUID.fromString("82629d99-8ea6-4631-a022-9ca77a12a058");
|
||||
|
||||
@NonNull
|
||||
private Context context;
|
||||
|
||||
private HabitList allHabits;
|
||||
|
||||
private CommandRunner commandRunner;
|
||||
@@ -61,6 +64,8 @@ public class PebbleReceiver extends PebbleDataReceiver
|
||||
if (context == null) throw new RuntimeException("context is null");
|
||||
if (data == null) throw new RuntimeException("data is null");
|
||||
|
||||
this.context = context;
|
||||
|
||||
HabitsApplication app =
|
||||
(HabitsApplication) context.getApplicationContext();
|
||||
|
||||
@@ -136,7 +141,7 @@ public class PebbleReceiver extends PebbleDataReceiver
|
||||
|
||||
private void sendDict(@NonNull PebbleDictionary dict)
|
||||
{
|
||||
PebbleKit.sendDataToPebble(HabitsApplication.getContext(),
|
||||
PebbleKit.sendDataToPebble(context,
|
||||
PebbleReceiver.WATCHAPP_UUID, dict);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,10 +19,13 @@
|
||||
|
||||
package org.isoron.uhabits.tasks;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.*;
|
||||
|
||||
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.models.*;
|
||||
import org.isoron.uhabits.utils.*;
|
||||
@@ -35,6 +38,9 @@ public class ExportCSVTask implements Task
|
||||
{
|
||||
private String archiveFilename;
|
||||
|
||||
@NonNull
|
||||
private final Context context;
|
||||
|
||||
@NonNull
|
||||
private final List<Habit> selectedHabits;
|
||||
|
||||
@@ -44,10 +50,12 @@ public class ExportCSVTask implements Task
|
||||
@NonNull
|
||||
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 Listener listener)
|
||||
{
|
||||
this.context = context;
|
||||
this.listener = listener;
|
||||
this.habitList = habitList;
|
||||
this.selectedHabits = selectedHabits;
|
||||
@@ -58,7 +66,7 @@ public class ExportCSVTask implements Task
|
||||
{
|
||||
try
|
||||
{
|
||||
File dir = FileUtils.getFilesDir("CSV");
|
||||
File dir = FileUtils.getFilesDir(context, "CSV");
|
||||
if (dir == null) return;
|
||||
|
||||
HabitsCSVExporter exporter;
|
||||
|
||||
@@ -19,22 +19,33 @@
|
||||
|
||||
package org.isoron.uhabits.tasks;
|
||||
|
||||
import android.content.Context;
|
||||
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 java.io.*;
|
||||
|
||||
@AutoFactory(allowSubclasses = true)
|
||||
public class ExportDBTask implements Task
|
||||
{
|
||||
private String filename;
|
||||
|
||||
@NonNull
|
||||
private Context context;
|
||||
|
||||
@NonNull
|
||||
private final Listener listener;
|
||||
|
||||
public ExportDBTask(@NonNull Listener listener)
|
||||
public ExportDBTask(@Provided @AppContext @NonNull Context context, @NonNull Listener listener)
|
||||
{
|
||||
this.listener = listener;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -44,10 +55,10 @@ public class ExportDBTask implements Task
|
||||
|
||||
try
|
||||
{
|
||||
File dir = FileUtils.getFilesDir("Backups");
|
||||
File dir = FileUtils.getFilesDir(context, "Backups");
|
||||
if (dir == null) return;
|
||||
|
||||
filename = DatabaseUtils.saveDatabaseCopy(dir);
|
||||
filename = DatabaseUtils.saveDatabaseCopy(context, dir);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
|
||||
@@ -74,4 +74,14 @@ public class AttributeSetUtils
|
||||
if (number != null) return Float.parseFloat(number);
|
||||
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 org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.models.sqlite.*;
|
||||
import org.isoron.uhabits.models.sqlite.records.*;
|
||||
|
||||
import java.io.*;
|
||||
@@ -47,9 +48,8 @@ public abstract class DatabaseUtils
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static File getDatabaseFile()
|
||||
public static File getDatabaseFile(Context context)
|
||||
{
|
||||
Context context = HabitsApplication.getContext();
|
||||
String databaseFilename = getDatabaseFilename();
|
||||
String root = context.getFilesDir().getPath();
|
||||
|
||||
@@ -68,9 +68,8 @@ public abstract class DatabaseUtils
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void initializeActiveAndroid()
|
||||
public static void initializeActiveAndroid(Context context)
|
||||
{
|
||||
Context context = HabitsApplication.getContext();
|
||||
Configuration dbConfig = new Configuration.Builder(context)
|
||||
.setDatabaseName(getDatabaseFilename())
|
||||
.setDatabaseVersion(BuildConfig.databaseVersion)
|
||||
@@ -78,18 +77,27 @@ public abstract class DatabaseUtils
|
||||
RepetitionRecord.class, ScoreRecord.class, StreakRecord.class)
|
||||
.create();
|
||||
|
||||
ActiveAndroid.initialize(dbConfig);
|
||||
try
|
||||
{
|
||||
ActiveAndroid.initialize(dbConfig);
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
if(e.getMessage().contains("downgrade"))
|
||||
throw new InvalidDatabaseVersionException();
|
||||
else throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
public static String saveDatabaseCopy(File dir) throws IOException
|
||||
public static String saveDatabaseCopy(Context context, File dir) throws IOException
|
||||
{
|
||||
SimpleDateFormat dateFormat = DateFormats.getBackupDateFormat();
|
||||
String date = dateFormat.format(DateUtils.getLocalTime());
|
||||
String format = "%s/Loop Habits Backup %s.db";
|
||||
String filename = String.format(format, dir.getAbsolutePath(), date);
|
||||
|
||||
File db = getDatabaseFile();
|
||||
File db = getDatabaseFile(context);
|
||||
File dbCopy = new File(filename);
|
||||
FileUtils.copy(db, dbCopy);
|
||||
|
||||
|
||||
@@ -187,6 +187,11 @@ public abstract class DateUtils
|
||||
return getStartOfDay(DateUtils.getLocalTime());
|
||||
}
|
||||
|
||||
public static long millisecondsUntilTomorrow()
|
||||
{
|
||||
return getStartOfToday() + millisecondsInOneDay - getLocalTime();
|
||||
}
|
||||
|
||||
public static GregorianCalendar getStartOfTodayCalendar()
|
||||
{
|
||||
return getCalendar(getStartOfToday());
|
||||
|
||||
@@ -87,9 +87,8 @@ public abstract class FileUtils
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static File getFilesDir(@Nullable String relativePath)
|
||||
public static File getFilesDir(@NonNull Context context, @Nullable String relativePath)
|
||||
{
|
||||
Context context = HabitsApplication.getContext();
|
||||
File externalFilesDirs[] =
|
||||
ContextCompat.getExternalFilesDirs(context, null);
|
||||
|
||||
|
||||
@@ -22,19 +22,12 @@ package org.isoron.uhabits.utils;
|
||||
import android.content.*;
|
||||
import android.content.res.*;
|
||||
import android.graphics.*;
|
||||
import android.support.v4.view.*;
|
||||
import android.util.*;
|
||||
|
||||
import java.util.*;
|
||||
import android.view.*;
|
||||
|
||||
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;
|
||||
|
||||
public static Typeface getFontAwesome(Context context)
|
||||
@@ -59,14 +52,9 @@ public abstract class InterfaceUtils
|
||||
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();
|
||||
|
||||
for(String lang : fullyTranslatedLanguages)
|
||||
if(currentLanguage.equals(lang)) return true;
|
||||
|
||||
return false;
|
||||
return ViewCompat.getLayoutDirection(view) ==
|
||||
ViewCompat.LAYOUT_DIRECTION_RTL;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.widget.*;
|
||||
|
||||
import com.activeandroid.util.*;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.models.*;
|
||||
import org.isoron.uhabits.preferences.*;
|
||||
@@ -76,8 +78,15 @@ public abstract class BaseWidgetProvider extends AppWidgetProvider
|
||||
|
||||
for (int id : ids)
|
||||
{
|
||||
BaseWidget widget = getWidgetFromId(context, id);
|
||||
widget.delete();
|
||||
try
|
||||
{
|
||||
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"/>
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/scrollView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/toolbar">
|
||||
@@ -86,6 +87,11 @@
|
||||
style="@style/About.Item.Clickable"
|
||||
android:text="@string/pref_send_feedback"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTranslate"
|
||||
style="@style/About.Item.Clickable"
|
||||
android:text="@string/help_translate"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSource"
|
||||
style="@style/About.Item.Clickable"
|
||||
@@ -117,6 +123,9 @@
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Nikhil (regularcoder)"/>
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="JanetQC"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -129,17 +138,33 @@
|
||||
android:text="@string/translators"
|
||||
android:textColor="?aboutScreenColor"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Mihail Stefanov (Bǎlgarski)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Angga Rifandi (Bahasa Indonesia)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="David Nos (Català)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Tomáš Borovec (Čeština)"/>
|
||||
|
||||
<TextView
|
||||
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
|
||||
style="@style/About.Item"
|
||||
@@ -157,6 +182,22 @@
|
||||
style="@style/About.Item"
|
||||
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
|
||||
style="@style/About.Item"
|
||||
android:text="François Mahé (Français)"/>
|
||||
@@ -205,6 +246,10 @@
|
||||
style="@style/About.Item"
|
||||
android:text="Dmitriy Bogdanov (Русский)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Andrei Pleș (Română)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Dušan Strgar (Slovenščina)"/>
|
||||
@@ -275,7 +320,12 @@
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Andreas Michelakis (Ελληνικά)"/>
|
||||
android:text="Niraj Yadav (हिन्दी)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Yoav Argov (עברית)"/>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
|
||||
<EditText
|
||||
android:id="@+id/tvFreqNum"
|
||||
style="@style/dialogFormInputSmallNumber"/>
|
||||
style="@style/dialogFormInputLargeNumber"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView3"
|
||||
@@ -89,7 +89,7 @@
|
||||
|
||||
<EditText
|
||||
android:id="@+id/tvFreqDen"
|
||||
style="@style/dialogFormInputSmallNumber"/>
|
||||
style="@style/dialogFormInputLargeNumber"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView5"
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/scrollView"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/toolbar"
|
||||
|
||||
@@ -45,6 +45,26 @@
|
||||
android:checkable="true"
|
||||
android:enabled="true"
|
||||
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>
|
||||
</item>
|
||||
|
||||
@@ -73,5 +93,4 @@
|
||||
android:orderInCategory="100"
|
||||
android:title="@string/about"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
</menu>
|
||||
|
||||
@@ -21,6 +21,11 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/export"
|
||||
android:title="@string/export"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_edit_habit"
|
||||
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"?>
|
||||
<!--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>
|
||||
|
||||
<!-- There is no letter P in arabic so the name is unfortunately translated to "Loob" -->
|
||||
<string name="app_name">"لوب ملاحق العادة "</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>
|
||||
|
||||
<!-- 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="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>
|
||||
|
||||
<!-- 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="repeat">"كرر"</string>
|
||||
<string name="times_every">"مرات في"</string>
|
||||
<string name="days">"أيام"</string>
|
||||
<string name="reminder">"تذكير"</string>
|
||||
<string name="discard">"حذف"</string>
|
||||
<string name="save">"حفظ"</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="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">"لوب يساعدك على خلق والحفاظ على العادات الجيدة."</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">"ساعة واحدة"</string>
|
||||
<string name="interval_2_hour">"ساعتين"</string>
|
||||
<string name="interval_4_hour">"أربع ساعات"</string>
|
||||
<string name="interval_8_hour">"ثماني ساعات"</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">"تقييم هذا التطبيق على جوجل بلاي"</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="show_archived">"عرض أرشفة"</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="weekends">"عطلة نهاية الأسبوع"</string>
|
||||
|
||||
<!-- Fuzzy -->
|
||||
<string name="any_weekday">"أيام الأسبوع"</string>
|
||||
|
||||
<!-- Fuzzy -->
|
||||
<string name="any_day">"أي يوم"</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="select_weekdays">"إختار أيام "</string>
|
||||
|
||||
<!-- Fuzzy -->
|
||||
<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>
|
||||
|
||||
<!-- Short description used on the Google Play store. There is an 80-character limit. -->
|
||||
<string name="store_short_description">"خلق عادات جيدة وتتبع تقدمك على مر الزمن"</string>
|
||||
<string name="store_description_1">"لب يساعدك على خلق والحفاظ على العادات الجيدة، مما يسمح لك لتحقيق أهدافكة. الرسوم البيانية والإحصاءات التفصيلية تبين لكم كيف تحسن عاداتك مع مرور الوقت. هو تماما خالية من الاعلانات ومفتوحة المصدر."</string>
|
||||
<string name="store_feature_interface">"<b>واجهة بسيطة، جميلة وحديثة </b>
|
||||
لوب يحتوي على واجهة بسيطة وهي سهلة الاستخدام و تتابع نظام تصميم الماتريل دسيجن."</string>
|
||||
<string name="store_feature_score">"<b>نتيجة العادات</b>
|
||||
بالإضافة إلى عرض التقدم الحالي، لوب ديه خوارزمية متقدمة لحساب قوة عاداتك. كل التكرار يجعل هذه العادة أقوى، وفي كل يوم غاب يجعلها أضعف. مع ذلك غيب أيام قليلة بعد تقدم طويلة ، لن تدمر تماما تقدمك ."</string>
|
||||
<string name="store_feature_statistics">"<b>الرسوم البيانية والإحصاءات المفصلة</b>
|
||||
نرى بوضوح كيف كنت قد تحسنت عاداتك بمرور الوقت مع الرسوم البيانية الجميله ومفصلة. انتقل إلى الوراء لنرى التاريخ الكامل لعاداتك."</string>
|
||||
<string name="store_feature_schedules">"<b>جداول مرنة</b>
|
||||
تؤيد كل من العادات اليومية والعادات مع جداول أكثر تعقيدا، مثل 3 مرات كل أسبوع، مرة واحدة كل أسبوعين، أو مرة كل يومين."</string>
|
||||
<string name="store_feature_reminders">"<b>تذكير</b>
|
||||
إنشاء تذكير لكل فرد من عاداتك، في ساعة اختيار من اليوم. تحقق بسهولة، رفض أو غفوة عادتك مباشرة من الإخطار، دون الحاجة إلى فتح التطبيق."</string>
|
||||
<string name="store_feature_opensource">"<b>خالية تماما من الإعلانات و المصدر المفتوح</b>
|
||||
لا توجد على الاطلاق الإعلانات والشعارات المزعجة أو أذونات إضافية في هذا التطبيق، و سوف يكون هناك أبدا."</string>
|
||||
<string name="store_feature_wear">"<b>الأمثل للساعات الذكية</b>
|
||||
يمكن التحقق من رسائل التذكير، رفض أو غفوة عادتك مباشرة من ساعتك الاندرويد وير. "</string>
|
||||
<string name="about">"معلومات حول"</string>
|
||||
<string name="translators">"المترجمين"</string>
|
||||
<string name="developers">"المطورين"</string>
|
||||
|
||||
<!-- %s will get replaced by the version number. For example, "Versão %d" will become "Versão 1.2.0". -->
|
||||
<string name="version_n">"الإصدار %s"</string>
|
||||
<string name="frequency">"تردد"</string>
|
||||
<string name="checkmark">"علامة الاختيار"</string>
|
||||
|
||||
<!-- This is a shorter version of "Habit Strength" -->
|
||||
<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>
|
||||
|
||||
<!-- "All time" number of repetitions. Or number of repetitions "since the beginning". -->
|
||||
<string name="all_time">"كل الوقت"</string>
|
||||
<string name="every_day">"كل يوم"</string>
|
||||
<string name="every_week">"كل اسبوع"</string>
|
||||
<string name="two_times_per_week">"مرتين في الأسبوع"</string>
|
||||
<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>
|
||||
<string name="app_name">لوب ملاحق العادة </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">لوب يساعدك على خلق والحفاظ على العادات الجيدة.</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">ساعة واحدة</string>
|
||||
<string name="interval_2_hour">ساعتين</string>
|
||||
<string name="interval_4_hour">أربع ساعات</string>
|
||||
<string name="interval_8_hour">ثماني ساعات</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">تقييم هذا التطبيق على جوجل بلاي</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="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">مرتين في الأسبوع</string>
|
||||
<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>
|
||||
<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>
|
||||
<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="action">عمل</string>
|
||||
<string name="download">تحميل</string>
|
||||
<string name="export">استخراج</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>
|
||||