Merge branch 'hotfix/1.7.9'

pull/419/merge
Alinson S. Xavier 8 years ago
commit ce27773138

@ -1,5 +1,11 @@
# Changelog
### 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

@ -5,8 +5,8 @@ apply plugin: 'jacoco'
apply plugin: 'com.github.triplet.play'
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
compileSdkVersion 27
buildToolsVersion "27.0.3"
// signingConfigs {
// release {
@ -27,7 +27,7 @@ android {
defaultConfig {
applicationId "org.isoron.uhabits"
minSdkVersion 15
targetSdkVersion 25
targetSdkVersion 27
buildConfigField "Integer", "databaseVersion", "15"
buildConfigField "String", "databaseFilename", "\"uhabits.db\""
@ -73,7 +73,7 @@ dependencies {
androidTestApt 'com.google.dagger:dagger-compiler:2.2'
androidTestCompile 'com.android.support:support-annotations:25.3.0'
androidTestCompile 'com.android.support:support-annotations:27.1.1'
androidTestCompile 'com.android.support.test:rules:0.5'
androidTestCompile 'com.android.support.test:runner:0.5'
androidTestCompile 'com.google.auto.factory:auto-factory:1.0-beta3'
@ -84,16 +84,15 @@ dependencies {
apt 'com.google.dagger:dagger-compiler:2.2'
apt 'com.jakewharton:butterknife-compiler:8.0.1'
compile 'com.android.support:appcompat-v7:25.3.0'
compile 'com.android.support:design:25.3.0'
compile 'com.android.support:preference-v14:25.3.0'
compile 'com.android.support:support-v4:25.3.0'
compile 'com.android.support:appcompat-v7:27.1.1'
compile 'com.android.support:design:27.1.1'
compile 'com.android.support:preference-v14:27.1.1'
compile 'com.android.support:support-v4:27.1.1'
compile 'com.getpebble:pebblekit:3.0.0'
compile 'com.github.paolorotolo:appintro:3.4.0'
compile 'com.google.auto.factory:auto-factory:1.0-beta3'
compile 'com.google.dagger:dagger:2.2'
compile 'com.jakewharton:butterknife:8.0.1'
compile 'com.michaelpardo:activeandroid:3.1.0-SNAPSHOT'
compile 'com.opencsv:opencsv:3.7'
compile 'org.apmem.tools:layouts:1.10@aar'
compile 'org.jetbrains:annotations-java5:15.0'

@ -1,173 +0,0 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.pebble;
import android.content.*;
import android.support.annotation.*;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import com.getpebble.android.kit.*;
import com.getpebble.android.kit.util.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.receivers.*;
import org.json.*;
import org.junit.*;
import org.junit.runner.*;
import static com.getpebble.android.kit.Constants.*;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.core.IsEqual.*;
@RunWith(AndroidJUnit4.class)
@MediumTest
public class PebbleReceiverTest extends BaseAndroidTest
{
private Habit habit1;
private Habit habit2;
@Override
public void setUp()
{
super.setUp();
fixtures.purgeHabits(habitList);
habit1 = fixtures.createEmptyHabit();
habit1.setName("Exercise");
habit2 = fixtures.createEmptyHabit();
habit2.setName("Meditate");
}
@Test
public void testCount() throws Exception
{
onPebbleReceived((dict) -> {
assertThat(dict.getString(0), equalTo("COUNT"));
assertThat(dict.getInteger(1), equalTo(2L));
});
PebbleDictionary dict = buildCountRequest();
sendFromPebbleToAndroid(dict);
awaitLatch();
}
@Test
public void testFetch() throws Exception
{
onPebbleReceived((dict) -> {
assertThat(dict.getString(0), equalTo("HABIT"));
assertThat(dict.getInteger(1), equalTo(habit2.getId()));
assertThat(dict.getString(2), equalTo(habit2.getName()));
assertThat(dict.getInteger(3), equalTo(0L));
});
PebbleDictionary dict = buildFetchRequest(1);
sendFromPebbleToAndroid(dict);
awaitLatch();
}
// @Test
// public void testToggle() throws Exception
// {
// int v = habit1.getCheckmarks().getTodayValue();
// assertThat(v, equalTo(Checkmark.UNCHECKED));
//
// onPebbleReceived((dict) -> {
// assertThat(dict.getString(0), equalTo("OK"));
// int value = habit1.getCheckmarks().getTodayValue();
// assertThat(value, equalTo(200)); //Checkmark.CHECKED_EXPLICITLY));
// });
//
// PebbleDictionary dict = buildToggleRequest(habit1.getId());
// sendFromPebbleToAndroid(dict);
// awaitLatch();
// }
@NonNull
protected PebbleDictionary buildCountRequest()
{
PebbleDictionary dict = new PebbleDictionary();
dict.addString(0, "COUNT");
return dict;
}
@NonNull
protected PebbleDictionary buildFetchRequest(int position)
{
PebbleDictionary dict = new PebbleDictionary();
dict.addString(0, "FETCH");
dict.addInt32(1, position);
return dict;
}
protected void onPebbleReceived(PebbleProcessor processor)
{
BroadcastReceiver pebbleReceiver = new BroadcastReceiver()
{
@Override
public void onReceive(Context context, Intent intent)
{
try
{
String jsonData = intent.getStringExtra(MSG_DATA);
PebbleDictionary dict = PebbleDictionary.fromJson(jsonData);
processor.process(dict);
latch.countDown();
targetContext.unregisterReceiver(this);
}
catch (JSONException e)
{
throw new RuntimeException(e);
}
}
};
IntentFilter filter = new IntentFilter(Constants.INTENT_APP_SEND);
targetContext.registerReceiver(pebbleReceiver, filter);
}
protected void sendFromPebbleToAndroid(PebbleDictionary dict)
{
Intent intent = new Intent(Constants.INTENT_APP_RECEIVE);
intent.putExtra(Constants.APP_UUID, PebbleReceiver.WATCHAPP_UUID);
intent.putExtra(Constants.TRANSACTION_ID, 0);
intent.putExtra(Constants.MSG_DATA, dict.toJsonString());
targetContext.sendBroadcast(intent);
}
private PebbleDictionary buildToggleRequest(long habitId)
{
PebbleDictionary dict = new PebbleDictionary();
dict.addString(0, "TOGGLE");
dict.addInt32(1, (int) habitId);
return dict;
}
interface PebbleProcessor
{
void process(PebbleDictionary dict);
}
}

@ -21,8 +21,8 @@
<manifest
package="org.isoron.uhabits"
xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="35"
android:versionName="1.7.8">
android:versionCode="36"
android:versionName="1.7.9">
<uses-permission android:name="android.permission.VIBRATE"/>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 49 KiB

@ -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);
}
}

@ -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();
}
}

@ -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;
}
}
}

@ -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);
}
}
}

@ -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.
}
}

@ -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);
}
}
}
}

@ -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;
}
}

@ -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 {};
}

@ -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;
}

@ -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 ";
}
}

@ -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;
}
}

@ -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();
}
}

@ -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();
}
}

@ -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;
}
}

@ -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();
}

@ -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);
}
}

@ -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);
}
}
}

@ -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);
}
}
}

@ -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;
}
}

@ -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;
}
}

@ -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 == ' ';
}
}

@ -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;
}
}

@ -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&lt;? extends T&gt; 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;
}
}
}

@ -22,12 +22,16 @@ package org.isoron.uhabits.activities.settings;
import android.app.backup.*;
import android.content.*;
import android.os.*;
import android.provider.*;
import android.support.v7.preference.*;
import org.isoron.uhabits.R;
import org.isoron.uhabits.activities.habits.list.*;
import org.isoron.uhabits.notifications.*;
import org.isoron.uhabits.utils.*;
import static android.os.Build.VERSION.*;
public class SettingsFragment extends PreferenceFragmentCompat
implements SharedPreferences.OnSharedPreferenceChangeListener
{
@ -61,6 +65,14 @@ public class SettingsFragment extends PreferenceFragmentCompat
setResultOnPreferenceClick("bugReport", ListHabitsScreen.RESULT_BUG_REPORT);
updateRingtoneDescription();
if (SDK_INT < Build.VERSION_CODES.O)
findPreference("reminderCustomize").setVisible(false);
else
{
findPreference("reminderSound").setVisible(false);
findPreference("pref_snooze_interval").setVisible(false);
}
}
@Override
@ -88,6 +100,17 @@ public class SettingsFragment extends PreferenceFragmentCompat
RINGTONE_REQUEST_CODE);
return true;
}
else if (key.equals("reminderCustomize"))
{
if (SDK_INT < Build.VERSION_CODES.O) return true;
NotificationTray.createAndroidNotificationChannel(getContext());
Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
intent.putExtra(Settings.EXTRA_APP_PACKAGE, getContext().getPackageName());
intent.putExtra(Settings.EXTRA_CHANNEL_ID, NotificationTray.REMINDERS_CHANNEL_ID);
startActivity(intent);
return true;
}
return super.onPreferenceTreeClick(preference);
}

@ -22,6 +22,7 @@ package org.isoron.uhabits.notifications;
import android.app.*;
import android.content.*;
import android.graphics.*;
import android.os.*;
import android.support.annotation.*;
import android.support.v4.app.*;
import android.support.v4.app.NotificationCompat.*;
@ -39,12 +40,15 @@ import java.util.*;
import javax.inject.*;
import static android.graphics.BitmapFactory.*;
import static android.os.Build.VERSION.*;
import static org.isoron.uhabits.utils.RingtoneUtils.*;
@AppScope
public class NotificationTray
implements CommandRunner.Listener, Preferences.Listener
{
public static final String REMINDERS_CHANNEL_ID = "REMINDERS";
@NonNull
private final Context context;
@ -196,10 +200,6 @@ public class NotificationTray
context.getString(R.string.check),
pendingIntents.addCheckmark(habit, timestamp));
Action snoozeAction = new Action(R.drawable.ic_action_snooze,
context.getString(R.string.snooze),
pendingIntents.snoozeNotification(habit));
Bitmap wearableBg =
decodeResource(context.getResources(), R.drawable.stripe);
@ -208,30 +208,38 @@ public class NotificationTray
// WearableExtender.
WearableExtender wearableExtender = new WearableExtender()
.setBackground(wearableBg)
.addAction(checkAction)
.addAction(snoozeAction);
.addAction(checkAction);
Notification notification = new NotificationCompat.Builder(context)
Builder builder = new Builder(context, REMINDERS_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(habit.getName())
.setContentText(habit.getDescription())
.setContentIntent(pendingIntents.showHabit(habit))
.setDeleteIntent(pendingIntents.dismissNotification(habit))
.addAction(checkAction)
.addAction(snoozeAction)
.setSound(getRingtoneUri(context))
.extend(wearableExtender)
.setWhen(reminderTime)
.setShowWhen(true)
.setOngoing(preferences.shouldMakeNotificationsSticky())
.build();
.setOngoing(preferences.shouldMakeNotificationsSticky());
if(SDK_INT < Build.VERSION_CODES.O) {
Action snoozeAction = new Action(R.drawable.ic_action_snooze,
context.getString(R.string.snooze),
pendingIntents.snoozeNotification(habit));
wearableExtender.addAction(snoozeAction);
builder.addAction(snoozeAction);
}
builder.extend(wearableExtender);
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(
Activity.NOTIFICATION_SERVICE);
createAndroidNotificationChannel(context);
int notificationId = getNotificationId(habit);
notificationManager.notify(notificationId, notification);
notificationManager.notify(notificationId, builder.build());
}
private boolean shouldShowReminderToday()
@ -245,4 +253,19 @@ public class NotificationTray
return reminderDays[weekday];
}
}
public static void createAndroidNotificationChannel(Context context) {
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(
Activity.NOTIFICATION_SERVICE);
if (SDK_INT >= Build.VERSION_CODES.O)
{
NotificationChannel channel =
new NotificationChannel(REMINDERS_CHANNEL_ID,
context.getResources().getString(R.string.reminder),
NotificationManager.IMPORTANCE_DEFAULT);
notificationManager.createNotificationChannel(channel);
}
}
}

