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/
|
gen/
|
||||||
local.properties
|
local.properties
|
||||||
crowdin.yaml
|
crowdin.yaml
|
||||||
|
local
|
||||||
|
secret/
|
||||||
|
|||||||
41
CHANGELOG.md
@@ -1,5 +1,46 @@
|
|||||||
# Changelog
|
# 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)
|
### 1.7.0 (Mar 31, 2017)
|
||||||
|
|
||||||
* Sort habits automatically
|
* Sort habits automatically
|
||||||
|
|||||||
@@ -2,30 +2,50 @@ apply plugin: 'com.android.application'
|
|||||||
apply plugin: 'com.neenbedankt.android-apt'
|
apply plugin: 'com.neenbedankt.android-apt'
|
||||||
apply plugin: 'me.tatarka.retrolambda'
|
apply plugin: 'me.tatarka.retrolambda'
|
||||||
apply plugin: 'jacoco'
|
apply plugin: 'jacoco'
|
||||||
|
apply plugin: 'com.github.triplet.play'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 25
|
compileSdkVersion 28
|
||||||
buildToolsVersion "25.0.2"
|
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 {
|
defaultConfig {
|
||||||
applicationId "org.isoron.uhabits"
|
applicationId "org.isoron.uhabits"
|
||||||
minSdkVersion 15
|
minSdkVersion 15
|
||||||
targetSdkVersion 25
|
targetSdkVersion 28
|
||||||
|
|
||||||
buildConfigField "Integer", "databaseVersion", "15"
|
buildConfigField "Integer", "databaseVersion", "15"
|
||||||
buildConfigField "String", "databaseFilename", "\"uhabits.db\""
|
buildConfigField "String", "databaseFilename", "\"uhabits.db\""
|
||||||
|
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
testInstrumentationRunnerArgument "size", "medium"
|
testInstrumentationRunnerArgument "size", "medium"
|
||||||
|
|
||||||
|
// playAccountConfig = playAccountConfigs.defaultAccountConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
minifyEnabled false
|
minifyEnabled false
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||||
|
// signingConfig signingConfigs.release
|
||||||
}
|
}
|
||||||
debug {
|
debug {
|
||||||
testCoverageEnabled = true
|
testCoverageEnabled = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +73,7 @@ dependencies {
|
|||||||
|
|
||||||
androidTestApt 'com.google.dagger:dagger-compiler:2.2'
|
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:rules:0.5'
|
||||||
androidTestCompile 'com.android.support.test:runner:0.5'
|
androidTestCompile 'com.android.support.test:runner:0.5'
|
||||||
androidTestCompile 'com.google.auto.factory:auto-factory:1.0-beta3'
|
androidTestCompile 'com.google.auto.factory:auto-factory:1.0-beta3'
|
||||||
@@ -64,16 +84,15 @@ dependencies {
|
|||||||
apt 'com.google.dagger:dagger-compiler:2.2'
|
apt 'com.google.dagger:dagger-compiler:2.2'
|
||||||
apt 'com.jakewharton:butterknife-compiler:8.0.1'
|
apt 'com.jakewharton:butterknife-compiler:8.0.1'
|
||||||
|
|
||||||
compile 'com.android.support:appcompat-v7:25.3.0'
|
compile 'com.android.support:appcompat-v7:27.1.1'
|
||||||
compile 'com.android.support:design:25.3.0'
|
compile 'com.android.support:design:27.1.1'
|
||||||
compile 'com.android.support:preference-v14:25.3.0'
|
compile 'com.android.support:preference-v14:27.1.1'
|
||||||
compile 'com.android.support:support-v4:25.3.0'
|
compile 'com.android.support:support-v4:27.1.1'
|
||||||
compile 'com.getpebble:pebblekit:3.0.0'
|
compile 'com.getpebble:pebblekit:3.0.0'
|
||||||
compile 'com.github.paolorotolo:appintro:3.4.0'
|
compile 'com.github.paolorotolo:appintro:3.4.0'
|
||||||
compile 'com.google.auto.factory:auto-factory:1.0-beta3'
|
compile 'com.google.auto.factory:auto-factory:1.0-beta3'
|
||||||
compile 'com.google.dagger:dagger:2.2'
|
compile 'com.google.dagger:dagger:2.2'
|
||||||
compile 'com.jakewharton:butterknife:8.0.1'
|
compile 'com.jakewharton:butterknife:8.0.1'
|
||||||
compile 'com.michaelpardo:activeandroid:3.1.0-SNAPSHOT'
|
|
||||||
compile 'com.opencsv:opencsv:3.7'
|
compile 'com.opencsv:opencsv:3.7'
|
||||||
compile 'org.apmem.tools:layouts:1.10@aar'
|
compile 'org.apmem.tools:layouts:1.10@aar'
|
||||||
compile 'org.jetbrains:annotations-java5:15.0'
|
compile 'org.jetbrains:annotations-java5:15.0'
|
||||||
@@ -140,3 +159,7 @@ task coverageReport(type: JacocoReport, dependsOn: ['testDebugUnitTest']) {
|
|||||||
classDirectories = files(fileTree(dir: classDir, excludes: excludes))
|
classDirectories = files(fileTree(dir: classDir, excludes: excludes))
|
||||||
executionData = files(jvmExecData, connectedExecData)
|
executionData = files(jvmExecData, connectedExecData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//play {
|
||||||
|
// track = 'alpha'
|
||||||
|
//}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import android.content.*;
|
|||||||
import android.os.*;
|
import android.os.*;
|
||||||
import android.support.annotation.*;
|
import android.support.annotation.*;
|
||||||
import android.support.test.*;
|
import android.support.test.*;
|
||||||
|
import android.util.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.models.*;
|
import org.isoron.uhabits.models.*;
|
||||||
import org.isoron.uhabits.preferences.*;
|
import org.isoron.uhabits.preferences.*;
|
||||||
@@ -31,6 +32,7 @@ import org.isoron.uhabits.tasks.*;
|
|||||||
import org.isoron.uhabits.utils.*;
|
import org.isoron.uhabits.utils.*;
|
||||||
import org.junit.*;
|
import org.junit.*;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
@@ -65,6 +67,8 @@ public class BaseAndroidTest
|
|||||||
|
|
||||||
protected ModelFactory modelFactory;
|
protected ModelFactory modelFactory;
|
||||||
|
|
||||||
|
private boolean isDone = false;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp()
|
public void setUp()
|
||||||
{
|
{
|
||||||
@@ -115,6 +119,25 @@ public class BaseAndroidTest
|
|||||||
assertTrue(latch.await(60, TimeUnit.SECONDS));
|
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)
|
protected void setTheme(@StyleRes int themeId)
|
||||||
{
|
{
|
||||||
targetContext.setTheme(themeId);
|
targetContext.setTheme(themeId);
|
||||||
@@ -132,4 +155,18 @@ public class BaseAndroidTest
|
|||||||
fail();
|
fail();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void startTracing()
|
||||||
|
{
|
||||||
|
File dir = FileUtils.getFilesDir(targetContext, "Profile");
|
||||||
|
assertNotNull(dir);
|
||||||
|
String tracePath = dir.getAbsolutePath() + "/performance.trace";
|
||||||
|
Log.d("PerformanceTest", String.format("Saving trace file to %s", tracePath));
|
||||||
|
Debug.startMethodTracingSampling(tracePath, 0, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void stopTracing()
|
||||||
|
{
|
||||||
|
Debug.stopMethodTracing();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,12 +46,12 @@ public class CheckmarkButtonViewTest extends BaseViewTest
|
|||||||
public void setUp()
|
public void setUp()
|
||||||
{
|
{
|
||||||
super.setUp();
|
super.setUp();
|
||||||
setSimilarityCutoff(0.03f);
|
setSimilarityCutoff(0.015f);
|
||||||
|
|
||||||
latch = new CountDownLatch(1);
|
latch = new CountDownLatch(1);
|
||||||
view = new CheckmarkButtonView(targetContext);
|
view = new CheckmarkButtonView(targetContext);
|
||||||
view.setValue(Checkmark.UNCHECKED);
|
view.setValue(Checkmark.UNCHECKED);
|
||||||
view.setColor(ColorUtils.getAndroidTestColor(7));
|
view.setColor(ColorUtils.getAndroidTestColor(5));
|
||||||
|
|
||||||
measureView(view, dpToPixels(40), dpToPixels(40));
|
measureView(view, dpToPixels(40), dpToPixels(40));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -171,13 +171,13 @@ public class MainTest
|
|||||||
clickMenuItem(R.string.archive);
|
clickMenuItem(R.string.archive);
|
||||||
assertHabitsDontExist(names);
|
assertHabitsDontExist(names);
|
||||||
|
|
||||||
clickMenuItem(R.string.show_archived);
|
clickMenuItem(R.string.hide_archived);
|
||||||
|
|
||||||
assertHabitsExist(names);
|
assertHabitsExist(names);
|
||||||
selectHabits(names);
|
selectHabits(names);
|
||||||
clickMenuItem(R.string.unarchive);
|
clickMenuItem(R.string.unarchive);
|
||||||
|
|
||||||
clickMenuItem(R.string.show_archived);
|
clickMenuItem(R.string.hide_archived);
|
||||||
|
|
||||||
assertHabitsExist(names);
|
assertHabitsExist(names);
|
||||||
deleteHabits(names);
|
deleteHabits(names);
|
||||||
|
|||||||
@@ -115,6 +115,41 @@ public class SQLiteCheckmarkListTest extends BaseAndroidTest
|
|||||||
assertThat(records.get(0).timestamp, equalTo(today - 21 * day));
|
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()
|
private List<CheckmarkRecord> getAllRecords()
|
||||||
{
|
{
|
||||||
return new Select()
|
return new Select()
|
||||||
|
|||||||
@@ -61,29 +61,6 @@ public class SQLiteScoreListTest extends BaseAndroidTest
|
|||||||
day = DateUtils.millisecondsInOneDay;
|
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
|
@Test
|
||||||
public void testAdd()
|
public void testAdd()
|
||||||
{
|
{
|
||||||
@@ -101,6 +78,15 @@ public class SQLiteScoreListTest extends BaseAndroidTest
|
|||||||
assertThat(records.get(0).timestamp, equalTo(today));
|
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
|
@Test
|
||||||
public void testGetByInterval()
|
public void testGetByInterval()
|
||||||
{
|
{
|
||||||
@@ -115,6 +101,16 @@ public class SQLiteScoreListTest extends BaseAndroidTest
|
|||||||
assertThat(list.get(7).getTimestamp(), equalTo(today - 10 * day));
|
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
|
@Test
|
||||||
public void testGetByInterval_withLongInterval()
|
public void testGetByInterval_withLongInterval()
|
||||||
{
|
{
|
||||||
@@ -125,6 +121,30 @@ public class SQLiteScoreListTest extends BaseAndroidTest
|
|||||||
assertThat(list.size(), equalTo(201));
|
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()
|
private List<ScoreRecord> getAllRecords()
|
||||||
{
|
{
|
||||||
return new Select()
|
return new Select()
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of Loop Habit Tracker.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.isoron.uhabits.performance;
|
||||||
|
|
||||||
|
import android.support.test.filters.*;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.*;
|
||||||
|
import org.isoron.uhabits.models.*;
|
||||||
|
import org.junit.*;
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
public class PerformanceTest extends BaseAndroidTest
|
||||||
|
{
|
||||||
|
private Habit habit;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUp()
|
||||||
|
{
|
||||||
|
super.setUp();
|
||||||
|
habit = fixtures.createLongHabit();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 1000)
|
||||||
|
public void testRepeatedGetTodayValue()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 100000; i++)
|
||||||
|
{
|
||||||
|
habit.getScores().getTodayValue();
|
||||||
|
habit.getCheckmarks().getTodayValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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
|
<manifest
|
||||||
package="org.isoron.uhabits"
|
package="org.isoron.uhabits"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:versionCode="27"
|
android:versionCode="38"
|
||||||
android:versionName="1.7.0">
|
android:versionName="1.7.11">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
<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.app.*;
|
||||||
import android.content.*;
|
import android.content.*;
|
||||||
import android.support.annotation.*;
|
|
||||||
|
|
||||||
import com.activeandroid.*;
|
import com.activeandroid.*;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.models.sqlite.*;
|
||||||
import org.isoron.uhabits.notifications.*;
|
import org.isoron.uhabits.notifications.*;
|
||||||
import org.isoron.uhabits.preferences.*;
|
import org.isoron.uhabits.preferences.*;
|
||||||
import org.isoron.uhabits.tasks.*;
|
import org.isoron.uhabits.tasks.*;
|
||||||
@@ -88,7 +88,16 @@ public class HabitsApplication extends Application
|
|||||||
if (db.exists()) db.delete();
|
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 = component.getWidgetUpdater();
|
||||||
widgetUpdater.startListening();
|
widgetUpdater.startListening();
|
||||||
|
|||||||
@@ -20,25 +20,27 @@
|
|||||||
package org.isoron.uhabits.activities.common.views;
|
package org.isoron.uhabits.activities.common.views;
|
||||||
|
|
||||||
import android.os.*;
|
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 =
|
public static final Parcelable.Creator<BundleSavedState> CREATOR =
|
||||||
new Parcelable.Creator<BundleSavedState>()
|
ParcelableCompat.newCreator(
|
||||||
{
|
new ParcelableCompatCreatorCallbacks<BundleSavedState>()
|
||||||
@Override
|
|
||||||
public BundleSavedState createFromParcel(Parcel source)
|
|
||||||
{
|
{
|
||||||
return new BundleSavedState(source);
|
@Override
|
||||||
}
|
public BundleSavedState createFromParcel(Parcel source,
|
||||||
|
ClassLoader loader)
|
||||||
|
{
|
||||||
|
return new BundleSavedState(source, loader);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BundleSavedState[] newArray(int size)
|
public BundleSavedState[] newArray(int size)
|
||||||
{
|
{
|
||||||
return new BundleSavedState[size];
|
return new BundleSavedState[size];
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
public final Bundle bundle;
|
public final Bundle bundle;
|
||||||
|
|
||||||
@@ -48,10 +50,10 @@ public class BundleSavedState extends View.BaseSavedState
|
|||||||
this.bundle = bundle;
|
this.bundle = bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BundleSavedState(Parcel source)
|
public BundleSavedState(Parcel source, ClassLoader loader)
|
||||||
{
|
{
|
||||||
super(source);
|
super(source, loader);
|
||||||
this.bundle = source.readBundle();
|
this.bundle = source.readBundle(loader);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -256,7 +256,7 @@ public class FrequencyChart extends ScrollableChart
|
|||||||
float scale = 1.0f/maxFreq * value;
|
float scale = 1.0f/maxFreq * value;
|
||||||
float radius = maxRadius * scale;
|
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]);
|
pGraph.setColor(colors[colorIndex]);
|
||||||
canvas.drawCircle(rect.centerX(), rect.centerY(), radius, pGraph);
|
canvas.drawCircle(rect.centerX(), rect.centerY(), radius, pGraph);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ import org.isoron.uhabits.utils.*;
|
|||||||
import java.text.*;
|
import java.text.*;
|
||||||
import java.util.*;
|
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
|
public class HistoryChart extends ScrollableChart
|
||||||
{
|
{
|
||||||
@@ -112,10 +113,21 @@ public class HistoryChart extends ScrollableChart
|
|||||||
if (!isEditable) return false;
|
if (!isEditable) return false;
|
||||||
|
|
||||||
performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
|
performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
|
||||||
|
float x, y;
|
||||||
|
|
||||||
int pointerId = e.getPointerId(0);
|
try
|
||||||
float x = e.getX(pointerId);
|
{
|
||||||
float y = e.getY(pointerId);
|
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);
|
final Long timestamp = positionToTimestamp(x, y);
|
||||||
if (timestamp == null) return false;
|
if (timestamp == null) return false;
|
||||||
|
|||||||
@@ -107,6 +107,12 @@ public abstract class ScrollableChart extends View
|
|||||||
@Override
|
@Override
|
||||||
public void onRestoreInstanceState(Parcelable state)
|
public void onRestoreInstanceState(Parcelable state)
|
||||||
{
|
{
|
||||||
|
if(!(state instanceof BundleSavedState))
|
||||||
|
{
|
||||||
|
super.onRestoreInstanceState(state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
BundleSavedState bss = (BundleSavedState) state;
|
BundleSavedState bss = (BundleSavedState) state;
|
||||||
int x = bss.bundle.getInt("x");
|
int x = bss.bundle.getInt("x");
|
||||||
int y = bss.bundle.getInt("y");
|
int y = bss.bundle.getInt("y");
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ import java.util.*;
|
|||||||
|
|
||||||
import butterknife.*;
|
import butterknife.*;
|
||||||
|
|
||||||
|
import static org.isoron.uhabits.activities.ThemeSwitcher.*;
|
||||||
|
|
||||||
public abstract class BaseDialog extends AppCompatDialogFragment
|
public abstract class BaseDialog extends AppCompatDialogFragment
|
||||||
{
|
{
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -62,6 +64,18 @@ public abstract class BaseDialog extends AppCompatDialogFragment
|
|||||||
|
|
||||||
private ColorPickerDialogFactory colorPickerDialogFactory;
|
private ColorPickerDialogFactory colorPickerDialogFactory;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTheme()
|
||||||
|
{
|
||||||
|
AppComponent component =
|
||||||
|
((HabitsApplication) getContext().getApplicationContext()).getComponent();
|
||||||
|
|
||||||
|
if(component.getPreferences().getTheme() == THEME_LIGHT)
|
||||||
|
return R.style.DialogWithTitle;
|
||||||
|
else
|
||||||
|
return R.style.DarkDialogWithTitle;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityCreated(Bundle savedInstanceState)
|
public void onActivityCreated(Bundle savedInstanceState)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -105,11 +105,10 @@ public class HabitCardListAdapter
|
|||||||
* Returns the item that occupies a certain position on the list
|
* Returns the item that occupies a certain position on the list
|
||||||
*
|
*
|
||||||
* @param position position of the item
|
* @param position position of the item
|
||||||
* @return the item at given position
|
* @return the item at given position or null if position is invalid
|
||||||
* @throws IndexOutOfBoundsException if position is not valid
|
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@NonNull
|
@Nullable
|
||||||
public Habit getItem(int position)
|
public Habit getItem(int position)
|
||||||
{
|
{
|
||||||
return cache.getHabitByPosition(position);
|
return cache.getHabitByPosition(position);
|
||||||
@@ -314,6 +313,8 @@ public class HabitCardListAdapter
|
|||||||
public void toggleSelection(int position)
|
public void toggleSelection(int position)
|
||||||
{
|
{
|
||||||
Habit h = getItem(position);
|
Habit h = getItem(position);
|
||||||
|
if (h == null) return;
|
||||||
|
|
||||||
int k = selected.indexOf(h);
|
int k = selected.indexOf(h);
|
||||||
if (k < 0) selected.add(h);
|
if (k < 0) selected.add(h);
|
||||||
else selected.remove(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.
|
* Returns the habits that occupies a certain position on the list.
|
||||||
*
|
*
|
||||||
* @param position the position of the habit
|
* @param position the position of the habit
|
||||||
* @return the habit at given position
|
* @return the habit at given position or null if position is invalid
|
||||||
* @throws IndexOutOfBoundsException if position is not valid
|
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@Nullable
|
||||||
public Habit getHabitByPosition(int position)
|
public synchronized Habit getHabitByPosition(int position)
|
||||||
{
|
{
|
||||||
|
if(position < 0 || position >= data.habits.size()) return null;
|
||||||
return data.habits.get(position);
|
return data.habits.get(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,21 +20,34 @@
|
|||||||
package org.isoron.uhabits.activities.habits.list.views;
|
package org.isoron.uhabits.activities.habits.list.views;
|
||||||
|
|
||||||
import android.content.*;
|
import android.content.*;
|
||||||
|
import android.content.res.*;
|
||||||
|
import android.graphics.*;
|
||||||
|
import android.support.annotation.*;
|
||||||
|
import android.text.*;
|
||||||
|
import android.util.*;
|
||||||
import android.view.*;
|
import android.view.*;
|
||||||
import android.widget.*;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
import org.isoron.uhabits.*;
|
||||||
import org.isoron.uhabits.activities.habits.list.controllers.*;
|
import org.isoron.uhabits.activities.habits.list.controllers.*;
|
||||||
import org.isoron.uhabits.models.*;
|
|
||||||
import org.isoron.uhabits.utils.*;
|
import org.isoron.uhabits.utils.*;
|
||||||
|
|
||||||
public class CheckmarkButtonView extends TextView
|
import static android.view.View.MeasureSpec.*;
|
||||||
|
import static org.isoron.uhabits.models.Checkmark.*;
|
||||||
|
import static org.isoron.uhabits.utils.AttributeSetUtils.*;
|
||||||
|
|
||||||
|
public class CheckmarkButtonView extends View
|
||||||
{
|
{
|
||||||
private int color;
|
private int color;
|
||||||
|
|
||||||
private int value;
|
private int value;
|
||||||
|
|
||||||
private StyledResources res;
|
private StyledResources styledRes;
|
||||||
|
|
||||||
|
private TextPaint paint;
|
||||||
|
|
||||||
|
private int lowContrastColor;
|
||||||
|
|
||||||
|
private RectF rect;
|
||||||
|
|
||||||
public CheckmarkButtonView(Context context)
|
public CheckmarkButtonView(Context context)
|
||||||
{
|
{
|
||||||
@@ -42,6 +55,21 @@ public class CheckmarkButtonView extends TextView
|
|||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CheckmarkButtonView(@Nullable Context ctx, @Nullable AttributeSet attrs)
|
||||||
|
{
|
||||||
|
super(ctx, attrs);
|
||||||
|
init();
|
||||||
|
|
||||||
|
if(ctx == null) throw new IllegalStateException();
|
||||||
|
if(attrs == null) throw new IllegalStateException();
|
||||||
|
|
||||||
|
int paletteColor = getIntAttribute(ctx, attrs, "color", 0);
|
||||||
|
setColor(ColorUtils.getAndroidTestColor(paletteColor));
|
||||||
|
|
||||||
|
int value = getIntAttribute(ctx, attrs, "value", 0);
|
||||||
|
setValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
public void setColor(int color)
|
public void setColor(int color)
|
||||||
{
|
{
|
||||||
this.color = color;
|
this.color = color;
|
||||||
@@ -57,54 +85,60 @@ public class CheckmarkButtonView extends TextView
|
|||||||
public void setValue(int value)
|
public void setValue(int value)
|
||||||
{
|
{
|
||||||
this.value = value;
|
this.value = value;
|
||||||
updateText();
|
postInvalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void toggle()
|
public void toggle()
|
||||||
{
|
{
|
||||||
value = (value == Checkmark.CHECKED_EXPLICITLY ? Checkmark.UNCHECKED :
|
value = (value == CHECKED_EXPLICITLY ? UNCHECKED : CHECKED_EXPLICITLY);
|
||||||
Checkmark.CHECKED_EXPLICITLY);
|
|
||||||
|
|
||||||
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||||
updateText();
|
postInvalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDraw(Canvas canvas)
|
||||||
|
{
|
||||||
|
super.onDraw(canvas);
|
||||||
|
Resources resources = getResources();
|
||||||
|
|
||||||
|
paint.setColor(value == CHECKED_EXPLICITLY ? color : lowContrastColor);
|
||||||
|
int id = (value == UNCHECKED ? R.string.fa_times : R.string.fa_check);
|
||||||
|
String label = resources.getString(id);
|
||||||
|
float em = paint.measureText("m");
|
||||||
|
|
||||||
|
rect.set(0, 0, getWidth(), getHeight());
|
||||||
|
rect.offset(0, 0.4f * em);
|
||||||
|
canvas.drawText(label, rect.centerX(), rect.centerY(), paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
||||||
|
{
|
||||||
|
Resources res = getResources();
|
||||||
|
int height = res.getDimensionPixelSize(R.dimen.checkmarkHeight);
|
||||||
|
int width = res.getDimensionPixelSize(R.dimen.checkmarkWidth);
|
||||||
|
|
||||||
|
widthMeasureSpec = makeMeasureSpec(width, EXACTLY);
|
||||||
|
heightMeasureSpec = makeMeasureSpec(height, EXACTLY);
|
||||||
|
|
||||||
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void init()
|
private void init()
|
||||||
{
|
{
|
||||||
res = new StyledResources(getContext());
|
|
||||||
|
|
||||||
setWillNotDraw(false);
|
|
||||||
|
|
||||||
setMinHeight(
|
|
||||||
getResources().getDimensionPixelSize(R.dimen.checkmarkHeight));
|
|
||||||
setMinWidth(
|
|
||||||
getResources().getDimensionPixelSize(R.dimen.checkmarkWidth));
|
|
||||||
|
|
||||||
setFocusable(false);
|
setFocusable(false);
|
||||||
setGravity(Gravity.CENTER);
|
|
||||||
setTypeface(InterfaceUtils.getFontAwesome(getContext()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateText()
|
Resources res = getResources();
|
||||||
{
|
styledRes = new StyledResources(getContext());
|
||||||
int lowContrastColor = res.getColor(R.attr.lowContrastTextColor);
|
|
||||||
|
|
||||||
if (value == Checkmark.CHECKED_EXPLICITLY)
|
paint = new TextPaint();
|
||||||
{
|
paint.setTypeface(InterfaceUtils.getFontAwesome(getContext()));
|
||||||
setText(R.string.fa_check);
|
paint.setAntiAlias(true);
|
||||||
setTextColor(color);
|
paint.setTextAlign(Paint.Align.CENTER);
|
||||||
}
|
paint.setTextSize(res.getDimension(R.dimen.regularTextSize));
|
||||||
|
|
||||||
if (value == Checkmark.CHECKED_IMPLICITLY)
|
rect = new RectF();
|
||||||
{
|
color = ColorUtils.getAndroidTestColor(0);
|
||||||
setText(R.string.fa_check);
|
lowContrastColor = styledRes.getColor(R.attr.lowContrastTextColor);
|
||||||
setTextColor(lowContrastColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value == Checkmark.UNCHECKED)
|
|
||||||
{
|
|
||||||
setText(R.string.fa_times);
|
|
||||||
setTextColor(lowContrastColor);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,6 +153,12 @@ public class HabitCardListView extends RecyclerView
|
|||||||
@Override
|
@Override
|
||||||
protected void onRestoreInstanceState(Parcelable state)
|
protected void onRestoreInstanceState(Parcelable state)
|
||||||
{
|
{
|
||||||
|
if(!(state instanceof BundleSavedState))
|
||||||
|
{
|
||||||
|
super.onRestoreInstanceState(state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
BundleSavedState bss = (BundleSavedState) state;
|
BundleSavedState bss = (BundleSavedState) state;
|
||||||
dataOffset = bss.bundle.getInt("dataOffset");
|
dataOffset = bss.bundle.getInt("dataOffset");
|
||||||
super.onRestoreInstanceState(bss.getSuperState());
|
super.onRestoreInstanceState(bss.getSuperState());
|
||||||
|
|||||||
@@ -143,12 +143,13 @@ public class HabitCardView extends FrameLayout
|
|||||||
updateBackground(isSelected);
|
updateBackground(isSelected);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void triggerRipple(long timestamp)
|
public synchronized void triggerRipple(long timestamp)
|
||||||
{
|
{
|
||||||
long today = DateUtils.getStartOfToday();
|
long today = DateUtils.getStartOfToday();
|
||||||
long day = DateUtils.millisecondsInOneDay;
|
long day = DateUtils.millisecondsInOneDay;
|
||||||
int offset = (int) ((today - timestamp) / day) - dataOffset;
|
int offset = (int) ((today - timestamp) / day) - dataOffset;
|
||||||
CheckmarkButtonView button = checkmarkPanel.indexToButton(offset);
|
CheckmarkButtonView button = checkmarkPanel.indexToButton(offset);
|
||||||
|
if (button == null) return;
|
||||||
|
|
||||||
float y = button.getHeight() / 2.0f;
|
float y = button.getHeight() / 2.0f;
|
||||||
float x = checkmarkPanel.getX() + button.getX() + button.getWidth() / 2;
|
float x = checkmarkPanel.getX() + button.getX() + button.getWidth() / 2;
|
||||||
@@ -214,6 +215,7 @@ public class HabitCardView extends FrameLayout
|
|||||||
scoreRing.setPercentage(rand.nextFloat());
|
scoreRing.setPercentage(rand.nextFloat());
|
||||||
checkmarkPanel.setColor(color);
|
checkmarkPanel.setColor(color);
|
||||||
checkmarkPanel.setCheckmarkValues(values);
|
checkmarkPanel.setCheckmarkValues(values);
|
||||||
|
checkmarkPanel.setButtonCount(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refresh()
|
private void refresh()
|
||||||
|
|||||||
@@ -50,8 +50,6 @@ public class HeaderView extends ScrollableChart
|
|||||||
|
|
||||||
private RectF rect;
|
private RectF rect;
|
||||||
|
|
||||||
private int maxDataOffset;
|
|
||||||
|
|
||||||
public HeaderView(Context context, AttributeSet attrs)
|
public HeaderView(Context context, AttributeSet attrs)
|
||||||
{
|
{
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
@@ -76,7 +74,6 @@ public class HeaderView extends ScrollableChart
|
|||||||
|
|
||||||
Resources res = context.getResources();
|
Resources res = context.getResources();
|
||||||
setScrollerBucketSize((int) res.getDimension(R.dimen.checkmarkWidth));
|
setScrollerBucketSize((int) res.getDimension(R.dimen.checkmarkWidth));
|
||||||
setDirection(shouldReverseCheckmarks() ? 1 : -1);
|
|
||||||
|
|
||||||
StyledResources sr = new StyledResources(context);
|
StyledResources sr = new StyledResources(context);
|
||||||
paint = new TextPaint();
|
paint = new TextPaint();
|
||||||
@@ -99,7 +96,7 @@ public class HeaderView extends ScrollableChart
|
|||||||
@Override
|
@Override
|
||||||
public void onCheckmarkOrderChanged()
|
public void onCheckmarkOrderChanged()
|
||||||
{
|
{
|
||||||
setDirection(shouldReverseCheckmarks() ? 1 : -1);
|
updateDirection();
|
||||||
postInvalidate();
|
postInvalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,11 +109,20 @@ public class HeaderView extends ScrollableChart
|
|||||||
@Override
|
@Override
|
||||||
protected void onAttachedToWindow()
|
protected void onAttachedToWindow()
|
||||||
{
|
{
|
||||||
|
updateDirection();
|
||||||
super.onAttachedToWindow();
|
super.onAttachedToWindow();
|
||||||
if (prefs != null) prefs.addListener(this);
|
if (prefs != null) prefs.addListener(this);
|
||||||
if (midnightTimer != null) midnightTimer.addListener(this);
|
if (midnightTimer != null) midnightTimer.addListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateDirection()
|
||||||
|
{
|
||||||
|
int direction = -1;
|
||||||
|
if (shouldReverseCheckmarks()) direction *= -1;
|
||||||
|
if (InterfaceUtils.isLayoutRtl(this)) direction *= -1;
|
||||||
|
setDirection(direction);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDetachedFromWindow()
|
protected void onDetachedFromWindow()
|
||||||
{
|
{
|
||||||
@@ -145,6 +151,7 @@ public class HeaderView extends ScrollableChart
|
|||||||
float width = res.getDimension(R.dimen.checkmarkWidth);
|
float width = res.getDimension(R.dimen.checkmarkWidth);
|
||||||
float height = res.getDimension(R.dimen.checkmarkHeight);
|
float height = res.getDimension(R.dimen.checkmarkHeight);
|
||||||
boolean reverse = shouldReverseCheckmarks();
|
boolean reverse = shouldReverseCheckmarks();
|
||||||
|
boolean isRtl = InterfaceUtils.isLayoutRtl(this);
|
||||||
|
|
||||||
day.add(GregorianCalendar.DAY_OF_MONTH, -getDataOffset());
|
day.add(GregorianCalendar.DAY_OF_MONTH, -getDataOffset());
|
||||||
float em = paint.measureText("m");
|
float em = paint.measureText("m");
|
||||||
@@ -153,9 +160,13 @@ public class HeaderView extends ScrollableChart
|
|||||||
{
|
{
|
||||||
rect.set(0, 0, width, height);
|
rect.set(0, 0, width, height);
|
||||||
rect.offset(canvas.getWidth(), 0);
|
rect.offset(canvas.getWidth(), 0);
|
||||||
|
|
||||||
if(reverse) rect.offset(- (i + 1) * width, 0);
|
if(reverse) rect.offset(- (i + 1) * width, 0);
|
||||||
else rect.offset((i - buttonCount) * 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 text = DateUtils.formatHeaderDate(day).toUpperCase();
|
||||||
String[] lines = text.split("\n");
|
String[] lines = text.split("\n");
|
||||||
|
|
||||||
|
|||||||
@@ -22,12 +22,16 @@ package org.isoron.uhabits.activities.settings;
|
|||||||
import android.app.backup.*;
|
import android.app.backup.*;
|
||||||
import android.content.*;
|
import android.content.*;
|
||||||
import android.os.*;
|
import android.os.*;
|
||||||
|
import android.provider.*;
|
||||||
import android.support.v7.preference.*;
|
import android.support.v7.preference.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.R;
|
import org.isoron.uhabits.R;
|
||||||
import org.isoron.uhabits.activities.habits.list.*;
|
import org.isoron.uhabits.activities.habits.list.*;
|
||||||
|
import org.isoron.uhabits.notifications.*;
|
||||||
import org.isoron.uhabits.utils.*;
|
import org.isoron.uhabits.utils.*;
|
||||||
|
|
||||||
|
import static android.os.Build.VERSION.*;
|
||||||
|
|
||||||
public class SettingsFragment extends PreferenceFragmentCompat
|
public class SettingsFragment extends PreferenceFragmentCompat
|
||||||
implements SharedPreferences.OnSharedPreferenceChangeListener
|
implements SharedPreferences.OnSharedPreferenceChangeListener
|
||||||
{
|
{
|
||||||
@@ -61,6 +65,14 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
|||||||
setResultOnPreferenceClick("bugReport", ListHabitsScreen.RESULT_BUG_REPORT);
|
setResultOnPreferenceClick("bugReport", ListHabitsScreen.RESULT_BUG_REPORT);
|
||||||
|
|
||||||
updateRingtoneDescription();
|
updateRingtoneDescription();
|
||||||
|
|
||||||
|
if (SDK_INT < Build.VERSION_CODES.O)
|
||||||
|
findPreference("reminderCustomize").setVisible(false);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
findPreference("reminderSound").setVisible(false);
|
||||||
|
findPreference("pref_snooze_interval").setVisible(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -88,6 +100,17 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
|||||||
RINGTONE_REQUEST_CODE);
|
RINGTONE_REQUEST_CODE);
|
||||||
return true;
|
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);
|
return super.onPreferenceTreeClick(preference);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ public class HabitsCSVExporter
|
|||||||
{
|
{
|
||||||
String sane = sanitizeFilename(h.getName());
|
String sane = sanitizeFilename(h.getName());
|
||||||
String habitDirName =
|
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() + "/";
|
habitDirName = habitDirName.trim() + "/";
|
||||||
|
|
||||||
new File(exportDirName + habitDirName).mkdirs();
|
new File(exportDirName + habitDirName).mkdirs();
|
||||||
@@ -202,7 +202,7 @@ public class HabitsCSVExporter
|
|||||||
checksWriter.write(String.valueOf(checkmarks.get(j)[i]));
|
checksWriter.write(String.valueOf(checkmarks.get(j)[i]));
|
||||||
checksWriter.write(DELIMITER);
|
checksWriter.write(DELIMITER);
|
||||||
String score =
|
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(score);
|
||||||
scoresWriter.write(DELIMITER);
|
scoresWriter.write(DELIMITER);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,20 +19,20 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.io;
|
package org.isoron.uhabits.io;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.*;
|
||||||
import android.database.Cursor;
|
import android.database.*;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.*;
|
||||||
import android.support.annotation.NonNull;
|
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.models.*;
|
||||||
import org.isoron.uhabits.utils.DatabaseUtils;
|
import org.isoron.uhabits.utils.DatabaseUtils;
|
||||||
import org.isoron.uhabits.utils.FileUtils;
|
import org.isoron.uhabits.utils.*;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.*;
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import javax.inject.*;
|
import javax.inject.*;
|
||||||
|
|
||||||
@@ -45,7 +45,8 @@ public class LoopDBImporter extends AbstractImporter
|
|||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public LoopDBImporter(@NonNull @AppContext Context context, @NonNull HabitList habits)
|
public LoopDBImporter(@NonNull @AppContext Context context,
|
||||||
|
@NonNull HabitList habits)
|
||||||
{
|
{
|
||||||
super(habits);
|
super(habits);
|
||||||
this.context = context;
|
this.context = context;
|
||||||
@@ -59,15 +60,29 @@ public class LoopDBImporter extends AbstractImporter
|
|||||||
SQLiteDatabase db = SQLiteDatabase.openDatabase(file.getPath(), null,
|
SQLiteDatabase db = SQLiteDatabase.openDatabase(file.getPath(), null,
|
||||||
SQLiteDatabase.OPEN_READONLY);
|
SQLiteDatabase.OPEN_READONLY);
|
||||||
|
|
||||||
|
boolean canHandle = true;
|
||||||
|
|
||||||
Cursor c = db.rawQuery(
|
Cursor c = db.rawQuery(
|
||||||
"select count(*) from SQLITE_MASTER where name=? or name=?",
|
"select count(*) from SQLITE_MASTER where name=? or name=?",
|
||||||
new String[]{"Checkmarks", "Repetitions"});
|
new String[]{ "Checkmarks", "Repetitions" });
|
||||||
|
|
||||||
boolean result = (c.moveToFirst() && c.getInt(0) == 2);
|
if (!c.moveToFirst() || c.getInt(0) != 2)
|
||||||
|
{
|
||||||
|
Log.w("LoopDBImporter", "Cannot handle file: tables not found");
|
||||||
|
canHandle = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (db.getVersion() > BuildConfig.databaseVersion)
|
||||||
|
{
|
||||||
|
Log.w("LoopDBImporter", String.format(
|
||||||
|
"Cannot handle file: incompatible version: %d > %d",
|
||||||
|
db.getVersion(), BuildConfig.databaseVersion));
|
||||||
|
canHandle = false;
|
||||||
|
}
|
||||||
|
|
||||||
c.close();
|
c.close();
|
||||||
db.close();
|
db.close();
|
||||||
return result;
|
return canHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ public abstract class CheckmarkList
|
|||||||
*
|
*
|
||||||
* @return value of today's checkmark
|
* @return value of today's checkmark
|
||||||
*/
|
*/
|
||||||
public final int getTodayValue()
|
public int getTodayValue()
|
||||||
{
|
{
|
||||||
Checkmark today = getToday();
|
Checkmark today = getToday();
|
||||||
if (today != null) return today.getValue();
|
if (today != null) return today.getValue();
|
||||||
@@ -192,7 +192,7 @@ public abstract class CheckmarkList
|
|||||||
Checkmark newest = getNewestComputed();
|
Checkmark newest = getNewestComputed();
|
||||||
Checkmark oldest = getOldestComputed();
|
Checkmark oldest = getOldestComputed();
|
||||||
|
|
||||||
if (newest == null)
|
if (newest == null || oldest == null)
|
||||||
{
|
{
|
||||||
forceRecompute(from, to);
|
forceRecompute(from, to);
|
||||||
}
|
}
|
||||||
@@ -208,6 +208,7 @@ public abstract class CheckmarkList
|
|||||||
*
|
*
|
||||||
* @return oldest checkmark already computed
|
* @return oldest checkmark already computed
|
||||||
*/
|
*/
|
||||||
|
@Nullable
|
||||||
protected abstract Checkmark getOldestComputed();
|
protected abstract Checkmark getOldestComputed();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -285,5 +286,6 @@ public abstract class CheckmarkList
|
|||||||
*
|
*
|
||||||
* @return newest checkmark already computed
|
* @return newest checkmark already computed
|
||||||
*/
|
*/
|
||||||
|
@Nullable
|
||||||
protected abstract Checkmark getNewestComputed();
|
protected abstract Checkmark getNewestComputed();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ public abstract class ScoreList implements Iterable<Score>
|
|||||||
* @param timestamp the timestamp of a day
|
* @param timestamp the timestamp of a day
|
||||||
* @return score value for that day
|
* @return score value for that day
|
||||||
*/
|
*/
|
||||||
public final int getValue(long timestamp)
|
public synchronized final int getValue(long timestamp)
|
||||||
{
|
{
|
||||||
compute(timestamp, timestamp);
|
compute(timestamp, timestamp);
|
||||||
Score s = getComputedByTimestamp(timestamp);
|
Score s = getComputedByTimestamp(timestamp);
|
||||||
@@ -173,7 +173,7 @@ public abstract class ScoreList implements Iterable<Score>
|
|||||||
{
|
{
|
||||||
String timestamp = dateFormat.format(s.getTimestamp());
|
String timestamp = dateFormat.format(s.getTimestamp());
|
||||||
String score =
|
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));
|
out.write(String.format("%s,%s\n", timestamp, score));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ public class MemoryCheckmarkList extends CheckmarkList
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Nullable
|
||||||
protected Checkmark getOldestComputed()
|
protected Checkmark getOldestComputed()
|
||||||
{
|
{
|
||||||
if(list.isEmpty()) return null;
|
if(list.isEmpty()) return null;
|
||||||
@@ -79,6 +80,7 @@ public class MemoryCheckmarkList extends CheckmarkList
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Nullable
|
||||||
protected Checkmark getNewestComputed()
|
protected Checkmark getNewestComputed()
|
||||||
{
|
{
|
||||||
if(list.isEmpty()) return null;
|
if(list.isEmpty()) return null;
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of Loop Habit Tracker.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.isoron.uhabits.models.sqlite;
|
||||||
|
|
||||||
|
public class InvalidDatabaseVersionException extends RuntimeException
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -24,7 +24,6 @@ import android.support.annotation.*;
|
|||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import com.activeandroid.*;
|
import com.activeandroid.*;
|
||||||
import com.activeandroid.query.*;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.models.*;
|
import org.isoron.uhabits.models.*;
|
||||||
import org.isoron.uhabits.models.sqlite.records.*;
|
import org.isoron.uhabits.models.sqlite.records.*;
|
||||||
@@ -38,12 +37,22 @@ import java.util.*;
|
|||||||
*/
|
*/
|
||||||
public class SQLiteCheckmarkList extends CheckmarkList
|
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
|
@Nullable
|
||||||
private HabitRecord habitRecord;
|
private HabitRecord habitRecord;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private final SQLiteUtils<CheckmarkRecord> sqlite;
|
private final SQLiteUtils<CheckmarkRecord> sqlite;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private CachedData cache;
|
||||||
|
|
||||||
public SQLiteCheckmarkList(Habit habit)
|
public SQLiteCheckmarkList(Habit habit)
|
||||||
{
|
{
|
||||||
super(habit);
|
super(habit);
|
||||||
@@ -54,16 +63,11 @@ public class SQLiteCheckmarkList extends CheckmarkList
|
|||||||
public void add(List<Checkmark> checkmarks)
|
public void add(List<Checkmark> checkmarks)
|
||||||
{
|
{
|
||||||
check(habit.getId());
|
check(habit.getId());
|
||||||
|
|
||||||
String query =
|
|
||||||
"insert into Checkmarks(habit, timestamp, value) values (?,?,?)";
|
|
||||||
|
|
||||||
SQLiteDatabase db = Cache.openDatabase();
|
SQLiteDatabase db = Cache.openDatabase();
|
||||||
|
SQLiteStatement statement = db.compileStatement(ADD_QUERY);
|
||||||
db.beginTransaction();
|
db.beginTransaction();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
SQLiteStatement statement = db.compileStatement(query);
|
|
||||||
|
|
||||||
for (Checkmark c : checkmarks)
|
for (Checkmark c : checkmarks)
|
||||||
{
|
{
|
||||||
statement.bindLong(1, habit.getId());
|
statement.bindLong(1, habit.getId());
|
||||||
@@ -87,8 +91,7 @@ public class SQLiteCheckmarkList extends CheckmarkList
|
|||||||
check(habit.getId());
|
check(habit.getId());
|
||||||
compute(fromTimestamp, toTimestamp);
|
compute(fromTimestamp, toTimestamp);
|
||||||
|
|
||||||
String query = "select habit, timestamp, value " +
|
String query = "select habit, timestamp, value from checkmarks " +
|
||||||
"from checkmarks " +
|
|
||||||
"where habit = ? and timestamp >= ? and timestamp <= ? " +
|
"where habit = ? and timestamp >= ? and timestamp <= ? " +
|
||||||
"order by timestamp desc";
|
"order by timestamp desc";
|
||||||
|
|
||||||
@@ -101,26 +104,28 @@ public class SQLiteCheckmarkList extends CheckmarkList
|
|||||||
List<CheckmarkRecord> records = sqlite.query(query, params);
|
List<CheckmarkRecord> records = sqlite.query(query, params);
|
||||||
for (CheckmarkRecord record : records) record.habit = habitRecord;
|
for (CheckmarkRecord record : records) record.habit = habitRecord;
|
||||||
|
|
||||||
int nDays = DateUtils.getDaysBetween(fromTimestamp, toTimestamp) + 1;
|
records = fixRecords(records, habitRecord, fromTimestamp, toTimestamp);
|
||||||
if (records.size() != nDays)
|
|
||||||
{
|
|
||||||
throw new InconsistentDatabaseException(
|
|
||||||
String.format("habit=%s, %d expected, %d found",
|
|
||||||
habit.getName(), nDays, records.size()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return toCheckmarks(records);
|
return toCheckmarks(records);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTodayValue()
|
||||||
|
{
|
||||||
|
if (cache == null || cache.expired())
|
||||||
|
cache = new CachedData(super.getTodayValue());
|
||||||
|
|
||||||
|
return cache.todayValue;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void invalidateNewerThan(long timestamp)
|
public void invalidateNewerThan(long timestamp)
|
||||||
{
|
{
|
||||||
new Delete()
|
cache = null;
|
||||||
.from(CheckmarkRecord.class)
|
SQLiteDatabase db = Cache.openDatabase();
|
||||||
.where("habit = ?", habit.getId())
|
SQLiteStatement statement = db.compileStatement(INVALIDATE_QUERY);
|
||||||
.and("timestamp >= ?", timestamp)
|
statement.bindLong(1, habit.getId());
|
||||||
.execute();
|
statement.bindLong(2, timestamp);
|
||||||
|
statement.execute();
|
||||||
observable.notifyListeners();
|
observable.notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,10 +134,8 @@ public class SQLiteCheckmarkList extends CheckmarkList
|
|||||||
protected Checkmark getNewestComputed()
|
protected Checkmark getNewestComputed()
|
||||||
{
|
{
|
||||||
check(habit.getId());
|
check(habit.getId());
|
||||||
String query = "select habit, timestamp, value " +
|
String query = "select habit, timestamp, value from checkmarks " +
|
||||||
"from checkmarks " +
|
"where habit = ? " + "order by timestamp desc " +
|
||||||
"where habit = ? " +
|
|
||||||
"order by timestamp desc " +
|
|
||||||
"limit 1";
|
"limit 1";
|
||||||
|
|
||||||
String params[] = { Long.toString(habit.getId()) };
|
String params[] = { Long.toString(habit.getId()) };
|
||||||
@@ -140,13 +143,12 @@ public class SQLiteCheckmarkList extends CheckmarkList
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Nullable
|
||||||
protected Checkmark getOldestComputed()
|
protected Checkmark getOldestComputed()
|
||||||
{
|
{
|
||||||
check(habit.getId());
|
check(habit.getId());
|
||||||
String query = "select habit, timestamp, value " +
|
String query = "select habit, timestamp, value from checkmarks " +
|
||||||
"from checkmarks " +
|
"where habit = ? " + "order by timestamp asc " +
|
||||||
"where habit = ? " +
|
|
||||||
"order by timestamp asc " +
|
|
||||||
"limit 1";
|
"limit 1";
|
||||||
|
|
||||||
String params[] = { Long.toString(habit.getId()) };
|
String params[] = { Long.toString(habit.getId()) };
|
||||||
@@ -179,4 +181,44 @@ public class SQLiteCheckmarkList extends CheckmarkList
|
|||||||
for (CheckmarkRecord r : records) checkmarks.add(r.toCheckmark());
|
for (CheckmarkRecord r : records) checkmarks.add(r.toCheckmark());
|
||||||
return checkmarks;
|
return checkmarks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
String query = buildSelectQuery();
|
||||||
List<HabitRecord> recordList = sqlite.query(query, null);
|
List<HabitRecord> recordList = sqlite.query(query, null);
|
||||||
@@ -270,9 +270,7 @@ public class SQLiteHabitList extends HabitList
|
|||||||
for (HabitRecord record : recordList)
|
for (HabitRecord record : recordList)
|
||||||
{
|
{
|
||||||
Habit habit = getById(record.getId());
|
Habit habit = getById(record.getId());
|
||||||
if (habit == null)
|
if (habit == null) continue;
|
||||||
throw new RuntimeException("habit not in database");
|
|
||||||
|
|
||||||
if (!filter.matches(habit)) continue;
|
if (!filter.matches(habit)) continue;
|
||||||
habits.add(habit);
|
habits.add(habit);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,12 +19,12 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.models.sqlite;
|
package org.isoron.uhabits.models.sqlite;
|
||||||
|
|
||||||
import android.database.DatabaseUtils;
|
import android.database.*;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.*;
|
||||||
import android.support.annotation.*;
|
import android.support.annotation.*;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import com.activeandroid.Cache;
|
import com.activeandroid.*;
|
||||||
import com.activeandroid.query.*;
|
import com.activeandroid.query.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.models.*;
|
import org.isoron.uhabits.models.*;
|
||||||
@@ -43,10 +43,16 @@ public class SQLiteRepetitionList extends RepetitionList
|
|||||||
@Nullable
|
@Nullable
|
||||||
private HabitRecord habitRecord;
|
private HabitRecord habitRecord;
|
||||||
|
|
||||||
|
private SQLiteStatement addStatement;
|
||||||
|
|
||||||
public SQLiteRepetitionList(@NonNull Habit habit)
|
public SQLiteRepetitionList(@NonNull Habit habit)
|
||||||
{
|
{
|
||||||
super(habit);
|
super(habit);
|
||||||
sqlite = new SQLiteUtils<>(RepetitionRecord.class);
|
sqlite = new SQLiteUtils<>(RepetitionRecord.class);
|
||||||
|
|
||||||
|
SQLiteDatabase db = Cache.openDatabase();
|
||||||
|
String addQuery = "insert into repetitions(habit, timestamp) values (?,?)";
|
||||||
|
addStatement = db.compileStatement(addQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -61,11 +67,9 @@ public class SQLiteRepetitionList extends RepetitionList
|
|||||||
public void add(Repetition rep)
|
public void add(Repetition rep)
|
||||||
{
|
{
|
||||||
check(habit.getId());
|
check(habit.getId());
|
||||||
|
addStatement.bindLong(1, habit.getId());
|
||||||
RepetitionRecord record = new RepetitionRecord();
|
addStatement.bindLong(2, rep.getTimestamp());
|
||||||
record.copyFrom(rep);
|
addStatement.execute();
|
||||||
record.habit = habitRecord;
|
|
||||||
record.save();
|
|
||||||
observable.notifyListeners();
|
observable.notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,10 +24,10 @@ import android.support.annotation.*;
|
|||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import com.activeandroid.*;
|
import com.activeandroid.*;
|
||||||
import com.activeandroid.query.*;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.models.*;
|
import org.isoron.uhabits.models.*;
|
||||||
import org.isoron.uhabits.models.sqlite.records.*;
|
import org.isoron.uhabits.models.sqlite.records.*;
|
||||||
|
import org.isoron.uhabits.utils.*;
|
||||||
import org.jetbrains.annotations.*;
|
import org.jetbrains.annotations.*;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -37,12 +37,21 @@ import java.util.*;
|
|||||||
*/
|
*/
|
||||||
public class SQLiteScoreList extends ScoreList
|
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
|
@Nullable
|
||||||
private HabitRecord habitRecord;
|
private HabitRecord habitRecord;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private final SQLiteUtils<ScoreRecord> sqlite;
|
private final SQLiteUtils<ScoreRecord> sqlite;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private CachedData cache = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new ScoreList associated with the given habit.
|
* Constructs a new ScoreList associated with the given habit.
|
||||||
*
|
*
|
||||||
@@ -58,16 +67,11 @@ public class SQLiteScoreList extends ScoreList
|
|||||||
public void add(List<Score> scores)
|
public void add(List<Score> scores)
|
||||||
{
|
{
|
||||||
check(habit.getId());
|
check(habit.getId());
|
||||||
String query =
|
|
||||||
"insert into Score(habit, timestamp, score) values (?,?,?)";
|
|
||||||
|
|
||||||
SQLiteDatabase db = Cache.openDatabase();
|
SQLiteDatabase db = Cache.openDatabase();
|
||||||
|
SQLiteStatement statement = db.compileStatement(ADD_QUERY);
|
||||||
db.beginTransaction();
|
db.beginTransaction();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
SQLiteStatement statement = db.compileStatement(query);
|
|
||||||
|
|
||||||
for (Score s : scores)
|
for (Score s : scores)
|
||||||
{
|
{
|
||||||
statement.bindLong(1, habit.getId());
|
statement.bindLong(1, habit.getId());
|
||||||
@@ -86,20 +90,20 @@ public class SQLiteScoreList extends ScoreList
|
|||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public List<Score> getByInterval(long fromTimestamp, long toTimestamp)
|
public synchronized List<Score> getByInterval(long fromTimestamp,
|
||||||
|
long toTimestamp)
|
||||||
{
|
{
|
||||||
check(habit.getId());
|
check(habit.getId());
|
||||||
compute(fromTimestamp, toTimestamp);
|
compute(fromTimestamp, toTimestamp);
|
||||||
|
|
||||||
String query = "select habit, timestamp, score " +
|
String query = "select habit, timestamp, score from Score " +
|
||||||
"from Score " +
|
"where habit = ? and timestamp >= ? and timestamp <= ? " +
|
||||||
"where habit = ? and timestamp >= ? and timestamp <= ? " +
|
"order by timestamp desc";
|
||||||
"order by timestamp desc";
|
|
||||||
|
|
||||||
String params[] = {
|
String params[] = {
|
||||||
Long.toString(habit.getId()),
|
Long.toString(habit.getId()),
|
||||||
Long.toString(fromTimestamp),
|
Long.toString(fromTimestamp),
|
||||||
Long.toString(toTimestamp)
|
Long.toString(toTimestamp)
|
||||||
};
|
};
|
||||||
|
|
||||||
List<ScoreRecord> records = sqlite.query(query, params);
|
List<ScoreRecord> records = sqlite.query(query, params);
|
||||||
@@ -124,14 +128,23 @@ public class SQLiteScoreList extends ScoreList
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void invalidateNewerThan(long timestamp)
|
public synchronized int getTodayValue()
|
||||||
{
|
{
|
||||||
new Delete()
|
if (cache == null || cache.expired())
|
||||||
.from(ScoreRecord.class)
|
cache = new CachedData(super.getTodayValue());
|
||||||
.where("habit = ?", habit.getId())
|
|
||||||
.and("timestamp >= ?", timestamp)
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
|
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();
|
getObservable().notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,8 +172,7 @@ public class SQLiteScoreList extends ScoreList
|
|||||||
{
|
{
|
||||||
check(habit.getId());
|
check(habit.getId());
|
||||||
String query = "select habit, timestamp, score from Score " +
|
String query = "select habit, timestamp, score from Score " +
|
||||||
"where habit = ? order by timestamp desc " +
|
"where habit = ? order by timestamp desc limit 1";
|
||||||
"limit 1";
|
|
||||||
|
|
||||||
String params[] = { Long.toString(habit.getId()) };
|
String params[] = { Long.toString(habit.getId()) };
|
||||||
return getScoreFromQuery(query, params);
|
return getScoreFromQuery(query, params);
|
||||||
@@ -172,8 +184,7 @@ public class SQLiteScoreList extends ScoreList
|
|||||||
{
|
{
|
||||||
check(habit.getId());
|
check(habit.getId());
|
||||||
String query = "select habit, timestamp, score from Score " +
|
String query = "select habit, timestamp, score from Score " +
|
||||||
"where habit = ? order by timestamp asc " +
|
"where habit = ? order by timestamp asc limit 1";
|
||||||
"limit 1";
|
|
||||||
|
|
||||||
String params[] = { Long.toString(habit.getId()) };
|
String params[] = { Long.toString(habit.getId()) };
|
||||||
return getScoreFromQuery(query, params);
|
return getScoreFromQuery(query, params);
|
||||||
@@ -204,4 +215,22 @@ public class SQLiteScoreList extends ScoreList
|
|||||||
for (ScoreRecord r : records) scores.add(r.toScore());
|
for (ScoreRecord r : records) scores.add(r.toScore());
|
||||||
return scores;
|
return scores;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
package org.isoron.uhabits.models.sqlite;
|
||||||
|
|
||||||
|
import android.database.sqlite.*;
|
||||||
import android.support.annotation.*;
|
import android.support.annotation.*;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import com.activeandroid.query.*;
|
import com.activeandroid.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.models.*;
|
import org.isoron.uhabits.models.*;
|
||||||
import org.isoron.uhabits.models.sqlite.records.*;
|
import org.isoron.uhabits.models.sqlite.records.*;
|
||||||
@@ -36,6 +37,10 @@ import java.util.*;
|
|||||||
*/
|
*/
|
||||||
public class SQLiteStreakList extends StreakList
|
public class SQLiteStreakList extends StreakList
|
||||||
{
|
{
|
||||||
|
|
||||||
|
private static final String INVALIDATE_QUERY =
|
||||||
|
"delete from Streak where habit = ? and end >= ?";
|
||||||
|
|
||||||
private HabitRecord habitRecord;
|
private HabitRecord habitRecord;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@@ -73,12 +78,11 @@ public class SQLiteStreakList extends StreakList
|
|||||||
@Override
|
@Override
|
||||||
public void invalidateNewerThan(long timestamp)
|
public void invalidateNewerThan(long timestamp)
|
||||||
{
|
{
|
||||||
new Delete()
|
SQLiteDatabase db = Cache.openDatabase();
|
||||||
.from(StreakRecord.class)
|
SQLiteStatement statement = db.compileStatement(INVALIDATE_QUERY);
|
||||||
.where("habit = ?", habit.getId())
|
statement.bindLong(1, habit.getId());
|
||||||
.and("end >= ?", timestamp - DateUtils.millisecondsInOneDay)
|
statement.bindLong(2, timestamp - DateUtils.millisecondsInOneDay);
|
||||||
.execute();
|
statement.execute();
|
||||||
|
|
||||||
observable.notifyListeners();
|
observable.notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import android.database.*;
|
|||||||
import com.activeandroid.*;
|
import com.activeandroid.*;
|
||||||
import com.activeandroid.annotation.*;
|
import com.activeandroid.annotation.*;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.builder.*;
|
||||||
import org.isoron.uhabits.models.*;
|
import org.isoron.uhabits.models.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,6 +54,17 @@ public class CheckmarkRecord extends Model implements SQLiteRecord
|
|||||||
@Column(name = "value")
|
@Column(name = "value")
|
||||||
public Integer value;
|
public Integer value;
|
||||||
|
|
||||||
|
public CheckmarkRecord()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public CheckmarkRecord(HabitRecord habit, Long timestamp, Integer value)
|
||||||
|
{
|
||||||
|
this.habit = habit;
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void copyFrom(Cursor c)
|
public void copyFrom(Cursor c)
|
||||||
{
|
{
|
||||||
@@ -64,4 +76,40 @@ public class CheckmarkRecord extends Model implements SQLiteRecord
|
|||||||
{
|
{
|
||||||
return new Checkmark(timestamp, value);
|
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.app.*;
|
||||||
import android.content.*;
|
import android.content.*;
|
||||||
import android.graphics.*;
|
import android.graphics.*;
|
||||||
|
import android.os.*;
|
||||||
import android.support.annotation.*;
|
import android.support.annotation.*;
|
||||||
import android.support.v4.app.*;
|
import android.support.v4.app.*;
|
||||||
import android.support.v4.app.NotificationCompat.*;
|
import android.support.v4.app.NotificationCompat.*;
|
||||||
@@ -39,12 +40,15 @@ import java.util.*;
|
|||||||
import javax.inject.*;
|
import javax.inject.*;
|
||||||
|
|
||||||
import static android.graphics.BitmapFactory.*;
|
import static android.graphics.BitmapFactory.*;
|
||||||
|
import static android.os.Build.VERSION.*;
|
||||||
import static org.isoron.uhabits.utils.RingtoneUtils.*;
|
import static org.isoron.uhabits.utils.RingtoneUtils.*;
|
||||||
|
|
||||||
@AppScope
|
@AppScope
|
||||||
public class NotificationTray
|
public class NotificationTray
|
||||||
implements CommandRunner.Listener, Preferences.Listener
|
implements CommandRunner.Listener, Preferences.Listener
|
||||||
{
|
{
|
||||||
|
public static final String REMINDERS_CHANNEL_ID = "REMINDERS";
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
|
||||||
@@ -196,10 +200,6 @@ public class NotificationTray
|
|||||||
context.getString(R.string.check),
|
context.getString(R.string.check),
|
||||||
pendingIntents.addCheckmark(habit, timestamp));
|
pendingIntents.addCheckmark(habit, timestamp));
|
||||||
|
|
||||||
Action snoozeAction = new Action(R.drawable.ic_action_snooze,
|
|
||||||
context.getString(R.string.snooze),
|
|
||||||
pendingIntents.snoozeNotification(habit));
|
|
||||||
|
|
||||||
Bitmap wearableBg =
|
Bitmap wearableBg =
|
||||||
decodeResource(context.getResources(), R.drawable.stripe);
|
decodeResource(context.getResources(), R.drawable.stripe);
|
||||||
|
|
||||||
@@ -208,30 +208,44 @@ public class NotificationTray
|
|||||||
// WearableExtender.
|
// WearableExtender.
|
||||||
WearableExtender wearableExtender = new WearableExtender()
|
WearableExtender wearableExtender = new WearableExtender()
|
||||||
.setBackground(wearableBg)
|
.setBackground(wearableBg)
|
||||||
.addAction(checkAction)
|
.addAction(checkAction);
|
||||||
.addAction(snoozeAction);
|
|
||||||
|
|
||||||
Notification notification = new NotificationCompat.Builder(context)
|
Builder builder = new Builder(context, REMINDERS_CHANNEL_ID)
|
||||||
.setSmallIcon(R.drawable.ic_notification)
|
.setSmallIcon(R.drawable.ic_notification)
|
||||||
.setContentTitle(habit.getName())
|
.setContentTitle(habit.getName())
|
||||||
.setContentText(habit.getDescription())
|
.setContentText(habit.getDescription())
|
||||||
.setContentIntent(pendingIntents.showHabit(habit))
|
.setContentIntent(pendingIntents.showHabit(habit))
|
||||||
.setDeleteIntent(pendingIntents.dismissNotification(habit))
|
.setDeleteIntent(pendingIntents.dismissNotification(habit))
|
||||||
.addAction(checkAction)
|
.addAction(checkAction)
|
||||||
.addAction(snoozeAction)
|
|
||||||
.setSound(getRingtoneUri(context))
|
.setSound(getRingtoneUri(context))
|
||||||
.extend(wearableExtender)
|
|
||||||
.setWhen(reminderTime)
|
.setWhen(reminderTime)
|
||||||
.setShowWhen(true)
|
.setShowWhen(true)
|
||||||
.setOngoing(preferences.shouldMakeNotificationsSticky())
|
.setOngoing(preferences.shouldMakeNotificationsSticky());
|
||||||
.build();
|
|
||||||
|
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 notificationManager =
|
||||||
(NotificationManager) context.getSystemService(
|
(NotificationManager) context.getSystemService(
|
||||||
Activity.NOTIFICATION_SERVICE);
|
Activity.NOTIFICATION_SERVICE);
|
||||||
|
|
||||||
|
createAndroidNotificationChannel(context);
|
||||||
int notificationId = getNotificationId(habit);
|
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()
|
private boolean shouldShowReminderToday()
|
||||||
@@ -245,4 +259,19 @@ public class NotificationTray
|
|||||||
return reminderDays[weekday];
|
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 android.preference.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
import org.isoron.uhabits.*;
|
||||||
|
import org.isoron.uhabits.models.*;
|
||||||
|
|
||||||
import javax.inject.*;
|
import javax.inject.*;
|
||||||
|
|
||||||
@@ -48,7 +49,7 @@ public class WidgetPreferences
|
|||||||
public long getHabitIdFromWidgetId(int widgetId)
|
public long getHabitIdFromWidgetId(int widgetId)
|
||||||
{
|
{
|
||||||
Long habitId = prefs.getLong(getHabitIdKey(widgetId), -1);
|
Long habitId = prefs.getLong(getHabitIdKey(widgetId), -1);
|
||||||
if (habitId < 0) throw new RuntimeException("widget not found");
|
if (habitId < 0) throw new HabitNotFoundException();
|
||||||
|
|
||||||
return habitId;
|
return habitId;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ import org.isoron.uhabits.utils.*;
|
|||||||
|
|
||||||
import javax.inject.*;
|
import javax.inject.*;
|
||||||
|
|
||||||
|
import static org.isoron.uhabits.utils.DateUtils.*;
|
||||||
|
|
||||||
@ReceiverScope
|
@ReceiverScope
|
||||||
public class ReminderController
|
public class ReminderController
|
||||||
{
|
{
|
||||||
@@ -66,7 +68,7 @@ public class ReminderController
|
|||||||
{
|
{
|
||||||
long snoozeInterval = preferences.getSnoozeInterval();
|
long snoozeInterval = preferences.getSnoozeInterval();
|
||||||
|
|
||||||
long now = DateUtils.getLocalTime();
|
long now = applyTimezone(getLocalTime());
|
||||||
long reminderTime = now + snoozeInterval * 60 * 1000;
|
long reminderTime = now + snoozeInterval * 60 * 1000;
|
||||||
|
|
||||||
reminderScheduler.schedule(habit, reminderTime);
|
reminderScheduler.schedule(habit, reminderTime);
|
||||||
|
|||||||
@@ -74,4 +74,14 @@ public class AttributeSetUtils
|
|||||||
if (number != null) return Float.parseFloat(number);
|
if (number != null) return Float.parseFloat(number);
|
||||||
else return defaultValue;
|
else return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int getIntAttribute(@NonNull Context context,
|
||||||
|
@NonNull AttributeSet attrs,
|
||||||
|
@NonNull String name,
|
||||||
|
int defaultValue)
|
||||||
|
{
|
||||||
|
String number = getAttribute(context, attrs, name, null);
|
||||||
|
if (number != null) return Integer.parseInt(number);
|
||||||
|
else return defaultValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import android.support.annotation.*;
|
|||||||
import com.activeandroid.*;
|
import com.activeandroid.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
import org.isoron.uhabits.*;
|
||||||
|
import org.isoron.uhabits.models.sqlite.*;
|
||||||
import org.isoron.uhabits.models.sqlite.records.*;
|
import org.isoron.uhabits.models.sqlite.records.*;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
@@ -76,7 +77,16 @@ public abstract class DatabaseUtils
|
|||||||
RepetitionRecord.class, ScoreRecord.class, StreakRecord.class)
|
RepetitionRecord.class, ScoreRecord.class, StreakRecord.class)
|
||||||
.create();
|
.create();
|
||||||
|
|
||||||
ActiveAndroid.initialize(dbConfig);
|
try
|
||||||
|
{
|
||||||
|
ActiveAndroid.initialize(dbConfig);
|
||||||
|
}
|
||||||
|
catch (RuntimeException e)
|
||||||
|
{
|
||||||
|
if(e.getMessage().contains("downgrade"))
|
||||||
|
throw new InvalidDatabaseVersionException();
|
||||||
|
else throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||||
|
|||||||
@@ -42,8 +42,7 @@ public abstract class DateUtils
|
|||||||
public static long applyTimezone(long localTimestamp)
|
public static long applyTimezone(long localTimestamp)
|
||||||
{
|
{
|
||||||
TimeZone tz = getTimezone();
|
TimeZone tz = getTimezone();
|
||||||
long now = new Date(localTimestamp).getTime();
|
return localTimestamp - tz.getOffset(localTimestamp - tz.getOffset(localTimestamp));
|
||||||
return now - tz.getOffset(now);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String formatHeaderDate(GregorianCalendar day)
|
public static String formatHeaderDate(GregorianCalendar day)
|
||||||
@@ -230,8 +229,7 @@ public abstract class DateUtils
|
|||||||
public static long removeTimezone(long timestamp)
|
public static long removeTimezone(long timestamp)
|
||||||
{
|
{
|
||||||
TimeZone tz = getTimezone();
|
TimeZone tz = getTimezone();
|
||||||
long now = new Date(timestamp).getTime();
|
return timestamp + tz.getOffset(timestamp);
|
||||||
return now + tz.getOffset(now);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setFixedLocalTime(Long timestamp)
|
public static void setFixedLocalTime(Long timestamp)
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ package org.isoron.uhabits.utils;
|
|||||||
import android.content.*;
|
import android.content.*;
|
||||||
import android.content.res.*;
|
import android.content.res.*;
|
||||||
import android.graphics.*;
|
import android.graphics.*;
|
||||||
|
import android.support.v4.view.*;
|
||||||
import android.util.*;
|
import android.util.*;
|
||||||
|
import android.view.*;
|
||||||
|
|
||||||
public abstract class InterfaceUtils
|
public abstract class InterfaceUtils
|
||||||
{
|
{
|
||||||
@@ -49,4 +51,10 @@ public abstract class InterfaceUtils
|
|||||||
DisplayMetrics metrics = resources.getDisplayMetrics();
|
DisplayMetrics metrics = resources.getDisplayMetrics();
|
||||||
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, metrics);
|
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.support.annotation.*;
|
||||||
import android.widget.*;
|
import android.widget.*;
|
||||||
|
|
||||||
|
import com.activeandroid.util.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
import org.isoron.uhabits.*;
|
||||||
import org.isoron.uhabits.models.*;
|
import org.isoron.uhabits.models.*;
|
||||||
import org.isoron.uhabits.preferences.*;
|
import org.isoron.uhabits.preferences.*;
|
||||||
@@ -76,8 +78,15 @@ public abstract class BaseWidgetProvider extends AppWidgetProvider
|
|||||||
|
|
||||||
for (int id : ids)
|
for (int id : ids)
|
||||||
{
|
{
|
||||||
BaseWidget widget = getWidgetFromId(context, id);
|
try
|
||||||
widget.delete();
|
{
|
||||||
|
BaseWidget widget = getWidgetFromId(context, id);
|
||||||
|
widget.delete();
|
||||||
|
}
|
||||||
|
catch (HabitNotFoundException e)
|
||||||
|
{
|
||||||
|
Log.e("BaseWidgetProvider", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -146,6 +146,14 @@
|
|||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
android:text="Angga Rifandi (Bahasa Indonesia)"/>
|
android:text="Angga Rifandi (Bahasa Indonesia)"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/About.Item"
|
||||||
|
android:text="raden20 (Bahasa Indonesia)"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/About.Item"
|
||||||
|
android:text="azzamsa (Bahasa Indonesia)"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
android:text="David Nos (Català)"/>
|
android:text="David Nos (Català)"/>
|
||||||
@@ -178,6 +186,10 @@
|
|||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
android:text="Can Altas (Deutsch)"/>
|
android:text="Can Altas (Deutsch)"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/About.Item"
|
||||||
|
android:text="Laura Sophie (Deutsch)"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
android:text="Ander Raso Vazquez (Español)"/>
|
android:text="Ander Raso Vazquez (Español)"/>
|
||||||
@@ -186,6 +198,10 @@
|
|||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
android:text="Beriain (Euskara)"/>
|
android:text="Beriain (Euskara)"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/About.Item"
|
||||||
|
android:text="Osoitz (Euskara)"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
android:text="Andreas Michelakis (Ελληνικά)"/>
|
android:text="Andreas Michelakis (Ελληνικά)"/>
|
||||||
@@ -198,6 +214,10 @@
|
|||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
android:text="Saeed Esmaili (Fārsi)"/>
|
android:text="Saeed Esmaili (Fārsi)"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/About.Item"
|
||||||
|
android:text="Behnood HRazy (Fārsi)"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
android:text="François Mahé (Français)"/>
|
android:text="François Mahé (Français)"/>
|
||||||
@@ -214,6 +234,10 @@
|
|||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
android:text="Michael Faille (Français)"/>
|
android:text="Michael Faille (Français)"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/About.Item"
|
||||||
|
android:text="Tiralka (Français)"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
android:text="Ivan Krušlin (Hrvatski)"/>
|
android:text="Ivan Krušlin (Hrvatski)"/>
|
||||||
@@ -230,6 +254,10 @@
|
|||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
android:text="Jelle den Butter (Nederlands)"/>
|
android:text="Jelle den Butter (Nederlands)"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/About.Item"
|
||||||
|
android:text="nitovf9292 (Norsk)"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
android:text="Adam Jurkiewicz (Polski)"/>
|
android:text="Adam Jurkiewicz (Polski)"/>
|
||||||
@@ -250,18 +278,26 @@
|
|||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
android:text="Andrei Pleș (Română)"/>
|
android:text="Andrei Pleș (Română)"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/About.Item"
|
||||||
|
android:text="Andreea Muscalagiu (Română)"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
android:text="Dušan Strgar (Slovenščina)"/>
|
android:text="Dušan Strgar (Slovenščina)"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
android:text="Dalecarlian (Svenska)"/>
|
android:text="Alexander Jansson (Svenska)"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
android:text="Robin (Svenska)"/>
|
android:text="Robin (Svenska)"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/About.Item"
|
||||||
|
android:text="Sofia Veijonen (Suomen kieli)"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
android:text="Đorđe Vasiljević (српски)"/>
|
android:text="Đorđe Vasiljević (српски)"/>
|
||||||
@@ -270,6 +306,10 @@
|
|||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
android:text="Caner Başaran (Türkçe)"/>
|
android:text="Caner Başaran (Türkçe)"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/About.Item"
|
||||||
|
android:text="hodanli (Türkçe)"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
android:text="Yurii Stavytskyi (Українська)"/>
|
android:text="Yurii Stavytskyi (Українська)"/>
|
||||||
@@ -278,6 +318,14 @@
|
|||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
android:text="Rystard (Українська)"/>
|
android:text="Rystard (Українська)"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/About.Item"
|
||||||
|
android:text="Oglaigh Rystard (Українська)"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/About.Item"
|
||||||
|
android:text="taras-ko (Українська)"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
android:text="Limin Lu (中文)"/>
|
android:text="Limin Lu (中文)"/>
|
||||||
@@ -310,13 +358,62 @@
|
|||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
android:text="Al Alloush (العَرَبِية)"/>
|
android:text="Al Alloush (العَرَبِية)"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/About.Item"
|
||||||
|
android:text="Boula (العَرَبِية)"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/About.Item"
|
||||||
|
android:text="Israa Z (العَرَبِية)"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
android:text="Josh Graham (한국어 )"/>
|
android:text="Josh Graham (한국어 )"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/About.Item"
|
||||||
|
android:text="Seoyul (한국어 )"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/About.Item"
|
style="@style/About.Item"
|
||||||
android:text="Aman Satnami (हिन्दी)"/>
|
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>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</ScrollView>
|
</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/>.
|
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
-->
|
-->
|
||||||
<resources>
|
<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 -->
|
<!-- 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' -->
|
<!-- Middle part of the sentence '1 time in xx days' -->
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -19,63 +19,63 @@
|
|||||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
-->
|
-->
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">لوب ملاحق العادة </string>
|
<string name="app_name">متعقب العادة لووب</string>
|
||||||
<string name="main_activity_title">عادات</string>
|
<string name="main_activity_title">عادات</string>
|
||||||
<string name="action_settings">إعدادات</string>
|
<string name="action_settings">إعدادات</string>
|
||||||
<string name="edit">تعديل</string>
|
<string name="edit">تعديل</string>
|
||||||
<string name="delete">حذف</string>
|
<string name="delete">حذف</string>
|
||||||
<string name="archive">أرشيف</string>
|
<string name="archive">أرشفة</string>
|
||||||
<string name="unarchive">إزالة من الأرشيف</string>
|
<string name="unarchive">إزالة من الأرشيف</string>
|
||||||
<string name="add_habit">إضافة العادة</string>
|
<string name="add_habit">إضافة عادة</string>
|
||||||
<string name="color_picker_default_title">غير اللون</string>
|
<string name="color_picker_default_title">تغيير اللون</string>
|
||||||
<string name="toast_habit_created">تم صنع عادة </string>
|
<string name="toast_habit_created">تم إنشاء عادة</string>
|
||||||
<string name="toast_habit_deleted">تم حذف عادة </string>
|
<string name="toast_habit_deleted">تم حذف العادات</string>
|
||||||
<string name="toast_habit_restored">تم ترجيع عادة</string>
|
<string name="toast_habit_restored">تم إستعادة العادات</string>
|
||||||
<string name="toast_nothing_to_undo">لا شيء للتراجع</string>
|
<string name="toast_nothing_to_undo">لا شيء للألغاء</string>
|
||||||
<string name="toast_nothing_to_redo">لا شيء لتكرار</string>
|
<string name="toast_nothing_to_redo">لا شيء للإعادة</string>
|
||||||
<string name="toast_habit_changed">تم تغييرعادة</string>
|
<string name="toast_habit_changed">تم تغيير عادة</string>
|
||||||
<string name="toast_habit_changed_back">تم ترجيع العادة إلى أصلها</string>
|
<string name="toast_habit_changed_back">تم أرجاع العادة إلى أصلها</string>
|
||||||
<string name="toast_habit_archived">تم أرشيف العادات</string>
|
<string name="toast_habit_archived">تم أرشفه العادات</string>
|
||||||
<string name="toast_habit_unarchived">تم إزالة العادة من الأرشيف </string>
|
<string name="toast_habit_unarchived">تم الغاء ارشفه العادات</string>
|
||||||
<string name="overview">نظرة عامة</string>
|
<string name="overview">نظرة عامة</string>
|
||||||
<string name="habit_strength">قوة العادة</string>
|
<string name="habit_strength">قوة العادة</string>
|
||||||
<string name="history">التاريخ</string>
|
<string name="history">السجل</string>
|
||||||
<string name="clear">مسح</string>
|
<string name="clear">إزالة</string>
|
||||||
<string name="description_hint">السؤال (هل ... اليوم؟)</string>
|
<string name="description_hint">السؤال (هل ... اليوم؟)</string>
|
||||||
<string name="repeat">كرر</string>
|
<string name="repeat">كرره</string>
|
||||||
<string name="times_every">مرات في</string>
|
<string name="times_every">مرات كل</string>
|
||||||
<string name="days">أيام</string>
|
<string name="days">أيام</string>
|
||||||
<string name="reminder">تذكير</string>
|
<string name="reminder">التذكرة</string>
|
||||||
<string name="discard">حذف</string>
|
<string name="discard">تجاهل</string>
|
||||||
<string name="save">حفظ</string>
|
<string name="save">حفظ</string>
|
||||||
<string name="streaks">تقدم متتالية</string>
|
<string name="streaks">الانجازات</string>
|
||||||
<string name="no_habits_found"> لا يوجد لديك عادات مفعله</string>
|
<string name="no_habits_found">لا يوجد لديك عادات مفعلة</string>
|
||||||
<string name="long_press_to_toggle">أضغط و إستمر لتحقق أو ازل</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_name_should_not_be_blank">لا يمكن أن يكون الإسم فارغ</string>
|
||||||
<string name="validation_number_should_be_positive">يجب أن يكون الرقم إيجابي</string>
|
<string name="validation_number_should_be_positive">يجب أن يكون الرقم موجب.</string>
|
||||||
<string name="validation_at_most_one_rep_per_day">يمكن أن يكون التكرار واحدة فقط كل يوم </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="edit_habit">تعديل العادة</string>
|
||||||
<string name="check">حقق</string>
|
<string name="check">حقق</string>
|
||||||
<string name="snooze">لاحقا</string>
|
<string name="snooze">لاحقاً</string>
|
||||||
<!-- App introduction -->
|
<!-- App introduction -->
|
||||||
<string name="intro_title_1">أهلا بك</string>
|
<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_title_2">إنشاء عادات جديدة</string>
|
||||||
<string name="intro_description_2">كل يوم، بعد أداء عادتك، وضع علامة على التطبيق.</string>
|
<string name="intro_description_2">كل يوم، بعد أداء عادتك، ضع علامة عليها في التطبيق.</string>
|
||||||
<string name="intro_title_3">حافظ على القيام بذلك</string>
|
<string name="intro_title_3">حافظ على القيام بذلك</string>
|
||||||
<string name="intro_description_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="intro_description_4">رسوم بيانية مفصلة تُريك كيف تحسنت عاداتك مع مرور الوقت.</string>
|
||||||
<string name="interval_15_minutes">15 دقيقة</string>
|
<string name="interval_15_minutes">15 دقيقة</string>
|
||||||
<string name="interval_30_minutes">30 دقيقة</string>
|
<string name="interval_30_minutes">30 دقيقة</string>
|
||||||
<string name="interval_1_hour">ساعة واحدة</string>
|
<string name="interval_1_hour">ساعة واحدة</string>
|
||||||
<string name="interval_2_hour">ساعتين</string>
|
<string name="interval_2_hour">ساعتان</string>
|
||||||
<string name="interval_4_hour">أربع ساعات</string>
|
<string name="interval_4_hour">٤ ساعات</string>
|
||||||
<string name="interval_8_hour">ثماني ساعات</string>
|
<string name="interval_8_hour">8 ساعات</string>
|
||||||
<string name="interval_24_hour">24 ساعة</string>
|
<string name="interval_24_hour">٢٤ ساعة</string>
|
||||||
<string name="pref_toggle_title">تبديل بكبسه</string>
|
<string name="pref_toggle_title">تبديل وضعية العادة بضغطة قصيرة</string>
|
||||||
<string name="pref_toggle_description">أكثر سهولة، لكنه ممكن يسبب كبسات غير مقصوده</string>
|
<string name="pref_toggle_description">أكثر سهولة، لكنه ممكن يسبب كبسات غير مقصوده</string>
|
||||||
<string name="pref_snooze_interval_title">فترتي الغفوى على التذكير</string>
|
<string name="pref_snooze_interval_title">فترتي الغفوى على التذكير</string>
|
||||||
<string name="pref_rate_this_app">تقييم هذا التطبيق على جوجل بلاي</string>
|
<string name="pref_rate_this_app">تقييم هذا التطبيق على جوجل بلاي</string>
|
||||||
@@ -92,6 +92,7 @@
|
|||||||
<string name="hint_landscape">يمكنك ان ترى المزيد أيام عن طريق وضع الهاتف في وضع أفقي.</string>
|
<string name="hint_landscape">يمكنك ان ترى المزيد أيام عن طريق وضع الهاتف في وضع أفقي.</string>
|
||||||
<string name="delete_habits">حذف عادات</string>
|
<string name="delete_habits">حذف عادات</string>
|
||||||
<string name="delete_habits_message">سيتم حذف عادات بشكل دائم. هذا العمل لا يمكن التراجع عنه.</string>
|
<string name="delete_habits_message">سيتم حذف عادات بشكل دائم. هذا العمل لا يمكن التراجع عنه.</string>
|
||||||
|
<string name="habit_not_found">العادة حذفت/لم يتم العثور عليها</string>
|
||||||
<string name="weekends">عطلة نهاية الأسبوع</string>
|
<string name="weekends">عطلة نهاية الأسبوع</string>
|
||||||
<string name="any_weekday">أيام الأسبوع</string>
|
<string name="any_weekday">أيام الأسبوع</string>
|
||||||
<string name="any_day">أي يوم</string>
|
<string name="any_day">أي يوم</string>
|
||||||
@@ -156,7 +157,22 @@
|
|||||||
<string name="score">النقاط</string>
|
<string name="score">النقاط</string>
|
||||||
<string name="reminder_sound">صوت تذكير</string>
|
<string name="reminder_sound">صوت تذكير</string>
|
||||||
<string name="none">صامت</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="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="download">تحميل</string>
|
||||||
<string name="export">استخراج</string>
|
<string name="export">استخراج</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
<string name="toast_habit_changed_back">Промяната на навика е отменена.</string>
|
<string name="toast_habit_changed_back">Промяната на навика е отменена.</string>
|
||||||
<string name="toast_habit_archived">Навиците са архивирани</string>
|
<string name="toast_habit_archived">Навиците са архивирани</string>
|
||||||
<string name="toast_habit_unarchived">Навиците са разархивирани</string>
|
<string name="toast_habit_unarchived">Навиците са разархивирани</string>
|
||||||
<string name="overview">Обобщение</string>
|
<string name="overview">Обзор</string>
|
||||||
<string name="habit_strength">Сила на навика</string>
|
<string name="habit_strength">Сила на навика</string>
|
||||||
<string name="history">История</string>
|
<string name="history">История</string>
|
||||||
<string name="clear">Изчистване</string>
|
<string name="clear">Изчистване</string>
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
<string name="validation_at_most_one_rep_per_day">Позволено е до едно повторение на ден.</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="edit_habit">Редактиране на навик</string>
|
||||||
<string name="check">Потвърди</string>
|
<string name="check">Поставяне на отметка</string>
|
||||||
<string name="snooze">По-късно</string>
|
<string name="snooze">По-късно</string>
|
||||||
<!-- App introduction -->
|
<!-- App introduction -->
|
||||||
<string name="intro_title_1">Добре дошли</string>
|
<string name="intro_title_1">Добре дошли</string>
|
||||||
@@ -65,8 +65,8 @@
|
|||||||
<string name="intro_title_2">Създайте нови навици</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_title_3">Продължавайте да го изпълнявате</string>
|
||||||
<string name="intro_description_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="intro_description_4">Подробни диаграми ви показват как вашите навици са се подобрили с времето.</string>
|
||||||
<string name="interval_15_minutes">15 минути</string>
|
<string name="interval_15_minutes">15 минути</string>
|
||||||
<string name="interval_30_minutes">30 минути</string>
|
<string name="interval_30_minutes">30 минути</string>
|
||||||
@@ -74,6 +74,7 @@
|
|||||||
<string name="interval_2_hour">2 часа</string>
|
<string name="interval_2_hour">2 часа</string>
|
||||||
<string name="interval_4_hour">4 часа</string>
|
<string name="interval_4_hour">4 часа</string>
|
||||||
<string name="interval_8_hour">8 часа</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_title">Маркиране с кратко натискане</string>
|
||||||
<string name="pref_toggle_description">Поставяне на отметки с кратко натискане вместо с натискане и задържане. По-удобно, но може да доведе до неволно маркиране.</string>
|
<string name="pref_toggle_description">Поставяне на отметки с кратко натискане вместо с натискане и задържане. По-удобно, но може да доведе до неволно маркиране.</string>
|
||||||
<string name="pref_snooze_interval_title">Интервал на напомняне след отлагане</string>
|
<string name="pref_snooze_interval_title">Интервал на напомняне след отлагане</string>
|
||||||
@@ -91,6 +92,7 @@
|
|||||||
<string name="hint_landscape">Може да виждате повече дни като обърнете телефона си в хоризонтално положение.</string>
|
<string name="hint_landscape">Може да виждате повече дни като обърнете телефона си в хоризонтално положение.</string>
|
||||||
<string name="delete_habits">Изтриване на навици</string>
|
<string name="delete_habits">Изтриване на навици</string>
|
||||||
<string name="delete_habits_message">Навиците ще се изтрият перманентно. Това действие не може да бъде отменено.</string>
|
<string name="delete_habits_message">Навиците ще се изтрият перманентно. Това действие не може да бъде отменено.</string>
|
||||||
|
<string name="habit_not_found">Навикът е изтрит / не е намерен</string>
|
||||||
<string name="weekends">Събота и неделя</string>
|
<string name="weekends">Събота и неделя</string>
|
||||||
<string name="any_weekday">От понеделник до петък</string>
|
<string name="any_weekday">От понеделник до петък</string>
|
||||||
<string name="any_day">Всеки ден от седмицата</string>
|
<string name="any_day">Всеки ден от седмицата</string>
|
||||||
@@ -100,7 +102,7 @@
|
|||||||
<string name="clear_label">Изчистване</string>
|
<string name="clear_label">Изчистване</string>
|
||||||
<string name="select_hours">Избиране на час</string>
|
<string name="select_hours">Избиране на час</string>
|
||||||
<string name="select_minutes">Избиране на минута</string>
|
<string name="select_minutes">Избиране на минута</string>
|
||||||
<string name="about">Информация</string>
|
<string name="about">За приложението</string>
|
||||||
<string name="translators">Преводачи</string>
|
<string name="translators">Преводачи</string>
|
||||||
<string name="developers">Разработчици</string>
|
<string name="developers">Разработчици</string>
|
||||||
<string name="version_n">Версия %s</string>
|
<string name="version_n">Версия %s</string>
|
||||||
@@ -146,6 +148,7 @@
|
|||||||
<string name="month">Месец</string>
|
<string name="month">Месец</string>
|
||||||
<string name="quarter">Тримесечие</string>
|
<string name="quarter">Тримесечие</string>
|
||||||
<string name="year">Година</string>
|
<string name="year">Година</string>
|
||||||
|
<string name="total">Общо</string>
|
||||||
<!-- Middle part of the sentence '1 time in xx days' -->
|
<!-- Middle part of the sentence '1 time in xx days' -->
|
||||||
<string name="time_every">път в период от</string>
|
<string name="time_every">път в период от</string>
|
||||||
<string name="every_x_days">На всеки %d дни</string>
|
<string name="every_x_days">На всеки %d дни</string>
|
||||||
@@ -154,4 +157,22 @@
|
|||||||
<string name="score">Сила</string>
|
<string name="score">Сила</string>
|
||||||
<string name="reminder_sound">Звук за напомняне</string>
|
<string name="reminder_sound">Звук за напомняне</string>
|
||||||
<string name="none">Няма</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>
|
</resources>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
-->
|
-->
|
||||||
<resources>
|
<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="main_activity_title">Zvyky</string>
|
||||||
<string name="action_settings">Nastavení</string>
|
<string name="action_settings">Nastavení</string>
|
||||||
<string name="edit">Upravit</string>
|
<string name="edit">Upravit</string>
|
||||||
@@ -45,11 +45,11 @@
|
|||||||
<string name="repeat">Opakovat</string>
|
<string name="repeat">Opakovat</string>
|
||||||
<string name="times_every">krát za</string>
|
<string name="times_every">krát za</string>
|
||||||
<string name="days">dní</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="discard">Zrušit</string>
|
||||||
<string name="save">Uložit</string>
|
<string name="save">Uložit</string>
|
||||||
<string name="streaks">Serie</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="long_press_to_toggle">Stiskni a drž pro označení</string>
|
||||||
<string name="reminder_off">Vyp.</string>
|
<string name="reminder_off">Vyp.</string>
|
||||||
<string name="validation_name_should_not_be_blank">Jméno musíte vyplnit.</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="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="create_habit">Vytvořit zvyk</string>
|
||||||
<string name="edit_habit">Upravit 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>
|
<string name="snooze">Odložit</string>
|
||||||
<!-- App introduction -->
|
<!-- App introduction -->
|
||||||
<string name="intro_title_1">Vítejte</string>
|
<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_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="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">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="habit_not_found">Zvyk smazán / nenalezen</string>
|
||||||
<string name="weekends">Víkendy</string>
|
<string name="weekends">Víkendy</string>
|
||||||
<string name="any_weekday">Pondělí až pátek</string>
|
<string name="any_weekday">Pondělí až pátek</string>
|
||||||
<string name="any_day">Jakýkoliv den v týdnu</string>
|
<string name="any_day">Jakýkoliv den v týdnu</string>
|
||||||
<string name="select_weekdays">Vyber dny</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="done_label">Hotovo</string>
|
||||||
<string name="clear_label">Smazat</string>
|
<string name="clear_label">Smazat</string>
|
||||||
<string name="select_hours">Vyber hodiny</string>
|
<string name="select_hours">Vyber hodiny</string>
|
||||||
@@ -169,7 +169,7 @@
|
|||||||
<string name="action">Akce</string>
|
<string name="action">Akce</string>
|
||||||
<string name="habit">Zvyk</string>
|
<string name="habit">Zvyk</string>
|
||||||
<string name="sort">Řadit</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_name">Abecedně</string>
|
||||||
<string name="by_color">Podle barvy</string>
|
<string name="by_color">Podle barvy</string>
|
||||||
<string name="by_score">Podle skóre</string>
|
<string name="by_score">Podle skóre</string>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
-->
|
-->
|
||||||
<resources>
|
<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="main_activity_title">Gewohnheiten</string>
|
||||||
<string name="action_settings">Einstellungen</string>
|
<string name="action_settings">Einstellungen</string>
|
||||||
<string name="edit">Bearbeiten</string>
|
<string name="edit">Bearbeiten</string>
|
||||||
@@ -48,26 +48,26 @@
|
|||||||
<string name="reminder">Erinnerung</string>
|
<string name="reminder">Erinnerung</string>
|
||||||
<string name="discard">Verwerfen</string>
|
<string name="discard">Verwerfen</string>
|
||||||
<string name="save">Speichern</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="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="long_press_to_toggle">Tippe und halte um aus- bzw. abzuwählen</string>
|
||||||
<string name="reminder_off">Aus</string>
|
<string name="reminder_off">Aus</string>
|
||||||
<string name="validation_name_should_not_be_blank">Name darf nicht leer sein.</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_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="create_habit">Gewohnheit erstellen</string>
|
||||||
<string name="edit_habit">Gewohnheit bearbeiten</string>
|
<string name="edit_habit">Gewohnheit bearbeiten</string>
|
||||||
<string name="check">Abhaken</string>
|
<string name="check">Markieren</string>
|
||||||
<string name="snooze">Später</string>
|
<string name="snooze">Später</string>
|
||||||
<!-- App introduction -->
|
<!-- App introduction -->
|
||||||
<string name="intro_title_1">Willkommen</string>
|
<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_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_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_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_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_15_minutes">15 Minuten</string>
|
||||||
<string name="interval_30_minutes">30 Minuten</string>
|
<string name="interval_30_minutes">30 Minuten</string>
|
||||||
<string name="interval_1_hour">1 Stunde</string>
|
<string name="interval_1_hour">1 Stunde</string>
|
||||||
@@ -75,29 +75,29 @@
|
|||||||
<string name="interval_4_hour">4 Stunden</string>
|
<string name="interval_4_hour">4 Stunden</string>
|
||||||
<string name="interval_8_hour">8 Stunden</string>
|
<string name="interval_8_hour">8 Stunden</string>
|
||||||
<string name="interval_24_hour">24 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_title">Markierung durch kurzes Drücken ändern</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_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_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_rate_this_app">Bewerte diese App auf Google Play</string>
|
||||||
<string name="pref_send_feedback">Sende dem Entwickler Feedback</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_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="links">Links</string>
|
||||||
<string name="behavior">Verhalten</string>
|
<string name="behavior">Verhalten</string>
|
||||||
<string name="name">Name</string>
|
<string name="name">Name</string>
|
||||||
<string name="settings">Einstellungen</string>
|
<string name="settings">Einstellungen</string>
|
||||||
<string name="snooze_interval">\"Später erinnern\"-Intervall</string>
|
<string name="snooze_interval">\"Später erinnern\"-Intervall</string>
|
||||||
<string name="hint_title">Wusstest du?</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="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">Lösche Gewohnheiten</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_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="habit_not_found">Gewohnheit gelöscht / nicht gefunden</string>
|
||||||
<string name="weekends">An Wochenenden</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="any_day">Jeden Tag</string>
|
||||||
<string name="select_weekdays">Wähle die Tage</string>
|
<string name="select_weekdays">Wähle Tage aus</string>
|
||||||
<string name="export_to_csv">als CSV exportieren</string>
|
<string name="export_to_csv">Exportiere als CSV</string>
|
||||||
<string name="done_label">Fertig</string>
|
<string name="done_label">Fertig</string>
|
||||||
<string name="clear_label">Löschen</string>
|
<string name="clear_label">Löschen</string>
|
||||||
<string name="select_hours">Wähle Stunden</string>
|
<string name="select_hours">Wähle Stunden</string>
|
||||||
@@ -106,11 +106,11 @@
|
|||||||
<string name="translators">Übersetzer</string>
|
<string name="translators">Übersetzer</string>
|
||||||
<string name="developers">Entwickler</string>
|
<string name="developers">Entwickler</string>
|
||||||
<string name="version_n">Version %s</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="checkmark">Häkchen</string>
|
||||||
<string name="strength">Stärke</string>
|
<string name="strength">Stärke</string>
|
||||||
<string name="best_streaks">Beste Strähnen</string>
|
<string name="best_streaks">Beste Serien</string>
|
||||||
<string name="current_streaks">Derzeitige Strähne</string>
|
<string name="current_streaks">Derzeitige Serie</string>
|
||||||
<string name="number_of_repetitions">Anzahl der Wiederholungen</string>
|
<string name="number_of_repetitions">Anzahl der Wiederholungen</string>
|
||||||
<string name="last_x_days">Letzten %d Tage</string>
|
<string name="last_x_days">Letzten %d Tage</string>
|
||||||
<string name="last_x_weeks">Letzten %d Wochen</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="five_times_per_week">5 Mal pro Woche</string>
|
||||||
<string name="custom_frequency">Benutzerdefiniert</string>
|
<string name="custom_frequency">Benutzerdefiniert</string>
|
||||||
<string name="help">Hilfe & FAQ</string>
|
<string name="help">Hilfe & FAQ</string>
|
||||||
<string name="could_not_export">Der Export von Daten ist fehlgeschlagen.</string>
|
<string name="could_not_export">Fehler beim Exportieren der Daten.</string>
|
||||||
<string name="could_not_import">Der Import von Daten ist fehlgeschlagen.</string>
|
<string name="could_not_import">Fehler beim Importieren der Daten.</string>
|
||||||
<string name="file_not_recognized">Datei nicht erkannt.</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="full_backup_success">Vollständige Sicherung erfolgreich exportiert.</string>
|
||||||
<string name="import_data">Importiere Daten</string>
|
<string name="import_data">Importiere Daten</string>
|
||||||
<string name="export_full_backup">Exportiere vollständige Sicherung</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="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">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_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">Erzeugt eine Datei, die alle deine Daten enthält. Diese Datei kann 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">Fehlermeldung konnte nicht erstellt werden.</string>
|
<string name="bug_report_failed">Fehler beim Erstellen eines Fehlerberichts.</string>
|
||||||
<string name="generate_bug_report">Einen Fehler melden</string>
|
<string name="generate_bug_report">Erstelle einen Fehlerbericht</string>
|
||||||
<string name="troubleshooting">Fehlerbehebung</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="night_mode">Nachtmodus</string>
|
||||||
<string name="use_pure_black">Reines Schwarz im Nachtmodus verwenden</string>
|
<string name="use_pure_black">Verwende reines Schwarz im Nachtmodus</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="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="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="reverse_days_description">Zeigt die Tage im Hauptfenster in umgekehrter Reihenfolge an</string>
|
||||||
<string name="day">Tag</string>
|
<string name="day">Tag</string>
|
||||||
<string name="week">Woche</string>
|
<string name="week">Woche</string>
|
||||||
@@ -151,18 +151,18 @@
|
|||||||
<string name="total">Gesamt</string>
|
<string name="total">Gesamt</string>
|
||||||
<!-- Middle part of the sentence '1 time in xx days' -->
|
<!-- Middle part of the sentence '1 time in xx days' -->
|
||||||
<string name="time_every">Mal in</string>
|
<string name="time_every">Mal in</string>
|
||||||
<string name="every_x_days">Jeden %d Tag</string>
|
<string name="every_x_days">Alle %d Tage</string>
|
||||||
<string name="every_x_weeks">Jede %d Woche</string>
|
<string name="every_x_weeks">Alle %d Wochen</string>
|
||||||
<string name="every_x_months">Jeden %d Monat</string>
|
<string name="every_x_months">Alle %d Monate</string>
|
||||||
<string name="score">Wertung</string>
|
<string name="score">Wertung</string>
|
||||||
<string name="reminder_sound">Benachrichtigungston</string>
|
<string name="reminder_sound">Erinnerungston</string>
|
||||||
<string name="none">Kein Ton</string>
|
<string name="none">Keiner</string>
|
||||||
<string name="filter">Filter</string>
|
<string name="filter">Filter</string>
|
||||||
<string name="hide_completed">Erledigte Gewohnheiten ausblenden</string>
|
<string name="hide_completed">Abgeschlossene verbergen</string>
|
||||||
<string name="hide_archived">Archivierte Gewohnheiten ausblenden</string>
|
<string name="hide_archived">Archivierte verbergen</string>
|
||||||
<string name="sticky_notifications">Permanente Benachrichtigungen</string>
|
<string name="sticky_notifications">Fixiere Benachrichtigungen</string>
|
||||||
<string name="sticky_notifications_description">Verhindert das Wegwischen von 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="database_repaired">Datenbank repariert.</string>
|
||||||
<string name="uncheck">Abwählen</string>
|
<string name="uncheck">Abwählen</string>
|
||||||
<string name="toggle">Umschalten</string>
|
<string name="toggle">Umschalten</string>
|
||||||
@@ -174,5 +174,5 @@
|
|||||||
<string name="by_color">Nach Farbe</string>
|
<string name="by_color">Nach Farbe</string>
|
||||||
<string name="by_score">Nach Wertung</string>
|
<string name="by_score">Nach Wertung</string>
|
||||||
<string name="download">Runterladen</string>
|
<string name="download">Runterladen</string>
|
||||||
<string name="export">Export</string>
|
<string name="export">Exportieren</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -19,6 +19,76 @@
|
|||||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
-->
|
-->
|
||||||
<resources>
|
<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 -->
|
<!-- 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' -->
|
<!-- 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>
|
</resources>
|
||||||
|
|||||||
@@ -28,16 +28,16 @@
|
|||||||
<string name="unarchive">Desarchivar</string>
|
<string name="unarchive">Desarchivar</string>
|
||||||
<string name="add_habit">Agregar hábito</string>
|
<string name="add_habit">Agregar hábito</string>
|
||||||
<string name="color_picker_default_title">Cambiar color</string>
|
<string name="color_picker_default_title">Cambiar color</string>
|
||||||
<string name="toast_habit_created">Hábito creado.</string>
|
<string name="toast_habit_created">Hábito creado</string>
|
||||||
<string name="toast_habit_deleted">Hábitos eliminados.</string>
|
<string name="toast_habit_deleted">Hábitos eliminados</string>
|
||||||
<string name="toast_habit_restored">Hábitos restaurados.</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_undo">Nada que deshacer</string>
|
||||||
<string name="toast_nothing_to_redo">Nada que rehacer.</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">Hábito cambiado</string>
|
||||||
<string name="toast_habit_changed_back">Hábito cambiado devuelta.</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_archived">Hábitos archivados</string>
|
||||||
<string name="toast_habit_unarchived">Hábitos desarchivados.</string>
|
<string name="toast_habit_unarchived">Hábitos desarchivados</string>
|
||||||
<string name="overview">Visión general</string>
|
<string name="overview">Resumen</string>
|
||||||
<string name="habit_strength">Fuerza del hábito</string>
|
<string name="habit_strength">Fuerza del hábito</string>
|
||||||
<string name="history">Historial</string>
|
<string name="history">Historial</string>
|
||||||
<string name="clear">Borrar</string>
|
<string name="clear">Borrar</string>
|
||||||
@@ -52,8 +52,8 @@
|
|||||||
<string name="no_habits_found">No tienes hábitos activos</string>
|
<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="long_press_to_toggle">Mantener apretado para marcar o desmarcar</string>
|
||||||
<string name="reminder_off">Apagado</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_name_should_not_be_blank">El nombre no puede quedar en blanco.</string>
|
||||||
<string name="validation_number_should_be_positive">Número debe ser positivo.</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="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="create_habit">Crear hábito</string>
|
||||||
<string name="edit_habit">Editar 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_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_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_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_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_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_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_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_15_minutes">15 minutos</string>
|
||||||
<string name="interval_30_minutes">30 minutos</string>
|
<string name="interval_30_minutes">30 minutos</string>
|
||||||
<string name="interval_1_hour">1 hora</string>
|
<string name="interval_1_hour">1 hora</string>
|
||||||
<string name="interval_2_hour">2 horas</string>
|
<string name="interval_2_hour">2 horas</string>
|
||||||
<string name="interval_4_hour">4 horas</string>
|
<string name="interval_4_hour">4 horas</string>
|
||||||
<string name="interval_8_hour">8 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_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_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_rate_this_app">Valora esta aplicación en Google Play</string>
|
||||||
<string name="pref_send_feedback">Enviar sugerencias al desarrollador</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_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="links">Enlaces</string>
|
||||||
<string name="behavior">Comportamiento</string>
|
<string name="behavior">Comportamiento</string>
|
||||||
<string name="name">Nombre</string>
|
<string name="name">Nombre</string>
|
||||||
<string name="settings">Configuración</string>
|
<string name="settings">Configuración</string>
|
||||||
<string name="snooze_interval">Intervalo de espera</string>
|
<string name="snooze_interval">Intervalo de espera</string>
|
||||||
<string name="hint_title">¿Sabías qué?</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="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">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="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="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="any_day">Cada día</string>
|
||||||
<string name="select_weekdays">Seleccionar días</string>
|
<string name="select_weekdays">Seleccionar días</string>
|
||||||
<string name="export_to_csv">Exportar datos (CSV)</string>
|
<string name="export_to_csv">Exportar datos (CSV)</string>
|
||||||
<string name="done_label">Hecho</string>
|
<string name="done_label">Hecho</string>
|
||||||
<string name="clear_label">Quitar</string>
|
<string name="clear_label">Quitar</string>
|
||||||
<string name="select_hours">Seleccionar horas</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="about">Acerca de</string>
|
||||||
<string name="translators">Traductores</string>
|
<string name="translators">Traductores</string>
|
||||||
<string name="developers">Desarrolladores</string>
|
<string name="developers">Desarrolladores</string>
|
||||||
<string name="version_n">Versión %s</string>
|
<string name="version_n">Versión %s</string>
|
||||||
<string name="frequency">Frecuencia</string>
|
<string name="frequency">Frecuencia</string>
|
||||||
|
<string name="checkmark">Marca de verificación</string>
|
||||||
<string name="strength">Fuerza</string>
|
<string name="strength">Fuerza</string>
|
||||||
<string name="best_streaks">Mejores rachas</string>
|
<string name="best_streaks">Mejores rachas</string>
|
||||||
<string name="current_streaks">Racha actual</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="five_times_per_week">5 veces por semana</string>
|
||||||
<string name="custom_frequency">Personalizado...</string>
|
<string name="custom_frequency">Personalizado...</string>
|
||||||
<string name="help">Ayuda & FAQ</string>
|
<string name="help">Ayuda & FAQ</string>
|
||||||
<string name="could_not_export">Fallo al exportar datos.</string>
|
<string name="could_not_export">Error al exportar datos.</string>
|
||||||
<string name="could_not_import">Fallo al importar datos.</string>
|
<string name="could_not_import">Error al importar datos.</string>
|
||||||
<string name="file_not_recognized">Archivo no reconocido.</string>
|
<string name="file_not_recognized">Archivo no reconocido.</string>
|
||||||
<string name="habits_imported">Hábitos importados exitosamente.</string>
|
<string name="habits_imported">Hábitos importados exitosamente.</string>
|
||||||
<string name="full_backup_success">Copia de seguridad exportada 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="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_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="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="bug_report_failed">Error al generar el reporte de error.</string>
|
||||||
<string name="generate_bug_report">Generar informe de bug</string>
|
<string name="generate_bug_report">Generar reporte de errores</string>
|
||||||
<string name="troubleshooting">Solución de problemas</string>
|
<string name="troubleshooting">Solución de problemas</string>
|
||||||
<string name="help_translate">Ayuda a traducir esta app</string>
|
<string name="help_translate">Ayuda a traducir esta app</string>
|
||||||
<string name="night_mode">Modo nocturno</string>
|
<string name="night_mode">Modo nocturno</string>
|
||||||
@@ -145,6 +148,7 @@
|
|||||||
<string name="month">Mes</string>
|
<string name="month">Mes</string>
|
||||||
<string name="quarter">Cuatrimestre</string>
|
<string name="quarter">Cuatrimestre</string>
|
||||||
<string name="year">Año</string>
|
<string name="year">Año</string>
|
||||||
|
<string name="total">Total</string>
|
||||||
<!-- Middle part of the sentence '1 time in xx days' -->
|
<!-- Middle part of the sentence '1 time in xx days' -->
|
||||||
<string name="time_every">veces en</string>
|
<string name="time_every">veces en</string>
|
||||||
<string name="every_x_days">Cada %d días</string>
|
<string name="every_x_days">Cada %d días</string>
|
||||||
@@ -153,4 +157,22 @@
|
|||||||
<string name="score">Puntuación</string>
|
<string name="score">Puntuación</string>
|
||||||
<string name="reminder_sound">Sonido de recordatorio</string>
|
<string name="reminder_sound">Sonido de recordatorio</string>
|
||||||
<string name="none">Ninguno</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>
|
</resources>
|
||||||
|
|||||||
@@ -19,24 +19,26 @@
|
|||||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
-->
|
-->
|
||||||
<resources>
|
<resources>
|
||||||
|
<string name="app_name">Loop Habit Tracker</string>
|
||||||
<string name="main_activity_title">Ohiturak</string>
|
<string name="main_activity_title">Ohiturak</string>
|
||||||
<string name="action_settings">Ezarpenak</string>
|
<string name="action_settings">Ezarpenak</string>
|
||||||
<string name="edit">Editatu</string>
|
<string name="edit">Editatu</string>
|
||||||
<string name="delete">Ezabatu</string>
|
<string name="delete">Ezabatu</string>
|
||||||
<string name="archive">Artxibatu</string>
|
<string name="archive">Artxibatu</string>
|
||||||
<string name="unarchive">Ezartxibatu</string>
|
<string name="unarchive">Desartxibatu</string>
|
||||||
<string name="add_habit">Ohitura gehitu</string>
|
<string name="add_habit">Gehitu ohitura</string>
|
||||||
<string name="color_picker_default_title">Kolorea aldatu</string>
|
<string name="color_picker_default_title">Kolorea aldatu</string>
|
||||||
<string name="toast_habit_created">Ohitura sortu da.</string>
|
<string name="toast_habit_created">Ohitura sortu da</string>
|
||||||
<string name="toast_habit_deleted">Ohiturak ezabatu dira.</string>
|
<string name="toast_habit_deleted">Ohiturak ezabatu dira</string>
|
||||||
<string name="toast_habit_restored">Ohiturak berrezarri 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_undo">Ez dago ezer desegiteko</string>
|
||||||
<string name="toast_nothing_to_redo">Ez dago berregiteko ezer.</string>
|
<string name="toast_nothing_to_redo">Ez dago ezer berregiteko</string>
|
||||||
<string name="toast_habit_changed">Ohitura aldatu da.</string>
|
<string name="toast_habit_changed">Ohitura aldatu egin da</string>
|
||||||
<string name="toast_habit_changed_back">Ohitura lehengoratu 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_archived">Ohiturak artxibatu dira</string>
|
||||||
<string name="toast_habit_unarchived">Ohiturak ezartxibatu dira.</string>
|
<string name="toast_habit_unarchived">Ohiturak desartxibatu dira</string>
|
||||||
<string name="overview">Ikuspegi orokorra</string>
|
<string name="overview">Ikuspegi orokorra</string>
|
||||||
|
<string name="habit_strength">Ohituraren indarra</string>
|
||||||
<string name="history">Historia</string>
|
<string name="history">Historia</string>
|
||||||
<string name="clear">Garbitu</string>
|
<string name="clear">Garbitu</string>
|
||||||
<string name="description_hint">Galdera (... al duzu gaur?)</string>
|
<string name="description_hint">Galdera (... al duzu gaur?)</string>
|
||||||
@@ -46,6 +48,7 @@
|
|||||||
<string name="reminder">Oroigarria</string>
|
<string name="reminder">Oroigarria</string>
|
||||||
<string name="discard">Baztertu</string>
|
<string name="discard">Baztertu</string>
|
||||||
<string name="save">Gorde</string>
|
<string name="save">Gorde</string>
|
||||||
|
<string name="streaks">Boladak</string>
|
||||||
<string name="no_habits_found">Ez duzu ohitura aktiborik</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="long_press_to_toggle">Sakatu eta mantendu markatu edo desmarkatzeko</string>
|
||||||
<string name="reminder_off">Itzalita</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="validation_at_most_one_rep_per_day">Gehienez errepikapen bat eguneko izan dezakezu</string>
|
||||||
<string name="create_habit">Ohitura sortu</string>
|
<string name="create_habit">Ohitura sortu</string>
|
||||||
<string name="edit_habit">Ohitura editatu</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 -->
|
<!-- App introduction -->
|
||||||
<string name="intro_title_1">Ongi etorri</string>
|
<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_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_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_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_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="intro_description_4">Grafiko zehatzen bitartez denboran zehar zure ohiturak nola hobetu diren ikus ditzakezu</string>
|
||||||
<string name="interval_15_minutes">15 minutu</string>
|
<string name="interval_15_minutes">15 minutu</string>
|
||||||
@@ -68,6 +74,10 @@
|
|||||||
<string name="interval_2_hour">2 ordu</string>
|
<string name="interval_2_hour">2 ordu</string>
|
||||||
<string name="interval_4_hour">4 ordu</string>
|
<string name="interval_4_hour">4 ordu</string>
|
||||||
<string name="interval_8_hour">8 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_rate_this_app">Aplikazio hau Google Playen puntuatu</string>
|
||||||
<string name="pref_send_feedback">Zure iritzia garatzaileari bidali</string>
|
<string name="pref_send_feedback">Zure iritzia garatzaileari bidali</string>
|
||||||
<string name="pref_view_source_code">Iturburu kodea GitHuben ikusi</string>
|
<string name="pref_view_source_code">Iturburu kodea GitHuben ikusi</string>
|
||||||
@@ -76,11 +86,13 @@
|
|||||||
<string name="behavior">Portaera</string>
|
<string name="behavior">Portaera</string>
|
||||||
<string name="name">Izena</string>
|
<string name="name">Izena</string>
|
||||||
<string name="settings">Ezarpenak</string>
|
<string name="settings">Ezarpenak</string>
|
||||||
|
<string name="snooze_interval">Atzeratze tartea</string>
|
||||||
<string name="hint_title">Ba al zenekien?</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_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="hint_landscape">Egun gehiago ikus ditzakezu zure gailua paisai moduan jarriz.</string>
|
||||||
<string name="delete_habits">Ohiturak ezabatu</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="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="weekends">Asteburuak</string>
|
||||||
<string name="any_weekday">Astelehenetik ostiralera</string>
|
<string name="any_weekday">Astelehenetik ostiralera</string>
|
||||||
<string name="any_day">Astearen edozen egun</string>
|
<string name="any_day">Astearen edozen egun</string>
|
||||||
@@ -96,6 +108,9 @@
|
|||||||
<string name="version_n">%s bertsioa</string>
|
<string name="version_n">%s bertsioa</string>
|
||||||
<string name="frequency">Maiztasuna</string>
|
<string name="frequency">Maiztasuna</string>
|
||||||
<string name="checkmark">Egiaztatze marka</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="number_of_repetitions">Errepikapenen kopurua</string>
|
||||||
<string name="last_x_days">Azken %d egunak</string>
|
<string name="last_x_days">Azken %d egunak</string>
|
||||||
<string name="last_x_weeks">Azken %d asteak</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="full_backup_success">Babes kopia osoa ondo esportatu da.</string>
|
||||||
<string name="import_data">Datuak inportatu</string>
|
<string name="import_data">Datuak inportatu</string>
|
||||||
<string name="export_full_backup">Babes kopia osoa esportatu</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="bug_report_failed">Huts akats txostena sortzerakoan.</string>
|
||||||
<string name="generate_bug_report">Akats txostena sortu</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="help_translate">Lagundu aplikazio hau itzultzen</string>
|
||||||
<string name="night_mode">Gau modua</string>
|
<string name="night_mode">Gau modua</string>
|
||||||
<string name="use_pure_black">Benetazko beltza erabili gau moduan</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="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="day">Eguna</string>
|
||||||
<string name="week">Astea</string>
|
<string name="week">Astea</string>
|
||||||
<string name="month">Hilabetea</string>
|
<string name="month">Hilabetea</string>
|
||||||
<string name="quarter">Hiruhilekoa</string>
|
<string name="quarter">Hiruhilekoa</string>
|
||||||
<string name="year">Urtea</string>
|
<string name="year">Urtea</string>
|
||||||
|
<string name="total">Guztira</string>
|
||||||
<!-- Middle part of the sentence '1 time in xx days' -->
|
<!-- Middle part of the sentence '1 time in xx days' -->
|
||||||
|
<string name="time_every">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="reminder_sound">Oroigarriaren soinua</string>
|
||||||
<string name="none">Bat ere ez</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>
|
</resources>
|
||||||
|
|||||||
@@ -19,34 +19,34 @@
|
|||||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
-->
|
-->
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">عادتسنج</string>
|
<string name="app_name">Loop Habit Tracker</string>
|
||||||
<string name="main_activity_title">عادتها</string>
|
<string name="main_activity_title">عادتها</string>
|
||||||
<string name="action_settings">تنظیمات</string>
|
<string name="action_settings">تنظیمات</string>
|
||||||
<string name="edit">ویرایش</string>
|
<string name="edit">ویرایش</string>
|
||||||
<string name="delete">حذف</string>
|
<string name="delete">حذف</string>
|
||||||
<string name="archive">آرشیو کردن</string>
|
<string name="archive">بایگانی کن</string>
|
||||||
<string name="unarchive">خارج کردن از آرشیو</string>
|
<string name="unarchive">خارج کردن از بایگانی</string>
|
||||||
<string name="add_habit">افزودن عادت</string>
|
<string name="add_habit">افزودن عادت</string>
|
||||||
<string name="color_picker_default_title">تغییر رنگ</string>
|
<string name="color_picker_default_title">تغییر رنگ</string>
|
||||||
<string name="toast_habit_created">عادت ساخته شد.</string>
|
<string name="toast_habit_created">عادت ایجاد شد</string>
|
||||||
<string name="toast_habit_deleted">عادت حذف شد.</string>
|
<string name="toast_habit_deleted">عادت حذف شد</string>
|
||||||
<string name="toast_habit_restored">عادت بازگردانده شد.</string>
|
<string name="toast_habit_restored">عادت بازگردانده شد</string>
|
||||||
<string name="toast_nothing_to_undo">چیزی برای واکردن وجود ندارد.</string>
|
<string name="toast_nothing_to_undo">چیزی برای بازگرداندن به حالت قبلی وجود ندارد</string>
|
||||||
<string name="toast_nothing_to_redo">چیزی برای برگشت وجود ندارد.</string>
|
<string name="toast_nothing_to_redo">چیزی برای انجام مجدد وجود ندارد</string>
|
||||||
<string name="toast_habit_changed">عادت تغییر کرد.</string>
|
<string name="toast_habit_changed">عادت تغییر کرد.</string>
|
||||||
<string name="toast_habit_changed_back">عادت به حالت قبل برگشت.</string>
|
<string name="toast_habit_changed_back">عادت به حالت قبل برگشت</string>
|
||||||
<string name="toast_habit_archived">عادت آرشیو شد.</string>
|
<string name="toast_habit_archived">عادتها بایگانی شدند</string>
|
||||||
<string name="toast_habit_unarchived">عادت از آرشیو خارج شد.</string>
|
<string name="toast_habit_unarchived">عادتها از بایگانی خارج شدند</string>
|
||||||
<string name="overview">مرور</string>
|
<string name="overview">مرور</string>
|
||||||
<string name="habit_strength">قوت عادت</string>
|
<string name="habit_strength">قدرت عادت</string>
|
||||||
<string name="history">تاریخچه</string>
|
<string name="history">تاریخچه</string>
|
||||||
<string name="clear">حذف</string>
|
<string name="clear">بیخیال</string>
|
||||||
<string name="description_hint">سوال (آیا امروز شما…؟)</string>
|
<string name="description_hint">سوال (آیا امروز شما…؟)</string>
|
||||||
<string name="repeat">تکرار</string>
|
<string name="repeat">تکرار</string>
|
||||||
<string name="times_every">بار در هر</string>
|
<string name="times_every">بار در هر</string>
|
||||||
<string name="days">روزها</string>
|
<string name="days">روز</string>
|
||||||
<string name="reminder">یادآور</string>
|
<string name="reminder">یادآور</string>
|
||||||
<string name="discard">حذف تغییرات</string>
|
<string name="discard">بیخیال</string>
|
||||||
<string name="save">ذخیره</string>
|
<string name="save">ذخیره</string>
|
||||||
<string name="streaks">روزهای پیوسته</string>
|
<string name="streaks">روزهای پیوسته</string>
|
||||||
<string name="no_habits_found">شما هیچ عادت فعالی ندارید</string>
|
<string name="no_habits_found">شما هیچ عادت فعالی ندارید</string>
|
||||||
@@ -55,18 +55,18 @@
|
|||||||
<string name="validation_name_should_not_be_blank">جای اسم نمیتواند خالی باشد.</string>
|
<string name="validation_name_should_not_be_blank">جای اسم نمیتواند خالی باشد.</string>
|
||||||
<string name="validation_number_should_be_positive">عدد بایستی مثبت باشد.</string>
|
<string name="validation_number_should_be_positive">عدد بایستی مثبت باشد.</string>
|
||||||
<string name="validation_at_most_one_rep_per_day">شما در نهایت میتوانید یک تکرار در یک روز داشته باشید.</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="edit_habit">ویرایش عادت</string>
|
||||||
<string name="check">تیک زدن</string>
|
<string name="check">تیک زدن</string>
|
||||||
<string name="snooze">بعدا</string>
|
<string name="snooze">بعداً</string>
|
||||||
<!-- App introduction -->
|
<!-- App introduction -->
|
||||||
<string name="intro_title_1">خوش آمدید</string>
|
<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_title_2">ساخت چند عادت جدید</string>
|
||||||
<string name="intro_description_2">هر روز، بعد از اینکه کار مربوط به عادت را انجام دادید، تیک مربوط به آن در برنامه را بزنید.</string>
|
<string name="intro_description_2">هر روز، بعد از انجام عادت، آن را در برنامه تیک بزنید.</string>
|
||||||
<string name="intro_title_3">ادامه دهید</string>
|
<string name="intro_title_3">ادامه دهید</string>
|
||||||
<string name="intro_description_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="intro_description_4">نمودار جزئیات به شما نشان میدهد که چطور عادتهایتان با گذشت زمان بهبود پیدا کردهاند.</string>
|
||||||
<string name="interval_15_minutes">۱۵ دقیقه</string>
|
<string name="interval_15_minutes">۱۵ دقیقه</string>
|
||||||
<string name="interval_30_minutes">۳۰ دقیقه</string>
|
<string name="interval_30_minutes">۳۰ دقیقه</string>
|
||||||
@@ -74,30 +74,40 @@
|
|||||||
<string name="interval_2_hour">۲ ساعت</string>
|
<string name="interval_2_hour">۲ ساعت</string>
|
||||||
<string name="interval_4_hour">۴ ساعت</string>
|
<string name="interval_4_hour">۴ ساعت</string>
|
||||||
<string name="interval_8_hour">۸ ساعت</string>
|
<string name="interval_8_hour">۸ ساعت</string>
|
||||||
|
<string name="interval_24_hour">۲۴ ساعت</string>
|
||||||
<string name="pref_toggle_title">با اشارهی کوتاهمدت وضعیت عادت را تغییر بده</string>
|
<string name="pref_toggle_title">با اشارهی کوتاهمدت وضعیت عادت را تغییر بده</string>
|
||||||
<string name="pref_toggle_description">راحتتر است ولی ممکن است باعث شود اشتباهی عادتی را تیک بزنید.</string>
|
<string name="pref_toggle_description">تیک زدن با تکضربه در مقابل ضربهزدن و نگهداشتن راحتتر است ولی ممکن است باعث شود اشتباهی عادتی را تیک بزنید.</string>
|
||||||
<string name="pref_rate_this_app">دادن امتیاز به اپ در گوگلپلی</string>
|
<string name="pref_snooze_interval_title">بازه به تعویق انداختن یادآورها</string>
|
||||||
|
<string name="pref_rate_this_app">به این برنامه در گوگلپلی امتیاز بدهید</string>
|
||||||
<string name="pref_send_feedback">ارسال بازخورد به توسعهدهنده</string>
|
<string name="pref_send_feedback">ارسال بازخورد به توسعهدهنده</string>
|
||||||
<string name="pref_view_source_code">دیدن سورس اپ در گیتهاب</string>
|
<string name="pref_view_source_code">دیدن منبع برنامه در گیتهاب</string>
|
||||||
<string name="pref_view_app_introduction">دیدن معرفی اپ</string>
|
<string name="pref_view_app_introduction">مشاهده معرفی برنامه</string>
|
||||||
<string name="links">لینکها</string>
|
<string name="links">لینکها</string>
|
||||||
<string name="behavior">رفتار</string>
|
<string name="behavior">رفتار</string>
|
||||||
<string name="name">نام</string>
|
<string name="name">نام</string>
|
||||||
<string name="settings">تنظیمات</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">حذف عادتها</string>
|
||||||
|
<string name="delete_habits_message">عادتها برای همیشه حذف خواهد شد. این عمل قابل بازگشت نیست.</string>
|
||||||
|
<string name="habit_not_found">عادت حذف شده/ پیدا نشد</string>
|
||||||
<string name="weekends">آخر هفتهها</string>
|
<string name="weekends">آخر هفتهها</string>
|
||||||
<string name="any_weekday">دوشنبه تا جمعه</string>
|
<string name="any_weekday">دوشنبه تا جمعه</string>
|
||||||
<string name="any_day">هر روز از هفته</string>
|
<string name="any_day">هر روز هفته</string>
|
||||||
<string name="select_weekdays">انتخاب روزها</string>
|
<string name="select_weekdays">انتخاب روزها</string>
|
||||||
<string name="export_to_csv">دخیره به عنوان فایل CSV</string>
|
<string name="export_to_csv">صدور فایل CSV</string>
|
||||||
<string name="done_label">انجام شده</string>
|
<string name="done_label">انجام شد</string>
|
||||||
<string name="clear_label">حذف</string>
|
<string name="clear_label">بیخیال</string>
|
||||||
<string name="select_hours">انتخاب ساعتها</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="translators">مترجمان</string>
|
||||||
<string name="developers">توسعهدهندگان</string>
|
<string name="developers">توسعهدهندگان</string>
|
||||||
<string name="version_n">نسخه %s</string>
|
<string name="version_n">نسخه %s</string>
|
||||||
|
<string name="frequency">تناوب</string>
|
||||||
|
<string name="checkmark">علامت</string>
|
||||||
<string name="strength">قدرت</string>
|
<string name="strength">قدرت</string>
|
||||||
<string name="best_streaks">بهترین استمرار</string>
|
<string name="best_streaks">بهترین استمرار</string>
|
||||||
<string name="current_streaks">استمرار فعلی</string>
|
<string name="current_streaks">استمرار فعلی</string>
|
||||||
@@ -106,20 +116,39 @@
|
|||||||
<string name="last_x_weeks">%d هفته اخیر</string>
|
<string name="last_x_weeks">%d هفته اخیر</string>
|
||||||
<string name="last_x_months">%d ماه اخیر</string>
|
<string name="last_x_months">%d ماه اخیر</string>
|
||||||
<string name="last_x_years">%d سال اخیر</string>
|
<string name="last_x_years">%d سال اخیر</string>
|
||||||
|
<string name="all_time">همیشه</string>
|
||||||
<string name="every_day">هر روز</string>
|
<string name="every_day">هر روز</string>
|
||||||
<string name="every_week">هر هفته</string>
|
<string name="every_week">هر هفته</string>
|
||||||
<string name="two_times_per_week">۲ بار در هفته</string>
|
<string name="two_times_per_week">۲ بار در هفته</string>
|
||||||
<string name="five_times_per_week">۵ بار در هفته</string>
|
<string name="five_times_per_week">۵ بار در هفته</string>
|
||||||
<string name="custom_frequency">سفارشیسازی ...</string>
|
<string name="custom_frequency">سفارشیسازی ...</string>
|
||||||
<string name="help">راهنما و سوالات متداول</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="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="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="day">روز</string>
|
||||||
<string name="week">هفته</string>
|
<string name="week">هفته</string>
|
||||||
<string name="month">ماه</string>
|
<string name="month">ماه</string>
|
||||||
<string name="quarter">فصل</string>
|
<string name="quarter">فصل</string>
|
||||||
<string name="year">سال</string>
|
<string name="year">سال</string>
|
||||||
|
<string name="total">مجموع</string>
|
||||||
<!-- Middle part of the sentence '1 time in xx days' -->
|
<!-- Middle part of the sentence '1 time in xx days' -->
|
||||||
<string name="time_every">بار در هر</string>
|
<string name="time_every">بار در هر</string>
|
||||||
<string name="every_x_days">هر %d روز یکبار</string>
|
<string name="every_x_days">هر %d روز یکبار</string>
|
||||||
@@ -128,4 +157,22 @@
|
|||||||
<string name="score">امتیاز</string>
|
<string name="score">امتیاز</string>
|
||||||
<string name="reminder_sound">صدای یادآور</string>
|
<string name="reminder_sound">صدای یادآور</string>
|
||||||
<string name="none">هیچکدام</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>
|
</resources>
|
||||||
|
|||||||
@@ -19,6 +19,65 @@
|
|||||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
-->
|
-->
|
||||||
<resources>
|
<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 -->
|
<!-- 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' -->
|
<!-- Middle part of the sentence '1 time in xx days' -->
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -28,20 +28,20 @@
|
|||||||
<string name="unarchive">Désarchiver</string>
|
<string name="unarchive">Désarchiver</string>
|
||||||
<string name="add_habit">Ajouter une habitude</string>
|
<string name="add_habit">Ajouter une habitude</string>
|
||||||
<string name="color_picker_default_title">Changer la couleur</string>
|
<string name="color_picker_default_title">Changer la couleur</string>
|
||||||
<string name="toast_habit_created">Habitude créée.</string>
|
<string name="toast_habit_created">Habitude créée</string>
|
||||||
<string name="toast_habit_deleted">Habitude supprimée.</string>
|
<string name="toast_habit_deleted">Habitudes supprimées</string>
|
||||||
<string name="toast_habit_restored">Habitude rétablie.</string>
|
<string name="toast_habit_restored">Habitudes rétablies</string>
|
||||||
<string name="toast_nothing_to_undo">Rien à annuler.</string>
|
<string name="toast_nothing_to_undo">Rien à annuler</string>
|
||||||
<string name="toast_nothing_to_redo">Rien à refaire.</string>
|
<string name="toast_nothing_to_redo">Rien à refaire</string>
|
||||||
<string name="toast_habit_changed">Habitude changée.</string>
|
<string name="toast_habit_changed">Habitude changée</string>
|
||||||
<string name="toast_habit_changed_back">Habitude restauré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_archived">Habitudes archivées</string>
|
||||||
<string name="toast_habit_unarchived">Habitudes désarchivées.</string>
|
<string name="toast_habit_unarchived">Habitudes désarchivées</string>
|
||||||
<string name="overview">Vue d\'ensemble</string>
|
<string name="overview">Vue d\'ensemble</string>
|
||||||
<string name="habit_strength">Force de l\'habitude</string>
|
<string name="habit_strength">Force de l\'habitude</string>
|
||||||
<string name="history">Historique</string>
|
<string name="history">Historique</string>
|
||||||
<string name="clear">Supprimer</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="repeat">Répéter</string>
|
||||||
<string name="times_every">fois en</string>
|
<string name="times_every">fois en</string>
|
||||||
<string name="days">jours</string>
|
<string name="days">jours</string>
|
||||||
@@ -51,6 +51,7 @@
|
|||||||
<string name="streaks">Séries</string>
|
<string name="streaks">Séries</string>
|
||||||
<string name="no_habits_found">Vous n\'avez pas d\'habitudes actives</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="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_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_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>
|
<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_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_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="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_1_hour">1 heure</string>
|
||||||
<string name="interval_2_hour">2 heures</string>
|
<string name="interval_2_hour">2 heures</string>
|
||||||
<string name="interval_4_hour">4 heures</string>
|
<string name="interval_4_hour">4 heures</string>
|
||||||
<string name="interval_8_hour">8 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="interval_24_hour">24 heures</string>
|
||||||
<string name="pref_toggle_description">Plus pratique, mais peut causer des activations accidentelles.</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_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_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_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_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="links">Liens</string>
|
||||||
<string name="behavior">Comportement</string>
|
<string name="behavior">Comportement</string>
|
||||||
<string name="name">Nom</string>
|
<string name="name">Nom</string>
|
||||||
<string name="settings">Paramètres</string>
|
<string name="settings">Paramètres</string>
|
||||||
<string name="snooze_interval">Intervalle de report</string>
|
<string name="snooze_interval">Intervalle de report</string>
|
||||||
<string name="hint_title">Le saviez-vous ?</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="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">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="delete_habits_message">Les habitudes seront supprimées définitivement. Cette action est irréversible.</string>
|
||||||
<string name="weekends">Fin de semaine</string>
|
<string name="habit_not_found">Habitude supprimée / introuvable</string>
|
||||||
<string name="any_weekday">Jours de la semaine</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="any_day">N\'importe quel jour</string>
|
||||||
<string name="select_weekdays">Sélectionner des jours</string>
|
<string name="select_weekdays">Sélectionner des jours</string>
|
||||||
<string name="export_to_csv">Exporter les données dans un fichier CSV</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="about">À propos</string>
|
||||||
<string name="translators">Traducteurs</string>
|
<string name="translators">Traducteurs</string>
|
||||||
<string name="developers">Développeurs</string>
|
<string name="developers">Développeurs</string>
|
||||||
|
<string name="version_n">Version %s</string>
|
||||||
<string name="frequency">Fréquence</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="strength">Force</string>
|
||||||
<string name="best_streaks">Meilleures séries</string>
|
<string name="best_streaks">Meilleures séries</string>
|
||||||
<string name="current_streaks">Série actuelle</string>
|
<string name="current_streaks">Série actuelle</string>
|
||||||
@@ -125,15 +131,16 @@
|
|||||||
<string name="import_data">Importer des données</string>
|
<string name="import_data">Importer des données</string>
|
||||||
<string name="export_full_backup">Exporter une sauvegarde complète</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="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="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="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="generate_bug_report">Générer un rapport de bug.</string>
|
||||||
<string name="troubleshooting">Résolution de problèmes</string>
|
<string name="troubleshooting">Résolution de problèmes</string>
|
||||||
<string name="help_translate">Aider à traduire cette application</string>
|
<string name="help_translate">Aider à traduire cette application</string>
|
||||||
<string name="night_mode">Mode Nuit</string>
|
<string name="night_mode">Mode Nuit</string>
|
||||||
<string name="use_pure_black">Utiliser un noir pure dans le mode nuit.</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 pure dans le mode nuit; ça réduit l’usage de la batterie d\'un appareil ayant un écran AMOLED.</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">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="reverse_days_description">Montrer les jours dans l\'ordre inversé sur l\'écran principal</string>
|
||||||
<string name="day">Jour</string>
|
<string name="day">Jour</string>
|
||||||
@@ -141,12 +148,31 @@
|
|||||||
<string name="month">Mois</string>
|
<string name="month">Mois</string>
|
||||||
<string name="quarter">Trimestre</string>
|
<string name="quarter">Trimestre</string>
|
||||||
<string name="year">Année</string>
|
<string name="year">Année</string>
|
||||||
|
<string name="total">Total</string>
|
||||||
<!-- Middle part of the sentence '1 time in xx days' -->
|
<!-- Middle part of the sentence '1 time in xx days' -->
|
||||||
<string name="time_every">fois tous les</string>
|
<string name="time_every">fois tous les</string>
|
||||||
<string name="every_x_days">Tous les %d jours</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_weeks">Toutes les %d semaines</string>
|
||||||
<string name="every_x_months">Tous les %d mois</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="reminder_sound">Son de rappel</string>
|
||||||
<string name="none">Aucun</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>
|
</resources>
|
||||||
|
|||||||
@@ -91,6 +91,7 @@
|
|||||||
<string name="interval_2_hour">2 घंटा</string>
|
<string name="interval_2_hour">2 घंटा</string>
|
||||||
<string name="interval_4_hour">4 घंटा</string>
|
<string name="interval_4_hour">4 घंटा</string>
|
||||||
<string name="interval_8_hour">8 घंटा</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_title">टॉगल पुनरावृत्ति हल्का दबाने से</string>
|
||||||
<string name="pref_toggle_description">\"
|
<string name="pref_toggle_description">\"
|
||||||
अधिक सुविधाजनक है, लेकिन आकस्मिक टॉगल हो सकता है ।\"</string>
|
अधिक सुविधाजनक है, लेकिन आकस्मिक टॉगल हो सकता है ।\"</string>
|
||||||
@@ -179,7 +180,7 @@ repetitions की संख्या\"</string>
|
|||||||
इस फ़ाइल में वापस आयात नहीं किया जा सकता है।\"</string>
|
इस फ़ाइल में वापस आयात नहीं किया जा सकता है।\"</string>
|
||||||
<string name="export_full_backup_summary">ऐसी फाइल्स उत्पन्न करता है जिसमे आपका सारा डेटा रहता है इस फ़ाइल को वापस आयात किया जा सकता है।</string>
|
<string name="export_full_backup_summary">ऐसी फाइल्स उत्पन्न करता है जिसमे आपका सारा डेटा रहता है इस फ़ाइल को वापस आयात किया जा सकता है।</string>
|
||||||
<string name="bug_report_failed">बग रिपोर्ट जनरेट करने मे असफल</string>
|
<string name="bug_report_failed">बग रिपोर्ट जनरेट करने मे असफल</string>
|
||||||
<string name="generate_bug_report">बग रिपोर्ट जनरेट करने मे सफल</string>
|
<string name="generate_bug_report">बग रिपोर्ट जनरेट करें</string>
|
||||||
<string name="troubleshooting">\"
|
<string name="troubleshooting">\"
|
||||||
समस्या निवारण\"</string>
|
समस्या निवारण\"</string>
|
||||||
<string name="help_translate">\"
|
<string name="help_translate">\"
|
||||||
@@ -198,15 +199,26 @@ repetitions की संख्या\"</string>
|
|||||||
<string name="quarter">तिमाही</string>
|
<string name="quarter">तिमाही</string>
|
||||||
<string name="year">साल</string>
|
<string name="year">साल</string>
|
||||||
<!-- Middle part of the sentence '1 time in xx days' -->
|
<!-- Middle part of the sentence '1 time in xx days' -->
|
||||||
<string name="time_every">समय शुरू</string>
|
<string name="time_every">समय में</string>
|
||||||
<string name="every_x_days">\"
|
<string name="every_x_days">\"
|
||||||
हर %d दिन\"</string>
|
हर %d दिन\"</string>
|
||||||
<string name="every_x_weeks">\"
|
<string name="every_x_weeks">\"
|
||||||
हर %d हफ्ते\"</string>
|
हर %d हफ्ते\"</string>
|
||||||
<string name="every_x_months">\"
|
<string name="every_x_months">\"
|
||||||
हर %d साल\"</string>
|
हर %d महीने\"</string>
|
||||||
<string name="score">स्कोर</string>
|
<string name="score">स्कोर</string>
|
||||||
<string name="reminder_sound">अनुस्मारक ध्वनि</string>
|
<string name="reminder_sound">अनुस्मारक ध्वनि</string>
|
||||||
<string name="none">\"
|
<string name="none">\"
|
||||||
कोई आवाज नहीं\"</string>
|
कोई आवाज नहीं\"</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>
|
</resources>
|
||||||
|
|||||||
@@ -78,9 +78,9 @@
|
|||||||
<string name="pref_toggle_title">Označi ponavljanja sa kratkim pritisk.</string>
|
<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_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_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_rate_this_app">Ocijeni ovu aplikaciju na Google Playu</string>
|
||||||
<string name="pref_send_feedback">Pošaljite povratne informacije raz. programeru</string>
|
<string name="pref_send_feedback">Pošalji povratne informacije raz. programeru</string>
|
||||||
<string name="pref_view_source_code">Pogledajte izvorni kod na GitHubu</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="pref_view_app_introduction">Prikaži uvod u aplikaciju</string>
|
||||||
<string name="links">Poveznice</string>
|
<string name="links">Poveznice</string>
|
||||||
<string name="behavior">Ponašanje</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="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="generate_bug_report">Generiraj izvješće o pogreški</string>
|
||||||
<string name="troubleshooting">Rješavanje problema</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="night_mode">Noćni način</string>
|
||||||
<string name="use_pure_black">Koristi crnu boju za 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="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="interface_preferences">Sučelje</string>
|
||||||
<string name="reverse_days">Obrnuti poredak dana</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="day">Dan</string>
|
||||||
<string name="week">Tjedan</string>
|
<string name="week">Tjedan</string>
|
||||||
<string name="month">Mjesec</string>
|
<string name="month">Mjesec</string>
|
||||||
@@ -158,7 +158,7 @@
|
|||||||
<string name="reminder_sound">Zvuk podsjetnika</string>
|
<string name="reminder_sound">Zvuk podsjetnika</string>
|
||||||
<string name="none">Nijedan</string>
|
<string name="none">Nijedan</string>
|
||||||
<string name="filter">Filtar</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="hide_archived">Sakrij arhivirano</string>
|
||||||
<string name="sticky_notifications">Učini obavijesti trajnima</string>
|
<string name="sticky_notifications">Učini obavijesti trajnima</string>
|
||||||
<string name="sticky_notifications_description">Spriječava da se obavijesti zanemare.</string>
|
<string name="sticky_notifications_description">Spriječava da se obavijesti zanemare.</string>
|
||||||
|
|||||||