mirror of
https://github.com/iSoron/uhabits.git
synced 2025-12-06 01:08:50 -06:00
Compare commits
56 Commits
v2.0.3
...
feature/sy
| Author | SHA1 | Date | |
|---|---|---|---|
| e8a4a9740e | |||
| f15c660d33 | |||
| 1866743c47 | |||
|
|
a781a1f947 | ||
|
|
13e57b5026 | ||
|
|
f8c7abfff4 | ||
|
|
7fe3ce970c | ||
|
|
b9eb244b0b | ||
|
|
95a1786c4a | ||
|
|
caa1c9d72e | ||
|
|
a7afe0b309 | ||
|
|
9c03174eef | ||
|
|
da02926fa6 | ||
|
|
250dabfe58 | ||
|
|
0bba3b76bc | ||
|
|
c2479278ba | ||
|
|
499a403a06 | ||
|
|
af5d622339 | ||
|
|
5eeb54bc47 | ||
|
|
e09e899aad | ||
| 37f03aca37 | |||
|
|
0ab55f6f5a | ||
|
|
43921721d4 | ||
|
|
33c88cded3 | ||
|
|
aecce891ea | ||
|
|
2ea98a7756 | ||
|
|
e667872d83 | ||
|
|
3602a614c4 | ||
|
|
d8c5f4d93c | ||
|
|
c9f4df9dae | ||
|
|
feb384bca6 | ||
|
|
71e9160460 | ||
|
|
64966d3c86 | ||
|
|
4787df4074 | ||
|
|
5908692a5c | ||
|
|
693dce8719 | ||
| b232827dfd | |||
|
|
02f9f411ce | ||
| bbf9da44e1 | |||
|
|
ee896fb4f9 | ||
|
|
4d7d8b6206 | ||
|
|
55b841a8b4 | ||
| 7fac86b617 | |||
|
|
c31d42be2d | ||
| 31c09b9c0b | |||
|
|
db91dce57f | ||
| 12c76245e6 | |||
| 7f1a1add8c | |||
| 59a4d7552c | |||
| 9d7840bdd1 | |||
|
|
32db4e363b | ||
|
|
004bb8d71c | ||
|
|
21a1e88c47 | ||
|
|
86fb718896 | ||
|
|
a4e9b2f874 | ||
|
|
ac924470b8 |
18
.github/ISSUE_TEMPLATE/bug_report.md
vendored
18
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -6,11 +6,16 @@ labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
<!--
|
||||
Please use the template below for your bug report. Issues that do not follow this
|
||||
template, or that do not contain all necessary information (namely, description of
|
||||
the problem, steps to reproduce, phone, phone operating system, and app version)
|
||||
will be closed without further consideration.
|
||||
-->
|
||||
|
||||
## Pre-submission checklist
|
||||
- [ ] I am submitting a bug report, not a feature request.
|
||||
- [ ] I am running the latest version of Loop Habit Tracker.
|
||||
- [ ] I have have searched for similar issues, but did not find any matches.
|
||||
- [ ] I have searched for similar issues, but did not find any matches.
|
||||
|
||||
## Description
|
||||
A clear and concise description of what the problem is.
|
||||
@@ -21,10 +26,11 @@ A clear and concise description of what the problem is.
|
||||
3. Scroll down to ....
|
||||
4. See error
|
||||
|
||||
## Screenshots
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
## System information
|
||||
- Phone: [e.g. Google Pixel 4]
|
||||
- Phone Operating System: [e.g. Android 10]
|
||||
- App version: [e.g. 1.8.9]
|
||||
- App version: [e.g. 2.0.2]
|
||||
|
||||
## Screenshots
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
|
||||
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@@ -3,4 +3,4 @@ updates:
|
||||
- package-ecosystem: "gradle"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
interval: "monthly"
|
||||
|
||||
5
.github/workflows/main.yml
vendored
5
.github/workflows/main.yml
vendored
@@ -55,6 +55,11 @@ jobs:
|
||||
name: uhabits-android
|
||||
path: uhabits-android/build/outputs/
|
||||
|
||||
- name: Install flock
|
||||
run: |
|
||||
brew install util-linux
|
||||
echo "/usr/local/opt/util-linux/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Run Android Tests
|
||||
run: ./build.sh android-tests ${{ matrix.api }}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ source.
|
||||
|
||||
<p align="center">
|
||||
<a href="https://play.google.com/store/apps/details?id=org.isoron.uhabits&utm_source=global_co&utm_medium=prtnr&utm_content=Mar2515&utm_campaign=PartBadge&pcampaignid=MKT-AC-global-none-all-co-pr-py-PartBadges-Oct1515-1"><img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/apps/en-play-badge-border.png" height="75px"/></a>
|
||||
<a href="http://f-droid.org/app/org.isoron.uhabits"><img alt="Get it on F-Droid" src="http://i.imgur.com/baSPE7X.png" height="75px"/></a>
|
||||
<a href="https://f-droid.org/app/org.isoron.uhabits"><img alt="Get it on F-Droid" src="https://i.imgur.com/baSPE7X.png" height="75px"/></a>
|
||||
</p>
|
||||
|
||||
## Screenshots
|
||||
@@ -109,7 +109,7 @@ contribute, even if you are not a software developer.
|
||||
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/>.
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
[screen1]: screenshots/1.png
|
||||
[screen2]: screenshots/2.png
|
||||
@@ -123,9 +123,9 @@ contribute, even if you are not a software developer.
|
||||
[screen4th]: screenshots/4.thumb.png
|
||||
[screen5th]: screenshots/5.thumb.png
|
||||
[screen6th]: screenshots/6.thumb.png
|
||||
[poedit]: http://translate.loophabits.org
|
||||
[poedit]: https://translate.loophabits.org
|
||||
[playstore]: https://play.google.com/store/apps/details?id=org.isoron.uhabits
|
||||
[releases]: https://github.com/iSoron/uhabits/releases
|
||||
[fdroid]: http://f-droid.org/app/org.isoron.uhabits
|
||||
[fdroid]: https://f-droid.org/app/org.isoron.uhabits
|
||||
[build]: https://github.com/iSoron/uhabits/blob/dev/docs/BUILD.md
|
||||
[beta]: https://play.google.com/apps/testing/org.isoron.uhabits
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
plugins {
|
||||
val kotlinVersion = "1.4.21"
|
||||
id("com.android.application") version ("4.1.0") apply (false)
|
||||
val kotlinVersion = "1.5.0"
|
||||
id("com.android.application") version ("4.2.0") apply (false)
|
||||
id("org.jetbrains.kotlin.android") version kotlinVersion apply (false)
|
||||
id("org.jetbrains.kotlin.kapt") version kotlinVersion apply (false)
|
||||
id("org.jetbrains.kotlin.android.extensions") version kotlinVersion apply (false)
|
||||
id("org.jetbrains.kotlin.multiplatform") version kotlinVersion apply (false)
|
||||
id("org.jlleitschuh.gradle.ktlint") version "10.0.0"
|
||||
id("org.jlleitschuh.gradle.ktlint") version "10.1.0"
|
||||
}
|
||||
|
||||
apply {
|
||||
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,5 @@
|
||||
#Sat Nov 28 09:55:24 CST 2020
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-all.zip
|
||||
|
||||
2
gradlew
vendored
2
gradlew
vendored
@@ -82,6 +82,7 @@ esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
@@ -129,6 +130,7 @@ fi
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
|
||||
25
gradlew.bat
vendored
25
gradlew.bat
vendored
@@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@@ -37,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
@@ -51,7 +54,7 @@ goto fail
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
@@ -61,28 +64,14 @@ echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("com.github.triplet.play") version "3.2.0"
|
||||
id("com.github.triplet.play") version "3.5.0"
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("org.jetbrains.kotlin.kapt")
|
||||
@@ -88,11 +88,11 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
val daggerVersion = "2.34.1"
|
||||
val kotlinVersion = "1.4.32"
|
||||
val kxCoroutinesVersion = "1.4.2"
|
||||
val ktorVersion = "1.5.3"
|
||||
val espressoVersion = "3.3.0"
|
||||
val daggerVersion = "2.38.1"
|
||||
val kotlinVersion = "1.5.21"
|
||||
val kxCoroutinesVersion = "1.5.1"
|
||||
val ktorVersion = "1.6.2"
|
||||
val espressoVersion = "3.4.0"
|
||||
|
||||
androidTestImplementation("androidx.test.espresso:espresso-contrib:$espressoVersion")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:$espressoVersion")
|
||||
@@ -101,13 +101,13 @@ dependencies {
|
||||
androidTestImplementation("io.ktor:ktor-client-mock:$ktorVersion")
|
||||
androidTestImplementation("io.ktor:ktor-jackson:$ktorVersion")
|
||||
androidTestImplementation("androidx.annotation:annotation:1.2.0")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.2")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.3")
|
||||
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.2.0")
|
||||
androidTestImplementation("androidx.test:rules:1.3.0")
|
||||
androidTestImplementation("androidx.test:rules:1.4.0")
|
||||
androidTestImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0")
|
||||
compileOnly("javax.annotation:jsr250-api:1.0")
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5")
|
||||
implementation("com.github.paolorotolo:appintro:3.4.0")
|
||||
implementation("com.github.paolorotolo:appintro:4.1.0")
|
||||
implementation("com.google.code.findbugs:jsr305:3.0.2")
|
||||
implementation("com.google.dagger:dagger:$daggerVersion")
|
||||
implementation("com.google.guava:guava:30.1.1-android")
|
||||
@@ -118,11 +118,12 @@ dependencies {
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$kxCoroutinesVersion")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kxCoroutinesVersion")
|
||||
implementation("androidx.appcompat:appcompat:1.2.0")
|
||||
implementation("androidx.appcompat:appcompat:1.3.1")
|
||||
implementation("androidx.legacy:legacy-preference-v14:1.0.0")
|
||||
implementation("androidx.legacy:legacy-support-v4:1.0.0")
|
||||
implementation("com.google.android.material:material:1.3.0")
|
||||
implementation("com.opencsv:opencsv:5.4")
|
||||
implementation("com.google.android.material:material:1.4.0")
|
||||
implementation("com.google.zxing:core:3.4.1")
|
||||
implementation("com.opencsv:opencsv:5.5.1")
|
||||
implementation(project(":uhabits-core"))
|
||||
kapt("com.google.dagger:dagger-compiler:$daggerVersion")
|
||||
kaptAndroidTest("com.google.dagger:dagger-compiler:$daggerVersion")
|
||||
|
||||
@@ -23,10 +23,10 @@ import org.isoron.uhabits.core.models.Entry.Companion.YES_MANUAL
|
||||
import org.isoron.uhabits.core.models.Frequency
|
||||
import org.isoron.uhabits.core.models.Frequency.Companion.DAILY
|
||||
import org.isoron.uhabits.core.models.Habit
|
||||
import org.isoron.uhabits.core.models.Habit.Companion.AT_LEAST
|
||||
import org.isoron.uhabits.core.models.Habit.Companion.NUMBER_HABIT
|
||||
import org.isoron.uhabits.core.models.HabitList
|
||||
import org.isoron.uhabits.core.models.HabitType
|
||||
import org.isoron.uhabits.core.models.ModelFactory
|
||||
import org.isoron.uhabits.core.models.NumericalHabitType
|
||||
import org.isoron.uhabits.core.models.PaletteColor
|
||||
import org.isoron.uhabits.core.models.Timestamp
|
||||
import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday
|
||||
@@ -102,8 +102,8 @@ class HabitFixtures(private val modelFactory: ModelFactory, private val habitLis
|
||||
val habit = modelFactory.buildHabit().apply {
|
||||
name = "Read"
|
||||
question = "How many pages did you walk today?"
|
||||
type = NUMBER_HABIT
|
||||
targetType = AT_LEAST
|
||||
type = HabitType.NUMERICAL
|
||||
targetType = NumericalHabitType.AT_LEAST
|
||||
targetValue = 200.0
|
||||
unit = "pages"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
||||
*
|
||||
* 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.sync
|
||||
|
||||
import androidx.test.filters.MediumTest
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.engine.mock.MockEngine
|
||||
import io.ktor.client.engine.mock.MockRequestHandleScope
|
||||
import io.ktor.client.engine.mock.respond
|
||||
import io.ktor.client.engine.mock.respondError
|
||||
import io.ktor.client.engine.mock.respondOk
|
||||
import io.ktor.client.features.json.JsonFeature
|
||||
import io.ktor.client.request.HttpRequestData
|
||||
import io.ktor.client.request.HttpResponseData
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.http.fullPath
|
||||
import io.ktor.http.headersOf
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.isoron.uhabits.BaseAndroidTest
|
||||
import org.isoron.uhabits.core.sync.AbstractSyncServer
|
||||
import org.isoron.uhabits.core.sync.GetDataVersionResponse
|
||||
import org.isoron.uhabits.core.sync.KeyNotFoundException
|
||||
import org.isoron.uhabits.core.sync.RegisterReponse
|
||||
import org.isoron.uhabits.core.sync.ServiceUnavailable
|
||||
import org.isoron.uhabits.core.sync.SyncData
|
||||
import org.junit.Test
|
||||
|
||||
@MediumTest
|
||||
class RemoteSyncServerTest : BaseAndroidTest() {
|
||||
|
||||
private val mapper = ObjectMapper()
|
||||
val data = SyncData(1, "Hello world")
|
||||
|
||||
@Test
|
||||
fun when_register_succeeds_should_return_key() = runBlocking {
|
||||
val server = server("/register") {
|
||||
respondWithJson(RegisterReponse("ABCDEF"))
|
||||
}
|
||||
assertEquals("ABCDEF", server.register())
|
||||
}
|
||||
|
||||
@Test(expected = ServiceUnavailable::class)
|
||||
fun when_register_fails_should_raise_correct_exception() = runBlocking {
|
||||
val server = server("/register") {
|
||||
respondError(HttpStatusCode.ServiceUnavailable)
|
||||
}
|
||||
server.register()
|
||||
return@runBlocking
|
||||
}
|
||||
|
||||
@Test
|
||||
fun when_get_data_version_succeeds_should_return_version() = runBlocking {
|
||||
server("/db/ABC/version") {
|
||||
respondWithJson(GetDataVersionResponse(5))
|
||||
}.apply {
|
||||
assertEquals(5, getDataVersion("ABC"))
|
||||
}
|
||||
return@runBlocking
|
||||
}
|
||||
|
||||
@Test(expected = ServiceUnavailable::class)
|
||||
fun when_get_data_version_with_server_error_should_raise_exception() = runBlocking {
|
||||
server("/db/ABC/version") {
|
||||
respondError(HttpStatusCode.InternalServerError)
|
||||
}.apply {
|
||||
getDataVersion("ABC")
|
||||
}
|
||||
return@runBlocking
|
||||
}
|
||||
|
||||
@Test(expected = KeyNotFoundException::class)
|
||||
fun when_get_data_version_with_invalid_key_should_raise_exception() = runBlocking {
|
||||
server("/db/ABC/version") {
|
||||
respondError(HttpStatusCode.NotFound)
|
||||
}.apply {
|
||||
getDataVersion("ABC")
|
||||
}
|
||||
return@runBlocking
|
||||
}
|
||||
|
||||
@Test
|
||||
fun when_get_data_succeeds_should_return_data() = runBlocking {
|
||||
server("/db/ABC") {
|
||||
respondWithJson(data)
|
||||
}.apply {
|
||||
assertEquals(data, getData("ABC"))
|
||||
}
|
||||
return@runBlocking
|
||||
}
|
||||
|
||||
@Test(expected = KeyNotFoundException::class)
|
||||
fun when_get_data_with_invalid_key_should_raise_exception() = runBlocking {
|
||||
server("/db/ABC") {
|
||||
respondError(HttpStatusCode.NotFound)
|
||||
}.apply {
|
||||
getData("ABC")
|
||||
}
|
||||
return@runBlocking
|
||||
}
|
||||
|
||||
@Test
|
||||
fun when_put_succeeds_should_not_raise_exceptions() = runBlocking {
|
||||
server("/db/ABC") {
|
||||
respondOk()
|
||||
}.apply {
|
||||
put("ABC", data)
|
||||
}
|
||||
return@runBlocking
|
||||
}
|
||||
|
||||
private fun server(
|
||||
expectedPath: String,
|
||||
action: MockRequestHandleScope.(HttpRequestData) -> HttpResponseData
|
||||
): AbstractSyncServer {
|
||||
return RemoteSyncServer(
|
||||
httpClient = HttpClient(MockEngine) {
|
||||
install(JsonFeature)
|
||||
engine {
|
||||
addHandler { request ->
|
||||
when (request.url.fullPath) {
|
||||
expectedPath -> action(request)
|
||||
else -> error("unexpected call: ${request.url.fullPath}")
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
preferences = prefs
|
||||
)
|
||||
}
|
||||
|
||||
private fun MockRequestHandleScope.respondWithJson(content: Any) =
|
||||
respond(
|
||||
mapper.writeValueAsBytes(content),
|
||||
headers = headersOf("Content-Type" to listOf("application/json"))
|
||||
)
|
||||
}
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:name=".HabitsApplication"
|
||||
@@ -40,6 +42,14 @@
|
||||
android:value=".activities.habits.list.ListHabitsActivity" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".activities.sync.SyncActivity"
|
||||
android:exported="true">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".activities.settings.SettingsActivity" />
|
||||
</activity>
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.android.backup.api_key"
|
||||
android:value="AEdPqrEAAAAI6aeWncbnMNo8E5GWeZ44dlc5cQ7tCROwFhOtiw" />
|
||||
@@ -49,6 +59,16 @@
|
||||
android:exported="true"
|
||||
android:label="@string/main_activity_title"
|
||||
android:launchMode="singleTop">
|
||||
<tools:validation testUrl="https://loophabits.org/sync/123" />
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data
|
||||
android:scheme="https"
|
||||
android:host="loophabits.org"
|
||||
android:pathPrefix="/sync" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity-alias
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.isoron.uhabits.activities.common.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import org.isoron.uhabits.R
|
||||
import org.isoron.uhabits.core.ui.callbacks.OnConfirmedCallback
|
||||
import org.isoron.uhabits.inject.ActivityContext
|
||||
|
||||
class ConfirmSyncKeyDialog(
|
||||
@ActivityContext context: Context,
|
||||
callback: OnConfirmedCallback
|
||||
) : AlertDialog(context) {
|
||||
init {
|
||||
setTitle(R.string.device_sync)
|
||||
val res = context.resources
|
||||
setMessage(res.getString(R.string.sync_confirm))
|
||||
setButton(
|
||||
BUTTON_POSITIVE,
|
||||
res.getString(R.string.yes)
|
||||
) { dialog: DialogInterface?, which: Int -> callback.onConfirmed() }
|
||||
setButton(
|
||||
BUTTON_NEGATIVE,
|
||||
res.getString(R.string.no)
|
||||
) { dialog: DialogInterface?, which: Int -> }
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ class TaskProgressBar(
|
||||
context,
|
||||
null,
|
||||
android.R.attr.progressBarStyleHorizontal
|
||||
),
|
||||
),
|
||||
TaskRunner.Listener {
|
||||
|
||||
init {
|
||||
|
||||
@@ -52,6 +52,8 @@ import org.isoron.uhabits.core.commands.CreateHabitCommand
|
||||
import org.isoron.uhabits.core.commands.EditHabitCommand
|
||||
import org.isoron.uhabits.core.models.Frequency
|
||||
import org.isoron.uhabits.core.models.Habit
|
||||
import org.isoron.uhabits.core.models.HabitType
|
||||
import org.isoron.uhabits.core.models.NumericalHabitType
|
||||
import org.isoron.uhabits.core.models.PaletteColor
|
||||
import org.isoron.uhabits.core.models.Reminder
|
||||
import org.isoron.uhabits.core.models.WeekdayList
|
||||
@@ -77,7 +79,7 @@ class EditHabitActivity : AppCompatActivity() {
|
||||
private lateinit var commandRunner: CommandRunner
|
||||
|
||||
var habitId = -1L
|
||||
var habitType = -1
|
||||
lateinit var habitType: HabitType
|
||||
var unit = ""
|
||||
var color = PaletteColor(11)
|
||||
var androidColor = 0
|
||||
@@ -116,12 +118,12 @@ class EditHabitActivity : AppCompatActivity() {
|
||||
binding.unitInput.setText(habit.unit)
|
||||
binding.targetInput.setText(habit.targetValue.toString())
|
||||
} else {
|
||||
habitType = intent.getIntExtra("habitType", Habit.YES_NO_HABIT)
|
||||
habitType = HabitType.fromInt(intent.getIntExtra("habitType", HabitType.YES_NO.value))
|
||||
}
|
||||
|
||||
if (state != null) {
|
||||
habitId = state.getLong("habitId")
|
||||
habitType = state.getInt("habitType")
|
||||
habitType = HabitType.fromInt(state.getInt("habitType"))
|
||||
color = PaletteColor(state.getInt("paletteColor"))
|
||||
freqNum = state.getInt("freqNum")
|
||||
freqDen = state.getInt("freqDen")
|
||||
@@ -132,14 +134,17 @@ class EditHabitActivity : AppCompatActivity() {
|
||||
|
||||
updateColors()
|
||||
|
||||
if (habitType == Habit.YES_NO_HABIT) {
|
||||
when (habitType) {
|
||||
HabitType.YES_NO -> {
|
||||
binding.unitOuterBox.visibility = View.GONE
|
||||
binding.targetOuterBox.visibility = View.GONE
|
||||
} else {
|
||||
}
|
||||
HabitType.NUMERICAL -> {
|
||||
binding.nameInput.hint = getString(R.string.measurable_short_example)
|
||||
binding.questionInput.hint = getString(R.string.measurable_question_example)
|
||||
binding.frequencyOuterBox.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
setSupportActionBar(binding.toolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
@@ -255,9 +260,9 @@ class EditHabitActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
habit.frequency = Frequency(freqNum, freqDen)
|
||||
if (habitType == Habit.NUMBER_HABIT) {
|
||||
if (habitType == HabitType.NUMERICAL) {
|
||||
habit.targetValue = targetInput.text.toString().toDouble()
|
||||
habit.targetType = Habit.AT_LEAST
|
||||
habit.targetType = NumericalHabitType.AT_LEAST
|
||||
habit.unit = unitInput.text.trim().toString()
|
||||
}
|
||||
habit.type = habitType
|
||||
@@ -285,7 +290,7 @@ class EditHabitActivity : AppCompatActivity() {
|
||||
nameInput.error = getFormattedValidationError(R.string.validation_cannot_be_blank)
|
||||
isValid = false
|
||||
}
|
||||
if (habitType == Habit.NUMBER_HABIT) {
|
||||
if (habitType == HabitType.NUMERICAL) {
|
||||
if (targetInput.text.isEmpty()) {
|
||||
targetInput.error = getString(R.string.validation_cannot_be_blank)
|
||||
isValid = false
|
||||
@@ -338,7 +343,7 @@ class EditHabitActivity : AppCompatActivity() {
|
||||
super.onSaveInstanceState(state)
|
||||
with(state) {
|
||||
putLong("habitId", habitId)
|
||||
putInt("habitType", habitType)
|
||||
putInt("habitType", habitType.value)
|
||||
putInt("paletteColor", color.paletteIndex)
|
||||
putInt("androidColor", androidColor)
|
||||
putInt("freqNum", freqNum)
|
||||
|
||||
@@ -25,7 +25,7 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatDialogFragment
|
||||
import org.isoron.uhabits.R
|
||||
import org.isoron.uhabits.core.models.Habit
|
||||
import org.isoron.uhabits.core.models.HabitType
|
||||
import org.isoron.uhabits.databinding.SelectHabitTypeBinding
|
||||
import org.isoron.uhabits.intents.IntentFactory
|
||||
|
||||
@@ -40,13 +40,13 @@ class HabitTypeDialog : AppCompatDialogFragment() {
|
||||
val binding = SelectHabitTypeBinding.inflate(inflater, container, false)
|
||||
|
||||
binding.buttonYesNo.setOnClickListener {
|
||||
val intent = IntentFactory().startEditActivity(activity!!, Habit.YES_NO_HABIT)
|
||||
val intent = IntentFactory().startEditActivity(activity!!, HabitType.YES_NO.value)
|
||||
startActivity(intent)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
binding.buttonMeasurable.setOnClickListener {
|
||||
val intent = IntentFactory().startEditActivity(activity!!, Habit.NUMBER_HABIT)
|
||||
val intent = IntentFactory().startEditActivity(activity!!, HabitType.NUMERICAL.value)
|
||||
startActivity(intent)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
@@ -26,10 +26,12 @@ import android.view.MenuItem
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.isoron.uhabits.BaseExceptionHandler
|
||||
import org.isoron.uhabits.HabitsApplication
|
||||
import org.isoron.uhabits.activities.habits.list.views.HabitCardListAdapter
|
||||
import org.isoron.uhabits.core.preferences.Preferences
|
||||
import org.isoron.uhabits.core.sync.SyncManager
|
||||
import org.isoron.uhabits.core.tasks.TaskRunner
|
||||
import org.isoron.uhabits.core.ui.ThemeSwitcher.Companion.THEME_DARK
|
||||
import org.isoron.uhabits.core.utils.MidnightTimer
|
||||
@@ -38,7 +40,7 @@ import org.isoron.uhabits.inject.ActivityContextModule
|
||||
import org.isoron.uhabits.inject.DaggerHabitsActivityComponent
|
||||
import org.isoron.uhabits.utils.restartWithFade
|
||||
|
||||
class ListHabitsActivity : AppCompatActivity() {
|
||||
class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
|
||||
|
||||
var pureBlack: Boolean = false
|
||||
lateinit var taskRunner: TaskRunner
|
||||
@@ -47,10 +49,15 @@ class ListHabitsActivity : AppCompatActivity() {
|
||||
lateinit var screen: ListHabitsScreen
|
||||
lateinit var prefs: Preferences
|
||||
lateinit var midnightTimer: MidnightTimer
|
||||
lateinit var syncManager: SyncManager
|
||||
private val scope = CoroutineScope(Dispatchers.Main)
|
||||
|
||||
private lateinit var menu: ListHabitsMenu
|
||||
|
||||
override fun onQuestionMarksChanged() {
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
@@ -63,6 +70,8 @@ class ListHabitsActivity : AppCompatActivity() {
|
||||
component.themeSwitcher.apply()
|
||||
|
||||
prefs = appComponent.preferences
|
||||
prefs.addListener(this)
|
||||
syncManager = appComponent.syncManager
|
||||
pureBlack = prefs.isPureBlackEnabled
|
||||
midnightTimer = appComponent.midnightTimer
|
||||
rootView = component.listHabitsRootView
|
||||
@@ -79,6 +88,9 @@ class ListHabitsActivity : AppCompatActivity() {
|
||||
midnightTimer.onPause()
|
||||
screen.onDettached()
|
||||
adapter.cancelRefresh()
|
||||
scope.launch {
|
||||
syncManager.onPause()
|
||||
}
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
@@ -87,6 +99,9 @@ class ListHabitsActivity : AppCompatActivity() {
|
||||
screen.onAttached()
|
||||
rootView.postInvalidate()
|
||||
midnightTimer.onResume()
|
||||
scope.launch {
|
||||
syncManager.onResume()
|
||||
}
|
||||
taskRunner.run {
|
||||
AutoBackup(this@ListHabitsActivity).run()
|
||||
}
|
||||
|
||||
@@ -52,6 +52,11 @@ class ListHabitsMenu @Inject constructor(
|
||||
nightModeItem.isChecked = themeSwitcher.isNightMode
|
||||
hideArchivedItem.isChecked = !preferences.showArchived
|
||||
hideCompletedItem.isChecked = !preferences.showCompleted
|
||||
if (preferences.areQuestionMarksEnabled || preferences.isSkipEnabled) {
|
||||
hideCompletedItem.title = activity.resources.getString(R.string.hide_entered)
|
||||
} else {
|
||||
hideCompletedItem.title = activity.resources.getString(R.string.hide_completed)
|
||||
}
|
||||
updateArrows(menu)
|
||||
}
|
||||
|
||||
|
||||
@@ -22,11 +22,13 @@ package org.isoron.uhabits.activities.habits.list
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import dagger.Lazy
|
||||
import org.isoron.uhabits.R
|
||||
import org.isoron.uhabits.activities.common.dialogs.ColorPickerDialogFactory
|
||||
import org.isoron.uhabits.activities.common.dialogs.ConfirmDeleteDialog
|
||||
import org.isoron.uhabits.activities.common.dialogs.ConfirmSyncKeyDialog
|
||||
import org.isoron.uhabits.activities.common.dialogs.NumberPickerFactory
|
||||
import org.isoron.uhabits.activities.habits.edit.HabitTypeDialog
|
||||
import org.isoron.uhabits.activities.habits.list.views.HabitCardListAdapter
|
||||
@@ -51,6 +53,8 @@ import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message
|
||||
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message.FILE_NOT_RECOGNIZED
|
||||
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message.IMPORT_FAILED
|
||||
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message.IMPORT_SUCCESSFUL
|
||||
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message.SYNC_ENABLED
|
||||
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message.SYNC_KEY_ALREADY_INSTALLED
|
||||
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsMenuBehavior
|
||||
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsSelectionMenuBehavior
|
||||
import org.isoron.uhabits.inject.ActivityContext
|
||||
@@ -99,6 +103,14 @@ class ListHabitsScreen
|
||||
|
||||
fun onAttached() {
|
||||
commandRunner.addListener(this)
|
||||
if (activity.intent.action == "android.intent.action.VIEW") {
|
||||
val uri = activity.intent.data!!.toString()
|
||||
val parts = uri.replace(Regex("^.*sync/"), "").split("#")
|
||||
val syncKey = parts[0]
|
||||
val encKey = parts[1]
|
||||
Log.i("ListHabitsScreen", "sync: $syncKey enc: $encKey")
|
||||
behavior.get().onSyncKeyOffer(syncKey, encKey)
|
||||
}
|
||||
}
|
||||
|
||||
fun onDettached() {
|
||||
@@ -196,6 +208,8 @@ class ListHabitsScreen
|
||||
DATABASE_REPAIRED -> R.string.database_repaired
|
||||
COULD_NOT_GENERATE_BUG_REPORT -> R.string.bug_report_failed
|
||||
FILE_NOT_RECOGNIZED -> R.string.file_not_recognized
|
||||
SYNC_ENABLED -> R.string.sync_enabled
|
||||
SYNC_KEY_ALREADY_INSTALLED -> R.string.sync_key_already_installed
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -230,6 +244,10 @@ class ListHabitsScreen
|
||||
numberPickerFactory.create(value, unit, callback).show()
|
||||
}
|
||||
|
||||
override fun showConfirmInstallSyncKey(callback: OnConfirmedCallback) {
|
||||
ConfirmSyncKeyDialog(activity, callback).show()
|
||||
}
|
||||
|
||||
private fun getExecuteString(command: Command): String? {
|
||||
when (command) {
|
||||
is ArchiveHabitsCommand -> {
|
||||
|
||||
@@ -131,7 +131,7 @@ class CheckmarkButtonView(
|
||||
paint.color = when (value) {
|
||||
YES_MANUAL, YES_AUTO, SKIP -> color
|
||||
NO -> {
|
||||
if (preferences.areQuestionMarksEnabled()) mediumContrastColor
|
||||
if (preferences.areQuestionMarksEnabled) mediumContrastColor
|
||||
else lowContrastColor
|
||||
}
|
||||
else -> lowContrastColor
|
||||
@@ -140,7 +140,7 @@ class CheckmarkButtonView(
|
||||
SKIP -> R.string.fa_skipped
|
||||
NO -> R.string.fa_times
|
||||
UNKNOWN -> {
|
||||
if (preferences.areQuestionMarksEnabled()) R.string.fa_question
|
||||
if (preferences.areQuestionMarksEnabled) R.string.fa_question
|
||||
else R.string.fa_times
|
||||
}
|
||||
else -> R.string.fa_check
|
||||
|
||||
@@ -169,7 +169,7 @@ class NumberButtonView(
|
||||
typeface = BOLD_TYPEFACE
|
||||
textSize = dim(R.dimen.smallTextSize)
|
||||
}
|
||||
preferences.areQuestionMarksEnabled() -> {
|
||||
preferences.areQuestionMarksEnabled -> {
|
||||
label = resources.getString(R.string.fa_question)
|
||||
typeface = getFontAwesome()
|
||||
textSize = dim(R.dimen.smallerTextSize)
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
package org.isoron.uhabits.activities.settings
|
||||
|
||||
import android.app.backup.BackupManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||
@@ -27,6 +28,7 @@ import android.os.Build.VERSION
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import androidx.preference.CheckBoxPreference
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceCategory
|
||||
@@ -41,6 +43,7 @@ import org.isoron.uhabits.activities.habits.list.RESULT_REPAIR_DB
|
||||
import org.isoron.uhabits.core.preferences.Preferences
|
||||
import org.isoron.uhabits.core.ui.NotificationTray
|
||||
import org.isoron.uhabits.core.utils.DateUtils.Companion.getLongWeekdayNames
|
||||
import org.isoron.uhabits.intents.IntentFactory
|
||||
import org.isoron.uhabits.notifications.AndroidNotificationTray.Companion.createAndroidNotificationChannel
|
||||
import org.isoron.uhabits.notifications.RingtoneManager
|
||||
import org.isoron.uhabits.widgets.WidgetUpdater
|
||||
@@ -97,6 +100,13 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis
|
||||
intent.putExtra(Settings.EXTRA_CHANNEL_ID, NotificationTray.REMINDERS_CHANNEL_ID)
|
||||
startActivity(intent)
|
||||
return true
|
||||
} else if (key == "pref_sync_enabled_dummy") {
|
||||
if (prefs.isSyncEnabled) {
|
||||
prefs.disableSync()
|
||||
} else {
|
||||
val context: Context? = activity
|
||||
context!!.startActivity(IntentFactory().startSyncActivity(context))
|
||||
}
|
||||
}
|
||||
return super.onPreferenceTreeClick(preference)
|
||||
}
|
||||
@@ -111,6 +121,7 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis
|
||||
devCategory.isVisible = false
|
||||
}
|
||||
updateWeekdayPreference()
|
||||
updateSyncPreferences()
|
||||
|
||||
if (VERSION.SDK_INT < Build.VERSION_CODES.O)
|
||||
findPreference("reminderCustomize").isVisible = false
|
||||
@@ -119,6 +130,12 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateSyncPreferences() {
|
||||
findPreference("pref_sync_display").isVisible = prefs.isSyncEnabled
|
||||
(findPreference("pref_sync_enabled_dummy") as CheckBoxPreference).isChecked =
|
||||
prefs.isSyncEnabled
|
||||
}
|
||||
|
||||
private fun updateWeekdayPreference() {
|
||||
val weekdayPref = findPreference("pref_first_weekday") as ListPreference
|
||||
val currentFirstWeekday = prefs.firstWeekday.daysSinceSunday + 1
|
||||
@@ -140,6 +157,7 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis
|
||||
}
|
||||
BackupManager.dataChanged("org.isoron.uhabits")
|
||||
updateWeekdayPreference()
|
||||
updateSyncPreferences()
|
||||
}
|
||||
|
||||
private fun setResultOnPreferenceClick(key: String, result: Int) {
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.activities.sync
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.text.Html
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import com.google.zxing.qrcode.QRCodeWriter
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.invoke
|
||||
import kotlinx.coroutines.launch
|
||||
import org.isoron.uhabits.HabitsApplication
|
||||
import org.isoron.uhabits.R
|
||||
import org.isoron.uhabits.activities.AndroidThemeSwitcher
|
||||
import org.isoron.uhabits.core.models.PaletteColor
|
||||
import org.isoron.uhabits.core.ui.screens.sync.SyncBehavior
|
||||
import org.isoron.uhabits.databinding.ActivitySyncBinding
|
||||
import org.isoron.uhabits.sync.RemoteSyncServer
|
||||
import org.isoron.uhabits.utils.InterfaceUtils.getFontAwesome
|
||||
import org.isoron.uhabits.utils.setupToolbar
|
||||
import org.isoron.uhabits.utils.showMessage
|
||||
|
||||
class SyncActivity : AppCompatActivity(), SyncBehavior.Screen {
|
||||
|
||||
private lateinit var binding: ActivitySyncBinding
|
||||
private lateinit var behavior: SyncBehavior
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.Main)
|
||||
|
||||
override fun onCreate(savedInstance: Bundle?) {
|
||||
super.onCreate(savedInstance)
|
||||
val component = (application as HabitsApplication).component
|
||||
val preferences = component.preferences
|
||||
val server = RemoteSyncServer(preferences = preferences)
|
||||
val themeSwitcher = AndroidThemeSwitcher(this, component.preferences)
|
||||
themeSwitcher.apply()
|
||||
|
||||
behavior = SyncBehavior(this, preferences, server, component.logging)
|
||||
binding = ActivitySyncBinding.inflate(layoutInflater)
|
||||
binding.errorIcon.typeface = getFontAwesome(this)
|
||||
binding.root.setupToolbar(
|
||||
toolbar = binding.toolbar,
|
||||
color = PaletteColor(11),
|
||||
title = resources.getString(R.string.device_sync),
|
||||
theme = themeSwitcher.currentTheme,
|
||||
)
|
||||
binding.syncLink.setOnClickListener { copyToClipboard() }
|
||||
binding.instructions.text = Html.fromHtml(resources.getString(R.string.sync_instructions))
|
||||
setContentView(binding.root)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
scope.launch {
|
||||
behavior.onResume()
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyToClipboard() {
|
||||
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
clipboard.setPrimaryClip(ClipData.newPlainText("Loop Sync Link", binding.syncLink.text))
|
||||
showMessage(resources.getString(R.string.copied_to_the_clipboard))
|
||||
}
|
||||
|
||||
suspend fun generateQR(msg: String): Bitmap = Dispatchers.IO {
|
||||
val writer = QRCodeWriter()
|
||||
val matrix = writer.encode(msg, BarcodeFormat.QR_CODE, 1024, 1024)
|
||||
val height = matrix.height
|
||||
val width = matrix.width
|
||||
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565)
|
||||
val bgColor = Color.WHITE
|
||||
val fgColor = Color.BLACK
|
||||
for (x in 0 until width) {
|
||||
for (y in 0 until height) {
|
||||
val color = if (matrix.get(x, y)) fgColor else bgColor
|
||||
bitmap.setPixel(x, y, color)
|
||||
}
|
||||
}
|
||||
return@IO bitmap
|
||||
}
|
||||
|
||||
suspend fun showQR(msg: String) {
|
||||
binding.progress.visibility = View.GONE
|
||||
binding.qrCode.visibility = View.VISIBLE
|
||||
binding.qrCode.setImageBitmap(generateQR(msg))
|
||||
}
|
||||
|
||||
override suspend fun showLoadingScreen() {
|
||||
binding.qrCode.visibility = View.GONE
|
||||
binding.progress.visibility = View.VISIBLE
|
||||
binding.errorPanel.visibility = View.GONE
|
||||
}
|
||||
|
||||
override suspend fun showErrorScreen() {
|
||||
binding.qrCode.visibility = View.GONE
|
||||
binding.progress.visibility = View.GONE
|
||||
binding.errorPanel.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
override suspend fun showLink(link: String) {
|
||||
binding.qrCode.visibility = View.GONE
|
||||
binding.progress.visibility = View.VISIBLE
|
||||
binding.errorPanel.visibility = View.GONE
|
||||
binding.syncLink.text = link
|
||||
showQR(link)
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import org.isoron.uhabits.core.models.ModelFactory
|
||||
import org.isoron.uhabits.core.preferences.Preferences
|
||||
import org.isoron.uhabits.core.preferences.WidgetPreferences
|
||||
import org.isoron.uhabits.core.reminders.ReminderScheduler
|
||||
import org.isoron.uhabits.core.sync.SyncManager
|
||||
import org.isoron.uhabits.core.tasks.TaskRunner
|
||||
import org.isoron.uhabits.core.ui.NotificationTray
|
||||
import org.isoron.uhabits.core.ui.screens.habits.list.HabitCardListCache
|
||||
@@ -63,4 +64,5 @@ interface HabitsApplicationComponent {
|
||||
val taskRunner: TaskRunner
|
||||
val widgetPreferences: WidgetPreferences
|
||||
val widgetUpdater: WidgetUpdater
|
||||
val syncManager: SyncManager
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
package org.isoron.uhabits.inject
|
||||
|
||||
import android.content.Context
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import org.isoron.uhabits.core.AppScope
|
||||
@@ -33,6 +34,8 @@ import org.isoron.uhabits.core.models.sqlite.SQLiteHabitList
|
||||
import org.isoron.uhabits.core.preferences.Preferences
|
||||
import org.isoron.uhabits.core.preferences.WidgetPreferences
|
||||
import org.isoron.uhabits.core.reminders.ReminderScheduler
|
||||
import org.isoron.uhabits.core.sync.AbstractSyncServer
|
||||
import org.isoron.uhabits.core.sync.NetworkManager
|
||||
import org.isoron.uhabits.core.tasks.TaskRunner
|
||||
import org.isoron.uhabits.core.ui.NotificationTray
|
||||
import org.isoron.uhabits.database.AndroidDatabase
|
||||
@@ -41,6 +44,8 @@ import org.isoron.uhabits.intents.IntentScheduler
|
||||
import org.isoron.uhabits.io.AndroidLogging
|
||||
import org.isoron.uhabits.notifications.AndroidNotificationTray
|
||||
import org.isoron.uhabits.preferences.SharedPreferencesStorage
|
||||
import org.isoron.uhabits.sync.AndroidNetworkManager
|
||||
import org.isoron.uhabits.sync.RemoteSyncServer
|
||||
import org.isoron.uhabits.utils.DatabaseUtils
|
||||
import java.io.File
|
||||
|
||||
@@ -109,6 +114,18 @@ class HabitsModule(dbFile: File) {
|
||||
return AndroidLogging()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@AppScope
|
||||
fun getNetworkManager(@AppContext context: Context): NetworkManager {
|
||||
return AndroidNetworkManager(context)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@AppScope
|
||||
fun getSyncServer(preferences: Preferences): AbstractSyncServer {
|
||||
return RemoteSyncServer(preferences)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@AppScope
|
||||
fun getDatabase(): Database {
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.isoron.uhabits.activities.habits.edit.EditHabitActivity
|
||||
import org.isoron.uhabits.activities.habits.show.ShowHabitActivity
|
||||
import org.isoron.uhabits.activities.intro.IntroActivity
|
||||
import org.isoron.uhabits.activities.settings.SettingsActivity
|
||||
import org.isoron.uhabits.activities.sync.SyncActivity
|
||||
import org.isoron.uhabits.core.models.Habit
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -100,4 +101,8 @@ class IntentFactory
|
||||
intent.putExtra("habitType", habitType)
|
||||
return intent
|
||||
}
|
||||
|
||||
fun startSyncActivity(context: Context): Intent {
|
||||
return Intent(context, SyncActivity::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +92,9 @@ class SharedPreferencesStorage
|
||||
preferences.setNotificationsSticky(getBoolean(key, false))
|
||||
"pref_led_notifications" ->
|
||||
preferences.setNotificationsLed(getBoolean(key, false))
|
||||
"pref_unknown_enabled" -> {
|
||||
preferences.areQuestionMarksEnabled = getBoolean(key, false)
|
||||
}
|
||||
}
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
||||
*
|
||||
* 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.sync
|
||||
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Network
|
||||
import android.net.NetworkRequest
|
||||
import org.isoron.uhabits.core.sync.NetworkManager
|
||||
|
||||
class AndroidNetworkManager(
|
||||
val context: Context,
|
||||
) : NetworkManager, ConnectivityManager.NetworkCallback() {
|
||||
|
||||
val listeners = mutableListOf<NetworkManager.Listener>()
|
||||
var connected = false
|
||||
|
||||
init {
|
||||
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
cm.registerNetworkCallback(NetworkRequest.Builder().build(), this)
|
||||
}
|
||||
|
||||
override fun addListener(listener: NetworkManager.Listener) {
|
||||
if (connected) listener.onNetworkAvailable()
|
||||
else listener.onNetworkLost()
|
||||
listeners.add(listener)
|
||||
}
|
||||
|
||||
override fun remoteListener(listener: NetworkManager.Listener) {
|
||||
listeners.remove(listener)
|
||||
}
|
||||
|
||||
override fun onAvailable(network: Network) {
|
||||
connected = true
|
||||
for (l in listeners) l.onNetworkAvailable()
|
||||
}
|
||||
|
||||
override fun onLost(network: Network) {
|
||||
connected = false
|
||||
for (l in listeners) l.onNetworkLost()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
||||
*
|
||||
* 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.sync
|
||||
|
||||
import android.util.Log
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.engine.android.Android
|
||||
import io.ktor.client.features.ClientRequestException
|
||||
import io.ktor.client.features.ServerResponseException
|
||||
import io.ktor.client.features.json.JsonFeature
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.request.header
|
||||
import io.ktor.client.request.post
|
||||
import io.ktor.client.request.put
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.invoke
|
||||
import org.isoron.uhabits.core.preferences.Preferences
|
||||
import org.isoron.uhabits.core.sync.AbstractSyncServer
|
||||
import org.isoron.uhabits.core.sync.EditConflictException
|
||||
import org.isoron.uhabits.core.sync.GetDataVersionResponse
|
||||
import org.isoron.uhabits.core.sync.KeyNotFoundException
|
||||
import org.isoron.uhabits.core.sync.RegisterReponse
|
||||
import org.isoron.uhabits.core.sync.ServiceUnavailable
|
||||
import org.isoron.uhabits.core.sync.SyncData
|
||||
|
||||
class RemoteSyncServer(
|
||||
private val preferences: Preferences,
|
||||
private val httpClient: HttpClient = HttpClient(Android) {
|
||||
install(JsonFeature)
|
||||
}
|
||||
) : AbstractSyncServer {
|
||||
|
||||
override suspend fun register(): String = Dispatchers.IO {
|
||||
try {
|
||||
val url = "${preferences.syncBaseURL}/register"
|
||||
Log.i("RemoteSyncServer", "POST $url")
|
||||
val response: RegisterReponse = httpClient.post(url)
|
||||
return@IO response.key
|
||||
} catch (e: ServerResponseException) {
|
||||
throw ServiceUnavailable()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun put(key: String, newData: SyncData) = Dispatchers.IO {
|
||||
try {
|
||||
val url = "${preferences.syncBaseURL}/db/$key"
|
||||
Log.i("RemoteSyncServer", "PUT $url")
|
||||
val response: String = httpClient.put(url) {
|
||||
header("Content-Type", "application/json")
|
||||
body = newData
|
||||
}
|
||||
} catch (e: ServerResponseException) {
|
||||
throw ServiceUnavailable()
|
||||
} catch (e: ClientRequestException) {
|
||||
Log.w("RemoteSyncServer", "ClientRequestException", e)
|
||||
if (e.message!!.contains("409")) throw EditConflictException()
|
||||
if (e.message!!.contains("404")) throw KeyNotFoundException()
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getData(key: String): SyncData = Dispatchers.IO {
|
||||
try {
|
||||
val url = "${preferences.syncBaseURL}/db/$key"
|
||||
Log.i("RemoteSyncServer", "GET $url")
|
||||
return@IO httpClient.get<SyncData>(url)
|
||||
} catch (e: ServerResponseException) {
|
||||
throw ServiceUnavailable()
|
||||
} catch (e: ClientRequestException) {
|
||||
Log.w("RemoteSyncServer", "ClientRequestException", e)
|
||||
throw KeyNotFoundException()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getDataVersion(key: String): Long = Dispatchers.IO {
|
||||
try {
|
||||
val url = "${preferences.syncBaseURL}/db/$key/version"
|
||||
Log.i("RemoteSyncServer", "GET $url")
|
||||
val response: GetDataVersionResponse = httpClient.get(url)
|
||||
return@IO response.version
|
||||
} catch (e: ServerResponseException) {
|
||||
throw ServiceUnavailable()
|
||||
} catch (e: ClientRequestException) {
|
||||
Log.w("RemoteSyncServer", "ClientRequestException", e)
|
||||
throw KeyNotFoundException()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,7 +103,7 @@ class CheckmarkWidgetView : HabitWidgetView {
|
||||
SKIP -> resources.getString(R.string.fa_skipped)
|
||||
UNKNOWN -> {
|
||||
run {
|
||||
if (preferences!!.areQuestionMarksEnabled()) {
|
||||
if (preferences!!.areQuestionMarksEnabled) {
|
||||
return resources.getString(R.string.fa_question)
|
||||
} else {
|
||||
resources.getString(R.string.fa_times)
|
||||
|
||||
1
uhabits-android/src/main/play/listings/af-rZA/title.txt
Normal file
1
uhabits-android/src/main/play/listings/af-rZA/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Loop - Gewoonte Boekhouer
|
||||
@@ -1,22 +0,0 @@
|
||||
لب يساعدك على خلق والحفاظ على العادات الجيدة، مما يسمح لك لتحقيق أهدافكة. الرسوم البيانية والإحصاءات التفصيلية تبين لكم كيف تحسن عاداتك مع مرور الوقت. هو تماما خالية من الاعلانات ومفتوحة المصدر.
|
||||
|
||||
<b>واجهة بسيطة، جميلة وحديثة </b>
|
||||
لوب يحتوي على واجهة بسيطة وهي سهلة الاستخدام و تتابع نظام تصميم الماتريل دسيجن.
|
||||
|
||||
<b>نتيجة العادات</b>
|
||||
بالإضافة إلى عرض التقدم الحالي، لوب ديه خوارزمية متقدمة لحساب قوة عاداتك. كل التكرار يجعل هذه العادة أقوى، وفي كل يوم غاب يجعلها أضعف. مع ذلك غيب أيام قليلة بعد تقدم طويلة ، لن تدمر تماما تقدمك .
|
||||
|
||||
<b>الرسوم البيانية والإحصاءات المفصلة</b>
|
||||
نرى بوضوح كيف كنت قد تحسنت عاداتك بمرور الوقت مع الرسوم البيانية الجميله ومفصلة. انتقل إلى الوراء لنرى التاريخ الكامل لعاداتك.
|
||||
|
||||
<b>جداول مرنة</b>
|
||||
تؤيد كل من العادات اليومية والعادات مع جداول أكثر تعقيدا، مثل 3 مرات كل أسبوع، مرة واحدة كل أسبوعين، أو مرة كل يومين.
|
||||
|
||||
<b>تذكير</b>
|
||||
إنشاء تذكير لكل فرد من عاداتك، في ساعة اختيار من اليوم. تحقق بسهولة، رفض أو غفوة عادتك مباشرة من الإخطار، دون الحاجة إلى فتح التطبيق.
|
||||
|
||||
<b>خالية تماما من الإعلانات و المصدر المفتوح</b>
|
||||
لا توجد على الاطلاق الإعلانات والشعارات المزعجة أو أذونات إضافية في هذا التطبيق، و سوف يكون هناك أبدا.
|
||||
|
||||
<b>الأمثل للساعات الذكية</b>
|
||||
يمكن التحقق من رسائل التذكير، رفض أو غفوة عادتك مباشرة من ساعتك الاندرويد وير.
|
||||
@@ -1 +0,0 @@
|
||||
خلق عادات جيدة وتتبع تقدمك على مر الزمن
|
||||
@@ -1 +0,0 @@
|
||||
لوب ملاحق العادة
|
||||
1
uhabits-android/src/main/play/listings/bg-rBG/title.txt
Normal file
1
uhabits-android/src/main/play/listings/bg-rBG/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Loop Следене на навици
|
||||
1
uhabits-android/src/main/play/listings/ca-rES/title.txt
Normal file
1
uhabits-android/src/main/play/listings/ca-rES/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Loop - Hàbit Rastrejador
|
||||
@@ -1,22 +0,0 @@
|
||||
Loop t'ajuda a crear i mantenir bons hàbits, permetent-te aconseguir els teus objectius a llarg termini. Els gràfics i estadístiques detallades et mostren com han millorat els teus hàbits al llarg del temps. És completament de codi obert i lliure de publicitat.
|
||||
|
||||
<b>Senzilla, bonica i moderna interfície</b>
|
||||
Loop té una interfície minimalista que és fàcil d'utilitzar i segueix les guies de "material design".
|
||||
|
||||
<b>Puntuació d'hàbit</b>
|
||||
A més de mostrar la teva ratxa actual, Loop té un algoritme avançat per a calcular la fortalesa dels teus hàbits. Cada repetició fa el teu hàbit més fort, i cada dia que fallis el farà més dèbil. Tot i això uns quants dies que fallis després d'una llarga ratxa no malmetrà completament el teu progrès.
|
||||
|
||||
<b>Gràfics i estadístiques detallades</b>
|
||||
Permet veure de forma clara com han millorat els teus hàbits al llarg del temps amb gràfics bonics i detallats. Pots anar enrera i veure també l'històric complet dels teus hàbits.
|
||||
|
||||
<b>Planificació flexible</b>
|
||||
Suporta tant hàbits diaris com hàbits amb planificacions més complexes, com per exemple 3 vegades per setmana; un cop setmana si setmana no; o un dia si i altre no.
|
||||
|
||||
<b>Recordatoris</b>
|
||||
Crea un recordatori individual per a cada hàbit, a l'hora escollida del dia. Revisa fàcilment, endarrereix o anul·la el teu hàbit directament des de la notificació, sense obrir l'app.
|
||||
|
||||
<b>Completament de codi obert i lliure de publicitat</b>
|
||||
No hi ha cap tipus de publicitat, notificacions molestes o permisos intrusius en aquesta app, i mai n'hi haurà. El codi font complet està disponible sota la llicència GPLv3.
|
||||
|
||||
<b>Optimitzat per a rellotges intel·ligents</b>
|
||||
Els recordatoris poden ser revisats, endarrerits o anul·lats directament des del teu rellotge Android Wear.
|
||||
@@ -1 +0,0 @@
|
||||
Crea bons hàbits i segueix el seu progrés al llarg del temps (sense publicitat)
|
||||
@@ -1,22 +0,0 @@
|
||||
Aplikace ti pomůže vytvořit a udržovat si prospěšné návyky a dosáhnout tak tvých dlouhodobých cílů. Detailní grafy a statistiky ti ukáží, jak se tvoje zvyky postupem času zlepšují. Vše je kompletně bez reklam a open source.
|
||||
|
||||
<b>Jednoduché, krásné a moderní prostředí</b>
|
||||
Aplikace má minimalistický design s jednoduchým použitím. Dodržuje pravidla material designu.
|
||||
|
||||
<b>Síla zvyku</b>
|
||||
Pro zobrazení aktuální úspěšné serie aplikace využívá pokročilý algoritmus, aby vypočítala sílu tvých zvyků. Každé opakování dělá tvůj zvyk silnějším a každé nedodržení ho oslabuje. Ale pár vynechaných dní po dlouhé řadě kompletně nezničí celý tvůj postup.
|
||||
|
||||
<b>Detailní grafy a statistika</b>
|
||||
Přehledně vidíš na krásných grafech, jak moc se tvoje zvyky zlepšují v průběhu času. Jeď zpět, abys viděl kompletní historii.
|
||||
|
||||
<b>Flexibilní časový plán</b>
|
||||
Podpora jak denních návyků, tak návyků s komplexnějším rozvrhem. Jako jsou 3 krát týdně, jednou za dva týdny, nebo obden.
|
||||
|
||||
<b>Upomínky</b>
|
||||
Vytvoř si individuální upomínku pro každý zvyk ve zvolený čas. Jednoduše potvrď, přeskoč nebo odlož notifikace bez nutnosti otevření aplikace.
|
||||
|
||||
<b>Kompletně bez reklam a open source</b>
|
||||
Nejsou tu naprosto žádné reklamy, otravné notifikace nebo dotěrná povolení aplikace. A nikdy nebudou. Kompletní zdrojový kód je dostupný pod GPLv3.
|
||||
|
||||
<b>Optimalizované pro chytré hodinky</b>
|
||||
Upomínky mohou být potvrzen, odloženy nebo smazány přímo z tvého zařízení s Android Wear.
|
||||
@@ -1 +0,0 @@
|
||||
Vytvoř si prospěšné návyky a sleduj jejich vývoj v průběhu času (bez reklam)
|
||||
@@ -1 +0,0 @@
|
||||
Loop - Habit Tracker
|
||||
1
uhabits-android/src/main/play/listings/da-rDK/title.txt
Normal file
1
uhabits-android/src/main/play/listings/da-rDK/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Loop Vane Tracker
|
||||
@@ -1,22 +0,0 @@
|
||||
Loop hilft dir gute Gewohnheiten anzunehmen und deine langfristigen Ziele zu erreichen. Detailierte Statistiken zeigen dir, wie du dich entwickelt hast. Es ist ohne Werbung und Open Source.
|
||||
|
||||
<b>Einfaches, schönes und modernes Oberfläche</b>
|
||||
Loop hat eine minimale Oberfläche und ist deshalb einfach zu nutzen. Es folgt dem material Design.
|
||||
|
||||
<b>Habit Punkte</b>
|
||||
Um dir deine Schwächen zu zeigen, hat Loop einen Algorithmus, um deine starken Angewohnheiten zu erkennen. Jede Wiederholung verstärkt diese und jedes Aussetzen schwächt sie. Aber ein paar Verfehlungen nach langem Durchhalten machen natürlich nicht gleich alles zu nichte.
|
||||
|
||||
<b>Statistiken</b>
|
||||
Schau dir an, wie sich deine Angewohnheiten im Laufe der Zeit gemacht haben. Schau auf die schönen Diagramme und gehe zurück im gesamten Verlauf.
|
||||
|
||||
<b>Flexible Zeiten</b>
|
||||
Unterstützt sowohl tägliche Vorgaben, als auch komplexere Pläne, woe etwa 3 mal pro Woche; ein mal in jeder anderen Woche; oder jeden anderen Tag.
|
||||
|
||||
<b>Erinnerungen</b>
|
||||
Erstelle individuelle Erinnerungen und wann diese dich benachrichtigen sollen. Kontrolliere deine Vorhaben ganz einfach und lehne sie bei Bedarf direkt ab, ohne in die App zu wechseln.
|
||||
|
||||
<b>Komplett werbefrei und Open Source</b>
|
||||
Es gibt absolut keine Werbung, nervende Einblendungen oder merkwürdige Berechtigungen in dieser App und das wird auch so bleiben. Der komplette Quellcode steht unter der GPLv3.
|
||||
|
||||
<b>Optimiert für Smartwatches</b>
|
||||
Erinnerungen können direkt von der Android Wear watch kontrolliert, pausiert, oder verschoben werden.
|
||||
@@ -1 +0,0 @@
|
||||
Nimm gute Gewohnheiten an und verfolge deinen Fortschritt (ohne Werbung)
|
||||
1
uhabits-android/src/main/play/listings/de-rDE/title.txt
Normal file
1
uhabits-android/src/main/play/listings/de-rDE/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Loop - Gewohnheiten Tracking
|
||||
1
uhabits-android/src/main/play/listings/el-rGR/title.txt
Normal file
1
uhabits-android/src/main/play/listings/el-rGR/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Loop - Καταγραφή Συνηθειών
|
||||
@@ -1,22 +0,0 @@
|
||||
Loop te ayuda a crear y mantener buenos hábitos, permitiéndote alcanzar tus metas a largo plazo. Detallados gráficos y estadísticas muestran como tus hábitos mejoran con el tiempo. No existe ningún anuncio y es de código abierto.
|
||||
|
||||
<b>Una interfaz simple, bella y moderna</b>
|
||||
Loop tiene una interfaz minimalista que es fácil de usar y sigue los principios del material design.
|
||||
|
||||
<b>Puntuación del hábito</b>
|
||||
Además de mostrar tu racha actual, Loop tiene un algoritmo avanzado para calcular la fuerza de tus hábitos. Cada repetición hace tu hábito más fuerte y cada día fallido lo hace más débil. Sin embargo, unos pocos días después de una larga racha no destruirán completamente todo tu progreso.
|
||||
|
||||
<b>Detallados gráficos y estadísticas</b>
|
||||
Observa claramente como tus hábitos han mejorado con el tiempo con bellos y detallados gráficos. Ve hacia atrás para ver el historial completo del hábito.
|
||||
|
||||
<b>Horarios flexibles</b>
|
||||
Soporta hábitos diarios y hábitos con repeticiones más complejas, como 3 veces por semana; una vez cada varias semanas; o cada día.
|
||||
|
||||
<b>Recordatorios</b>
|
||||
Crea recordatorios individuales para cada hábito a una hora determinada del día. Fácilmente marcables, descartables o posponibles directamente desde la notificación, sin abrir la app.
|
||||
|
||||
<b>Completamente sin anuncios y de código abierto</b>
|
||||
No existe ningún tipo de publicidad, notificaciones molestas o permisos intrusivos, y nunca los habrá. Todo el código está disponible bajo GPLv3.
|
||||
|
||||
<b>Optimizado para smartwatches</b>
|
||||
Los recordatorios se pueden marcar, posponer o descartar directamente desde tu reloj Android Wear.
|
||||
@@ -1 +0,0 @@
|
||||
Crea buenos hábitos y haz un seguimiento de su progreso a lo largo del tiempo
|
||||
@@ -1 +0,0 @@
|
||||
Loop - Analizador de Hábitos
|
||||
1
uhabits-android/src/main/play/listings/es-rES/title.txt
Normal file
1
uhabits-android/src/main/play/listings/es-rES/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Loop Analizador de Hábitos
|
||||
@@ -1,22 +0,0 @@
|
||||
Loop vous aide à créer et maintenir de bonnes habitudes, permettant de réussir vos objectifs à long terme. Des graphiques détaillés et des statistiques vous montrent comment vos habitudes s’améliorent au fil du temps. C'est totalement sans pub et open source.
|
||||
|
||||
<b>Simple, beau avec une interface moderne</b>
|
||||
Loop a une interface minimaliste, facile à utiliser et qui suit les règles de material design.
|
||||
|
||||
<b>Score d'habitude</b>
|
||||
En plus de montrer votre série en cours, Loop a un algorithme pour calculer la force de vos habitudes. Chaque jours réussis augmente la force de l'habitude chaque jours ratés le rend plus faible. Cependant, quelques jours ratés après une longue série ne détruiront pas entièrement votre progrès.
|
||||
|
||||
<b>Graphiques détaillés et statistiques</b>
|
||||
Observez clairement comment vos habitudes s’améliorent au fil du temps avec de beaux graphiques détaillés. Défilez vers les jours passés pour voir l'historique complet de vos habitudes.
|
||||
|
||||
<b>Calendrier flexible</b>
|
||||
Supporte les habitudes quotidiennes et celles avec un calendrier plus complexes, comme 3 fois par semaine, une fois par semaine ou un jour sur deux.
|
||||
|
||||
<b>Rappel</b>
|
||||
Créez un rappel propre pour chaque habitude, à une heure choise de la journée. Cochez, supprimez ou réglez à plus tard votre habitude directement à partir de la notification, sans l'ouvrir l'application.
|
||||
|
||||
<b>Entièrement sans pub et open-source</b>
|
||||
Il n'y a pas de publicités, de notifications embêtantes ou de permissions intrusives avec cette application, et il n'y en aura jamais. L'ensemble du code source est disponible sous GPLv3.
|
||||
|
||||
<b>Optimisée pour les montres android</b>
|
||||
Les rappels peuvent être cochés, reportés ou supprimés directement à partir de votre montre Android
|
||||
@@ -1 +0,0 @@
|
||||
Créez des bonnes habitudes et suivez leurs avancées au fil du temps (sans pub)
|
||||
1
uhabits-android/src/main/play/listings/hi-rIN/title.txt
Normal file
1
uhabits-android/src/main/play/listings/hi-rIN/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Loop - आदत पर नजर
|
||||
1
uhabits-android/src/main/play/listings/hr-rHR/title.txt
Normal file
1
uhabits-android/src/main/play/listings/hr-rHR/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Loop Habit Tracker
|
||||
@@ -1,21 +0,0 @@
|
||||
Loop Vam pomaže stvoriti i održavati dobre navike kako biste ostvarili svoje dugoročne ciljeve. Detaljni grafikoni Vam pomažu kako biste bolje pratili kako napreduju Vaše navike. Loop je open source aplikacija i ne sadrži nikakav oblik reklama.
|
||||
|
||||
<b>Jednostavno, lijepo i moderno sučelje.</b>
|
||||
Loop sadrži minimalistično sučelje jednostavno za korištenje koje prati smjernice Material Design-a.
|
||||
|
||||
<b>Rezultat navike</b>
|
||||
Osim prikazivanja trenutnog niza, Loop ima napredni algoritam za računanje snage vaših navika. Svako ponavljanje jača vašu naviku, a svaki propušteni dan ju čini slabijom. Međutim, par propuštenih dana nakon dugačkog niza neće u potpunosti uništiti cjelokupni napredak.
|
||||
|
||||
<b>Detaljni grafovi i statistike</b>
|
||||
Jasno vidite kako su se vaše navike poboljšale kroz vrijeme u prekrasnim i detaljnim grafovima. Kliznite natrag kako bi prikazali kompletnu povijest vaših navika.
|
||||
|
||||
<b>Fleksibilno planiranje</b>
|
||||
Podržava i dnevne navike i navike sa složenijim planiranjem, kao npr. 3 puta svakog tjedna; jednom svaki drugi tjedan; ili svaki drugi dan.
|
||||
|
||||
<b>Podsjetnici</b>
|
||||
Stvorite individualne podsjetnike za svaku naviku, u određenom satu u danu. Lako provjeravajte, odbacite i odgodite vašu naviku direktno iz obavijesti, bez otvaranja aplikacije.
|
||||
|
||||
<b>Optimizirano za pametne satove</b>
|
||||
Podsjetnici se mogu provjeravati, odgoditi ili odbaciti direktno sa vašeg Android Wear sata.
|
||||
|
||||
<b>Potpuno bez oglasa i otvorenog izvora koda</b>
|
||||
@@ -1 +0,0 @@
|
||||
Stvorite dobre navike i pratite njihov napredak (bez reklama)
|
||||
1
uhabits-android/src/main/play/listings/hu-rHU/title.txt
Normal file
1
uhabits-android/src/main/play/listings/hu-rHU/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Loop Szokásrögzítő
|
||||
1
uhabits-android/src/main/play/listings/hy-rAM/title.txt
Normal file
1
uhabits-android/src/main/play/listings/hy-rAM/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Loop Habit Tracker
|
||||
@@ -1,23 +0,0 @@
|
||||
Loop membantu Anda untuk membuat dan mengelola Kebiasaan baik, memungkinkan Anda mencapai gol jangka panjang. Grafik detail dan statistik menampilkan perkembangan Kebiasaan Anda dari waktu ke waktu. Aplikasi ini bebas iklan dan open-source.
|
||||
|
||||
<b>Sederhana, minimalis dengan balutan antarmuka yang modern</b>
|
||||
Loop memiliki tampilan minimalis yang mudah digunakan dan mengikuti panduan material design.
|
||||
|
||||
<b>Skor Kebiasaan</b>
|
||||
Algoritma Loop akan menghitung kekuatan Kebiasaan Anda. Setiap kali Anda melakukan pengulangan maka skor Anda akan bertambah, sebaliknya jika Anda tidak mengerjakan Kebiasaan maka nilai akan dikurangi. Beberapa hari yang terlewat tidak akan menghapus seluruh perkembangan Kebiasaan Anda.
|
||||
|
||||
<b>Detail Grafik dan Statistik</b>
|
||||
Lihat perkembangan Kebiasaan dari waktu ke waktu dengan tampilan yang menarik dan detail. Geser ke bawah untuk melihat seluruh catatan Kebiasaan Anda.
|
||||
|
||||
<b>Pengaturan jadwal fleksibel</b>
|
||||
Mendukung kebiasaan harian dan kebiasaan dengan penjadwalan yang kompleks, seperti 3 kali dalam setiap minggu; 2 minggu sekali; hingga 2 hari sekali.
|
||||
|
||||
<b>Pengingat</b>
|
||||
Anda dapat membuat pengingat untuk setiap Kebiasaan, dengan waktu yang telah Anda tentukan setiap harinya. Mudah untuk di-cek, batalkan ataupun tunda melalui panel notifikasi tanpa perlu membuka aplikasi.
|
||||
|
||||
<b>Bebas dari iklan dan open-source</b>
|
||||
Tidak ada iklan, notifikasi menyebalkan dan juga hak akses yang tidak dibutuhkan.
|
||||
Kode aplikasi tersedia dengan lisensi GPLv3
|
||||
|
||||
<b>Mendukung Smartwatch</b>
|
||||
Anda dapat dengan mudah mengecek, menunda ataupun membatalkan pengingat melalui smartwatch Anda.
|
||||
@@ -1 +0,0 @@
|
||||
Buat Kebiasaan baik dan catat perkembangannya setiap waktu (bebas iklan)
|
||||
1
uhabits-android/src/main/play/listings/in-rID/title.txt
Normal file
1
uhabits-android/src/main/play/listings/in-rID/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Loop Habit Tracker
|
||||
@@ -1,18 +0,0 @@
|
||||
Loop ti aiuta a creare e mantenere buone abitudini, permettendoti di raggiungere i tuoi obbiettivi a lungo termine. Grafici dettagliati e le statistiche ti mostrano come le tue abitudini sono migliorate durante il tempo. E' completamente senza pubblicità ed opensource.
|
||||
|
||||
<b>Interfaccia semplice e moderna</b> Loop ha un'interfaccia minimale che è semplice da usare e segue le linee guida del Material Design
|
||||
|
||||
<b>Forza dell'abitudine</b>
|
||||
In aggiunta al traguardo attuale, Loop ha un algoritmo avanzato per calcolare la forza delle tue abitudini. Ogni ripetizione la rafforza, mentre ogni giorno mancato la indebolisce. Pochi giorni mancati dopo una lunga serie però non vanificherà completamente il tuo progresso totale.
|
||||
|
||||
<b>Grafici dettagliati e statistiche</b> Visualizza in modo semplice come le tue abitudini sono migliorate nel tempo con grafici dettagliati. Scorri indietro per vedere la cronologia completa delle tue abitudini.
|
||||
|
||||
<b>Programmi flessibili</b> Supporto per abitudini sia giornaliere che con organizzazioni più complesse, come 3 volte alla settimana; una volta ogni 2 settimane; ogni due giorni...
|
||||
|
||||
<b>Promemoria</b>
|
||||
Crea un promemoria per ogni abitudine, ad una specificata ora del giorno. Completa, ritarda o ignora il promemoria direttamente dalla notifica, senza aprire l'app.
|
||||
|
||||
<b>Completamente gratuito ed opensource</b>
|
||||
Non ci sono pubblicità, notifiche invasive o permessi intrusivi e mai ce ne saranno. Il codice sorgente completo è disponibile sotto licenza GPLv3.
|
||||
|
||||
<b>Ottimizzata per gli smartwatch</b> I promemoria possono essere completati, ritardati o ignorati direttamente dal tuo orologio Android Wear.
|
||||
@@ -1 +0,0 @@
|
||||
Acquisisci nuove abitudini e traccia il tuo progresso (senza pubblicità)
|
||||
1
uhabits-android/src/main/play/listings/it-rIT/title.txt
Normal file
1
uhabits-android/src/main/play/listings/it-rIT/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Loop - Tracciatore di Abitudine
|
||||
1
uhabits-android/src/main/play/listings/iw-rIL/title.txt
Normal file
1
uhabits-android/src/main/play/listings/iw-rIL/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Loop Habit Tracker
|
||||
@@ -1,22 +0,0 @@
|
||||
ループは、良い習慣を作って、維持するのに役立ちます。あなたの長期的な目標を達成することができます。詳細なグラフや統計情報が、時間をかけてあなたの習慣が改善された様子を示します。完全に広告フリーでオープンソースです。
|
||||
|
||||
<b>シンプルで、美しく、モダンなインターフェース</b>
|
||||
ループは、使いやすく、マテリアルデザインのガイドラインに従った、最小限のインターフェースです。
|
||||
|
||||
<b>習慣のスコア</b>
|
||||
ループは、現在の連続記録を示すことに加えて、習慣の強さを計算するための高度なアルゴリズムを持っています。繰り返しがあなたの習慣を強くし、忘れた日は弱くなります。長い連続記録の後に、少し忘れた日があっても、全体の進捗状況を完全に失うわけではありません。
|
||||
|
||||
<b>詳細なグラフと統計情報</b>
|
||||
美しくて詳細なグラフで、あなたの習慣が時間をかけて改善された様子がはっきりわかります。スクロールで戻ると、あなたの習慣の完全な履歴を参照することができます。
|
||||
|
||||
<b>柔軟なスケジュール</b>
|
||||
毎日の習慣と、毎週 3 回、隔週に 1 回、または一日おきなどの、より複雑なスケジュールの習慣の両方をサポートします。
|
||||
|
||||
<b>リマインダー</b>
|
||||
1 日の選択した時間で、それぞれの習慣に個々のリマインダーを作成します。アプリを開かずに、通知から直接、簡単に習慣をチェック、解除、またはスヌーズすることができます。
|
||||
|
||||
<b>完全に広告フリーでオープンソース</b>
|
||||
このアプリは、広告、迷惑な通知、押し付けがましいアクセス許可は全くありませんし、そうなることもありません。GPLv3 の下で全てのソースコードが利用可能です。
|
||||
|
||||
<b>スマートウォッチの最適化</b>
|
||||
Android Wear 端末から直接、リマインダーをチェック、スヌーズ、解除することができます。
|
||||
@@ -1 +0,0 @@
|
||||
良い習慣を作って、時間をかけてその進捗状況を追跡します (広告なし)
|
||||
@@ -1,20 +0,0 @@
|
||||
Loop은 습관을 만들고 유지하도록 도와주어, 장기간 목표를 달성하도록 합니다. 그래프와 통계를 보고 습관이 만들어지는 과정을 보세요. 오프소스이고, 무광고입니다.
|
||||
|
||||
<b>아름답고 심플한 디자인</b>
|
||||
Loop은 Material디자인을 따라 사용이 편합니다.
|
||||
|
||||
<b>습관 포인트</b>
|
||||
얼마나 오랫동안 습관을 유지했는지 보여줌과 동시에, Loop은 특화된 알고리듬으로 습관의 견고함을 계산합니다. 반복할 수록 강해지고, 빠뜨린 날들이 많아질 수록 포인트는 낮아집니다. 하지만, 오랫동안 유지된 습관은 몇 일을 빠뜨렸다고 해서 포인트가 급강하하지는 않습니다.
|
||||
|
||||
<b>그래프와 통계</b>
|
||||
습관이 어떤 과정을 거쳐서 완성되는지 그래프를 보면 알 수 있습니다.
|
||||
스크롤해서 장기간의 과정을 볼 수 있습니다.
|
||||
|
||||
<b>여러 종류의 스케줄 조정가능</b>
|
||||
매일 습관, 혹은 일주일에 3번; 2주에 한번; 같은 여러 종류의 스케줄을 만들 수 있습니다.
|
||||
|
||||
<b>오픈소스, 무광고</b>
|
||||
광고, 귀찮은 스팸, 비정상적인 정보요구는 없습니다, 앞으로 쭉. 모든 소스코드는 GPLv3로 라이센스되었습니다.
|
||||
|
||||
<b>스마트시계에 최적화</B>
|
||||
안드로이드 시계에서 바로 알림을 확인, 미루기 혹은 무시할 수 있습니다.
|
||||
@@ -1 +0,0 @@
|
||||
좋은 습관을 만들고 관리하세요. (광고 없음)
|
||||
1
uhabits-android/src/main/play/listings/nl-rNL/title.txt
Normal file
1
uhabits-android/src/main/play/listings/nl-rNL/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Loop Gewoonte tracker
|
||||
1
uhabits-android/src/main/play/listings/no-rNO/title.txt
Normal file
1
uhabits-android/src/main/play/listings/no-rNO/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Loop Vaneoversikt
|
||||
@@ -1,22 +0,0 @@
|
||||
Loop pozwala Ci na tworzenie i utrzymywanie dobrych nawyków, pozwala na Ci na osiągnięcie Twoich długoterminowych celów. Szczegółowe grafiki i statystyki pozwalają na zobaczenie jak Twoje nawyki polepszyły się. Jest całkowicie wolna od reklam i open source.
|
||||
|
||||
<b>Prosty, piękny i nowoczesny interfejs</b>
|
||||
Loop posiada minimalistyczny interfejs, który jest prosty do użycia i przestrzega zasad material design.
|
||||
|
||||
<b>Punkty nawyku</b>
|
||||
Oprócz pokazywania Twojej aktualnej serii, Loop posiada zaawansowany algorytm obliczania siły Twoich nawyków. Każde powtórzenie nawyku czyni go silniejszymi a każdy opuszczony dzień słabszym. Jednakże kilka opuszczonych dni po dłuższej serii nie zrujnuje Twojego całego postępu.
|
||||
|
||||
<b>Szczegółowe grafiki i statystyki</b>
|
||||
Zobacz jak Twoje nawyki ulepszają się poprzez piękne i szczegółowe wykresy. Przewiń do tyłu aby zobaczyć pełną historię Twoich nawyków.
|
||||
|
||||
<b>Elastyczne plany</b>
|
||||
Wspiera zarówno codzienne nawyki jak i nawyki z bardziej złożonym planem, jakie jak 3 razy co tydzień; raz w ciągu innego tygodnia; lub każdego innego dnia.
|
||||
|
||||
<b>Przypomnienia</b>
|
||||
Utwórz indywidualne przypomnienia dla każdego nawyku, w określonej godzinie dnia. Łatwo sprawdź, usuń i uśpij powiadomienia o nawyku bezpośrednio z powiadomienia, bez otwierania aplikacji.
|
||||
|
||||
<b>Całkowicie bez reklam i open source</b>
|
||||
Absolutnie nie ma żadnych reklam, denerwujących powiadomień czy szpiegujących uprawnień w tej aplikacji i nigdy nie będzie. Cały kod źródłowy jest dostępny pod licencją GPLv3.
|
||||
|
||||
<b>Zoptymalizowana pod smartwatche</b>
|
||||
Przypomnienia można sprawdzić, uśpić czy usunąć bezpośrednio z twojego zegarka Android Wear.
|
||||
@@ -1 +0,0 @@
|
||||
Twórz dobre nawyki i śledź ich postęp (bez reklam)
|
||||
@@ -1,19 +0,0 @@
|
||||
Loop é um aplicativo que te ajuda a criar e manter bons hábitos, permitindo que você alcance os seus objetivos de longo prazo. Gráficos e estatísticas mostram em detalhe como os seus hábitos progrediram ao passar do tempo. Este aplicativo não tem nenhuma propaganda e é Open Source.
|
||||
|
||||
<b>Interface bonita, moderna e simples</b>
|
||||
Loop tem uma interface minimalista bem simples de se usar, e segue o novo visual Material Design.
|
||||
|
||||
<b>Estabilidade dos hábitos</b>
|
||||
Além de mostrar quantos dias seguidos você praticou cada rotina, Loop também conta com uma fórmula mais avançada para calcular a estabilidade dos seus hábitos. Cada repetição melhora a estabilidade do hábito, e cada dia de folga piora a estabilidade. Alguns dias de folga após um longo período perfeito, no entanto, não destruirá por completo o seu progresso.
|
||||
|
||||
<b>Gráficos e estatísticas detalhadas</b>
|
||||
Veja claramente como os seus hábitos estão progredindo com o passar do tempo, através de bonitos e detalhados diagramas. O histórico completo de cada hábito fica arquivado para sempre.
|
||||
|
||||
<b>Rotina flexível</b>
|
||||
Loop aceita tanto hábitos diários, quanto hábitos com rotinas mais complexas, como 3 vezes por semana; uma vez a cada duas semanas; ou uma vez a cada dois dias.
|
||||
|
||||
<b>Lembretes</b>
|
||||
É possível criar lembretes individuais para cada hábito, na hora do dia em que você escolher. Marque como completado diretamente da barra de notificação, sem precisar abrir o aplicativo.
|
||||
|
||||
<b>Completamente sem propagandas e Open Source</b>
|
||||
Não há nenhum anúncio neste aplicativo, nem notificações chatas ou permissões intrusivas; e será assim para sempre. O código fonte completo está disponível sob a licença livre GPLv3.
|
||||
@@ -1 +0,0 @@
|
||||
Forme bons hábitos e acompanhe seu progresso ao passar do tempo (sem anúncios)
|
||||
1
uhabits-android/src/main/play/listings/pt-rPT/title.txt
Normal file
1
uhabits-android/src/main/play/listings/pt-rPT/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Loop - Acompanhador de Hábitos
|
||||
1
uhabits-android/src/main/play/listings/ro-rRO/title.txt
Normal file
1
uhabits-android/src/main/play/listings/ro-rRO/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Loop Habit Tracker
|
||||
@@ -1,22 +0,0 @@
|
||||
"Трекер привычек Loop" поможет вам завести и поддерживать полезные привычки, позволяя достичь долгосрочных целей. Детализированные диаграммы и статистика покажут, как ваши привычки закрепились со временем. Приложение не содержит рекламы и является ПО с открытым исходным кодом.
|
||||
|
||||
<b>Простой, красивый и современный интерфейс</b>
|
||||
У приложения минималистичный интерфейс, который лёгок в использовании и придерживается руководства по Material Design.
|
||||
|
||||
<b>Оценка привычек</b>
|
||||
В дополнение к отображению текущего рекорда повторений, приложение имеет передовой алгоритм расчёта "силы" ваших привычек. Каждое повторение делает вашу привычку "сильнее", а каждый пропуск "слабее". Несколько пропущенных дней после долгих успешных повторений, однако, не загубят ваши успехи полностью.
|
||||
|
||||
<b>Детализированные диаграммы и статистика</b>
|
||||
При помощи красивых и детализированных диаграмм вы с лёгкостью можете просмотреть, как ваши привычки закрепились со временем. Просмотрите полную историю ваших привычек, пролистав её.
|
||||
|
||||
<b>Гибкий график</b>
|
||||
Поддерживаются как ежедневные привычки, так и привычки с более сложным графиком. Например: 3 раза в неделю; один раз каждую неделю; через день.
|
||||
|
||||
<b>Напоминания</b>
|
||||
Создавайте отдельные для каждой привычки напоминания в выбранное время дня. С лёгкостью отмечайте, пропускайте или откладывайте выполнение привычки прямо из уведомления - без открытия приложения.
|
||||
|
||||
<b>Полное отсутствие рекламы и открытый исходный код</b>
|
||||
В приложении совершенно нет рекламы, назойливых уведомлений или лишних разрешений. И их никогда не будет. Весь исходный код доступен под лицензией GPLv3.
|
||||
|
||||
<b>Оптимизировано для "умных" часов</b>
|
||||
Напоминания могут быть отмечены, отложены или пропущены прямо с ваших часов Android Wear.
|
||||
@@ -1 +0,0 @@
|
||||
Заводите полезные привычки и отслеживайте свои успехи (без рекламы)
|
||||
1
uhabits-android/src/main/play/listings/sk-rSK/title.txt
Normal file
1
uhabits-android/src/main/play/listings/sk-rSK/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Loop Habit Tracker
|
||||
@@ -1,22 +0,0 @@
|
||||
Loop vam pomaga ustvariti in vzdrževati dobre navade, kar vam omogoča, da bi dosegli svoje dolgoročne cilje. Podrobni grafi in statistike vam pokažejo kako so se vaše navade skozi čas izboljšale. Je popolnoma brez oglasov in odprtokodno.
|
||||
|
||||
<b>Enostaven, lep in sodoben vmesnik</b>
|
||||
Loop ima minimalističen vmesnik, ki je preprost za uporabo in sledi smernicam Material design.
|
||||
|
||||
<b>Ocena navade</b>
|
||||
Poleg tega, da vam prikazuje vaše trenutne serije, ima Loop napreden algoritem za izračun moči vaših navad. Vsaka ponovitev naredi vašo navado močnejšo in vsak zgrešen dan jo naredi šibkejšo. Nekaj zgrešenih dni po dolgi seriji, pa vendar ne bo popolnoma uničilo vašega celotnega napredka.
|
||||
|
||||
<b>Podrobni grafi in statistike</b>
|
||||
Z lepimi in natančnimi grafi, jasno poglejte, kako so se skozi čas vaše navade izboljšale. Da boste videli celotno zgodovino svojih navad se pomaknite nazaj.
|
||||
|
||||
<b>Prilagodljivi urniki</b>
|
||||
Podpira tako vsakdanje navade in navade z bolj zapletenimi urniki, kot so 3-krat na teden; enkrat vsak drugi teden; ali vsak drugi dan.
|
||||
|
||||
<b>Opomniki</b>
|
||||
Ustvari individualni opomnik za vsako navado, pri izbrani uri dneva. Enostavno preverite, zavrzite ali dajte v dremež svoje navade neposredno iz obvestila, ne da bi odprli aplikacijo.
|
||||
|
||||
<b>Popolnoma brez oglasov in odprtokodno</b>
|
||||
V tej aplikaciji ni absolutno nobene reklame, sitnih obvestil ali vsiljivih dovoljenj in jih tudi nikoli ne bo. Celotna izvorna koda je na razpolago pod GPLv3.
|
||||
|
||||
<b>Optimirano za pametne ure</b>
|
||||
Opomnike lahko preverite, jih date v dremež ali pa zavrzite neposredno iz vaše Android Wear ure.
|
||||
@@ -1 +0,0 @@
|
||||
Ustvarite dobre navade in spremljajte njihov napredek skozi čas (brez reklam)
|
||||
1
uhabits-android/src/main/play/listings/sr-rCS/title.txt
Normal file
1
uhabits-android/src/main/play/listings/sr-rCS/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
„Loop“ — praćenje navika
|
||||
1
uhabits-android/src/main/play/listings/sr-rSP/title.txt
Normal file
1
uhabits-android/src/main/play/listings/sr-rSP/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
„Loop“ — праћење навика
|
||||
@@ -1,22 +0,0 @@
|
||||
Loop вам помаже да створите и одржавате здраве навике, самим тим и да достигнете дугорочне циљеве. Детаљна статистика и графикони показују вам колико су се ваше навике временом побољшале. Апликација је отвореног кода и не садржи огласе.
|
||||
|
||||
<b>Једноставан, леп и модеран дизајн</b>
|
||||
Loop има минималистички интерфејс који је једноставан за коришћење и прати смернице за материјални дизајн.
|
||||
|
||||
<b>Оцена навике</b>
|
||||
Поред приказа тренутног низа, Loop поседује напредни алгоритам за израчунавање снаге ваших навика. Ваша навика са сваким понављањем постаје јача, а са сваким пропуштеним даном слабија. Неколико пропуштених дана након дугог низа, пак, неће у потпуности упропастити ваш резултат.
|
||||
|
||||
<b>Детаљни графикони и статистика</b>
|
||||
Пратите свој напредак уз лепе и детаљне графиконе. Вратите се уназад да бисте видели целу историју навика.
|
||||
|
||||
<b>Флексибилни распоред</b>
|
||||
Подржава како дневне навике, тако и оне са сложенијим понављањем (трипут недељно, једанпут сваке друге недеље, сваки други дан и сл.).
|
||||
|
||||
<b>Подсетници</b>
|
||||
Направите појединачни подсетник за сваку навику у жељено доба дана. С лакоћом маркирајте, одложите или откажите навику директно из обавештења, без отварања апликације.
|
||||
|
||||
<b>У потпуности отвореног кода и без огласа</b>
|
||||
Апликација не садржи никакве огласе, досадна обавештења или наметљиве дозволе; никада и неће. Изворни кôд је у целости доступан под лиценцом GPLv3.
|
||||
|
||||
<b>Оптимизовано за паметне сатове</b>
|
||||
Навике можете маркирати, одложити или отказати директно помоћу Android Wear сата.
|
||||
@@ -1 +0,0 @@
|
||||
Створите здраве навике и пратите напредак (без огласа).
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user