@ -186,6 +186,10 @@
style="@style/About.Item"
android:text="Can Altas (Deutsch)"/>
<TextView
style="@style/About.Item"
android:text="Laura Sophie (Deutsch)"/>
<TextView
style="@style/About.Item"
android:text="Ander Raso Vazquez (Español)"/>
@ -194,6 +198,10 @@
style="@style/About.Item"
android:text="Beriain (Euskara)"/>
<TextView
style="@style/About.Item"
android:text="Osoitz (Euskara)"/>
<TextView
style="@style/About.Item"
android:text="Andreas Michelakis (Ελληνικά)"/>
@ -206,6 +214,10 @@
style="@style/About.Item"
android:text="Saeed Esmaili (Fārsi)"/>
<TextView
style="@style/About.Item"
android:text="Behnood HRazy (Fārsi)"/>
<TextView
style="@style/About.Item"
android:text="François Mahé (Français)"/>
@ -222,6 +234,10 @@
style="@style/About.Item"
android:text="Michael Faille (Français)"/>
<TextView
style="@style/About.Item"
android:text="Tiralka (Français)"/>
<TextView
style="@style/About.Item"
android:text="Ivan Krušlin (Hrvatski)"/>
@ -262,6 +278,10 @@
style="@style/About.Item"
android:text="Andrei Pleș (Română)"/>
<TextView
style="@style/About.Item"
android:text="Andreea Muscalagiu (Română)"/>
<TextView
style="@style/About.Item"
android:text="Dušan Strgar (Slovenščina)"/>
@ -274,6 +294,10 @@
style="@style/About.Item"
android:text="Robin (Svenska)"/>
<TextView
style="@style/About.Item"
android:text="Sofia Veijonen (Suomen kieli)"/>
<TextView
style="@style/About.Item"
android:text="Đorđe Vasiljević (српски)"/>
@ -282,6 +306,10 @@
style="@style/About.Item"
android:text="Caner Başaran (Türkçe)"/>
<TextView
style="@style/About.Item"
android:text="hodanli (Türkçe)"/>
<TextView
style="@style/About.Item"
android:text="Yurii Stavytskyi (Українська)"/>
@ -294,6 +322,10 @@
style="@style/About.Item"
android:text="Oglaigh Rystard (Українська)"/>
<TextView
style="@style/About.Item"
android:text="taras-ko (Українська)"/>
<TextView
style="@style/About.Item"
android:text="Limin Lu (中文)"/>
@ -326,6 +358,14 @@
style="@style/About.Item"
android:text="Al Alloush (العَرَبِية‎)"/>
<TextView
style="@style/About.Item"
android:text="Boula (العَرَبِية‎)"/>
<TextView
style="@style/About.Item"
android:text="Israa Z (العَرَبِية‎)"/>
<TextView
style="@style/About.Item"
android:text="Josh Graham (한국어 )"/>
@ -350,6 +390,30 @@
style="@style/About.Item"
android:text="Mahdi Nasiri (فارسی‎)"/>
<TextView
style="@style/About.Item"
android:text="Mohammed Imthath (தமிழ்‎)"/>
<TextView
style="@style/About.Item"
android:text="magimai (தமிழ்‎)"/>
<TextView
style="@style/About.Item"
android:text="Anshoe (தமிழ்‎)"/>
<TextView
style="@style/About.Item"
android:text="Trần Thái (Tiếng Việt)"/>
<TextView
style="@style/About.Item"
android:text="Anh Quân (Tiếng Việt)"/>
<TextView
style="@style/About.Item"
android:text="pnhpnh (Tiếng Việt)"/>
</LinearLayout>
</LinearLayout>
</ScrollView>

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@ -19,6 +19,34 @@
~ with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<string name="app_name">Herhaalde Gewoonte Boekhouer</string>
<string name="main_activity_title">Gewoontes</string>
<string name="action_settings">Instellings</string>
<string name="edit">Redigeer</string>
<string name="delete">Verwyder</string>
<string name="archive">Argiveer</string>
<string name="unarchive">Deargiveer</string>
<string name="add_habit">Voeg gewoonte by</string>
<string name="color_picker_default_title">Verander kleur</string>
<string name="toast_habit_created">Gewoonte geskep</string>
<string name="toast_habit_deleted">Gewoontes verwyder</string>
<string name="toast_habit_restored">Gewoontes herstel</string>
<string name="toast_nothing_to_undo">Niks om terug te doen nie</string>
<string name="toast_nothing_to_redo">Niks om oor te doen nie</string>
<string name="toast_habit_changed">Gewoonte verander</string>
<string name="toast_habit_changed_back">Gewoonte terug verander</string>
<string name="toast_habit_archived">Gewoontes geargiveer</string>
<string name="toast_habit_unarchived">Gewoontes gedeargiveer</string>
<string name="overview">Oorsig</string>
<!-- App introduction -->
<string name="intro_title_1">Welkom</string>
<string name="interval_15_minutes">15 minute</string>
<string name="interval_30_minutes">30 minute</string>
<string name="interval_1_hour">1 uur</string>
<string name="interval_2_hour">2 ure</string>
<string name="interval_4_hour">4 ure</string>
<string name="interval_8_hour">8 ure</string>
<string name="interval_24_hour">24 ure</string>
<string name="settings">Instellings</string>
<!-- Middle part of the sentence '1 time in xx days' -->
</resources>

@ -19,63 +19,63 @@
~ with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<string name="app_name">لوب ملاحق العادة </string>
<string name="app_name">متعقب العادة لووب</string>
<string name="main_activity_title">عادات</string>
<string name="action_settings">إعدادات</string>
<string name="edit">تعديل</string>
<string name="delete">حذف</string>
<string name="archive">أرشيف</string>
<string name="archive">أرشفة</string>
<string name="unarchive">إزالة من الأرشيف</string>
<string name="add_habit">إضافة العادة</string>
<string name="color_picker_default_title">غير اللون</string>
<string name="toast_habit_created">تم صنع عادة </string>
<string name="toast_habit_deleted">تم حذف عادة </string>
<string name="toast_habit_restored">تم ترجيع عادة</string>
<string name="toast_nothing_to_undo">لا شيء للتراجع</string>
<string name="toast_nothing_to_redo">لا شيء لتكرار</string>
<string name="add_habit">إضافة عادة</string>
<string name="color_picker_default_title">تغيير اللون</string>
<string name="toast_habit_created">تم إنشاء عادة</string>
<string name="toast_habit_deleted">تم حذف العادات</string>
<string name="toast_habit_restored">تم إستعادة العادات</string>
<string name="toast_nothing_to_undo">لا شيء للألغاء</string>
<string name="toast_nothing_to_redo">لا شيء للإعادة</string>
<string name="toast_habit_changed">تم تغيير عادة</string>
<string name="toast_habit_changed_back">تم ترجيع العادة إلى أصلها</string>
<string name="toast_habit_archived">تم أرشيف العادات</string>
<string name="toast_habit_unarchived">تم إزالة العادة من الأرشيف </string>
<string name="toast_habit_changed_back">تم أرجاع العادة إلى أصلها</string>
<string name="toast_habit_archived">تم أرشفه العادات</string>
<string name="toast_habit_unarchived">تم الغاء ارشفه العادات</string>
<string name="overview">نظرة عامة</string>
<string name="habit_strength">قوة العادة</string>
<string name="history">التاريخ</string>
<string name="clear">مسح</string>
<string name="history">السجل</string>
<string name="clear">إزالة</string>
<string name="description_hint">السؤال (هل ... اليوم؟)</string>
<string name="repeat">كرر</string>
<string name="times_every">مرات في</string>
<string name="repeat">كرره</string>
<string name="times_every">مرات كل</string>
<string name="days">أيام</string>
<string name="reminder">تذكير</string>
<string name="discard">حذف</string>
<string name="reminder">التذكرة</string>
<string name="discard">تجاهل</string>
<string name="save">حفظ</string>
<string name="streaks">تقدم متتالية</string>
<string name="no_habits_found"> لا يوجد لديك عادات مفعله</string>
<string name="streaks">الانجازات</string>
<string name="no_habits_found">لا يوجد لديك عادات مفعلة</string>
<string name="long_press_to_toggle">أضغط و إستمر لتحقق أو ازل</string>
<string name="reminder_off">أوقف</string>
<string name="reminder_off">إيقاف</string>
<string name="validation_name_should_not_be_blank">لا يمكن أن يكون الإسم فارغ</string>
<string name="validation_number_should_be_positive">يجب أن يكون الرقم إيجابي</string>
<string name="validation_at_most_one_rep_per_day">يمكن أن يكون التكرار واحدة فقط كل يوم </string>
<string name="create_habit">اخلق عادة</string>
<string name="validation_number_should_be_positive">يجب أن يكون الرقم موجب.</string>
<string name="validation_at_most_one_rep_per_day">يجب أن يكون التكرار مرة واحدة فقط كل يوم</string>
<string name="create_habit">انشاء العادة</string>
<string name="edit_habit">تعديل العادة</string>
<string name="check">حقق</string>
<string name="snooze">لاحقا</string>
<string name="snooze">لاحقاً</string>
<!-- App introduction -->
<string name="intro_title_1">أهلا بك</string>
<string name="intro_description_1">لوب يساعدك على خلق والحفاظ على العادات الجيدة.</string>
<string name="intro_title_2">إنشاء بعض عادات جديدة</string>
<string name="intro_description_2">كل يوم، بعد أداء عادتك، وضع علامة على التطبيق.</string>
<string name="intro_description_1">لوب يساعدك في بدأ عادات جيدة والحفاظ عليها.</string>
<string name="intro_title_2">إنشاء عادات جديدة</string>
<string name="intro_description_2">كل يوم، بعد أداء عادتك، ضع علامة عليها في التطبيق.</string>
<string name="intro_title_3">حافظ على القيام بذلك</string>
<string name="intro_description_3">العادة المستمرة لفترات طويلة تكسب نجمة كامله</string>
<string name="intro_title_4">تتبع تقدمك</string>
<string name="intro_description_4">رسوم بيانية مفصلة تبين لكم كيف تحسن عاداتك مع مرور الوقت.</string>
<string name="intro_description_3">العادة المستمرة لفترة طويلة تكسب نجمة كامله.</string>
<string name="intro_title_4">تتبع اداءك</string>
<string name="intro_description_4">رسوم بيانية مفصلة تُريك كيف تحسنت عاداتك مع مرور الوقت.</string>
<string name="interval_15_minutes">15 دقيقة</string>
<string name="interval_30_minutes">30 دقيقة</string>
<string name="interval_1_hour">ساعة واحدة</string>
<string name="interval_2_hour">ساعتين</string>
<string name="interval_4_hour">أربع ساعات</string>
<string name="interval_8_hour">ثماني ساعات</string>
<string name="interval_24_hour">24 ساعة</string>
<string name="pref_toggle_title">تبديل بكبسه</string>
<string name="interval_2_hour">ساعتان</string>
<string name="interval_4_hour">٤ ساعات</string>
<string name="interval_8_hour">8 ساعات</string>
<string name="interval_24_hour">٢٤ ساعة</string>
<string name="pref_toggle_title">تبديل وضعية العادة بضغطة قصيرة</string>
<string name="pref_toggle_description">أكثر سهولة، لكنه ممكن يسبب كبسات غير مقصوده</string>
<string name="pref_snooze_interval_title">فترتي الغفوى على التذكير</string>
<string name="pref_rate_this_app">تقييم هذا التطبيق على جوجل بلاي</string>
@ -92,6 +92,7 @@
<string name="hint_landscape">يمكنك ان ترى المزيد أيام عن طريق وضع الهاتف في وضع أفقي.</string>
<string name="delete_habits">حذف عادات</string>
<string name="delete_habits_message">سيتم حذف عادات بشكل دائم. هذا العمل لا يمكن التراجع عنه.</string>
<string name="habit_not_found">العادة حذفت/لم يتم العثور عليها</string>
<string name="weekends">عطلة نهاية الأسبوع</string>
<string name="any_weekday">أيام الأسبوع</string>
<string name="any_day">أي يوم</string>
@ -156,7 +157,22 @@
<string name="score">النقاط</string>
<string name="reminder_sound">صوت تذكير</string>
<string name="none">صامت</string>
<string name="filter">تصنيف</string>
<string name="hide_completed">إخفاء المكتملة</string>
<string name="hide_archived">إخفاء المؤرشفة</string>
<string name="sticky_notifications">جعل الإشعارات ثابتة</string>
<string name="sticky_notifications_description">منع الإشعارات من تمريرها بعيداً.</string>
<string name="repair_database">إصلاح قاعدة البيانات</string>
<string name="database_repaired">تم إصلاح قاعدة البيانات.</string>
<string name="uncheck">إلغاء تحديد</string>
<string name="toggle">تبديل</string>
<string name="action">عمل</string>
<string name="habit">عادة</string>
<string name="sort">فرز</string>
<string name="manually">يدوياً</string>
<string name="by_name">حسب الإسم</string>
<string name="by_color">حسب اللون</string>
<string name="by_score">حسب النقاط</string>
<string name="download">تحميل</string>
<string name="export">استخراج</string>
</resources>

@ -61,7 +61,7 @@
<string name="snooze">Später</string>
<!-- App introduction -->
<string name="intro_title_1">Willkommen</string>
<string name="intro_description_1">Loop Habit Tracker hilft dir gute Gewohnheiten anzueignen.</string>
<string name="intro_description_1">Loop Habit Tracker hilft dir dabei, gute Gewohnheiten anzunehmen.</string>
<string name="intro_title_2">Erstelle neue Gewohnheiten</string>
<string name="intro_description_2">Hake die Gewohnheit jeden Tag in der App ab, nachdem du sie erledigt hast.</string>
<string name="intro_title_3">Bleib dran</string>
@ -75,7 +75,7 @@
<string name="interval_4_hour">4 Stunden</string>
<string name="interval_8_hour">8 Stunden</string>
<string name="interval_24_hour">24 Stunden</string>
<string name="pref_toggle_title">Markierung durch kurzes Tippen ändern</string>
<string name="pref_toggle_title">Markierung durch kurzes Drücken ändern</string>
<string name="pref_toggle_description">Markierungen durch einfaches Tippen setzen anstatt durch Tippen und Halten. Bequemer, kann aber versehentlich eine Markierung ändern.</string>
<string name="pref_snooze_interval_title">\"Später erinnern\"-Intervall bei Erinnerungen</string>
<string name="pref_rate_this_app">Bewerte diese App auf Google Play</string>
@ -94,7 +94,7 @@
<string name="delete_habits_message">Die Gewohnheit wird für immer gelöscht. Dies kann nicht rückgängig gemacht werden.</string>
<string name="habit_not_found">Gewohnheit gelöscht / nicht gefunden</string>
<string name="weekends">An Wochenenden</string>
<string name="any_weekday">Werktags</string>
<string name="any_weekday">Montag bis Freitag</string>
<string name="any_day">Jeden Tag</string>
<string name="select_weekdays">Wähle Tage aus</string>
<string name="export_to_csv">Exportiere als CSV</string>

