Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 957a5b7c17 | |||
| 64cc9f78a8 | |||
| e50c411d1e | |||
| ce27773138 | |||
| 0864f83307 | |||
| 624cc67d9b | |||
| 462bac8167 | |||
| 5865eb41f7 | |||
| 2bfd4a942d | |||
| b4a33cba39 | |||
| f02c86e61b | |||
| 5021f50e18 | |||
| bf3964a231 | |||
| 657cde75d8 | |||
| c56b86d32c | |||
| 16f20d50a0 | |||
| 7bb88dcb97 | |||
| 7bb62c197f | |||
| 540a618ba8 | |||
| bf24cc608c | |||
| a73459784e | |||
| de3b97dfdf | |||
| 91996924d9 | |||
| 9fe446b424 | |||
| 3857eaf5e9 | |||
| e29fb58922 | |||
| 404fc869b0 | |||
| 001dd5a7c1 | |||
| 7930cc8f31 | |||
| 526830ba61 | |||
| fe1513bb64 | |||
| e06ace9ea8 | |||
| d727dabb2b | |||
| d17e8fcbfb | |||
| be3d7145ab | |||
| cf66587644 | |||
| 0dc9ec2e5f | |||
| 0a375ded96 | |||
| fa5d6f8fee | |||
| 534e6c2d9d | |||
| b6501c9a29 | |||
| 238a1c724d | |||
| 34ca9d17a2 | |||
| e844390614 | |||
| 5e00d07b73 | |||
| 28b6ae7014 | |||
| 2a1bf5fc2e | |||
| ef7483f9dc | |||
| bbb9ed8f99 | |||
| c49d576871 | |||
| bc66ae4f7a | |||
| fa416adbb9 | |||
| 8b835b9918 | |||
| 471c5d341f | |||
| 57296745b3 | |||
| 140ab34a76 | |||
| 0d6ad26505 | |||
| 65cc99dbf7 | |||
| 6855ef9d5e | |||
| 4c58b084c6 | |||
| 5c8e522646 | |||
| 55da0759d4 |
2
.gitignore
vendored
@@ -21,3 +21,5 @@ docs/
|
||||
gen/
|
||||
local.properties
|
||||
crowdin.yaml
|
||||
local
|
||||
secret/
|
||||
|
||||
41
CHANGELOG.md
@@ -1,5 +1,46 @@
|
||||
# Changelog
|
||||
|
||||
### 1.7.11 (Aug 10, 2019)
|
||||
|
||||
* Fix bug that produced corrupted CSV files in some countries
|
||||
|
||||
### 1.7.10 (June 15, 2019)
|
||||
|
||||
* Fix bug that prevented some devices from showing notifications.
|
||||
* Update targetSdk to Android Pie (API level 28)
|
||||
|
||||
### 1.7.8 (April 21, 2018)
|
||||
|
||||
* Add support for adaptive icons (Oreo)
|
||||
* Add support for notification channels (Oreo)
|
||||
* Update translations
|
||||
|
||||
### 1.7.7 (September 30, 2017)
|
||||
|
||||
* Fix bug that caused reminders to show repeatedly on DST changes
|
||||
|
||||
### 1.7.6 (July 18, 2017)
|
||||
|
||||
* Fix bug that caused widgets not to render sometimes
|
||||
* Fix other minor bugs
|
||||
* Update translations
|
||||
|
||||
### 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
|
||||
|
||||
@@ -2,30 +2,50 @@ apply plugin: 'com.android.application'
|
||||
apply plugin: 'com.neenbedankt.android-apt'
|
||||
apply plugin: 'me.tatarka.retrolambda'
|
||||
apply plugin: 'jacoco'
|
||||
apply plugin: 'com.github.triplet.play'
|
||||
|
||||
android {
|
||||
compileSdkVersion 25
|
||||
buildToolsVersion "25.0.2"
|
||||
compileSdkVersion 28
|
||||
buildToolsVersion "28.0.3"
|
||||
|
||||
// signingConfigs {
|
||||
// release {
|
||||
// storeFile file(LOOP_STORE_FILE)
|
||||
// storePassword LOOP_STORE_PASSWORD
|
||||
// keyAlias LOOP_KEY_ALIAS
|
||||
// keyPassword LOOP_KEY_PASSWORD
|
||||
// }
|
||||
// }
|
||||
|
||||
playAccountConfigs {
|
||||
defaultAccountConfig {
|
||||
jsonFile = file('../secret/playstore.json')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
defaultConfig {
|
||||
applicationId "org.isoron.uhabits"
|
||||
minSdkVersion 15
|
||||
targetSdkVersion 25
|
||||
targetSdkVersion 28
|
||||
|
||||
buildConfigField "Integer", "databaseVersion", "15"
|
||||
buildConfigField "String", "databaseFilename", "\"uhabits.db\""
|
||||
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
testInstrumentationRunnerArgument "size", "medium"
|
||||
|
||||
// playAccountConfig = playAccountConfigs.defaultAccountConfig
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||
// signingConfig signingConfigs.release
|
||||
}
|
||||
debug {
|
||||
testCoverageEnabled = true
|
||||
testCoverageEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +73,7 @@ dependencies {
|
||||
|
||||
androidTestApt 'com.google.dagger:dagger-compiler:2.2'
|
||||
|
||||
androidTestCompile 'com.android.support:support-annotations:25.3.0'
|
||||
androidTestCompile 'com.android.support:support-annotations:27.1.1'
|
||||
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,16 +84,15 @@ dependencies {
|
||||
apt 'com.google.dagger:dagger-compiler:2.2'
|
||||
apt 'com.jakewharton:butterknife-compiler:8.0.1'
|
||||
|
||||
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.android.support:appcompat-v7:27.1.1'
|
||||
compile 'com.android.support:design:27.1.1'
|
||||
compile 'com.android.support:preference-v14:27.1.1'
|
||||
compile 'com.android.support:support-v4:27.1.1'
|
||||
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'
|
||||
compile 'com.google.dagger:dagger:2.2'
|
||||
compile 'com.jakewharton:butterknife:8.0.1'
|
||||
compile 'com.michaelpardo:activeandroid:3.1.0-SNAPSHOT'
|
||||
compile 'com.opencsv:opencsv:3.7'
|
||||
compile 'org.apmem.tools:layouts:1.10@aar'
|
||||
compile 'org.jetbrains:annotations-java5:15.0'
|
||||
@@ -140,3 +159,7 @@ task coverageReport(type: JacocoReport, dependsOn: ['testDebugUnitTest']) {
|
||||
classDirectories = files(fileTree(dir: classDir, excludes: excludes))
|
||||
executionData = files(jvmExecData, connectedExecData)
|
||||
}
|
||||
|
||||
//play {
|
||||
// track = 'alpha'
|
||||
//}
|
||||
|
||||
@@ -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.*;
|
||||
|
||||
@@ -65,6 +67,8 @@ public class BaseAndroidTest
|
||||
|
||||
protected ModelFactory modelFactory;
|
||||
|
||||
private boolean isDone = false;
|
||||
|
||||
@Before
|
||||
public void setUp()
|
||||
{
|
||||
@@ -115,6 +119,25 @@ public class BaseAndroidTest
|
||||
assertTrue(latch.await(60, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
protected void runConcurrently(Runnable... runnableList) throws Exception
|
||||
{
|
||||
isDone = false;
|
||||
ExecutorService executor = Executors.newFixedThreadPool(100);
|
||||
List<Future> futures = new LinkedList<>();
|
||||
for (Runnable r : runnableList)
|
||||
futures.add(executor.submit(() ->
|
||||
{
|
||||
while (!isDone) r.run();
|
||||
return null;
|
||||
}));
|
||||
|
||||
Thread.sleep(3000);
|
||||
isDone = true;
|
||||
executor.shutdown();
|
||||
for(Future f : futures) f.get();
|
||||
while (!executor.isTerminated()) Thread.sleep(50);
|
||||
}
|
||||
|
||||
protected void setTheme(@StyleRes int themeId)
|
||||
{
|
||||
targetContext.setTheme(themeId);
|
||||
@@ -132,4 +155,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -171,13 +171,13 @@ public class MainTest
|
||||
clickMenuItem(R.string.archive);
|
||||
assertHabitsDontExist(names);
|
||||
|
||||
clickMenuItem(R.string.show_archived);
|
||||
clickMenuItem(R.string.hide_archived);
|
||||
|
||||
assertHabitsExist(names);
|
||||
selectHabits(names);
|
||||
clickMenuItem(R.string.unarchive);
|
||||
|
||||
clickMenuItem(R.string.show_archived);
|
||||
clickMenuItem(R.string.hide_archived);
|
||||
|
||||
assertHabitsExist(names);
|
||||
deleteHabits(names);
|
||||
|
||||
@@ -115,6 +115,41 @@ public class SQLiteCheckmarkListTest extends BaseAndroidTest
|
||||
assertThat(records.get(0).timestamp, equalTo(today - 21 * day));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFixRecords() throws Exception
|
||||
{
|
||||
long day = DateUtils.millisecondsInOneDay;
|
||||
long from = DateUtils.getStartOfToday();
|
||||
long to = from + 5 * day;
|
||||
|
||||
List<CheckmarkRecord> original, actual, expected;
|
||||
HabitRecord habit = new HabitRecord();
|
||||
|
||||
original = new ArrayList<>();
|
||||
original.add(new CheckmarkRecord(habit, from + 8*day, 2));
|
||||
original.add(new CheckmarkRecord(habit, from + 5*day, 0));
|
||||
original.add(new CheckmarkRecord(habit, from + 4*day, 0));
|
||||
original.add(new CheckmarkRecord(habit, from + 4*day, 2));
|
||||
original.add(new CheckmarkRecord(habit, from + 3*day, 2));
|
||||
original.add(new CheckmarkRecord(habit, from + 2*day, 1));
|
||||
original.add(new CheckmarkRecord(habit, from + 2*day + 100, 1));
|
||||
original.add(new CheckmarkRecord(habit, from, 0));
|
||||
original.add(new CheckmarkRecord(habit, from, 2));
|
||||
original.add(new CheckmarkRecord(habit, from - day, 2));
|
||||
|
||||
actual = SQLiteCheckmarkList.fixRecords(original, habit, from, to);
|
||||
|
||||
expected = new ArrayList<>();
|
||||
expected.add(new CheckmarkRecord(habit, from + 5*day, 0));
|
||||
expected.add(new CheckmarkRecord(habit, from + 4*day, 2));
|
||||
expected.add(new CheckmarkRecord(habit, from + 3*day, 2));
|
||||
expected.add(new CheckmarkRecord(habit, from + 2*day, 1));
|
||||
expected.add(new CheckmarkRecord(habit, from + day, 0));
|
||||
expected.add(new CheckmarkRecord(habit, from, 2));
|
||||
|
||||
assertThat(actual, equalTo(expected));
|
||||
}
|
||||
|
||||
private List<CheckmarkRecord> getAllRecords()
|
||||
{
|
||||
return new Select()
|
||||
|
||||
@@ -61,29 +61,6 @@ public class SQLiteScoreListTest extends BaseAndroidTest
|
||||
day = DateUtils.millisecondsInOneDay;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAll()
|
||||
{
|
||||
List<Score> list = scores.toList();
|
||||
assertThat(list.size(), equalTo(121));
|
||||
assertThat(list.get(0).getTimestamp(), equalTo(today));
|
||||
assertThat(list.get(10).getTimestamp(), equalTo(today - 10 * day));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidateNewerThan()
|
||||
{
|
||||
scores.getTodayValue(); // force recompute
|
||||
List<ScoreRecord> records = getAllRecords();
|
||||
assertThat(records.size(), equalTo(121));
|
||||
|
||||
scores.invalidateNewerThan(today - 10 * day);
|
||||
|
||||
records = getAllRecords();
|
||||
assertThat(records.size(), equalTo(110));
|
||||
assertThat(records.get(0).timestamp, equalTo(today - 11 * day));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdd()
|
||||
{
|
||||
@@ -101,6 +78,15 @@ public class SQLiteScoreListTest extends BaseAndroidTest
|
||||
assertThat(records.get(0).timestamp, equalTo(today));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAll()
|
||||
{
|
||||
List<Score> list = scores.toList();
|
||||
assertThat(list.size(), equalTo(121));
|
||||
assertThat(list.get(0).getTimestamp(), equalTo(today));
|
||||
assertThat(list.get(10).getTimestamp(), equalTo(today - 10 * day));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetByInterval()
|
||||
{
|
||||
@@ -115,6 +101,16 @@ public class SQLiteScoreListTest extends BaseAndroidTest
|
||||
assertThat(list.get(7).getTimestamp(), equalTo(today - 10 * day));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetByInterval_concurrent() throws Exception
|
||||
{
|
||||
Runnable block1 = () -> scores.invalidateNewerThan(0);
|
||||
Runnable block2 =
|
||||
() -> assertThat(scores.getByInterval(today, today).size(),
|
||||
equalTo(1));
|
||||
runConcurrently(block1, block2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetByInterval_withLongInterval()
|
||||
{
|
||||
@@ -125,6 +121,30 @@ public class SQLiteScoreListTest extends BaseAndroidTest
|
||||
assertThat(list.size(), equalTo(201));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTodayValue_concurrent() throws Exception
|
||||
{
|
||||
Runnable block1 = () -> scores.invalidateNewerThan(0);
|
||||
Runnable block2 =
|
||||
() -> assertThat(scores.getTodayValue(), equalTo(18407827));
|
||||
|
||||
runConcurrently(block1, block2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidateNewerThan()
|
||||
{
|
||||
scores.getTodayValue(); // force recompute
|
||||
List<ScoreRecord> records = getAllRecords();
|
||||
assertThat(records.size(), equalTo(121));
|
||||
|
||||
scores.invalidateNewerThan(today - 10 * day);
|
||||
|
||||
records = getAllRecords();
|
||||
assertThat(records.size(), equalTo(110));
|
||||
assertThat(records.get(0).timestamp, equalTo(today - 11 * day));
|
||||
}
|
||||
|
||||
private List<ScoreRecord> getAllRecords()
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
/*
|
||||
* 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.pebble;
|
||||
|
||||
import android.content.*;
|
||||
import android.support.annotation.*;
|
||||
import android.support.test.runner.*;
|
||||
import android.test.suitebuilder.annotation.*;
|
||||
|
||||
import com.getpebble.android.kit.*;
|
||||
import com.getpebble.android.kit.util.*;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.models.*;
|
||||
import org.isoron.uhabits.receivers.*;
|
||||
import org.json.*;
|
||||
import org.junit.*;
|
||||
import org.junit.runner.*;
|
||||
|
||||
import static com.getpebble.android.kit.Constants.*;
|
||||
import static org.hamcrest.MatcherAssert.*;
|
||||
import static org.hamcrest.core.IsEqual.*;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@MediumTest
|
||||
public class PebbleReceiverTest extends BaseAndroidTest
|
||||
{
|
||||
|
||||
private Habit habit1;
|
||||
|
||||
private Habit habit2;
|
||||
|
||||
@Override
|
||||
public void setUp()
|
||||
{
|
||||
super.setUp();
|
||||
|
||||
fixtures.purgeHabits(habitList);
|
||||
|
||||
habit1 = fixtures.createEmptyHabit();
|
||||
habit1.setName("Exercise");
|
||||
|
||||
habit2 = fixtures.createEmptyHabit();
|
||||
habit2.setName("Meditate");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCount() throws Exception
|
||||
{
|
||||
onPebbleReceived((dict) -> {
|
||||
assertThat(dict.getString(0), equalTo("COUNT"));
|
||||
assertThat(dict.getInteger(1), equalTo(2L));
|
||||
});
|
||||
|
||||
PebbleDictionary dict = buildCountRequest();
|
||||
sendFromPebbleToAndroid(dict);
|
||||
awaitLatch();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFetch() throws Exception
|
||||
{
|
||||
onPebbleReceived((dict) -> {
|
||||
assertThat(dict.getString(0), equalTo("HABIT"));
|
||||
assertThat(dict.getInteger(1), equalTo(habit2.getId()));
|
||||
assertThat(dict.getString(2), equalTo(habit2.getName()));
|
||||
assertThat(dict.getInteger(3), equalTo(0L));
|
||||
});
|
||||
|
||||
PebbleDictionary dict = buildFetchRequest(1);
|
||||
sendFromPebbleToAndroid(dict);
|
||||
awaitLatch();
|
||||
}
|
||||
|
||||
// @Test
|
||||
// public void testToggle() throws Exception
|
||||
// {
|
||||
// int v = habit1.getCheckmarks().getTodayValue();
|
||||
// assertThat(v, equalTo(Checkmark.UNCHECKED));
|
||||
//
|
||||
// onPebbleReceived((dict) -> {
|
||||
// assertThat(dict.getString(0), equalTo("OK"));
|
||||
// int value = habit1.getCheckmarks().getTodayValue();
|
||||
// assertThat(value, equalTo(200)); //Checkmark.CHECKED_EXPLICITLY));
|
||||
// });
|
||||
//
|
||||
// PebbleDictionary dict = buildToggleRequest(habit1.getId());
|
||||
// sendFromPebbleToAndroid(dict);
|
||||
// awaitLatch();
|
||||
// }
|
||||
|
||||
@NonNull
|
||||
protected PebbleDictionary buildCountRequest()
|
||||
{
|
||||
PebbleDictionary dict = new PebbleDictionary();
|
||||
dict.addString(0, "COUNT");
|
||||
return dict;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected PebbleDictionary buildFetchRequest(int position)
|
||||
{
|
||||
PebbleDictionary dict = new PebbleDictionary();
|
||||
dict.addString(0, "FETCH");
|
||||
dict.addInt32(1, position);
|
||||
return dict;
|
||||
}
|
||||
|
||||
protected void onPebbleReceived(PebbleProcessor processor)
|
||||
{
|
||||
BroadcastReceiver pebbleReceiver = new BroadcastReceiver()
|
||||
{
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent)
|
||||
{
|
||||
try
|
||||
{
|
||||
String jsonData = intent.getStringExtra(MSG_DATA);
|
||||
PebbleDictionary dict = PebbleDictionary.fromJson(jsonData);
|
||||
processor.process(dict);
|
||||
latch.countDown();
|
||||
targetContext.unregisterReceiver(this);
|
||||
}
|
||||
catch (JSONException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
IntentFilter filter = new IntentFilter(Constants.INTENT_APP_SEND);
|
||||
targetContext.registerReceiver(pebbleReceiver, filter);
|
||||
}
|
||||
|
||||
protected void sendFromPebbleToAndroid(PebbleDictionary dict)
|
||||
{
|
||||
Intent intent = new Intent(Constants.INTENT_APP_RECEIVE);
|
||||
intent.putExtra(Constants.APP_UUID, PebbleReceiver.WATCHAPP_UUID);
|
||||
intent.putExtra(Constants.TRANSACTION_ID, 0);
|
||||
intent.putExtra(Constants.MSG_DATA, dict.toJsonString());
|
||||
targetContext.sendBroadcast(intent);
|
||||
}
|
||||
|
||||
private PebbleDictionary buildToggleRequest(long habitId)
|
||||
{
|
||||
PebbleDictionary dict = new PebbleDictionary();
|
||||
dict.addString(0, "TOGGLE");
|
||||
dict.addInt32(1, (int) habitId);
|
||||
return dict;
|
||||
}
|
||||
|
||||
interface PebbleProcessor
|
||||
{
|
||||
void process(PebbleDictionary dict);
|
||||
}
|
||||
}
|
||||
@@ -21,8 +21,8 @@
|
||||
<manifest
|
||||
package="org.isoron.uhabits"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:versionCode="27"
|
||||
android:versionName="1.7.0">
|
||||
android:versionCode="38"
|
||||
android:versionName="1.7.11">
|
||||
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 49 KiB |
86
app/src/main/java/com/activeandroid/ActiveAndroid.java
Normal file
@@ -0,0 +1,86 @@
|
||||
package com.activeandroid;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2010 Michael Pardo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
import com.activeandroid.util.Log;
|
||||
|
||||
public final class ActiveAndroid {
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// PUBLIC METHODS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static void initialize(Context context) {
|
||||
initialize(new Configuration.Builder(context).create());
|
||||
}
|
||||
|
||||
public static void initialize(Configuration configuration) {
|
||||
initialize(configuration, false);
|
||||
}
|
||||
|
||||
public static void initialize(Context context, boolean loggingEnabled) {
|
||||
initialize(new Configuration.Builder(context).create(), loggingEnabled);
|
||||
}
|
||||
|
||||
public static void initialize(Configuration configuration, boolean loggingEnabled) {
|
||||
// Set logging enabled first
|
||||
setLoggingEnabled(loggingEnabled);
|
||||
Cache.initialize(configuration);
|
||||
}
|
||||
|
||||
public static void clearCache() {
|
||||
Cache.clear();
|
||||
}
|
||||
|
||||
public static void dispose() {
|
||||
Cache.dispose();
|
||||
}
|
||||
|
||||
public static void setLoggingEnabled(boolean enabled) {
|
||||
Log.setEnabled(enabled);
|
||||
}
|
||||
|
||||
public static SQLiteDatabase getDatabase() {
|
||||
return Cache.openDatabase();
|
||||
}
|
||||
|
||||
public static void beginTransaction() {
|
||||
Cache.openDatabase().beginTransaction();
|
||||
}
|
||||
|
||||
public static void endTransaction() {
|
||||
Cache.openDatabase().endTransaction();
|
||||
}
|
||||
|
||||
public static void setTransactionSuccessful() {
|
||||
Cache.openDatabase().setTransactionSuccessful();
|
||||
}
|
||||
|
||||
public static boolean inTransaction() {
|
||||
return Cache.openDatabase().inTransaction();
|
||||
}
|
||||
|
||||
public static void execSQL(String sql) {
|
||||
Cache.openDatabase().execSQL(sql);
|
||||
}
|
||||
|
||||
public static void execSQL(String sql, Object[] bindArgs) {
|
||||
Cache.openDatabase().execSQL(sql, bindArgs);
|
||||
}
|
||||
}
|
||||
158
app/src/main/java/com/activeandroid/Cache.java
Normal file
@@ -0,0 +1,158 @@
|
||||
package com.activeandroid;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2010 Michael Pardo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.support.v4.util.LruCache;
|
||||
|
||||
import com.activeandroid.serializer.TypeSerializer;
|
||||
import com.activeandroid.util.Log;
|
||||
|
||||
public final class Cache {
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// PUBLIC CONSTANTS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static final int DEFAULT_CACHE_SIZE = 1024;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// PRIVATE MEMBERS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private static Context sContext;
|
||||
|
||||
private static ModelInfo sModelInfo;
|
||||
private static DatabaseHelper sDatabaseHelper;
|
||||
|
||||
private static LruCache<String, Model> sEntities;
|
||||
|
||||
private static boolean sIsInitialized = false;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// CONSTRUCTORS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private Cache() {
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// PUBLIC METHODS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static synchronized void initialize(Configuration configuration) {
|
||||
if (sIsInitialized) {
|
||||
Log.v("ActiveAndroid already initialized.");
|
||||
return;
|
||||
}
|
||||
|
||||
sContext = configuration.getContext();
|
||||
sModelInfo = new ModelInfo(configuration);
|
||||
sDatabaseHelper = new DatabaseHelper(configuration);
|
||||
|
||||
// TODO: It would be nice to override sizeOf here and calculate the memory
|
||||
// actually used, however at this point it seems like the reflection
|
||||
// required would be too costly to be of any benefit. We'll just set a max
|
||||
// object size instead.
|
||||
sEntities = new LruCache<String, Model>(configuration.getCacheSize());
|
||||
|
||||
openDatabase();
|
||||
|
||||
sIsInitialized = true;
|
||||
|
||||
Log.v("ActiveAndroid initialized successfully.");
|
||||
}
|
||||
|
||||
public static synchronized void clear() {
|
||||
sEntities.evictAll();
|
||||
Log.v("Cache cleared.");
|
||||
}
|
||||
|
||||
public static synchronized void dispose() {
|
||||
closeDatabase();
|
||||
|
||||
sEntities = null;
|
||||
sModelInfo = null;
|
||||
sDatabaseHelper = null;
|
||||
|
||||
sIsInitialized = false;
|
||||
|
||||
Log.v("ActiveAndroid disposed. Call initialize to use library.");
|
||||
}
|
||||
|
||||
// Database access
|
||||
|
||||
public static boolean isInitialized() {
|
||||
return sIsInitialized;
|
||||
}
|
||||
|
||||
public static synchronized SQLiteDatabase openDatabase() {
|
||||
return sDatabaseHelper.getWritableDatabase();
|
||||
}
|
||||
|
||||
public static synchronized void closeDatabase() {
|
||||
sDatabaseHelper.close();
|
||||
}
|
||||
|
||||
// Context access
|
||||
|
||||
public static Context getContext() {
|
||||
return sContext;
|
||||
}
|
||||
|
||||
// Entity cache
|
||||
|
||||
public static String getIdentifier(Class<? extends Model> type, Long id) {
|
||||
return getTableName(type) + "@" + id;
|
||||
}
|
||||
|
||||
public static String getIdentifier(Model entity) {
|
||||
return getIdentifier(entity.getClass(), entity.getId());
|
||||
}
|
||||
|
||||
public static synchronized void addEntity(Model entity) {
|
||||
sEntities.put(getIdentifier(entity), entity);
|
||||
}
|
||||
|
||||
public static synchronized Model getEntity(Class<? extends Model> type, long id) {
|
||||
return sEntities.get(getIdentifier(type, id));
|
||||
}
|
||||
|
||||
public static synchronized void removeEntity(Model entity) {
|
||||
sEntities.remove(getIdentifier(entity));
|
||||
}
|
||||
|
||||
// Model cache
|
||||
|
||||
public static synchronized Collection<TableInfo> getTableInfos() {
|
||||
return sModelInfo.getTableInfos();
|
||||
}
|
||||
|
||||
public static synchronized TableInfo getTableInfo(Class<? extends Model> type) {
|
||||
return sModelInfo.getTableInfo(type);
|
||||
}
|
||||
|
||||
public static synchronized TypeSerializer getParserForType(Class<?> type) {
|
||||
return sModelInfo.getTypeSerializer(type);
|
||||
}
|
||||
|
||||
public static synchronized String getTableName(Class<? extends Model> type) {
|
||||
return sModelInfo.getTableInfo(type).getTableName();
|
||||
}
|
||||
}
|
||||
318
app/src/main/java/com/activeandroid/Configuration.java
Normal file
@@ -0,0 +1,318 @@
|
||||
package com.activeandroid;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2010 Michael Pardo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.activeandroid.serializer.TypeSerializer;
|
||||
import com.activeandroid.util.Log;
|
||||
import com.activeandroid.util.ReflectionUtils;
|
||||
|
||||
public class Configuration {
|
||||
|
||||
public final static String SQL_PARSER_LEGACY = "legacy";
|
||||
public final static String SQL_PARSER_DELIMITED = "delimited";
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// PRIVATE MEMBERS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private Context mContext;
|
||||
private String mDatabaseName;
|
||||
private int mDatabaseVersion;
|
||||
private String mSqlParser;
|
||||
private List<Class<? extends Model>> mModelClasses;
|
||||
private List<Class<? extends TypeSerializer>> mTypeSerializers;
|
||||
private int mCacheSize;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// CONSTRUCTORS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private Configuration(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// PUBLIC METHODS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
public String getDatabaseName() {
|
||||
return mDatabaseName;
|
||||
}
|
||||
|
||||
public int getDatabaseVersion() {
|
||||
return mDatabaseVersion;
|
||||
}
|
||||
|
||||
public String getSqlParser() {
|
||||
return mSqlParser;
|
||||
}
|
||||
|
||||
public List<Class<? extends Model>> getModelClasses() {
|
||||
return mModelClasses;
|
||||
}
|
||||
|
||||
public List<Class<? extends TypeSerializer>> getTypeSerializers() {
|
||||
return mTypeSerializers;
|
||||
}
|
||||
|
||||
public int getCacheSize() {
|
||||
return mCacheSize;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return mModelClasses != null && mModelClasses.size() > 0;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// INNER CLASSES
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static class Builder {
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// PRIVATE CONSTANTS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private static final String AA_DB_NAME = "AA_DB_NAME";
|
||||
private static final String AA_DB_VERSION = "AA_DB_VERSION";
|
||||
private final static String AA_MODELS = "AA_MODELS";
|
||||
private final static String AA_SERIALIZERS = "AA_SERIALIZERS";
|
||||
private final static String AA_SQL_PARSER = "AA_SQL_PARSER";
|
||||
|
||||
private static final int DEFAULT_CACHE_SIZE = 1024;
|
||||
private static final String DEFAULT_DB_NAME = "Application.db";
|
||||
private static final String DEFAULT_SQL_PARSER = SQL_PARSER_LEGACY;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// PRIVATE MEMBERS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private Context mContext;
|
||||
|
||||
private Integer mCacheSize;
|
||||
private String mDatabaseName;
|
||||
private Integer mDatabaseVersion;
|
||||
private String mSqlParser;
|
||||
private List<Class<? extends Model>> mModelClasses;
|
||||
private List<Class<? extends TypeSerializer>> mTypeSerializers;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// CONSTRUCTORS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public Builder(Context context) {
|
||||
mContext = context.getApplicationContext();
|
||||
mCacheSize = DEFAULT_CACHE_SIZE;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// PUBLIC METHODS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public Builder setCacheSize(int cacheSize) {
|
||||
mCacheSize = cacheSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setDatabaseName(String databaseName) {
|
||||
mDatabaseName = databaseName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setDatabaseVersion(int databaseVersion) {
|
||||
mDatabaseVersion = databaseVersion;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSqlParser(String sqlParser) {
|
||||
mSqlParser = sqlParser;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addModelClass(Class<? extends Model> modelClass) {
|
||||
if (mModelClasses == null) {
|
||||
mModelClasses = new ArrayList<Class<? extends Model>>();
|
||||
}
|
||||
|
||||
mModelClasses.add(modelClass);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addModelClasses(Class<? extends Model>... modelClasses) {
|
||||
if (mModelClasses == null) {
|
||||
mModelClasses = new ArrayList<Class<? extends Model>>();
|
||||
}
|
||||
|
||||
mModelClasses.addAll(Arrays.asList(modelClasses));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setModelClasses(Class<? extends Model>... modelClasses) {
|
||||
mModelClasses = Arrays.asList(modelClasses);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addTypeSerializer(Class<? extends TypeSerializer> typeSerializer) {
|
||||
if (mTypeSerializers == null) {
|
||||
mTypeSerializers = new ArrayList<Class<? extends TypeSerializer>>();
|
||||
}
|
||||
|
||||
mTypeSerializers.add(typeSerializer);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addTypeSerializers(Class<? extends TypeSerializer>... typeSerializers) {
|
||||
if (mTypeSerializers == null) {
|
||||
mTypeSerializers = new ArrayList<Class<? extends TypeSerializer>>();
|
||||
}
|
||||
|
||||
mTypeSerializers.addAll(Arrays.asList(typeSerializers));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setTypeSerializers(Class<? extends TypeSerializer>... typeSerializers) {
|
||||
mTypeSerializers = Arrays.asList(typeSerializers);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Configuration create() {
|
||||
Configuration configuration = new Configuration(mContext);
|
||||
configuration.mCacheSize = mCacheSize;
|
||||
|
||||
// Get database name from meta-data
|
||||
if (mDatabaseName != null) {
|
||||
configuration.mDatabaseName = mDatabaseName;
|
||||
} else {
|
||||
configuration.mDatabaseName = getMetaDataDatabaseNameOrDefault();
|
||||
}
|
||||
|
||||
// Get database version from meta-data
|
||||
if (mDatabaseVersion != null) {
|
||||
configuration.mDatabaseVersion = mDatabaseVersion;
|
||||
} else {
|
||||
configuration.mDatabaseVersion = getMetaDataDatabaseVersionOrDefault();
|
||||
}
|
||||
|
||||
// Get SQL parser from meta-data
|
||||
if (mSqlParser != null) {
|
||||
configuration.mSqlParser = mSqlParser;
|
||||
} else {
|
||||
configuration.mSqlParser = getMetaDataSqlParserOrDefault();
|
||||
}
|
||||
|
||||
// Get model classes from meta-data
|
||||
if (mModelClasses != null) {
|
||||
configuration.mModelClasses = mModelClasses;
|
||||
} else {
|
||||
final String modelList = ReflectionUtils.getMetaData(mContext, AA_MODELS);
|
||||
if (modelList != null) {
|
||||
configuration.mModelClasses = loadModelList(modelList.split(","));
|
||||
}
|
||||
}
|
||||
|
||||
// Get type serializer classes from meta-data
|
||||
if (mTypeSerializers != null) {
|
||||
configuration.mTypeSerializers = mTypeSerializers;
|
||||
} else {
|
||||
final String serializerList = ReflectionUtils.getMetaData(mContext, AA_SERIALIZERS);
|
||||
if (serializerList != null) {
|
||||
configuration.mTypeSerializers = loadSerializerList(serializerList.split(","));
|
||||
}
|
||||
}
|
||||
|
||||
return configuration;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// PRIVATE METHODS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Meta-data methods
|
||||
|
||||
private String getMetaDataDatabaseNameOrDefault() {
|
||||
String aaName = ReflectionUtils.getMetaData(mContext, AA_DB_NAME);
|
||||
if (aaName == null) {
|
||||
aaName = DEFAULT_DB_NAME;
|
||||
}
|
||||
|
||||
return aaName;
|
||||
}
|
||||
|
||||
private int getMetaDataDatabaseVersionOrDefault() {
|
||||
Integer aaVersion = ReflectionUtils.getMetaData(mContext, AA_DB_VERSION);
|
||||
if (aaVersion == null || aaVersion == 0) {
|
||||
aaVersion = 1;
|
||||
}
|
||||
|
||||
return aaVersion;
|
||||
}
|
||||
|
||||
private String getMetaDataSqlParserOrDefault() {
|
||||
final String mode = ReflectionUtils.getMetaData(mContext, AA_SQL_PARSER);
|
||||
if (mode == null) {
|
||||
return DEFAULT_SQL_PARSER;
|
||||
}
|
||||
return mode;
|
||||
}
|
||||
|
||||
private List<Class<? extends Model>> loadModelList(String[] models) {
|
||||
final List<Class<? extends Model>> modelClasses = new ArrayList<Class<? extends Model>>();
|
||||
final ClassLoader classLoader = mContext.getClass().getClassLoader();
|
||||
for (String model : models) {
|
||||
try {
|
||||
Class modelClass = Class.forName(model.trim(), false, classLoader);
|
||||
if (ReflectionUtils.isModel(modelClass)) {
|
||||
modelClasses.add(modelClass);
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
Log.e("Couldn't create class.", e);
|
||||
}
|
||||
}
|
||||
|
||||
return modelClasses;
|
||||
}
|
||||
|
||||
private List<Class<? extends TypeSerializer>> loadSerializerList(String[] serializers) {
|
||||
final List<Class<? extends TypeSerializer>> typeSerializers = new ArrayList<Class<? extends TypeSerializer>>();
|
||||
final ClassLoader classLoader = mContext.getClass().getClassLoader();
|
||||
for (String serializer : serializers) {
|
||||
try {
|
||||
Class serializerClass = Class.forName(serializer.trim(), false, classLoader);
|
||||
if (ReflectionUtils.isTypeSerializer(serializerClass)) {
|
||||
typeSerializers.add(serializerClass);
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
Log.e("Couldn't create class.", e);
|
||||
}
|
||||
}
|
||||
|
||||
return typeSerializers;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
257
app/src/main/java/com/activeandroid/DatabaseHelper.java
Normal file
@@ -0,0 +1,257 @@
|
||||
package com.activeandroid;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2010 Michael Pardo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.activeandroid.util.IOUtils;
|
||||
import com.activeandroid.util.Log;
|
||||
import com.activeandroid.util.NaturalOrderComparator;
|
||||
import com.activeandroid.util.SQLiteUtils;
|
||||
import com.activeandroid.util.SqlParser;
|
||||
|
||||
public final class DatabaseHelper extends SQLiteOpenHelper {
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// PUBLIC CONSTANTS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public final static String MIGRATION_PATH = "migrations";
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// PRIVATE FIELDS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private final String mSqlParser;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// CONSTRUCTORS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public DatabaseHelper(Configuration configuration) {
|
||||
super(configuration.getContext(), configuration.getDatabaseName(), null, configuration.getDatabaseVersion());
|
||||
copyAttachedDatabase(configuration.getContext(), configuration.getDatabaseName());
|
||||
mSqlParser = configuration.getSqlParser();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// OVERRIDEN METHODS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void onOpen(SQLiteDatabase db) {
|
||||
executePragmas(db);
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
executePragmas(db);
|
||||
executeCreate(db);
|
||||
executeMigrations(db, -1, db.getVersion());
|
||||
executeCreateIndex(db);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
executePragmas(db);
|
||||
executeCreate(db);
|
||||
executeMigrations(db, oldVersion, newVersion);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// PUBLIC METHODS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void copyAttachedDatabase(Context context, String databaseName) {
|
||||
final File dbPath = context.getDatabasePath(databaseName);
|
||||
|
||||
// If the database already exists, return
|
||||
if (dbPath.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure we have a path to the file
|
||||
dbPath.getParentFile().mkdirs();
|
||||
|
||||
// Try to copy database file
|
||||
try {
|
||||
final InputStream inputStream = context.getAssets().open(databaseName);
|
||||
final OutputStream output = new FileOutputStream(dbPath);
|
||||
|
||||
byte[] buffer = new byte[8192];
|
||||
int length;
|
||||
|
||||
while ((length = inputStream.read(buffer, 0, 8192)) > 0) {
|
||||
output.write(buffer, 0, length);
|
||||
}
|
||||
|
||||
output.flush();
|
||||
output.close();
|
||||
inputStream.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
Log.e("Failed to open file", e);
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// PRIVATE METHODS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void executePragmas(SQLiteDatabase db) {
|
||||
if (SQLiteUtils.FOREIGN_KEYS_SUPPORTED) {
|
||||
db.execSQL("PRAGMA foreign_keys=ON;");
|
||||
Log.i("Foreign Keys supported. Enabling foreign key features.");
|
||||
}
|
||||
}
|
||||
|
||||
private void executeCreateIndex(SQLiteDatabase db) {
|
||||
db.beginTransaction();
|
||||
try {
|
||||
for (TableInfo tableInfo : Cache.getTableInfos()) {
|
||||
String[] definitions = SQLiteUtils.createIndexDefinition(tableInfo);
|
||||
|
||||
for (String definition : definitions) {
|
||||
db.execSQL(definition);
|
||||
}
|
||||
}
|
||||
db.setTransactionSuccessful();
|
||||
}
|
||||
finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
private void executeCreate(SQLiteDatabase db) {
|
||||
db.beginTransaction();
|
||||
try {
|
||||
for (TableInfo tableInfo : Cache.getTableInfos()) {
|
||||
db.execSQL(SQLiteUtils.createTableDefinition(tableInfo));
|
||||
}
|
||||
db.setTransactionSuccessful();
|
||||
}
|
||||
finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean executeMigrations(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
boolean migrationExecuted = false;
|
||||
try {
|
||||
final List<String> files = Arrays.asList(Cache.getContext().getAssets().list(MIGRATION_PATH));
|
||||
Collections.sort(files, new NaturalOrderComparator());
|
||||
|
||||
db.beginTransaction();
|
||||
try {
|
||||
for (String file : files) {
|
||||
try {
|
||||
final int version = Integer.valueOf(file.replace(".sql", ""));
|
||||
|
||||
if (version > oldVersion && version <= newVersion) {
|
||||
executeSqlScript(db, file);
|
||||
migrationExecuted = true;
|
||||
|
||||
Log.i(file + " executed succesfully.");
|
||||
}
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
Log.w("Skipping invalidly named file: " + file, e);
|
||||
}
|
||||
}
|
||||
db.setTransactionSuccessful();
|
||||
}
|
||||
finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
Log.e("Failed to execute migrations.", e);
|
||||
}
|
||||
|
||||
return migrationExecuted;
|
||||
}
|
||||
|
||||
private void executeSqlScript(SQLiteDatabase db, String file) {
|
||||
|
||||
InputStream stream = null;
|
||||
|
||||
try {
|
||||
stream = Cache.getContext().getAssets().open(MIGRATION_PATH + "/" + file);
|
||||
|
||||
if (Configuration.SQL_PARSER_DELIMITED.equalsIgnoreCase(mSqlParser)) {
|
||||
executeDelimitedSqlScript(db, stream);
|
||||
|
||||
} else {
|
||||
executeLegacySqlScript(db, stream);
|
||||
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
Log.e("Failed to execute " + file, e);
|
||||
|
||||
} finally {
|
||||
IOUtils.closeQuietly(stream);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void executeDelimitedSqlScript(SQLiteDatabase db, InputStream stream) throws IOException {
|
||||
|
||||
List<String> commands = SqlParser.parse(stream);
|
||||
|
||||
for(String command : commands) {
|
||||
db.execSQL(command);
|
||||
}
|
||||
}
|
||||
|
||||
private void executeLegacySqlScript(SQLiteDatabase db, InputStream stream) throws IOException {
|
||||
|
||||
InputStreamReader reader = null;
|
||||
BufferedReader buffer = null;
|
||||
|
||||
try {
|
||||
reader = new InputStreamReader(stream);
|
||||
buffer = new BufferedReader(reader);
|
||||
String line = null;
|
||||
|
||||
while ((line = buffer.readLine()) != null) {
|
||||
line = line.replace(";", "").trim();
|
||||
if (!TextUtils.isEmpty(line)) {
|
||||
db.execSQL(line);
|
||||
}
|
||||
}
|
||||
|
||||
} finally {
|
||||
IOUtils.closeQuietly(buffer);
|
||||
IOUtils.closeQuietly(reader);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
314
app/src/main/java/com/activeandroid/Model.java
Normal file
@@ -0,0 +1,314 @@
|
||||
package com.activeandroid;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2010 Michael Pardo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
import com.activeandroid.query.Delete;
|
||||
import com.activeandroid.query.Select;
|
||||
import com.activeandroid.serializer.TypeSerializer;
|
||||
import com.activeandroid.util.Log;
|
||||
import com.activeandroid.util.ReflectionUtils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public abstract class Model {
|
||||
|
||||
/** Prime number used for hashcode() implementation. */
|
||||
private static final int HASH_PRIME = 739;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// PRIVATE MEMBERS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private Long mId = null;
|
||||
|
||||
private final TableInfo mTableInfo;
|
||||
private final String idName;
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// CONSTRUCTORS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public Model() {
|
||||
mTableInfo = Cache.getTableInfo(getClass());
|
||||
idName = mTableInfo.getIdName();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// PUBLIC METHODS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public final Long getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
public final void delete() {
|
||||
Cache.openDatabase().delete(mTableInfo.getTableName(), idName+"=?", new String[] { getId().toString() });
|
||||
Cache.removeEntity(this);
|
||||
}
|
||||
|
||||
public final Long save() {
|
||||
final SQLiteDatabase db = Cache.openDatabase();
|
||||
final ContentValues values = new ContentValues();
|
||||
|
||||
for (Field field : mTableInfo.getFields()) {
|
||||
final String fieldName = mTableInfo.getColumnName(field);
|
||||
Class<?> fieldType = field.getType();
|
||||
|
||||
field.setAccessible(true);
|
||||
|
||||
try {
|
||||
Object value = field.get(this);
|
||||
|
||||
if (value != null) {
|
||||
final TypeSerializer typeSerializer = Cache.getParserForType(fieldType);
|
||||
if (typeSerializer != null) {
|
||||
// serialize data
|
||||
value = typeSerializer.serialize(value);
|
||||
// set new object type
|
||||
if (value != null) {
|
||||
fieldType = value.getClass();
|
||||
// check that the serializer returned what it promised
|
||||
if (!fieldType.equals(typeSerializer.getSerializedType())) {
|
||||
Log.w(String.format("TypeSerializer returned wrong type: expected a %s but got a %s",
|
||||
typeSerializer.getSerializedType(), fieldType));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Find a smarter way to do this? This if block is necessary because we
|
||||
// can't know the type until runtime.
|
||||
if (value == null) {
|
||||
values.putNull(fieldName);
|
||||
}
|
||||
else if (fieldType.equals(Byte.class) || fieldType.equals(byte.class)) {
|
||||
values.put(fieldName, (Byte) value);
|
||||
}
|
||||
else if (fieldType.equals(Short.class) || fieldType.equals(short.class)) {
|
||||
values.put(fieldName, (Short) value);
|
||||
}
|
||||
else if (fieldType.equals(Integer.class) || fieldType.equals(int.class)) {
|
||||
values.put(fieldName, (Integer) value);
|
||||
}
|
||||
else if (fieldType.equals(Long.class) || fieldType.equals(long.class)) {
|
||||
values.put(fieldName, (Long) value);
|
||||
}
|
||||
else if (fieldType.equals(Float.class) || fieldType.equals(float.class)) {
|
||||
values.put(fieldName, (Float) value);
|
||||
}
|
||||
else if (fieldType.equals(Double.class) || fieldType.equals(double.class)) {
|
||||
values.put(fieldName, (Double) value);
|
||||
}
|
||||
else if (fieldType.equals(Boolean.class) || fieldType.equals(boolean.class)) {
|
||||
values.put(fieldName, (Boolean) value);
|
||||
}
|
||||
else if (fieldType.equals(Character.class) || fieldType.equals(char.class)) {
|
||||
values.put(fieldName, value.toString());
|
||||
}
|
||||
else if (fieldType.equals(String.class)) {
|
||||
values.put(fieldName, value.toString());
|
||||
}
|
||||
else if (fieldType.equals(Byte[].class) || fieldType.equals(byte[].class)) {
|
||||
values.put(fieldName, (byte[]) value);
|
||||
}
|
||||
else if (ReflectionUtils.isModel(fieldType)) {
|
||||
values.put(fieldName, ((Model) value).getId());
|
||||
}
|
||||
else if (ReflectionUtils.isSubclassOf(fieldType, Enum.class)) {
|
||||
values.put(fieldName, ((Enum<?>) value).name());
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
Log.e(e.getClass().getName(), e);
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
Log.e(e.getClass().getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
if (mId == null) {
|
||||
mId = db.insert(mTableInfo.getTableName(), null, values);
|
||||
}
|
||||
else {
|
||||
db.update(mTableInfo.getTableName(), values, idName+"=" + mId, null);
|
||||
}
|
||||
|
||||
return mId;
|
||||
}
|
||||
|
||||
// Convenience methods
|
||||
|
||||
public static void delete(Class<? extends Model> type, long id) {
|
||||
TableInfo tableInfo = Cache.getTableInfo(type);
|
||||
new Delete().from(type).where(tableInfo.getIdName()+"=?", id).execute();
|
||||
}
|
||||
|
||||
public static <T extends Model> T load(Class<T> type, long id) {
|
||||
TableInfo tableInfo = Cache.getTableInfo(type);
|
||||
return (T) new Select().from(type).where(tableInfo.getIdName()+"=?", id).executeSingle();
|
||||
}
|
||||
|
||||
// Model population
|
||||
|
||||
public final void loadFromCursor(Cursor cursor) {
|
||||
/**
|
||||
* Obtain the columns ordered to fix issue #106 (https://github.com/pardom/ActiveAndroid/issues/106)
|
||||
* when the cursor have multiple columns with same name obtained from join tables.
|
||||
*/
|
||||
List<String> columnsOrdered = new ArrayList<String>(Arrays.asList(cursor.getColumnNames()));
|
||||
for (Field field : mTableInfo.getFields()) {
|
||||
final String fieldName = mTableInfo.getColumnName(field);
|
||||
Class<?> fieldType = field.getType();
|
||||
final int columnIndex = columnsOrdered.indexOf(fieldName);
|
||||
|
||||
if (columnIndex < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
field.setAccessible(true);
|
||||
|
||||
try {
|
||||
boolean columnIsNull = cursor.isNull(columnIndex);
|
||||
TypeSerializer typeSerializer = Cache.getParserForType(fieldType);
|
||||
Object value = null;
|
||||
|
||||
if (typeSerializer != null) {
|
||||
fieldType = typeSerializer.getSerializedType();
|
||||
}
|
||||
|
||||
// TODO: Find a smarter way to do this? This if block is necessary because we
|
||||
// can't know the type until runtime.
|
||||
if (columnIsNull) {
|
||||
field = null;
|
||||
}
|
||||
else if (fieldType.equals(Byte.class) || fieldType.equals(byte.class)) {
|
||||
value = cursor.getInt(columnIndex);
|
||||
}
|
||||
else if (fieldType.equals(Short.class) || fieldType.equals(short.class)) {
|
||||
value = cursor.getInt(columnIndex);
|
||||
}
|
||||
else if (fieldType.equals(Integer.class) || fieldType.equals(int.class)) {
|
||||
value = cursor.getInt(columnIndex);
|
||||
}
|
||||
else if (fieldType.equals(Long.class) || fieldType.equals(long.class)) {
|
||||
value = cursor.getLong(columnIndex);
|
||||
}
|
||||
else if (fieldType.equals(Float.class) || fieldType.equals(float.class)) {
|
||||
value = cursor.getFloat(columnIndex);
|
||||
}
|
||||
else if (fieldType.equals(Double.class) || fieldType.equals(double.class)) {
|
||||
value = cursor.getDouble(columnIndex);
|
||||
}
|
||||
else if (fieldType.equals(Boolean.class) || fieldType.equals(boolean.class)) {
|
||||
value = cursor.getInt(columnIndex) != 0;
|
||||
}
|
||||
else if (fieldType.equals(Character.class) || fieldType.equals(char.class)) {
|
||||
value = cursor.getString(columnIndex).charAt(0);
|
||||
}
|
||||
else if (fieldType.equals(String.class)) {
|
||||
value = cursor.getString(columnIndex);
|
||||
}
|
||||
else if (fieldType.equals(Byte[].class) || fieldType.equals(byte[].class)) {
|
||||
value = cursor.getBlob(columnIndex);
|
||||
}
|
||||
else if (ReflectionUtils.isModel(fieldType)) {
|
||||
final long entityId = cursor.getLong(columnIndex);
|
||||
final Class<? extends Model> entityType = (Class<? extends Model>) fieldType;
|
||||
|
||||
Model entity = Cache.getEntity(entityType, entityId);
|
||||
if (entity == null) {
|
||||
entity = new Select().from(entityType).where(idName+"=?", entityId).executeSingle();
|
||||
}
|
||||
|
||||
value = entity;
|
||||
}
|
||||
else if (ReflectionUtils.isSubclassOf(fieldType, Enum.class)) {
|
||||
@SuppressWarnings("rawtypes")
|
||||
final Class<? extends Enum> enumType = (Class<? extends Enum>) fieldType;
|
||||
value = Enum.valueOf(enumType, cursor.getString(columnIndex));
|
||||
}
|
||||
|
||||
// Use a deserializer if one is available
|
||||
if (typeSerializer != null && !columnIsNull) {
|
||||
value = typeSerializer.deserialize(value);
|
||||
}
|
||||
|
||||
// Set the field value
|
||||
if (value != null) {
|
||||
field.set(this, value);
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
Log.e(e.getClass().getName(), e);
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
Log.e(e.getClass().getName(), e);
|
||||
}
|
||||
catch (SecurityException e) {
|
||||
Log.e(e.getClass().getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
if (mId != null) {
|
||||
Cache.addEntity(this);
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTECTED METHODS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
protected final <T extends Model> List<T> getMany(Class<T> type, String foreignKey) {
|
||||
return new Select().from(type).where(Cache.getTableName(type) + "." + foreignKey + "=?", getId()).execute();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// OVERRIDEN METHODS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mTableInfo.getTableName() + "@" + getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof Model && this.mId != null) {
|
||||
final Model other = (Model) obj;
|
||||
|
||||
return this.mId.equals(other.mId)
|
||||
&& (this.mTableInfo.getTableName().equals(other.mTableInfo.getTableName()));
|
||||
} else {
|
||||
return this == obj;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = HASH_PRIME;
|
||||
hash += HASH_PRIME * (mId == null ? super.hashCode() : mId.hashCode()); //if id is null, use Object.hashCode()
|
||||
hash += HASH_PRIME * mTableInfo.getTableName().hashCode();
|
||||
return hash; //To change body of generated methods, choose Tools | Templates.
|
||||
}
|
||||
}
|
||||
209
app/src/main/java/com/activeandroid/ModelInfo.java
Normal file
@@ -0,0 +1,209 @@
|
||||
package com.activeandroid;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2010 Michael Pardo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collection;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.activeandroid.serializer.CalendarSerializer;
|
||||
import com.activeandroid.serializer.SqlDateSerializer;
|
||||
import com.activeandroid.serializer.TypeSerializer;
|
||||
import com.activeandroid.serializer.UtilDateSerializer;
|
||||
import com.activeandroid.serializer.FileSerializer;
|
||||
import com.activeandroid.util.Log;
|
||||
import com.activeandroid.util.ReflectionUtils;
|
||||
import dalvik.system.DexFile;
|
||||
|
||||
final class ModelInfo {
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// PRIVATE METHODS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private Map<Class<? extends Model>, TableInfo> mTableInfos = new HashMap<Class<? extends Model>, TableInfo>();
|
||||
private Map<Class<?>, TypeSerializer> mTypeSerializers = new HashMap<Class<?>, TypeSerializer>() {
|
||||
{
|
||||
put(Calendar.class, new CalendarSerializer());
|
||||
put(java.sql.Date.class, new SqlDateSerializer());
|
||||
put(java.util.Date.class, new UtilDateSerializer());
|
||||
put(java.io.File.class, new FileSerializer());
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// CONSTRUCTORS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public ModelInfo(Configuration configuration) {
|
||||
if (!loadModelFromMetaData(configuration)) {
|
||||
try {
|
||||
scanForModel(configuration.getContext());
|
||||
}
|
||||
catch (IOException e) {
|
||||
Log.e("Couldn't open source path.", e);
|
||||
}
|
||||
}
|
||||
|
||||
Log.i("ModelInfo loaded.");
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// PUBLIC METHODS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public Collection<TableInfo> getTableInfos() {
|
||||
return mTableInfos.values();
|
||||
}
|
||||
|
||||
public TableInfo getTableInfo(Class<? extends Model> type) {
|
||||
return mTableInfos.get(type);
|
||||
}
|
||||
|
||||
public TypeSerializer getTypeSerializer(Class<?> type) {
|
||||
return mTypeSerializers.get(type);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// PRIVATE METHODS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private boolean loadModelFromMetaData(Configuration configuration) {
|
||||
if (!configuration.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final List<Class<? extends Model>> models = configuration.getModelClasses();
|
||||
if (models != null) {
|
||||
for (Class<? extends Model> model : models) {
|
||||
mTableInfos.put(model, new TableInfo(model));
|
||||
}
|
||||
}
|
||||
|
||||
final List<Class<? extends TypeSerializer>> typeSerializers = configuration.getTypeSerializers();
|
||||
if (typeSerializers != null) {
|
||||
for (Class<? extends TypeSerializer> typeSerializer : typeSerializers) {
|
||||
try {
|
||||
TypeSerializer instance = typeSerializer.newInstance();
|
||||
mTypeSerializers.put(instance.getDeserializedType(), instance);
|
||||
}
|
||||
catch (InstantiationException e) {
|
||||
Log.e("Couldn't instantiate TypeSerializer.", e);
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
Log.e("IllegalAccessException", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void scanForModel(Context context) throws IOException {
|
||||
String packageName = context.getPackageName();
|
||||
String sourcePath = context.getApplicationInfo().sourceDir;
|
||||
List<String> paths = new ArrayList<String>();
|
||||
|
||||
if (sourcePath != null && !(new File(sourcePath).isDirectory())) {
|
||||
DexFile dexfile = new DexFile(sourcePath);
|
||||
Enumeration<String> entries = dexfile.entries();
|
||||
|
||||
while (entries.hasMoreElements()) {
|
||||
paths.add(entries.nextElement());
|
||||
}
|
||||
}
|
||||
// Robolectric fallback
|
||||
else {
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
Enumeration<URL> resources = classLoader.getResources("");
|
||||
|
||||
while (resources.hasMoreElements()) {
|
||||
String path = resources.nextElement().getFile();
|
||||
if (path.contains("bin") || path.contains("classes")) {
|
||||
paths.add(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (String path : paths) {
|
||||
File file = new File(path);
|
||||
scanForModelClasses(file, packageName, context.getClassLoader());
|
||||
}
|
||||
}
|
||||
|
||||
private void scanForModelClasses(File path, String packageName, ClassLoader classLoader) {
|
||||
if (path.isDirectory()) {
|
||||
for (File file : path.listFiles()) {
|
||||
scanForModelClasses(file, packageName, classLoader);
|
||||
}
|
||||
}
|
||||
else {
|
||||
String className = path.getName();
|
||||
|
||||
// Robolectric fallback
|
||||
if (!path.getPath().equals(className)) {
|
||||
className = path.getPath();
|
||||
|
||||
if (className.endsWith(".class")) {
|
||||
className = className.substring(0, className.length() - 6);
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
|
||||
className = className.replace(System.getProperty("file.separator"), ".");
|
||||
|
||||
int packageNameIndex = className.lastIndexOf(packageName);
|
||||
if (packageNameIndex < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
className = className.substring(packageNameIndex);
|
||||
}
|
||||
|
||||
try {
|
||||
Class<?> discoveredClass = Class.forName(className, false, classLoader);
|
||||
if (ReflectionUtils.isModel(discoveredClass)) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends Model> modelClass = (Class<? extends Model>) discoveredClass;
|
||||
mTableInfos.put(modelClass, new TableInfo(modelClass));
|
||||
}
|
||||
else if (ReflectionUtils.isTypeSerializer(discoveredClass)) {
|
||||
TypeSerializer instance = (TypeSerializer) discoveredClass.newInstance();
|
||||
mTypeSerializers.put(instance.getDeserializedType(), instance);
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
Log.e("Couldn't create class.", e);
|
||||
}
|
||||
catch (InstantiationException e) {
|
||||
Log.e("Couldn't instantiate TypeSerializer.", e);
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
Log.e("IllegalAccessException", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
124
app/src/main/java/com/activeandroid/TableInfo.java
Normal file
@@ -0,0 +1,124 @@
|
||||
package com.activeandroid;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2010 Michael Pardo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.activeandroid.annotation.Column;
|
||||
import com.activeandroid.annotation.Table;
|
||||
import com.activeandroid.util.ReflectionUtils;
|
||||
|
||||
public final class TableInfo {
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// PRIVATE MEMBERS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private Class<? extends Model> mType;
|
||||
private String mTableName;
|
||||
private String mIdName = Table.DEFAULT_ID_NAME;
|
||||
|
||||
private Map<Field, String> mColumnNames = new LinkedHashMap<Field, String>();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// CONSTRUCTORS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public TableInfo(Class<? extends Model> type) {
|
||||
mType = type;
|
||||
|
||||
final Table tableAnnotation = type.getAnnotation(Table.class);
|
||||
|
||||
if (tableAnnotation != null) {
|
||||
mTableName = tableAnnotation.name();
|
||||
mIdName = tableAnnotation.id();
|
||||
}
|
||||
else {
|
||||
mTableName = type.getSimpleName();
|
||||
}
|
||||
|
||||
// Manually add the id column since it is not declared like the other columns.
|
||||
Field idField = getIdField(type);
|
||||
mColumnNames.put(idField, mIdName);
|
||||
|
||||
List<Field> fields = new LinkedList<Field>(ReflectionUtils.getDeclaredColumnFields(type));
|
||||
Collections.reverse(fields);
|
||||
|
||||
for (Field field : fields) {
|
||||
if (field.isAnnotationPresent(Column.class)) {
|
||||
final Column columnAnnotation = field.getAnnotation(Column.class);
|
||||
String columnName = columnAnnotation.name();
|
||||
if (TextUtils.isEmpty(columnName)) {
|
||||
columnName = field.getName();
|
||||
}
|
||||
|
||||
mColumnNames.put(field, columnName);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// PUBLIC METHODS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public Class<? extends Model> getType() {
|
||||
return mType;
|
||||
}
|
||||
|
||||
public String getTableName() {
|
||||
return mTableName;
|
||||
}
|
||||
|
||||
public String getIdName() {
|
||||
return mIdName;
|
||||
}
|
||||
|
||||
public Collection<Field> getFields() {
|
||||
return mColumnNames.keySet();
|
||||
}
|
||||
|
||||
public String getColumnName(Field field) {
|
||||
return mColumnNames.get(field);
|
||||
}
|
||||
|
||||
|
||||
private Field getIdField(Class<?> type) {
|
||||
if (type.equals(Model.class)) {
|
||||
try {
|
||||
return type.getDeclaredField("mId");
|
||||
}
|
||||
catch (NoSuchFieldException e) {
|
||||
Log.e("Impossible!", e.toString());
|
||||
}
|
||||
}
|
||||
else if (type.getSuperclass() != null) {
|
||||
return getIdField(type.getSuperclass());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
110
app/src/main/java/com/activeandroid/annotation/Column.java
Normal file
@@ -0,0 +1,110 @@
|
||||
package com.activeandroid.annotation;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2010 Michael Pardo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Column {
|
||||
public enum ConflictAction {
|
||||
ROLLBACK, ABORT, FAIL, IGNORE, REPLACE
|
||||
}
|
||||
|
||||
public enum ForeignKeyAction {
|
||||
SET_NULL, SET_DEFAULT, CASCADE, RESTRICT, NO_ACTION
|
||||
}
|
||||
|
||||
public String name() default "";
|
||||
|
||||
public int length() default -1;
|
||||
|
||||
public boolean notNull() default false;
|
||||
|
||||
public ConflictAction onNullConflict() default ConflictAction.FAIL;
|
||||
|
||||
public ForeignKeyAction onDelete() default ForeignKeyAction.NO_ACTION;
|
||||
|
||||
public ForeignKeyAction onUpdate() default ForeignKeyAction.NO_ACTION;
|
||||
|
||||
public boolean unique() default false;
|
||||
|
||||
public ConflictAction onUniqueConflict() default ConflictAction.FAIL;
|
||||
|
||||
/*
|
||||
* If set uniqueGroups = {"group_name"}, we will create a table constraint with group.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* @Table(name = "table_name")
|
||||
* public class Table extends Model {
|
||||
* @Column(name = "member1", uniqueGroups = {"group1"}, onUniqueConflicts = {ConflictAction.FAIL})
|
||||
* public String member1;
|
||||
*
|
||||
* @Column(name = "member2", uniqueGroups = {"group1", "group2"}, onUniqueConflicts = {ConflictAction.FAIL, ConflictAction.IGNORE})
|
||||
* public String member2;
|
||||
*
|
||||
* @Column(name = "member3", uniqueGroups = {"group2"}, onUniqueConflicts = {ConflictAction.IGNORE})
|
||||
* public String member3;
|
||||
* }
|
||||
*
|
||||
* CREATE TABLE table_name (..., UNIQUE (member1, member2) ON CONFLICT FAIL, UNIQUE (member2, member3) ON CONFLICT IGNORE)
|
||||
*/
|
||||
public String[] uniqueGroups() default {};
|
||||
|
||||
public ConflictAction[] onUniqueConflicts() default {};
|
||||
|
||||
/*
|
||||
* If set index = true, we will create a index with single column.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* @Table(name = "table_name")
|
||||
* public class Table extends Model {
|
||||
* @Column(name = "member", index = true)
|
||||
* public String member;
|
||||
* }
|
||||
*
|
||||
* Execute CREATE INDEX index_table_name_member on table_name(member)
|
||||
*/
|
||||
public boolean index() default false;
|
||||
|
||||
/*
|
||||
* If set indexGroups = {"group_name"}, we will create a index with group.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* @Table(name = "table_name")
|
||||
* public class Table extends Model {
|
||||
* @Column(name = "member1", indexGroups = {"group1"})
|
||||
* public String member1;
|
||||
*
|
||||
* @Column(name = "member2", indexGroups = {"group1", "group2"})
|
||||
* public String member2;
|
||||
*
|
||||
* @Column(name = "member3", indexGroups = {"group2"})
|
||||
* public String member3;
|
||||
* }
|
||||
*
|
||||
* Execute CREATE INDEX index_table_name_group1 on table_name(member1, member2)
|
||||
* Execute CREATE INDEX index_table_name_group2 on table_name(member2, member3)
|
||||
*/
|
||||
public String[] indexGroups() default {};
|
||||
}
|
||||
31
app/src/main/java/com/activeandroid/annotation/Table.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package com.activeandroid.annotation;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2010 Michael Pardo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Table {
|
||||
|
||||
public static final String DEFAULT_ID_NAME = "Id";
|
||||
public String name();
|
||||
public String id() default DEFAULT_ID_NAME;
|
||||
}
|
||||
33
app/src/main/java/com/activeandroid/query/Delete.java
Normal file
@@ -0,0 +1,33 @@
|
||||
package com.activeandroid.query;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2010 Michael Pardo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import com.activeandroid.Model;
|
||||
|
||||
public final class Delete implements Sqlable {
|
||||
public Delete() {
|
||||
}
|
||||
|
||||
public From from(Class<? extends Model> table) {
|
||||
return new From(table, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toSql() {
|
||||
return "DELETE ";
|
||||
}
|
||||
}
|
||||
344
app/src/main/java/com/activeandroid/query/From.java
Normal file
@@ -0,0 +1,344 @@
|
||||
package com.activeandroid.query;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2010 Michael Pardo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.activeandroid.Cache;
|
||||
import com.activeandroid.Model;
|
||||
import com.activeandroid.query.Join.JoinType;
|
||||
import com.activeandroid.util.Log;
|
||||
import com.activeandroid.util.SQLiteUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public final class From implements Sqlable {
|
||||
private Sqlable mQueryBase;
|
||||
|
||||
private Class<? extends Model> mType;
|
||||
private String mAlias;
|
||||
private List<Join> mJoins;
|
||||
private final StringBuilder mWhere = new StringBuilder();
|
||||
private String mGroupBy;
|
||||
private String mHaving;
|
||||
private String mOrderBy;
|
||||
private String mLimit;
|
||||
private String mOffset;
|
||||
|
||||
private List<Object> mArguments;
|
||||
|
||||
public From(Class<? extends Model> table, Sqlable queryBase) {
|
||||
mType = table;
|
||||
mJoins = new ArrayList<Join>();
|
||||
mQueryBase = queryBase;
|
||||
|
||||
mJoins = new ArrayList<Join>();
|
||||
mArguments = new ArrayList<Object>();
|
||||
}
|
||||
|
||||
public From as(String alias) {
|
||||
mAlias = alias;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Join join(Class<? extends Model> table) {
|
||||
Join join = new Join(this, table, null);
|
||||
mJoins.add(join);
|
||||
return join;
|
||||
}
|
||||
|
||||
public Join leftJoin(Class<? extends Model> table) {
|
||||
Join join = new Join(this, table, JoinType.LEFT);
|
||||
mJoins.add(join);
|
||||
return join;
|
||||
}
|
||||
|
||||
public Join outerJoin(Class<? extends Model> table) {
|
||||
Join join = new Join(this, table, JoinType.OUTER);
|
||||
mJoins.add(join);
|
||||
return join;
|
||||
}
|
||||
|
||||
public Join innerJoin(Class<? extends Model> table) {
|
||||
Join join = new Join(this, table, JoinType.INNER);
|
||||
mJoins.add(join);
|
||||
return join;
|
||||
}
|
||||
|
||||
public Join crossJoin(Class<? extends Model> table) {
|
||||
Join join = new Join(this, table, JoinType.CROSS);
|
||||
mJoins.add(join);
|
||||
return join;
|
||||
}
|
||||
|
||||
public From where(String clause) {
|
||||
// Chain conditions if a previous condition exists.
|
||||
if (mWhere.length() > 0) {
|
||||
mWhere.append(" AND ");
|
||||
}
|
||||
mWhere.append(clause);
|
||||
return this;
|
||||
}
|
||||
|
||||
public From where(String clause, Object... args) {
|
||||
where(clause).addArguments(args);
|
||||
return this;
|
||||
}
|
||||
|
||||
public From and(String clause) {
|
||||
return where(clause);
|
||||
}
|
||||
|
||||
public From and(String clause, Object... args) {
|
||||
return where(clause, args);
|
||||
}
|
||||
|
||||
public From or(String clause) {
|
||||
if (mWhere.length() > 0) {
|
||||
mWhere.append(" OR ");
|
||||
}
|
||||
mWhere.append(clause);
|
||||
return this;
|
||||
}
|
||||
|
||||
public From or(String clause, Object... args) {
|
||||
or(clause).addArguments(args);
|
||||
return this;
|
||||
}
|
||||
|
||||
public From groupBy(String groupBy) {
|
||||
mGroupBy = groupBy;
|
||||
return this;
|
||||
}
|
||||
|
||||
public From having(String having) {
|
||||
mHaving = having;
|
||||
return this;
|
||||
}
|
||||
|
||||
public From orderBy(String orderBy) {
|
||||
mOrderBy = orderBy;
|
||||
return this;
|
||||
}
|
||||
|
||||
public From limit(int limit) {
|
||||
return limit(String.valueOf(limit));
|
||||
}
|
||||
|
||||
public From limit(String limit) {
|
||||
mLimit = limit;
|
||||
return this;
|
||||
}
|
||||
|
||||
public From offset(int offset) {
|
||||
return offset(String.valueOf(offset));
|
||||
}
|
||||
|
||||
public From offset(String offset) {
|
||||
mOffset = offset;
|
||||
return this;
|
||||
}
|
||||
|
||||
void addArguments(Object[] args) {
|
||||
for(Object arg : args) {
|
||||
if (arg.getClass() == boolean.class || arg.getClass() == Boolean.class) {
|
||||
arg = (arg.equals(true) ? 1 : 0);
|
||||
}
|
||||
mArguments.add(arg);
|
||||
}
|
||||
}
|
||||
|
||||
private void addFrom(final StringBuilder sql) {
|
||||
sql.append("FROM ");
|
||||
sql.append(Cache.getTableName(mType)).append(" ");
|
||||
|
||||
if (mAlias != null) {
|
||||
sql.append("AS ");
|
||||
sql.append(mAlias);
|
||||
sql.append(" ");
|
||||
}
|
||||
}
|
||||
|
||||
private void addJoins(final StringBuilder sql) {
|
||||
for (final Join join : mJoins) {
|
||||
sql.append(join.toSql());
|
||||
}
|
||||
}
|
||||
|
||||
private void addWhere(final StringBuilder sql) {
|
||||
if (mWhere.length() > 0) {
|
||||
sql.append("WHERE ");
|
||||
sql.append(mWhere);
|
||||
sql.append(" ");
|
||||
}
|
||||
}
|
||||
|
||||
private void addGroupBy(final StringBuilder sql) {
|
||||
if (mGroupBy != null) {
|
||||
sql.append("GROUP BY ");
|
||||
sql.append(mGroupBy);
|
||||
sql.append(" ");
|
||||
}
|
||||
}
|
||||
|
||||
private void addHaving(final StringBuilder sql) {
|
||||
if (mHaving != null) {
|
||||
sql.append("HAVING ");
|
||||
sql.append(mHaving);
|
||||
sql.append(" ");
|
||||
}
|
||||
}
|
||||
|
||||
private void addOrderBy(final StringBuilder sql) {
|
||||
if (mOrderBy != null) {
|
||||
sql.append("ORDER BY ");
|
||||
sql.append(mOrderBy);
|
||||
sql.append(" ");
|
||||
}
|
||||
}
|
||||
|
||||
private void addLimit(final StringBuilder sql) {
|
||||
if (mLimit != null) {
|
||||
sql.append("LIMIT ");
|
||||
sql.append(mLimit);
|
||||
sql.append(" ");
|
||||
}
|
||||
}
|
||||
|
||||
private void addOffset(final StringBuilder sql) {
|
||||
if (mOffset != null) {
|
||||
sql.append("OFFSET ");
|
||||
sql.append(mOffset);
|
||||
sql.append(" ");
|
||||
}
|
||||
}
|
||||
|
||||
private String sqlString(final StringBuilder sql) {
|
||||
|
||||
final String sqlString = sql.toString().trim();
|
||||
|
||||
// Don't waste time building the string
|
||||
// unless we're going to log it.
|
||||
if (Log.isEnabled()) {
|
||||
Log.v(sqlString + " " + TextUtils.join(",", getArguments()));
|
||||
}
|
||||
|
||||
return sqlString;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toSql() {
|
||||
final StringBuilder sql = new StringBuilder();
|
||||
sql.append(mQueryBase.toSql());
|
||||
|
||||
addFrom(sql);
|
||||
addJoins(sql);
|
||||
addWhere(sql);
|
||||
addGroupBy(sql);
|
||||
addHaving(sql);
|
||||
addOrderBy(sql);
|
||||
addLimit(sql);
|
||||
addOffset(sql);
|
||||
|
||||
return sqlString(sql);
|
||||
}
|
||||
|
||||
public String toExistsSql() {
|
||||
|
||||
final StringBuilder sql = new StringBuilder();
|
||||
sql.append("SELECT EXISTS(SELECT 1 ");
|
||||
|
||||
addFrom(sql);
|
||||
addJoins(sql);
|
||||
addWhere(sql);
|
||||
addGroupBy(sql);
|
||||
addHaving(sql);
|
||||
addLimit(sql);
|
||||
addOffset(sql);
|
||||
|
||||
sql.append(")");
|
||||
|
||||
return sqlString(sql);
|
||||
}
|
||||
|
||||
public String toCountSql() {
|
||||
|
||||
final StringBuilder sql = new StringBuilder();
|
||||
sql.append("SELECT COUNT(*) ");
|
||||
|
||||
addFrom(sql);
|
||||
addJoins(sql);
|
||||
addWhere(sql);
|
||||
addGroupBy(sql);
|
||||
addHaving(sql);
|
||||
addLimit(sql);
|
||||
addOffset(sql);
|
||||
|
||||
return sqlString(sql);
|
||||
}
|
||||
|
||||
public <T extends Model> List<T> execute() {
|
||||
if (mQueryBase instanceof Select) {
|
||||
return SQLiteUtils.rawQuery(mType, toSql(), getArguments());
|
||||
|
||||
} else {
|
||||
SQLiteUtils.execSql(toSql(), getArguments());
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public <T extends Model> T executeSingle() {
|
||||
if (mQueryBase instanceof Select) {
|
||||
limit(1);
|
||||
return (T) SQLiteUtils.rawQuerySingle(mType, toSql(), getArguments());
|
||||
|
||||
} else {
|
||||
limit(1);
|
||||
SQLiteUtils.rawQuerySingle(mType, toSql(), getArguments()).delete();
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a value indicating whether the query returns any rows.
|
||||
* @return <code>true</code> if the query returns at least one row; otherwise, <code>false</code>.
|
||||
*/
|
||||
public boolean exists() {
|
||||
return SQLiteUtils.intQuery(toExistsSql(), getArguments()) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of rows returned by the query.
|
||||
*/
|
||||
public int count() {
|
||||
return SQLiteUtils.intQuery(toCountSql(), getArguments());
|
||||
}
|
||||
|
||||
public String[] getArguments() {
|
||||
final int size = mArguments.size();
|
||||
final String[] args = new String[size];
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
args[i] = mArguments.get(i).toString();
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
}
|
||||
94
app/src/main/java/com/activeandroid/query/Join.java
Normal file
@@ -0,0 +1,94 @@
|
||||
package com.activeandroid.query;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2010 Michael Pardo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.activeandroid.Cache;
|
||||
import com.activeandroid.Model;
|
||||
|
||||
public final class Join implements Sqlable {
|
||||
static enum JoinType {
|
||||
LEFT, OUTER, INNER, CROSS
|
||||
}
|
||||
|
||||
private From mFrom;
|
||||
private Class<? extends Model> mType;
|
||||
private String mAlias;
|
||||
private JoinType mJoinType;
|
||||
private String mOn;
|
||||
private String[] mUsing;
|
||||
|
||||
Join(From from, Class<? extends Model> table, JoinType joinType) {
|
||||
mFrom = from;
|
||||
mType = table;
|
||||
mJoinType = joinType;
|
||||
}
|
||||
|
||||
public Join as(String alias) {
|
||||
mAlias = alias;
|
||||
return this;
|
||||
}
|
||||
|
||||
public From on(String on) {
|
||||
mOn = on;
|
||||
return mFrom;
|
||||
}
|
||||
|
||||
public From on(String on, Object... args) {
|
||||
mOn = on;
|
||||
mFrom.addArguments(args);
|
||||
return mFrom;
|
||||
}
|
||||
|
||||
public From using(String... columns) {
|
||||
mUsing = columns;
|
||||
return mFrom;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toSql() {
|
||||
StringBuilder sql = new StringBuilder();
|
||||
|
||||
if (mJoinType != null) {
|
||||
sql.append(mJoinType.toString()).append(" ");
|
||||
}
|
||||
|
||||
sql.append("JOIN ");
|
||||
sql.append(Cache.getTableName(mType));
|
||||
sql.append(" ");
|
||||
|
||||
if (mAlias != null) {
|
||||
sql.append("AS ");
|
||||
sql.append(mAlias);
|
||||
sql.append(" ");
|
||||
}
|
||||
|
||||
if (mOn != null) {
|
||||
sql.append("ON ");
|
||||
sql.append(mOn);
|
||||
sql.append(" ");
|
||||
}
|
||||
else if (mUsing != null) {
|
||||
sql.append("USING (");
|
||||
sql.append(TextUtils.join(", ", mUsing));
|
||||
sql.append(") ");
|
||||
}
|
||||
|
||||
return sql.toString();
|
||||
}
|
||||
}
|
||||
93
app/src/main/java/com/activeandroid/query/Select.java
Normal file
@@ -0,0 +1,93 @@
|
||||
package com.activeandroid.query;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2010 Michael Pardo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.activeandroid.Model;
|
||||
|
||||
public final class Select implements Sqlable {
|
||||
private String[] mColumns;
|
||||
private boolean mDistinct = false;
|
||||
private boolean mAll = false;
|
||||
|
||||
public Select() {
|
||||
}
|
||||
|
||||
public Select(String... columns) {
|
||||
mColumns = columns;
|
||||
}
|
||||
|
||||
public Select(Column... columns) {
|
||||
final int size = columns.length;
|
||||
mColumns = new String[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
mColumns[i] = columns[i].name + " AS " + columns[i].alias;
|
||||
}
|
||||
}
|
||||
|
||||
public Select distinct() {
|
||||
mDistinct = true;
|
||||
mAll = false;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Select all() {
|
||||
mDistinct = false;
|
||||
mAll = true;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public From from(Class<? extends Model> table) {
|
||||
return new From(table, this);
|
||||
}
|
||||
|
||||
public static class Column {
|
||||
String name;
|
||||
String alias;
|
||||
|
||||
public Column(String name, String alias) {
|
||||
this.name = name;
|
||||
this.alias = alias;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toSql() {
|
||||
StringBuilder sql = new StringBuilder();
|
||||
|
||||
sql.append("SELECT ");
|
||||
|
||||
if (mDistinct) {
|
||||
sql.append("DISTINCT ");
|
||||
}
|
||||
else if (mAll) {
|
||||
sql.append("ALL ");
|
||||
}
|
||||
|
||||
if (mColumns != null && mColumns.length > 0) {
|
||||
sql.append(TextUtils.join(", ", mColumns) + " ");
|
||||
}
|
||||
else {
|
||||
sql.append("* ");
|
||||
}
|
||||
|
||||
return sql.toString();
|
||||
}
|
||||
}
|
||||
103
app/src/main/java/com/activeandroid/query/Set.java
Normal file
@@ -0,0 +1,103 @@
|
||||
package com.activeandroid.query;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2010 Michael Pardo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import com.activeandroid.util.SQLiteUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public final class Set implements Sqlable {
|
||||
private Update mUpdate;
|
||||
|
||||
private String mSet;
|
||||
private String mWhere;
|
||||
|
||||
private List<Object> mSetArguments;
|
||||
private List<Object> mWhereArguments;
|
||||
|
||||
public Set(Update queryBase, String set) {
|
||||
mUpdate = queryBase;
|
||||
mSet = set;
|
||||
|
||||
mSetArguments = new ArrayList<Object>();
|
||||
mWhereArguments = new ArrayList<Object>();
|
||||
}
|
||||
|
||||
public Set(Update queryBase, String set, Object... args) {
|
||||
mUpdate = queryBase;
|
||||
mSet = set;
|
||||
|
||||
mSetArguments = new ArrayList<Object>();
|
||||
mWhereArguments = new ArrayList<Object>();
|
||||
|
||||
mSetArguments.addAll(Arrays.asList(args));
|
||||
}
|
||||
|
||||
public Set where(String where) {
|
||||
mWhere = where;
|
||||
mWhereArguments.clear();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Set where(String where, Object... args) {
|
||||
mWhere = where;
|
||||
mWhereArguments.clear();
|
||||
mWhereArguments.addAll(Arrays.asList(args));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toSql() {
|
||||
StringBuilder sql = new StringBuilder();
|
||||
sql.append(mUpdate.toSql());
|
||||
sql.append("SET ");
|
||||
sql.append(mSet);
|
||||
sql.append(" ");
|
||||
|
||||
if (mWhere != null) {
|
||||
sql.append("WHERE ");
|
||||
sql.append(mWhere);
|
||||
sql.append(" ");
|
||||
}
|
||||
|
||||
return sql.toString();
|
||||
}
|
||||
|
||||
public void execute() {
|
||||
SQLiteUtils.execSql(toSql(), getArguments());
|
||||
}
|
||||
|
||||
public String[] getArguments() {
|
||||
final int setSize = mSetArguments.size();
|
||||
final int whereSize = mWhereArguments.size();
|
||||
final String[] args = new String[setSize + whereSize];
|
||||
|
||||
for (int i = 0; i < setSize; i++) {
|
||||
args[i] = mSetArguments.get(i).toString();
|
||||
}
|
||||
|
||||
for (int i = 0; i < whereSize; i++) {
|
||||
args[i + setSize] = mWhereArguments.get(i).toString();
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
}
|
||||
21
app/src/main/java/com/activeandroid/query/Sqlable.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package com.activeandroid.query;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2010 Michael Pardo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
public interface Sqlable {
|
||||
public String toSql();
|
||||
}
|
||||
50
app/src/main/java/com/activeandroid/query/Update.java
Normal file
@@ -0,0 +1,50 @@
|
||||
package com.activeandroid.query;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2010 Michael Pardo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import com.activeandroid.Cache;
|
||||
import com.activeandroid.Model;
|
||||
|
||||
public final class Update implements Sqlable {
|
||||
private Class<? extends Model> mType;
|
||||
|
||||
public Update(Class<? extends Model> table) {
|
||||
mType = table;
|
||||
}
|
||||
|
||||
public Set set(String set) {
|
||||
return new Set(this, set);
|
||||
}
|
||||
|
||||
public Set set(String set, Object... args) {
|
||||
return new Set(this, set, args);
|
||||
}
|
||||
|
||||
Class<? extends Model> getType() {
|
||||
return mType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toSql() {
|
||||
StringBuilder sql = new StringBuilder();
|
||||
sql.append("UPDATE ");
|
||||
sql.append(Cache.getTableName(mType));
|
||||
sql.append(" ");
|
||||
|
||||
return sql.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.activeandroid.serializer;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public final class BigDecimalSerializer extends TypeSerializer {
|
||||
public Class<?> getDeserializedType() {
|
||||
return BigDecimal.class;
|
||||
}
|
||||
|
||||
public Class<?> getSerializedType() {
|
||||
return String.class;
|
||||
}
|
||||
|
||||
public String serialize(Object data) {
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ((BigDecimal) data).toString();
|
||||
}
|
||||
|
||||
public BigDecimal deserialize(Object data) {
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new BigDecimal((String) data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.activeandroid.serializer;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2010 Michael Pardo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
public final class CalendarSerializer extends TypeSerializer {
|
||||
public Class<?> getDeserializedType() {
|
||||
return Calendar.class;
|
||||
}
|
||||
|
||||
public Class<?> getSerializedType() {
|
||||
return long.class;
|
||||
}
|
||||
|
||||
public Long serialize(Object data) {
|
||||
return ((Calendar) data).getTimeInMillis();
|
||||
}
|
||||
|
||||
public Calendar deserialize(Object data) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTimeInMillis((Long) data);
|
||||
|
||||
return calendar;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.activeandroid.serializer;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2010 Michael Pardo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
public final class FileSerializer extends TypeSerializer {
|
||||
public Class<?> getDeserializedType() {
|
||||
return File.class;
|
||||
}
|
||||
|
||||
public Class<?> getSerializedType() {
|
||||
return String.class;
|
||||
}
|
||||
|
||||
public String serialize(Object data) {
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ((File) data).toString();
|
||||
}
|
||||
|
||||
public File deserialize(Object data) {
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new File((String) data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.activeandroid.serializer;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2010 Michael Pardo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.sql.Date;
|
||||
|
||||
public final class SqlDateSerializer extends TypeSerializer {
|
||||
public Class<?> getDeserializedType() {
|
||||
return Date.class;
|
||||
}
|
||||
|
||||
public Class<?> getSerializedType() {
|
||||
return long.class;
|
||||
}
|
||||
|
||||
public Long serialize(Object data) {
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ((Date) data).getTime();
|
||||
}
|
||||
|
||||
public Date deserialize(Object data) {
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Date((Long) data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.activeandroid.serializer;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2010 Michael Pardo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
public abstract class TypeSerializer {
|
||||
public abstract Class<?> getDeserializedType();
|
||||
|
||||
public abstract Class<?> getSerializedType();
|
||||
|
||||
public abstract Object serialize(Object data);
|
||||
|
||||
public abstract Object deserialize(Object data);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.activeandroid.serializer;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public final class UUIDSerializer extends TypeSerializer {
|
||||
public Class<?> getDeserializedType() {
|
||||
return UUID.class;
|
||||
}
|
||||
|
||||
public Class<?> getSerializedType() {
|
||||
return String.class;
|
||||
}
|
||||
|
||||
public String serialize(Object data) {
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ((UUID) data).toString();
|
||||
}
|
||||
|
||||
public UUID deserialize(Object data) {
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return UUID.fromString((String)data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.activeandroid.serializer;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2010 Michael Pardo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public final class UtilDateSerializer extends TypeSerializer {
|
||||
public Class<?> getDeserializedType() {
|
||||
return Date.class;
|
||||
}
|
||||
|
||||
public Class<?> getSerializedType() {
|
||||
return long.class;
|
||||
}
|
||||
|
||||
public Long serialize(Object data) {
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ((Date) data).getTime();
|
||||
}
|
||||
|
||||
public Date deserialize(Object data) {
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Date((Long) data);
|
||||
}
|
||||
}
|
||||
71
app/src/main/java/com/activeandroid/util/IOUtils.java
Normal file
@@ -0,0 +1,71 @@
|
||||
|
||||
package com.activeandroid.util;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2014 Markus Pfeiffer
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import android.database.Cursor;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
||||
import com.activeandroid.util.Log;
|
||||
|
||||
|
||||
public class IOUtils {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Unconditionally close a {@link Closeable}.
|
||||
* </p>
|
||||
* Equivalent to {@link Closeable#close()}, except any exceptions will be ignored. This is
|
||||
* typically used in finally blocks.
|
||||
* @param closeable A {@link Closeable} to close.
|
||||
*/
|
||||
public static void closeQuietly(final Closeable closeable) {
|
||||
|
||||
if (closeable == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
closeable.close();
|
||||
} catch (final IOException e) {
|
||||
Log.e("Couldn't close closeable.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Unconditionally close a {@link Cursor}.
|
||||
* </p>
|
||||
* Equivalent to {@link Cursor#close()}, except any exceptions will be ignored. This is
|
||||
* typically used in finally blocks.
|
||||
* @param cursor A {@link Cursor} to close.
|
||||
*/
|
||||
public static void closeQuietly(final Cursor cursor) {
|
||||
|
||||
if (cursor == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
cursor.close();
|
||||
} catch (final Exception e) {
|
||||
Log.e("Couldn't close cursor.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
196
app/src/main/java/com/activeandroid/util/Log.java
Normal file
@@ -0,0 +1,196 @@
|
||||
package com.activeandroid.util;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2010 Michael Pardo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
public final class Log {
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// PUBLIC MEMBERS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private static String sTag = "ActiveAndroid";
|
||||
private static boolean sEnabled = false;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// CONSTRUCTORS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private Log() {
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// PUBLIC METHODS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static boolean isEnabled() {
|
||||
return sEnabled;
|
||||
}
|
||||
|
||||
public static void setEnabled(boolean enabled) {
|
||||
sEnabled = enabled;
|
||||
}
|
||||
|
||||
public static boolean isLoggingEnabled() {
|
||||
return sEnabled;
|
||||
}
|
||||
|
||||
public static int v(String msg) {
|
||||
if (sEnabled) {
|
||||
return android.util.Log.v(sTag, msg);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int v(String tag, String msg) {
|
||||
if (sEnabled) {
|
||||
return android.util.Log.v(tag, msg);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int v(String msg, Throwable tr) {
|
||||
if (sEnabled) {
|
||||
return android.util.Log.v(sTag, msg, tr);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int v(String tag, String msg, Throwable tr) {
|
||||
if (sEnabled) {
|
||||
return android.util.Log.v(tag, msg, tr);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int d(String msg) {
|
||||
if (sEnabled) {
|
||||
return android.util.Log.d(sTag, msg);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int d(String tag, String msg) {
|
||||
if (sEnabled) {
|
||||
return android.util.Log.d(tag, msg);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int d(String msg, Throwable tr) {
|
||||
if (sEnabled) {
|
||||
return android.util.Log.d(sTag, msg, tr);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int d(String tag, String msg, Throwable tr) {
|
||||
if (sEnabled) {
|
||||
return android.util.Log.d(tag, msg, tr);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int i(String msg) {
|
||||
if (sEnabled) {
|
||||
return android.util.Log.i(sTag, msg);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int i(String tag, String msg) {
|
||||
if (sEnabled) {
|
||||
return android.util.Log.i(tag, msg);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int i(String msg, Throwable tr) {
|
||||
if (sEnabled) {
|
||||
return android.util.Log.i(sTag, msg, tr);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int i(String tag, String msg, Throwable tr) {
|
||||
if (sEnabled) {
|
||||
return android.util.Log.i(tag, msg, tr);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int w(String msg) {
|
||||
if (sEnabled) {
|
||||
return android.util.Log.w(sTag, msg);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int w(String tag, String msg) {
|
||||
if (sEnabled) {
|
||||
return android.util.Log.w(tag, msg);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int w(String msg, Throwable tr) {
|
||||
if (sEnabled) {
|
||||
return android.util.Log.w(sTag, msg, tr);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int w(String tag, String msg, Throwable tr) {
|
||||
if (sEnabled) {
|
||||
return android.util.Log.w(tag, msg, tr);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int e(String msg) {
|
||||
if (sEnabled) {
|
||||
return android.util.Log.e(sTag, msg);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int e(String tag, String msg) {
|
||||
if (sEnabled) {
|
||||
return android.util.Log.e(tag, msg);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int e(String msg, Throwable tr) {
|
||||
if (sEnabled) {
|
||||
return android.util.Log.e(sTag, msg, tr);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int e(String tag, String msg, Throwable tr) {
|
||||
if (sEnabled) {
|
||||
return android.util.Log.e(tag, msg, tr);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int t(String msg, Object... args) {
|
||||
if (sEnabled) {
|
||||
return android.util.Log.v("test", String.format(msg, args));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package com.activeandroid.util;
|
||||
|
||||
/*
|
||||
NaturalOrderComparator.java -- Perform 'natural order' comparisons of strings in Java.
|
||||
Copyright (C) 2003 by Pierre-Luc Paour <natorder@paour.com>
|
||||
|
||||
Based on the C version by Martin Pool, of which this is more or less a straight conversion.
|
||||
Copyright (C) 2000 by Martin Pool <mbp@humbug.org.au>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
public class NaturalOrderComparator implements Comparator<Object> {
|
||||
int compareRight(String a, String b) {
|
||||
int bias = 0;
|
||||
int ia = 0;
|
||||
int ib = 0;
|
||||
|
||||
// The longest run of digits wins. That aside, the greatest
|
||||
// value wins, but we can't know that it will until we've scanned
|
||||
// both numbers to know that they have the same magnitude, so we
|
||||
// remember it in BIAS.
|
||||
for (;; ia++, ib++) {
|
||||
char ca = charAt(a, ia);
|
||||
char cb = charAt(b, ib);
|
||||
|
||||
if (!Character.isDigit(ca) && !Character.isDigit(cb)) {
|
||||
return bias;
|
||||
}
|
||||
else if (!Character.isDigit(ca)) {
|
||||
return -1;
|
||||
}
|
||||
else if (!Character.isDigit(cb)) {
|
||||
return +1;
|
||||
}
|
||||
else if (ca < cb) {
|
||||
if (bias == 0) {
|
||||
bias = -1;
|
||||
}
|
||||
}
|
||||
else if (ca > cb) {
|
||||
if (bias == 0)
|
||||
bias = +1;
|
||||
}
|
||||
else if (ca == 0 && cb == 0) {
|
||||
return bias;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int compare(Object o1, Object o2) {
|
||||
String a = o1.toString();
|
||||
String b = o2.toString();
|
||||
|
||||
int ia = 0, ib = 0;
|
||||
int nza = 0, nzb = 0;
|
||||
char ca, cb;
|
||||
int result;
|
||||
|
||||
while (true) {
|
||||
// only count the number of zeroes leading the last number compared
|
||||
nza = nzb = 0;
|
||||
|
||||
ca = charAt(a, ia);
|
||||
cb = charAt(b, ib);
|
||||
|
||||
// skip over leading spaces or zeros
|
||||
while (Character.isSpaceChar(ca) || ca == '0') {
|
||||
if (ca == '0') {
|
||||
nza++;
|
||||
}
|
||||
else {
|
||||
// only count consecutive zeroes
|
||||
nza = 0;
|
||||
}
|
||||
|
||||
ca = charAt(a, ++ia);
|
||||
}
|
||||
|
||||
while (Character.isSpaceChar(cb) || cb == '0') {
|
||||
if (cb == '0') {
|
||||
nzb++;
|
||||
}
|
||||
else {
|
||||
// only count consecutive zeroes
|
||||
nzb = 0;
|
||||
}
|
||||
|
||||
cb = charAt(b, ++ib);
|
||||
}
|
||||
|
||||
// process run of digits
|
||||
if (Character.isDigit(ca) && Character.isDigit(cb)) {
|
||||
if ((result = compareRight(a.substring(ia), b.substring(ib))) != 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (ca == 0 && cb == 0) {
|
||||
// The strings compare the same. Perhaps the caller
|
||||
// will want to call strcmp to break the tie.
|
||||
return nza - nzb;
|
||||
}
|
||||
|
||||
if (ca < cb) {
|
||||
return -1;
|
||||
}
|
||||
else if (ca > cb) {
|
||||
return +1;
|
||||
}
|
||||
|
||||
++ia;
|
||||
++ib;
|
||||
}
|
||||
}
|
||||
|
||||
static char charAt(String s, int i) {
|
||||
if (i >= s.length()) {
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
return s.charAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
110
app/src/main/java/com/activeandroid/util/ReflectionUtils.java
Normal file
@@ -0,0 +1,110 @@
|
||||
package com.activeandroid.util;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2010 Michael Pardo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
|
||||
import com.activeandroid.Model;
|
||||
import com.activeandroid.annotation.Column;
|
||||
import com.activeandroid.serializer.TypeSerializer;
|
||||
|
||||
public final class ReflectionUtils {
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// PUBLIC METHODS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static boolean isModel(Class<?> type) {
|
||||
return isSubclassOf(type, Model.class) && (!Modifier.isAbstract(type.getModifiers()));
|
||||
}
|
||||
|
||||
public static boolean isTypeSerializer(Class<?> type) {
|
||||
return isSubclassOf(type, TypeSerializer.class);
|
||||
}
|
||||
|
||||
// Meta-data
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T getMetaData(Context context, String name) {
|
||||
try {
|
||||
final ApplicationInfo ai = context.getPackageManager().getApplicationInfo(context.getPackageName(),
|
||||
PackageManager.GET_META_DATA);
|
||||
|
||||
if (ai.metaData != null) {
|
||||
return (T) ai.metaData.get(name);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Log.w("Couldn't find meta-data: " + name);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Set<Field> getDeclaredColumnFields(Class<?> type) {
|
||||
Set<Field> declaredColumnFields = Collections.emptySet();
|
||||
|
||||
if (ReflectionUtils.isSubclassOf(type, Model.class) || Model.class.equals(type)) {
|
||||
declaredColumnFields = new LinkedHashSet<Field>();
|
||||
|
||||
Field[] fields = type.getDeclaredFields();
|
||||
Arrays.sort(fields, new Comparator<Field>() {
|
||||
@Override
|
||||
public int compare(Field field1, Field field2) {
|
||||
return field2.getName().compareTo(field1.getName());
|
||||
}
|
||||
});
|
||||
for (Field field : fields) {
|
||||
if (field.isAnnotationPresent(Column.class)) {
|
||||
declaredColumnFields.add(field);
|
||||
}
|
||||
}
|
||||
|
||||
Class<?> parentType = type.getSuperclass();
|
||||
if (parentType != null) {
|
||||
declaredColumnFields.addAll(getDeclaredColumnFields(parentType));
|
||||
}
|
||||
}
|
||||
|
||||
return declaredColumnFields;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// PRIVATE METHODS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static boolean isSubclassOf(Class<?> type, Class<?> superClass) {
|
||||
if (type.getSuperclass() != null) {
|
||||
if (type.getSuperclass().equals(superClass)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return isSubclassOf(type.getSuperclass(), superClass);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
406
app/src/main/java/com/activeandroid/util/SQLiteUtils.java
Normal file
@@ -0,0 +1,406 @@
|
||||
package com.activeandroid.util;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2010 Michael Pardo
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.activeandroid.Cache;
|
||||
import com.activeandroid.Model;
|
||||
import com.activeandroid.TableInfo;
|
||||
import com.activeandroid.annotation.Column;
|
||||
import com.activeandroid.annotation.Column.ConflictAction;
|
||||
import com.activeandroid.serializer.TypeSerializer;
|
||||
|
||||
import java.lang.Long;
|
||||
import java.lang.String;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public final class SQLiteUtils {
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// ENUMERATIONS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public enum SQLiteType {
|
||||
INTEGER, REAL, TEXT, BLOB
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// PUBLIC CONSTANTS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static final boolean FOREIGN_KEYS_SUPPORTED = Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// PRIVATE CONTSANTS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static final HashMap<Class<?>, SQLiteType> TYPE_MAP = new HashMap<Class<?>, SQLiteType>() {
|
||||
{
|
||||
put(byte.class, SQLiteType.INTEGER);
|
||||
put(short.class, SQLiteType.INTEGER);
|
||||
put(int.class, SQLiteType.INTEGER);
|
||||
put(long.class, SQLiteType.INTEGER);
|
||||
put(float.class, SQLiteType.REAL);
|
||||
put(double.class, SQLiteType.REAL);
|
||||
put(boolean.class, SQLiteType.INTEGER);
|
||||
put(char.class, SQLiteType.TEXT);
|
||||
put(byte[].class, SQLiteType.BLOB);
|
||||
put(Byte.class, SQLiteType.INTEGER);
|
||||
put(Short.class, SQLiteType.INTEGER);
|
||||
put(Integer.class, SQLiteType.INTEGER);
|
||||
put(Long.class, SQLiteType.INTEGER);
|
||||
put(Float.class, SQLiteType.REAL);
|
||||
put(Double.class, SQLiteType.REAL);
|
||||
put(Boolean.class, SQLiteType.INTEGER);
|
||||
put(Character.class, SQLiteType.TEXT);
|
||||
put(String.class, SQLiteType.TEXT);
|
||||
put(Byte[].class, SQLiteType.BLOB);
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// PRIVATE MEMBERS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private static HashMap<String, List<String>> sIndexGroupMap;
|
||||
private static HashMap<String, List<String>> sUniqueGroupMap;
|
||||
private static HashMap<String, ConflictAction> sOnUniqueConflictsMap;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// PUBLIC METHODS
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static void execSql(String sql) {
|
||||
Cache.openDatabase().execSQL(sql);
|
||||
}
|
||||
|
||||
public static void execSql(String sql, Object[] bindArgs) {
|
||||
Cache.openDatabase().execSQL(sql, bindArgs);
|
||||
}
|
||||
|
||||
public static <T extends Model> List<T> rawQuery(Class<? extends Model> type, String sql, String[] selectionArgs) {
|
||||
Cursor cursor = Cache.openDatabase().rawQuery(sql, selectionArgs);
|
||||
List<T> entities = processCursor(type, cursor);
|
||||
cursor.close();
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
public static int intQuery(final String sql, final String[] selectionArgs) {
|
||||
final Cursor cursor = Cache.openDatabase().rawQuery(sql, selectionArgs);
|
||||
final int number = processIntCursor(cursor);
|
||||
cursor.close();
|
||||
|
||||
return number;
|
||||
}
|
||||
|
||||
public static <T extends Model> T rawQuerySingle(Class<? extends Model> type, String sql, String[] selectionArgs) {
|
||||
List<T> entities = rawQuery(type, sql, selectionArgs);
|
||||
|
||||
if (entities.size() > 0) {
|
||||
return entities.get(0);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Database creation
|
||||
|
||||
public static ArrayList<String> createUniqueDefinition(TableInfo tableInfo) {
|
||||
final ArrayList<String> definitions = new ArrayList<String>();
|
||||
sUniqueGroupMap = new HashMap<String, List<String>>();
|
||||
sOnUniqueConflictsMap = new HashMap<String, ConflictAction>();
|
||||
|
||||
for (Field field : tableInfo.getFields()) {
|
||||
createUniqueColumnDefinition(tableInfo, field);
|
||||
}
|
||||
|
||||
if (sUniqueGroupMap.isEmpty()) {
|
||||
return definitions;
|
||||
}
|
||||
|
||||
Set<String> keySet = sUniqueGroupMap.keySet();
|
||||
for (String key : keySet) {
|
||||
List<String> group = sUniqueGroupMap.get(key);
|
||||
ConflictAction conflictAction = sOnUniqueConflictsMap.get(key);
|
||||
|
||||
definitions.add(String.format("UNIQUE (%s) ON CONFLICT %s",
|
||||
TextUtils.join(", ", group), conflictAction.toString()));
|
||||
}
|
||||
|
||||
return definitions;
|
||||
}
|
||||
|
||||
public static void createUniqueColumnDefinition(TableInfo tableInfo, Field field) {
|
||||
final String name = tableInfo.getColumnName(field);
|
||||
final Column column = field.getAnnotation(Column.class);
|
||||
|
||||
if (field.getName().equals("mId")) {
|
||||
return;
|
||||
}
|
||||
|
||||
String[] groups = column.uniqueGroups();
|
||||
ConflictAction[] conflictActions = column.onUniqueConflicts();
|
||||
if (groups.length != conflictActions.length)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < groups.length; i++) {
|
||||
String group = groups[i];
|
||||
ConflictAction conflictAction = conflictActions[i];
|
||||
|
||||
if (TextUtils.isEmpty(group))
|
||||
continue;
|
||||
|
||||
List<String> list = sUniqueGroupMap.get(group);
|
||||
if (list == null) {
|
||||
list = new ArrayList<String>();
|
||||
}
|
||||
list.add(name);
|
||||
|
||||
sUniqueGroupMap.put(group, list);
|
||||
sOnUniqueConflictsMap.put(group, conflictAction);
|
||||
}
|
||||
}
|
||||
|
||||
public static String[] createIndexDefinition(TableInfo tableInfo) {
|
||||
final ArrayList<String> definitions = new ArrayList<String>();
|
||||
sIndexGroupMap = new HashMap<String, List<String>>();
|
||||
|
||||
for (Field field : tableInfo.getFields()) {
|
||||
createIndexColumnDefinition(tableInfo, field);
|
||||
}
|
||||
|
||||
if (sIndexGroupMap.isEmpty()) {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
for (Map.Entry<String, List<String>> entry : sIndexGroupMap.entrySet()) {
|
||||
definitions.add(String.format("CREATE INDEX IF NOT EXISTS %s on %s(%s);",
|
||||
"index_" + tableInfo.getTableName() + "_" + entry.getKey(),
|
||||
tableInfo.getTableName(), TextUtils.join(", ", entry.getValue())));
|
||||
}
|
||||
|
||||
return definitions.toArray(new String[definitions.size()]);
|
||||
}
|
||||
|
||||
public static void createIndexColumnDefinition(TableInfo tableInfo, Field field) {
|
||||
final String name = tableInfo.getColumnName(field);
|
||||
final Column column = field.getAnnotation(Column.class);
|
||||
|
||||
if (field.getName().equals("mId")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (column.index()) {
|
||||
List<String> list = new ArrayList<String>();
|
||||
list.add(name);
|
||||
sIndexGroupMap.put(name, list);
|
||||
}
|
||||
|
||||
String[] groups = column.indexGroups();
|
||||
for (String group : groups) {
|
||||
if (TextUtils.isEmpty(group))
|
||||
continue;
|
||||
|
||||
List<String> list = sIndexGroupMap.get(group);
|
||||
if (list == null) {
|
||||
list = new ArrayList<String>();
|
||||
}
|
||||
|
||||
list.add(name);
|
||||
sIndexGroupMap.put(group, list);
|
||||
}
|
||||
}
|
||||
|
||||
public static String createTableDefinition(TableInfo tableInfo) {
|
||||
final ArrayList<String> definitions = new ArrayList<String>();
|
||||
|
||||
for (Field field : tableInfo.getFields()) {
|
||||
String definition = createColumnDefinition(tableInfo, field);
|
||||
if (!TextUtils.isEmpty(definition)) {
|
||||
definitions.add(definition);
|
||||
}
|
||||
}
|
||||
|
||||
definitions.addAll(createUniqueDefinition(tableInfo));
|
||||
|
||||
return String.format("CREATE TABLE IF NOT EXISTS %s (%s);", tableInfo.getTableName(),
|
||||
TextUtils.join(", ", definitions));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static String createColumnDefinition(TableInfo tableInfo, Field field) {
|
||||
StringBuilder definition = new StringBuilder();
|
||||
|
||||
Class<?> type = field.getType();
|
||||
final String name = tableInfo.getColumnName(field);
|
||||
final TypeSerializer typeSerializer = Cache.getParserForType(field.getType());
|
||||
final Column column = field.getAnnotation(Column.class);
|
||||
|
||||
if (typeSerializer != null) {
|
||||
type = typeSerializer.getSerializedType();
|
||||
}
|
||||
|
||||
if (TYPE_MAP.containsKey(type)) {
|
||||
definition.append(name);
|
||||
definition.append(" ");
|
||||
definition.append(TYPE_MAP.get(type).toString());
|
||||
}
|
||||
else if (ReflectionUtils.isModel(type)) {
|
||||
definition.append(name);
|
||||
definition.append(" ");
|
||||
definition.append(SQLiteType.INTEGER.toString());
|
||||
}
|
||||
else if (ReflectionUtils.isSubclassOf(type, Enum.class)) {
|
||||
definition.append(name);
|
||||
definition.append(" ");
|
||||
definition.append(SQLiteType.TEXT.toString());
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(definition)) {
|
||||
|
||||
if (name.equals(tableInfo.getIdName())) {
|
||||
definition.append(" PRIMARY KEY AUTOINCREMENT");
|
||||
}else if(column!=null){
|
||||
if (column.length() > -1) {
|
||||
definition.append("(");
|
||||
definition.append(column.length());
|
||||
definition.append(")");
|
||||
}
|
||||
|
||||
if (column.notNull()) {
|
||||
definition.append(" NOT NULL ON CONFLICT ");
|
||||
definition.append(column.onNullConflict().toString());
|
||||
}
|
||||
|
||||
if (column.unique()) {
|
||||
definition.append(" UNIQUE ON CONFLICT ");
|
||||
definition.append(column.onUniqueConflict().toString());
|
||||
}
|
||||
}
|
||||
|
||||
if (FOREIGN_KEYS_SUPPORTED && ReflectionUtils.isModel(type)) {
|
||||
definition.append(" REFERENCES ");
|
||||
definition.append(Cache.getTableInfo((Class<? extends Model>) type).getTableName());
|
||||
definition.append("("+tableInfo.getIdName()+")");
|
||||
definition.append(" ON DELETE ");
|
||||
definition.append(column.onDelete().toString().replace("_", " "));
|
||||
definition.append(" ON UPDATE ");
|
||||
definition.append(column.onUpdate().toString().replace("_", " "));
|
||||
}
|
||||
}
|
||||
else {
|
||||
Log.e("No type mapping for: " + type.toString());
|
||||
}
|
||||
|
||||
return definition.toString();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends Model> List<T> processCursor(Class<? extends Model> type, Cursor cursor) {
|
||||
TableInfo tableInfo = Cache.getTableInfo(type);
|
||||
String idName = tableInfo.getIdName();
|
||||
final List<T> entities = new ArrayList<T>();
|
||||
|
||||
try {
|
||||
Constructor<?> entityConstructor = type.getConstructor();
|
||||
|
||||
if (cursor.moveToFirst()) {
|
||||
/**
|
||||
* Obtain the columns ordered to fix issue #106 (https://github.com/pardom/ActiveAndroid/issues/106)
|
||||
* when the cursor have multiple columns with same name obtained from join tables.
|
||||
*/
|
||||
List<String> columnsOrdered = new ArrayList<String>(Arrays.asList(cursor.getColumnNames()));
|
||||
do {
|
||||
Model entity = Cache.getEntity(type, cursor.getLong(columnsOrdered.indexOf(idName)));
|
||||
if (entity == null) {
|
||||
entity = (T) entityConstructor.newInstance();
|
||||
}
|
||||
|
||||
entity.loadFromCursor(cursor);
|
||||
entities.add((T) entity);
|
||||
}
|
||||
while (cursor.moveToNext());
|
||||
}
|
||||
|
||||
}
|
||||
catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException(
|
||||
"Your model " + type.getName() + " does not define a default " +
|
||||
"constructor. The default constructor is required for " +
|
||||
"now in ActiveAndroid models, as the process to " +
|
||||
"populate the ORM model is : " +
|
||||
"1. instantiate default model " +
|
||||
"2. populate fields"
|
||||
);
|
||||
}
|
||||
catch (Exception e) {
|
||||
Log.e("Failed to process cursor.", e);
|
||||
}
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
private static int processIntCursor(final Cursor cursor) {
|
||||
if (cursor.moveToFirst()) {
|
||||
return cursor.getInt(0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static List<String> lexSqlScript(String sqlScript) {
|
||||
ArrayList<String> sl = new ArrayList<String>();
|
||||
boolean inString = false, quoteNext = false;
|
||||
StringBuilder b = new StringBuilder(100);
|
||||
|
||||
for (int i = 0; i < sqlScript.length(); i++) {
|
||||
char c = sqlScript.charAt(i);
|
||||
|
||||
if (c == ';' && !inString && !quoteNext) {
|
||||
sl.add(b.toString());
|
||||
b = new StringBuilder(100);
|
||||
inString = false;
|
||||
quoteNext = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == '\'' && !quoteNext) {
|
||||
inString = !inString;
|
||||
}
|
||||
|
||||
quoteNext = c == '\\' && !quoteNext;
|
||||
|
||||
b.append(c);
|
||||
}
|
||||
|
||||
if (b.length() > 0) {
|
||||
sl.add(b.toString());
|
||||
}
|
||||
|
||||
return sl;
|
||||
}
|
||||
}
|
||||
110
app/src/main/java/com/activeandroid/util/SqlParser.java
Normal file
@@ -0,0 +1,110 @@
|
||||
|
||||
package com.activeandroid.util;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2014 Markus Pfeiffer
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public class SqlParser {
|
||||
|
||||
public final static int STATE_NONE = 0;
|
||||
public final static int STATE_STRING = 1;
|
||||
public final static int STATE_COMMENT = 2;
|
||||
public final static int STATE_COMMENT_BLOCK = 3;
|
||||
|
||||
public static List<String> parse(final InputStream stream) throws IOException {
|
||||
|
||||
final BufferedInputStream buffer = new BufferedInputStream(stream);
|
||||
final List<String> commands = new ArrayList<String>();
|
||||
final StringBuffer sb = new StringBuffer();
|
||||
|
||||
try {
|
||||
final Tokenizer tokenizer = new Tokenizer(buffer);
|
||||
int state = STATE_NONE;
|
||||
|
||||
while (tokenizer.hasNext()) {
|
||||
final char c = (char) tokenizer.next();
|
||||
|
||||
if (state == STATE_COMMENT_BLOCK) {
|
||||
if (tokenizer.skip("*/")) {
|
||||
state = STATE_NONE;
|
||||
}
|
||||
continue;
|
||||
|
||||
} else if (state == STATE_COMMENT) {
|
||||
if (isNewLine(c)) {
|
||||
state = STATE_NONE;
|
||||
}
|
||||
continue;
|
||||
|
||||
} else if (state == STATE_NONE && tokenizer.skip("/*")) {
|
||||
state = STATE_COMMENT_BLOCK;
|
||||
continue;
|
||||
|
||||
} else if (state == STATE_NONE && tokenizer.skip("--")) {
|
||||
state = STATE_COMMENT;
|
||||
continue;
|
||||
|
||||
} else if (state == STATE_NONE && c == ';') {
|
||||
final String command = sb.toString().trim();
|
||||
commands.add(command);
|
||||
sb.setLength(0);
|
||||
continue;
|
||||
|
||||
} else if (state == STATE_NONE && c == '\'') {
|
||||
state = STATE_STRING;
|
||||
|
||||
} else if (state == STATE_STRING && c == '\'') {
|
||||
state = STATE_NONE;
|
||||
|
||||
}
|
||||
|
||||
if (state == STATE_NONE || state == STATE_STRING) {
|
||||
if (state == STATE_NONE && isWhitespace(c)) {
|
||||
if (sb.length() > 0 && sb.charAt(sb.length() - 1) != ' ') {
|
||||
sb.append(' ');
|
||||
}
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} finally {
|
||||
IOUtils.closeQuietly(buffer);
|
||||
}
|
||||
|
||||
if (sb.length() > 0) {
|
||||
commands.add(sb.toString().trim());
|
||||
}
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
private static boolean isNewLine(final char c) {
|
||||
return c == '\r' || c == '\n';
|
||||
}
|
||||
|
||||
private static boolean isWhitespace(final char c) {
|
||||
return c == '\r' || c == '\n' || c == '\t' || c == ' ';
|
||||
}
|
||||
}
|
||||
76
app/src/main/java/com/activeandroid/util/Tokenizer.java
Normal file
@@ -0,0 +1,76 @@
|
||||
|
||||
package com.activeandroid.util;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2014 Markus Pfeiffer
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
|
||||
public class Tokenizer {
|
||||
|
||||
private final InputStream mStream;
|
||||
|
||||
private boolean mIsNext;
|
||||
private int mCurrent;
|
||||
|
||||
public Tokenizer(final InputStream in) {
|
||||
this.mStream = in;
|
||||
}
|
||||
|
||||
public boolean hasNext() throws IOException {
|
||||
|
||||
if (!this.mIsNext) {
|
||||
this.mIsNext = true;
|
||||
this.mCurrent = this.mStream.read();
|
||||
}
|
||||
return this.mCurrent != -1;
|
||||
}
|
||||
|
||||
public int next() throws IOException {
|
||||
|
||||
if (!this.mIsNext) {
|
||||
this.mCurrent = this.mStream.read();
|
||||
}
|
||||
this.mIsNext = false;
|
||||
return this.mCurrent;
|
||||
}
|
||||
|
||||
public boolean skip(final String s) throws IOException {
|
||||
|
||||
if (s == null || s.length() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (s.charAt(0) != this.mCurrent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int len = s.length();
|
||||
this.mStream.mark(len - 1);
|
||||
|
||||
for (int n = 1; n < len; n++) {
|
||||
final int value = this.mStream.read();
|
||||
|
||||
if (value != s.charAt(n)) {
|
||||
this.mStream.reset();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
57
app/src/main/java/com/activeandroid/widget/ModelAdapter.java
Normal file
@@ -0,0 +1,57 @@
|
||||
package com.activeandroid.widget;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.Context;
|
||||
import android.widget.ArrayAdapter;
|
||||
|
||||
import com.activeandroid.Model;
|
||||
|
||||
public class ModelAdapter<T extends Model> extends ArrayAdapter<T> {
|
||||
public ModelAdapter(Context context, int textViewResourceId) {
|
||||
super(context, textViewResourceId);
|
||||
}
|
||||
|
||||
public ModelAdapter(Context context, int resource, int textViewResourceId) {
|
||||
super(context, resource, textViewResourceId);
|
||||
}
|
||||
|
||||
public ModelAdapter(Context context, int textViewResourceId, List<T> objects) {
|
||||
super(context, textViewResourceId, objects);
|
||||
}
|
||||
|
||||
public ModelAdapter(Context context, int resource, int textViewResourceId, List<T> objects) {
|
||||
super(context, resource, textViewResourceId, objects);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the adapter and, if data != null, fills if with new Items.
|
||||
*
|
||||
* @param collection A Collection<? extends T> which members get added to the adapter.
|
||||
*/
|
||||
public void setData(Collection<? extends T> collection) {
|
||||
clear();
|
||||
|
||||
if (collection != null) {
|
||||
for (T item : collection) {
|
||||
add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The Id of the record at position.
|
||||
*/
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
T item = getItem(position);
|
||||
|
||||
if (item != null) {
|
||||
return item.getId();
|
||||
}
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.*;
|
||||
@@ -88,7 +88,16 @@ public class HabitsApplication extends Application
|
||||
if (db.exists()) db.delete();
|
||||
}
|
||||
|
||||
DatabaseUtils.initializeActiveAndroid(context);
|
||||
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();
|
||||
|
||||
@@ -20,25 +20,27 @@
|
||||
package org.isoron.uhabits.activities.common.views;
|
||||
|
||||
import android.os.*;
|
||||
import android.view.*;
|
||||
import android.support.v4.os.*;
|
||||
|
||||
public class BundleSavedState extends View.BaseSavedState
|
||||
public class BundleSavedState extends android.support.v4.view.AbsSavedState
|
||||
{
|
||||
public static final Parcelable.Creator<BundleSavedState> CREATOR =
|
||||
new Parcelable.Creator<BundleSavedState>()
|
||||
{
|
||||
@Override
|
||||
public BundleSavedState createFromParcel(Parcel source)
|
||||
ParcelableCompat.newCreator(
|
||||
new ParcelableCompatCreatorCallbacks<BundleSavedState>()
|
||||
{
|
||||
return new BundleSavedState(source);
|
||||
}
|
||||
@Override
|
||||
public BundleSavedState createFromParcel(Parcel source,
|
||||
ClassLoader loader)
|
||||
{
|
||||
return new BundleSavedState(source, loader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BundleSavedState[] newArray(int size)
|
||||
{
|
||||
return new BundleSavedState[size];
|
||||
}
|
||||
};
|
||||
@Override
|
||||
public BundleSavedState[] newArray(int size)
|
||||
{
|
||||
return new BundleSavedState[size];
|
||||
}
|
||||
});
|
||||
|
||||
public final Bundle bundle;
|
||||
|
||||
@@ -48,10 +50,10 @@ public class BundleSavedState extends View.BaseSavedState
|
||||
this.bundle = bundle;
|
||||
}
|
||||
|
||||
public BundleSavedState(Parcel source)
|
||||
public BundleSavedState(Parcel source, ClassLoader loader)
|
||||
{
|
||||
super(source);
|
||||
this.bundle = source.readBundle();
|
||||
super(source, loader);
|
||||
this.bundle = source.readBundle(loader);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -256,7 +256,7 @@ public class FrequencyChart extends ScrollableChart
|
||||
float scale = 1.0f/maxFreq * value;
|
||||
float radius = maxRadius * scale;
|
||||
|
||||
int colorIndex = Math.round((colors.length-1) * scale);
|
||||
int colorIndex = Math.min(colors.length - 1, Math.round((colors.length - 1) * scale));
|
||||
pGraph.setColor(colors[colorIndex]);
|
||||
canvas.drawCircle(rect.centerX(), rect.centerY(), radius, pGraph);
|
||||
}
|
||||
|
||||
@@ -32,7 +32,8 @@ import org.isoron.uhabits.utils.*;
|
||||
import java.text.*;
|
||||
import java.util.*;
|
||||
|
||||
import static org.isoron.uhabits.models.Checkmark.*;
|
||||
import static org.isoron.uhabits.models.Checkmark.CHECKED_EXPLICITLY;
|
||||
import static org.isoron.uhabits.models.Checkmark.UNCHECKED;
|
||||
|
||||
public class HistoryChart extends ScrollableChart
|
||||
{
|
||||
@@ -112,10 +113,21 @@ public class HistoryChart extends ScrollableChart
|
||||
if (!isEditable) return false;
|
||||
|
||||
performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
|
||||
float x, y;
|
||||
|
||||
int pointerId = e.getPointerId(0);
|
||||
float x = e.getX(pointerId);
|
||||
float y = e.getY(pointerId);
|
||||
try
|
||||
{
|
||||
int pointerId = e.getPointerId(0);
|
||||
x = e.getX(pointerId);
|
||||
y = e.getY(pointerId);
|
||||
}
|
||||
catch (RuntimeException ex)
|
||||
{
|
||||
// Android often throws IllegalArgumentException here. Apparently,
|
||||
// the pointer id may become invalid shortly after calling
|
||||
// e.getPointerId.
|
||||
return false;
|
||||
}
|
||||
|
||||
final Long timestamp = positionToTimestamp(x, y);
|
||||
if (timestamp == null) return false;
|
||||
|
||||
@@ -107,6 +107,12 @@ public abstract class ScrollableChart extends View
|
||||
@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");
|
||||
|
||||
@@ -39,6 +39,8 @@ import java.util.*;
|
||||
|
||||
import butterknife.*;
|
||||
|
||||
import static org.isoron.uhabits.activities.ThemeSwitcher.*;
|
||||
|
||||
public abstract class BaseDialog extends AppCompatDialogFragment
|
||||
{
|
||||
@Nullable
|
||||
@@ -62,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)
|
||||
{
|
||||
|
||||
@@ -105,11 +105,10 @@ public class HabitCardListAdapter
|
||||
* Returns the item that occupies a certain position on the list
|
||||
*
|
||||
* @param position position of the item
|
||||
* @return the item at given position
|
||||
* @throws IndexOutOfBoundsException if position is not valid
|
||||
* @return the item at given position or null if position is invalid
|
||||
*/
|
||||
@Deprecated
|
||||
@NonNull
|
||||
@Nullable
|
||||
public Habit getItem(int position)
|
||||
{
|
||||
return cache.getHabitByPosition(position);
|
||||
@@ -314,6 +313,8 @@ public class HabitCardListAdapter
|
||||
public void toggleSelection(int position)
|
||||
{
|
||||
Habit h = getItem(position);
|
||||
if (h == null) return;
|
||||
|
||||
int k = selected.indexOf(h);
|
||||
if (k < 0) selected.add(h);
|
||||
else selected.remove(h);
|
||||
|
||||
@@ -93,12 +93,12 @@ public class HabitCardListCache implements CommandRunner.Listener
|
||||
* Returns the habits that occupies a certain position on the list.
|
||||
*
|
||||
* @param position the position of the habit
|
||||
* @return the habit at given position
|
||||
* @throws IndexOutOfBoundsException if position is not valid
|
||||
* @return the habit at given position or null if position is invalid
|
||||
*/
|
||||
@NonNull
|
||||
public Habit getHabitByPosition(int position)
|
||||
@Nullable
|
||||
public synchronized Habit getHabitByPosition(int position)
|
||||
{
|
||||
if(position < 0 || position >= data.habits.size()) return null;
|
||||
return data.habits.get(position);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,54 +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);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +153,12 @@ public class HabitCardListView extends RecyclerView
|
||||
@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());
|
||||
|
||||
@@ -143,12 +143,13 @@ public class HabitCardView extends FrameLayout
|
||||
updateBackground(isSelected);
|
||||
}
|
||||
|
||||
public void triggerRipple(long timestamp)
|
||||
public synchronized void triggerRipple(long timestamp)
|
||||
{
|
||||
long today = DateUtils.getStartOfToday();
|
||||
long day = DateUtils.millisecondsInOneDay;
|
||||
int offset = (int) ((today - timestamp) / day) - dataOffset;
|
||||
CheckmarkButtonView button = checkmarkPanel.indexToButton(offset);
|
||||
if (button == null) return;
|
||||
|
||||
float y = button.getHeight() / 2.0f;
|
||||
float x = checkmarkPanel.getX() + button.getX() + button.getWidth() / 2;
|
||||
@@ -214,6 +215,7 @@ public class HabitCardView extends FrameLayout
|
||||
scoreRing.setPercentage(rand.nextFloat());
|
||||
checkmarkPanel.setColor(color);
|
||||
checkmarkPanel.setCheckmarkValues(values);
|
||||
checkmarkPanel.setButtonCount(5);
|
||||
}
|
||||
|
||||
private void refresh()
|
||||
|
||||
@@ -50,8 +50,6 @@ public class HeaderView extends ScrollableChart
|
||||
|
||||
private RectF rect;
|
||||
|
||||
private int maxDataOffset;
|
||||
|
||||
public HeaderView(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
@@ -76,7 +74,6 @@ public class HeaderView extends ScrollableChart
|
||||
|
||||
Resources res = context.getResources();
|
||||
setScrollerBucketSize((int) res.getDimension(R.dimen.checkmarkWidth));
|
||||
setDirection(shouldReverseCheckmarks() ? 1 : -1);
|
||||
|
||||
StyledResources sr = new StyledResources(context);
|
||||
paint = new TextPaint();
|
||||
@@ -99,7 +96,7 @@ public class HeaderView extends ScrollableChart
|
||||
@Override
|
||||
public void onCheckmarkOrderChanged()
|
||||
{
|
||||
setDirection(shouldReverseCheckmarks() ? 1 : -1);
|
||||
updateDirection();
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
@@ -112,11 +109,20 @@ public class HeaderView extends ScrollableChart
|
||||
@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()
|
||||
{
|
||||
@@ -145,6 +151,7 @@ public class HeaderView extends ScrollableChart
|
||||
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");
|
||||
@@ -153,9 +160,13 @@ public class HeaderView extends ScrollableChart
|
||||
{
|
||||
rect.set(0, 0, width, height);
|
||||
rect.offset(canvas.getWidth(), 0);
|
||||
|
||||
if(reverse) rect.offset(- (i + 1) * width, 0);
|
||||
else rect.offset((i - buttonCount) * width, 0);
|
||||
|
||||
if (isRtl) rect.set(canvas.getWidth() - rect.right, rect.top,
|
||||
canvas.getWidth() - rect.left, rect.bottom);
|
||||
|
||||
String text = DateUtils.formatHeaderDate(day).toUpperCase();
|
||||
String[] lines = text.split("\n");
|
||||
|
||||
|
||||
@@ -22,12 +22,16 @@ package org.isoron.uhabits.activities.settings;
|
||||
import android.app.backup.*;
|
||||
import android.content.*;
|
||||
import android.os.*;
|
||||
import android.provider.*;
|
||||
import android.support.v7.preference.*;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.activities.habits.list.*;
|
||||
import org.isoron.uhabits.notifications.*;
|
||||
import org.isoron.uhabits.utils.*;
|
||||
|
||||
import static android.os.Build.VERSION.*;
|
||||
|
||||
public class SettingsFragment extends PreferenceFragmentCompat
|
||||
implements SharedPreferences.OnSharedPreferenceChangeListener
|
||||
{
|
||||
@@ -61,6 +65,14 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
setResultOnPreferenceClick("bugReport", ListHabitsScreen.RESULT_BUG_REPORT);
|
||||
|
||||
updateRingtoneDescription();
|
||||
|
||||
if (SDK_INT < Build.VERSION_CODES.O)
|
||||
findPreference("reminderCustomize").setVisible(false);
|
||||
else
|
||||
{
|
||||
findPreference("reminderSound").setVisible(false);
|
||||
findPreference("pref_snooze_interval").setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -88,6 +100,17 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
RINGTONE_REQUEST_CODE);
|
||||
return true;
|
||||
}
|
||||
else if (key.equals("reminderCustomize"))
|
||||
{
|
||||
if (SDK_INT < Build.VERSION_CODES.O) return true;
|
||||
|
||||
NotificationTray.createAndroidNotificationChannel(getContext());
|
||||
Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
|
||||
intent.putExtra(Settings.EXTRA_APP_PACKAGE, getContext().getPackageName());
|
||||
intent.putExtra(Settings.EXTRA_CHANNEL_ID, NotificationTray.REMINDERS_CHANNEL_ID);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onPreferenceTreeClick(preference);
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ public class HabitsCSVExporter
|
||||
{
|
||||
String sane = sanitizeFilename(h.getName());
|
||||
String habitDirName =
|
||||
String.format("%03d %s", allHabits.indexOf(h) + 1, sane);
|
||||
String.format(Locale.US, "%03d %s", allHabits.indexOf(h) + 1, sane);
|
||||
habitDirName = habitDirName.trim() + "/";
|
||||
|
||||
new File(exportDirName + habitDirName).mkdirs();
|
||||
@@ -202,7 +202,7 @@ public class HabitsCSVExporter
|
||||
checksWriter.write(String.valueOf(checkmarks.get(j)[i]));
|
||||
checksWriter.write(DELIMITER);
|
||||
String score =
|
||||
String.format("%.4f", ((float) scores.get(j)[i]) / Score.MAX_VALUE);
|
||||
String.format(Locale.US, "%.4f", ((float) scores.get(j)[i]) / Score.MAX_VALUE);
|
||||
scoresWriter.write(score);
|
||||
scoresWriter.write(DELIMITER);
|
||||
}
|
||||
|
||||
@@ -19,20 +19,20 @@
|
||||
|
||||
package org.isoron.uhabits.io;
|
||||
|
||||
import android.content.Context;
|
||||
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.AppContext;
|
||||
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.*;
|
||||
|
||||
@@ -45,7 +45,8 @@ public class LoopDBImporter extends AbstractImporter
|
||||
private Context context;
|
||||
|
||||
@Inject
|
||||
public LoopDBImporter(@NonNull @AppContext Context context, @NonNull HabitList habits)
|
||||
public LoopDBImporter(@NonNull @AppContext Context context,
|
||||
@NonNull HabitList habits)
|
||||
{
|
||||
super(habits);
|
||||
this.context = context;
|
||||
@@ -59,15 +60,29 @@ 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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ public abstract class ScoreList implements Iterable<Score>
|
||||
* @param timestamp the timestamp of a day
|
||||
* @return score value for that day
|
||||
*/
|
||||
public final int getValue(long timestamp)
|
||||
public synchronized final int getValue(long timestamp)
|
||||
{
|
||||
compute(timestamp, timestamp);
|
||||
Score s = getComputedByTimestamp(timestamp);
|
||||
@@ -173,7 +173,7 @@ public abstract class ScoreList implements Iterable<Score>
|
||||
{
|
||||
String timestamp = dateFormat.format(s.getTimestamp());
|
||||
String score =
|
||||
String.format("%.4f", ((float) s.getValue()) / Score.MAX_VALUE);
|
||||
String.format(Locale.US, "%.4f", ((float) s.getValue()) / Score.MAX_VALUE);
|
||||
out.write(String.format("%s,%s\n", timestamp, score));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,12 +37,22 @@ import java.util.*;
|
||||
*/
|
||||
public class SQLiteCheckmarkList extends CheckmarkList
|
||||
{
|
||||
|
||||
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 >= ?";
|
||||
|
||||
@Nullable
|
||||
private HabitRecord habitRecord;
|
||||
|
||||
@NonNull
|
||||
private final SQLiteUtils<CheckmarkRecord> sqlite;
|
||||
|
||||
@Nullable
|
||||
private CachedData cache;
|
||||
|
||||
public SQLiteCheckmarkList(Habit habit)
|
||||
{
|
||||
super(habit);
|
||||
@@ -54,16 +63,11 @@ public class SQLiteCheckmarkList extends CheckmarkList
|
||||
public void add(List<Checkmark> checkmarks)
|
||||
{
|
||||
check(habit.getId());
|
||||
|
||||
String query =
|
||||
"insert into Checkmarks(habit, timestamp, value) values (?,?,?)";
|
||||
|
||||
SQLiteDatabase db = Cache.openDatabase();
|
||||
SQLiteStatement statement = db.compileStatement(ADD_QUERY);
|
||||
db.beginTransaction();
|
||||
try
|
||||
{
|
||||
SQLiteStatement statement = db.compileStatement(query);
|
||||
|
||||
for (Checkmark c : checkmarks)
|
||||
{
|
||||
statement.bindLong(1, habit.getId());
|
||||
@@ -87,8 +91,7 @@ public class SQLiteCheckmarkList extends CheckmarkList
|
||||
check(habit.getId());
|
||||
compute(fromTimestamp, toTimestamp);
|
||||
|
||||
String query = "select habit, timestamp, value " +
|
||||
"from checkmarks " +
|
||||
String query = "select habit, timestamp, value from checkmarks " +
|
||||
"where habit = ? and timestamp >= ? and timestamp <= ? " +
|
||||
"order by timestamp desc";
|
||||
|
||||
@@ -101,26 +104,28 @@ public class SQLiteCheckmarkList extends CheckmarkList
|
||||
List<CheckmarkRecord> records = sqlite.query(query, params);
|
||||
for (CheckmarkRecord record : records) record.habit = habitRecord;
|
||||
|
||||
int nDays = DateUtils.getDaysBetween(fromTimestamp, toTimestamp) + 1;
|
||||
if (records.size() != nDays)
|
||||
{
|
||||
throw new InconsistentDatabaseException(
|
||||
String.format("habit=%s, %d expected, %d found",
|
||||
habit.getName(), nDays, records.size()));
|
||||
}
|
||||
|
||||
records = fixRecords(records, habitRecord, fromTimestamp, toTimestamp);
|
||||
return toCheckmarks(records);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTodayValue()
|
||||
{
|
||||
if (cache == null || cache.expired())
|
||||
cache = new CachedData(super.getTodayValue());
|
||||
|
||||
return cache.todayValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateNewerThan(long timestamp)
|
||||
{
|
||||
new Delete()
|
||||
.from(CheckmarkRecord.class)
|
||||
.where("habit = ?", habit.getId())
|
||||
.and("timestamp >= ?", timestamp)
|
||||
.execute();
|
||||
|
||||
cache = null;
|
||||
SQLiteDatabase db = Cache.openDatabase();
|
||||
SQLiteStatement statement = db.compileStatement(INVALIDATE_QUERY);
|
||||
statement.bindLong(1, habit.getId());
|
||||
statement.bindLong(2, timestamp);
|
||||
statement.execute();
|
||||
observable.notifyListeners();
|
||||
}
|
||||
|
||||
@@ -129,10 +134,8 @@ public class SQLiteCheckmarkList extends CheckmarkList
|
||||
protected Checkmark getNewestComputed()
|
||||
{
|
||||
check(habit.getId());
|
||||
String query = "select habit, timestamp, value " +
|
||||
"from checkmarks " +
|
||||
"where habit = ? " +
|
||||
"order by timestamp desc " +
|
||||
String query = "select habit, timestamp, value from checkmarks " +
|
||||
"where habit = ? " + "order by timestamp desc " +
|
||||
"limit 1";
|
||||
|
||||
String params[] = { Long.toString(habit.getId()) };
|
||||
@@ -140,13 +143,12 @@ public class SQLiteCheckmarkList extends CheckmarkList
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected Checkmark getOldestComputed()
|
||||
{
|
||||
check(habit.getId());
|
||||
String query = "select habit, timestamp, value " +
|
||||
"from checkmarks " +
|
||||
"where habit = ? " +
|
||||
"order by timestamp asc " +
|
||||
String query = "select habit, timestamp, value from checkmarks " +
|
||||
"where habit = ? " + "order by timestamp asc " +
|
||||
"limit 1";
|
||||
|
||||
String params[] = { Long.toString(habit.getId()) };
|
||||
@@ -179,4 +181,44 @@ public class SQLiteCheckmarkList extends CheckmarkList
|
||||
for (CheckmarkRecord r : records) checkmarks.add(r.toCheckmark());
|
||||
return checkmarks;
|
||||
}
|
||||
|
||||
public static List<CheckmarkRecord> fixRecords(List<CheckmarkRecord> original,
|
||||
HabitRecord habit,
|
||||
long fromTimestamp,
|
||||
long toTimestamp)
|
||||
{
|
||||
long day = DateUtils.millisecondsInOneDay;
|
||||
ArrayList<CheckmarkRecord> records = new ArrayList<>();
|
||||
|
||||
for (long t = toTimestamp; t >= fromTimestamp; t -= day)
|
||||
records.add(new CheckmarkRecord(habit, t, Checkmark.UNCHECKED));
|
||||
|
||||
for (CheckmarkRecord record : original)
|
||||
{
|
||||
if ((toTimestamp - record.timestamp) % day != 0) continue;
|
||||
int offset = (int) ((toTimestamp - record.timestamp) / day);
|
||||
if (offset < 0 || offset >= records.size()) continue;
|
||||
records.set(offset, record);
|
||||
}
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
private static class CachedData
|
||||
{
|
||||
int todayValue;
|
||||
|
||||
private long today;
|
||||
|
||||
CachedData(int todayValue)
|
||||
{
|
||||
this.todayValue = todayValue;
|
||||
this.today = DateUtils.getStartOfToday();
|
||||
}
|
||||
|
||||
boolean expired()
|
||||
{
|
||||
return today != DateUtils.getStartOfToday();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,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);
|
||||
@@ -270,9 +270,7 @@ public class SQLiteHabitList extends HabitList
|
||||
for (HabitRecord record : recordList)
|
||||
{
|
||||
Habit habit = getById(record.getId());
|
||||
if (habit == null)
|
||||
throw new RuntimeException("habit not in database");
|
||||
|
||||
if (habit == null) continue;
|
||||
if (!filter.matches(habit)) continue;
|
||||
habits.add(habit);
|
||||
}
|
||||
|
||||
@@ -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,10 +24,10 @@ 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.*;
|
||||
import org.isoron.uhabits.utils.*;
|
||||
import org.jetbrains.annotations.*;
|
||||
|
||||
import java.util.*;
|
||||
@@ -37,12 +37,21 @@ import java.util.*;
|
||||
*/
|
||||
public class SQLiteScoreList extends ScoreList
|
||||
{
|
||||
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 >= ?";
|
||||
|
||||
@Nullable
|
||||
private HabitRecord habitRecord;
|
||||
|
||||
@NonNull
|
||||
private final SQLiteUtils<ScoreRecord> sqlite;
|
||||
|
||||
@Nullable
|
||||
private CachedData cache = null;
|
||||
|
||||
/**
|
||||
* Constructs a new ScoreList associated with the given habit.
|
||||
*
|
||||
@@ -58,16 +67,11 @@ public class SQLiteScoreList extends ScoreList
|
||||
public void add(List<Score> scores)
|
||||
{
|
||||
check(habit.getId());
|
||||
String query =
|
||||
"insert into Score(habit, timestamp, score) values (?,?,?)";
|
||||
|
||||
SQLiteDatabase db = Cache.openDatabase();
|
||||
SQLiteStatement statement = db.compileStatement(ADD_QUERY);
|
||||
db.beginTransaction();
|
||||
|
||||
try
|
||||
{
|
||||
SQLiteStatement statement = db.compileStatement(query);
|
||||
|
||||
for (Score s : scores)
|
||||
{
|
||||
statement.bindLong(1, habit.getId());
|
||||
@@ -86,20 +90,20 @@ public class SQLiteScoreList extends ScoreList
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public List<Score> getByInterval(long fromTimestamp, long toTimestamp)
|
||||
public synchronized List<Score> getByInterval(long fromTimestamp,
|
||||
long toTimestamp)
|
||||
{
|
||||
check(habit.getId());
|
||||
compute(fromTimestamp, toTimestamp);
|
||||
|
||||
String query = "select habit, timestamp, score " +
|
||||
"from Score " +
|
||||
"where habit = ? and timestamp >= ? and timestamp <= ? " +
|
||||
"order by timestamp desc";
|
||||
String query = "select habit, timestamp, score from Score " +
|
||||
"where habit = ? and timestamp >= ? and timestamp <= ? " +
|
||||
"order by timestamp desc";
|
||||
|
||||
String params[] = {
|
||||
Long.toString(habit.getId()),
|
||||
Long.toString(fromTimestamp),
|
||||
Long.toString(toTimestamp)
|
||||
Long.toString(habit.getId()),
|
||||
Long.toString(fromTimestamp),
|
||||
Long.toString(toTimestamp)
|
||||
};
|
||||
|
||||
List<ScoreRecord> records = sqlite.query(query, params);
|
||||
@@ -124,14 +128,23 @@ public class SQLiteScoreList extends ScoreList
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateNewerThan(long timestamp)
|
||||
public synchronized int getTodayValue()
|
||||
{
|
||||
new Delete()
|
||||
.from(ScoreRecord.class)
|
||||
.where("habit = ?", habit.getId())
|
||||
.and("timestamp >= ?", timestamp)
|
||||
.execute();
|
||||
if (cache == null || cache.expired())
|
||||
cache = new CachedData(super.getTodayValue());
|
||||
|
||||
return cache.todayValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void invalidateNewerThan(long timestamp)
|
||||
{
|
||||
cache = null;
|
||||
SQLiteDatabase db = Cache.openDatabase();
|
||||
SQLiteStatement statement = db.compileStatement(INVALIDATE_QUERY);
|
||||
statement.bindLong(1, habit.getId());
|
||||
statement.bindLong(2, timestamp);
|
||||
statement.execute();
|
||||
getObservable().notifyListeners();
|
||||
}
|
||||
|
||||
@@ -159,8 +172,7 @@ public class SQLiteScoreList extends ScoreList
|
||||
{
|
||||
check(habit.getId());
|
||||
String query = "select habit, timestamp, score from Score " +
|
||||
"where habit = ? order by timestamp desc " +
|
||||
"limit 1";
|
||||
"where habit = ? order by timestamp desc limit 1";
|
||||
|
||||
String params[] = { Long.toString(habit.getId()) };
|
||||
return getScoreFromQuery(query, params);
|
||||
@@ -172,8 +184,7 @@ public class SQLiteScoreList extends ScoreList
|
||||
{
|
||||
check(habit.getId());
|
||||
String query = "select habit, timestamp, score from Score " +
|
||||
"where habit = ? order by timestamp asc " +
|
||||
"limit 1";
|
||||
"where habit = ? order by timestamp asc limit 1";
|
||||
|
||||
String params[] = { Long.toString(habit.getId()) };
|
||||
return getScoreFromQuery(query, params);
|
||||
@@ -204,4 +215,22 @@ public class SQLiteScoreList extends ScoreList
|
||||
for (ScoreRecord r : records) scores.add(r.toScore());
|
||||
return scores;
|
||||
}
|
||||
|
||||
private static class CachedData
|
||||
{
|
||||
int todayValue;
|
||||
|
||||
private long today;
|
||||
|
||||
CachedData(int todayValue)
|
||||
{
|
||||
this.todayValue = todayValue;
|
||||
this.today = DateUtils.getStartOfToday();
|
||||
}
|
||||
|
||||
boolean expired()
|
||||
{
|
||||
return today != DateUtils.getStartOfToday();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.*;
|
||||
@@ -36,6 +37,10 @@ import java.util.*;
|
||||
*/
|
||||
public class SQLiteStreakList extends StreakList
|
||||
{
|
||||
|
||||
private static final String INVALIDATE_QUERY =
|
||||
"delete from Streak where habit = ? and end >= ?";
|
||||
|
||||
private HabitRecord habitRecord;
|
||||
|
||||
@NonNull
|
||||
@@ -73,12 +78,11 @@ 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();
|
||||
|
||||
SQLiteDatabase db = Cache.openDatabase();
|
||||
SQLiteStatement statement = db.compileStatement(INVALIDATE_QUERY);
|
||||
statement.bindLong(1, habit.getId());
|
||||
statement.bindLong(2, timestamp - DateUtils.millisecondsInOneDay);
|
||||
statement.execute();
|
||||
observable.notifyListeners();
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.database.*;
|
||||
import com.activeandroid.*;
|
||||
import com.activeandroid.annotation.*;
|
||||
|
||||
import org.apache.commons.lang3.builder.*;
|
||||
import org.isoron.uhabits.models.*;
|
||||
|
||||
/**
|
||||
@@ -53,6 +54,17 @@ public class CheckmarkRecord extends Model implements SQLiteRecord
|
||||
@Column(name = "value")
|
||||
public Integer value;
|
||||
|
||||
public CheckmarkRecord()
|
||||
{
|
||||
}
|
||||
|
||||
public CheckmarkRecord(HabitRecord habit, Long timestamp, Integer value)
|
||||
{
|
||||
this.habit = habit;
|
||||
this.timestamp = timestamp;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyFrom(Cursor c)
|
||||
{
|
||||
@@ -64,4 +76,40 @@ public class CheckmarkRecord extends Model implements SQLiteRecord
|
||||
{
|
||||
return new Checkmark(timestamp, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (this == o) return true;
|
||||
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
CheckmarkRecord that = (CheckmarkRecord) o;
|
||||
|
||||
return new EqualsBuilder()
|
||||
.append(habit, that.habit)
|
||||
.append(timestamp, that.timestamp)
|
||||
.append(value, that.value)
|
||||
.isEquals();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return new HashCodeBuilder(17, 37)
|
||||
.append(habit)
|
||||
.append(timestamp)
|
||||
.append(value)
|
||||
.toHashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return new ToStringBuilder(this)
|
||||
.append("habit", habit)
|
||||
.append("timestamp", timestamp)
|
||||
.append("value", value)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ package org.isoron.uhabits.notifications;
|
||||
import android.app.*;
|
||||
import android.content.*;
|
||||
import android.graphics.*;
|
||||
import android.os.*;
|
||||
import android.support.annotation.*;
|
||||
import android.support.v4.app.*;
|
||||
import android.support.v4.app.NotificationCompat.*;
|
||||
@@ -39,12 +40,15 @@ import java.util.*;
|
||||
import javax.inject.*;
|
||||
|
||||
import static android.graphics.BitmapFactory.*;
|
||||
import static android.os.Build.VERSION.*;
|
||||
import static org.isoron.uhabits.utils.RingtoneUtils.*;
|
||||
|
||||
@AppScope
|
||||
public class NotificationTray
|
||||
implements CommandRunner.Listener, Preferences.Listener
|
||||
{
|
||||
public static final String REMINDERS_CHANNEL_ID = "REMINDERS";
|
||||
|
||||
@NonNull
|
||||
private final Context context;
|
||||
|
||||
@@ -196,10 +200,6 @@ public class NotificationTray
|
||||
context.getString(R.string.check),
|
||||
pendingIntents.addCheckmark(habit, timestamp));
|
||||
|
||||
Action snoozeAction = new Action(R.drawable.ic_action_snooze,
|
||||
context.getString(R.string.snooze),
|
||||
pendingIntents.snoozeNotification(habit));
|
||||
|
||||
Bitmap wearableBg =
|
||||
decodeResource(context.getResources(), R.drawable.stripe);
|
||||
|
||||
@@ -208,30 +208,44 @@ public class NotificationTray
|
||||
// WearableExtender.
|
||||
WearableExtender wearableExtender = new WearableExtender()
|
||||
.setBackground(wearableBg)
|
||||
.addAction(checkAction)
|
||||
.addAction(snoozeAction);
|
||||
.addAction(checkAction);
|
||||
|
||||
Notification notification = new NotificationCompat.Builder(context)
|
||||
Builder builder = new Builder(context, REMINDERS_CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setContentTitle(habit.getName())
|
||||
.setContentText(habit.getDescription())
|
||||
.setContentIntent(pendingIntents.showHabit(habit))
|
||||
.setDeleteIntent(pendingIntents.dismissNotification(habit))
|
||||
.addAction(checkAction)
|
||||
.addAction(snoozeAction)
|
||||
.setSound(getRingtoneUri(context))
|
||||
.extend(wearableExtender)
|
||||
.setWhen(reminderTime)
|
||||
.setShowWhen(true)
|
||||
.setOngoing(preferences.shouldMakeNotificationsSticky())
|
||||
.build();
|
||||
.setOngoing(preferences.shouldMakeNotificationsSticky());
|
||||
|
||||
if(SDK_INT < Build.VERSION_CODES.O) {
|
||||
Action snoozeAction = new Action(R.drawable.ic_action_snooze,
|
||||
context.getString(R.string.snooze),
|
||||
pendingIntents.snoozeNotification(habit));
|
||||
|
||||
wearableExtender.addAction(snoozeAction);
|
||||
builder.addAction(snoozeAction);
|
||||
}
|
||||
|
||||
builder.extend(wearableExtender);
|
||||
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) context.getSystemService(
|
||||
Activity.NOTIFICATION_SERVICE);
|
||||
|
||||
createAndroidNotificationChannel(context);
|
||||
int notificationId = getNotificationId(habit);
|
||||
notificationManager.notify(notificationId, notification);
|
||||
|
||||
try {
|
||||
notificationManager.notify(notificationId, builder.build());
|
||||
} catch(RuntimeException e) {
|
||||
builder.setSound(null);
|
||||
notificationManager.notify(notificationId, builder.build());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldShowReminderToday()
|
||||
@@ -245,4 +259,19 @@ public class NotificationTray
|
||||
return reminderDays[weekday];
|
||||
}
|
||||
}
|
||||
|
||||
public static void createAndroidNotificationChannel(Context context) {
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) context.getSystemService(
|
||||
Activity.NOTIFICATION_SERVICE);
|
||||
|
||||
if (SDK_INT >= Build.VERSION_CODES.O)
|
||||
{
|
||||
NotificationChannel channel =
|
||||
new NotificationChannel(REMINDERS_CHANNEL_ID,
|
||||
context.getResources().getString(R.string.reminder),
|
||||
NotificationManager.IMPORTANCE_DEFAULT);
|
||||
notificationManager.createNotificationChannel(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ import org.isoron.uhabits.utils.*;
|
||||
|
||||
import javax.inject.*;
|
||||
|
||||
import static org.isoron.uhabits.utils.DateUtils.*;
|
||||
|
||||
@ReceiverScope
|
||||
public class ReminderController
|
||||
{
|
||||
@@ -66,7 +68,7 @@ public class ReminderController
|
||||
{
|
||||
long snoozeInterval = preferences.getSnoozeInterval();
|
||||
|
||||
long now = DateUtils.getLocalTime();
|
||||
long now = applyTimezone(getLocalTime());
|
||||
long reminderTime = now + snoozeInterval * 60 * 1000;
|
||||
|
||||
reminderScheduler.schedule(habit, reminderTime);
|
||||
|
||||
@@ -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.*;
|
||||
@@ -76,7 +77,16 @@ 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")
|
||||
|
||||
@@ -42,8 +42,7 @@ public abstract class DateUtils
|
||||
public static long applyTimezone(long localTimestamp)
|
||||
{
|
||||
TimeZone tz = getTimezone();
|
||||
long now = new Date(localTimestamp).getTime();
|
||||
return now - tz.getOffset(now);
|
||||
return localTimestamp - tz.getOffset(localTimestamp - tz.getOffset(localTimestamp));
|
||||
}
|
||||
|
||||
public static String formatHeaderDate(GregorianCalendar day)
|
||||
@@ -230,8 +229,7 @@ public abstract class DateUtils
|
||||
public static long removeTimezone(long timestamp)
|
||||
{
|
||||
TimeZone tz = getTimezone();
|
||||
long now = new Date(timestamp).getTime();
|
||||
return now + tz.getOffset(now);
|
||||
return timestamp + tz.getOffset(timestamp);
|
||||
}
|
||||
|
||||
public static void setFixedLocalTime(Long timestamp)
|
||||
|
||||
@@ -22,7 +22,9 @@ package org.isoron.uhabits.utils;
|
||||
import android.content.*;
|
||||
import android.content.res.*;
|
||||
import android.graphics.*;
|
||||
import android.support.v4.view.*;
|
||||
import android.util.*;
|
||||
import android.view.*;
|
||||
|
||||
public abstract class InterfaceUtils
|
||||
{
|
||||
@@ -49,4 +51,10 @@ public abstract class InterfaceUtils
|
||||
DisplayMetrics metrics = resources.getDisplayMetrics();
|
||||
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, metrics);
|
||||
}
|
||||
|
||||
public static boolean isLayoutRtl(View view)
|
||||
{
|
||||
return ViewCompat.getLayoutDirection(view) ==
|
||||
ViewCompat.LAYOUT_DIRECTION_RTL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -146,6 +146,14 @@
|
||||
style="@style/About.Item"
|
||||
android:text="Angga Rifandi (Bahasa Indonesia)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="raden20 (Bahasa Indonesia)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="azzamsa (Bahasa Indonesia)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="David Nos (Català)"/>
|
||||
@@ -178,6 +186,10 @@
|
||||
style="@style/About.Item"
|
||||
android:text="Can Altas (Deutsch)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Laura Sophie (Deutsch)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Ander Raso Vazquez (Español)"/>
|
||||
@@ -186,6 +198,10 @@
|
||||
style="@style/About.Item"
|
||||
android:text="Beriain (Euskara)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Osoitz (Euskara)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Andreas Michelakis (Ελληνικά)"/>
|
||||
@@ -198,6 +214,10 @@
|
||||
style="@style/About.Item"
|
||||
android:text="Saeed Esmaili (Fārsi)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Behnood HRazy (Fārsi)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="François Mahé (Français)"/>
|
||||
@@ -214,6 +234,10 @@
|
||||
style="@style/About.Item"
|
||||
android:text="Michael Faille (Français)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Tiralka (Français)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Ivan Krušlin (Hrvatski)"/>
|
||||
@@ -230,6 +254,10 @@
|
||||
style="@style/About.Item"
|
||||
android:text="Jelle den Butter (Nederlands)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="nitovf9292 (Norsk)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Adam Jurkiewicz (Polski)"/>
|
||||
@@ -250,18 +278,26 @@
|
||||
style="@style/About.Item"
|
||||
android:text="Andrei Pleș (Română)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Andreea Muscalagiu (Română)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Dušan Strgar (Slovenščina)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Dalecarlian (Svenska)"/>
|
||||
android:text="Alexander Jansson (Svenska)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Robin (Svenska)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Sofia Veijonen (Suomen kieli)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Đorđe Vasiljević (српски)"/>
|
||||
@@ -270,6 +306,10 @@
|
||||
style="@style/About.Item"
|
||||
android:text="Caner Başaran (Türkçe)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="hodanli (Türkçe)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Yurii Stavytskyi (Українська)"/>
|
||||
@@ -278,6 +318,14 @@
|
||||
style="@style/About.Item"
|
||||
android:text="Rystard (Українська)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Oglaigh Rystard (Українська)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="taras-ko (Українська)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Limin Lu (中文)"/>
|
||||
@@ -310,13 +358,62 @@
|
||||
style="@style/About.Item"
|
||||
android:text="Al Alloush (العَرَبِية)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Boula (العَرَبِية)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Israa Z (العَرَبِية)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Josh Graham (한국어 )"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Seoyul (한국어 )"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Aman Satnami (हिन्दी)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Niraj Yadav (हिन्दी)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Yoav Argov (עברית)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Mahdi Nasiri (فارسی)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Mohammed Imthath (தமிழ்)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="magimai (தமிழ்)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Anshoe (தமிழ்)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Trần Thái (Tiếng Việt)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="Anh Quân (Tiếng Việt)"/>
|
||||
|
||||
<TextView
|
||||
style="@style/About.Item"
|
||||
android:text="pnhpnh (Tiếng Việt)"/>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.7 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
@@ -19,6 +19,34 @@
|
||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<resources>
|
||||
<string name="app_name">Herhaalde Gewoonte Boekhouer</string>
|
||||
<string name="main_activity_title">Gewoontes</string>
|
||||
<string name="action_settings">Instellings</string>
|
||||
<string name="edit">Redigeer</string>
|
||||
<string name="delete">Verwyder</string>
|
||||
<string name="archive">Argiveer</string>
|
||||
<string name="unarchive">Deargiveer</string>
|
||||
<string name="add_habit">Voeg gewoonte by</string>
|
||||
<string name="color_picker_default_title">Verander kleur</string>
|
||||
<string name="toast_habit_created">Gewoonte geskep</string>
|
||||
<string name="toast_habit_deleted">Gewoontes verwyder</string>
|
||||
<string name="toast_habit_restored">Gewoontes herstel</string>
|
||||
<string name="toast_nothing_to_undo">Niks om terug te doen nie</string>
|
||||
<string name="toast_nothing_to_redo">Niks om oor te doen nie</string>
|
||||
<string name="toast_habit_changed">Gewoonte verander</string>
|
||||
<string name="toast_habit_changed_back">Gewoonte terug verander</string>
|
||||
<string name="toast_habit_archived">Gewoontes geargiveer</string>
|
||||
<string name="toast_habit_unarchived">Gewoontes gedeargiveer</string>
|
||||
<string name="overview">Oorsig</string>
|
||||
<!-- App introduction -->
|
||||
<string name="intro_title_1">Welkom</string>
|
||||
<string name="interval_15_minutes">15 minute</string>
|
||||
<string name="interval_30_minutes">30 minute</string>
|
||||
<string name="interval_1_hour">1 uur</string>
|
||||
<string name="interval_2_hour">2 ure</string>
|
||||
<string name="interval_4_hour">4 ure</string>
|
||||
<string name="interval_8_hour">8 ure</string>
|
||||
<string name="interval_24_hour">24 ure</string>
|
||||
<string name="settings">Instellings</string>
|
||||
<!-- Middle part of the sentence '1 time in xx days' -->
|
||||
</resources>
|
||||
|
||||
@@ -19,63 +19,63 @@
|
||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<resources>
|
||||
<string name="app_name">لوب ملاحق العادة </string>
|
||||
<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="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="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="history">السجل</string>
|
||||
<string name="clear">إزالة</string>
|
||||
<string name="description_hint">السؤال (هل ... اليوم؟)</string>
|
||||
<string name="repeat">كرر</string>
|
||||
<string name="times_every">مرات في</string>
|
||||
<string name="repeat">كرره</string>
|
||||
<string name="times_every">مرات كل</string>
|
||||
<string name="days">أيام</string>
|
||||
<string name="reminder">تذكير</string>
|
||||
<string name="discard">حذف</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="streaks">الانجازات</string>
|
||||
<string name="no_habits_found">لا يوجد لديك عادات مفعلة</string>
|
||||
<string name="long_press_to_toggle">أضغط و إستمر لتحقق أو ازل</string>
|
||||
<string name="reminder_off">أوقف</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="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>
|
||||
<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_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="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="interval_2_hour">ساعتان</string>
|
||||
<string name="interval_4_hour">٤ ساعات</string>
|
||||
<string name="interval_8_hour">8 ساعات</string>
|
||||
<string name="interval_24_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>
|
||||
@@ -92,6 +92,7 @@
|
||||
<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>
|
||||
@@ -156,7 +157,22 @@
|
||||
<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>
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<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="overview">Обзор</string>
|
||||
<string name="habit_strength">Сила на навика</string>
|
||||
<string name="history">История</string>
|
||||
<string name="clear">Изчистване</string>
|
||||
@@ -57,7 +57,7 @@
|
||||
<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="check">Поставяне на отметка</string>
|
||||
<string name="snooze">По-късно</string>
|
||||
<!-- App introduction -->
|
||||
<string name="intro_title_1">Добре дошли</string>
|
||||
@@ -65,8 +65,8 @@
|
||||
<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_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>
|
||||
@@ -74,6 +74,7 @@
|
||||
<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>
|
||||
@@ -91,6 +92,7 @@
|
||||
<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>
|
||||
@@ -100,7 +102,7 @@
|
||||
<string name="clear_label">Изчистване</string>
|
||||
<string name="select_hours">Избиране на час</string>
|
||||
<string name="select_minutes">Избиране на минута</string>
|
||||
<string name="about">Информация</string>
|
||||
<string name="about">За приложението</string>
|
||||
<string name="translators">Преводачи</string>
|
||||
<string name="developers">Разработчици</string>
|
||||
<string name="version_n">Версия %s</string>
|
||||
@@ -146,6 +148,7 @@
|
||||
<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>
|
||||
@@ -154,4 +157,22 @@
|
||||
<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>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<resources>
|
||||
<string name="app_name">Sledování zvyků</string>
|
||||
<string name="app_name">Loop Habit Tracker</string>
|
||||
<string name="main_activity_title">Zvyky</string>
|
||||
<string name="action_settings">Nastavení</string>
|
||||
<string name="edit">Upravit</string>
|
||||
@@ -45,11 +45,11 @@
|
||||
<string name="repeat">Opakovat</string>
|
||||
<string name="times_every">krát za</string>
|
||||
<string name="days">dní</string>
|
||||
<string name="reminder">Připomenout</string>
|
||||
<string name="reminder">Připomenutí</string>
|
||||
<string name="discard">Zrušit</string>
|
||||
<string name="save">Uložit</string>
|
||||
<string name="streaks">Serie</string>
|
||||
<string name="no_habits_found">Nemáš žádné aktivní zvyky</string>
|
||||
<string name="no_habits_found">Nemáte žádné nedokončené zvyky</string>
|
||||
<string name="long_press_to_toggle">Stiskni a drž pro označení</string>
|
||||
<string name="reminder_off">Vyp.</string>
|
||||
<string name="validation_name_should_not_be_blank">Jméno musíte vyplnit.</string>
|
||||
@@ -57,7 +57,7 @@
|
||||
<string name="validation_at_most_one_rep_per_day">Můžete mít maximálně jedno označení denně.</string>
|
||||
<string name="create_habit">Vytvořit zvyk</string>
|
||||
<string name="edit_habit">Upravit zvyk</string>
|
||||
<string name="check">Označeno</string>
|
||||
<string name="check">Hotovo</string>
|
||||
<string name="snooze">Odložit</string>
|
||||
<!-- App introduction -->
|
||||
<string name="intro_title_1">Vítejte</string>
|
||||
@@ -91,13 +91,13 @@
|
||||
<string name="hint_drag">Přeřazení záznamů proveď stisknutím a podržením názvu zvyku a poté přesunutím na správné místo.</string>
|
||||
<string name="hint_landscape">Můžeš vidět více dnů otočením telefonu na šířku.</string>
|
||||
<string name="delete_habits">Smazat zvyky</string>
|
||||
<string name="delete_habits_message">Zvyky budou navždy odstraněny. Toto nelze vzít zpět.</string>
|
||||
<string name="delete_habits_message">Označené zvyky budou navždy odstraněny. Toto nelze vzít zpět.</string>
|
||||
<string name="habit_not_found">Zvyk smazán / nenalezen</string>
|
||||
<string name="weekends">Víkendy</string>
|
||||
<string name="any_weekday">Pondělí až pátek</string>
|
||||
<string name="any_day">Jakýkoliv den v týdnu</string>
|
||||
<string name="select_weekdays">Vyber dny</string>
|
||||
<string name="export_to_csv">Exportuj CSV</string>
|
||||
<string name="export_to_csv">Exportovat CSV</string>
|
||||
<string name="done_label">Hotovo</string>
|
||||
<string name="clear_label">Smazat</string>
|
||||
<string name="select_hours">Vyber hodiny</string>
|
||||
@@ -169,7 +169,7 @@
|
||||
<string name="action">Akce</string>
|
||||
<string name="habit">Zvyk</string>
|
||||
<string name="sort">Řadit</string>
|
||||
<string name="manually">Manuálně</string>
|
||||
<string name="manually">Ručně</string>
|
||||
<string name="by_name">Abecedně</string>
|
||||
<string name="by_color">Podle barvy</string>
|
||||
<string name="by_score">Podle skóre</string>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<resources>
|
||||
<string name="app_name">Loop Habit Tracker</string>
|
||||
<string name="app_name">Loop \"Gewohnheiten Tracking\"</string>
|
||||
<string name="main_activity_title">Gewohnheiten</string>
|
||||
<string name="action_settings">Einstellungen</string>
|
||||
<string name="edit">Bearbeiten</string>
|
||||
@@ -48,26 +48,26 @@
|
||||
<string name="reminder">Erinnerung</string>
|
||||
<string name="discard">Verwerfen</string>
|
||||
<string name="save">Speichern</string>
|
||||
<string name="streaks">Strähnen</string>
|
||||
<string name="streaks">Serien</string>
|
||||
<string name="no_habits_found">Du hast keine aktiven Gewohnheiten</string>
|
||||
<string name="long_press_to_toggle">Tippe und halte um aus- bzw. abzuwählen</string>
|
||||
<string name="reminder_off">Aus</string>
|
||||
<string name="validation_name_should_not_be_blank">Name darf nicht leer sein.</string>
|
||||
<string name="validation_number_should_be_positive">Zahl muss positiv sein.</string>
|
||||
<string name="validation_at_most_one_rep_per_day">Du musst wenigstens eine Wiederholung pro Tag haben</string>
|
||||
<string name="validation_at_most_one_rep_per_day">Du kannst höchstens eine Wiederholung pro Tag haben</string>
|
||||
<string name="create_habit">Gewohnheit erstellen</string>
|
||||
<string name="edit_habit">Gewohnheit bearbeiten</string>
|
||||
<string name="check">Abhaken</string>
|
||||
<string name="check">Markieren</string>
|
||||
<string name="snooze">Später</string>
|
||||
<!-- App introduction -->
|
||||
<string name="intro_title_1">Willkommen</string>
|
||||
<string name="intro_description_1">Loop Habit Tracker hilft dir gute Gewohnheiten implementieren.</string>
|
||||
<string name="intro_description_1">Loop Habit Tracker hilft dir dabei, gute Gewohnheiten anzunehmen.</string>
|
||||
<string name="intro_title_2">Erstelle neue Gewohnheiten</string>
|
||||
<string name="intro_description_2">Jeden Tag, nachdem du die Gewohnheit gemacht hast, hake sie in der App ab.</string>
|
||||
<string name="intro_description_2">Hake die Gewohnheit jeden Tag in der App ab, nachdem du sie erledigt hast.</string>
|
||||
<string name="intro_title_3">Bleib dran</string>
|
||||
<string name="intro_description_3">Gewohnheiten, die über einen längeren Zeitraum absolviert werden, bekommen einen ganzen Stern.</string>
|
||||
<string name="intro_title_4">Verfolge deinen Fortschritt</string>
|
||||
<string name="intro_description_4">Detaillierte Diagramme zeigen dir an wie sich deine Gewohnheiten entwickelt haben.</string>
|
||||
<string name="intro_description_4">Detaillierte Diagramme zeigen dir an, wie sich deine Gewohnheiten entwickelt haben.</string>
|
||||
<string name="interval_15_minutes">15 Minuten</string>
|
||||
<string name="interval_30_minutes">30 Minuten</string>
|
||||
<string name="interval_1_hour">1 Stunde</string>
|
||||
@@ -75,29 +75,29 @@
|
||||
<string name="interval_4_hour">4 Stunden</string>
|
||||
<string name="interval_8_hour">8 Stunden</string>
|
||||
<string name="interval_24_hour">24 Stunden</string>
|
||||
<string name="pref_toggle_title">Gewohnheiten durch kurzes Tippen abhaken</string>
|
||||
<string name="pref_toggle_description">Abhaken durch einfaches Tippen, anstatt durch Tippen und Halten. Bequemer, kann aber eine falsche Auswahl verursachen.</string>
|
||||
<string name="pref_toggle_title">Markierung durch kurzes Drücken ändern</string>
|
||||
<string name="pref_toggle_description">Markierungen durch einfaches Tippen setzen anstatt durch Tippen und Halten. Bequemer, kann aber versehentlich eine Markierung ändern.</string>
|
||||
<string name="pref_snooze_interval_title">\"Später erinnern\"-Intervall bei Erinnerungen</string>
|
||||
<string name="pref_rate_this_app">Bewerte diese App auf Google Play</string>
|
||||
<string name="pref_send_feedback">Sende dem Entwickler Feedback</string>
|
||||
<string name="pref_view_source_code">Zeige den Quellcode auf GitHub</string>
|
||||
<string name="pref_view_app_introduction">Zeige die Anleitung</string>
|
||||
<string name="pref_view_app_introduction">Zeige die App-Einführung</string>
|
||||
<string name="links">Links</string>
|
||||
<string name="behavior">Verhalten</string>
|
||||
<string name="name">Name</string>
|
||||
<string name="settings">Einstellungen</string>
|
||||
<string name="snooze_interval">\"Später erinnern\"-Intervall</string>
|
||||
<string name="hint_title">Wusstest du?</string>
|
||||
<string name="hint_drag">Um Einträge umzusortieren, tippe und ziehe sie an die richtige Stelle.</string>
|
||||
<string name="hint_drag">Um Einträge umzusortieren, tippe, halte und ziehe sie an die richtige Stelle.</string>
|
||||
<string name="hint_landscape">Du kannst mehr Tage sehen, wenn du dein Smartphone quer hältst.</string>
|
||||
<string name="delete_habits">Gewohnheiten löschen</string>
|
||||
<string name="delete_habits_message">Diese Gewohnheit wird permanent gelöscht. Dies kann nicht rückgängig gemacht werden.</string>
|
||||
<string name="delete_habits">Lösche Gewohnheiten</string>
|
||||
<string name="delete_habits_message">Die Gewohnheit wird für immer gelöscht. Dies kann nicht rückgängig gemacht werden.</string>
|
||||
<string name="habit_not_found">Gewohnheit gelöscht / nicht gefunden</string>
|
||||
<string name="weekends">An Wochenenden</string>
|
||||
<string name="any_weekday">Werktags</string>
|
||||
<string name="any_weekday">Montag bis Freitag</string>
|
||||
<string name="any_day">Jeden Tag</string>
|
||||
<string name="select_weekdays">Wähle die Tage</string>
|
||||
<string name="export_to_csv">als CSV exportieren</string>
|
||||
<string name="select_weekdays">Wähle Tage aus</string>
|
||||
<string name="export_to_csv">Exportiere als CSV</string>
|
||||
<string name="done_label">Fertig</string>
|
||||
<string name="clear_label">Löschen</string>
|
||||
<string name="select_hours">Wähle Stunden</string>
|
||||
@@ -106,11 +106,11 @@
|
||||
<string name="translators">Übersetzer</string>
|
||||
<string name="developers">Entwickler</string>
|
||||
<string name="version_n">Version %s</string>
|
||||
<string name="frequency">Frequenz</string>
|
||||
<string name="frequency">Häufigkeit</string>
|
||||
<string name="checkmark">Häkchen</string>
|
||||
<string name="strength">Stärke</string>
|
||||
<string name="best_streaks">Beste Strähnen</string>
|
||||
<string name="current_streaks">Derzeitige Strähne</string>
|
||||
<string name="best_streaks">Beste Serien</string>
|
||||
<string name="current_streaks">Derzeitige Serie</string>
|
||||
<string name="number_of_repetitions">Anzahl der Wiederholungen</string>
|
||||
<string name="last_x_days">Letzten %d Tage</string>
|
||||
<string name="last_x_weeks">Letzten %d Wochen</string>
|
||||
@@ -123,25 +123,25 @@
|
||||
<string name="five_times_per_week">5 Mal pro Woche</string>
|
||||
<string name="custom_frequency">Benutzerdefiniert</string>
|
||||
<string name="help">Hilfe & FAQ</string>
|
||||
<string name="could_not_export">Der Export von Daten ist fehlgeschlagen.</string>
|
||||
<string name="could_not_import">Der Import von Daten ist fehlgeschlagen.</string>
|
||||
<string name="could_not_export">Fehler beim Exportieren der Daten.</string>
|
||||
<string name="could_not_import">Fehler beim Importieren der Daten.</string>
|
||||
<string name="file_not_recognized">Datei nicht erkannt.</string>
|
||||
<string name="habits_imported">Gewohnheiten wurden erfolgreich importiert.</string>
|
||||
<string name="habits_imported">Gewohnheiten erfolgreich importiert.</string>
|
||||
<string name="full_backup_success">Vollständige Sicherung erfolgreich exportiert.</string>
|
||||
<string name="import_data">Importiere Daten</string>
|
||||
<string name="export_full_backup">Exportiere vollständige Sicherung</string>
|
||||
<string name="import_data_summary">Unterstützt vollständige Sicherungen dieser App, sowie erstellte Sicherungen von Tickmate, HabitBull oder Rewire. Siehe FAQ für mehr Information.</string>
|
||||
<string name="export_as_csv_summary">Erzeugt Dateien, die von Tabellenkalkulationsprogrammen, wie Microsoft Excel oder LibreOffice Calc, geöffnet werden können. Diese Dateien können nicht wieder importiert werden.</string>
|
||||
<string name="export_full_backup_summary">Erzeugt eine Datei, die alle deine Daten enthält. Diese Datei kann wieder importiert werden.</string>
|
||||
<string name="bug_report_failed">Fehlermeldung konnte nicht erstellt werden.</string>
|
||||
<string name="generate_bug_report">Einen Fehler melden</string>
|
||||
<string name="import_data_summary">Unterstützt vollständige Sicherungen dieser App sowie Sicherungen von Tickmate, HabitBull und Rewire. Siehe FAQ für weitere Informationen.</string>
|
||||
<string name="export_as_csv_summary">Erstellt Dateien, die von Tabellenkalkulationsprogrammen wie Microsoft Excel oder LibreOffice Calc geöffnet werden können. Diese Dateien können nicht wieder importiert werden.</string>
|
||||
<string name="export_full_backup_summary">Erstellt eine Datei, die alle deine Daten enthält. Diese Datei kann wieder importiert werden.</string>
|
||||
<string name="bug_report_failed">Fehler beim Erstellen eines Fehlerberichts.</string>
|
||||
<string name="generate_bug_report">Erstelle einen Fehlerbericht</string>
|
||||
<string name="troubleshooting">Fehlerbehebung</string>
|
||||
<string name="help_translate">Hilf diese App zu übersetzen</string>
|
||||
<string name="help_translate">Hilf mit, diese App zu übersetzen</string>
|
||||
<string name="night_mode">Nachtmodus</string>
|
||||
<string name="use_pure_black">Reines Schwarz im Nachtmodus verwenden</string>
|
||||
<string name="pure_black_description">Ersetzt das Grau im Hintergrund durch ein reines Schwarz im Nachtmodus. Reduziert den Stromverbrauch von Smartphones mit einem AMOLED Display.</string>
|
||||
<string name="use_pure_black">Verwende reines Schwarz im Nachtmodus</string>
|
||||
<string name="pure_black_description">Ersetzt im Nachtmodus das Grau im Hintergrund durch ein reines Schwarz. Reduziert den Stromverbrauch von Smartphones mit einem AMOLED Display.</string>
|
||||
<string name="interface_preferences">Oberfläche</string>
|
||||
<string name="reverse_days">Die Reihenfolge der Tage umkehren</string>
|
||||
<string name="reverse_days">Kehre die Tagesreihenfolge um</string>
|
||||
<string name="reverse_days_description">Zeigt die Tage im Hauptfenster in umgekehrter Reihenfolge an</string>
|
||||
<string name="day">Tag</string>
|
||||
<string name="week">Woche</string>
|
||||
@@ -151,18 +151,18 @@
|
||||
<string name="total">Gesamt</string>
|
||||
<!-- Middle part of the sentence '1 time in xx days' -->
|
||||
<string name="time_every">Mal in</string>
|
||||
<string name="every_x_days">Jeden %d Tag</string>
|
||||
<string name="every_x_weeks">Jede %d Woche</string>
|
||||
<string name="every_x_months">Jeden %d Monat</string>
|
||||
<string name="every_x_days">Alle %d Tage</string>
|
||||
<string name="every_x_weeks">Alle %d Wochen</string>
|
||||
<string name="every_x_months">Alle %d Monate</string>
|
||||
<string name="score">Wertung</string>
|
||||
<string name="reminder_sound">Benachrichtigungston</string>
|
||||
<string name="none">Kein Ton</string>
|
||||
<string name="reminder_sound">Erinnerungston</string>
|
||||
<string name="none">Keiner</string>
|
||||
<string name="filter">Filter</string>
|
||||
<string name="hide_completed">Erledigte Gewohnheiten ausblenden</string>
|
||||
<string name="hide_archived">Archivierte Gewohnheiten ausblenden</string>
|
||||
<string name="sticky_notifications">Permanente Benachrichtigungen</string>
|
||||
<string name="hide_completed">Abgeschlossene verbergen</string>
|
||||
<string name="hide_archived">Archivierte verbergen</string>
|
||||
<string name="sticky_notifications">Fixiere Benachrichtigungen</string>
|
||||
<string name="sticky_notifications_description">Verhindert das Wegwischen von Benachrichtigungen.</string>
|
||||
<string name="repair_database">Datenbank reparieren</string>
|
||||
<string name="repair_database">Repariere Datenbank</string>
|
||||
<string name="database_repaired">Datenbank repariert.</string>
|
||||
<string name="uncheck">Abwählen</string>
|
||||
<string name="toggle">Umschalten</string>
|
||||
@@ -174,5 +174,5 @@
|
||||
<string name="by_color">Nach Farbe</string>
|
||||
<string name="by_score">Nach Wertung</string>
|
||||
<string name="download">Runterladen</string>
|
||||
<string name="export">Export</string>
|
||||
<string name="export">Exportieren</string>
|
||||
</resources>
|
||||
|
||||
@@ -19,6 +19,76 @@
|
||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<resources>
|
||||
<string name="main_activity_title">Kutimoj</string>
|
||||
<string name="action_settings">Agordoj</string>
|
||||
<string name="edit">Redakti</string>
|
||||
<string name="delete">Forigi</string>
|
||||
<string name="archive">Arĥivo</string>
|
||||
<string name="unarchive">Elarĥivigi</string>
|
||||
<string name="add_habit">Aldonu kutimon</string>
|
||||
<string name="color_picker_default_title">Ŝanĝi koloron</string>
|
||||
<string name="toast_habit_changed">Kutimo ŝanĝita</string>
|
||||
<string name="toast_habit_archived">Kutimo arĥivita</string>
|
||||
<string name="habit_strength">Kutimo forteco</string>
|
||||
<string name="days">tagoj</string>
|
||||
<string name="reminder">Memorigaĵoj</string>
|
||||
<string name="discard">Nuligi</string>
|
||||
<string name="save">Konservi</string>
|
||||
<string name="streaks">Strioj</string>
|
||||
<string name="reminder_off">Neaktiva</string>
|
||||
<string name="snooze">Poste</string>
|
||||
<!-- App introduction -->
|
||||
<string name="intro_title_1">Bonvenon</string>
|
||||
<string name="interval_15_minutes">15 minutoj</string>
|
||||
<string name="interval_30_minutes">30 minutoj</string>
|
||||
<string name="settings">Agordoj</string>
|
||||
<string name="delete_habits">Forigi kutimojn</string>
|
||||
<string name="weekends">Semajnfinoj</string>
|
||||
<string name="any_weekday">Lundo al vendredo</string>
|
||||
<string name="any_day">Io semajntago</string>
|
||||
<string name="select_weekdays">Elekti tagojn</string>
|
||||
<string name="export_to_csv">Eksporti kiel CSV</string>
|
||||
<string name="done_label">Farite</string>
|
||||
<string name="select_hours">Elekti horojn</string>
|
||||
<string name="select_minutes">Elekti minutojn</string>
|
||||
<string name="about">Pri programo</string>
|
||||
<string name="translators">Tradukantoj</string>
|
||||
<string name="developers">Evoluigantoj</string>
|
||||
<string name="version_n">Versio %s</string>
|
||||
<string name="frequency">Frekvenco</string>
|
||||
<string name="strength">Forteco</string>
|
||||
<string name="number_of_repetitions">Nombro de ripetoj</string>
|
||||
<string name="last_x_days">Lastaj %d tagoj</string>
|
||||
<string name="last_x_weeks">Lastaj %d semajnoj</string>
|
||||
<string name="last_x_months">Lastaj %d monatoj</string>
|
||||
<string name="last_x_years">Lastaj %d jaroj</string>
|
||||
<string name="all_time">Ĉiuj tempoj</string>
|
||||
<string name="every_day">Ĉiu tago</string>
|
||||
<string name="every_week">Ĉiu semajno</string>
|
||||
<string name="two_times_per_week">Dufoje en semajno</string>
|
||||
<string name="five_times_per_week">Kvinfoje en semajno</string>
|
||||
<string name="help">Helpo & Ofte Demandite</string>
|
||||
<string name="file_not_recognized">Dosiero ne rekonita.</string>
|
||||
<string name="full_backup_success">Plena savkopio sukcese eksportita.</string>
|
||||
<string name="troubleshooting">Problemserĉado</string>
|
||||
<string name="night_mode">Nokta reĝimo</string>
|
||||
<string name="day">Tago</string>
|
||||
<string name="week">Semajno</string>
|
||||
<string name="month">Monato</string>
|
||||
<string name="quarter">Jarkvarono</string>
|
||||
<string name="year">Jaro</string>
|
||||
<!-- Middle part of the sentence '1 time in xx days' -->
|
||||
<string name="none">Nenio</string>
|
||||
<string name="filter">Filtrilo</string>
|
||||
<string name="hide_completed">Kaŝi kompletajn</string>
|
||||
<string name="hide_archived">Kaŝi arĥivitajn</string>
|
||||
<string name="repair_database">Ripari datumbazon</string>
|
||||
<string name="database_repaired">Datumbazon riparita.</string>
|
||||
<string name="action">Ago</string>
|
||||
<string name="habit">Kutimo</string>
|
||||
<string name="sort">Enkursigi</string>
|
||||
<string name="by_name">Laŭ nomo</string>
|
||||
<string name="by_color">Laŭ koloro</string>
|
||||
<string name="download">Elŝuti</string>
|
||||
<string name="export">Eksporti</string>
|
||||
</resources>
|
||||
|
||||
@@ -28,16 +28,16 @@
|
||||
<string name="unarchive">Desarchivar</string>
|
||||
<string name="add_habit">Agregar hábito</string>
|
||||
<string name="color_picker_default_title">Cambiar color</string>
|
||||
<string name="toast_habit_created">Hábito creado.</string>
|
||||
<string name="toast_habit_deleted">Hábitos eliminados.</string>
|
||||
<string name="toast_habit_restored">Hábitos restaurados.</string>
|
||||
<string name="toast_nothing_to_undo">Nada que deshacer.</string>
|
||||
<string name="toast_nothing_to_redo">Nada que rehacer.</string>
|
||||
<string name="toast_habit_changed">Hábito cambiado.</string>
|
||||
<string name="toast_habit_changed_back">Hábito cambiado devuelta.</string>
|
||||
<string name="toast_habit_archived">Hábitos archivados.</string>
|
||||
<string name="toast_habit_unarchived">Hábitos desarchivados.</string>
|
||||
<string name="overview">Visión general</string>
|
||||
<string name="toast_habit_created">Hábito creado</string>
|
||||
<string name="toast_habit_deleted">Hábitos eliminados</string>
|
||||
<string name="toast_habit_restored">Hábitos restaurados</string>
|
||||
<string name="toast_nothing_to_undo">Nada que deshacer</string>
|
||||
<string name="toast_nothing_to_redo">Nada que rehacer</string>
|
||||
<string name="toast_habit_changed">Hábito cambiado</string>
|
||||
<string name="toast_habit_changed_back">Cambio en hábito vuelto atrás</string>
|
||||
<string name="toast_habit_archived">Hábitos archivados</string>
|
||||
<string name="toast_habit_unarchived">Hábitos desarchivados</string>
|
||||
<string name="overview">Resumen</string>
|
||||
<string name="habit_strength">Fuerza del hábito</string>
|
||||
<string name="history">Historial</string>
|
||||
<string name="clear">Borrar</string>
|
||||
@@ -52,8 +52,8 @@
|
||||
<string name="no_habits_found">No tienes hábitos activos</string>
|
||||
<string name="long_press_to_toggle">Mantener apretado para marcar o desmarcar</string>
|
||||
<string name="reminder_off">Apagado</string>
|
||||
<string name="validation_name_should_not_be_blank">Nombre no puede estar en blanco.</string>
|
||||
<string name="validation_number_should_be_positive">Número debe ser positivo.</string>
|
||||
<string name="validation_name_should_not_be_blank">El nombre no puede quedar en blanco.</string>
|
||||
<string name="validation_number_should_be_positive">El número debe ser positivo.</string>
|
||||
<string name="validation_at_most_one_rep_per_day">Puedes tener como máximo una repetición por día</string>
|
||||
<string name="create_habit">Crear hábito</string>
|
||||
<string name="edit_habit">Editar hábito</string>
|
||||
@@ -63,48 +63,51 @@
|
||||
<string name="intro_title_1">Bienvenido</string>
|
||||
<string name="intro_description_1">Loop Analizador de Hábitos te ayuda a crear y mantener buenos hábitos.</string>
|
||||
<string name="intro_title_2">Crea algunos hábitos nuevos</string>
|
||||
<string name="intro_description_2">Cada día, después de realizar tu hábito, pon una marca en la app.</string>
|
||||
<string name="intro_title_3">Sigue haciéndolo.</string>
|
||||
<string name="intro_description_2">Cada día, después de realizar tu hábito, pon una marca en la aplicación.</string>
|
||||
<string name="intro_title_3">Sigue haciéndolo</string>
|
||||
<string name="intro_description_3">Los hábitos realizados consistentemente por un largo tiempo ganarán una estrella completa.</string>
|
||||
<string name="intro_title_4">Haz un seguimiento de tu progreso</string>
|
||||
<string name="intro_description_4">Detallados gráficos muestran como han mejorado tus hábitos con el tiempo.</string>
|
||||
<string name="intro_description_4">Gráficos detallados muestran cómo mejoraron sus hábitos con el tiempo.</string>
|
||||
<string name="interval_15_minutes">15 minutos</string>
|
||||
<string name="interval_30_minutes">30 minutos</string>
|
||||
<string name="interval_1_hour">1 hora</string>
|
||||
<string name="interval_2_hour">2 horas</string>
|
||||
<string name="interval_4_hour">4 horas</string>
|
||||
<string name="interval_8_hour">8 horas</string>
|
||||
<string name="pref_toggle_title">Marca las repeticiones con una corta pulsación.</string>
|
||||
<string name="interval_24_hour">24 horas</string>
|
||||
<string name="pref_toggle_title">Marca las repeticiones con una pulsación corta</string>
|
||||
<string name="pref_toggle_description">Más cómodo, pero puede causar marcas accidentales.</string>
|
||||
<string name="pref_snooze_interval_title">Tiempo de espera al aplazar recordatorios.</string>
|
||||
<string name="pref_rate_this_app">Valora esta app en Google Play</string>
|
||||
<string name="pref_snooze_interval_title">Tiempo de espera al aplazar recordatorios</string>
|
||||
<string name="pref_rate_this_app">Valora esta aplicación en Google Play</string>
|
||||
<string name="pref_send_feedback">Enviar sugerencias al desarrollador</string>
|
||||
<string name="pref_view_source_code">Ver código fuente en GitHub</string>
|
||||
<string name="pref_view_app_introduction">Ver la introducción de la app</string>
|
||||
<string name="pref_view_app_introduction">Ver la introducción de la aplicación</string>
|
||||
<string name="links">Enlaces</string>
|
||||
<string name="behavior">Comportamiento</string>
|
||||
<string name="name">Nombre</string>
|
||||
<string name="settings">Configuración</string>
|
||||
<string name="snooze_interval">Intervalo de espera</string>
|
||||
<string name="hint_title">¿Sabías qué?</string>
|
||||
<string name="hint_drag">Para reordenar las entradas, mantén la pulsación sobre el nombre del hábito, después arrástralo a su posición correcta.</string>
|
||||
<string name="hint_drag">Para reordenar las entradas, mantén la pulsado sobre el nombre del hábito, después arrástralo a su posición correcta.</string>
|
||||
<string name="hint_landscape">Puedes ver más días al poner tu teléfono en modo horizontal.</string>
|
||||
<string name="delete_habits">Eliminar Hábitos</string>
|
||||
<string name="delete_habits_message">Los hábitos serán eliminados permanentemente. Esta acción no se puede deshacer.</string>
|
||||
<string name="habit_not_found">Hábito eliminado / no encontrado</string>
|
||||
<string name="weekends">Fines de semana</string>
|
||||
<string name="any_weekday">Días laborables</string>
|
||||
<string name="any_weekday">De lunes a viernes</string>
|
||||
<string name="any_day">Cada día</string>
|
||||
<string name="select_weekdays">Seleccionar días</string>
|
||||
<string name="export_to_csv">Exportar datos (CSV)</string>
|
||||
<string name="done_label">Hecho</string>
|
||||
<string name="clear_label">Quitar</string>
|
||||
<string name="select_hours">Seleccionar horas</string>
|
||||
<string name="select_minutes">Seleccionar</string>
|
||||
<string name="select_minutes">Seleccionar minutos</string>
|
||||
<string name="about">Acerca de</string>
|
||||
<string name="translators">Traductores</string>
|
||||
<string name="developers">Desarrolladores</string>
|
||||
<string name="version_n">Versión %s</string>
|
||||
<string name="frequency">Frecuencia</string>
|
||||
<string name="checkmark">Marca de verificación</string>
|
||||
<string name="strength">Fuerza</string>
|
||||
<string name="best_streaks">Mejores rachas</string>
|
||||
<string name="current_streaks">Racha actual</string>
|
||||
@@ -120,8 +123,8 @@
|
||||
<string name="five_times_per_week">5 veces por semana</string>
|
||||
<string name="custom_frequency">Personalizado...</string>
|
||||
<string name="help">Ayuda & FAQ</string>
|
||||
<string name="could_not_export">Fallo al exportar datos.</string>
|
||||
<string name="could_not_import">Fallo al importar datos.</string>
|
||||
<string name="could_not_export">Error al exportar datos.</string>
|
||||
<string name="could_not_import">Error al importar datos.</string>
|
||||
<string name="file_not_recognized">Archivo no reconocido.</string>
|
||||
<string name="habits_imported">Hábitos importados exitosamente.</string>
|
||||
<string name="full_backup_success">Copia de seguridad exportada exitosamente.</string>
|
||||
@@ -130,8 +133,8 @@
|
||||
<string name="import_data_summary">Soporta exportar copias de seguridad completas, así como archivos generados por Tickmate, HabitBull o Rewire. Mira el FAQ para más información.</string>
|
||||
<string name="export_as_csv_summary">Genera archivos que pueden ser abiertos por programas de hojas de cálculo como Microsoft Excel o OpenOffice Calc. Este archivo no puede volver a importarse.</string>
|
||||
<string name="export_full_backup_summary">Genera un archivo que contiene todos tus datos. Este archivo puede volver a importarse.</string>
|
||||
<string name="bug_report_failed">Fallo al generar el informe del bug.</string>
|
||||
<string name="generate_bug_report">Generar informe de bug</string>
|
||||
<string name="bug_report_failed">Error al generar el reporte de error.</string>
|
||||
<string name="generate_bug_report">Generar reporte de errores</string>
|
||||
<string name="troubleshooting">Solución de problemas</string>
|
||||
<string name="help_translate">Ayuda a traducir esta app</string>
|
||||
<string name="night_mode">Modo nocturno</string>
|
||||
@@ -145,6 +148,7 @@
|
||||
<string name="month">Mes</string>
|
||||
<string name="quarter">Cuatrimestre</string>
|
||||
<string name="year">Año</string>
|
||||
<string name="total">Total</string>
|
||||
<!-- Middle part of the sentence '1 time in xx days' -->
|
||||
<string name="time_every">veces en</string>
|
||||
<string name="every_x_days">Cada %d días</string>
|
||||
@@ -153,4 +157,22 @@
|
||||
<string name="score">Puntuación</string>
|
||||
<string name="reminder_sound">Sonido de recordatorio</string>
|
||||
<string name="none">Ninguno</string>
|
||||
<string name="filter">Filtrar</string>
|
||||
<string name="hide_completed">Ocultar completos</string>
|
||||
<string name="hide_archived">Ocultar archivados</string>
|
||||
<string name="sticky_notifications">Hacer notificaciones fijas</string>
|
||||
<string name="sticky_notifications_description">Evita que las notificaciones sean descartadas.</string>
|
||||
<string name="repair_database">Reparar base de datos</string>
|
||||
<string name="database_repaired">Base de datos reparada.</string>
|
||||
<string name="uncheck">Desmarcar</string>
|
||||
<string name="toggle">Alternar</string>
|
||||
<string name="action">Acción</string>
|
||||
<string name="habit">Hábito</string>
|
||||
<string name="sort">Ordenar</string>
|
||||
<string name="manually">Manualmente</string>
|
||||
<string name="by_name">Por nombre</string>
|
||||
<string name="by_color">Por color</string>
|
||||
<string name="by_score">Por puntuación</string>
|
||||
<string name="download">Descargar</string>
|
||||
<string name="export">Exportar</string>
|
||||
</resources>
|
||||
|
||||
@@ -19,24 +19,26 @@
|
||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<resources>
|
||||
<string name="app_name">Loop Habit Tracker</string>
|
||||
<string name="main_activity_title">Ohiturak</string>
|
||||
<string name="action_settings">Ezarpenak</string>
|
||||
<string name="edit">Editatu</string>
|
||||
<string name="delete">Ezabatu</string>
|
||||
<string name="archive">Artxibatu</string>
|
||||
<string name="unarchive">Ezartxibatu</string>
|
||||
<string name="add_habit">Ohitura gehitu</string>
|
||||
<string name="unarchive">Desartxibatu</string>
|
||||
<string name="add_habit">Gehitu ohitura</string>
|
||||
<string name="color_picker_default_title">Kolorea aldatu</string>
|
||||
<string name="toast_habit_created">Ohitura sortu da.</string>
|
||||
<string name="toast_habit_deleted">Ohiturak ezabatu dira.</string>
|
||||
<string name="toast_habit_restored">Ohiturak berrezarri dira.</string>
|
||||
<string name="toast_nothing_to_undo">Ez dago desegiteko ezer.</string>
|
||||
<string name="toast_nothing_to_redo">Ez dago berregiteko ezer.</string>
|
||||
<string name="toast_habit_changed">Ohitura aldatu da.</string>
|
||||
<string name="toast_habit_changed_back">Ohitura lehengoratu da.</string>
|
||||
<string name="toast_habit_archived">Ohiturak artxibatu dira.</string>
|
||||
<string name="toast_habit_unarchived">Ohiturak ezartxibatu dira.</string>
|
||||
<string name="toast_habit_created">Ohitura sortu da</string>
|
||||
<string name="toast_habit_deleted">Ohiturak ezabatu dira</string>
|
||||
<string name="toast_habit_restored">Ohiturak berrezarri dira</string>
|
||||
<string name="toast_nothing_to_undo">Ez dago ezer desegiteko</string>
|
||||
<string name="toast_nothing_to_redo">Ez dago ezer berregiteko</string>
|
||||
<string name="toast_habit_changed">Ohitura aldatu egin da</string>
|
||||
<string name="toast_habit_changed_back">Ohitura berrezarri da</string>
|
||||
<string name="toast_habit_archived">Ohiturak artxibatu dira</string>
|
||||
<string name="toast_habit_unarchived">Ohiturak desartxibatu dira</string>
|
||||
<string name="overview">Ikuspegi orokorra</string>
|
||||
<string name="habit_strength">Ohituraren indarra</string>
|
||||
<string name="history">Historia</string>
|
||||
<string name="clear">Garbitu</string>
|
||||
<string name="description_hint">Galdera (... al duzu gaur?)</string>
|
||||
@@ -46,6 +48,7 @@
|
||||
<string name="reminder">Oroigarria</string>
|
||||
<string name="discard">Baztertu</string>
|
||||
<string name="save">Gorde</string>
|
||||
<string name="streaks">Boladak</string>
|
||||
<string name="no_habits_found">Ez duzu ohitura aktiborik</string>
|
||||
<string name="long_press_to_toggle">Sakatu eta mantendu markatu edo desmarkatzeko</string>
|
||||
<string name="reminder_off">Itzalita</string>
|
||||
@@ -54,12 +57,15 @@
|
||||
<string name="validation_at_most_one_rep_per_day">Gehienez errepikapen bat eguneko izan dezakezu</string>
|
||||
<string name="create_habit">Ohitura sortu</string>
|
||||
<string name="edit_habit">Ohitura editatu</string>
|
||||
<string name="snooze">Beranduago</string>
|
||||
<string name="check">Markatu</string>
|
||||
<string name="snooze">Geroago</string>
|
||||
<!-- App introduction -->
|
||||
<string name="intro_title_1">Ongi etorri</string>
|
||||
<string name="intro_description_1">Loop Habit Tracker-ek ohitura onak hartzen eta mantentzen laguntzen dizu.</string>
|
||||
<string name="intro_title_2">Sor itzazu ohitura berri batzuk</string>
|
||||
<string name="intro_description_2">Egunero, zure ohitura egin ostean, jarri ezazu egiaztatze marka bat aplikazioan.</string>
|
||||
<string name="intro_title_3">Jarrai ezazu ohitura egiten</string>
|
||||
<string name="intro_description_3">Denbora luzean zehar trinkotasunez egindako ohiturek izar oso bat irabaziko dute.</string>
|
||||
<string name="intro_title_4">Jarrai ezazu zure aurrerapena</string>
|
||||
<string name="intro_description_4">Grafiko zehatzen bitartez denboran zehar zure ohiturak nola hobetu diren ikus ditzakezu</string>
|
||||
<string name="interval_15_minutes">15 minutu</string>
|
||||
@@ -68,6 +74,10 @@
|
||||
<string name="interval_2_hour">2 ordu</string>
|
||||
<string name="interval_4_hour">4 ordu</string>
|
||||
<string name="interval_8_hour">8 ordu</string>
|
||||
<string name="interval_24_hour">24 ordu</string>
|
||||
<string name="pref_toggle_title">Ukitze laburrarekin markatu</string>
|
||||
<string name="pref_toggle_description">Ukitze bakar batekin marka jartzen du ukitu eta mantendu egin beharrean. Erosoagoa, baina nahi gabeko markak ekar litzake.</string>
|
||||
<string name="pref_snooze_interval_title">Atzeratze tartea oroigarrietan</string>
|
||||
<string name="pref_rate_this_app">Aplikazio hau Google Playen puntuatu</string>
|
||||
<string name="pref_send_feedback">Zure iritzia garatzaileari bidali</string>
|
||||
<string name="pref_view_source_code">Iturburu kodea GitHuben ikusi</string>
|
||||
@@ -76,11 +86,13 @@
|
||||
<string name="behavior">Portaera</string>
|
||||
<string name="name">Izena</string>
|
||||
<string name="settings">Ezarpenak</string>
|
||||
<string name="snooze_interval">Atzeratze tartea</string>
|
||||
<string name="hint_title">Ba al zenekien?</string>
|
||||
<string name="hint_drag">Sarrerak berrantolatzeko, sakatu eta mantendu ohituraren izena, ondoren mugi ezazu leku aproposera.</string>
|
||||
<string name="hint_landscape">Egun gehiago ikus ditzakezu zure gailua paisai moduan jarriz.</string>
|
||||
<string name="delete_habits">Ohiturak ezabatu</string>
|
||||
<string name="delete_habits_message">Ohiturak betirako ezabatuko dira. Ekintza hau ezin da desegin.</string>
|
||||
<string name="habit_not_found">Ohitura ezabatua / ez aurkitua</string>
|
||||
<string name="weekends">Asteburuak</string>
|
||||
<string name="any_weekday">Astelehenetik ostiralera</string>
|
||||
<string name="any_day">Astearen edozen egun</string>
|
||||
@@ -96,6 +108,9 @@
|
||||
<string name="version_n">%s bertsioa</string>
|
||||
<string name="frequency">Maiztasuna</string>
|
||||
<string name="checkmark">Egiaztatze marka</string>
|
||||
<string name="strength">Indarra</string>
|
||||
<string name="best_streaks">Bolada onenak</string>
|
||||
<string name="current_streaks">Uneko bolada</string>
|
||||
<string name="number_of_repetitions">Errepikapenen kopurua</string>
|
||||
<string name="last_x_days">Azken %d egunak</string>
|
||||
<string name="last_x_weeks">Azken %d asteak</string>
|
||||
@@ -115,18 +130,49 @@
|
||||
<string name="full_backup_success">Babes kopia osoa ondo esportatu da.</string>
|
||||
<string name="import_data">Datuak inportatu</string>
|
||||
<string name="export_full_backup">Babes kopia osoa esportatu</string>
|
||||
<string name="import_data_summary">Aplikazio honek esportatutako babes kopia osoak onartzen dira, baita Tickmate, HabitBull edo Rewirek sortutako fitxategiak ere. Ikusi ohito galderak informazio gehiago lortzeko.</string>
|
||||
<string name="export_as_csv_summary">Microsoft Excel edo OpenOffice Calc bezalako kalkulu orrietarako softwareak ireki dezaketen fitxategiak sortzen ditu. Fitxategi hau ezin da berriz inportatu.</string>
|
||||
<string name="export_full_backup_summary">Zure datu guztiak dituen fitxategi bat sortzen du. Fitxategi hau ezin da berriz inportatu.</string>
|
||||
<string name="bug_report_failed">Huts akats txostena sortzerakoan.</string>
|
||||
<string name="generate_bug_report">Akats txostena sortu</string>
|
||||
<string name="troubleshooting">Arazoen konponketa</string>
|
||||
<string name="help_translate">Lagundu aplikazio hau itzultzen</string>
|
||||
<string name="night_mode">Gau modua</string>
|
||||
<string name="use_pure_black">Benetazko beltza erabili gau moduan</string>
|
||||
<string name="pure_black_description">Atzeko plano grisak beltz hutsez aldatzen ditu gau moduan. Bateriaren erabilera gutxitzen du AMOLED duten gailuetan.</string>
|
||||
<string name="interface_preferences">Interfazea</string>
|
||||
<string name="reverse_days">Egunak atzekoz aurrera ordenatu</string>
|
||||
<string name="reverse_days_description">Pantaila nagusian egunak atzekoz aurrera ikusi</string>
|
||||
<string name="day">Eguna</string>
|
||||
<string name="week">Astea</string>
|
||||
<string name="month">Hilabetea</string>
|
||||
<string name="quarter">Hiruhilekoa</string>
|
||||
<string name="year">Urtea</string>
|
||||
<string name="total">Guztira</string>
|
||||
<!-- Middle part of the sentence '1 time in xx days' -->
|
||||
<string name="time_every">denbora</string>
|
||||
<string name="every_x_days">%d egunero</string>
|
||||
<string name="every_x_weeks">%d astero</string>
|
||||
<string name="every_x_months">%d hilabetero</string>
|
||||
<string name="score">Puntuak</string>
|
||||
<string name="reminder_sound">Oroigarriaren soinua</string>
|
||||
<string name="none">Bat ere ez</string>
|
||||
<string name="filter">Iragazkia</string>
|
||||
<string name="hide_completed">Ezkutatu lortutakoak</string>
|
||||
<string name="hide_archived">Artxibatutakoak ezkutatu</string>
|
||||
<string name="sticky_notifications">Jakinarazpenak itsaskorrak bihurtu</string>
|
||||
<string name="sticky_notifications_description">Jakinarazpenak keinu batez ezabatzea sahiesten du.</string>
|
||||
<string name="repair_database">Datu basea konpondu</string>
|
||||
<string name="database_repaired">Datu basea konpondu da.</string>
|
||||
<string name="uncheck">Desmarkatu</string>
|
||||
<string name="toggle">Aldatu</string>
|
||||
<string name="action">Ekintza</string>
|
||||
<string name="habit">Ohitura</string>
|
||||
<string name="sort">Ordenatu</string>
|
||||
<string name="manually">Eskuz</string>
|
||||
<string name="by_name">Izenaren arabera</string>
|
||||
<string name="by_color">Kolorearen arabera</string>
|
||||
<string name="by_score">Puntuen arabera</string>
|
||||
<string name="download">Deskargatu</string>
|
||||
<string name="export">Esportatu</string>
|
||||
</resources>
|
||||
|
||||
@@ -19,34 +19,34 @@
|
||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<resources>
|
||||
<string name="app_name">عادتسنج</string>
|
||||
<string name="app_name">Loop Habit Tracker</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="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_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="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="habit_strength">قدرت عادت</string>
|
||||
<string name="history">تاریخچه</string>
|
||||
<string name="clear">حذف</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="days">روز</string>
|
||||
<string name="reminder">یادآور</string>
|
||||
<string name="discard">حذف تغییرات</string>
|
||||
<string name="discard">بیخیال</string>
|
||||
<string name="save">ذخیره</string>
|
||||
<string name="streaks">روزهای پیوسته</string>
|
||||
<string name="no_habits_found">شما هیچ عادت فعالی ندارید</string>
|
||||
@@ -55,18 +55,18 @@
|
||||
<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="create_habit">درج عادت جدید</string>
|
||||
<string name="edit_habit">ویرایش عادت</string>
|
||||
<string name="check">تیک زدن</string>
|
||||
<string name="snooze">بعدا</string>
|
||||
<string name="snooze">بعداً</string>
|
||||
<!-- App introduction -->
|
||||
<string name="intro_title_1">خوش آمدید</string>
|
||||
<string name="intro_description_1">رهگیر عادت به شما کمک میکند تا برای خودتان عادتهای خوبی بسازید.</string>
|
||||
<string name="intro_description_1">رهگیر عادت لوپ به شما کمک میکند تا برای خودتان عادتهای خوبی بسازید.</string>
|
||||
<string name="intro_title_2">ساخت چند عادت جدید</string>
|
||||
<string name="intro_description_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_title_4">پیشرفت خود را رهگیری کنید</string>
|
||||
<string name="intro_description_4">نمودار جزئیات به شما نشان میدهد که چطور عادتهایتان با گذشت زمان بهبود پیدا کردهاند.</string>
|
||||
<string name="interval_15_minutes">۱۵ دقیقه</string>
|
||||
<string name="interval_30_minutes">۳۰ دقیقه</string>
|
||||
@@ -74,30 +74,40 @@
|
||||
<string name="interval_2_hour">۲ ساعت</string>
|
||||
<string name="interval_4_hour">۴ ساعت</string>
|
||||
<string name="interval_8_hour">۸ ساعت</string>
|
||||
<string name="interval_24_hour">۲۴ ساعت</string>
|
||||
<string name="pref_toggle_title">با اشارهی کوتاهمدت وضعیت عادت را تغییر بده</string>
|
||||
<string name="pref_toggle_description">راحتتر است ولی ممکن است باعث شود اشتباهی عادتی را تیک بزنید.</string>
|
||||
<string name="pref_rate_this_app">دادن امتیاز به اپ در گوگلپلی</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">دیدن سورس اپ در گیتهاب</string>
|
||||
<string name="pref_view_app_introduction">دیدن معرفی اپ</string>
|
||||
<string name="pref_view_source_code">دیدن منبع برنامه در گیتهاب</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="hint_title">آیا میدانید؟</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="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="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="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>
|
||||
@@ -106,20 +116,39 @@
|
||||
<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، HabitbBull و یا 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="night_mode">حالت شبانه</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>
|
||||
@@ -128,4 +157,22 @@
|
||||
<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>
|
||||
|
||||
@@ -19,6 +19,65 @@
|
||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<resources>
|
||||
<string name="app_name">Rutiini - Tracker</string>
|
||||
<string name="main_activity_title">Rutiinit</string>
|
||||
<string name="action_settings">Asetukset</string>
|
||||
<string name="edit">Muokkaa</string>
|
||||
<string name="delete">Poista</string>
|
||||
<string name="archive">Arkistoi</string>
|
||||
<string name="add_habit">Lisää rutiini</string>
|
||||
<string name="color_picker_default_title">Vaihda väriä</string>
|
||||
<string name="toast_habit_created">Rutiini luotu</string>
|
||||
<string name="toast_habit_deleted">Rutiinit poistettu</string>
|
||||
<string name="toast_habit_restored">Rutiinit palautettu</string>
|
||||
<string name="toast_habit_changed">Rutiini muutettu</string>
|
||||
<string name="toast_habit_changed_back">Rutiini muutettu takaisin</string>
|
||||
<string name="overview">Yleiskatsaus</string>
|
||||
<string name="history">Historia</string>
|
||||
<string name="clear">Tyhjennä</string>
|
||||
<string name="description_hint">Kysymys (Teitkö... tänään?)</string>
|
||||
<string name="repeat">Toista</string>
|
||||
<string name="times_every">kertaa</string>
|
||||
<string name="days">päivässä</string>
|
||||
<string name="reminder">Muistutus</string>
|
||||
<string name="discard">Hylkää</string>
|
||||
<string name="save">Tallenna</string>
|
||||
<string name="streaks">Pisimmät toistot</string>
|
||||
<string name="no_habits_found">Ei aktiivisia rutiineja</string>
|
||||
<string name="long_press_to_toggle">Paina pitkään merkitäksesi suoritetuksi tai postaaksesi suorituksen</string>
|
||||
<string name="reminder_off">Pois päältä</string>
|
||||
<string name="validation_name_should_not_be_blank">Nimi ei voi olla tyhjä.</string>
|
||||
<string name="validation_number_should_be_positive">Luvun on oltava positiivinen.</string>
|
||||
<string name="create_habit">Luo rutiini</string>
|
||||
<string name="edit_habit">Muokkaa rutiinia</string>
|
||||
<string name="check">Tehty</string>
|
||||
<string name="snooze">Lykkää</string>
|
||||
<!-- App introduction -->
|
||||
<string name="intro_title_1">Tervetuloa</string>
|
||||
<string name="intro_title_2">Merkitse uusia rutiineja</string>
|
||||
<string name="intro_description_2">Joka päivä, suoritettuasi rutiinin, merkitse se sovellukseen.</string>
|
||||
<string name="links">Linkit</string>
|
||||
<string name="behavior">Käyttäytyminen</string>
|
||||
<string name="name">Nimi</string>
|
||||
<string name="settings">Asetukset</string>
|
||||
<string name="hint_title">Tiesitkö?</string>
|
||||
<string name="done_label">Valmis</string>
|
||||
<string name="clear_label">Tyhjennä</string>
|
||||
<string name="translators">Kääntäjät</string>
|
||||
<string name="developers">Kehittäjät</string>
|
||||
<string name="version_n">Versio %s</string>
|
||||
<string name="every_day">Joka päivä</string>
|
||||
<string name="every_week">Joka viikko</string>
|
||||
<string name="two_times_per_week">2 kertaa viikossa</string>
|
||||
<string name="five_times_per_week">5 kertaa viikossa</string>
|
||||
<string name="custom_frequency">Mukautettu…</string>
|
||||
<string name="night_mode">Yötila</string>
|
||||
<string name="use_pure_black">Käytä puhdasta mustaa yötilassa</string>
|
||||
<string name="day">Päivä</string>
|
||||
<string name="week">Viikko</string>
|
||||
<string name="month">Kuukausi</string>
|
||||
<string name="quarter">Kvartaali</string>
|
||||
<string name="year">Vuosi</string>
|
||||
<string name="total">Yhteensä</string>
|
||||
<!-- Middle part of the sentence '1 time in xx days' -->
|
||||
</resources>
|
||||
|
||||
@@ -28,20 +28,20 @@
|
||||
<string name="unarchive">Désarchiver</string>
|
||||
<string name="add_habit">Ajouter une habitude</string>
|
||||
<string name="color_picker_default_title">Changer la couleur</string>
|
||||
<string name="toast_habit_created">Habitude créée.</string>
|
||||
<string name="toast_habit_deleted">Habitude supprimée.</string>
|
||||
<string name="toast_habit_restored">Habitude rétablie.</string>
|
||||
<string name="toast_nothing_to_undo">Rien à annuler.</string>
|
||||
<string name="toast_nothing_to_redo">Rien à refaire.</string>
|
||||
<string name="toast_habit_changed">Habitude changée.</string>
|
||||
<string name="toast_habit_changed_back">Habitude restaurée.</string>
|
||||
<string name="toast_habit_archived">Habitudes archivées.</string>
|
||||
<string name="toast_habit_unarchived">Habitudes désarchivées.</string>
|
||||
<string name="toast_habit_created">Habitude créée</string>
|
||||
<string name="toast_habit_deleted">Habitudes supprimées</string>
|
||||
<string name="toast_habit_restored">Habitudes rétablies</string>
|
||||
<string name="toast_nothing_to_undo">Rien à annuler</string>
|
||||
<string name="toast_nothing_to_redo">Rien à refaire</string>
|
||||
<string name="toast_habit_changed">Habitude changée</string>
|
||||
<string name="toast_habit_changed_back">Habitude restaurée</string>
|
||||
<string name="toast_habit_archived">Habitudes archivées</string>
|
||||
<string name="toast_habit_unarchived">Habitudes désarchivées</string>
|
||||
<string name="overview">Vue d\'ensemble</string>
|
||||
<string name="habit_strength">Force de l\'habitude</string>
|
||||
<string name="history">Historique</string>
|
||||
<string name="clear">Supprimer</string>
|
||||
<string name="description_hint">Question (As-tu ... aujourd\'hui?)</string>
|
||||
<string name="description_hint">Question (As-tu ... aujourd\'hui ?)</string>
|
||||
<string name="repeat">Répéter</string>
|
||||
<string name="times_every">fois en</string>
|
||||
<string name="days">jours</string>
|
||||
@@ -51,6 +51,7 @@
|
||||
<string name="streaks">Séries</string>
|
||||
<string name="no_habits_found">Vous n\'avez pas d\'habitudes actives</string>
|
||||
<string name="long_press_to_toggle">Appuyez longtemps pour cocher ou décocher</string>
|
||||
<string name="reminder_off">Aucun</string>
|
||||
<string name="validation_name_should_not_be_blank">Le nom ne peut être vide.</string>
|
||||
<string name="validation_number_should_be_positive">Le nombre doit être positif.</string>
|
||||
<string name="validation_at_most_one_rep_per_day">Vous pouvez avoir au plus une répétition par jour</string>
|
||||
@@ -67,29 +68,33 @@
|
||||
<string name="intro_description_3">Les habitudes régulières pendant une période de temps étendue gagneront une étoile complète.</string>
|
||||
<string name="intro_title_4">Suivez votre progrès</string>
|
||||
<string name="intro_description_4">Des graphiques détaillés vous montrent comment vos habitudes évoluent au fil du temps.</string>
|
||||
<string name="interval_15_minutes">15 minutes</string>
|
||||
<string name="interval_30_minutes">30 minutes</string>
|
||||
<string name="interval_1_hour">1 heure</string>
|
||||
<string name="interval_2_hour">2 heures</string>
|
||||
<string name="interval_4_hour">4 heures</string>
|
||||
<string name="interval_8_hour">8 heures</string>
|
||||
<string name="pref_toggle_title">Activer les répétitions avec un appui court</string>
|
||||
<string name="pref_toggle_description">Plus pratique, mais peut causer des activations accidentelles.</string>
|
||||
<string name="interval_24_hour">24 heures</string>
|
||||
<string name="pref_toggle_title">Valider l\'habitude avec un appui court</string>
|
||||
<string name="pref_toggle_description">Valide l\'habitude avec un appui court plutôt qu\'un appuie long. Plus pratique, mais peut causer des activations accidentelles.</string>
|
||||
<string name="pref_snooze_interval_title">Intervalle de report des rappels</string>
|
||||
<string name="pref_rate_this_app">Notez cette app sur le Google Play Store</string>
|
||||
<string name="pref_send_feedback">Envoyez un avis au développeur</string>
|
||||
<string name="pref_view_source_code">Voir le code source sur GitHub</string>
|
||||
<string name="pref_view_app_introduction">Voir l\'intro de l\'app</string>
|
||||
<string name="pref_view_app_introduction">Voir l\'introduction de l\'app</string>
|
||||
<string name="links">Liens</string>
|
||||
<string name="behavior">Comportement</string>
|
||||
<string name="name">Nom</string>
|
||||
<string name="settings">Paramètres</string>
|
||||
<string name="snooze_interval">Intervalle de report</string>
|
||||
<string name="hint_title">Le saviez-vous ?</string>
|
||||
<string name="hint_drag">Pour réarranger les habitudes, faites un appui long sur le nom de l\'habitude et placez la à la bonne place.</string>
|
||||
<string name="hint_drag">Pour réordonner les habitudes, faites un appui long sur le nom de l\'habitude et placez-la à la bonne place.</string>
|
||||
<string name="hint_landscape">Vous pouvez voir plus de jours en mettant votre téléphone en mode paysage.</string>
|
||||
<string name="delete_habits">Supprimer des habitudes</string>
|
||||
<string name="delete_habits_message">Les habitudes seront supprimées définitivement. Cette action ne peut être annulée.</string>
|
||||
<string name="weekends">Fin de semaine</string>
|
||||
<string name="any_weekday">Jours de la semaine</string>
|
||||
<string name="delete_habits_message">Les habitudes seront supprimées définitivement. Cette action est irréversible.</string>
|
||||
<string name="habit_not_found">Habitude supprimée / introuvable</string>
|
||||
<string name="weekends">Weekends</string>
|
||||
<string name="any_weekday">Du lundi au vendredi</string>
|
||||
<string name="any_day">N\'importe quel jour</string>
|
||||
<string name="select_weekdays">Sélectionner des jours</string>
|
||||
<string name="export_to_csv">Exporter les données dans un fichier CSV</string>
|
||||
@@ -100,8 +105,9 @@
|
||||
<string name="about">À propos</string>
|
||||
<string name="translators">Traducteurs</string>
|
||||
<string name="developers">Développeurs</string>
|
||||
<string name="version_n">Version %s</string>
|
||||
<string name="frequency">Fréquence</string>
|
||||
<string name="checkmark">Croix</string>
|
||||
<string name="checkmark">Case à cocher</string>
|
||||
<string name="strength">Force</string>
|
||||
<string name="best_streaks">Meilleures séries</string>
|
||||
<string name="current_streaks">Série actuelle</string>
|
||||
@@ -125,15 +131,16 @@
|
||||
<string name="import_data">Importer des données</string>
|
||||
<string name="export_full_backup">Exporter une sauvegarde complète</string>
|
||||
<string name="import_data_summary">Supporte les sauvegardes complètes générées par cette application, ainsi que les fichiers Tickmate, HabitBull et Rewire. Voir la FAQ pour plus d\'informations.</string>
|
||||
<string name="export_as_csv_summary">Génère des fichiers pouvant être ouverts par des tableurs comme Microsoft Excel ou LibreOffice Calc. Ces fichiers ne peuvent être réimportés.</string>
|
||||
<string name="export_as_csv_summary">Génère des fichiers pouvant être ouverts par des tableurs comme Microsoft Excel ou LibreOffice Calc. Ce fichier ne peut pas être réimporté.</string>
|
||||
<string name="export_full_backup_summary">Génère un fichier contenant toutes vos données. Ce fichier peut être réimporté.</string>
|
||||
<string name="bug_report_failed">La génération du rapport de bug a échouée.</string>
|
||||
<string name="generate_bug_report">Générer un rapport de bug.</string>
|
||||
<string name="troubleshooting">Résolution de problèmes</string>
|
||||
<string name="help_translate">Aider à traduire cette application</string>
|
||||
<string name="night_mode">Mode Nuit</string>
|
||||
<string name="use_pure_black">Utiliser un noir pure dans le mode nuit.</string>
|
||||
<string name="pure_black_description">Remplacer le fond gris par un noir pure dans le mode nuit; ça réduit l’usage de la batterie d\'un appareil ayant un écran AMOLED.</string>
|
||||
<string name="use_pure_black">Utiliser un noir pur dans le mode nuit</string>
|
||||
<string name="pure_black_description">Remplacer le fond gris par un noir pur dans le mode nuit ; ça réduit l’usage de la batterie des appareils ayant un écran AMOLED.</string>
|
||||
<string name="interface_preferences">Interface</string>
|
||||
<string name="reverse_days">Inverser l\'ordre des jours</string>
|
||||
<string name="reverse_days_description">Montrer les jours dans l\'ordre inversé sur l\'écran principal</string>
|
||||
<string name="day">Jour</string>
|
||||
@@ -141,12 +148,31 @@
|
||||
<string name="month">Mois</string>
|
||||
<string name="quarter">Trimestre</string>
|
||||
<string name="year">Année</string>
|
||||
<string name="total">Total</string>
|
||||
<!-- Middle part of the sentence '1 time in xx days' -->
|
||||
<string name="time_every">fois tous les</string>
|
||||
<string name="every_x_days">Tous les %d jours</string>
|
||||
<string name="every_x_weeks">Toutes les %d semaines</string>
|
||||
<string name="every_x_months">Tous les %d mois</string>
|
||||
<string name="score">Pointage</string>
|
||||
<string name="score">Score</string>
|
||||
<string name="reminder_sound">Son de rappel</string>
|
||||
<string name="none">Aucun</string>
|
||||
<string name="filter">Filtre</string>
|
||||
<string name="hide_completed">Cacher les habitudes complétées</string>
|
||||
<string name="hide_archived">Cacher les habitudes archivées</string>
|
||||
<string name="sticky_notifications">Rendre les notifications persistantes</string>
|
||||
<string name="sticky_notifications_description">Évite que les notifications ne soient enlevées.</string>
|
||||
<string name="repair_database">Réparer la base de données</string>
|
||||
<string name="database_repaired">Base de données réparée.</string>
|
||||
<string name="uncheck">Décocher</string>
|
||||
<string name="toggle">Basculer</string>
|
||||
<string name="action">Action</string>
|
||||
<string name="habit">Habitude</string>
|
||||
<string name="sort">Trier</string>
|
||||
<string name="manually">Manuellement</string>
|
||||
<string name="by_name">Par nom</string>
|
||||
<string name="by_color">Par couleur</string>
|
||||
<string name="by_score">Par score</string>
|
||||
<string name="download">Télécharger</string>
|
||||
<string name="export">Exporter</string>
|
||||
</resources>
|
||||
|
||||
@@ -91,6 +91,7 @@
|
||||
<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>
|
||||
@@ -179,7 +180,7 @@ repetitions की संख्या\"</string>
|
||||
इस फ़ाइल में वापस आयात नहीं किया जा सकता है।\"</string>
|
||||
<string name="export_full_backup_summary">ऐसी फाइल्स उत्पन्न करता है जिसमे आपका सारा डेटा रहता है इस फ़ाइल को वापस आयात किया जा सकता है।</string>
|
||||
<string name="bug_report_failed">बग रिपोर्ट जनरेट करने मे असफल</string>
|
||||
<string name="generate_bug_report">बग रिपोर्ट जनरेट करने मे सफल</string>
|
||||
<string name="generate_bug_report">बग रिपोर्ट जनरेट करें</string>
|
||||
<string name="troubleshooting">\"
|
||||
समस्या निवारण\"</string>
|
||||
<string name="help_translate">\"
|
||||
@@ -198,15 +199,26 @@ repetitions की संख्या\"</string>
|
||||
<string name="quarter">तिमाही</string>
|
||||
<string name="year">साल</string>
|
||||
<!-- Middle part of the sentence '1 time in xx days' -->
|
||||
<string name="time_every">समय शुरू</string>
|
||||
<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>
|
||||
हर %d महीने\"</string>
|
||||
<string name="score">स्कोर</string>
|
||||
<string name="reminder_sound">अनुस्मारक ध्वनि</string>
|
||||
<string name="none">\"
|
||||
कोई आवाज नहीं\"</string>
|
||||
<string name="filter">फिल्टर</string>
|
||||
<string name="repair_database">डेटाबेस को रिपेयर करें</string>
|
||||
<string name="database_repaired">डेटाबेस रिपेयर सफल</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>
|
||||
|
||||
@@ -78,9 +78,9 @@
|
||||
<string name="pref_toggle_title">Označi ponavljanja sa kratkim pritisk.</string>
|
||||
<string name="pref_toggle_description">Zgodnije je, no može uzrokovati slučajna označavanja.</string>
|
||||
<string name="pref_snooze_interval_title">Interval odgađanja na podsjetnicima</string>
|
||||
<string name="pref_rate_this_app">Ocijenite ovu aplikaciju na Google Playu</string>
|
||||
<string name="pref_send_feedback">Pošaljite povratne informacije raz. programeru</string>
|
||||
<string name="pref_view_source_code">Pogledajte izvorni kod na GitHubu</string>
|
||||
<string name="pref_rate_this_app">Ocijeni ovu aplikaciju na Google Playu</string>
|
||||
<string name="pref_send_feedback">Pošalji povratne informacije raz. programeru</string>
|
||||
<string name="pref_view_source_code">Pogledaj izvorni kod na GitHubu</string>
|
||||
<string name="pref_view_app_introduction">Prikaži uvod u aplikaciju</string>
|
||||
<string name="links">Poveznice</string>
|
||||
<string name="behavior">Ponašanje</string>
|
||||
@@ -136,13 +136,13 @@
|
||||
<string name="bug_report_failed">Generiranje izvješća o pogrešci nije uspjelo.</string>
|
||||
<string name="generate_bug_report">Generiraj izvješće o pogreški</string>
|
||||
<string name="troubleshooting">Rješavanje problema</string>
|
||||
<string name="help_translate">Pomozite prevesti ovu aplikaciju</string>
|
||||
<string name="help_translate">Pomozi prevesti ovu aplikaciju</string>
|
||||
<string name="night_mode">Noćni način</string>
|
||||
<string name="use_pure_black">Koristi crnu boju za noćni način</string>
|
||||
<string name="pure_black_description">Zamjenjuje sivu pozadinu sa crnom u noćnom načinu. To smanjuje potrošnju bateriju na uređajima s AMOLED zaslonima.</string>
|
||||
<string name="interface_preferences">Sučelje</string>
|
||||
<string name="reverse_days">Obrnuti poredak dana</string>
|
||||
<string name="reverse_days_description">Prikaži dane obrnutim redom na glavnom zaslonu</string>
|
||||
<string name="reverse_days_description">Prikažite dane obrnutim redom na glavnom zaslonu</string>
|
||||
<string name="day">Dan</string>
|
||||
<string name="week">Tjedan</string>
|
||||
<string name="month">Mjesec</string>
|
||||
@@ -158,7 +158,7 @@
|
||||
<string name="reminder_sound">Zvuk podsjetnika</string>
|
||||
<string name="none">Nijedan</string>
|
||||
<string name="filter">Filtar</string>
|
||||
<string name="hide_completed">Skrivanje je uspjelo</string>
|
||||
<string name="hide_completed">Sakrij završeno</string>
|
||||
<string name="hide_archived">Sakrij arhivirano</string>
|
||||
<string name="sticky_notifications">Učini obavijesti trajnima</string>
|
||||
<string name="sticky_notifications_description">Spriječava da se obavijesti zanemare.</string>
|
||||
|
||||