mirror of
https://github.com/iSoron/uhabits.git
synced 2025-12-11 11:38:52 -06:00
Compare commits
78 Commits
f70d33878c
...
v2.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
d10538e720
|
|||
|
02e9e2384e
|
|||
|
|
b627ff4413
|
||
|
|
0683ea43f4
|
||
|
08f77a5cae
|
|||
|
27df792775
|
|||
|
|
800f92f255
|
||
|
|
e06ed3ed7d | ||
|
|
3bb119c6ed | ||
|
|
0762699a86 | ||
|
|
8837326d44 | ||
|
|
481a3d5784 | ||
|
8eff782f54
|
|||
|
c590734c42
|
|||
|
7e7f68282b
|
|||
|
4bd5cee17b
|
|||
|
b2421dc8b1
|
|||
|
93363bff96
|
|||
|
d01044b203
|
|||
|
70fe513e52
|
|||
|
dee93fde8f
|
|||
|
f0ce05e06e
|
|||
|
4975ba2752
|
|||
|
ed8c60e52f
|
|||
|
|
7735247521 | ||
|
|
b32a6ad1b8 | ||
|
|
21a512ae71 | ||
|
|
b0944d3f1c | ||
|
|
8a9b719c50 | ||
|
|
ef7454ae75 | ||
|
|
b51f6abfce | ||
|
|
a013635224 | ||
|
|
a9f028a34b | ||
|
|
3b0fba12f5 | ||
|
|
4139f09fb7 | ||
|
|
248ff2ec62 | ||
|
|
cc7178eb21 | ||
|
6116ef9450
|
|||
|
8801960615
|
|||
|
b0a4284b66
|
|||
|
|
334dabb407 | ||
|
|
b2fc79a3ab | ||
|
|
f0e8643e6b | ||
|
|
5cd616f967 | ||
|
|
b53ef758ec | ||
|
|
5add03bf23 | ||
|
|
b465ee588b | ||
|
|
b9253d41ea | ||
|
|
ca0a9dd85f | ||
|
|
9951525cbe | ||
|
|
98b2c9cce2 | ||
|
|
19de2a2d1c | ||
|
|
80f783b669 | ||
|
|
761fe59c5e | ||
|
|
f58d8a52ff | ||
|
|
f4e5b68258 | ||
|
|
5df3ee0d61 | ||
|
|
9f66a27b82 | ||
|
|
4bbb20e18e | ||
|
|
4f9ab6d263 | ||
|
|
af21fd25db | ||
|
|
35097e1263 | ||
|
|
8e4274d923 | ||
|
|
12649141b1 | ||
|
|
0526d37fbd | ||
|
|
b083899ec8 | ||
|
|
6b793c7c16 | ||
|
|
11ca993a75 | ||
|
|
7348ddeffa | ||
|
88df8d2552
|
|||
|
d4f4f8b4a9
|
|||
|
9ca1aa911a
|
|||
|
ba57ebad31
|
|||
|
|
e4d2c93a1d | ||
|
|
07065a60ad | ||
|
|
916cd76be1 | ||
|
8b55ffb147
|
|||
|
727e88b7b1
|
5
.github/dependabot.yml
vendored
5
.github/dependabot.yml
vendored
@@ -4,3 +4,8 @@ updates:
|
|||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "monthly"
|
interval: "monthly"
|
||||||
|
open-pull-requests-limit: 10
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "monthly"
|
||||||
|
|||||||
6
.github/workflows/main.yml
vendored
6
.github/workflows/main.yml
vendored
@@ -12,17 +12,17 @@ jobs:
|
|||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
steps:
|
steps:
|
||||||
- name: Check out source code
|
- name: Check out source code
|
||||||
uses: actions/checkout@v1
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Build project
|
- name: Build project
|
||||||
run: ./build.sh build
|
run: ./build.sh build
|
||||||
|
|
||||||
- name: Run Android tests
|
- name: Run Android tests
|
||||||
run: ./build.sh android-tests-parallel 28 29 30 31 32 33
|
run: ./build.sh android-tests-parallel 28 29 30 32 33 34
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: build
|
name: build
|
||||||
path: |
|
path: |
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -17,3 +17,4 @@ node_modules
|
|||||||
*xcuserdata*
|
*xcuserdata*
|
||||||
*.sketch
|
*.sketch
|
||||||
crowdin.yml
|
crowdin.yml
|
||||||
|
kotlin-js-store
|
||||||
|
|||||||
17
CHANGELOG.md
17
CHANGELOG.md
@@ -1,5 +1,22 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [2.2.0] -- 2024-01-30
|
||||||
|
### Added
|
||||||
|
- Add support for Android 14 (@iSoron, @hiqua)
|
||||||
|
- Allow user to change app language (@leondzn)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Implement workaround to make notifications non-dismissible in Android 14 (@iSoron, #1872)
|
||||||
|
- Fix splash screen background color in dark mode (@SIKV, #1888)
|
||||||
|
|
||||||
|
## [2.1.3] -- 2023-08-28
|
||||||
|
### Fixed
|
||||||
|
- Use text input on Samsung devices (@iSoron, #1719)
|
||||||
|
- Prevent crash if alarm permission is revoked (@iSoron)
|
||||||
|
- Adjust widget colors (@iSoron)
|
||||||
|
- Fix bug preventing screens from updating at midnight (@iSoron)
|
||||||
|
- Fix skip button in locales that use comma instead of dot (@iSoron, #1721)
|
||||||
|
|
||||||
## [2.1.2] -- 2023-05-26
|
## [2.1.2] -- 2023-05-26
|
||||||
### Fixed
|
### Fixed
|
||||||
- Fix bug that caused widget to enter checkmark on wrong date (@iSoron, #1541)
|
- Fix bug that caused widget to enter checkmark on wrong date (@iSoron, #1541)
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
plugins {
|
plugins {
|
||||||
val kotlinVersion = "1.7.21"
|
val kotlinVersion = "1.9.21"
|
||||||
id("com.android.application") version ("7.3.1") apply (false)
|
id("com.android.application") version "8.1.4" apply (false)
|
||||||
id("org.jetbrains.kotlin.android") version kotlinVersion apply (false)
|
id("org.jetbrains.kotlin.android") version kotlinVersion apply (false)
|
||||||
id("org.jetbrains.kotlin.kapt") 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.jetbrains.kotlin.multiplatform") version kotlinVersion apply (false)
|
||||||
id("org.jlleitschuh.gradle.ktlint") version "11.0.0"
|
id("org.jlleitschuh.gradle.ktlint") version "11.6.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply {
|
apply {
|
||||||
|
|||||||
2
build.sh
2
build.sh
@@ -181,7 +181,7 @@ android_test() {
|
|||||||
OUT_INSTRUMENT=${ANDROID_OUTPUTS_DIR}/instrument-${API}.txt
|
OUT_INSTRUMENT=${ANDROID_OUTPUTS_DIR}/instrument-${API}.txt
|
||||||
OUT_LOGCAT=${ANDROID_OUTPUTS_DIR}/logcat-${API}.txt
|
OUT_LOGCAT=${ANDROID_OUTPUTS_DIR}/logcat-${API}.txt
|
||||||
FAILED_TESTS=""
|
FAILED_TESTS=""
|
||||||
for i in {1..5}; do
|
for i in {1..10}; do
|
||||||
log_info "Running $size instrumented tests (attempt $i)..."
|
log_info "Running $size instrumented tests (attempt $i)..."
|
||||||
$ADB shell am instrument \
|
$ADB shell am instrument \
|
||||||
-r -e coverage true -e size "$size" $FAILED_TESTS \
|
-r -e coverage true -e size "$size" $FAILED_TESTS \
|
||||||
|
|||||||
@@ -3,3 +3,6 @@ org.gradle.daemon=true
|
|||||||
org.gradle.jvmargs=-Xms2048m -Xmx2048m
|
org.gradle.jvmargs=-Xms2048m -Xmx2048m
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
|
android.defaults.buildfeatures.buildconfig=true
|
||||||
|
android.nonTransitiveRClass=false
|
||||||
|
android.nonFinalResIds=false
|
||||||
|
|||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
@@ -18,11 +18,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.github.triplet.play") version "3.7.0"
|
id("com.github.triplet.play") version "3.8.6"
|
||||||
id("com.android.application")
|
id("com.android.application") version "8.1.4"
|
||||||
id("org.jetbrains.kotlin.android")
|
id("org.jetbrains.kotlin.android")
|
||||||
id("org.jetbrains.kotlin.kapt")
|
id("org.jetbrains.kotlin.kapt")
|
||||||
id("org.jetbrains.kotlin.android.extensions")
|
|
||||||
id("org.jlleitschuh.gradle.ktlint")
|
id("org.jlleitschuh.gradle.ktlint")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,15 +29,27 @@ tasks.compileLint {
|
|||||||
dependsOn("updateTranslators")
|
dependsOn("updateTranslators")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Added on top of kotlinOptions to work around this issue:
|
||||||
|
https://youtrack.jetbrains.com/issue/KTIJ-24311/task-current-target-is-17-and-kaptGenerateStubsProductionDebugKotlin-task-current-target-is-1.8-jvm-target-compatibility-should#focus=Comments-27-6798448.0-0
|
||||||
|
Updating gradle might fix this, so try again in the future to remove this and run:
|
||||||
|
./gradlew --rerun-tasks :uhabits-android:kaptGenerateStubsReleaseKotlin
|
||||||
|
If this doesn't produce any warning, try to remove it.
|
||||||
|
*/
|
||||||
|
kotlin {
|
||||||
|
jvmToolchain(11)
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|
||||||
compileSdk = 32
|
namespace = "org.isoron.uhabits"
|
||||||
|
compileSdk = 34
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
versionCode = 20200
|
versionCode = 20200
|
||||||
versionName = "2.2.0"
|
versionName = "2.2.0"
|
||||||
minSdk = 23
|
minSdk = 28
|
||||||
targetSdk = 31
|
targetSdk = 34
|
||||||
applicationId = "org.isoron.uhabits"
|
applicationId = "org.isoron.uhabits"
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
@@ -70,8 +81,11 @@ android {
|
|||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
isCoreLibraryDesugaringEnabled = true
|
isCoreLibraryDesugaringEnabled = true
|
||||||
targetCompatibility(JavaVersion.VERSION_1_8)
|
targetCompatibility(JavaVersion.VERSION_11)
|
||||||
sourceCompatibility(JavaVersion.VERSION_1_8)
|
sourceCompatibility(JavaVersion.VERSION_11)
|
||||||
|
}
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = JavaVersion.VERSION_11.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
@@ -80,9 +94,9 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
val daggerVersion = "2.46"
|
val daggerVersion = "2.48.1"
|
||||||
val kotlinVersion = "1.7.21"
|
val kotlinVersion = "1.9.21"
|
||||||
val kxCoroutinesVersion = "1.6.4"
|
val kxCoroutinesVersion = "1.7.3"
|
||||||
val ktorVersion = "1.6.8"
|
val ktorVersion = "1.6.8"
|
||||||
val espressoVersion = "3.5.1"
|
val espressoVersion = "3.5.1"
|
||||||
|
|
||||||
@@ -92,17 +106,17 @@ dependencies {
|
|||||||
androidTestImplementation("com.linkedin.dexmaker:dexmaker-mockito:2.28.3")
|
androidTestImplementation("com.linkedin.dexmaker:dexmaker-mockito:2.28.3")
|
||||||
androidTestImplementation("io.ktor:ktor-client-mock:$ktorVersion")
|
androidTestImplementation("io.ktor:ktor-client-mock:$ktorVersion")
|
||||||
androidTestImplementation("io.ktor:ktor-jackson:$ktorVersion")
|
androidTestImplementation("io.ktor:ktor-jackson:$ktorVersion")
|
||||||
androidTestImplementation("androidx.annotation:annotation:1.5.0")
|
androidTestImplementation("androidx.annotation:annotation:1.7.0")
|
||||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||||
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.2.0")
|
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.2.0")
|
||||||
androidTestImplementation("androidx.test:rules:1.5.0")
|
androidTestImplementation("androidx.test:rules:1.5.0")
|
||||||
androidTestImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0")
|
androidTestImplementation("org.mockito.kotlin:mockito-kotlin:5.1.0")
|
||||||
compileOnly("javax.annotation:jsr250-api:1.0")
|
compileOnly("javax.annotation:jsr250-api:1.0")
|
||||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.2.2")
|
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
|
||||||
implementation("com.github.AppIntro:AppIntro:6.2.0")
|
implementation("com.github.AppIntro:AppIntro:6.3.1")
|
||||||
implementation("com.google.code.findbugs:jsr305:3.0.2")
|
implementation("com.google.code.findbugs:jsr305:3.0.2")
|
||||||
implementation("com.google.dagger:dagger:$daggerVersion")
|
implementation("com.google.dagger:dagger:$daggerVersion")
|
||||||
implementation("com.google.guava:guava:31.1-android")
|
implementation("com.google.guava:guava:32.1.3-android")
|
||||||
implementation("io.ktor:ktor-client-android:$ktorVersion")
|
implementation("io.ktor:ktor-client-android:$ktorVersion")
|
||||||
implementation("io.ktor:ktor-client-core:$ktorVersion")
|
implementation("io.ktor:ktor-client-core:$ktorVersion")
|
||||||
implementation("io.ktor:ktor-client-jackson:$ktorVersion")
|
implementation("io.ktor:ktor-client-jackson:$ktorVersion")
|
||||||
@@ -110,17 +124,17 @@ dependencies {
|
|||||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion")
|
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$kxCoroutinesVersion")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$kxCoroutinesVersion")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kxCoroutinesVersion")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kxCoroutinesVersion")
|
||||||
implementation("androidx.appcompat:appcompat:1.5.1")
|
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||||
implementation("androidx.legacy:legacy-preference-v14:1.0.0")
|
implementation("androidx.legacy:legacy-preference-v14:1.0.0")
|
||||||
implementation("androidx.legacy:legacy-support-v4:1.0.0")
|
implementation("androidx.legacy:legacy-support-v4:1.0.0")
|
||||||
implementation("com.google.android.material:material:1.8.0")
|
implementation("com.google.android.material:material:1.10.0")
|
||||||
implementation("com.opencsv:opencsv:5.7.1")
|
implementation("com.opencsv:opencsv:5.9")
|
||||||
implementation(project(":uhabits-core"))
|
implementation(project(":uhabits-core"))
|
||||||
kapt("com.google.dagger:dagger-compiler:$daggerVersion")
|
kapt("com.google.dagger:dagger-compiler:$daggerVersion")
|
||||||
kaptAndroidTest("com.google.dagger:dagger-compiler:$daggerVersion")
|
kaptAndroidTest("com.google.dagger:dagger-compiler:$daggerVersion")
|
||||||
testImplementation("com.google.dagger:dagger:$daggerVersion")
|
testImplementation("com.google.dagger:dagger:$daggerVersion")
|
||||||
testImplementation("junit:junit:4.13.2")
|
testImplementation("junit:junit:4.13.2")
|
||||||
testImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0")
|
testImplementation("org.mockito.kotlin:mockito-kotlin:5.1.0")
|
||||||
}
|
}
|
||||||
|
|
||||||
kapt {
|
kapt {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ open class BaseUserInterfaceTest {
|
|||||||
private lateinit var prefs: Preferences
|
private lateinit var prefs: Preferences
|
||||||
private lateinit var fixtures: HabitFixtures
|
private lateinit var fixtures: HabitFixtures
|
||||||
private lateinit var cache: HabitCardListCache
|
private lateinit var cache: HabitCardListCache
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
|
|||||||
@@ -153,18 +153,22 @@ open class BaseViewTest : BaseAndroidTest() {
|
|||||||
var filename = filename
|
var filename = filename
|
||||||
var dir = getSDCardDir("test-screenshots")
|
var dir = getSDCardDir("test-screenshots")
|
||||||
if (dir == null) dir = AndroidDirFinder(targetContext).getFilesDir("test-screenshots")
|
if (dir == null) dir = AndroidDirFinder(targetContext).getFilesDir("test-screenshots")
|
||||||
if (dir == null) throw RuntimeException(
|
if (dir == null) {
|
||||||
|
throw RuntimeException(
|
||||||
"Could not find suitable dir for screenshots"
|
"Could not find suitable dir for screenshots"
|
||||||
)
|
)
|
||||||
|
}
|
||||||
filename = filename.replace("\\.png$".toRegex(), "$suffix.png")
|
filename = filename.replace("\\.png$".toRegex(), "$suffix.png")
|
||||||
val absolutePath = String.format("%s/%s", dir.absolutePath, filename)
|
val absolutePath = String.format("%s/%s", dir.absolutePath, filename)
|
||||||
val parent = File(absolutePath).parentFile
|
val parent = File(absolutePath).parentFile
|
||||||
if (!parent.exists() && !parent.mkdirs()) throw RuntimeException(
|
if (!parent.exists() && !parent.mkdirs()) {
|
||||||
|
throw RuntimeException(
|
||||||
String.format(
|
String.format(
|
||||||
"Could not create dir: %s",
|
"Could not create dir: %s",
|
||||||
parent.absolutePath
|
parent.absolutePath
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
val out = FileOutputStream(absolutePath)
|
val out = FileOutputStream(absolutePath)
|
||||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)
|
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)
|
||||||
return absolutePath
|
return absolutePath
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits
|
package org.isoron.uhabits
|
||||||
|
|
||||||
import com.nhaarman.mockitokotlin2.mock
|
|
||||||
import dagger.Component
|
import dagger.Component
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
@@ -35,6 +34,7 @@ import org.isoron.uhabits.inject.ActivityScope
|
|||||||
import org.isoron.uhabits.inject.HabitModule
|
import org.isoron.uhabits.inject.HabitModule
|
||||||
import org.isoron.uhabits.inject.HabitsActivityModule
|
import org.isoron.uhabits.inject.HabitsActivityModule
|
||||||
import org.isoron.uhabits.inject.HabitsApplicationComponent
|
import org.isoron.uhabits.inject.HabitsApplicationComponent
|
||||||
|
import org.mockito.kotlin.mock
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
class TestModule {
|
class TestModule {
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ package org.isoron.uhabits.acceptance.steps
|
|||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Build.VERSION.SDK_INT
|
import android.os.Build.VERSION.SDK_INT
|
||||||
import android.os.Build.VERSION_CODES
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.test.espresso.Espresso
|
import androidx.test.espresso.Espresso
|
||||||
@@ -33,11 +32,11 @@ import androidx.test.espresso.matcher.ViewMatchers
|
|||||||
import androidx.test.uiautomator.By
|
import androidx.test.uiautomator.By
|
||||||
import androidx.test.uiautomator.UiSelector
|
import androidx.test.uiautomator.UiSelector
|
||||||
import androidx.test.uiautomator.Until
|
import androidx.test.uiautomator.Until
|
||||||
import junit.framework.Assert.assertTrue
|
|
||||||
import org.hamcrest.CoreMatchers
|
import org.hamcrest.CoreMatchers
|
||||||
import org.isoron.uhabits.BaseUserInterfaceTest
|
import org.isoron.uhabits.BaseUserInterfaceTest
|
||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
import org.isoron.uhabits.activities.habits.list.ListHabitsActivity
|
import org.isoron.uhabits.activities.habits.list.ListHabitsActivity
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
|
||||||
object CommonSteps : BaseUserInterfaceTest() {
|
object CommonSteps : BaseUserInterfaceTest() {
|
||||||
fun pressBack() {
|
fun pressBack() {
|
||||||
@@ -148,15 +147,19 @@ object CommonSteps : BaseUserInterfaceTest() {
|
|||||||
Screen.LIST_HABITS ->
|
Screen.LIST_HABITS ->
|
||||||
Espresso.onView(ViewMatchers.withClassName(CoreMatchers.endsWith("ListHabitsRootView")))
|
Espresso.onView(ViewMatchers.withClassName(CoreMatchers.endsWith("ListHabitsRootView")))
|
||||||
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
|
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
|
||||||
|
|
||||||
Screen.SHOW_HABIT ->
|
Screen.SHOW_HABIT ->
|
||||||
Espresso.onView(ViewMatchers.withId(R.id.subtitleCard))
|
Espresso.onView(ViewMatchers.withId(R.id.subtitleCard))
|
||||||
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
|
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
|
||||||
|
|
||||||
Screen.EDIT_HABIT ->
|
Screen.EDIT_HABIT ->
|
||||||
Espresso.onView(ViewMatchers.withId(R.id.questionInput))
|
Espresso.onView(ViewMatchers.withId(R.id.questionInput))
|
||||||
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
|
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
|
||||||
|
|
||||||
Screen.SELECT_HABIT_TYPE ->
|
Screen.SELECT_HABIT_TYPE ->
|
||||||
Espresso.onView(ViewMatchers.withText(R.string.yes_or_no_example))
|
Espresso.onView(ViewMatchers.withText(R.string.yes_or_no_example))
|
||||||
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
|
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
|
||||||
|
|
||||||
else -> throw IllegalStateException()
|
else -> throw IllegalStateException()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ package org.isoron.uhabits.acceptance.steps
|
|||||||
import android.os.Build.VERSION.SDK_INT
|
import android.os.Build.VERSION.SDK_INT
|
||||||
import androidx.test.uiautomator.UiScrollable
|
import androidx.test.uiautomator.UiScrollable
|
||||||
import androidx.test.uiautomator.UiSelector
|
import androidx.test.uiautomator.UiSelector
|
||||||
import junit.framework.Assert.assertFalse
|
|
||||||
import junit.framework.Assert.assertTrue
|
|
||||||
import org.isoron.uhabits.BaseUserInterfaceTest
|
import org.isoron.uhabits.BaseUserInterfaceTest
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
|
||||||
object WidgetSteps {
|
object WidgetSteps {
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.isoron.uhabits.activities.common.views
|
package org.isoron.uhabits.activities.common.views
|
||||||
|
|
||||||
|
import android.view.MotionEvent
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.filters.MediumTest
|
import androidx.test.filters.MediumTest
|
||||||
import org.isoron.uhabits.BaseViewTest
|
import org.isoron.uhabits.BaseViewTest
|
||||||
@@ -52,7 +53,8 @@ class FrequencyChartTest : BaseViewTest() {
|
|||||||
@Test
|
@Test
|
||||||
@Throws(Throwable::class)
|
@Throws(Throwable::class)
|
||||||
fun testRender_withDataOffset() {
|
fun testRender_withDataOffset() {
|
||||||
view.onScroll(null, null, -dpToPixels(150), 0f)
|
val e = MotionEvent.obtain(0, 0, 0, 0f, 0f, 0)
|
||||||
|
view.onScroll(e, e, -dpToPixels(150), 0f)
|
||||||
view.invalidate()
|
view.invalidate()
|
||||||
assertRenders(view, BASE_PATH + "renderDataOffset.png")
|
assertRenders(view, BASE_PATH + "renderDataOffset.png")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.isoron.uhabits.activities.common.views
|
package org.isoron.uhabits.activities.common.views
|
||||||
|
|
||||||
|
import android.view.MotionEvent
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.filters.MediumTest
|
import androidx.test.filters.MediumTest
|
||||||
import org.isoron.uhabits.BaseViewTest
|
import org.isoron.uhabits.BaseViewTest
|
||||||
@@ -44,7 +45,7 @@ class ScoreChartTest : BaseViewTest() {
|
|||||||
habit = habit,
|
habit = habit,
|
||||||
firstWeekday = prefs.firstWeekdayInt,
|
firstWeekday = prefs.firstWeekdayInt,
|
||||||
spinnerPosition = 0,
|
spinnerPosition = 0,
|
||||||
theme = LightTheme(),
|
theme = LightTheme()
|
||||||
)
|
)
|
||||||
view = ScoreChart(targetContext).apply {
|
view = ScoreChart(targetContext).apply {
|
||||||
setScores(state.scores)
|
setScores(state.scores)
|
||||||
@@ -63,7 +64,8 @@ class ScoreChartTest : BaseViewTest() {
|
|||||||
@Test
|
@Test
|
||||||
@Throws(Throwable::class)
|
@Throws(Throwable::class)
|
||||||
fun testRender_withDataOffset() {
|
fun testRender_withDataOffset() {
|
||||||
view.onScroll(null, null, -dpToPixels(150), 0f)
|
val e = MotionEvent.obtain(0, 0, 0, 0f, 0f, 0)
|
||||||
|
view.onScroll(e, e, -dpToPixels(150), 0f)
|
||||||
view.invalidate()
|
view.invalidate()
|
||||||
assertRenders(view, BASE_PATH + "renderDataOffset.png")
|
assertRenders(view, BASE_PATH + "renderDataOffset.png")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import org.junit.runner.RunWith
|
|||||||
@MediumTest
|
@MediumTest
|
||||||
class StreakChartTest : BaseViewTest() {
|
class StreakChartTest : BaseViewTest() {
|
||||||
private lateinit var view: StreakChart
|
private lateinit var view: StreakChart
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
override fun setUp() {
|
override fun setUp() {
|
||||||
super.setUp()
|
super.setUp()
|
||||||
|
|||||||
@@ -20,15 +20,15 @@ package org.isoron.uhabits.activities.habits.list.views
|
|||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.filters.MediumTest
|
import androidx.test.filters.MediumTest
|
||||||
import com.nhaarman.mockitokotlin2.doReturn
|
|
||||||
import com.nhaarman.mockitokotlin2.mock
|
|
||||||
import com.nhaarman.mockitokotlin2.verify
|
|
||||||
import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions
|
|
||||||
import com.nhaarman.mockitokotlin2.whenever
|
|
||||||
import org.isoron.uhabits.BaseViewTest
|
import org.isoron.uhabits.BaseViewTest
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.kotlin.doReturn
|
||||||
|
import org.mockito.kotlin.mock
|
||||||
|
import org.mockito.kotlin.verify
|
||||||
|
import org.mockito.kotlin.verifyNoMoreInteractions
|
||||||
|
import org.mockito.kotlin.whenever
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@MediumTest
|
@MediumTest
|
||||||
|
|||||||
@@ -20,9 +20,6 @@ package org.isoron.uhabits.activities.habits.list.views
|
|||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.filters.MediumTest
|
import androidx.test.filters.MediumTest
|
||||||
import com.nhaarman.mockitokotlin2.doReturn
|
|
||||||
import com.nhaarman.mockitokotlin2.mock
|
|
||||||
import com.nhaarman.mockitokotlin2.whenever
|
|
||||||
import org.hamcrest.CoreMatchers.equalTo
|
import org.hamcrest.CoreMatchers.equalTo
|
||||||
import org.hamcrest.MatcherAssert.assertThat
|
import org.hamcrest.MatcherAssert.assertThat
|
||||||
import org.isoron.uhabits.BaseViewTest
|
import org.isoron.uhabits.BaseViewTest
|
||||||
@@ -30,6 +27,9 @@ import org.isoron.uhabits.core.ui.screens.habits.list.HintList
|
|||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.kotlin.doReturn
|
||||||
|
import org.mockito.kotlin.mock
|
||||||
|
import org.mockito.kotlin.whenever
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@MediumTest
|
@MediumTest
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class FrequencyCardViewTest : BaseViewTest() {
|
|||||||
FrequencyCardPresenter.buildState(
|
FrequencyCardPresenter.buildState(
|
||||||
habit = habit,
|
habit = habit,
|
||||||
firstWeekday = 0,
|
firstWeekday = 0,
|
||||||
theme = LightTheme(),
|
theme = LightTheme()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
measureView(view, 800f, 600f)
|
measureView(view, 800f, 600f)
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class HistoryCardViewTest : BaseViewTest() {
|
|||||||
HistoryCardPresenter.buildState(
|
HistoryCardPresenter.buildState(
|
||||||
habit = habit,
|
habit = habit,
|
||||||
firstWeekday = SUNDAY,
|
firstWeekday = SUNDAY,
|
||||||
theme = LightTheme(),
|
theme = LightTheme()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
measureView(view, 800f, 600f)
|
measureView(view, 800f, 600f)
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ class OverviewCardViewTest : BaseViewTest() {
|
|||||||
scoreYearDiff = 0.74f,
|
scoreYearDiff = 0.74f,
|
||||||
totalCount = 44,
|
totalCount = 44,
|
||||||
color = PaletteColor(7),
|
color = PaletteColor(7),
|
||||||
theme = LightTheme(),
|
theme = LightTheme()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
measureView(view, 800f, 300f)
|
measureView(view, 800f, 300f)
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class ScoreCardViewTest : BaseViewTest() {
|
|||||||
habit = habit,
|
habit = habit,
|
||||||
firstWeekday = 0,
|
firstWeekday = 0,
|
||||||
spinnerPosition = 0,
|
spinnerPosition = 0,
|
||||||
theme = LightTheme(),
|
theme = LightTheme()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
measureView(view, 800f, 600f)
|
measureView(view, 800f, 600f)
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class StreakCardViewTest : BaseViewTest() {
|
|||||||
StreakCardState(
|
StreakCardState(
|
||||||
bestStreaks = habit.streaks.getBest(10),
|
bestStreaks = habit.streaks.getBest(10),
|
||||||
color = habit.color,
|
color = habit.color,
|
||||||
theme = LightTheme(),
|
theme = LightTheme()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
measureView(view, 800f, 600f)
|
measureView(view, 800f, 600f)
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class SubtitleCardViewTest : BaseViewTest() {
|
|||||||
isNumerical = false,
|
isNumerical = false,
|
||||||
question = "Did you meditate this morning?",
|
question = "Did you meditate this morning?",
|
||||||
reminder = Reminder(8, 30, EVERY_DAY),
|
reminder = Reminder(8, 30, EVERY_DAY),
|
||||||
theme = LightTheme(),
|
theme = LightTheme()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
measureView(view, 800f, 200f)
|
measureView(view, 800f, 200f)
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ class ListHabitsRegressionTest : BaseUserInterfaceTest() {
|
|||||||
"Wake up early",
|
"Wake up early",
|
||||||
"Meditate",
|
"Meditate",
|
||||||
"Read books",
|
"Read books",
|
||||||
"Track time",
|
"Track time"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import java.io.IOException
|
|||||||
@MediumTest
|
@MediumTest
|
||||||
class CheckmarkWidgetViewTest : BaseViewTest() {
|
class CheckmarkWidgetViewTest : BaseViewTest() {
|
||||||
private lateinit var view: CheckmarkWidgetView
|
private lateinit var view: CheckmarkWidgetView
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
override fun setUp() {
|
override fun setUp() {
|
||||||
super.setUp()
|
super.setUp()
|
||||||
|
|||||||
@@ -16,12 +16,13 @@
|
|||||||
~ You should have received a copy of the GNU General Public License along
|
~ 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 <http://www.gnu.org/licenses/>.
|
||||||
-->
|
-->
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
package="org.isoron.uhabits">
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
|
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||||
|
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
|
||||||
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".HabitsApplication"
|
android:name=".HabitsApplication"
|
||||||
@@ -29,6 +30,7 @@
|
|||||||
android:backupAgent=".HabitsBackupAgent"
|
android:backupAgent=".HabitsBackupAgent"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/main_activity_title"
|
android:label="@string/main_activity_title"
|
||||||
|
android:localeConfig="@xml/locales_config"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppBaseTheme">
|
android:theme="@style/AppBaseTheme">
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import java.util.Locale;
|
|||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
|||||||
@@ -23,14 +23,13 @@ import android.graphics.Paint.*;
|
|||||||
import android.os.*;
|
import android.os.*;
|
||||||
import androidx.core.view.*;
|
import androidx.core.view.*;
|
||||||
import androidx.core.view.accessibility.*;
|
import androidx.core.view.accessibility.*;
|
||||||
import androidx.core.widget.*;
|
|
||||||
import android.text.format.*;
|
import android.text.format.*;
|
||||||
import android.view.*;
|
import android.view.*;
|
||||||
import android.view.accessibility.*;
|
import android.view.accessibility.*;
|
||||||
|
|
||||||
import androidx.customview.widget.ExploreByTouchHelper;
|
import androidx.customview.widget.ExploreByTouchHelper;
|
||||||
|
|
||||||
import com.android.*;
|
|
||||||
import com.android.datetimepicker.*;
|
import com.android.datetimepicker.*;
|
||||||
import com.android.datetimepicker.date.MonthAdapter.*;
|
import com.android.datetimepicker.date.MonthAdapter.*;
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import android.graphics.Paint.*;
|
|||||||
import android.util.*;
|
import android.util.*;
|
||||||
import android.view.*;
|
import android.view.*;
|
||||||
|
|
||||||
import com.android.*;
|
|
||||||
import com.android.datetimepicker.*;
|
import com.android.datetimepicker.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.R;
|
import org.isoron.uhabits.R;
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import android.view.View.*;
|
|||||||
import android.view.accessibility.*;
|
import android.view.accessibility.*;
|
||||||
import android.widget.*;
|
import android.widget.*;
|
||||||
|
|
||||||
import com.android.*;
|
|
||||||
import com.android.datetimepicker.*;
|
import com.android.datetimepicker.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.R;
|
import org.isoron.uhabits.R;
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class AndroidCanvas : Canvas {
|
|||||||
y1.toDp(),
|
y1.toDp(),
|
||||||
x2.toDp(),
|
x2.toDp(),
|
||||||
y2.toDp(),
|
y2.toDp(),
|
||||||
paint,
|
paint
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ class AndroidCanvas : Canvas {
|
|||||||
text,
|
text,
|
||||||
x.toDp(),
|
x.toDp(),
|
||||||
y.toDp() + 0.6f * mHeight,
|
y.toDp() + 0.6f * mHeight,
|
||||||
textPaint,
|
textPaint
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ class AndroidCanvas : Canvas {
|
|||||||
y: Double,
|
y: Double,
|
||||||
width: Double,
|
width: Double,
|
||||||
height: Double,
|
height: Double,
|
||||||
cornerRadius: Double,
|
cornerRadius: Double
|
||||||
) {
|
) {
|
||||||
paint.style = Paint.Style.FILL
|
paint.style = Paint.Style.FILL
|
||||||
innerCanvas.drawRoundRect(
|
innerCanvas.drawRoundRect(
|
||||||
@@ -93,7 +93,7 @@ class AndroidCanvas : Canvas {
|
|||||||
(y + height).toDp(),
|
(y + height).toDp(),
|
||||||
cornerRadius.toDp(),
|
cornerRadius.toDp(),
|
||||||
cornerRadius.toDp(),
|
cornerRadius.toDp(),
|
||||||
paint,
|
paint
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ class AndroidCanvas : Canvas {
|
|||||||
y.toDp(),
|
y.toDp(),
|
||||||
(x + width).toDp(),
|
(x + width).toDp(),
|
||||||
(y + height).toDp(),
|
(y + height).toDp(),
|
||||||
paint,
|
paint
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,7 +148,7 @@ class AndroidCanvas : Canvas {
|
|||||||
centerY: Double,
|
centerY: Double,
|
||||||
radius: Double,
|
radius: Double,
|
||||||
startAngle: Double,
|
startAngle: Double,
|
||||||
swipeAngle: Double,
|
swipeAngle: Double
|
||||||
) {
|
) {
|
||||||
paint.style = Paint.Style.FILL
|
paint.style = Paint.Style.FILL
|
||||||
innerCanvas.drawArc(
|
innerCanvas.drawArc(
|
||||||
@@ -159,14 +159,14 @@ class AndroidCanvas : Canvas {
|
|||||||
-startAngle.toFloat(),
|
-startAngle.toFloat(),
|
||||||
-swipeAngle.toFloat(),
|
-swipeAngle.toFloat(),
|
||||||
true,
|
true,
|
||||||
paint,
|
paint
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fillCircle(
|
override fun fillCircle(
|
||||||
centerX: Double,
|
centerX: Double,
|
||||||
centerY: Double,
|
centerY: Double,
|
||||||
radius: Double,
|
radius: Double
|
||||||
) {
|
) {
|
||||||
paint.style = Paint.Style.FILL
|
paint.style = Paint.Style.FILL
|
||||||
innerCanvas.drawCircle(centerX.toDp(), centerY.toDp(), radius.toDp(), paint)
|
innerCanvas.drawCircle(centerX.toDp(), centerY.toDp(), radius.toDp(), paint)
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import kotlin.math.max
|
|||||||
*/
|
*/
|
||||||
class AndroidDataView(
|
class AndroidDataView(
|
||||||
context: Context,
|
context: Context,
|
||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null
|
||||||
) : AndroidView<DataView>(context, attrs),
|
) : AndroidView<DataView>(context, attrs),
|
||||||
GestureDetector.OnGestureListener,
|
GestureDetector.OnGestureListener,
|
||||||
ValueAnimator.AnimatorUpdateListener {
|
ValueAnimator.AnimatorUpdateListener {
|
||||||
@@ -44,23 +44,23 @@ class AndroidDataView(
|
|||||||
addUpdateListener(this@AndroidDataView)
|
addUpdateListener(this@AndroidDataView)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTouchEvent(event: MotionEvent?) = detector.onTouchEvent(event)
|
override fun onTouchEvent(event: MotionEvent) = detector.onTouchEvent(event)
|
||||||
override fun onDown(e: MotionEvent?) = true
|
override fun onDown(e: MotionEvent) = true
|
||||||
override fun onShowPress(e: MotionEvent?) = Unit
|
override fun onShowPress(e: MotionEvent) = Unit
|
||||||
|
|
||||||
override fun onSingleTapUp(e: MotionEvent?): Boolean {
|
override fun onSingleTapUp(e: MotionEvent): Boolean {
|
||||||
return handleClick(e, true)
|
return handleClick(e, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLongPress(e: MotionEvent?) {
|
override fun onLongPress(e: MotionEvent) {
|
||||||
handleClick(e)
|
handleClick(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onScroll(
|
override fun onScroll(
|
||||||
e1: MotionEvent?,
|
e1: MotionEvent?,
|
||||||
e2: MotionEvent?,
|
e2: MotionEvent,
|
||||||
dx: Float,
|
dx: Float,
|
||||||
dy: Float,
|
dy: Float
|
||||||
): Boolean {
|
): Boolean {
|
||||||
if (abs(dx) > abs(dy)) {
|
if (abs(dx) > abs(dy)) {
|
||||||
val parent = parent
|
val parent = parent
|
||||||
@@ -80,9 +80,9 @@ class AndroidDataView(
|
|||||||
|
|
||||||
override fun onFling(
|
override fun onFling(
|
||||||
e1: MotionEvent?,
|
e1: MotionEvent?,
|
||||||
e2: MotionEvent?,
|
e2: MotionEvent,
|
||||||
velocityX: Float,
|
velocityX: Float,
|
||||||
velocityY: Float,
|
velocityY: Float
|
||||||
): Boolean {
|
): Boolean {
|
||||||
scroller.fling(
|
scroller.fling(
|
||||||
scroller.currX,
|
scroller.currX,
|
||||||
@@ -100,7 +100,7 @@ class AndroidDataView(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAnimationUpdate(animation: ValueAnimator?) {
|
override fun onAnimationUpdate(animation: ValueAnimator) {
|
||||||
if (!scroller.isFinished) {
|
if (!scroller.isFinished) {
|
||||||
scroller.computeScrollOffset()
|
scroller.computeScrollOffset()
|
||||||
updateDataOffset()
|
updateDataOffset()
|
||||||
@@ -127,11 +127,11 @@ class AndroidDataView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleClick(e: MotionEvent?, isSingleTap: Boolean = false): Boolean {
|
private fun handleClick(e: MotionEvent, isSingleTap: Boolean = false): Boolean {
|
||||||
val x: Float
|
val x: Float
|
||||||
val y: Float
|
val y: Float
|
||||||
try {
|
try {
|
||||||
val pointerId = e!!.getPointerId(0)
|
val pointerId = e.getPointerId(0)
|
||||||
x = e.getX(pointerId)
|
x = e.getX(pointerId)
|
||||||
y = e.getY(pointerId)
|
y = e.getY(pointerId)
|
||||||
} catch (ex: RuntimeException) {
|
} catch (ex: RuntimeException) {
|
||||||
@@ -140,8 +140,11 @@ class AndroidDataView(
|
|||||||
// e.getPointerId.
|
// e.getPointerId.
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (isSingleTap) view?.onClick(x / canvas.innerDensity, y / canvas.innerDensity)
|
if (isSingleTap) {
|
||||||
else view?.onLongClick(x / canvas.innerDensity, y / canvas.innerDensity)
|
view?.onClick(x / canvas.innerDensity, y / canvas.innerDensity)
|
||||||
|
} else {
|
||||||
|
view?.onLongClick(x / canvas.innerDensity, y / canvas.innerDensity)
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,6 @@ fun Color.toInt(): Int {
|
|||||||
(255 * this.alpha).roundToInt(),
|
(255 * this.alpha).roundToInt(),
|
||||||
(255 * this.red).roundToInt(),
|
(255 * this.red).roundToInt(),
|
||||||
(255 * this.green).roundToInt(),
|
(255 * this.green).roundToInt(),
|
||||||
(255 * this.blue).roundToInt(),
|
(255 * this.blue).roundToInt()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import android.util.AttributeSet
|
|||||||
|
|
||||||
open class AndroidView<T : View>(
|
open class AndroidView<T : View>(
|
||||||
context: Context,
|
context: Context,
|
||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null
|
||||||
) : android.view.View(context, attrs) {
|
) : android.view.View(context, attrs) {
|
||||||
|
|
||||||
var view: T? = null
|
var view: T? = null
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import org.isoron.uhabits.inject.ActivityScope
|
|||||||
class AndroidThemeSwitcher
|
class AndroidThemeSwitcher
|
||||||
constructor(
|
constructor(
|
||||||
@ActivityContext val context: Context,
|
@ActivityContext val context: Context,
|
||||||
preferences: Preferences,
|
preferences: Preferences
|
||||||
) : ThemeSwitcher(preferences) {
|
) : ThemeSwitcher(preferences) {
|
||||||
|
|
||||||
override var currentTheme: Theme = LightTheme()
|
override var currentTheme: Theme = LightTheme()
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import org.isoron.uhabits.utils.startActivitySafely
|
|||||||
class AboutScreen(
|
class AboutScreen(
|
||||||
private val activity: AboutActivity,
|
private val activity: AboutActivity,
|
||||||
private val intents: IntentFactory,
|
private val intents: IntentFactory,
|
||||||
private val prefs: Preferences,
|
private val prefs: Preferences
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private var developerCountdown = 5
|
private var developerCountdown = 5
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import org.isoron.uhabits.utils.setupToolbar
|
|||||||
@SuppressLint("ViewConstructor")
|
@SuppressLint("ViewConstructor")
|
||||||
class AboutView(
|
class AboutView(
|
||||||
context: Context,
|
context: Context,
|
||||||
private val screen: AboutScreen,
|
private val screen: AboutScreen
|
||||||
) : FrameLayout(context) {
|
) : FrameLayout(context) {
|
||||||
|
|
||||||
private var binding = AboutBinding.inflate(LayoutInflater.from(context))
|
private var binding = AboutBinding.inflate(LayoutInflater.from(context))
|
||||||
@@ -43,7 +43,7 @@ class AboutView(
|
|||||||
toolbar = binding.toolbar,
|
toolbar = binding.toolbar,
|
||||||
color = PaletteColor(11),
|
color = PaletteColor(11),
|
||||||
title = resources.getString(R.string.about),
|
title = resources.getString(R.string.about),
|
||||||
theme = currentTheme(),
|
theme = currentTheme()
|
||||||
)
|
)
|
||||||
val version = resources.getString(R.string.version_n)
|
val version = resources.getString(R.string.version_n)
|
||||||
binding.tvContributors.setOnClickListener { screen.showCodeContributorsWebsite() }
|
binding.tvContributors.setOnClickListener { screen.showCodeContributorsWebsite() }
|
||||||
|
|||||||
@@ -22,97 +22,101 @@ package org.isoron.uhabits.activities.common.dialogs
|
|||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.RadioButton
|
import android.widget.RadioButton
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatDialogFragment
|
import androidx.appcompat.app.AppCompatDialogFragment
|
||||||
import kotlinx.android.synthetic.main.frequency_picker_dialog.view.*
|
|
||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
|
import org.isoron.uhabits.databinding.FrequencyPickerDialogBinding
|
||||||
|
|
||||||
class FrequencyPickerDialog(
|
class FrequencyPickerDialog(
|
||||||
var freqNumerator: Int,
|
var freqNumerator: Int,
|
||||||
var freqDenominator: Int
|
var freqDenominator: Int
|
||||||
) : AppCompatDialogFragment() {
|
) : AppCompatDialogFragment() {
|
||||||
|
private var _binding: FrequencyPickerDialogBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
lateinit var contentView: View
|
|
||||||
var onFrequencyPicked: (num: Int, den: Int) -> Unit = { _, _ -> }
|
var onFrequencyPicked: (num: Int, den: Int) -> Unit = { _, _ -> }
|
||||||
|
|
||||||
constructor() : this(1, 1)
|
constructor() : this(1, 1)
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
val inflater = LayoutInflater.from(requireActivity())
|
_binding = FrequencyPickerDialogBinding.inflate(LayoutInflater.from(requireActivity()))
|
||||||
contentView = inflater.inflate(R.layout.frequency_picker_dialog, null)
|
|
||||||
|
|
||||||
addBeforeAfterText(
|
addBeforeAfterText(
|
||||||
this.getString(R.string.every_x_days),
|
this.getString(R.string.every_x_days),
|
||||||
contentView.everyXDaysContainer,
|
binding.everyXDaysContainer
|
||||||
)
|
)
|
||||||
|
|
||||||
addBeforeAfterText(
|
addBeforeAfterText(
|
||||||
this.getString(R.string.x_times_per_week),
|
this.getString(R.string.x_times_per_week),
|
||||||
contentView.xTimesPerWeekContainer,
|
binding.xTimesPerWeekContainer
|
||||||
)
|
)
|
||||||
|
|
||||||
addBeforeAfterText(
|
addBeforeAfterText(
|
||||||
this.getString(R.string.x_times_per_month),
|
this.getString(R.string.x_times_per_month),
|
||||||
contentView.xTimesPerMonthContainer,
|
binding.xTimesPerMonthContainer
|
||||||
)
|
)
|
||||||
|
|
||||||
addBeforeAfterText(
|
addBeforeAfterText(
|
||||||
this.getString(R.string.x_times_per_y_days),
|
this.getString(R.string.x_times_per_y_days),
|
||||||
contentView.xTimesPerYDaysContainer,
|
binding.xTimesPerYDaysContainer
|
||||||
)
|
)
|
||||||
|
|
||||||
contentView.everyDayRadioButton.setOnClickListener {
|
binding.everyDayRadioButton.setOnClickListener {
|
||||||
check(contentView.everyDayRadioButton)
|
check(binding.everyDayRadioButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
contentView.everyXDaysRadioButton.setOnClickListener {
|
binding.everyXDaysRadioButton.setOnClickListener {
|
||||||
check(contentView.everyXDaysRadioButton)
|
check(binding.everyXDaysRadioButton)
|
||||||
val everyXDaysTextView = contentView.everyXDaysTextView
|
val everyXDaysTextView = binding.everyXDaysTextView
|
||||||
selectInputField(everyXDaysTextView)
|
selectInputField(everyXDaysTextView)
|
||||||
}
|
}
|
||||||
|
|
||||||
contentView.everyXDaysTextView.setOnFocusChangeListener { v, hasFocus ->
|
binding.everyXDaysTextView.setOnFocusChangeListener { v, hasFocus ->
|
||||||
if (hasFocus) check(contentView.everyXDaysRadioButton)
|
if (hasFocus) check(binding.everyXDaysRadioButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
contentView.xTimesPerWeekRadioButton.setOnClickListener {
|
binding.xTimesPerWeekRadioButton.setOnClickListener {
|
||||||
check(contentView.xTimesPerWeekRadioButton)
|
check(binding.xTimesPerWeekRadioButton)
|
||||||
selectInputField(contentView.xTimesPerWeekTextView)
|
selectInputField(binding.xTimesPerWeekTextView)
|
||||||
}
|
}
|
||||||
|
|
||||||
contentView.xTimesPerWeekTextView.setOnFocusChangeListener { v, hasFocus ->
|
binding.xTimesPerWeekTextView.setOnFocusChangeListener { v, hasFocus ->
|
||||||
if (hasFocus) check(contentView.xTimesPerWeekRadioButton)
|
if (hasFocus) check(binding.xTimesPerWeekRadioButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
contentView.xTimesPerMonthRadioButton.setOnClickListener {
|
binding.xTimesPerMonthRadioButton.setOnClickListener {
|
||||||
check(contentView.xTimesPerMonthRadioButton)
|
check(binding.xTimesPerMonthRadioButton)
|
||||||
selectInputField(contentView.xTimesPerMonthTextView)
|
selectInputField(binding.xTimesPerMonthTextView)
|
||||||
}
|
}
|
||||||
|
|
||||||
contentView.xTimesPerMonthTextView.setOnFocusChangeListener { v, hasFocus ->
|
binding.xTimesPerMonthTextView.setOnFocusChangeListener { v, hasFocus ->
|
||||||
if (hasFocus) check(contentView.xTimesPerMonthRadioButton)
|
if (hasFocus) check(binding.xTimesPerMonthRadioButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
contentView.xTimesPerYDaysRadioButton.setOnClickListener {
|
binding.xTimesPerYDaysRadioButton.setOnClickListener {
|
||||||
check(contentView.xTimesPerYDaysRadioButton)
|
check(binding.xTimesPerYDaysRadioButton)
|
||||||
selectInputField(contentView.xTimesPerYDaysXTextView)
|
selectInputField(binding.xTimesPerYDaysXTextView)
|
||||||
}
|
}
|
||||||
|
|
||||||
contentView.xTimesPerYDaysXTextView.setOnFocusChangeListener { v, hasFocus ->
|
binding.xTimesPerYDaysXTextView.setOnFocusChangeListener { v, hasFocus ->
|
||||||
if (hasFocus) check(contentView.xTimesPerYDaysRadioButton)
|
if (hasFocus) check(binding.xTimesPerYDaysRadioButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
contentView.xTimesPerYDaysYTextView.setOnFocusChangeListener { v, hasFocus ->
|
binding.xTimesPerYDaysYTextView.setOnFocusChangeListener { v, hasFocus ->
|
||||||
if (hasFocus) check(contentView.xTimesPerYDaysRadioButton)
|
if (hasFocus) check(binding.xTimesPerYDaysRadioButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
return AlertDialog.Builder(requireActivity())
|
return AlertDialog.Builder(requireActivity())
|
||||||
.setView(contentView)
|
.setView(binding.root)
|
||||||
.setPositiveButton(R.string.save) { _, _ -> onSaveClicked() }
|
.setPositiveButton(R.string.save) { _, _ -> onSaveClicked() }
|
||||||
.create()
|
.create()
|
||||||
}
|
}
|
||||||
@@ -124,7 +128,8 @@ class FrequencyPickerDialog(
|
|||||||
val parts = str.split("%d")
|
val parts = str.split("%d")
|
||||||
for (i in parts.indices) {
|
for (i in parts.indices) {
|
||||||
container.addView(
|
container.addView(
|
||||||
TextView(activity).apply { text = parts[i].trim() }, 2 * i + 1,
|
TextView(activity).apply { text = parts[i].trim() },
|
||||||
|
2 * i + 1
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,31 +138,35 @@ class FrequencyPickerDialog(
|
|||||||
var numerator = 1
|
var numerator = 1
|
||||||
var denominator = 1
|
var denominator = 1
|
||||||
when {
|
when {
|
||||||
contentView.everyDayRadioButton.isChecked -> {
|
binding.everyDayRadioButton.isChecked -> {
|
||||||
// NOP
|
// NOP
|
||||||
}
|
}
|
||||||
contentView.everyXDaysRadioButton.isChecked -> {
|
|
||||||
if (contentView.everyXDaysTextView.text.isNotEmpty()) {
|
binding.everyXDaysRadioButton.isChecked -> {
|
||||||
denominator = Integer.parseInt(contentView.everyXDaysTextView.text.toString())
|
if (binding.everyXDaysTextView.text.isNotEmpty()) {
|
||||||
|
denominator = Integer.parseInt(binding.everyXDaysTextView.text.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
contentView.xTimesPerWeekRadioButton.isChecked -> {
|
|
||||||
if (contentView.xTimesPerWeekTextView.text.isNotEmpty()) {
|
binding.xTimesPerWeekRadioButton.isChecked -> {
|
||||||
numerator = Integer.parseInt(contentView.xTimesPerWeekTextView.text.toString())
|
if (binding.xTimesPerWeekTextView.text.isNotEmpty()) {
|
||||||
|
numerator = Integer.parseInt(binding.xTimesPerWeekTextView.text.toString())
|
||||||
denominator = 7
|
denominator = 7
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
contentView.xTimesPerYDaysRadioButton.isChecked -> {
|
|
||||||
if (contentView.xTimesPerYDaysXTextView.text.isNotEmpty() && contentView.xTimesPerYDaysYTextView.text.isNotEmpty()) {
|
binding.xTimesPerYDaysRadioButton.isChecked -> {
|
||||||
|
if (binding.xTimesPerYDaysXTextView.text.isNotEmpty() && binding.xTimesPerYDaysYTextView.text.isNotEmpty()) {
|
||||||
numerator =
|
numerator =
|
||||||
Integer.parseInt(contentView.xTimesPerYDaysXTextView.text.toString())
|
Integer.parseInt(binding.xTimesPerYDaysXTextView.text.toString())
|
||||||
denominator =
|
denominator =
|
||||||
Integer.parseInt(contentView.xTimesPerYDaysYTextView.text.toString())
|
Integer.parseInt(binding.xTimesPerYDaysYTextView.text.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
if (contentView.xTimesPerMonthTextView.text.isNotEmpty()) {
|
if (binding.xTimesPerMonthTextView.text.isNotEmpty()) {
|
||||||
numerator = Integer.parseInt(contentView.xTimesPerMonthTextView.text.toString())
|
numerator = Integer.parseInt(binding.xTimesPerMonthTextView.text.toString())
|
||||||
denominator = 30
|
denominator = 30
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,27 +193,27 @@ class FrequencyPickerDialog(
|
|||||||
private fun populateViews() {
|
private fun populateViews() {
|
||||||
uncheckAll()
|
uncheckAll()
|
||||||
if (freqDenominator == 30 || freqDenominator == 31) {
|
if (freqDenominator == 30 || freqDenominator == 31) {
|
||||||
contentView.xTimesPerMonthRadioButton.isChecked = true
|
binding.xTimesPerMonthRadioButton.isChecked = true
|
||||||
contentView.xTimesPerMonthTextView.setText(freqNumerator.toString())
|
binding.xTimesPerMonthTextView.setText(freqNumerator.toString())
|
||||||
selectInputField(contentView.xTimesPerMonthTextView)
|
selectInputField(binding.xTimesPerMonthTextView)
|
||||||
} else {
|
} else {
|
||||||
if (freqNumerator == 1) {
|
if (freqNumerator == 1) {
|
||||||
if (freqDenominator == 1) {
|
if (freqDenominator == 1) {
|
||||||
contentView.everyDayRadioButton.isChecked = true
|
binding.everyDayRadioButton.isChecked = true
|
||||||
} else {
|
} else {
|
||||||
contentView.everyXDaysRadioButton.isChecked = true
|
binding.everyXDaysRadioButton.isChecked = true
|
||||||
contentView.everyXDaysTextView.setText(freqDenominator.toString())
|
binding.everyXDaysTextView.setText(freqDenominator.toString())
|
||||||
selectInputField(contentView.everyXDaysTextView)
|
selectInputField(binding.everyXDaysTextView)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (freqDenominator == 7) {
|
if (freqDenominator == 7) {
|
||||||
contentView.xTimesPerWeekRadioButton.isChecked = true
|
binding.xTimesPerWeekRadioButton.isChecked = true
|
||||||
contentView.xTimesPerWeekTextView.setText(freqNumerator.toString())
|
binding.xTimesPerWeekTextView.setText(freqNumerator.toString())
|
||||||
selectInputField(contentView.xTimesPerWeekTextView)
|
selectInputField(binding.xTimesPerWeekTextView)
|
||||||
} else {
|
} else {
|
||||||
contentView.xTimesPerYDaysRadioButton.isChecked = true
|
binding.xTimesPerYDaysRadioButton.isChecked = true
|
||||||
contentView.xTimesPerYDaysXTextView.setText(freqNumerator.toString())
|
binding.xTimesPerYDaysXTextView.setText(freqNumerator.toString())
|
||||||
contentView.xTimesPerYDaysYTextView.setText(freqDenominator.toString())
|
binding.xTimesPerYDaysYTextView.setText(freqDenominator.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -215,10 +224,10 @@ class FrequencyPickerDialog(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun uncheckAll() {
|
private fun uncheckAll() {
|
||||||
contentView.everyDayRadioButton.isChecked = false
|
binding.everyDayRadioButton.isChecked = false
|
||||||
contentView.everyXDaysRadioButton.isChecked = false
|
binding.everyXDaysRadioButton.isChecked = false
|
||||||
contentView.xTimesPerWeekRadioButton.isChecked = false
|
binding.xTimesPerWeekRadioButton.isChecked = false
|
||||||
contentView.xTimesPerMonthRadioButton.isChecked = false
|
binding.xTimesPerMonthRadioButton.isChecked = false
|
||||||
contentView.xTimesPerYDaysRadioButton.isChecked = false
|
binding.xTimesPerYDaysRadioButton.isChecked = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ class HistoryEditorDialog : AppCompatDialogFragment(), CommandRunner.Listener {
|
|||||||
theme = themeSwitcher.currentTheme,
|
theme = themeSwitcher.currentTheme,
|
||||||
today = DateUtils.getTodayWithOffset().toLocalDate(),
|
today = DateUtils.getTodayWithOffset().toLocalDate(),
|
||||||
onDateClickedListener = onDateClickedListener ?: object : OnDateClickedListener {},
|
onDateClickedListener = onDateClickedListener ?: object : OnDateClickedListener {},
|
||||||
padding = 10.0,
|
padding = 10.0
|
||||||
)
|
)
|
||||||
dataView = AndroidDataView(requireContext(), null)
|
dataView = AndroidDataView(requireContext(), null)
|
||||||
dataView.view = chart!!
|
dataView.view = chart!!
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ package org.isoron.uhabits.activities.common.dialogs
|
|||||||
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.provider.Settings
|
||||||
import android.text.method.DigitsKeyListener
|
import android.text.method.DigitsKeyListener
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.inputmethod.EditorInfo
|
||||||
import androidx.appcompat.app.AppCompatDialogFragment
|
import androidx.appcompat.app.AppCompatDialogFragment
|
||||||
import org.isoron.uhabits.HabitsApplication
|
import org.isoron.uhabits.HabitsApplication
|
||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
@@ -65,7 +67,7 @@ class NumberDialog : AppCompatDialogFragment() {
|
|||||||
save()
|
save()
|
||||||
}
|
}
|
||||||
view.skipBtnNumber.setOnClickListener {
|
view.skipBtnNumber.setOnClickListener {
|
||||||
view.value.setText((Entry.SKIP.toDouble() / 1000).toString())
|
view.value.setText(DecimalFormat("#.###").format((Entry.SKIP.toDouble() / 1000)))
|
||||||
save()
|
save()
|
||||||
}
|
}
|
||||||
view.notes.setOnEditorActionListener { v, actionId, event ->
|
view.notes.setOnEditorActionListener { v, actionId, event ->
|
||||||
@@ -86,6 +88,15 @@ class NumberDialog : AppCompatDialogFragment() {
|
|||||||
// https://stackoverflow.com/a/34256139
|
// https://stackoverflow.com/a/34256139
|
||||||
val separator = DecimalFormatSymbols.getInstance().decimalSeparator
|
val separator = DecimalFormatSymbols.getInstance().decimalSeparator
|
||||||
view.value.keyListener = DigitsKeyListener.getInstance("0123456789$separator")
|
view.value.keyListener = DigitsKeyListener.getInstance("0123456789$separator")
|
||||||
|
|
||||||
|
// https://github.com/flutter/flutter/issues/61175
|
||||||
|
val currKeyboard = Settings.Secure.getString(
|
||||||
|
requireContext().contentResolver,
|
||||||
|
Settings.Secure.DEFAULT_INPUT_METHOD
|
||||||
|
)
|
||||||
|
if (currKeyboard.contains("swiftkey") || currKeyboard.contains("samsung")) {
|
||||||
|
view.value.inputType = EditorInfo.TYPE_CLASS_TEXT
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun save() {
|
fun save() {
|
||||||
|
|||||||
@@ -181,8 +181,9 @@ class FrequencyChart : ScrollableChart {
|
|||||||
rect[0f, 0f, baseSize.toFloat()] = baseSize.toFloat()
|
rect[0f, 0f, baseSize.toFloat()] = baseSize.toFloat()
|
||||||
rect.offset(prevRect!!.left, prevRect!!.top + baseSize * j)
|
rect.offset(prevRect!!.left, prevRect!!.top + baseSize * j)
|
||||||
val i = localeWeekdayList[j] % 7
|
val i = localeWeekdayList[j] % 7
|
||||||
if (values != null)
|
if (values != null) {
|
||||||
drawMarker(canvas, rect, values[i], weekDaysInMonth[i])
|
drawMarker(canvas, rect, values[i], weekDaysInMonth[i])
|
||||||
|
}
|
||||||
rect.offset(0f, rowHeight)
|
rect.offset(0f, rowHeight)
|
||||||
}
|
}
|
||||||
drawFooter(canvas, rect, date)
|
drawFooter(canvas, rect, date)
|
||||||
@@ -196,13 +197,15 @@ class FrequencyChart : ScrollableChart {
|
|||||||
rect.centerY() - 0.1f * em,
|
rect.centerY() - 0.1f * em,
|
||||||
pText!!
|
pText!!
|
||||||
)
|
)
|
||||||
if (date[Calendar.MONTH] == 1) canvas.drawText(
|
if (date[Calendar.MONTH] == 1) {
|
||||||
|
canvas.drawText(
|
||||||
dfYear!!.format(time),
|
dfYear!!.format(time),
|
||||||
rect.centerX(),
|
rect.centerX(),
|
||||||
rect.centerY() + 0.9f * em,
|
rect.centerY() + 0.9f * em,
|
||||||
pText!!
|
pText!!
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun drawGrid(canvas: Canvas, rGrid: RectF?) {
|
private fun drawGrid(canvas: Canvas, rGrid: RectF?) {
|
||||||
val nRows = 7
|
val nRows = 7
|
||||||
|
|||||||
@@ -148,8 +148,12 @@ class RingView : View {
|
|||||||
pRing!!.color = inactiveColor!!
|
pRing!!.color = inactiveColor!!
|
||||||
activeCanvas.drawArc(rect!!, angle - 90, 360 - angle, true, pRing!!)
|
activeCanvas.drawArc(rect!!, angle - 90, 360 - angle, true, pRing!!)
|
||||||
if (thickness > 0) {
|
if (thickness > 0) {
|
||||||
if (isTransparencyEnabled) pRing!!.xfermode = XFERMODE_CLEAR else pRing!!.color =
|
if (isTransparencyEnabled) {
|
||||||
|
pRing!!.xfermode = XFERMODE_CLEAR
|
||||||
|
} else {
|
||||||
|
pRing!!.color =
|
||||||
backgroundColor!!
|
backgroundColor!!
|
||||||
|
}
|
||||||
rect!!.inset(thickness, thickness)
|
rect!!.inset(thickness, thickness)
|
||||||
activeCanvas.drawArc(rect!!, 0f, 360f, true, pRing!!)
|
activeCanvas.drawArc(rect!!, 0f, 360f, true, pRing!!)
|
||||||
pRing!!.xfermode = null
|
pRing!!.xfermode = null
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ abstract class ScrollableChart : View, GestureDetector.OnGestureListener, Animat
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onFling(
|
override fun onFling(
|
||||||
e1: MotionEvent,
|
e1: MotionEvent?,
|
||||||
e2: MotionEvent,
|
e2: MotionEvent,
|
||||||
velocityX: Float,
|
velocityX: Float,
|
||||||
velocityY: Float
|
velocityY: Float
|
||||||
@@ -116,7 +116,7 @@ abstract class ScrollableChart : View, GestureDetector.OnGestureListener, Animat
|
|||||||
return BundleSavedState(superState, bundle)
|
return BundleSavedState(superState, bundle)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onScroll(e1: MotionEvent?, e2: MotionEvent?, dx: Float, dy: Float): Boolean {
|
override fun onScroll(e1: MotionEvent?, e2: MotionEvent, dx: Float, dy: Float): Boolean {
|
||||||
var dx = dx
|
var dx = dx
|
||||||
if (scrollerBucketSize == 0) return false
|
if (scrollerBucketSize == 0) return false
|
||||||
if (abs(dx) > abs(dy)) {
|
if (abs(dx) > abs(dy)) {
|
||||||
|
|||||||
@@ -35,11 +35,6 @@ import androidx.appcompat.app.AppCompatActivity
|
|||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import com.android.datetimepicker.time.RadialPickerLayout
|
import com.android.datetimepicker.time.RadialPickerLayout
|
||||||
import com.android.datetimepicker.time.TimePickerDialog
|
import com.android.datetimepicker.time.TimePickerDialog
|
||||||
import kotlinx.android.synthetic.main.activity_edit_habit.nameInput
|
|
||||||
import kotlinx.android.synthetic.main.activity_edit_habit.notesInput
|
|
||||||
import kotlinx.android.synthetic.main.activity_edit_habit.questionInput
|
|
||||||
import kotlinx.android.synthetic.main.activity_edit_habit.targetInput
|
|
||||||
import kotlinx.android.synthetic.main.activity_edit_habit.unitInput
|
|
||||||
import org.isoron.platform.gui.toInt
|
import org.isoron.platform.gui.toInt
|
||||||
import org.isoron.uhabits.HabitsApplication
|
import org.isoron.uhabits.HabitsApplication
|
||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
@@ -271,9 +266,9 @@ class EditHabitActivity : AppCompatActivity() {
|
|||||||
habit.copyFrom(original)
|
habit.copyFrom(original)
|
||||||
}
|
}
|
||||||
|
|
||||||
habit.name = nameInput.text.trim().toString()
|
habit.name = binding.nameInput.text.trim().toString()
|
||||||
habit.question = questionInput.text.trim().toString()
|
habit.question = binding.questionInput.text.trim().toString()
|
||||||
habit.description = notesInput.text.trim().toString()
|
habit.description = binding.notesInput.text.trim().toString()
|
||||||
habit.color = color
|
habit.color = color
|
||||||
if (reminderHour >= 0) {
|
if (reminderHour >= 0) {
|
||||||
habit.reminder = Reminder(reminderHour, reminderMin, reminderDays)
|
habit.reminder = Reminder(reminderHour, reminderMin, reminderDays)
|
||||||
@@ -283,9 +278,9 @@ class EditHabitActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
habit.frequency = Frequency(freqNum, freqDen)
|
habit.frequency = Frequency(freqNum, freqDen)
|
||||||
if (habitType == HabitType.NUMERICAL) {
|
if (habitType == HabitType.NUMERICAL) {
|
||||||
habit.targetValue = targetInput.text.toString().toDouble()
|
habit.targetValue = binding.targetInput.text.toString().toDouble()
|
||||||
habit.targetType = targetType
|
habit.targetType = targetType
|
||||||
habit.unit = unitInput.text.trim().toString()
|
habit.unit = binding.unitInput.text.trim().toString()
|
||||||
}
|
}
|
||||||
habit.type = habitType
|
habit.type = habitType
|
||||||
|
|
||||||
@@ -308,13 +303,13 @@ class EditHabitActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private fun validate(): Boolean {
|
private fun validate(): Boolean {
|
||||||
var isValid = true
|
var isValid = true
|
||||||
if (nameInput.text.isEmpty()) {
|
if (binding.nameInput.text.isEmpty()) {
|
||||||
nameInput.error = getFormattedValidationError(R.string.validation_cannot_be_blank)
|
binding.nameInput.error = getFormattedValidationError(R.string.validation_cannot_be_blank)
|
||||||
isValid = false
|
isValid = false
|
||||||
}
|
}
|
||||||
if (habitType == HabitType.NUMERICAL) {
|
if (habitType == HabitType.NUMERICAL) {
|
||||||
if (targetInput.text.isEmpty()) {
|
if (binding.targetInput.text.isEmpty()) {
|
||||||
targetInput.error = getString(R.string.validation_cannot_be_blank)
|
binding.targetInput.error = getString(R.string.validation_cannot_be_blank)
|
||||||
isValid = false
|
isValid = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,12 +19,17 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.activities.habits.list
|
package org.isoron.uhabits.activities.habits.list
|
||||||
|
|
||||||
|
import android.Manifest.permission.POST_NOTIFICATIONS
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager.PERMISSION_GRANTED
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.content.ContextCompat.checkSelfPermission
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import org.isoron.uhabits.BaseExceptionHandler
|
import org.isoron.uhabits.BaseExceptionHandler
|
||||||
@@ -56,6 +61,16 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
|
|||||||
lateinit var midnightTimer: MidnightTimer
|
lateinit var midnightTimer: MidnightTimer
|
||||||
private val scope = CoroutineScope(Dispatchers.Main)
|
private val scope = CoroutineScope(Dispatchers.Main)
|
||||||
|
|
||||||
|
private var permissionAlreadyRequested = false
|
||||||
|
private val permissionLauncher =
|
||||||
|
registerForActivityResult(RequestPermission()) { isGranted: Boolean ->
|
||||||
|
if (isGranted) {
|
||||||
|
scheduleReminders()
|
||||||
|
} else {
|
||||||
|
Log.i("ListHabitsActivity", "POST_NOTIFICATIONS denied")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private lateinit var menu: ListHabitsMenu
|
private lateinit var menu: ListHabitsMenu
|
||||||
|
|
||||||
override fun onQuestionMarksChanged() {
|
override fun onQuestionMarksChanged() {
|
||||||
@@ -101,7 +116,26 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
|
|||||||
screen.onAttached()
|
screen.onAttached()
|
||||||
rootView.postInvalidate()
|
rootView.postInvalidate()
|
||||||
midnightTimer.onResume()
|
midnightTimer.onResume()
|
||||||
appComponent.reminderScheduler.scheduleAll()
|
|
||||||
|
if (appComponent.reminderScheduler.hasHabitsWithReminders()) {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
scheduleReminders()
|
||||||
|
} else {
|
||||||
|
if (checkSelfPermission(this, POST_NOTIFICATIONS) == PERMISSION_GRANTED) {
|
||||||
|
scheduleReminders()
|
||||||
|
} else {
|
||||||
|
// If we have not requested the permission yet, request it. Otherwide do
|
||||||
|
// nothing. This check is necessary to avoid an infinite onResume loop in case
|
||||||
|
// the user denies the permission.
|
||||||
|
if (!permissionAlreadyRequested) {
|
||||||
|
Log.i("ListHabitsActivity", "Requestion permission: POST_NOTIFICATIONS")
|
||||||
|
permissionLauncher.launch(POST_NOTIFICATIONS)
|
||||||
|
permissionAlreadyRequested = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
taskRunner.run {
|
taskRunner.run {
|
||||||
try {
|
try {
|
||||||
AutoBackup(this@ListHabitsActivity).run()
|
AutoBackup(this@ListHabitsActivity).run()
|
||||||
@@ -117,6 +151,10 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
|
|||||||
super.onResume()
|
super.onResume()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun scheduleReminders() {
|
||||||
|
appComponent.reminderScheduler.scheduleAll()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(m: Menu): Boolean {
|
override fun onCreateOptionsMenu(m: Menu): Boolean {
|
||||||
menu.onCreate(menuInflater, m)
|
menu.onCreate(menuInflater, m)
|
||||||
return true
|
return true
|
||||||
@@ -127,6 +165,7 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
|
|||||||
return menu.onItemSelected(item)
|
return menu.onItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Deprecated in Java")
|
||||||
override fun onActivityResult(request: Int, result: Int, data: Intent?) {
|
override fun onActivityResult(request: Int, result: Int, data: Intent?) {
|
||||||
super.onActivityResult(request, result, data)
|
super.onActivityResult(request, result, data)
|
||||||
screen.onResult(request, result, data)
|
screen.onResult(request, result, data)
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ class ListHabitsRootView @Inject constructor(
|
|||||||
title = resources.getString(R.string.main_activity_title),
|
title = resources.getString(R.string.main_activity_title),
|
||||||
color = PaletteColor(17),
|
color = PaletteColor(17),
|
||||||
displayHomeAsUpEnabled = false,
|
displayHomeAsUpEnabled = false,
|
||||||
theme = currentTheme(),
|
theme = currentTheme()
|
||||||
)
|
)
|
||||||
addView(rootView, MATCH_PARENT, MATCH_PARENT)
|
addView(rootView, MATCH_PARENT, MATCH_PARENT)
|
||||||
listAdapter.setListView(listView)
|
listAdapter.setListView(listView)
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ class ListHabitsScreen
|
|||||||
private val colorPickerFactory: ColorPickerDialogFactory,
|
private val colorPickerFactory: ColorPickerDialogFactory,
|
||||||
private val behavior: Lazy<ListHabitsBehavior>,
|
private val behavior: Lazy<ListHabitsBehavior>,
|
||||||
private val preferences: Preferences,
|
private val preferences: Preferences,
|
||||||
private val rootView: Lazy<ListHabitsRootView>,
|
private val rootView: Lazy<ListHabitsRootView>
|
||||||
) : CommandRunner.Listener,
|
) : CommandRunner.Listener,
|
||||||
ListHabitsBehavior.Screen,
|
ListHabitsBehavior.Screen,
|
||||||
ListHabitsMenuBehavior.Screen,
|
ListHabitsMenuBehavior.Screen,
|
||||||
@@ -321,8 +321,11 @@ class ListHabitsScreen
|
|||||||
private fun onExportDB() {
|
private fun onExportDB() {
|
||||||
taskRunner.execute(
|
taskRunner.execute(
|
||||||
exportDBFactory.create { filename ->
|
exportDBFactory.create { filename ->
|
||||||
if (filename != null) activity.showSendFileScreen(filename)
|
if (filename != null) {
|
||||||
else activity.showMessage(activity.resources.getString(R.string.could_not_export))
|
activity.showSendFileScreen(filename)
|
||||||
|
} else {
|
||||||
|
activity.showMessage(activity.resources.getString(R.string.could_not_export))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,8 +60,11 @@ abstract class ButtonPanelView<T : View>(
|
|||||||
repeat(buttonCount) { buttons.add(createButton()) }
|
repeat(buttonCount) { buttons.add(createButton()) }
|
||||||
|
|
||||||
removeAllViews()
|
removeAllViews()
|
||||||
if (reverse) buttons.reversed().forEach { addView(it) }
|
if (reverse) {
|
||||||
else buttons.forEach { addView(it) }
|
buttons.reversed().forEach { addView(it) }
|
||||||
|
} else {
|
||||||
|
buttons.forEach { addView(it) }
|
||||||
|
}
|
||||||
setupButtons()
|
setupButtons()
|
||||||
requestLayout()
|
requestLayout()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,13 +100,19 @@ class CheckmarkButtonView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(v: View) {
|
override fun onClick(v: View) {
|
||||||
if (preferences.isShortToggleEnabled) performToggle()
|
if (preferences.isShortToggleEnabled) {
|
||||||
else onEdit()
|
performToggle()
|
||||||
|
} else {
|
||||||
|
onEdit()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLongClick(v: View): Boolean {
|
override fun onLongClick(v: View): Boolean {
|
||||||
if (preferences.isShortToggleEnabled) onEdit()
|
if (preferences.isShortToggleEnabled) {
|
||||||
else performToggle()
|
onEdit()
|
||||||
|
} else {
|
||||||
|
performToggle()
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,8 +146,11 @@ class CheckmarkButtonView(
|
|||||||
paint.color = when (value) {
|
paint.color = when (value) {
|
||||||
YES_MANUAL, YES_AUTO, SKIP -> color
|
YES_MANUAL, YES_AUTO, SKIP -> color
|
||||||
NO -> {
|
NO -> {
|
||||||
if (preferences.areQuestionMarksEnabled) mediumContrastColor
|
if (preferences.areQuestionMarksEnabled) {
|
||||||
else lowContrastColor
|
mediumContrastColor
|
||||||
|
} else {
|
||||||
|
lowContrastColor
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else -> lowContrastColor
|
else -> lowContrastColor
|
||||||
}
|
}
|
||||||
@@ -149,8 +158,11 @@ class CheckmarkButtonView(
|
|||||||
SKIP -> R.string.fa_skipped
|
SKIP -> R.string.fa_skipped
|
||||||
NO -> R.string.fa_times
|
NO -> R.string.fa_times
|
||||||
UNKNOWN -> {
|
UNKNOWN -> {
|
||||||
if (preferences.areQuestionMarksEnabled) R.string.fa_question
|
if (preferences.areQuestionMarksEnabled) {
|
||||||
else R.string.fa_times
|
R.string.fa_question
|
||||||
|
} else {
|
||||||
|
R.string.fa_times
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else -> R.string.fa_check
|
else -> R.string.fa_check
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,10 +156,11 @@ class HabitCardListController @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun notifyListener() {
|
private fun notifyListener() {
|
||||||
if (activeMode is SelectionMode)
|
if (activeMode is SelectionMode) {
|
||||||
selectionMenu.get().onSelectionChange()
|
selectionMenu.get().onSelectionChange()
|
||||||
else
|
} else {
|
||||||
selectionMenu.get().onSelectionFinish()
|
selectionMenu.get().onSelectionFinish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -225,7 +225,6 @@ class HabitCardView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun copyAttributesFrom(h: Habit) {
|
private fun copyAttributesFrom(h: Habit) {
|
||||||
|
|
||||||
fun getActiveColor(habit: Habit): Int {
|
fun getActiveColor(habit: Habit): Int {
|
||||||
return when (habit.isArchived) {
|
return when (habit.isArchived) {
|
||||||
true -> sres.getColor(R.attr.contrast60)
|
true -> sres.getColor(R.attr.contrast60)
|
||||||
@@ -271,7 +270,6 @@ class HabitCardView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateBackground(isSelected: Boolean) {
|
private fun updateBackground(isSelected: Boolean) {
|
||||||
|
|
||||||
val background = when (isSelected) {
|
val background = when (isSelected) {
|
||||||
true -> R.drawable.selected_box
|
true -> R.drawable.selected_box
|
||||||
false -> R.drawable.ripple
|
false -> R.drawable.ripple
|
||||||
|
|||||||
@@ -124,15 +124,20 @@ class HeaderView(
|
|||||||
rect.set(0f, 0f, width, height)
|
rect.set(0f, 0f, width, height)
|
||||||
rect.offset(canvas.width.toFloat() - dp(3.0f), 0f)
|
rect.offset(canvas.width.toFloat() - dp(3.0f), 0f)
|
||||||
|
|
||||||
if (isReversed) rect.offset(-(index + 1) * width, 0f)
|
if (isReversed) {
|
||||||
else rect.offset((index - buttonCount) * width, 0f)
|
rect.offset(-(index + 1) * width, 0f)
|
||||||
|
} else {
|
||||||
|
rect.offset((index - buttonCount) * width, 0f)
|
||||||
|
}
|
||||||
|
|
||||||
if (isRTL()) rect.set(
|
if (isRTL()) {
|
||||||
|
rect.set(
|
||||||
canvas.width - rect.right,
|
canvas.width - rect.right,
|
||||||
rect.top,
|
rect.top,
|
||||||
canvas.width - rect.left,
|
canvas.width - rect.left,
|
||||||
rect.bottom
|
rect.bottom
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val y1 = rect.centerY() - 0.25 * em
|
val y1 = rect.centerY() - 0.25 * em
|
||||||
val y2 = rect.centerY() + 1.25 * em
|
val y2 = rect.centerY() + 1.25 * em
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
|
|||||||
habit = habit,
|
habit = habit,
|
||||||
habitList = habitList,
|
habitList = habitList,
|
||||||
preferences = preferences,
|
preferences = preferences,
|
||||||
screen = screen,
|
screen = screen
|
||||||
)
|
)
|
||||||
|
|
||||||
view = ShowHabitView(this)
|
view = ShowHabitView(this)
|
||||||
@@ -99,13 +99,13 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
|
|||||||
habitList = habitList,
|
habitList = habitList,
|
||||||
screen = screen,
|
screen = screen,
|
||||||
system = HabitsDirFinder(AndroidDirFinder(this)),
|
system = HabitsDirFinder(AndroidDirFinder(this)),
|
||||||
taskRunner = appComponent.taskRunner,
|
taskRunner = appComponent.taskRunner
|
||||||
)
|
)
|
||||||
|
|
||||||
menu = ShowHabitMenu(
|
menu = ShowHabitMenu(
|
||||||
activity = this,
|
activity = this,
|
||||||
presenter = menuPresenter,
|
presenter = menuPresenter,
|
||||||
preferences = preferences,
|
preferences = preferences
|
||||||
)
|
)
|
||||||
|
|
||||||
view.setListener(presenter)
|
view.setListener(presenter)
|
||||||
@@ -150,7 +150,7 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
|
|||||||
ShowHabitPresenter.buildState(
|
ShowHabitPresenter.buildState(
|
||||||
habit = habit,
|
habit = habit,
|
||||||
preferences = preferences,
|
preferences = preferences,
|
||||||
theme = themeSwitcher.currentTheme,
|
theme = themeSwitcher.currentTheme
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitMenuPresenter
|
|||||||
class ShowHabitMenu(
|
class ShowHabitMenu(
|
||||||
val activity: ShowHabitActivity,
|
val activity: ShowHabitActivity,
|
||||||
val presenter: ShowHabitMenuPresenter,
|
val presenter: ShowHabitMenuPresenter,
|
||||||
val preferences: Preferences,
|
val preferences: Preferences
|
||||||
) {
|
) {
|
||||||
fun onCreateOptionsMenu(menu: Menu): Boolean {
|
fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
activity.menuInflater.inflate(R.menu.show_habit, menu)
|
activity.menuInflater.inflate(R.menu.show_habit, menu)
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class ShowHabitView(context: Context) : FrameLayout(context) {
|
|||||||
binding.toolbar,
|
binding.toolbar,
|
||||||
title = data.title,
|
title = data.title,
|
||||||
color = data.color,
|
color = data.color,
|
||||||
theme = data.theme,
|
theme = data.theme
|
||||||
)
|
)
|
||||||
binding.subtitleCard.setState(data.subtitle)
|
binding.subtitleCard.setState(data.subtitle)
|
||||||
binding.overviewCard.setState(data.overview)
|
binding.overviewCard.setState(data.overview)
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class BarCardView(context: Context, attrs: AttributeSet) : LinearLayout(context,
|
|||||||
|
|
||||||
fun setState(state: BarCardState) {
|
fun setState(state: BarCardState) {
|
||||||
val androidColor = state.theme.color(state.color).toInt()
|
val androidColor = state.theme.color(state.color).toInt()
|
||||||
binding.chart.view = BarChart(state.theme, JavaLocalDateFormatter(Locale.US)).apply {
|
binding.chart.view = BarChart(state.theme, JavaLocalDateFormatter(Locale.getDefault())).apply {
|
||||||
series = mutableListOf(state.entries.map { it.value / 1000.0 })
|
series = mutableListOf(state.entries.map { it.value / 1000.0 })
|
||||||
colors = mutableListOf(theme.color(state.color.paletteIndex))
|
colors = mutableListOf(theme.color(state.color.paletteIndex))
|
||||||
axis = state.entries.map { it.timestamp.toLocalDate() }
|
axis = state.entries.map { it.timestamp.toLocalDate() }
|
||||||
@@ -63,7 +63,7 @@ class BarCardView(context: Context, attrs: AttributeSet) : LinearLayout(context,
|
|||||||
parent: AdapterView<*>?,
|
parent: AdapterView<*>?,
|
||||||
view: View?,
|
view: View?,
|
||||||
position: Int,
|
position: Int,
|
||||||
id: Long,
|
id: Long
|
||||||
) {
|
) {
|
||||||
presenter.onBoolSpinnerPosition(position)
|
presenter.onBoolSpinnerPosition(position)
|
||||||
}
|
}
|
||||||
@@ -77,7 +77,7 @@ class BarCardView(context: Context, attrs: AttributeSet) : LinearLayout(context,
|
|||||||
parent: AdapterView<*>?,
|
parent: AdapterView<*>?,
|
||||||
view: View?,
|
view: View?,
|
||||||
position: Int,
|
position: Int,
|
||||||
id: Long,
|
id: Long
|
||||||
) {
|
) {
|
||||||
presenter.onNumericalSpinnerPosition(position)
|
presenter.onNumericalSpinnerPosition(position)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class HistoryCardView(context: Context, attrs: AttributeSet) : LinearLayout(cont
|
|||||||
series = state.series,
|
series = state.series,
|
||||||
defaultSquare = state.defaultSquare,
|
defaultSquare = state.defaultSquare,
|
||||||
notesIndicators = state.notesIndicators,
|
notesIndicators = state.notesIndicators,
|
||||||
firstWeekday = state.firstWeekday,
|
firstWeekday = state.firstWeekday
|
||||||
)
|
)
|
||||||
binding.chart.postInvalidate()
|
binding.chart.postInvalidate()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class SubtitleCardView(context: Context, attrs: AttributeSet) : LinearLayout(con
|
|||||||
binding.frequencyLabel.text = formatFrequency(
|
binding.frequencyLabel.text = formatFrequency(
|
||||||
state.frequency.numerator,
|
state.frequency.numerator,
|
||||||
state.frequency.denominator,
|
state.frequency.denominator,
|
||||||
resources,
|
resources
|
||||||
)
|
)
|
||||||
binding.questionLabel.setTextColor(color)
|
binding.questionLabel.setTextColor(color)
|
||||||
binding.questionLabel.text = state.question
|
binding.questionLabel.text = state.question
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
toolbar = binding.toolbar,
|
toolbar = binding.toolbar,
|
||||||
title = resources.getString(R.string.settings),
|
title = resources.getString(R.string.settings),
|
||||||
color = PaletteColor(11),
|
color = PaletteColor(11),
|
||||||
theme = themeSwitcher.currentTheme,
|
theme = themeSwitcher.currentTheme
|
||||||
)
|
)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis
|
|||||||
private var ringtoneManager: RingtoneManager? = null
|
private var ringtoneManager: RingtoneManager? = null
|
||||||
private lateinit var prefs: Preferences
|
private lateinit var prefs: Preferences
|
||||||
private var widgetUpdater: WidgetUpdater? = null
|
private var widgetUpdater: WidgetUpdater? = null
|
||||||
|
|
||||||
|
@Deprecated("Deprecated in Java")
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
if (requestCode == RINGTONE_REQUEST_CODE) {
|
if (requestCode == RINGTONE_REQUEST_CODE) {
|
||||||
ringtoneManager!!.update(data)
|
ringtoneManager!!.update(data)
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class EditSettingActivity : AppCompatActivity() {
|
|||||||
val habits = app.component.habitList.getFiltered(
|
val habits = app.component.habitList.getFiltered(
|
||||||
HabitMatcher(
|
HabitMatcher(
|
||||||
isArchivedAllowed = false,
|
isArchivedAllowed = false,
|
||||||
isCompletedAllowed = true,
|
isCompletedAllowed = true
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
AndroidThemeSwitcher(this, app.component.preferences).apply()
|
AndroidThemeSwitcher(this, app.component.preferences).apply()
|
||||||
@@ -43,7 +43,7 @@ class EditSettingActivity : AppCompatActivity() {
|
|||||||
context = this,
|
context = this,
|
||||||
habitList = app.component.habitList,
|
habitList = app.component.habitList,
|
||||||
onSave = controller::onSave,
|
onSave = controller::onSave,
|
||||||
args = args,
|
args = args
|
||||||
)
|
)
|
||||||
setContentView(view)
|
setContentView(view)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class EditSettingRootView(
|
|||||||
title = resources.getString(R.string.app_name),
|
title = resources.getString(R.string.app_name),
|
||||||
color = PaletteColor(11),
|
color = PaletteColor(11),
|
||||||
displayHomeAsUpEnabled = false,
|
displayHomeAsUpEnabled = false,
|
||||||
theme = currentTheme(),
|
theme = currentTheme()
|
||||||
)
|
)
|
||||||
populateHabitSpinner()
|
populateHabitSpinner()
|
||||||
binding.habitSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
binding.habitSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||||
@@ -69,7 +69,7 @@ class EditSettingRootView(
|
|||||||
val habit = habitList.getByPosition(binding.habitSpinner.selectedItemPosition)
|
val habit = habitList.getByPosition(binding.habitSpinner.selectedItemPosition)
|
||||||
val action = mapSpinnerPositionToAction(
|
val action = mapSpinnerPositionToAction(
|
||||||
isNumerical = habit.isNumerical,
|
isNumerical = habit.isNumerical,
|
||||||
itemPosition = binding.actionSpinner.selectedItemPosition,
|
itemPosition = binding.actionSpinner.selectedItemPosition
|
||||||
)
|
)
|
||||||
onSave(habit, action)
|
onSave(habit, action)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,22 +27,34 @@ class AndroidCursor(private val cursor: android.database.Cursor) : Cursor {
|
|||||||
override fun moveToNext() = cursor.moveToNext()
|
override fun moveToNext() = cursor.moveToNext()
|
||||||
|
|
||||||
override fun getInt(index: Int): Int? {
|
override fun getInt(index: Int): Int? {
|
||||||
return if (cursor.isNull(index)) null
|
return if (cursor.isNull(index)) {
|
||||||
else cursor.getInt(index)
|
null
|
||||||
|
} else {
|
||||||
|
cursor.getInt(index)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLong(index: Int): Long? {
|
override fun getLong(index: Int): Long? {
|
||||||
return if (cursor.isNull(index)) null
|
return if (cursor.isNull(index)) {
|
||||||
else cursor.getLong(index)
|
null
|
||||||
|
} else {
|
||||||
|
cursor.getLong(index)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDouble(index: Int): Double? {
|
override fun getDouble(index: Int): Double? {
|
||||||
return if (cursor.isNull(index)) null
|
return if (cursor.isNull(index)) {
|
||||||
else cursor.getDouble(index)
|
null
|
||||||
|
} else {
|
||||||
|
cursor.getDouble(index)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getString(index: Int): String? {
|
override fun getString(index: Int): String? {
|
||||||
return if (cursor.isNull(index)) null
|
return if (cursor.isNull(index)) {
|
||||||
else cursor.getString(index)
|
null
|
||||||
|
} else {
|
||||||
|
cursor.getString(index)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import java.io.File
|
|||||||
|
|
||||||
class AndroidDatabase(
|
class AndroidDatabase(
|
||||||
private val db: SQLiteDatabase,
|
private val db: SQLiteDatabase,
|
||||||
override val file: File?,
|
override val file: File?
|
||||||
) : Database {
|
) : Database {
|
||||||
|
|
||||||
override fun beginTransaction() = db.beginTransaction()
|
override fun beginTransaction() = db.beginTransaction()
|
||||||
@@ -45,7 +45,7 @@ class AndroidDatabase(
|
|||||||
tableName: String,
|
tableName: String,
|
||||||
values: Map<String, Any?>,
|
values: Map<String, Any?>,
|
||||||
where: String,
|
where: String,
|
||||||
vararg params: String,
|
vararg params: String
|
||||||
): Int {
|
): Int {
|
||||||
val contValues = mapToContentValues(values)
|
val contValues = mapToContentValues(values)
|
||||||
return db.update(tableName, contValues, where, params)
|
return db.update(tableName, contValues, where, params)
|
||||||
@@ -59,7 +59,7 @@ class AndroidDatabase(
|
|||||||
override fun delete(
|
override fun delete(
|
||||||
tableName: String,
|
tableName: String,
|
||||||
where: String,
|
where: String,
|
||||||
vararg params: String,
|
vararg params: String
|
||||||
) {
|
) {
|
||||||
db.delete(tableName, where, params)
|
db.delete(tableName, where, params)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class AndroidDatabaseOpener @Inject constructor() : DatabaseOpener {
|
|||||||
db = SQLiteDatabase.openDatabase(
|
db = SQLiteDatabase.openDatabase(
|
||||||
file.absolutePath,
|
file.absolutePath,
|
||||||
null,
|
null,
|
||||||
SQLiteDatabase.OPEN_READWRITE,
|
SQLiteDatabase.OPEN_READWRITE
|
||||||
),
|
),
|
||||||
file = file
|
file = file
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -25,5 +25,6 @@ import dagger.Provides
|
|||||||
@Module
|
@Module
|
||||||
class ActivityContextModule(
|
class ActivityContextModule(
|
||||||
@get:Provides
|
@get:Provides
|
||||||
@get:ActivityContext val context: Context
|
@get:ActivityContext
|
||||||
|
val context: Context
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -26,5 +26,6 @@ import dagger.Provides
|
|||||||
class AppContextModule(
|
class AppContextModule(
|
||||||
@get:Provides
|
@get:Provides
|
||||||
@get:AppContext
|
@get:AppContext
|
||||||
@param:AppContext val context: Context
|
@param:AppContext
|
||||||
|
val context: Context
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -53,8 +53,9 @@ class IntentParser
|
|||||||
var timestamp = intent.getLongExtra("timestamp", today)
|
var timestamp = intent.getLongExtra("timestamp", today)
|
||||||
timestamp = DateUtils.getStartOfDay(timestamp)
|
timestamp = DateUtils.getStartOfDay(timestamp)
|
||||||
|
|
||||||
if (timestamp < 0 || timestamp > today)
|
if (timestamp < 0 || timestamp > today) {
|
||||||
throw IllegalArgumentException("timestamp is not valid")
|
throw IllegalArgumentException("timestamp is not valid")
|
||||||
|
}
|
||||||
|
|
||||||
return Timestamp(timestamp)
|
return Timestamp(timestamp)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import android.app.AlarmManager.RTC_WAKEUP
|
|||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Context.ALARM_SERVICE
|
import android.content.Context.ALARM_SERVICE
|
||||||
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import org.isoron.uhabits.core.AppScope
|
import org.isoron.uhabits.core.AppScope
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
@@ -56,6 +57,10 @@ class IntentScheduler
|
|||||||
)
|
)
|
||||||
return SchedulerResult.IGNORED
|
return SchedulerResult.IGNORED
|
||||||
}
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !manager.canScheduleExactAlarms()) {
|
||||||
|
Log.e("IntentScheduler", "No permission to schedule exact alarms")
|
||||||
|
return SchedulerResult.IGNORED
|
||||||
|
}
|
||||||
manager.setExactAndAllowWhileIdle(alarmType, timestamp, intent)
|
manager.setExactAndAllowWhileIdle(alarmType, timestamp, intent)
|
||||||
return SchedulerResult.OK
|
return SchedulerResult.OK
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,7 +96,6 @@ class AndroidNotificationTray
|
|||||||
timestamp: Timestamp,
|
timestamp: Timestamp,
|
||||||
disableSound: Boolean = false
|
disableSound: Boolean = false
|
||||||
): Notification {
|
): Notification {
|
||||||
|
|
||||||
val addRepetitionAction = Action(
|
val addRepetitionAction = Action(
|
||||||
R.drawable.ic_action_check,
|
R.drawable.ic_action_check,
|
||||||
context.getString(R.string.yes),
|
context.getString(R.string.yes),
|
||||||
@@ -146,8 +145,9 @@ class AndroidNotificationTray
|
|||||||
.addAction(removeRepetitionAction)
|
.addAction(removeRepetitionAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!disableSound)
|
if (!disableSound) {
|
||||||
builder.setSound(ringtoneManager.getURI())
|
builder.setSound(ringtoneManager.getURI())
|
||||||
|
}
|
||||||
|
|
||||||
if (SDK_INT < Build.VERSION_CODES.S) {
|
if (SDK_INT < Build.VERSION_CODES.S) {
|
||||||
val snoozeAction = Action(
|
val snoozeAction = Action(
|
||||||
|
|||||||
@@ -61,8 +61,9 @@ class RingtoneManager
|
|||||||
"pref_ringtone_uri",
|
"pref_ringtone_uri",
|
||||||
defaultRingtoneUri.toString()
|
defaultRingtoneUri.toString()
|
||||||
)!!
|
)!!
|
||||||
if (prefRingtoneUri.isNotEmpty())
|
if (prefRingtoneUri.isNotEmpty()) {
|
||||||
ringtoneUri = Uri.parse(prefRingtoneUri)
|
ringtoneUri = Uri.parse(prefRingtoneUri)
|
||||||
|
}
|
||||||
|
|
||||||
return ringtoneUri
|
return ringtoneUri
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,7 +103,9 @@ class SnoozeDelayPickerActivity : FragmentActivity(), OnItemClickListener {
|
|||||||
if (snoozeValues[position] >= 0) {
|
if (snoozeValues[position] >= 0) {
|
||||||
reminderController!!.onSnoozeDelayPicked(habit!!, snoozeValues[position])
|
reminderController!!.onSnoozeDelayPicked(habit!!, snoozeValues[position])
|
||||||
finish()
|
finish()
|
||||||
} else showTimePicker()
|
} else {
|
||||||
|
showTimePicker()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun finish() {
|
override fun finish() {
|
||||||
|
|||||||
@@ -66,8 +66,14 @@ class ReminderController @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onDismiss(habit: Habit) {
|
fun onDismiss(habit: Habit) {
|
||||||
|
if (preferences.shouldMakeNotificationsSticky()) {
|
||||||
|
// This is a workaround to keep sticky notifications non-dismissible in Android 14+.
|
||||||
|
// If the notification is dismissed, we immediately reshow it.
|
||||||
|
notificationTray.reshow(habit)
|
||||||
|
} else {
|
||||||
notificationTray.cancel(habit)
|
notificationTray.cancel(habit)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun showSnoozeDelayPicker(habit: Habit, context: Context) {
|
private fun showSnoozeDelayPicker(habit: Habit, context: Context) {
|
||||||
context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
|
context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
|
||||||
|
|||||||
@@ -60,12 +60,14 @@ class AndroidTaskRunner : TaskRunner {
|
|||||||
publishProgress(progress)
|
publishProgress(progress)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Deprecated in Java")
|
||||||
override fun doInBackground(vararg params: Void?): Void? {
|
override fun doInBackground(vararg params: Void?): Void? {
|
||||||
if (isCancelled) return null
|
if (isCancelled) return null
|
||||||
task.doInBackground()
|
task.doInBackground()
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Deprecated in Java")
|
||||||
override fun onPostExecute(aVoid: Void?) {
|
override fun onPostExecute(aVoid: Void?) {
|
||||||
if (isCancelled) return
|
if (isCancelled) return
|
||||||
task.onPostExecute()
|
task.onPostExecute()
|
||||||
@@ -74,6 +76,7 @@ class AndroidTaskRunner : TaskRunner {
|
|||||||
for (l in listeners) l.onTaskFinished(task)
|
for (l in listeners) l.onTaskFinished(task)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Deprecated in Java")
|
||||||
override fun onPreExecute() {
|
override fun onPreExecute() {
|
||||||
if (isCancelled) return
|
if (isCancelled) return
|
||||||
for (l in listeners) l.onTaskStarted(task)
|
for (l in listeners) l.onTaskStarted(task)
|
||||||
@@ -82,6 +85,7 @@ class AndroidTaskRunner : TaskRunner {
|
|||||||
task.onPreExecute()
|
task.onPreExecute()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Deprecated in Java")
|
||||||
override fun onProgressUpdate(vararg values: Int?) {
|
override fun onProgressUpdate(vararg values: Int?) {
|
||||||
values[0]?.let { task.onProgressUpdate(it) }
|
values[0]?.let { task.onProgressUpdate(it) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import javax.inject.Inject
|
|||||||
class ExportDBTaskFactory
|
class ExportDBTaskFactory
|
||||||
@Inject constructor(
|
@Inject constructor(
|
||||||
@AppContext private val context: Context,
|
@AppContext private val context: Context,
|
||||||
private val system: AndroidDirFinder,
|
private val system: AndroidDirFinder
|
||||||
) {
|
) {
|
||||||
fun create(listener: ExportDBTask.Listener) = ExportDBTask(context, system, listener)
|
fun create(listener: ExportDBTask.Listener) = ExportDBTask(context, system, listener)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import javax.inject.Inject
|
|||||||
class ImportDataTaskFactory
|
class ImportDataTaskFactory
|
||||||
@Inject constructor(
|
@Inject constructor(
|
||||||
private val importer: GenericImporter,
|
private val importer: GenericImporter,
|
||||||
private val modelFactory: ModelFactory,
|
private val modelFactory: ModelFactory
|
||||||
) {
|
) {
|
||||||
fun create(file: File, listener: ImportDataTask.Listener) =
|
fun create(file: File, listener: ImportDataTask.Listener) =
|
||||||
ImportDataTask(importer, modelFactory, file, listener)
|
ImportDataTask(importer, modelFactory, file, listener)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import org.jetbrains.annotations.Contract
|
|||||||
|
|
||||||
object AttributeSetUtils {
|
object AttributeSetUtils {
|
||||||
const val ISORON_NAMESPACE = "http://isoron.org/android"
|
const val ISORON_NAMESPACE = "http://isoron.org/android"
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getAttribute(
|
fun getAttribute(
|
||||||
context: Context,
|
context: Context,
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import java.text.SimpleDateFormat
|
|||||||
|
|
||||||
object DatabaseUtils {
|
object DatabaseUtils {
|
||||||
private var opener: HabitsDatabaseOpener? = null
|
private var opener: HabitsDatabaseOpener? = null
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getDatabaseFile(context: Context): File {
|
fun getDatabaseFile(context: Context): File {
|
||||||
val databaseFilename = databaseFilename
|
val databaseFilename = databaseFilename
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ fun RelativeLayout.addBelow(
|
|||||||
height: Int = WRAP_CONTENT,
|
height: Int = WRAP_CONTENT,
|
||||||
applyCustomRules: (params: RelativeLayout.LayoutParams) -> Unit = {}
|
applyCustomRules: (params: RelativeLayout.LayoutParams) -> Unit = {}
|
||||||
) {
|
) {
|
||||||
|
|
||||||
view.layoutParams = RelativeLayout.LayoutParams(width, height).apply {
|
view.layoutParams = RelativeLayout.LayoutParams(width, height).apply {
|
||||||
addRule(BELOW, subject.id)
|
addRule(BELOW, subject.id)
|
||||||
applyCustomRules(this)
|
applyCustomRules(this)
|
||||||
@@ -75,7 +74,6 @@ fun RelativeLayout.addAtBottom(
|
|||||||
width: Int = MATCH_PARENT,
|
width: Int = MATCH_PARENT,
|
||||||
height: Int = WRAP_CONTENT
|
height: Int = WRAP_CONTENT
|
||||||
) {
|
) {
|
||||||
|
|
||||||
view.layoutParams = RelativeLayout.LayoutParams(width, height).apply {
|
view.layoutParams = RelativeLayout.LayoutParams(width, height).apply {
|
||||||
addRule(ALIGN_PARENT_BOTTOM)
|
addRule(ALIGN_PARENT_BOTTOM)
|
||||||
}
|
}
|
||||||
@@ -88,7 +86,6 @@ fun RelativeLayout.addAtTop(
|
|||||||
width: Int = MATCH_PARENT,
|
width: Int = MATCH_PARENT,
|
||||||
height: Int = WRAP_CONTENT
|
height: Int = WRAP_CONTENT
|
||||||
) {
|
) {
|
||||||
|
|
||||||
view.layoutParams = RelativeLayout.LayoutParams(width, height).apply {
|
view.layoutParams = RelativeLayout.LayoutParams(width, height).apply {
|
||||||
addRule(ALIGN_PARENT_TOP)
|
addRule(ALIGN_PARENT_TOP)
|
||||||
}
|
}
|
||||||
@@ -159,7 +156,7 @@ fun Activity.restartWithFade(cls: Class<*>?) {
|
|||||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
|
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
|
||||||
startActivity(Intent(this, cls))
|
startActivity(Intent(this, cls))
|
||||||
},
|
},
|
||||||
500,
|
500
|
||||||
) // HACK: Let the menu disappear first
|
) // HACK: Let the menu disappear first
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +165,7 @@ fun View.setupToolbar(
|
|||||||
title: String,
|
title: String,
|
||||||
color: PaletteColor,
|
color: PaletteColor,
|
||||||
theme: Theme,
|
theme: Theme,
|
||||||
displayHomeAsUpEnabled: Boolean = true,
|
displayHomeAsUpEnabled: Boolean = true
|
||||||
) {
|
) {
|
||||||
toolbar.elevation = InterfaceUtils.dpToPixels(context, 2f)
|
toolbar.elevation = InterfaceUtils.dpToPixels(context, 2f)
|
||||||
val res = StyledResources(context)
|
val res = StyledResources(context)
|
||||||
|
|||||||
@@ -147,11 +147,12 @@ abstract class BaseWidget(val context: Context, val id: Int, val stacked: Boolea
|
|||||||
|
|
||||||
protected val preferedBackgroundAlpha: Int
|
protected val preferedBackgroundAlpha: Int
|
||||||
get() {
|
get() {
|
||||||
return if (stacked)
|
return if (stacked) {
|
||||||
255
|
255
|
||||||
else
|
} else {
|
||||||
prefs.widgetOpacity
|
prefs.widgetOpacity
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val app = context.applicationContext as HabitsApplication
|
val app = context.applicationContext as HabitsApplication
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ open class CheckmarkWidget(
|
|||||||
context: Context,
|
context: Context,
|
||||||
widgetId: Int,
|
widgetId: Int,
|
||||||
protected val habit: Habit,
|
protected val habit: Habit,
|
||||||
stacked: Boolean = false,
|
stacked: Boolean = false
|
||||||
) : BaseWidget(context, widgetId, stacked) {
|
) : BaseWidget(context, widgetId, stacked) {
|
||||||
|
|
||||||
override val defaultHeight: Int = 125
|
override val defaultHeight: Int = 125
|
||||||
|
|||||||
@@ -23,7 +23,10 @@ import android.content.Context
|
|||||||
class CheckmarkWidgetProvider : BaseWidgetProvider() {
|
class CheckmarkWidgetProvider : BaseWidgetProvider() {
|
||||||
override fun getWidgetFromId(context: Context, id: Int): BaseWidget {
|
override fun getWidgetFromId(context: Context, id: Int): BaseWidget {
|
||||||
val habits = getHabitsFromWidgetId(id)
|
val habits = getHabitsFromWidgetId(id)
|
||||||
return if (habits.size == 1) CheckmarkWidget(context, id, habits[0])
|
return if (habits.size == 1) {
|
||||||
else StackWidget(context, id, StackWidgetType.CHECKMARK, habits)
|
CheckmarkWidget(context, id, habits[0])
|
||||||
|
} else {
|
||||||
|
StackWidget(context, id, StackWidgetType.CHECKMARK, habits)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import org.isoron.uhabits.widgets.views.EmptyWidgetView
|
|||||||
class EmptyWidget(
|
class EmptyWidget(
|
||||||
context: Context,
|
context: Context,
|
||||||
widgetId: Int,
|
widgetId: Int,
|
||||||
stacked: Boolean = false,
|
stacked: Boolean = false
|
||||||
) : BaseWidget(context, widgetId, stacked) {
|
) : BaseWidget(context, widgetId, stacked) {
|
||||||
override val defaultHeight: Int = 200
|
override val defaultHeight: Int = 200
|
||||||
override val defaultWidth: Int = 200
|
override val defaultWidth: Int = 200
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class FrequencyWidget(
|
|||||||
widgetId: Int,
|
widgetId: Int,
|
||||||
private val habit: Habit,
|
private val habit: Habit,
|
||||||
private val firstWeekday: Int,
|
private val firstWeekday: Int,
|
||||||
stacked: Boolean = false,
|
stacked: Boolean = false
|
||||||
) : BaseWidget(context, widgetId, stacked) {
|
) : BaseWidget(context, widgetId, stacked) {
|
||||||
override val defaultHeight: Int = 200
|
override val defaultHeight: Int = 200
|
||||||
override val defaultWidth: Int = 200
|
override val defaultWidth: Int = 200
|
||||||
|
|||||||
@@ -24,12 +24,15 @@ import android.content.Context
|
|||||||
class FrequencyWidgetProvider : BaseWidgetProvider() {
|
class FrequencyWidgetProvider : BaseWidgetProvider() {
|
||||||
override fun getWidgetFromId(context: Context, id: Int): BaseWidget {
|
override fun getWidgetFromId(context: Context, id: Int): BaseWidget {
|
||||||
val habits = getHabitsFromWidgetId(id)
|
val habits = getHabitsFromWidgetId(id)
|
||||||
return if (habits.size == 1) FrequencyWidget(
|
return if (habits.size == 1) {
|
||||||
|
FrequencyWidget(
|
||||||
context,
|
context,
|
||||||
id,
|
id,
|
||||||
habits[0],
|
habits[0],
|
||||||
preferences.firstWeekdayInt
|
preferences.firstWeekdayInt
|
||||||
)
|
)
|
||||||
else StackWidget(context, id, StackWidgetType.FREQUENCY, habits)
|
} else {
|
||||||
|
StackWidget(context, id, StackWidgetType.FREQUENCY, habits)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class HistoryWidget(
|
|||||||
context: Context,
|
context: Context,
|
||||||
id: Int,
|
id: Int,
|
||||||
private val habit: Habit,
|
private val habit: Habit,
|
||||||
stacked: Boolean = false,
|
stacked: Boolean = false
|
||||||
) : BaseWidget(context, id, stacked) {
|
) : BaseWidget(context, id, stacked) {
|
||||||
|
|
||||||
override val defaultHeight: Int = 250
|
override val defaultHeight: Int = 250
|
||||||
@@ -53,7 +53,7 @@ class HistoryWidget(
|
|||||||
val model = HistoryCardPresenter.buildState(
|
val model = HistoryCardPresenter.buildState(
|
||||||
habit = habit,
|
habit = habit,
|
||||||
firstWeekday = prefs.firstWeekday,
|
firstWeekday = prefs.firstWeekday,
|
||||||
theme = WidgetTheme(),
|
theme = WidgetTheme()
|
||||||
)
|
)
|
||||||
(widgetView.dataView as AndroidDataView).apply {
|
(widgetView.dataView as AndroidDataView).apply {
|
||||||
val historyChart = (this.view as HistoryChart)
|
val historyChart = (this.view as HistoryChart)
|
||||||
@@ -75,7 +75,7 @@ class HistoryWidget(
|
|||||||
firstWeekday = prefs.firstWeekday,
|
firstWeekday = prefs.firstWeekday,
|
||||||
series = listOf(),
|
series = listOf(),
|
||||||
defaultSquare = HistoryChart.Square.OFF,
|
defaultSquare = HistoryChart.Square.OFF,
|
||||||
notesIndicators = listOf(),
|
notesIndicators = listOf()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
).apply {
|
).apply {
|
||||||
|
|||||||
@@ -23,11 +23,14 @@ import android.content.Context
|
|||||||
class HistoryWidgetProvider : BaseWidgetProvider() {
|
class HistoryWidgetProvider : BaseWidgetProvider() {
|
||||||
override fun getWidgetFromId(context: Context, id: Int): BaseWidget {
|
override fun getWidgetFromId(context: Context, id: Int): BaseWidget {
|
||||||
val habits = getHabitsFromWidgetId(id)
|
val habits = getHabitsFromWidgetId(id)
|
||||||
return if (habits.size == 1) HistoryWidget(
|
return if (habits.size == 1) {
|
||||||
|
HistoryWidget(
|
||||||
context,
|
context,
|
||||||
id,
|
id,
|
||||||
habits[0]
|
habits[0]
|
||||||
)
|
)
|
||||||
else StackWidget(context, id, StackWidgetType.HISTORY, habits)
|
} else {
|
||||||
|
StackWidget(context, id, StackWidgetType.HISTORY, habits)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class ScoreWidget(
|
|||||||
context: Context,
|
context: Context,
|
||||||
id: Int,
|
id: Int,
|
||||||
private val habit: Habit,
|
private val habit: Habit,
|
||||||
stacked: Boolean = false,
|
stacked: Boolean = false
|
||||||
) : BaseWidget(context, id, stacked) {
|
) : BaseWidget(context, id, stacked) {
|
||||||
override val defaultHeight: Int = 300
|
override val defaultHeight: Int = 300
|
||||||
override val defaultWidth: Int = 300
|
override val defaultWidth: Int = 300
|
||||||
@@ -46,7 +46,7 @@ class ScoreWidget(
|
|||||||
habit = habit,
|
habit = habit,
|
||||||
firstWeekday = prefs.firstWeekdayInt,
|
firstWeekday = prefs.firstWeekdayInt,
|
||||||
spinnerPosition = prefs.scoreCardSpinnerPosition,
|
spinnerPosition = prefs.scoreCardSpinnerPosition,
|
||||||
theme = WidgetTheme(),
|
theme = WidgetTheme()
|
||||||
)
|
)
|
||||||
val widgetView = view as GraphWidgetView
|
val widgetView = view as GraphWidgetView
|
||||||
widgetView.setBackgroundAlpha(preferedBackgroundAlpha)
|
widgetView.setBackgroundAlpha(preferedBackgroundAlpha)
|
||||||
|
|||||||
@@ -23,7 +23,10 @@ import android.content.Context
|
|||||||
class ScoreWidgetProvider : BaseWidgetProvider() {
|
class ScoreWidgetProvider : BaseWidgetProvider() {
|
||||||
override fun getWidgetFromId(context: Context, id: Int): BaseWidget {
|
override fun getWidgetFromId(context: Context, id: Int): BaseWidget {
|
||||||
val habits = getHabitsFromWidgetId(id)
|
val habits = getHabitsFromWidgetId(id)
|
||||||
return if (habits.size == 1) ScoreWidget(context, id, habits[0])
|
return if (habits.size == 1) {
|
||||||
else StackWidget(context, id, StackWidgetType.SCORE, habits)
|
ScoreWidget(context, id, habits[0])
|
||||||
|
} else {
|
||||||
|
StackWidget(context, id, StackWidgetType.SCORE, habits)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class StackWidget(
|
|||||||
widgetId: Int,
|
widgetId: Int,
|
||||||
private val widgetType: StackWidgetType,
|
private val widgetType: StackWidgetType,
|
||||||
private val habits: List<Habit>,
|
private val habits: List<Habit>,
|
||||||
stacked: Boolean = true,
|
stacked: Boolean = true
|
||||||
) : BaseWidget(context, widgetId, stacked) {
|
) : BaseWidget(context, widgetId, stacked) {
|
||||||
override val defaultHeight: Int = 0
|
override val defaultHeight: Int = 0
|
||||||
override val defaultWidth: Int = 0
|
override val defaultWidth: Int = 0
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class StreakWidget(
|
|||||||
context: Context,
|
context: Context,
|
||||||
id: Int,
|
id: Int,
|
||||||
private val habit: Habit,
|
private val habit: Habit,
|
||||||
stacked: Boolean = false,
|
stacked: Boolean = false
|
||||||
) : BaseWidget(context, id, stacked) {
|
) : BaseWidget(context, id, stacked) {
|
||||||
override val defaultHeight: Int = 200
|
override val defaultHeight: Int = 200
|
||||||
override val defaultWidth: Int = 200
|
override val defaultWidth: Int = 200
|
||||||
|
|||||||
@@ -23,7 +23,10 @@ import android.content.Context
|
|||||||
class StreakWidgetProvider : BaseWidgetProvider() {
|
class StreakWidgetProvider : BaseWidgetProvider() {
|
||||||
override fun getWidgetFromId(context: Context, id: Int): BaseWidget {
|
override fun getWidgetFromId(context: Context, id: Int): BaseWidget {
|
||||||
val habits = getHabitsFromWidgetId(id)
|
val habits = getHabitsFromWidgetId(id)
|
||||||
return if (habits.size == 1) StreakWidget(context, id, habits[0])
|
return if (habits.size == 1) {
|
||||||
else StackWidget(context, id, StackWidgetType.STREAKS, habits)
|
StreakWidget(context, id, habits[0])
|
||||||
|
} else {
|
||||||
|
StackWidget(context, id, StackWidgetType.STREAKS, habits)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class TargetWidget(
|
|||||||
context: Context,
|
context: Context,
|
||||||
id: Int,
|
id: Int,
|
||||||
private val habit: Habit,
|
private val habit: Habit,
|
||||||
stacked: Boolean = false,
|
stacked: Boolean = false
|
||||||
) : BaseWidget(context, id, stacked) {
|
) : BaseWidget(context, id, stacked) {
|
||||||
override val defaultHeight: Int = 200
|
override val defaultHeight: Int = 200
|
||||||
override val defaultWidth: Int = 200
|
override val defaultWidth: Int = 200
|
||||||
@@ -53,7 +53,7 @@ class TargetWidget(
|
|||||||
val data = TargetCardPresenter.buildState(
|
val data = TargetCardPresenter.buildState(
|
||||||
habit = habit,
|
habit = habit,
|
||||||
firstWeekday = prefs.firstWeekdayInt,
|
firstWeekday = prefs.firstWeekdayInt,
|
||||||
theme = WidgetTheme(),
|
theme = WidgetTheme()
|
||||||
)
|
)
|
||||||
chart.setColor(WidgetTheme().color(habit.color).toInt())
|
chart.setColor(WidgetTheme().color(habit.color).toInt())
|
||||||
chart.setTargets(data.targets)
|
chart.setTargets(data.targets)
|
||||||
|
|||||||
@@ -23,7 +23,10 @@ import android.content.Context
|
|||||||
class TargetWidgetProvider : BaseWidgetProvider() {
|
class TargetWidgetProvider : BaseWidgetProvider() {
|
||||||
override fun getWidgetFromId(context: Context, id: Int): BaseWidget {
|
override fun getWidgetFromId(context: Context, id: Int): BaseWidget {
|
||||||
val habits = getHabitsFromWidgetId(id)
|
val habits = getHabitsFromWidgetId(id)
|
||||||
return if (habits.size == 1) TargetWidget(context, id, habits[0])
|
return if (habits.size == 1) {
|
||||||
else StackWidget(context, id, StackWidgetType.TARGET, habits)
|
TargetWidget(context, id, habits[0])
|
||||||
|
} else {
|
||||||
|
StackWidget(context, id, StackWidgetType.TARGET, habits)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,7 +96,8 @@ class CheckmarkWidgetView : HabitWidgetView {
|
|||||||
private val text: String
|
private val text: String
|
||||||
get() = if (isNumerical) {
|
get() = if (isNumerical) {
|
||||||
(max(0, entryValue) / 1000.0).toShortString()
|
(max(0, entryValue) / 1000.0).toShortString()
|
||||||
} else when (entryState) {
|
} else {
|
||||||
|
when (entryState) {
|
||||||
YES_MANUAL, YES_AUTO -> resources.getString(R.string.fa_check)
|
YES_MANUAL, YES_AUTO -> resources.getString(R.string.fa_check)
|
||||||
SKIP -> resources.getString(R.string.fa_skipped)
|
SKIP -> resources.getString(R.string.fa_skipped)
|
||||||
UNKNOWN -> {
|
UNKNOWN -> {
|
||||||
@@ -112,6 +113,7 @@ class CheckmarkWidgetView : HabitWidgetView {
|
|||||||
NO -> resources.getString(R.string.fa_times)
|
NO -> resources.getString(R.string.fa_times)
|
||||||
else -> resources.getString(R.string.fa_times)
|
else -> resources.getString(R.string.fa_times)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override val innerLayoutId: Int
|
override val innerLayoutId: Int
|
||||||
get() = R.layout.widget_checkmark
|
get() = R.layout.widget_checkmark
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:inputType="textCapSentences"
|
android:inputType="textCapSentences|textMultiLine"
|
||||||
android:textSize="@dimen/smallTextSize"
|
android:textSize="@dimen/smallTextSize"
|
||||||
android:padding="4dp"
|
android:padding="4dp"
|
||||||
android:background="@color/transparent"
|
android:background="@color/transparent"
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:textSize="@dimen/smallTextSize"
|
android:textSize="@dimen/smallTextSize"
|
||||||
|
android:maxLines="2"
|
||||||
android:textColor="@color/white"/>
|
android:textColor="@color/white"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
23
uhabits-android/src/main/res/values-night/colors.xml
Normal file
23
uhabits-android/src/main/res/values-night/colors.xml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
<color name="color_background">@color/grey_900</color>
|
||||||
|
</resources>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user