@ -19,6 +19,76 @@
~ with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<string name="main_activity_title">Kutimoj</string>
<string name="action_settings">Agordoj</string>
<string name="edit">Redakti</string>
<string name="delete">Forigi</string>
<string name="archive">Arĥivo</string>
<string name="unarchive">Elarĥivigi</string>
<string name="add_habit">Aldonu kutimon</string>
<string name="color_picker_default_title">Ŝanĝi koloron</string>
<string name="toast_habit_changed">Kutimo ŝanĝita</string>
<string name="toast_habit_archived">Kutimo arĥivita</string>
<string name="habit_strength">Kutimo forteco</string>
<string name="days">tagoj</string>
<string name="reminder">Memorigaĵoj</string>
<string name="discard">Nuligi</string>
<string name="save">Konservi</string>
<string name="streaks">Strioj</string>
<string name="reminder_off">Neaktiva</string>
<string name="snooze">Poste</string>
<!-- App introduction -->
<string name="intro_title_1">Bonvenon</string>
<string name="interval_15_minutes">15 minutoj</string>
<string name="interval_30_minutes">30 minutoj</string>
<string name="settings">Agordoj</string>
<string name="delete_habits">Forigi kutimojn</string>
<string name="weekends">Semajnfinoj</string>
<string name="any_weekday">Lundo al vendredo</string>
<string name="any_day">Io semajntago</string>
<string name="select_weekdays">Elekti tagojn</string>
<string name="export_to_csv">Eksporti kiel CSV</string>
<string name="done_label">Farite</string>
<string name="select_hours">Elekti horojn</string>
<string name="select_minutes">Elekti minutojn</string>
<string name="about">Pri programo</string>
<string name="translators">Tradukantoj</string>
<string name="developers">Evoluigantoj</string>
<string name="version_n">Versio %s</string>
<string name="frequency">Frekvenco</string>
<string name="strength">Forteco</string>
<string name="number_of_repetitions">Nombro de ripetoj</string>
<string name="last_x_days">Lastaj %d tagoj</string>
<string name="last_x_weeks">Lastaj %d semajnoj</string>
<string name="last_x_months">Lastaj %d monatoj</string>
<string name="last_x_years">Lastaj %d jaroj</string>
<string name="all_time">Ĉiuj tempoj</string>
<string name="every_day">Ĉiu tago</string>
<string name="every_week">Ĉiu semajno</string>
<string name="two_times_per_week">Dufoje en semajno</string>
<string name="five_times_per_week">Kvinfoje en semajno</string>
<string name="help">Helpo &amp; Ofte Demandite</string>
<string name="file_not_recognized">Dosiero ne rekonita.</string>
<string name="full_backup_success">Plena savkopio sukcese eksportita.</string>
<string name="troubleshooting">Problemserĉado</string>
<string name="night_mode">Nokta reĝimo</string>
<string name="day">Tago</string>
<string name="week">Semajno</string>
<string name="month">Monato</string>
<string name="quarter">Jarkvarono</string>
<string name="year">Jaro</string>
<!-- Middle part of the sentence '1 time in xx days' -->
<string name="none">Nenio</string>
<string name="filter">Filtrilo</string>
<string name="hide_completed">Kaŝi kompletajn</string>
<string name="hide_archived">Kaŝi arĥivitajn</string>
<string name="repair_database">Ripari datumbazon</string>
<string name="database_repaired">Datumbazon riparita.</string>
<string name="action">Ago</string>
<string name="habit">Kutimo</string>
<string name="sort">Enkursigi</string>
<string name="by_name">Laŭ nomo</string>
<string name="by_color">Laŭ koloro</string>
<string name="download">Elŝuti</string>
<string name="export">Eksporti</string>
</resources>

@ -28,20 +28,20 @@
<string name="unarchive">Desarchivar</string>
<string name="add_habit">Agregar hábito</string>
<string name="color_picker_default_title">Cambiar color</string>
<string name="toast_habit_created">Hábito creado.</string>
<string name="toast_habit_deleted">Hábitos eliminados.</string>
<string name="toast_habit_restored">Hábitos restaurados.</string>
<string name="toast_nothing_to_undo">Nada que deshacer.</string>
<string name="toast_nothing_to_redo">Nada que rehacer.</string>
<string name="toast_habit_changed">Hábito cambiado.</string>
<string name="toast_habit_changed_back">Hábito cambiado nuevamente.</string>
<string name="toast_habit_archived">Hábitos archivados.</string>
<string name="toast_habit_unarchived">Hábitos desarchivados.</string>
<string name="toast_habit_created">Hábito creado</string>
<string name="toast_habit_deleted">Hábitos eliminados</string>
<string name="toast_habit_restored">Hábitos restaurados</string>
<string name="toast_nothing_to_undo">Nada que deshacer</string>
<string name="toast_nothing_to_redo">Nada que rehacer</string>
<string name="toast_habit_changed">Hábito cambiado</string>
<string name="toast_habit_changed_back">Cambio en hábito vuelto atrás</string>
<string name="toast_habit_archived">Hábitos archivados</string>
<string name="toast_habit_unarchived">Hábitos desarchivados</string>
<string name="overview">Resumen</string>
<string name="habit_strength">Fuerza del hábito</string>
<string name="history">Historial</string>
<string name="clear">Eliminar</string>
<string name="description_hint">Pregunta (Has ___ hoy?)</string>
<string name="clear">Borrar</string>
<string name="description_hint">Pregunta (Has ... hoy?)</string>
<string name="repeat">Repetir</string>
<string name="times_every">veces cada</string>
<string name="days">días</string>
@ -64,7 +64,7 @@
<string name="intro_description_1">Loop Analizador de Hábitos te ayuda a crear y mantener buenos hábitos.</string>
<string name="intro_title_2">Crea algunos hábitos nuevos</string>
<string name="intro_description_2">Cada día, después de realizar tu hábito, pon una marca en la 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_title_4">Haz un seguimiento de tu progreso</string>
<string name="intro_description_4">Gráficos detallados muestran cómo mejoraron sus hábitos con el tiempo.</string>
@ -75,9 +75,9 @@
<string name="interval_4_hour">4 horas</string>
<string name="interval_8_hour">8 horas</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_title">Marca las repeticiones con una pulsación corta</string>
<string name="pref_toggle_description">Más cómodo, pero puede causar marcas accidentales.</string>
<string name="pref_snooze_interval_title">Tiempo de espera al aplazar recordatorios.</string>
<string name="pref_snooze_interval_title">Tiempo de espera al aplazar recordatorios</string>
<string name="pref_rate_this_app">Valora esta aplicación en Google Play</string>
<string name="pref_send_feedback">Enviar sugerencias al desarrollador</string>
<string name="pref_view_source_code">Ver código fuente en GitHub</string>
@ -88,20 +88,20 @@
<string name="settings">Configuración</string>
<string name="snooze_interval">Intervalo de espera</string>
<string name="hint_title">¿Sabías qué?</string>
<string name="hint_drag">Para reordenar las entradas, mantén la pulsación sobre el nombre del hábito, después arrástralo a su posición correcta.</string>
<string name="hint_drag">Para reordenar las entradas, mantén la pulsado sobre el nombre del hábito, después arrástralo a su posición correcta.</string>
<string name="hint_landscape">Puedes ver más días al poner tu teléfono en modo horizontal.</string>
<string name="delete_habits">Eliminar Hábitos</string>
<string name="delete_habits_message">Los hábitos serán eliminados permanentemente. Esta acción no se puede deshacer.</string>
<string name="habit_not_found">Hábito eliminado / no encontrado</string>
<string name="weekends">Fines de semana</string>
<string name="any_weekday">Días laborables</string>
<string name="any_weekday">De lunes a viernes</string>
<string name="any_day">Cada día</string>
<string name="select_weekdays">Seleccionar días</string>
<string name="export_to_csv">Exportar datos (CSV)</string>
<string name="done_label">Hecho</string>
<string name="clear_label">Quitar</string>
<string name="select_hours">Seleccionar horas</string>
<string name="select_minutes">Seleccionar</string>
<string name="select_minutes">Seleccionar minutos</string>
<string name="about">Acerca de</string>
<string name="translators">Traductores</string>
<string name="developers">Desarrolladores</string>

@ -19,24 +19,24 @@
~ with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<string name="app_name">Ohitura Tracker Loop</string>
<string name="app_name">Loop Habit Tracker</string>
<string name="main_activity_title">Ohiturak</string>
<string name="action_settings">Ezarpenak</string>
<string name="edit">Editatu</string>
<string name="delete">Ezabatu</string>
<string name="archive">Artxibatu</string>
<string name="unarchive">Ezartxibatu</string>
<string name="add_habit">Ohitura gehitu</string>
<string name="unarchive">Desartxibatu</string>
<string name="add_habit">Gehitu ohitura</string>
<string name="color_picker_default_title">Kolorea aldatu</string>
<string name="toast_habit_created">Ohitura sortu da.</string>
<string name="toast_habit_deleted">Ohiturak ezabatu dira.</string>
<string name="toast_habit_restored">Ohiturak berrezarri dira.</string>
<string name="toast_nothing_to_undo">Ez dago desegiteko ezer.</string>
<string name="toast_nothing_to_redo">Ez dago berregiteko ezer.</string>
<string name="toast_habit_changed">Ohitura aldatu da.</string>
<string name="toast_habit_changed_back">Ohitura lehengoratu da.</string>
<string name="toast_habit_archived">Ohiturak artxibatu dira.</string>
<string name="toast_habit_unarchived">Ohiturak ezartxibatu dira.</string>
<string name="toast_habit_created">Ohitura sortu da</string>
<string name="toast_habit_deleted">Ohiturak ezabatu dira</string>
<string name="toast_habit_restored">Ohiturak berrezarri dira</string>
<string name="toast_nothing_to_undo">Ez dago ezer desegiteko</string>
<string name="toast_nothing_to_redo">Ez dago ezer berregiteko</string>
<string name="toast_habit_changed">Ohitura aldatu egin da</string>
<string name="toast_habit_changed_back">Ohitura berrezarri da</string>
<string name="toast_habit_archived">Ohiturak artxibatu dira</string>
<string name="toast_habit_unarchived">Ohiturak desartxibatu dira</string>
<string name="overview">Ikuspegi orokorra</string>
<string name="habit_strength">Ohituraren indarra</string>
<string name="history">Historia</string>
@ -58,10 +58,10 @@
<string name="create_habit">Ohitura sortu</string>
<string name="edit_habit">Ohitura editatu</string>
<string name="check">Markatu</string>
<string name="snooze">Beranduago</string>
<string name="snooze">Geroago</string>
<!-- App introduction -->
<string name="intro_title_1">Ongi etorri</string>
<string name="intro_description_1">Loop Habit Trackerek ohitura onak hartzen eta mantentzen laguntzen dizu.</string>
<string name="intro_description_1">Loop Habit Tracker-ek ohitura onak hartzen eta mantentzen laguntzen dizu.</string>
<string name="intro_title_2">Sor itzazu ohitura berri batzuk</string>
<string name="intro_description_2">Egunero, zure ohitura egin ostean, jarri ezazu egiaztatze marka bat aplikazioan.</string>
<string name="intro_title_3">Jarrai ezazu ohitura egiten</string>
@ -77,7 +77,7 @@
<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 gogorarazpenetan</string>
<string name="pref_snooze_interval_title">Atzeratze tartea oroigarrietan</string>
<string name="pref_rate_this_app">Aplikazio hau Google Playen puntuatu</string>
<string name="pref_send_feedback">Zure iritzia garatzaileari bidali</string>
<string name="pref_view_source_code">Iturburu kodea GitHuben ikusi</string>
@ -158,7 +158,7 @@
<string name="reminder_sound">Oroigarriaren soinua</string>
<string name="none">Bat ere ez</string>
<string name="filter">Iragazkia</string>
<string name="hide_completed">Lortutakoak ezkutatu</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>

@ -19,24 +19,24 @@
~ with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<string name="app_name">عادت‌سنج لوپ</string>
<string name="app_name">Loop Habit Tracker</string>
<string name="main_activity_title">عادت‌ها</string>
<string name="action_settings">تنظیمات</string>
<string name="edit">ویرایش</string>
<string name="delete">حذف کن</string>
<string name="delete">حذف</string>
<string name="archive">بایگانی کن</string>
<string name="unarchive">خارج کردن از بایگانی</string>
<string name="add_habit">افزودن عادت</string>
<string name="color_picker_default_title">تغییر رنگ</string>
<string name="toast_habit_created">عادت ساخته شد.</string>
<string name="toast_habit_deleted">عادت حذف شد.</string>
<string name="toast_habit_restored">عادت بازگردانده شد.</string>
<string name="toast_habit_created">عادت ایجاد شد</string>
<string name="toast_habit_deleted">عادت حذف شد</string>
<string name="toast_habit_restored">عادت بازگردانده شد</string>
<string name="toast_nothing_to_undo">چیزی برای بازگرداندن به حالت قبلی وجود ندارد</string>
<string name="toast_nothing_to_redo">چیزی برای انجام مجدد وجود ندارد</string>
<string name="toast_habit_changed">عادت تغییر کرد.</string>
<string name="toast_habit_changed_back">عادت به حالت قبل برگشت</string>
<string name="toast_habit_archived">عادات بایگانی شدند</string>
<string name="toast_habit_unarchived">عادت از بایگانی خارج شدند</string>
<string name="toast_habit_archived">عادتها بایگانی شدند</string>
<string name="toast_habit_unarchived">عادتها از بایگانی خارج شدند</string>
<string name="overview">مرور</string>
<string name="habit_strength">قدرت عادت</string>
<string name="history">تاریخچه</string>

@ -19,6 +19,65 @@
~ with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<string name="app_name">Rutiini - Tracker</string>
<string name="main_activity_title">Rutiinit</string>
<string name="action_settings">Asetukset</string>
<string name="edit">Muokkaa</string>
<string name="delete">Poista</string>
<string name="archive">Arkistoi</string>
<string name="add_habit">Lisää rutiini</string>
<string name="color_picker_default_title">Vaihda väriä</string>
<string name="toast_habit_created">Rutiini luotu</string>
<string name="toast_habit_deleted">Rutiinit poistettu</string>
<string name="toast_habit_restored">Rutiinit palautettu</string>
<string name="toast_habit_changed">Rutiini muutettu</string>
<string name="toast_habit_changed_back">Rutiini muutettu takaisin</string>
<string name="overview">Yleiskatsaus</string>
<string name="history">Historia</string>
<string name="clear">Tyhjennä</string>
<string name="description_hint">Kysymys (Teitkö... tänään?)</string>
<string name="repeat">Toista</string>
<string name="times_every">kertaa</string>
<string name="days">päivässä</string>
<string name="reminder">Muistutus</string>
<string name="discard">Hylkää</string>
<string name="save">Tallenna</string>
<string name="streaks">Pisimmät toistot</string>
<string name="no_habits_found">Ei aktiivisia rutiineja</string>
<string name="long_press_to_toggle">Paina pitkään merkitäksesi suoritetuksi tai postaaksesi suorituksen</string>
<string name="reminder_off">Pois päältä</string>
<string name="validation_name_should_not_be_blank">Nimi ei voi olla tyhjä.</string>
<string name="validation_number_should_be_positive">Luvun on oltava positiivinen.</string>
<string name="create_habit">Luo rutiini</string>
<string name="edit_habit">Muokkaa rutiinia</string>
<string name="check">Tehty</string>
<string name="snooze">Lykkää</string>
<!-- App introduction -->
<string name="intro_title_1">Tervetuloa</string>
<string name="intro_title_2">Merkitse uusia rutiineja</string>
<string name="intro_description_2">Joka päivä, suoritettuasi rutiinin, merkitse se sovellukseen.</string>
<string name="links">Linkit</string>
<string name="behavior">Käyttäytyminen</string>
<string name="name">Nimi</string>
<string name="settings">Asetukset</string>
<string name="hint_title">Tiesitkö?</string>
<string name="done_label">Valmis</string>
<string name="clear_label">Tyhjennä</string>
<string name="translators">Kääntäjät</string>
<string name="developers">Kehittäjät</string>
<string name="version_n">Versio %s</string>
<string name="every_day">Joka päivä</string>
<string name="every_week">Joka viikko</string>
<string name="two_times_per_week">2 kertaa viikossa</string>
<string name="five_times_per_week">5 kertaa viikossa</string>
<string name="custom_frequency">Mukautettu&#8230;</string>
<string name="night_mode">Yötila</string>
<string name="use_pure_black">Käytä puhdasta mustaa yötilassa</string>
<string name="day">Päivä</string>
<string name="week">Viikko</string>
<string name="month">Kuukausi</string>
<string name="quarter">Kvartaali</string>
<string name="year">Vuosi</string>
<string name="total">Yhteensä</string>
<!-- Middle part of the sentence '1 time in xx days' -->
</resources>

@ -29,8 +29,8 @@
<string name="add_habit">Ajouter une habitude</string>
<string name="color_picker_default_title">Changer la couleur</string>
<string name="toast_habit_created">Habitude créée</string>
<string name="toast_habit_deleted">Habitude supprimée</string>
<string name="toast_habit_restored">Habitude rétablie</string>
<string name="toast_habit_deleted">Habitudes supprimées</string>
<string name="toast_habit_restored">Habitudes rétablies</string>
<string name="toast_nothing_to_undo">Rien à annuler</string>
<string name="toast_nothing_to_redo">Rien à refaire</string>
<string name="toast_habit_changed">Habitude changée</string>
@ -75,8 +75,8 @@
<string name="interval_4_hour">4 heures</string>
<string name="interval_8_hour">8 heures</string>
<string name="interval_24_hour">24 heures</string>
<string name="pref_toggle_title">Activer les répétitions avec un appui court</string>
<string name="pref_toggle_description">Pointe l\'habitude avec un appui court plutôt qu\'un appuie long. 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_rate_this_app">Notez cette app sur le Google Play Store</string>
<string name="pref_send_feedback">Envoyez un avis au développeur</string>
@ -91,10 +91,10 @@
<string name="hint_drag">Pour réordonner les habitudes, faites un appui long sur le nom de l\'habitude et placez-la à la bonne place.</string>
<string name="hint_landscape">Vous pouvez voir plus de jours en mettant votre téléphone en mode paysage.</string>
<string name="delete_habits">Supprimer des habitudes</string>
<string name="delete_habits_message">Les habitudes seront supprimées définitivement. Cette action ne peut être annulée.</string>
<string name="delete_habits_message">Les habitudes seront supprimées définitivement. Cette action est irréversible.</string>
<string name="habit_not_found">Habitude supprimée / introuvable</string>
<string name="weekends">Fin de semaine</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="select_weekdays">Sélectionner des jours</string>
<string name="export_to_csv">Exporter les données dans un fichier CSV</string>
@ -107,7 +107,7 @@
<string name="developers">Développeurs</string>
<string name="version_n">Version %s</string>
<string name="frequency">Fréquence</string>
<string name="checkmark">Croix</string>
<string name="checkmark">Case à cocher</string>
<string name="strength">Force</string>
<string name="best_streaks">Meilleures séries</string>
<string name="current_streaks">Série actuelle</string>
@ -131,7 +131,7 @@
<string name="import_data">Importer des données</string>
<string name="export_full_backup">Exporter une sauvegarde complète</string>
<string name="import_data_summary">Supporte les sauvegardes complètes générées par cette application, ainsi que les fichiers Tickmate, HabitBull et Rewire. Voir la FAQ pour plus d\'informations.</string>
<string name="export_as_csv_summary">Génère des fichiers pouvant être ouverts par des tableurs comme Microsoft Excel ou LibreOffice Calc. Ces fichiers ne peuvent être réimportés.</string>
<string name="export_as_csv_summary">Génère des fichiers pouvant être ouverts par des tableurs comme Microsoft Excel ou LibreOffice Calc. Ce fichier ne peut pas être réimporté.</string>
<string name="export_full_backup_summary">Génère un fichier contenant toutes vos données. Ce fichier peut être réimporté.</string>
<string name="bug_report_failed">La génération du rapport de bug a échouée.</string>
<string name="generate_bug_report">Générer un rapport de bug.</string>
@ -154,7 +154,7 @@
<string name="every_x_days">Tous les %d jours</string>
<string name="every_x_weeks">Toutes les %d semaines</string>
<string name="every_x_months">Tous les %d mois</string>
<string name="score">Pointage</string>
<string name="score">Score</string>
<string name="reminder_sound">Son de rappel</string>
<string name="none">Aucun</string>
<string name="filter">Filtre</string>
@ -162,7 +162,7 @@
<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 le base de donné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>

@ -37,7 +37,7 @@
<string name="toast_habit_changed_back">Kebiasaan telah dikembalikan.</string>
<string name="toast_habit_archived">Kebiasaan diarsipkan.</string>
<string name="toast_habit_unarchived">Kebiasaan dikeluarkan dari arsip.</string>
<string name="overview">Keseluruhan</string>
<string name="overview">Ikhtisar</string>
<string name="habit_strength">Kekuatan Kebiasaan</string>
<string name="history">Riwayat</string>
<string name="clear">Bersihkan</string>
@ -50,7 +50,7 @@
<string name="save">Simpan</string>
<string name="streaks">Rentetan</string>
<string name="no_habits_found">Anda tidak memiliki Kebiasaan yang aktif</string>
<string name="long_press_to_toggle">Tekan dan tahan untuk menambah atau menghapus tanda cek</string>
<string name="long_press_to_toggle">Tekan dan tahan untuk menambah atau menghapus centang</string>
<string name="reminder_off">Mati</string>
<string name="validation_name_should_not_be_blank">Nama tidak boleh kosong.</string>
<string name="validation_number_should_be_positive">Angka harus positif.</string>
@ -66,8 +66,8 @@
<string name="intro_description_2">Berikan tanda cek setiap kali Anda selesai melakukannya.</string>
<string name="intro_title_3">Terus lakukan</string>
<string name="intro_description_3">Kebiasaan yang dilakukan secara konsisten dalam jangka waktu panjang akan mendapatkan tanda bintang penuh.</string>
<string name="intro_title_4">Catat perkembangan Anda</string>
<string name="intro_description_4">Detail grafik menampilkan perkembangan Kebiasaanmu dari waktu ke waktu.</string>
<string name="intro_title_4">Lacak perkembangan Anda</string>
<string name="intro_description_4">Grafik terperinci menampilkan perkembangan Kebiasaanmu dari waktu ke waktu.</string>
<string name="interval_15_minutes">15 menit</string>
<string name="interval_30_minutes">30 menit</string>
<string name="interval_1_hour">1 jam</string>
@ -76,12 +76,12 @@
<string name="interval_8_hour">8 jam</string>
<string name="interval_24_hour">24 jam</string>
<string name="pref_toggle_title">Tandai dengan cepat.</string>
<string name="pref_toggle_description">Lebih nyaman namun memungkinkan kesalahan.</string>
<string name="pref_toggle_description">Beri tanda cek dengan sekali ketuk bukan tekan-dan-tahan. Lebih nyaman namun memungkinkan kesalahan.</string>
<string name="pref_snooze_interval_title">Durasi tunda sejenak pada pengingat</string>
<string name="pref_rate_this_app">Berikan rating aplikasi ini di Google Play</string>
<string name="pref_send_feedback">Kirimkan umpan balik kepada Developer</string>
<string name="pref_view_source_code">Lihat kode aplikasi di GitHub</string>
<string name="pref_view_app_introduction">Perkenalan aplikasi</string>
<string name="pref_view_app_introduction">Tampilkan perkenalan aplikasi</string>
<string name="links">Tautan</string>
<string name="behavior">Kebiasaan</string>
<string name="name">Nama</string>
@ -127,15 +127,15 @@
<string name="could_not_import">Gagal mengimpor data.</string>
<string name="file_not_recognized">File tidak dikenali.</string>
<string name="habits_imported">Impor data berhasil.</string>
<string name="full_backup_success">Ekspor data berhasil.</string>
<string name="full_backup_success">Seluruh data berhasil di-ekpor.</string>
<string name="import_data">Impor data</string>
<string name="export_full_backup">Ekspor data</string>
<string name="import_data_summary">Mendukung ekspor data dan file dari aplikasi Tickmate, HabitBull atau Rewire. Lihat FAQ untuk informasi lebih lanjut.</string>
<string name="export_as_csv_summary">Menghasilkan lembar kerja yang dapat dibuka menggunakan aplikasi seperti Microsoft Excel atau OpenOffice Calc. File ini tidak dapat di-impor kembali.</string>
<string name="export_full_backup_summary">Menghasilkan file yang berisikan seluruh data. File ini dapat di-impor kembali.</string>
<string name="export_full_backup">Ekspor keseluruhan data</string>
<string name="import_data_summary">Mendukung ekspor data dan berkas dari aplikasi Tickmate, HabitBull atau Rewire. Lihat FAQ untuk informasi lebih lanjut.</string>
<string name="export_as_csv_summary">Menghasilkan lembar kerja yang dapat dibuka menggunakan aplikasi seperti Microsoft Excel atau OpenOffice Calc. Berkas ini tidak dapat di-impor kembali.</string>
<string name="export_full_backup_summary">Menghasilkan berkas yang berisikan seluruh data. Berkas ini dapat di-impor kembali.</string>
<string name="bug_report_failed">Gagal membuat laporan masalah.</string>
<string name="generate_bug_report">Membuat laporan masalah</string>
<string name="troubleshooting">Troubleshoot</string>
<string name="troubleshooting">Penyelesaian masalah</string>
<string name="help_translate">Bantu menerjemahkan aplikasi ini</string>
<string name="night_mode">Mode malam</string>
<string name="use_pure_black">Gunakan warna hitam pada mode malam</string>

@ -26,17 +26,17 @@
<string name="delete">Elimina</string>
<string name="archive">Archivia</string>
<string name="unarchive">Ripristina</string>
<string name="add_habit">Aggiungi</string>
<string name="add_habit">Aggiungi abitudine</string>
<string name="color_picker_default_title">Cambia colore</string>
<string name="toast_habit_created">Abitudine creata.</string>
<string name="toast_habit_deleted">Abitudine rimossa.</string>
<string name="toast_habit_restored">Abitudine ripristinata.</string>
<string name="toast_nothing_to_undo">Niente da annullare.</string>
<string name="toast_nothing_to_redo">Niente da ripetere.</string>
<string name="toast_habit_changed">Abitudine modificata.</string>
<string name="toast_habit_changed_back">Abitudine ripristinata.</string>
<string name="toast_habit_archived">Abitudine archiviata.</string>
<string name="toast_habit_unarchived">Abitudine ripristinata.</string>
<string name="toast_habit_created">Abitudine creata</string>
<string name="toast_habit_deleted">Abitudine rimossa</string>
<string name="toast_habit_restored">Abitudine ripristinata</string>
<string name="toast_nothing_to_undo">Niente da annullare</string>
<string name="toast_nothing_to_redo">Niente da ripetere</string>
<string name="toast_habit_changed">Abitudine modificata</string>
<string name="toast_habit_changed_back">Abitudine ripristinata</string>
<string name="toast_habit_archived">Abitudine archiviata</string>
<string name="toast_habit_unarchived">Abitudine ripristinata</string>
<string name="overview">Panoramica</string>
<string name="habit_strength">Forza dell\'abitudine</string>
<string name="history">Cronologia</string>
@ -65,7 +65,7 @@
<string name="intro_title_2">Aggiungi qualche nuova abitudine</string>
<string name="intro_description_2">Ogni giorno, dopo aver portato a termine la tua abitudine, spuntala nell\'app.</string>
<string name="intro_title_3">Continua così</string>
<string name="intro_description_3">Abitudini portate a termine con regolarità per un lungo periodo ti faranno guadagnare una stella intera.</string>
<string name="intro_description_3">Le abitudini portate a termine regoalrmente per un lungo periodo riceveranno una stella piena.</string>
<string name="intro_title_4">Segui i tuoi progressi</string>
<string name="intro_description_4">Grafici dettagliati ti mostrano come le tue abitudini sono migliorate nel corso del tempo.</string>
<string name="interval_15_minutes">15 minuti</string>
@ -76,7 +76,7 @@
<string name="interval_8_hour">8 ore</string>
<string name="interval_24_hour">24 ore</string>
<string name="pref_toggle_title">Spunta le ripetizioni velocemente</string>
<string name="pref_toggle_description">Più comodo, ma potrebbe causare delle spunte accidentali.</string>
<string name="pref_toggle_description">Metti le spunte con un tocco singolo invece che tenendo premuto. Più comodo, ma potrebbe causare delle spunte accidentali.</string>
<string name="pref_snooze_interval_title">Intervallo di ritardo dei promemoria</string>
<string name="pref_rate_this_app">Valuta quest\'app su Google Play</string>
<string name="pref_send_feedback">Manda un feedback allo sviluppatore</string>
@ -88,7 +88,7 @@
<string name="settings">Impostazioni</string>
<string name="snooze_interval">Snooze</string>
<string name="hint_title">Lo sapevi?</string>
<string name="hint_drag">Per riordinare la lista, premi e mantieni premuta l\'abitudine e spostala nella posizione desiderata.</string>
<string name="hint_drag">Per riordinare le voci, tieni premuto sul nome dell\'abitudine, poi spostala nella posizione corretta.</string>
<string name="hint_landscape">Puoi vedere più giorni mettendo il tuo telefono orizzontale.</string>
<string name="delete_habits">Elimina abitudine</string>
<string name="delete_habits_message">L\'abitudine verrà cancellata definitivamente. Non sarà possibile annullare.</string>
@ -158,8 +158,8 @@
<string name="reminder_sound">Suono notifica</string>
<string name="none">Nessuno</string>
<string name="filter">Filtra</string>
<string name="hide_completed">Nascosti</string>
<string name="hide_archived">Nascosti</string>
<string name="hide_completed">Nascondi completati</string>
<string name="hide_archived">Nascondi archiviati</string>
<string name="sticky_notifications">Notifiche non rimuovibili</string>
<string name="sticky_notifications_description">Impedisce di poter rimuovere le notifiche.</string>
<string name="repair_database">Ripara database</string>

@ -74,6 +74,7 @@
<string name="interval_2_hour">2 時間</string>
<string name="interval_4_hour">4 時間</string>
<string name="interval_8_hour">8 時間</string>
<string name="interval_24_hour">24時間</string>
<string name="pref_toggle_title">クリックで繰り返しを切り替え</string>
<string name="pref_toggle_description">便利になりますが、間違って切り替えが起こる可能性があります。</string>
<string name="pref_snooze_interval_title">リマインダーのスヌーズ間隔</string>
@ -146,6 +147,7 @@
<string name="month"></string>
<string name="quarter">四半期</string>
<string name="year"></string>
<string name="total">合計</string>
<!-- Middle part of the sentence '1 time in xx days' -->
<string name="time_every">回 /</string>
<string name="every_x_days">%d 日ごと</string>
@ -154,4 +156,7 @@
<string name="score">スコア</string>
<string name="reminder_sound">リマインダー サウンド</string>
<string name="none">なし</string>
<string name="filter">フィルター</string>
<string name="download">ダウンロード</string>
<string name="export">エクスポート</string>
</resources>

@ -48,7 +48,7 @@
<string name="reminder">알림</string>
<string name="discard">취소</string>
<string name="save">저장</string>
<string name="streaks">길게 이은 기록</string>
<string name="streaks">연속</string>
<string name="no_habits_found">활성화된 습관이 없습니다.</string>
<string name="long_press_to_toggle">체크하거나 해제하려면 길게 누르세요.</string>
<string name="reminder_off"></string>
@ -57,7 +57,7 @@
<string name="validation_at_most_one_rep_per_day">하루에 한 번만 반복 가능합니다.</string>
<string name="create_habit">습관 만들기</string>
<string name="edit_habit">습관 수정하기</string>
<string name="check">선택</string>
<string name="check">완료</string>
<string name="snooze">나중에</string>
<!-- App introduction -->
<string name="intro_title_1">환영합니다</string>
@ -109,7 +109,7 @@
<string name="frequency">빈도</string>
<string name="checkmark">체크</string>
<string name="strength">강도</string>
<string name="best_streaks">가장 길게 이은 기록</string>
<string name="best_streaks">최고 연속 기록</string>
<string name="current_streaks">현재 기록</string>
<string name="number_of_repetitions">반복한 횟수</string>
<string name="last_x_days">이전 %d일 동안</string>

@ -29,7 +29,7 @@
<string name="add_habit">Nieuwe gewoonte</string>
<string name="color_picker_default_title">Verander kleur</string>
<string name="toast_habit_created">Gewoonte aangemaakt.</string>
<string name="toast_habit_deleted">Gewoonte verwijderd.</string>
<string name="toast_habit_deleted">Gewoontes verwijderd</string>
<string name="toast_habit_restored">Gewoontes hersteld</string>
<string name="toast_nothing_to_undo">Niets om ongedaan te maken.</string>
<string name="toast_nothing_to_redo">Niets om over te doen.</string>

@ -39,14 +39,14 @@
<string name="toast_habit_unarchived">Vaner uarkivert</string>
<string name="overview">Oversikt</string>
<string name="habit_strength">Vanestyrke</string>
<string name="history">Historie</string>
<string name="history">Logg</string>
<string name="clear">Fjern</string>
<string name="description_hint">Spørsmål (Gjorde du &#8230; i dag?)</string>
<string name="repeat">Gjenta</string>
<string name="times_every">ganger på</string>
<string name="days">dager</string>
<string name="reminder">Påminnelse</string>
<string name="discard">Kast</string>
<string name="discard">Forkast</string>
<string name="save">Lagr</string>
<string name="streaks">Gjentakelser</string>
<string name="no_habits_found">Du har ingen aktive vaner</string>
@ -77,7 +77,7 @@
<string name="interval_24_hour">1 døgn</string>
<string name="pref_toggle_title">Veksl med enkelttrykk</string>
<string name="pref_toggle_description">Sett på haker med et enkelttrykk i stedet for å tykke og holde. Mer praktisk, men kan forårsake utilsiktede vekslinger.</string>
<string name="pref_snooze_interval_title">Snooze-intervall på påminnelser</string>
<string name="pref_snooze_interval_title">Slumreintervall på påminnelser</string>
<string name="pref_rate_this_app">Vurdér denne appen på Google Play</string>
<string name="pref_send_feedback">Send tilbakemelding til utviklerne</string>
<string name="pref_view_source_code">Vis kildekode på GitHub</string>
@ -86,7 +86,7 @@
<string name="behavior">Oppførsel</string>
<string name="name">Navn</string>
<string name="settings">Innstillinger</string>
<string name="snooze_interval">Snooze-intervall</string>
<string name="snooze_interval">Slumreintervall</string>
<string name="hint_title">Visste du at?</string>
<string name="hint_drag">For å sortere innleggene, trykk og hold på navnet til vanen, deretter dra den til det korrekte stedet.</string>
<string name="hint_landscape">Du kan se flere dager ved å sette telefonen din i landskapsmodus.</string>

@ -75,10 +75,10 @@
<string name="interval_4_hour">4 godziny</string>
<string name="interval_8_hour">8 godzin</string>
<string name="interval_24_hour">24 godziny</string>
<string name="pref_toggle_title">Przełącz powtarzanie przy krótkim naciśnięciu</string>
<string name="pref_toggle_description">Wygodniejsze ale może spowodować przypadkowe przełączenia.</string>
<string name="pref_toggle_title">Przełącz powtarzanie krótkim naciśnięciem</string>
<string name="pref_toggle_description">Wygodniejsze, ale może spowodować przypadkowe przełączenia.</string>
<string name="pref_snooze_interval_title">Czas drzemki między przypomnieniami</string>
<string name="pref_rate_this_app">Oceń tą aplikację w Google Play</string>
<string name="pref_rate_this_app">Oceń tę aplikację w Google Play</string>
<string name="pref_send_feedback">Prześlij uwagi do programisty</string>
<string name="pref_view_source_code">Zobacz kod źródłowy na GitHub\'ie</string>
<string name="pref_view_app_introduction">Zobacz wprowadzenie do aplikacji</string>

@ -47,7 +47,7 @@
<string name="reminder">Reamintire</string>
<string name="discard">Renunță</string>
<string name="save">Salvează</string>
<string name="streaks">Serii</string>
<string name="streaks">Zile consecutive</string>
<string name="no_habits_found">Nu ai niciun obicei activ.</string>
<string name="long_press_to_toggle">Apasă și ține pentru a bifa sau a debifa</string>
<string name="reminder_off">Dezactivat</string>
@ -90,6 +90,7 @@
<string name="hint_landscape">Poți vedea mai multe zile în modul peisaj.</string>
<string name="delete_habits">Șterge obiceiuri</string>
<string name="delete_habits_message">Obiceiurile vor fi șterse permanent. Această acțiune nu este reversibilă.</string>
<string name="habit_not_found">Obicei şters / negăsit</string>
<string name="weekends">Weekenduri</string>
<string name="any_weekday">Zile de lucru</string>
<string name="any_day">Orice zi</string>
@ -106,8 +107,8 @@
<string name="frequency">Frecvență</string>
<string name="checkmark">Bifă</string>
<string name="strength">Putere</string>
<string name="best_streaks">Cele mai bune serii</string>
<string name="current_streaks">Seria curentă</string>
<string name="best_streaks">Cele mai multe zile consecutive</string>
<string name="current_streaks">Numărul curent de reușite succesive</string>
<string name="number_of_repetitions">Număr de repetiții</string>
<string name="last_x_days">Ultimele %d zile</string>
<string name="last_x_weeks">Ultimele %d săptămâni</string>
@ -133,5 +134,24 @@
<string name="bug_report_failed">Generare raport de erori nereușită.</string>
<string name="generate_bug_report">Generează raport de erori</string>
<string name="troubleshooting">Depanare</string>
<string name="reverse_days">Inversează ordinea zilelor</string>
<string name="day">Zi</string>
<string name="week">Săptămână</string>
<string name="month">Lună</string>
<string name="quarter">Trimestru</string>
<string name="year">An</string>
<string name="total">Total</string>
<!-- Middle part of the sentence '1 time in xx days' -->
<string name="time_every">dată la</string>
<string name="every_x_days">La fiecare %d zile</string>
<string name="every_x_weeks">La fiecare %d săptămâni</string>
<string name="every_x_months">La fiecare %d luni</string>
<string name="hide_completed">Ascunde cele completate</string>
<string name="hide_archived">Ascunde cele arhivate</string>
<string name="uncheck">Debifează</string>
<string name="habit">Obicei</string>
<string name="sort">Sortează</string>
<string name="manually">Manual</string>
<string name="by_name">După nume</string>
<string name="by_color">După culoare</string>
</resources>

@ -154,7 +154,7 @@
<string name="every_x_days">Каждые %d дней</string>
<string name="every_x_weeks">Каждые %d недель</string>
<string name="every_x_months">Каждые %d месяцев</string>
<string name="score">Стабильность</string>
<string name="score">Счет</string>
<string name="reminder_sound">Звук напоминания</string>
<string name="none">Без звука</string>
<string name="filter">Фильтр</string>
@ -172,7 +172,7 @@
<string name="manually">Вручную</string>
<string name="by_name">По названию</string>
<string name="by_color">По цвету</string>
<string name="by_score">По стабильности</string>
<string name="by_score">По оценке</string>
<string name="download">Загрузить</string>
<string name="export">Экспортировать</string>
</resources>

@ -19,18 +19,18 @@
~ with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<string name="app_name">Loop Sledilnik Navad</string>
<string name="app_name">Loop Navade</string>
<string name="main_activity_title">Navade</string>
<string name="action_settings">Nastavitve</string>
<string name="edit">Spremeni</string>
<string name="edit">Uredi</string>
<string name="delete">Izbriši</string>
<string name="archive">Arhiviraj</string>
<string name="unarchive">Odarhiviraj</string>
<string name="add_habit">Dodaj navado</string>
<string name="color_picker_default_title">Spremeni barvo</string>
<string name="toast_habit_created">Navada ustvarjana.</string>
<string name="toast_habit_deleted">Navada izbrisana.</string>
<string name="toast_habit_restored">Navada obnovljena.</string>
<string name="toast_habit_created">Navada ustvarjena</string>
<string name="toast_habit_deleted">Navada izbrisana</string>
<string name="toast_habit_restored">Navada obnovljena</string>
<string name="toast_nothing_to_undo">Nič za razveljaviti.</string>
<string name="toast_nothing_to_redo">Nič za ponovno opraviti.</string>
<string name="toast_habit_changed">Navada spremenjena.</string>

@ -107,7 +107,7 @@
<string name="developers">Utvecklare</string>
<string name="version_n">Version %s</string>
<string name="frequency">Frekvens</string>
<string name="checkmark">Avklarat/ej avklarat</string>
<string name="checkmark">Kryssruta</string>
<string name="strength">Styrka</string>
<string name="best_streaks">Bästa streak</string>
<string name="current_streaks">Nuvarande streak</string>

@ -0,0 +1,178 @@
<?xml version="1.0" encoding="utf-8"?>
<!--Generated by crowdin.com-->
<!--
~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
~
~ This file is part of Loop Habit Tracker.
~
~ Loop Habit Tracker is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by the
~ Free Software Foundation, either version 3 of the License, or (at your
~ option) any later version.
~
~ Loop Habit Tracker is distributed in the hope that it will be useful, but
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
~ more details.
~
~ You should have received a copy of the GNU General Public License along
~ with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<string name="app_name">பழக்க தடப்பாதை</string>
<string name="main_activity_title">பழக்கங்கள்</string>
<string name="action_settings">அமைப்புகள்</string>
<string name="edit">திருத்துக</string>
<string name="delete">நீக்கு</string>
<string name="archive">காப்பகம்</string>
<string name="unarchive">உயிர்க்க</string>
<string name="add_habit">சேர்க்க</string>
<string name="color_picker_default_title">நிறம் மாற்ற</string>
<string name="toast_habit_created">பழக்கம் உருவாக்கப்பட்டது</string>
<string name="toast_habit_deleted">பழக்கம் நீக்கப்பட்டது</string>
<string name="toast_habit_restored">பழக்கம் மீட்கப்பட்டது</string>
<string name="toast_nothing_to_undo">மீட்க ஒன்றும் இல்லை</string>
<string name="toast_nothing_to_redo">திருத்த எதுவும் இல்லை</string>
<string name="toast_habit_changed">பழக்கம் மாற்றப்பட்டது</string>
<string name="toast_habit_changed_back">பழக்கம் திரும்ப பழைய நிலைக்கு மாற்றப்பட்டது</string>
<string name="toast_habit_archived">காப்பகப்படுத்தியப் பழக்கம்</string>
<string name="toast_habit_unarchived">பழக்கங்கள் ஆவண காப்பகத்தில் இருந்து நீக்கப் பட்டது</string>
<string name="overview">மேற்பார்வை</string>
<string name="habit_strength">பழக்கத்தின் வலிமை</string>
<string name="history">வரலாறு</string>
<string name="clear">அழி</string>
<string name="description_hint">கேள்வி (இன்று ... செய்தீர்களா?)</string>
<string name="repeat">மீண்டும் செய்க</string>
<string name="times_every">காலங்களில்</string>
<string name="days">நாட்கள்</string>
<string name="reminder">நினைவூட்டல்கள்</string>
<string name="discard">நிராகரி</string>
<string name="save">சேமிக்கவும்</string>
<string name="streaks">சாதனைகள்</string>
<string name="no_habits_found">நடப்பு பழக்கம் எதுவும் இல்லை</string>
<string name="long_press_to_toggle">குறிக்க அல்லது குறிப்பை நீக்க அழுத்தி பிடிக்கவும்</string>
<string name="reminder_off">வேண்டாம்</string>
<string name="validation_name_should_not_be_blank">பெயர் காலியாக இருக்க கூடாது.</string>
<string name="validation_number_should_be_positive">நேர்மறை எண்ணாக இருக்க வேண்டும் (பூஜியத்தை விட அதிகம்).</string>
<string name="validation_at_most_one_rep_per_day">ஒரு நாளைக்கு அதிகப்பட்சம் ஒரு முறை மீள் நிகழ்வை பெற முடியும்</string>
<string name="create_habit">புதிய பழக்கம்</string>
<string name="edit_habit">பழக்கத்தை திருத்த</string>
<string name="check">சரிப்பார்ப்பு குறி</string>
<string name="snooze">பிறகு</string>
<!-- App introduction -->
<string name="intro_title_1">வருக</string>
<string name="intro_description_1">இந்த செயலி நல்ல பழக்க வழக்கங்களை துவங்க மற்றும் தொடர உதவுகிறது.</string>
<string name="intro_title_2">சில புது பழக்கங்களை துவங்கவும்!</string>
<string name="intro_description_2">தினமும் உங்கள் புதிய பழக்கத்தை முடித்தவுடன் இந்த செயலியில் அதை குறிக்கவும்.</string>
<string name="intro_title_3">மனம் தளராமல் தொடரவும்</string>
<string name="intro_description_3">தொடர்ச்சியாக செய்யும் பழக்கங்கள் ஒரு முழு நட்சத்திரத்தை பெற்று தரும்.</string>
<string name="intro_title_4">உங்கள் முன்னேற்றத்தை கண்காணிக்கவும்</string>
<string name="intro_description_4">நாளடைவில் நீங்கள் அடைந்த முன்னேற்றத்தை வரைபடத்தின் மூலம் அறியலாம்.</string>
<string name="interval_15_minutes">15 நிமிடங்கள்</string>
<string name="interval_30_minutes">30 நிமிடங்கள்</string>
<string name="interval_1_hour">1 மணி நேரம்</string>
<string name="interval_2_hour">2 மணி நேரம்</string>
<string name="interval_4_hour">4 மணி நேரம்</string>
<string name="interval_8_hour">8 மணி நேரம்</string>
<string name="interval_24_hour">24 மணி நேரம்</string>
<string name="pref_toggle_title">சிறிய அழுத்தலின் மூலம் தாவு</string>
<string name="pref_toggle_description">சரிப் பார்ப்பு குறி யை இட அழுத்தி பிடிப்பதற்கு பதில் ஒரு முறை தட்டலாம். இது முன்னதை விட எளிமையானது. ஆனால் இது தற்செயலான தாவல்களுக்கு வழி வகுக்கும்.</string>
<string name="pref_snooze_interval_title">எச்சரிகையை தள்ளி வைக்க வேண்டிய நேரம்</string>
<string name="pref_rate_this_app">Google Play-ல் இந்த செயலியை மதிப்பிட</string>
<string name="pref_send_feedback">இந்த செயலியை மேம்படுத்த உங்கள் கருத்துகளை பகிர</string>
<string name="pref_view_source_code">இந்த செயலியின் மூல நிரலை GitHub வலைதளத்தில் பார்க்கவும்</string>
<string name="pref_view_app_introduction">இந்த செயலியின் முன்னோட்டத்தை பார்க்க</string>
<string name="links">இணைப்புகள்</string>
<string name="behavior">செயல்பாடு</string>
<string name="name">பெயர்</string>
<string name="settings">அமைப்புகள்</string>
<string name="snooze_interval">தாமத காலம்</string>
<string name="hint_title">உங்களுக்கு தெரியுமா?</string>
<string name="hint_drag">பதிவுகளை மறுசீரைமக்க, தேவையான பழக்க பதிவின் மீது அழுத்தி பிடித்து பின் தேவையான இடத்திற்கு அதை இழுக்கவும்.</string>
<string name="hint_landscape">உங்கள் கைப்பேசியை அகலவாக்கில் வைக்கும்போது இன்னும் அதிக நாட்களை காண முடியும்.</string>
<string name="delete_habits">பழக்கங்களை நீக்கவும்</string>
<string name="delete_habits_message">பழக்கங்கள் நிரந்தரமாக நீக்கப்படும். இந்த செயலை மீட்டமைக்க இயலாது.</string>
<string name="habit_not_found">பழக்கம் நீக்கப்பட்டுவிட்டது / காணவில்லை</string>
<string name="weekends">வார இறுதிகள்</string>
<string name="any_weekday">திங்கள் முதல் வெள்ளி வரை</string>
<string name="any_day">வாரத்தின் எந்த நாளிலும்</string>
<string name="select_weekdays">நாட்களை தேர்வு செய்யவும்</string>
<string name="export_to_csv">CSV நிரல் வகையில் ஏற்றுமதி செய்யவும்</string>
<string name="done_label">முடிந்தது</string>
<string name="clear_label">அழி</string>
<string name="select_hours">மணி நேரங்களை தேர்வு செய்யவும்</string>
<string name="select_minutes">நிமிடங்களை தேர்வு செய்யவும்</string>
<string name="about">இதை பற்றி</string>
<string name="translators">மொழிப்பெயர்ப்பாளர்கள்</string>
<string name="developers">மென்பொருள் ஆசிரியர்கள்</string>
<string name="version_n">மென்பொருள் பதிப்பு %s</string>
<string name="frequency">கால இடைவெளி</string>
<string name="checkmark">சரிபார்ப்பு குறி</string>
<string name="strength">வலிமை</string>
<string name="best_streaks">சிறந்த சாதனைகள்</string>
<string name="current_streaks">நடப்பு சாதனை</string>
<string name="number_of_repetitions">மீள் நிகழ்வுகளின் எண்ணிக்கை</string>
<string name="last_x_days">கடந்த %d நாட்கள்</string>
<string name="last_x_weeks">கடந்த %d வாரங்கள்</string>
<string name="last_x_months">கடந்த %d மாதங்கள்</string>
<string name="last_x_years">கடந்த %d வருடங்கள்</string>
<string name="all_time">எல்லா நேரமும்</string>
<string name="every_day">எல்லா நாளும்</string>
<string name="every_week">எல்லா வாரமும்</string>
<string name="two_times_per_week">வாரத்திற்கு இரண்டு முறை</string>
<string name="five_times_per_week">வாரத்துக்கு 5 முறை</string>
<string name="custom_frequency">விருப்பத்திற்கு ஏற்றபடி&#8230;</string>
<string name="help">உதவி &amp; அதிகம் கேட்கப்படும் கேள்விகள்</string>
<string name="could_not_export">தரவுகளை ஏற்றுமதி செய்ய முடியவில்லை.</string>
<string name="could_not_import">தரவை இறக்குமதி செய்ய முடியவில்லை.</string>
<string name="file_not_recognized">இது எந்த வகையான ஆவணம் என்பதை உறுதி செய்ய முடியவில்லை.</string>
<string name="habits_imported">பழக்கங்களை வெற்றிகரமாக இறக்குமதி செய்யப்பட்டது.</string>
<string name="full_backup_success">முழு ஆவணக் காப்பு நகல் வெற்றிகரமாக ஏற்றுமதி செய்யப்பட்டது.</string>
<string name="import_data">தரவு இறக்குமதி</string>
<string name="export_full_backup">ஆவணக் காப்பு நகல் முழுமையாக ஏற்றுமதி செய்</string>
<string name="import_data_summary">இந்த செயலி மூலம் ஆவண காப்பு நகல் முழுவதுமாக ஏற்றுமதி செய்யவும் மற்றும் Tickmate, HabitBull அல்லது Rewire செயலிகள் மூலம் உருவாக்கப்படும் ஆவணங்களும் இந்த செயலியில் பயண்படுத்தலாம் மேலும் தகவல்களுக்கு அதிகம் கேட்கப்படும் கேள்விகளை (FAQ) பார்க்கவும்.</string>
<string name="export_as_csv_summary">உருவாக்கப்பட்ட ஆவணங்களை விரித்தாள் மென்பொருள்களான Microsoft Excel அல்லது OpenOffice Calc மூலம் திறக்கலாம். ஆனால் இவற்றை திரும்ப இறக்குமதி செய்ய முடியாது.</string>
<string name="export_full_backup_summary">உங்களின் அனைத்து தரவுகளையும் கொண்ட ஒரு ஆவணம் உருவாக்கப்படும். இந்த ஆவணத்தை மீண்டும் இந்த செயலியில் இறக்குமதி செய்யலாம்.</string>
<string name="bug_report_failed">செயலி பிழை அறிக்கை உருவாக்க முடியவில்லை.</string>
<string name="generate_bug_report">பிழை அறிக்கை உருவாக்கு</string>
<string name="troubleshooting">பழுது இடமறிதல்</string>
<string name="help_translate">இந்த செயலியை மற்ற மொழிகளில் மொழிபெயர்க்க உதவி செய்யவும்</string>
<string name="night_mode">இருள் வண்ண பாங்கு</string>
<string name="use_pure_black">இருள் பாங்கில் முழு கருப்பு நிறத்தை பயண்படுத்து</string>
<string name="pure_black_description">இதன் மூலம் செயலியில் உள்ள பழுப்பு பின்புலங்கள் நீக்கப்பட்டு முழுவதும் கருப்பு நிற பின்புலங்களாக மாற்றப்படும். இது AMOLED திரை கொண்ட கைப்பேசிகளில் மின்கல பயன்பாட்டை குறைக்கும்.</string>
<string name="interface_preferences">இடைமுகம்</string>
<string name="reverse_days">தலைகீழ் வரிசையில் நாட்கள்</string>
<string name="reverse_days_description">பிரதான திரையில் நாட்களை தலை கீழ் வரிசையில் காட்டு</string>
<string name="day">நாள்</string>
<string name="week">வாரம்</string>
<string name="month">மாதம்</string>
<string name="quarter">காற் பங்கு</string>
<string name="year">வருடம்</string>
<string name="total">மொத்தம்</string>
<!-- Middle part of the sentence '1 time in xx days' -->
<string name="time_every">நேரத்தை</string>
<string name="every_x_days">ஓவ்வொரு %d நாளும்</string>
<string name="every_x_weeks">ஒவ்வொரு %d வாரங்களும்</string>
<string name="every_x_months">ஒவ்வொரு %d மாதங்களு</string>
<string name="score">மதிப்பெண்கள்</string>
<string name="reminder_sound">நினைவூட்டல் சத்தம்</string>
<string name="none">எதுவும் இல்லை</string>
<string name="filter">வடிகட்டவும்</string>
<string name="hide_completed">மறைத்தல் முடிந்தது</string>
<string name="hide_archived">ஆவணக் காப்பை மறைக்கவும்</string>
<string name="sticky_notifications">நினைவூட்டல்களை நிலைத்து நிற்க வை</string>
<string name="sticky_notifications_description">நினைவூட்டல்களை விரல்களால் தள்ளி விட முடியாத படி செய்கிறது.</string>
<string name="repair_database">தரவு தளத்தை பழுது பார்க்கவும்</string>
<string name="database_repaired">தரவுதளம் பழுதடைந்து விட்டது.</string>
<string name="uncheck">சரிப்பார்க்காமல் அப்படியே விடு</string>
<string name="toggle">தாவு</string>
<string name="action">செயல்</string>
<string name="habit">பழக்கம்</string>
<string name="sort">வரிசைப்படுத்தவும்</string>
<string name="manually">கைமுறை</string>
<string name="by_name">பெயரின் மூலம்</string>
<string name="by_color">நிறத்தின் மூலம்</string>
<string name="by_score">மதிப்பெண்களின் மூலம்</string>
<string name="download">பதிவிறக்கம்</string>
<string name="export">ஏற்றுமதி</string>
</resources>

@ -19,7 +19,7 @@
~ with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<string name="app_name">Döngü Alışkanlık Takibi</string>
<string name="app_name">Loop Alışkanlık Takip</string>
<string name="main_activity_title">Alışkanlıklar</string>
<string name="action_settings">Ayarlar</string>
<string name="edit">Düzenle</string>
@ -32,25 +32,25 @@
<string name="toast_habit_deleted">Alışkanlık silindi.</string>
<string name="toast_habit_restored">Alışkanlıklar geri getirildi</string>
<string name="toast_nothing_to_undo">Geri alınacak bir şey yok</string>
<string name="toast_nothing_to_redo">Tekrar edilecek bir şey yok</string>
<string name="toast_habit_changed">Alışkanlık değişti</string>
<string name="toast_nothing_to_redo">Tekrar yapılacak birşey yok.</string>
<string name="toast_habit_changed">Alışkanlık değiştirildi</string>
<string name="toast_habit_changed_back">Alışkanlık eski haline getirildi</string>
<string name="toast_habit_archived">Alışkanlık arşivlendi.</string>
<string name="toast_habit_unarchived">Alışkanlık arşivden çıkarıldı.</string>
<string name="overview">Genel bakış</string>
<string name="habit_strength">Alışkanlık dayanımı</string>
<string name="overview">Genel Bakış</string>
<string name="habit_strength">Alışkanlık gücü</string>
<string name="history">Geçmiş</string>
<string name="clear">Temizle</string>
<string name="description_hint">Soru (Bugün ... yaptın mı?)</string>
<string name="repeat">Tekrar</string>
<string name="times_every">kez</string>
<string name="days">günde</string>
<string name="times_every">defa /</string>
<string name="days">gün</string>
<string name="reminder">Hatırlatma</string>
<string name="discard">İptal</string>
<string name="discard">Vazgeç</string>
<string name="save">Kaydet</string>
<string name="streaks">Etkinlikler</string>
<string name="streaks">Seriler</string>
<string name="no_habits_found">Etkin alışkanlığın yok</string>
<string name="long_press_to_toggle">Yapıldı veya yapılmadı işareti koymak için uzun basılı tut</string>
<string name="long_press_to_toggle">İşaretlemek ya da işaret kaldırmak için basılı tut</string>
<string name="reminder_off">Kapalı</string>
<string name="validation_name_should_not_be_blank">Adı boş bırakamazsın.</string>
<string name="validation_number_should_be_positive">Sayılar sıfırdan büyük olmalı.</string>
@ -61,13 +61,13 @@
<string name="snooze">Sonra</string>
<!-- App introduction -->
<string name="intro_title_1">Hoşgeldin</string>
<string name="intro_description_1">İyi alışkanlıklar edinmek ve devam etmene yardımcı olur.</string>
<string name="intro_description_1">Loop Alışkanlık Takibi, iyi alışkanlıklar edinmene ve sürdürmene yardımcı olur.</string>
<string name="intro_title_2">Yeni alışkanlıklar oluştur</string>
<string name="intro_description_2">Her gün, alışkanlığını gerçekleştirdikten sonra, uygulamada onay işareti koy.</string>
<string name="intro_title_3">Yapmaya devam et</string>
<string name="intro_description_3">Uzun bir süre ile sürekli yaptığın alışkanlıkların için tam yıldız kazanacaksın.</string>
<string name="intro_description_3">Uzun süre düzenli sürdürdüğün alışkanlıkların için bir tam yıldız kazanacaksın.</string>
<string name="intro_title_4">Gelişimini izle</string>
<string name="intro_description_4">Detaylı grafiklerle, zaman içinde alışkanlıklarını nasıl geliştiğini gör.</string>
<string name="intro_description_4">Detaylı grafiklerle, zaman içinde alışkanlıklarının nasıl geliştiğini gör.</string>
<string name="interval_15_minutes">15 dakika</string>
<string name="interval_30_minutes">30 dakika</string>
<string name="interval_1_hour">1 saat</string>
@ -75,26 +75,26 @@
<string name="interval_4_hour">4 saat</string>
<string name="interval_8_hour">8 saat</string>
<string name="interval_24_hour">24 saat</string>
<string name="pref_toggle_title">Daha kısa süre basma ile yapıldı/yapılmadı işaretleme</string>
<string name="pref_toggle_description">Daha kullanışlı ama kazara istenmeyen işaretlemeler olabilir</string>
<string name="pref_toggle_title">Kısa dokunuşla işaretle</string>
<string name="pref_toggle_description">Alışkanlıklarını basılı tutmak yerine tek dokunuşla işaretlemeni sağlar. Kullanımı daha rahattır ama kaza eseri işaretleme yapabilirsin.</string>
<string name="pref_snooze_interval_title">Hatırlatmalardaki erteleme süresi</string>
<string name="pref_rate_this_app">Google Play\'de uygulamayı oyla</string>
<string name="pref_send_feedback">Geliştiriciye geri bildirim gönder</string>
<string name="pref_view_source_code">Github\'da kaynak kodunu bak</string>
<string name="pref_view_source_code">Github\'da kaynak kodunu gör</string>
<string name="pref_view_app_introduction">Uygulama tanıtımını göster</string>
<string name="links">Bağlantılar</string>
<string name="behavior">Davranış</string>
<string name="name">Ad</string>
<string name="settings">Ayarlar</string>
<string name="snooze_interval">Erteleme süresi</string>
<string name="hint_title">Biliyor musun?</string>
<string name="hint_drag">Girdileri yeniden düzenlemek için, alışkanlık adının üstüne bas ve doğru yere sürükle.</string>
<string name="hint_title">Biliyor muydun?</string>
<string name="hint_drag">Girdileri sıralamak için, alışkanlık adının üstüne basılı tut ve doğru yere sürükle.</string>
<string name="hint_landscape">Cihazını yatay tutarak daha fazla gün görebilirsin.</string>
<string name="delete_habits">Alışkanlıkları Sil</string>
<string name="delete_habits_message">Alışkanlıklar kalıcı olarak silinecek. Bu eylem geri alınamaz.</string>
<string name="habit_not_found">Alışkanlık silinmiş ya da bulunamadı</string>
<string name="weekends">Hafta sonları</string>
<string name="any_weekday">Pazartesinden Cumaya</string>
<string name="any_weekday">Pazartesi-Cuma</string>
<string name="any_day">Haftanın herhangi bir günü</string>
<string name="select_weekdays">Günleri seç</string>
<string name="export_to_csv">CSV olarak dışa aktar</string>
@ -107,10 +107,10 @@
<string name="developers">Geliştiriciler</string>
<string name="version_n">Sürüm %s</string>
<string name="frequency">Sıklık</string>
<string name="checkmark">Yapıldı işareti</string>
<string name="strength">Dayanım</string>
<string name="best_streaks">En iyi etkinlik günü</string>
<string name="current_streaks">Bugünkü etkinlik</string>
<string name="checkmark">İşaret</string>
<string name="strength">Güç</string>
<string name="best_streaks">En uzun seriler</string>
<string name="current_streaks">Şimdiki seri</string>
<string name="number_of_repetitions">Tekrar sayısı</string>
<string name="last_x_days">Son %d gün</string>
<string name="last_x_weeks">Son %d hafta</string>
@ -126,23 +126,23 @@
<string name="could_not_export">Dışarı veri aktarımı başarısız.</string>
<string name="could_not_import">İçeri veri aktarımı başarısız.</string>
<string name="file_not_recognized">Dosya tanınamadı.</string>
<string name="habits_imported">Alışkanlıklar başarılı içeri aktarıldı.</string>
<string name="habits_imported">Alışkanlıklar başarıyla içeri aktarıldı.</string>
<string name="full_backup_success">Tam yedek başarıyla dışarı aktarıldı.</string>
<string name="import_data">Veri içeri aktar</string>
<string name="import_data">İçeri veri aktar</string>
<string name="export_full_backup">Tüm yedeği dışarı aktar</string>
<string name="import_data_summary">Hem bu uygulama tarafından dışarı aktarılmış tam yedekleri, hem de Tickmate, HabitBull veya Rewire tarafından üretilmiş dosyaları destekler. Daha fazla bilgi için SSS bakın.</string>
<string name="import_data_summary">Hem bu uygulama tarafından dışarı aktarılmış tam yedekleri, hem de Tickmate, HabitBull veya Rewire tarafından üretilmiş dosyaları destekler. Daha fazla bilgi için SSS\'a başvurun.</string>
<string name="export_as_csv_summary">Üretilen dosyalar, Microsoft Excel veya OpenOffice Calc. gibi hesap taplosu uygulamaları ile açılabilir. Bu dosya yeniden içeri aktarılamaz.</string>
<string name="export_full_backup_summary">Üretilen dosya, tüm verilerini içerir. Bu dosya yeniden içeri aktarılabilir.</string>
<string name="export_full_backup_summary">Tüm verilerini içeren bir dosya üretir. Bu dosya yeniden içeri aktarılabilir.</string>
<string name="bug_report_failed">Hata raporu oluşturulamadı.</string>
<string name="generate_bug_report">Hata raporu üret</string>
<string name="troubleshooting">Sorun Giderme</string>
<string name="help_translate">Bu uygulamanın çevirisine yardım et</string>
<string name="night_mode">Gece kipi</string>
<string name="use_pure_black">Gece kipinde saf siyah kullan</string>
<string name="pure_black_description">Gece kipinde gri arkaplanını, saf siyah ile değiştir. AMOLED ekranlı cihazlarda pil kullanımını düşür.</string>
<string name="pure_black_description">Gece kipinde gri arkaplanını, saf siyah ile değiştir. AMOLED ekranlı cihazlarda pil kullanımını azaltabilir.</string>
<string name="interface_preferences">Arayüz</string>
<string name="reverse_days">Günleri ters sırala</string>
<string name="reverse_days_description">Ana ekranda günleri tersen göster</string>
<string name="reverse_days_description">Ana ekranda günleri tersten göster</string>
<string name="day">Gün</string>
<string name="week">Hafta</string>
<string name="month">Ay</string>
@ -150,7 +150,7 @@
<string name="year">Yıl</string>
<string name="total">Tümü</string>
<!-- Middle part of the sentence '1 time in xx days' -->
<string name="time_every">kez</string>
<string name="time_every">defa /</string>
<string name="every_x_days">Her %d gün</string>
<string name="every_x_weeks">Her %d hafta</string>
<string name="every_x_months">Her %d ay</string>
@ -161,18 +161,18 @@
<string name="hide_completed">Tamamlananları gizle</string>
<string name="hide_archived">Arşivlenenleri gizle</string>
<string name="sticky_notifications">Bildirimleri kalıcı yap</string>
<string name="sticky_notifications_description">Bildirimin kaydırılarak götürülmesini engelle.</string>
<string name="repair_database">Verıtabanını onar</string>
<string name="database_repaired">Verıtabanı onarıldı.</string>
<string name="uncheck">Yapmadım</string>
<string name="sticky_notifications_description">Bildirimlerin kaydırılarak temizlenmesini engelle.</string>
<string name="repair_database">Veritabanını onar</string>
<string name="database_repaired">Veritabanı onarıldı.</string>
<string name="uncheck">İşareti kaldır</string>
<string name="toggle">Değiştir</string>
<string name="action">Eylem</string>
<string name="habit">Alışkanlık</string>
<string name="sort">Sırala</string>
<string name="manually">Elle</string>
<string name="by_name">Ad</string>
<string name="by_color">Renk</string>
<string name="by_score">Puan</string>
<string name="by_name">Ada göre</string>
<string name="by_color">Renge göre</string>
<string name="by_score">Puana göre</string>
<string name="download">İndir</string>
<string name="export">Dışarı aktar</string>
</resources>

@ -63,11 +63,11 @@
<string name="intro_title_1">Ласкаво просимо</string>
<string name="intro_description_1">Трекер звичок Loop допомагає вам розвивати і підтримувати корисні звички.</string>
<string name="intro_title_2">Додайте нові звички</string>
<string name="intro_description_2">Кожного дня, після виконання вашої звички, поставте пташку в програмі.</string>
<string name="intro_description_2">Щодня, після виконання вашої звички, ставте пташку в програмі.</string>
<string name="intro_title_3">Продовжуйте в тому ж дусі</string>
<string name="intro_description_3">Постійно дотримувані звички буде відзначено повною зірочкою.</string>
<string name="intro_title_4">Відстежуйте свої успіхи</string>
<string name="intro_description_4">Деталізовані хвилеписи (діяграми) демонструють, як ваші звички покращилися з часом.</string>
<string name="intro_description_4">Деталізовані хвилеписи демонструють, як ваші звички покращилися з часом.</string>
<string name="interval_15_minutes">15 хвилин</string>
<string name="interval_30_minutes">30 хвилин</string>
<string name="interval_1_hour">1 година</string>

@ -19,6 +19,160 @@
~ with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<string name="app_name">Trình theo dõi thói quen Loop</string>
<string name="main_activity_title">Thói quen</string>
<string name="action_settings">Cài đặt</string>
<string name="edit">Chỉnh sửa</string>
<string name="delete">Xoá</string>
<string name="archive">Lưu trữ</string>
<string name="unarchive">Hủy lưu trữ</string>
<string name="add_habit">Thêm thói quen</string>
<string name="color_picker_default_title">Thay đổi màu sắc</string>
<string name="toast_habit_created">Thói quen đã được tạo</string>
<string name="toast_habit_deleted">Thói quen đã bị xóa</string>
<string name="toast_habit_restored">Thói quen đã được phục hồi</string>
<string name="toast_nothing_to_undo">Không có gì để hoàn tác</string>
<string name="toast_nothing_to_redo">Không có gì để khôi phục</string>
<string name="toast_habit_changed">Thói quen đã được thay đổi</string>
<string name="toast_habit_changed_back">Thói quen đã huỷ chỉnh sửa</string>
<string name="toast_habit_archived">Thói quen đã được lưu trữ</string>
<string name="toast_habit_unarchived">Thói quen đã bị huỷ lưu trữ</string>
<string name="overview">Tổng quan</string>
<string name="habit_strength">Độ mạnh của thói quen</string>
<string name="history">Lịch sử</string>
<string name="clear">Dọn sạch</string>
<string name="description_hint">Câu hỏi (Bạn đã ... hôm nay?)</string>
<string name="repeat">Lặp lại</string>
<string name="times_every">times in</string>
<string name="days">ngày</string>
<string name="reminder">Nhắc nhở</string>
<string name="discard">Loại bỏ</string>
<string name="save">Lưu</string>
<string name="streaks">Mức độ</string>
<string name="no_habits_found">Bạn có không có thói quen nào đang hoạt động</string>
<string name="long_press_to_toggle">Nhấn giữ để đánh dấu hoặc bỏ đánh dấu</string>
<string name="reminder_off">Tắt</string>
<string name="validation_name_should_not_be_blank">Tên không thể để trống.</string>
<string name="validation_number_should_be_positive">Số phải là số dương.</string>
<string name="validation_at_most_one_rep_per_day">Bạn có thể có tối đa một lặp lại mỗi ngày</string>
<string name="create_habit">Tạo thói quen</string>
<string name="edit_habit">Chỉnh sửa thói quen</string>
<string name="check">Kiểm tra</string>
<string name="snooze">Lúc khác</string>
<!-- App introduction -->
<string name="intro_title_1">Chào mừng</string>
<string name="intro_description_1">Theo dõi thói quen Loop giúp bạn tạo ra và duy trì những thói quen tốt.</string>
<string name="intro_title_2">Tạo một số thói quen mới</string>
<string name="intro_description_2">Mỗi ngày, sau khi thực hiện các thói quen của bạn, hãy đánh dấu vào ứng dụng.</string>
<string name="intro_title_3">Hãy duy trì thói quen</string>
<string name="intro_description_3">Thói quen thực hiện một cách nhất quán trong một thời gian dài sẽ kiếm được trọn vẹn một ngôi sao.</string>
<string name="intro_title_4">Theo dõi quá trình của bạn</string>
<string name="intro_description_4">Đồ thị chi tiết cho bạn thấy các thói quen của bạn được cải thiện như thế nào theo thời gian.</string>
<string name="interval_15_minutes">15 phút</string>
<string name="interval_30_minutes">30 phút</string>
<string name="interval_1_hour">1 giờ</string>
<string name="interval_2_hour">2 giờ</string>
<string name="interval_4_hour">4 giờ</string>
<string name="interval_8_hour">8 giờ</string>
<string name="interval_24_hour">24 giờ</string>
<string name="pref_toggle_title">Bấm nhanh để chuyển trạng thái</string>
<string name="pref_toggle_description">Chỉ cần chạm một lần để đánh dấu thay cho việc nhấn giữ. Tiện lợi hơn nhưng có thể đánh dấu sai.</string>
<string name="pref_snooze_interval_title">Khoảng thời gian báo lại lời nhắc</string>
<string name="pref_rate_this_app">Đánh giá ứng dụng trên Google Play</string>
<string name="pref_send_feedback">Gửi phản hồi cho nhà phát triển</string>
<string name="pref_view_source_code">Xem mã nguồn trên Github</string>
<string name="pref_view_app_introduction">Xem giới thiệu ứng dụng</string>
<string name="links">Liên kết</string>
<string name="behavior">Hành vi</string>
<string name="name">Tên</string>
<string name="settings">Cài đặt</string>
<string name="snooze_interval">Khoảng thời gian tạm dừng</string>
<string name="hint_title">Bạn đã biết?</string>
<string name="hint_drag">Để sắp xếp lại các mục, nhấn giữ tên thói quen, sau đó kéo tới vị trí chính xác.</string>
<string name="hint_landscape">Bạn có thể xem thêm ngày bằng cách đặt điện thoại ở chế độ ngang.</string>
<string name="delete_habits">Xoá bỏ thói quen</string>
<string name="delete_habits_message">Thói quen sẽ bị xoá vĩnh viễn. Hành động này không thể khôi phục.</string>
<string name="habit_not_found">Thói quen đã bị xoá hoặc không tìm thấy</string>
<string name="weekends">Cuối tuần</string>
<string name="any_weekday">Thứ 2 đến thứ 6</string>
<string name="any_day">Bất kỳ ngày nào trong tuần</string>
<string name="select_weekdays">Chọn ngày</string>
<string name="export_to_csv">Xuất dưới dạng CSV</string>
<string name="done_label">Xong</string>
<string name="clear_label">Dọn sạch</string>
<string name="select_hours">Chọn giờ</string>
<string name="select_minutes">Chọn phút</string>
<string name="about">Giới thiệu</string>
<string name="translators">Dịch giả</string>
<string name="developers">Nhà phát triển</string>
<string name="version_n">Phiên bản %s</string>
<string name="frequency">Tần suất</string>
<string name="checkmark">Đánh dấu</string>
<string name="strength">Độ mạnh</string>
<string name="best_streaks">Duy trì lâu nhất</string>
<string name="current_streaks">Số ngày duy trì hiện tại</string>
<string name="number_of_repetitions">Số lần lặp</string>
<string name="last_x_days">Đã thực hiện được %d ngày</string>
<string name="last_x_weeks">Đã thực hiện được %d tuần</string>
<string name="last_x_months">Đã thực hiện được %d tháng</string>
<string name="last_x_years">Đã thực hiện được %d năm</string>
<string name="all_time">Toàn bộ thời gian</string>
<string name="every_day">Hàng ngày</string>
<string name="every_week">Hàng tuần</string>
<string name="two_times_per_week">2 lần một tuần</string>
<string name="five_times_per_week">5 lần một tuần</string>
<string name="custom_frequency">Tuỳ chỉnh&#8230;</string>
<string name="help">Trợ giúp &amp; Câu hỏi</string>
<string name="could_not_export">Xuất dữ liệu thất bại.</string>
<string name="could_not_import">Nhập dữ liệu thất bại.</string>
<string name="file_not_recognized">Không xác nhận được file.</string>
<string name="habits_imported">Thói quen được nhập thành công.</string>
<string name="full_backup_success">Xuất bản sao lưu đầy đủ thành công.</string>
<string name="import_data">Nhập dữ liệu</string>
<string name="export_full_backup">Xuất toàn bộ sao lưu</string>
<string name="import_data_summary">Hồ trợ các bản sao lưu đầy đủ được xuất ra bởi ứng dụng, cũng như các file được tạo bởi Tickmate, HabitBull hoặc Rewire. Xem FAQ để biết thêm thông tin.</string>
<string name="export_as_csv_summary">Các file tạo ra có thể mở bằng các phần mềm bảng tĩnh như Microsoft Excel hoặc OpenOffice Calc. Nhưng file này không thể nhập lại.</string>
<string name="export_full_backup_summary">Tạo ra một tệp chứa tất cả dữ liệu của bạn. Tệp này có thể nhập lại.</string>
<string name="bug_report_failed">Tạo báo cáo về lỗi.</string>
<string name="generate_bug_report">Tạo báo cáo lỗi</string>
<string name="troubleshooting">Xử lí sự cố</string>
<string name="help_translate">Giúp dịch ứng dụng</string>
<string name="night_mode">Chế độ ban đêm</string>
<string name="use_pure_black">Sử dụng màu đen thuần trong chế độ ban đêm</string>
<string name="pure_black_description">Thay thế nền màu xám bởi màu đen thuần trong chế độ ban đêm. Giảm thiểu việc sử dụng pin của điện thoại có màn hình AMOLED.</string>
<string name="interface_preferences">Giao diện</string>
<string name="reverse_days">Đảo ngược thứ tự của ngày</string>
<string name="reverse_days_description">Hiển thị ngày ngược trên màn hình chính</string>
<string name="day">Ngày</string>
<string name="week">Tuần</string>
<string name="month">Tháng</string>
<string name="quarter">Quý</string>
<string name="year">Năm</string>
<string name="total">Tổng</string>
<!-- Middle part of the sentence '1 time in xx days' -->
<string name="time_every">lần trong</string>
<string name="every_x_days">Mỗi %d ngày</string>
<string name="every_x_weeks">Mỗi %d tuần</string>
<string name="every_x_months">Mỗi %d tháng</string>
<string name="score">Điểm</string>
<string name="reminder_sound">Âm báo</string>
<string name="none">Không có</string>
<string name="filter">Lọc</string>
<string name="hide_completed">Ẩn mục đã hoàn thành</string>
<string name="hide_archived">Ẩn mục đã lưu trữ</string>
<string name="sticky_notifications">Gửi thông báo cố định</string>
<string name="sticky_notifications_description">Không cho các thông báo bị vuốt ngang mất.</string>
<string name="repair_database">Sửa cơ sở dữ liệu</string>
<string name="database_repaired">Cơ sở dữ liệu đã được sửa.</string>
<string name="uncheck">Bỏ đánh dấu</string>
<string name="toggle">Bật/tắt</string>
<string name="action">Hành động</string>
<string name="habit">Thói quen</string>
<string name="sort">Sắp xếp</string>
<string name="manually">Thủ công</string>
<string name="by_name">Theo tên</string>
<string name="by_color">Theo màu sắc</string>
<string name="by_score">Theo điểm số</string>
<string name="download">Tải về</string>
<string name="export">Xuất dữ liệu ra</string>
</resources>

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#1976D2</color>
</resources>

@ -204,4 +204,6 @@
<string name="by_score">By score</string>
<string name="download">Download</string>
<string name="export">Export</string>
<string name="customize_notification_summary">Change sound, vibration, light and other notification settings</string>
<string name="customize_notification">Customize notifications</string>
</resources>

@ -68,6 +68,11 @@
android:title="@string/sticky_notifications"
android:summary="@string/sticky_notifications_description"/>
<Preference
android:key="reminderCustomize"
android:summary="@string/customize_notification_summary"
android:title="@string/customize_notification"/>
</PreferenceCategory>
<PreferenceCategory

@ -2,6 +2,7 @@
buildscript {
repositories {
jcenter()
maven { url "https://maven.google.com" }
}
dependencies {
@ -18,5 +19,6 @@ allprojects {
repositories {
jcenter()
maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
maven { url "https://maven.google.com" }
}
}

Loading…
Cancel
Save