Compare commits
130 Commits
feature/sy
...
sdk_30_31
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1ad28820f | ||
|
|
edaed20a98 | ||
|
|
68daf6587b | ||
|
|
b7606f33d2 | ||
|
|
4350ff0cd7 | ||
|
|
7bf74634bb | ||
|
|
fc645a81bc | ||
|
|
35365bbdf3 | ||
|
|
779ef5dbee | ||
|
|
e82994c76b | ||
|
|
4e1d01d8d1 | ||
|
|
5de0fc86e5 | ||
| e26b643423 | |||
| 621534d610 | |||
| a01300e9c6 | |||
| ecb8ce105a | |||
| 32ef3c14f7 | |||
| 4972257635 | |||
| c98cb50baa | |||
| c331f34fa9 | |||
| a1aea532b5 | |||
| 43489aeb4c | |||
| 990c85aedd | |||
|
|
64337b9bee | ||
|
|
8bdfaa2434 | ||
|
|
5f6060858d | ||
| b62e436054 | |||
| bf63b4dbcf | |||
| a82d940bcc | |||
| ba59dc7ca9 | |||
| 181290a0f3 | |||
|
|
d553c2f3f2 | ||
|
|
7776093217 | ||
|
|
b27f3f8540 | ||
| eb041bf6b2 | |||
|
|
e3c53bf07f | ||
|
|
9ddab6ee59 | ||
|
|
2615795402 | ||
|
|
6531445d7f | ||
|
|
4fbf8a8ca2 | ||
|
|
707b2b4380 | ||
|
|
aae85c1170 | ||
|
|
c12a6c6a4d | ||
|
|
b15c02adbf | ||
|
|
9e24128675 | ||
|
|
66c61e2e6c | ||
|
|
7bddfbe5a7 | ||
|
|
8036b10ee6 | ||
|
|
71f400f587 | ||
|
|
79e302f922 | ||
|
|
af7f60fc4d | ||
|
|
7cc4b66dfd | ||
|
|
a9fddf9963 | ||
|
|
36c1504c6a | ||
|
|
d644170141 | ||
|
|
d38f83e961 | ||
|
|
c50c5af497 | ||
|
|
fa3774a32b | ||
|
|
fd124f2a6c | ||
|
|
265b65eb8a | ||
|
|
4c269c55d2 | ||
|
|
c03305120e | ||
|
|
29615b670b | ||
|
|
6ab4a696b6 | ||
|
|
23479c7765 | ||
|
|
6d98f7aafa | ||
| 75078ed52b | |||
| 2a0afedb1d | |||
| 66a2b41250 | |||
|
|
d6a7fa3d7a | ||
|
|
07e55f1c76 | ||
| 4ee5dd910b | |||
| 87f071b5b4 | |||
| bb0b5e8adf | |||
|
|
c79d1e82a5 | ||
|
|
4aebeedec6 | ||
|
|
7de94f2caf | ||
|
|
17ed85fc1b | ||
|
|
4355fb4d68 | ||
|
|
508200abeb | ||
|
|
a29943e783 | ||
|
|
3e6a9181d6 | ||
| 1fe3a3d1ca | |||
|
|
b2951a3475 | ||
|
|
9d3c63cf62 | ||
|
|
65d237254c | ||
|
|
fe1d5c66cb | ||
|
|
113a5028af | ||
|
|
1a56260757 | ||
|
|
697fffbc99 | ||
|
|
804edfa64e | ||
| 2ab6c396d0 | |||
| a55f467339 | |||
|
|
cf682f68c9 | ||
| 0e988e746c | |||
|
|
f119cbf8e7 | ||
|
|
056f5f6fce | ||
|
|
42f6125d5e | ||
|
|
3e20fc4d1d | ||
|
|
1f763feb69 | ||
|
|
6e7ad329fe | ||
|
|
5cb241475d | ||
|
|
27e76c7243 | ||
|
|
576ad04064 | ||
|
|
5f8187ef6d | ||
|
|
f16f919e27 | ||
|
|
736bb8a75e | ||
|
|
de9ad6d4a4 | ||
|
|
1d37ce54ea | ||
|
|
f88f1cfb54 | ||
|
|
fc1478645b | ||
|
|
ffab001b09 | ||
|
|
a58a8005e1 | ||
|
|
c884ada187 | ||
|
|
e3d46ad5a0 | ||
|
|
f4a2c03216 | ||
|
|
f2b8f2f98d | ||
|
|
2c5fd87a2a | ||
|
|
39768f7f04 | ||
|
|
cc3e1ced15 | ||
|
|
2e26cc104e | ||
|
|
42fd0926ef | ||
| ec202aa9a7 | |||
|
|
1fb56c8777 | ||
|
|
a5d4a37da8 | ||
|
|
4804a48549 | ||
|
|
c892a845b4 | ||
|
|
e98064b6a5 | ||
| 57f5f6ed5b | |||
| 79f5b8b7e8 |
4
.github/workflows/main.yml
vendored
@@ -13,10 +13,10 @@ jobs:
|
||||
- name: Check out source code
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Install Java Development Kit 1.8
|
||||
- name: Install Java Development Kit 11
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
java-version: 11
|
||||
|
||||
- name: Build Project
|
||||
run: ./build.sh build
|
||||
|
||||
@@ -90,7 +90,7 @@ contribute, even if you are not a software developer.
|
||||
is already completed, you are also very welcome to join and proofread it.
|
||||
|
||||
* **Write some code.** If you are an Android developer, you are very welcome to
|
||||
contribute with code. Please see `docs/GUIDELINES.md`.
|
||||
contribute with code. Please see the [guidelines](https://github.com/iSoron/uhabits/blob/dev/docs/GUIDELINES.md).
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
plugins {
|
||||
val kotlinVersion = "1.5.0"
|
||||
id("com.android.application") version ("4.2.0") apply (false)
|
||||
id("com.android.application") version ("7.0.3") apply (false)
|
||||
id("org.jetbrains.kotlin.android") version kotlinVersion apply (false)
|
||||
id("org.jetbrains.kotlin.kapt") version kotlinVersion apply (false)
|
||||
id("org.jetbrains.kotlin.android.extensions") version kotlinVersion apply (false)
|
||||
id("org.jetbrains.kotlin.multiplatform") version kotlinVersion apply (false)
|
||||
id("org.jlleitschuh.gradle.ktlint") version "10.1.0"
|
||||
id("org.jlleitschuh.gradle.ktlint") version "10.2.0"
|
||||
}
|
||||
|
||||
apply {
|
||||
@@ -15,11 +15,9 @@ apply {
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven(url = "https://plugins.gradle.org/m2/")
|
||||
maven(url = "https://oss.sonatype.org/content/repositories/snapshots/")
|
||||
maven(url = "https://kotlin.bintray.com/ktor")
|
||||
maven(url = "https://kotlin.bintray.com/kotlin-js-wrappers")
|
||||
maven(url = "https://jitpack.io")
|
||||
}
|
||||
}
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
jcenter()
|
||||
google()
|
||||
}
|
||||
resolutionStrategy.eachPlugin {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
/*
|
||||
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
||||
*
|
||||
@@ -20,7 +18,7 @@
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("com.github.triplet.play") version "3.5.0"
|
||||
id("com.github.triplet.play") version "3.7.0"
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("org.jetbrains.kotlin.kapt")
|
||||
@@ -34,15 +32,15 @@ tasks.compileLint {
|
||||
|
||||
android {
|
||||
|
||||
compileSdkVersion(30)
|
||||
compileSdk = 31
|
||||
|
||||
defaultConfig {
|
||||
versionCode(20003)
|
||||
versionName("2.0.3")
|
||||
minSdkVersion(23)
|
||||
targetSdkVersion(30)
|
||||
applicationId("org.isoron.uhabits")
|
||||
testInstrumentationRunner("androidx.test.runner.AndroidJUnitRunner")
|
||||
versionCode = 20003
|
||||
versionName = "2.0.3"
|
||||
minSdk = 23
|
||||
targetSdk = 31
|
||||
applicationId = "org.isoron.uhabits"
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
@@ -58,7 +56,7 @@ android {
|
||||
|
||||
buildTypes {
|
||||
getByName("release") {
|
||||
minifyEnabled(true)
|
||||
isMinifyEnabled = true
|
||||
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.txt")
|
||||
if (signingConfigs.findByName("release") != null) {
|
||||
signingConfig = signingConfigs.getByName("release")
|
||||
@@ -70,7 +68,7 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
lint {
|
||||
isCheckReleaseBuilds = false
|
||||
isAbortOnError = false
|
||||
disable("GoogleAppIndexingWarning")
|
||||
@@ -88,10 +86,10 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
val daggerVersion = "2.38.1"
|
||||
val kotlinVersion = "1.5.21"
|
||||
val kxCoroutinesVersion = "1.5.1"
|
||||
val ktorVersion = "1.6.2"
|
||||
val daggerVersion = "2.40.3"
|
||||
val kotlinVersion = "1.6.0"
|
||||
val kxCoroutinesVersion = "1.5.2"
|
||||
val ktorVersion = "1.6.6"
|
||||
val espressoVersion = "3.4.0"
|
||||
|
||||
androidTestImplementation("androidx.test.espresso:espresso-contrib:$espressoVersion")
|
||||
@@ -100,17 +98,17 @@ dependencies {
|
||||
androidTestImplementation("com.linkedin.dexmaker:dexmaker-mockito:2.28.1")
|
||||
androidTestImplementation("io.ktor:ktor-client-mock:$ktorVersion")
|
||||
androidTestImplementation("io.ktor:ktor-jackson:$ktorVersion")
|
||||
androidTestImplementation("androidx.annotation:annotation:1.2.0")
|
||||
androidTestImplementation("androidx.annotation:annotation:1.3.0")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.3")
|
||||
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.2.0")
|
||||
androidTestImplementation("androidx.test:rules:1.4.0")
|
||||
androidTestImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0")
|
||||
compileOnly("javax.annotation:jsr250-api:1.0")
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5")
|
||||
implementation("com.github.paolorotolo:appintro:4.1.0")
|
||||
implementation("com.github.AppIntro:AppIntro:6.1.0")
|
||||
implementation("com.google.code.findbugs:jsr305:3.0.2")
|
||||
implementation("com.google.dagger:dagger:$daggerVersion")
|
||||
implementation("com.google.guava:guava:30.1.1-android")
|
||||
implementation("com.google.guava:guava:31.0.1-android")
|
||||
implementation("io.ktor:ktor-client-android:$ktorVersion")
|
||||
implementation("io.ktor:ktor-client-core:$ktorVersion")
|
||||
implementation("io.ktor:ktor-client-jackson:$ktorVersion")
|
||||
@@ -122,7 +120,7 @@ dependencies {
|
||||
implementation("androidx.legacy:legacy-preference-v14:1.0.0")
|
||||
implementation("androidx.legacy:legacy-support-v4:1.0.0")
|
||||
implementation("com.google.android.material:material:1.4.0")
|
||||
implementation("com.opencsv:opencsv:5.5.1")
|
||||
implementation("com.opencsv:opencsv:5.5.2")
|
||||
implementation(project(":uhabits-core"))
|
||||
kapt("com.google.dagger:dagger-compiler:$daggerVersion")
|
||||
kaptAndroidTest("com.google.dagger:dagger-compiler:$daggerVersion")
|
||||
|
||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 6.4 KiB |
@@ -55,6 +55,7 @@ import org.junit.runner.RunWith
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@LargeTest
|
||||
class HabitsTest : BaseUserInterfaceTest() {
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun shouldCreateHabit() {
|
||||
@@ -180,6 +181,8 @@ class HabitsTest : BaseUserInterfaceTest() {
|
||||
longPressCheckmarks("Wake up early", count = 2)
|
||||
clickText("Wake up early")
|
||||
verifyShowsScreen(SHOW_HABIT)
|
||||
// TODO: find a better way than sleeping in tests
|
||||
Thread.sleep(2001L)
|
||||
verifyDisplaysText("10%")
|
||||
}
|
||||
|
||||
@@ -194,6 +197,8 @@ class HabitsTest : BaseUserInterfaceTest() {
|
||||
verifyDoesNotDisplayText("Track time")
|
||||
verifyDisplaysText("Wake up early")
|
||||
longPressCheckmarks("Wake up early", count = 1)
|
||||
// TODO: find a better way than sleeping in tests
|
||||
Thread.sleep(2001L)
|
||||
verifyDoesNotDisplayText("Wake up early")
|
||||
clickMenu(TOGGLE_COMPLETED)
|
||||
verifyDisplaysText("Track time")
|
||||
|
||||
@@ -24,6 +24,7 @@ import androidx.test.filters.MediumTest
|
||||
import org.hamcrest.CoreMatchers.equalTo
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.isoron.uhabits.BaseViewTest
|
||||
import org.isoron.uhabits.core.models.NumericalHabitType
|
||||
import org.isoron.uhabits.utils.PaletteUtils
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
@@ -42,6 +43,7 @@ class NumberButtonViewTest : BaseViewTest() {
|
||||
super.setUp()
|
||||
view = component.getNumberButtonViewFactory().create().apply {
|
||||
units = "steps"
|
||||
targetType = NumericalHabitType.AT_LEAST
|
||||
threshold = 100.0
|
||||
color = PaletteUtils.getAndroidTestColor(8)
|
||||
onEdit = { edited = true }
|
||||
@@ -74,10 +76,10 @@ class NumberButtonViewTest : BaseViewTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRender_emptyUnits() {
|
||||
fun testRender_atMostAboveThreshold() {
|
||||
view.value = 500.0
|
||||
view.units = ""
|
||||
assertRenders(view, "$PATH/render_unitless.png")
|
||||
view.targetType = NumericalHabitType.AT_MOST
|
||||
assertRenders(view, "$PATH/render_at_most_above.png")
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -86,6 +88,13 @@ class NumberButtonViewTest : BaseViewTest() {
|
||||
assertRenders(view, "$PATH/render_below.png")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRender_atMostBetweenThresholds() {
|
||||
view.value = 110.0
|
||||
view.targetType = NumericalHabitType.AT_MOST
|
||||
assertRenders(view, "$PATH/render_at_most_between.png")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRender_zero() {
|
||||
view.value = 0.0
|
||||
@@ -93,15 +102,21 @@ class NumberButtonViewTest : BaseViewTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testClick_shortToggleDisabled() {
|
||||
prefs.isShortToggleEnabled = false
|
||||
view.performClick()
|
||||
assertFalse(edited)
|
||||
fun testRender_atMostBelowThreshold() {
|
||||
view.value = 0.0
|
||||
view.targetType = NumericalHabitType.AT_MOST
|
||||
assertRenders(view, "$PATH/render_at_most_below.png")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testClick_shortToggleEnabled() {
|
||||
prefs.isShortToggleEnabled = true
|
||||
fun testRender_emptyUnits() {
|
||||
view.value = 500.0
|
||||
view.units = ""
|
||||
assertRenders(view, "$PATH/render_unitless.png")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testClick() {
|
||||
view.performClick()
|
||||
assertTrue(edited)
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import androidx.test.filters.MediumTest
|
||||
import org.hamcrest.CoreMatchers.equalTo
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.isoron.uhabits.BaseViewTest
|
||||
import org.isoron.uhabits.core.models.NumericalHabitType
|
||||
import org.isoron.uhabits.core.models.Timestamp
|
||||
import org.isoron.uhabits.utils.PaletteUtils
|
||||
import org.junit.After
|
||||
@@ -55,6 +56,7 @@ class NumberPanelViewTest : BaseViewTest() {
|
||||
buttonCount = 4
|
||||
color = PaletteUtils.getAndroidTestColor(7)
|
||||
units = "steps"
|
||||
targetType = NumericalHabitType.AT_LEAST
|
||||
threshold = 5000.0
|
||||
}
|
||||
view.onAttachedToWindow()
|
||||
|
||||
@@ -53,8 +53,6 @@ class SubtitleCardViewTest : BaseViewTest() {
|
||||
isNumerical = false,
|
||||
question = "Did you meditate this morning?",
|
||||
reminder = Reminder(8, 30, EVERY_DAY),
|
||||
unit = "",
|
||||
targetValue = 0.0,
|
||||
theme = LightTheme(),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -61,7 +61,7 @@ class PerformanceTest : BaseAndroidTest() {
|
||||
val habit = fixtures.createEmptyHabit()
|
||||
for (i in 0..4999) {
|
||||
val timestamp: Timestamp = Timestamp(i * DAY_LENGTH)
|
||||
CreateRepetitionCommand(habitList, habit, timestamp, 1).run()
|
||||
CreateRepetitionCommand(habitList, habit, timestamp, 1, "").run()
|
||||
}
|
||||
db.setTransactionSuccessful()
|
||||
db.endTransaction()
|
||||
|
||||
@@ -24,8 +24,8 @@ import android.widget.FrameLayout
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.MediumTest
|
||||
import org.hamcrest.CoreMatchers
|
||||
import org.hamcrest.CoreMatchers.`is`
|
||||
import org.hamcrest.CoreMatchers.equalTo
|
||||
import org.hamcrest.CoreMatchers.`is`
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.isoron.uhabits.BaseViewTest
|
||||
import org.isoron.uhabits.R
|
||||
|
||||
@@ -17,9 +17,10 @@
|
||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="org.isoron.uhabits">
|
||||
|
||||
<uses-permission android:name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS" />
|
||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
|
||||
@@ -48,11 +49,11 @@
|
||||
android:name=".activities.habits.list.ListHabitsActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/main_activity_title"
|
||||
android:launchMode="singleTop">
|
||||
</activity>
|
||||
android:launchMode="singleTop" />
|
||||
|
||||
<activity-alias
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/main_activity_title"
|
||||
android:launchMode="singleTop"
|
||||
android:targetActivity=".activities.habits.list.ListHabitsActivity">
|
||||
@@ -85,6 +86,7 @@
|
||||
|
||||
<activity
|
||||
android:name=".widgets.activities.HabitPickerDialog"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.AppCompat.Light.Dialog">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
||||
@@ -93,6 +95,7 @@
|
||||
|
||||
<activity
|
||||
android:name=".widgets.activities.BooleanHabitPickerDialog"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.AppCompat.Light.Dialog">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
||||
@@ -101,6 +104,7 @@
|
||||
|
||||
<activity
|
||||
android:name=".widgets.activities.NumericalHabitPickerDialog"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.AppCompat.Light.Dialog">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
||||
@@ -117,9 +121,10 @@
|
||||
|
||||
<activity
|
||||
android:name=".widgets.activities.NumericalCheckmarkWidgetActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:label="NumericalCheckmarkWidget"
|
||||
android:noHistory="true"
|
||||
android:excludeFromRecents="true"
|
||||
android:theme="@style/Theme.AppCompat.Light.Dialog">
|
||||
<intent-filter>
|
||||
<action android:name="org.isoron.uhabits.ACTION_SHOW_NUMERICAL_VALUE_ACTIVITY" />
|
||||
@@ -128,13 +133,14 @@
|
||||
|
||||
<activity
|
||||
android:name=".notifications.SnoozeDelayPickerActivity"
|
||||
android:taskAffinity=""
|
||||
android:excludeFromRecents="true"
|
||||
android:launchMode="singleInstance"
|
||||
android:taskAffinity=""
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
|
||||
|
||||
<receiver
|
||||
android:name=".widgets.CheckmarkWidgetProvider"
|
||||
android:exported="true"
|
||||
android:label="@string/checkmark">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
@@ -152,6 +158,7 @@
|
||||
|
||||
<receiver
|
||||
android:name=".widgets.HistoryWidgetProvider"
|
||||
android:exported="true"
|
||||
android:label="@string/history">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
@@ -164,6 +171,7 @@
|
||||
|
||||
<receiver
|
||||
android:name=".widgets.ScoreWidgetProvider"
|
||||
android:exported="true"
|
||||
android:label="@string/score">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
@@ -176,6 +184,7 @@
|
||||
|
||||
<receiver
|
||||
android:name=".widgets.StreakWidgetProvider"
|
||||
android:exported="true"
|
||||
android:label="@string/streaks">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
@@ -188,6 +197,7 @@
|
||||
|
||||
<receiver
|
||||
android:name=".widgets.FrequencyWidgetProvider"
|
||||
android:exported="true"
|
||||
android:label="@string/frequency">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
@@ -200,6 +210,7 @@
|
||||
|
||||
<receiver
|
||||
android:name=".widgets.TargetWidgetProvider"
|
||||
android:exported="true"
|
||||
android:label="@string/target">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
@@ -210,13 +221,17 @@
|
||||
android:resource="@xml/widget_target_info" />
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".receivers.ReminderReceiver">
|
||||
<receiver
|
||||
android:name=".receivers.ReminderReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".receivers.WidgetReceiver">
|
||||
<receiver android:name=".receivers.WidgetReceiver"
|
||||
android:exported="true"
|
||||
android:permission="false">
|
||||
<intent-filter>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<action android:name="org.isoron.uhabits.ACTION_SET_NUMERICAL_VALUE" />
|
||||
@@ -267,7 +282,7 @@
|
||||
<!-- Locale/Tasker -->
|
||||
<receiver
|
||||
android:name=".automation.FireSettingReceiver"
|
||||
android:exported="true">
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING" />
|
||||
</intent-filter>
|
||||
|
||||
@@ -49,23 +49,12 @@ class AndroidDataView(
|
||||
override fun onShowPress(e: MotionEvent?) = Unit
|
||||
|
||||
override fun onSingleTapUp(e: MotionEvent?): Boolean {
|
||||
val x: Float
|
||||
val y: Float
|
||||
try {
|
||||
val pointerId = e!!.getPointerId(0)
|
||||
x = e.getX(pointerId)
|
||||
y = e.getY(pointerId)
|
||||
} catch (ex: RuntimeException) {
|
||||
// Android often throws IllegalArgumentException here. Apparently,
|
||||
// the pointer id may become invalid shortly after calling
|
||||
// e.getPointerId.
|
||||
return false
|
||||
}
|
||||
view?.onClick(x / canvas.innerDensity, y / canvas.innerDensity)
|
||||
return true
|
||||
return handleClick(e, true)
|
||||
}
|
||||
|
||||
override fun onLongPress(e: MotionEvent?) = Unit
|
||||
override fun onLongPress(e: MotionEvent?) {
|
||||
handleClick(e)
|
||||
}
|
||||
|
||||
override fun onScroll(
|
||||
e1: MotionEvent?,
|
||||
@@ -137,4 +126,22 @@ class AndroidDataView(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleClick(e: MotionEvent?, isSingleTap: Boolean = false): Boolean {
|
||||
val x: Float
|
||||
val y: Float
|
||||
try {
|
||||
val pointerId = e!!.getPointerId(0)
|
||||
x = e.getX(pointerId)
|
||||
y = e.getY(pointerId)
|
||||
} catch (ex: RuntimeException) {
|
||||
// Android often throws IllegalArgumentException here. Apparently,
|
||||
// the pointer id may become invalid shortly after calling
|
||||
// e.getPointerId.
|
||||
return false
|
||||
}
|
||||
if (isSingleTap) view?.onClick(x / canvas.innerDensity, y / canvas.innerDensity)
|
||||
else view?.onLongClick(x / canvas.innerDensity, y / canvas.innerDensity)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
package org.isoron.uhabits.activities.common.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Typeface
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE
|
||||
import android.widget.Button
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import org.isoron.platform.gui.toInt
|
||||
import org.isoron.uhabits.R
|
||||
import org.isoron.uhabits.core.models.Entry.Companion.NO
|
||||
import org.isoron.uhabits.core.models.Entry.Companion.SKIP
|
||||
import org.isoron.uhabits.core.models.Entry.Companion.UNKNOWN
|
||||
import org.isoron.uhabits.core.models.Entry.Companion.YES_AUTO
|
||||
import org.isoron.uhabits.core.models.Entry.Companion.YES_MANUAL
|
||||
import org.isoron.uhabits.core.models.PaletteColor
|
||||
import org.isoron.uhabits.core.preferences.Preferences
|
||||
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
|
||||
import org.isoron.uhabits.core.ui.views.Theme
|
||||
import org.isoron.uhabits.databinding.CheckmarkDialogBinding
|
||||
import org.isoron.uhabits.inject.ActivityContext
|
||||
import org.isoron.uhabits.utils.InterfaceUtils
|
||||
import org.isoron.uhabits.utils.StyledResources
|
||||
import javax.inject.Inject
|
||||
|
||||
class CheckmarkDialog
|
||||
@Inject constructor(
|
||||
@ActivityContext private val context: Context,
|
||||
private val preferences: Preferences,
|
||||
) : View.OnClickListener {
|
||||
|
||||
private lateinit var binding: CheckmarkDialogBinding
|
||||
private lateinit var fontAwesome: Typeface
|
||||
private val allButtons = mutableListOf<Button>()
|
||||
private var selectedButton: Button? = null
|
||||
|
||||
fun create(
|
||||
value: Int,
|
||||
notes: String,
|
||||
dateString: String,
|
||||
paletteColor: PaletteColor,
|
||||
callback: ListHabitsBehavior.CheckMarkDialogCallback,
|
||||
theme: Theme,
|
||||
): AlertDialog {
|
||||
binding = CheckmarkDialogBinding.inflate(LayoutInflater.from(context))
|
||||
fontAwesome = InterfaceUtils.getFontAwesome(context)!!
|
||||
binding.etNotes.append(notes)
|
||||
setUpButtons(value, theme.color(paletteColor).toInt())
|
||||
|
||||
val dialog = AlertDialog.Builder(context)
|
||||
.setView(binding.root)
|
||||
.setTitle(dateString)
|
||||
.setPositiveButton(R.string.save) { _, _ ->
|
||||
val newValue = when (selectedButton?.id) {
|
||||
R.id.yesBtn -> YES_MANUAL
|
||||
R.id.noBtn -> NO
|
||||
R.id.skippedBtn -> SKIP
|
||||
else -> UNKNOWN
|
||||
}
|
||||
callback.onNotesSaved(newValue, binding.etNotes.text.toString())
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||
callback.onNotesDismissed()
|
||||
}
|
||||
.setOnDismissListener {
|
||||
callback.onNotesDismissed()
|
||||
}
|
||||
.create()
|
||||
|
||||
dialog.setOnShowListener {
|
||||
binding.etNotes.requestFocus()
|
||||
dialog.window?.setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_VISIBLE)
|
||||
}
|
||||
|
||||
return dialog
|
||||
}
|
||||
|
||||
private fun setUpButtons(value: Int, color: Int) {
|
||||
val sres = StyledResources(context)
|
||||
val mediumContrastColor = sres.getColor(R.attr.contrast60)
|
||||
setButtonAttrs(binding.yesBtn, color)
|
||||
setButtonAttrs(binding.noBtn, mediumContrastColor)
|
||||
setButtonAttrs(binding.skippedBtn, color, visible = preferences.isSkipEnabled)
|
||||
setButtonAttrs(binding.questionBtn, mediumContrastColor, visible = preferences.areQuestionMarksEnabled)
|
||||
when (value) {
|
||||
UNKNOWN -> if (preferences.areQuestionMarksEnabled) {
|
||||
binding.questionBtn.performClick()
|
||||
} else {
|
||||
binding.noBtn.performClick()
|
||||
}
|
||||
SKIP -> binding.skippedBtn.performClick()
|
||||
YES_MANUAL -> binding.yesBtn.performClick()
|
||||
YES_AUTO, NO -> binding.noBtn.performClick()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setButtonAttrs(button: Button, color: Int, visible: Boolean = true) {
|
||||
button.apply {
|
||||
visibility = if (visible) View.VISIBLE else View.GONE
|
||||
typeface = fontAwesome
|
||||
setTextColor(color)
|
||||
setOnClickListener(this@CheckmarkDialog)
|
||||
}
|
||||
allButtons.add(button)
|
||||
}
|
||||
|
||||
override fun onClick(v: View?) {
|
||||
allButtons.forEach {
|
||||
if (v?.id == it.id) {
|
||||
it.isSelected = true
|
||||
selectedButton = it
|
||||
} else it.isSelected = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,6 @@ package org.isoron.uhabits.activities.common.dialogs
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
@@ -44,7 +43,7 @@ class FrequencyPickerDialog(
|
||||
constructor() : this(1, 1)
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val inflater = LayoutInflater.from(activity!!)
|
||||
val inflater = LayoutInflater.from(requireActivity())
|
||||
contentView = inflater.inflate(R.layout.frequency_picker_dialog, null)
|
||||
|
||||
addBeforeAfterText(
|
||||
@@ -62,15 +61,19 @@ class FrequencyPickerDialog(
|
||||
contentView.xTimesPerMonthContainer,
|
||||
)
|
||||
|
||||
addBeforeAfterText(
|
||||
this.getString(R.string.x_times_per_y_days),
|
||||
contentView.xTimesPerYDaysContainer,
|
||||
)
|
||||
|
||||
contentView.everyDayRadioButton.setOnClickListener {
|
||||
check(contentView.everyDayRadioButton)
|
||||
unfocusAll()
|
||||
}
|
||||
|
||||
contentView.everyXDaysRadioButton.setOnClickListener {
|
||||
check(contentView.everyXDaysRadioButton)
|
||||
val everyXDaysTextView = contentView.everyXDaysTextView
|
||||
focus(everyXDaysTextView)
|
||||
selectInputField(everyXDaysTextView)
|
||||
}
|
||||
|
||||
contentView.everyXDaysTextView.setOnFocusChangeListener { v, hasFocus ->
|
||||
@@ -79,7 +82,7 @@ class FrequencyPickerDialog(
|
||||
|
||||
contentView.xTimesPerWeekRadioButton.setOnClickListener {
|
||||
check(contentView.xTimesPerWeekRadioButton)
|
||||
focus(contentView.xTimesPerWeekTextView)
|
||||
selectInputField(contentView.xTimesPerWeekTextView)
|
||||
}
|
||||
|
||||
contentView.xTimesPerWeekTextView.setOnFocusChangeListener { v, hasFocus ->
|
||||
@@ -88,14 +91,27 @@ class FrequencyPickerDialog(
|
||||
|
||||
contentView.xTimesPerMonthRadioButton.setOnClickListener {
|
||||
check(contentView.xTimesPerMonthRadioButton)
|
||||
focus(contentView.xTimesPerMonthTextView)
|
||||
selectInputField(contentView.xTimesPerMonthTextView)
|
||||
}
|
||||
|
||||
contentView.xTimesPerMonthTextView.setOnFocusChangeListener { v, hasFocus ->
|
||||
if (hasFocus) check(contentView.xTimesPerMonthRadioButton)
|
||||
}
|
||||
|
||||
return AlertDialog.Builder(activity!!)
|
||||
contentView.xTimesPerYDaysRadioButton.setOnClickListener {
|
||||
check(contentView.xTimesPerYDaysRadioButton)
|
||||
selectInputField(contentView.xTimesPerYDaysXTextView)
|
||||
}
|
||||
|
||||
contentView.xTimesPerYDaysXTextView.setOnFocusChangeListener { v, hasFocus ->
|
||||
if (hasFocus) check(contentView.xTimesPerYDaysRadioButton)
|
||||
}
|
||||
|
||||
contentView.xTimesPerYDaysYTextView.setOnFocusChangeListener { v, hasFocus ->
|
||||
if (hasFocus) check(contentView.xTimesPerYDaysRadioButton)
|
||||
}
|
||||
|
||||
return AlertDialog.Builder(requireActivity())
|
||||
.setView(contentView)
|
||||
.setPositiveButton(R.string.save) { _, _ -> onSaveClicked() }
|
||||
.create()
|
||||
@@ -106,12 +122,11 @@ class FrequencyPickerDialog(
|
||||
container: LinearLayout
|
||||
) {
|
||||
val parts = str.split("%d")
|
||||
container.addView(
|
||||
TextView(activity).apply { text = parts[0].trim() }, 1,
|
||||
)
|
||||
container.addView(
|
||||
TextView(activity).apply { text = parts[1].trim() }, 3,
|
||||
)
|
||||
for (i in parts.indices) {
|
||||
container.addView(
|
||||
TextView(activity).apply { text = parts[i].trim() }, 2 * i + 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSaveClicked() {
|
||||
@@ -132,6 +147,12 @@ class FrequencyPickerDialog(
|
||||
denominator = 7
|
||||
}
|
||||
}
|
||||
contentView.xTimesPerYDaysRadioButton.isChecked -> {
|
||||
if (contentView.xTimesPerYDaysXTextView.text.isNotEmpty() && contentView.xTimesPerYDaysYTextView.text.isNotEmpty()) {
|
||||
numerator = Integer.parseInt(contentView.xTimesPerYDaysXTextView.text.toString())
|
||||
denominator = Integer.parseInt(contentView.xTimesPerYDaysYTextView.text.toString())
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
if (contentView.xTimesPerMonthTextView.text.isNotEmpty()) {
|
||||
numerator = Integer.parseInt(contentView.xTimesPerMonthTextView.text.toString())
|
||||
@@ -147,10 +168,10 @@ class FrequencyPickerDialog(
|
||||
dismiss()
|
||||
}
|
||||
|
||||
private fun check(view: RadioButton?) {
|
||||
private fun check(view: RadioButton) {
|
||||
uncheckAll()
|
||||
view?.isChecked = true
|
||||
view?.requestFocus()
|
||||
view.isChecked = true
|
||||
view.requestFocus()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
@@ -163,7 +184,7 @@ class FrequencyPickerDialog(
|
||||
if (freqDenominator == 30 || freqDenominator == 31) {
|
||||
contentView.xTimesPerMonthRadioButton.isChecked = true
|
||||
contentView.xTimesPerMonthTextView.setText(freqNumerator.toString())
|
||||
focus(contentView.xTimesPerMonthTextView)
|
||||
selectInputField(contentView.xTimesPerMonthTextView)
|
||||
} else {
|
||||
if (freqNumerator == 1) {
|
||||
if (freqDenominator == 1) {
|
||||
@@ -171,23 +192,23 @@ class FrequencyPickerDialog(
|
||||
} else {
|
||||
contentView.everyXDaysRadioButton.isChecked = true
|
||||
contentView.everyXDaysTextView.setText(freqDenominator.toString())
|
||||
focus(contentView.everyXDaysTextView)
|
||||
selectInputField(contentView.everyXDaysTextView)
|
||||
}
|
||||
} else {
|
||||
if (freqDenominator == 7) {
|
||||
contentView.xTimesPerWeekRadioButton.isChecked = true
|
||||
contentView.xTimesPerWeekTextView.setText(freqNumerator.toString())
|
||||
focus(contentView.xTimesPerWeekTextView)
|
||||
selectInputField(contentView.xTimesPerWeekTextView)
|
||||
} else {
|
||||
Log.w("FrequencyPickerDialog", "Unknown frequency: $freqNumerator/$freqDenominator")
|
||||
contentView.everyDayRadioButton.isChecked = true
|
||||
contentView.xTimesPerYDaysRadioButton.isChecked = true
|
||||
contentView.xTimesPerYDaysXTextView.setText(freqNumerator.toString())
|
||||
contentView.xTimesPerYDaysYTextView.setText(freqDenominator.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun focus(view: EditText) {
|
||||
view.requestFocus()
|
||||
private fun selectInputField(view: EditText) {
|
||||
view.setSelection(view.text.length)
|
||||
}
|
||||
|
||||
@@ -196,11 +217,6 @@ class FrequencyPickerDialog(
|
||||
contentView.everyXDaysRadioButton.isChecked = false
|
||||
contentView.xTimesPerWeekRadioButton.isChecked = false
|
||||
contentView.xTimesPerMonthRadioButton.isChecked = false
|
||||
}
|
||||
|
||||
private fun unfocusAll() {
|
||||
contentView.everyXDaysTextView.clearFocus()
|
||||
contentView.xTimesPerWeekTextView.clearFocus()
|
||||
contentView.xTimesPerMonthTextView.clearFocus()
|
||||
contentView.xTimesPerYDaysRadioButton.isChecked = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,9 +62,11 @@ class HistoryEditorDialog : AppCompatDialogFragment(), CommandRunner.Listener {
|
||||
firstWeekday = preferences.firstWeekday,
|
||||
paletteColor = habit.color,
|
||||
series = emptyList(),
|
||||
defaultSquare = HistoryChart.Square.OFF,
|
||||
notesIndicators = emptyList(),
|
||||
theme = themeSwitcher.currentTheme,
|
||||
today = DateUtils.getTodayWithOffset().toLocalDate(),
|
||||
onDateClickedListener = onDateClickedListener ?: OnDateClickedListener { },
|
||||
onDateClickedListener = onDateClickedListener ?: object : OnDateClickedListener {},
|
||||
padding = 10.0,
|
||||
)
|
||||
dataView = AndroidDataView(context!!, null)
|
||||
@@ -101,6 +103,8 @@ class HistoryEditorDialog : AppCompatDialogFragment(), CommandRunner.Listener {
|
||||
theme = LightTheme()
|
||||
)
|
||||
chart?.series = model.series
|
||||
chart?.defaultSquare = model.defaultSquare
|
||||
chart?.notesIndicators = model.notesIndicators
|
||||
dataView.postInvalidate()
|
||||
}
|
||||
|
||||
|
||||
@@ -19,9 +19,11 @@
|
||||
|
||||
package org.isoron.uhabits.activities.common.dialogs
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.text.InputFilter
|
||||
import android.text.Spanned
|
||||
import android.view.LayoutInflater
|
||||
import android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE
|
||||
import android.view.inputmethod.EditorInfo
|
||||
@@ -33,6 +35,7 @@ import org.isoron.uhabits.R
|
||||
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
|
||||
import org.isoron.uhabits.inject.ActivityContext
|
||||
import org.isoron.uhabits.utils.InterfaceUtils
|
||||
import java.text.DecimalFormatSymbols
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.roundToLong
|
||||
|
||||
@@ -40,18 +43,30 @@ class NumberPickerFactory
|
||||
@Inject constructor(
|
||||
@ActivityContext private val context: Context
|
||||
) {
|
||||
|
||||
fun create(
|
||||
value: Double,
|
||||
unit: String,
|
||||
notes: String,
|
||||
dateString: String,
|
||||
callback: ListHabitsBehavior.NumberPickerCallback
|
||||
): AlertDialog {
|
||||
|
||||
val inflater = LayoutInflater.from(context)
|
||||
val view = inflater.inflate(R.layout.number_picker_dialog, null)
|
||||
|
||||
val picker = view.findViewById<NumberPicker>(R.id.picker)
|
||||
val picker2 = view.findViewById<NumberPicker>(R.id.picker2)
|
||||
val tvUnit = view.findViewById<TextView>(R.id.tvUnit)
|
||||
val etNotes = view.findViewById<EditText>(R.id.etNotes)
|
||||
|
||||
val watcherFilter: InputFilter = SeparatorWatcherInputFilter(picker2)
|
||||
val numberPickerInputText = getNumberPickerInputText(picker)
|
||||
|
||||
// watch the unfiltered input before the filters remove a possible separator from it
|
||||
numberPickerInputText.filters = arrayOf(watcherFilter).plus(numberPickerInputText.filters)
|
||||
|
||||
view.findViewById<TextView>(R.id.tvUnit).text = unit
|
||||
view.findViewById<TextView>(R.id.tvSeparator).text =
|
||||
DecimalFormatSymbols.getInstance().decimalSeparator.toString()
|
||||
|
||||
val intValue = (value * 100).roundToLong().toInt()
|
||||
|
||||
@@ -61,20 +76,22 @@ class NumberPickerFactory
|
||||
picker.wrapSelectorWheel = false
|
||||
|
||||
picker2.minValue = 0
|
||||
picker2.maxValue = 19
|
||||
picker2.setFormatter { v -> String.format("%02d", 5 * v) }
|
||||
picker2.value = intValue % 100 / 5
|
||||
refreshInitialValue(picker2)
|
||||
|
||||
tvUnit.text = unit
|
||||
picker2.maxValue = 99
|
||||
picker2.setFormatter { v -> String.format("%02d", v) }
|
||||
picker2.value = intValue % 100
|
||||
|
||||
etNotes.setText(notes)
|
||||
val dialog = AlertDialog.Builder(context)
|
||||
.setView(view)
|
||||
.setTitle(R.string.change_value)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
.setTitle(dateString)
|
||||
.setPositiveButton(R.string.save) { _, _ ->
|
||||
picker.clearFocus()
|
||||
val v = picker.value + 0.05 * picker2.value
|
||||
callback.onNumberPicked(v)
|
||||
val v = picker.value + 0.01 * picker2.value
|
||||
val note = etNotes.text.toString()
|
||||
callback.onNumberPicked(v, note)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||
callback.onNumberPickerDismissed()
|
||||
}
|
||||
.setOnDismissListener {
|
||||
callback.onNumberPickerDismissed()
|
||||
@@ -89,20 +106,50 @@ class NumberPickerFactory
|
||||
InterfaceUtils.setupEditorAction(
|
||||
picker
|
||||
) { _, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE)
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick()
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
InterfaceUtils.setupEditorAction(
|
||||
picker2
|
||||
) { _, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick()
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
return dialog
|
||||
}
|
||||
|
||||
private fun refreshInitialValue(picker: NumberPicker) {
|
||||
// Workaround for Android bug:
|
||||
// https://code.google.com/p/android/issues/detail?id=35482
|
||||
@SuppressLint("DiscouragedPrivateApi")
|
||||
private fun getNumberPickerInputText(picker: NumberPicker): EditText {
|
||||
val f = NumberPicker::class.java.getDeclaredField("mInputText")
|
||||
f.isAccessible = true
|
||||
val inputText = f.get(picker) as EditText
|
||||
inputText.filters = arrayOfNulls<InputFilter>(0)
|
||||
return f.get(picker) as EditText
|
||||
}
|
||||
}
|
||||
|
||||
class SeparatorWatcherInputFilter(private val nextPicker: NumberPicker) : InputFilter {
|
||||
override fun filter(
|
||||
source: CharSequence?,
|
||||
start: Int,
|
||||
end: Int,
|
||||
dest: Spanned?,
|
||||
dstart: Int,
|
||||
dend: Int
|
||||
): CharSequence {
|
||||
if (source == null || source.isEmpty()) {
|
||||
return ""
|
||||
}
|
||||
for (c in source) {
|
||||
if (c == DecimalFormatSymbols.getInstance().decimalSeparator || c == '.' || c == ',') {
|
||||
nextPicker.performLongClick()
|
||||
break
|
||||
}
|
||||
}
|
||||
return source
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ fun formatFrequency(freqNum: Int, freqDen: Int, resources: Resources) = when {
|
||||
freqNum == 1 && freqDen == 7 -> resources.getString(R.string.every_week)
|
||||
freqNum == 1 && freqDen > 1 -> resources.getString(R.string.every_x_days, freqDen)
|
||||
freqDen == 7 -> resources.getString(R.string.x_times_per_week, freqNum)
|
||||
else -> "$freqNum/$freqDen"
|
||||
else -> resources.getString(R.string.x_times_per_y_days, freqNum, freqDen)
|
||||
}
|
||||
|
||||
class EditHabitActivity : AppCompatActivity() {
|
||||
@@ -88,6 +88,7 @@ class EditHabitActivity : AppCompatActivity() {
|
||||
var reminderHour = -1
|
||||
var reminderMin = -1
|
||||
var reminderDays: WeekdayList = WeekdayList.EVERY_DAY
|
||||
var targetType = NumericalHabitType.AT_LEAST
|
||||
|
||||
override fun onCreate(state: Bundle?) {
|
||||
super.onCreate(state)
|
||||
@@ -107,6 +108,7 @@ class EditHabitActivity : AppCompatActivity() {
|
||||
color = habit.color
|
||||
freqNum = habit.frequency.numerator
|
||||
freqDen = habit.frequency.denominator
|
||||
targetType = habit.targetType
|
||||
habit.reminder?.let {
|
||||
reminderHour = it.hour
|
||||
reminderMin = it.minute
|
||||
@@ -138,6 +140,7 @@ class EditHabitActivity : AppCompatActivity() {
|
||||
HabitType.YES_NO -> {
|
||||
binding.unitOuterBox.visibility = View.GONE
|
||||
binding.targetOuterBox.visibility = View.GONE
|
||||
binding.targetTypeOuterBox.visibility = View.GONE
|
||||
}
|
||||
HabitType.NUMERICAL -> {
|
||||
binding.nameInput.hint = getString(R.string.measurable_short_example)
|
||||
@@ -172,6 +175,23 @@ class EditHabitActivity : AppCompatActivity() {
|
||||
dialog.show(supportFragmentManager, "frequencyPicker")
|
||||
}
|
||||
|
||||
populateTargetType()
|
||||
binding.targetTypePicker.setOnClickListener {
|
||||
val builder = AlertDialog.Builder(this)
|
||||
val arrayAdapter = ArrayAdapter<String>(this, android.R.layout.select_dialog_item)
|
||||
arrayAdapter.add(getString(R.string.target_type_at_least))
|
||||
arrayAdapter.add(getString(R.string.target_type_at_most))
|
||||
builder.setAdapter(arrayAdapter) { dialog, which ->
|
||||
targetType = when (which) {
|
||||
0 -> NumericalHabitType.AT_LEAST
|
||||
else -> NumericalHabitType.AT_MOST
|
||||
}
|
||||
populateTargetType()
|
||||
dialog.dismiss()
|
||||
}
|
||||
builder.show()
|
||||
}
|
||||
|
||||
binding.numericalFrequencyPicker.setOnClickListener {
|
||||
val builder = AlertDialog.Builder(this)
|
||||
val arrayAdapter = ArrayAdapter<String>(this, android.R.layout.select_dialog_item)
|
||||
@@ -262,7 +282,7 @@ class EditHabitActivity : AppCompatActivity() {
|
||||
habit.frequency = Frequency(freqNum, freqDen)
|
||||
if (habitType == HabitType.NUMERICAL) {
|
||||
habit.targetValue = targetInput.text.toString().toDouble()
|
||||
habit.targetType = NumericalHabitType.AT_LEAST
|
||||
habit.targetType = targetType
|
||||
habit.unit = unitInput.text.trim().toString()
|
||||
}
|
||||
habit.type = habitType
|
||||
@@ -324,6 +344,13 @@ class EditHabitActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun populateTargetType() {
|
||||
binding.targetTypePicker.text = when (targetType) {
|
||||
NumericalHabitType.AT_MOST -> getString(R.string.target_type_at_most)
|
||||
else -> getString(R.string.target_type_at_least)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateColors() {
|
||||
androidColor = themeSwitcher.currentTheme.color(color).toInt()
|
||||
binding.colorButton.backgroundTintList = ColorStateList.valueOf(androidColor)
|
||||
|
||||
@@ -53,6 +53,7 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
|
||||
|
||||
override fun onQuestionMarksChanged() {
|
||||
invalidateOptionsMenu()
|
||||
menu.behavior.onPreferencesChanged()
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -82,7 +83,7 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
|
||||
|
||||
override fun onPause() {
|
||||
midnightTimer.onPause()
|
||||
screen.onDettached()
|
||||
screen.onDetached()
|
||||
adapter.cancelRefresh()
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ class ListHabitsMenu @Inject constructor(
|
||||
@ActivityContext context: Context,
|
||||
private val preferences: Preferences,
|
||||
private val themeSwitcher: ThemeSwitcher,
|
||||
private val behavior: ListHabitsMenuBehavior
|
||||
val behavior: ListHabitsMenuBehavior
|
||||
) {
|
||||
val activity = (context as AppCompatActivity)
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import android.content.Intent
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import dagger.Lazy
|
||||
import org.isoron.uhabits.R
|
||||
import org.isoron.uhabits.activities.common.dialogs.CheckmarkDialog
|
||||
import org.isoron.uhabits.activities.common.dialogs.ColorPickerDialogFactory
|
||||
import org.isoron.uhabits.activities.common.dialogs.ConfirmDeleteDialog
|
||||
import org.isoron.uhabits.activities.common.dialogs.NumberPickerFactory
|
||||
@@ -89,6 +90,7 @@ class ListHabitsScreen
|
||||
private val importTaskFactory: ImportDataTaskFactory,
|
||||
private val colorPickerFactory: ColorPickerDialogFactory,
|
||||
private val numberPickerFactory: NumberPickerFactory,
|
||||
private val checkMarkDialog: CheckmarkDialog,
|
||||
private val behavior: Lazy<ListHabitsBehavior>
|
||||
) : CommandRunner.Listener,
|
||||
ListHabitsBehavior.Screen,
|
||||
@@ -101,7 +103,7 @@ class ListHabitsScreen
|
||||
commandRunner.addListener(this)
|
||||
}
|
||||
|
||||
fun onDettached() {
|
||||
fun onDetached() {
|
||||
commandRunner.removeListener(this)
|
||||
}
|
||||
|
||||
@@ -225,9 +227,28 @@ class ListHabitsScreen
|
||||
override fun showNumberPicker(
|
||||
value: Double,
|
||||
unit: String,
|
||||
notes: String,
|
||||
dateString: String,
|
||||
callback: ListHabitsBehavior.NumberPickerCallback
|
||||
) {
|
||||
numberPickerFactory.create(value, unit, callback).show()
|
||||
numberPickerFactory.create(value, unit, notes, dateString, callback).show()
|
||||
}
|
||||
|
||||
override fun showCheckmarkDialog(
|
||||
value: Int,
|
||||
notes: String,
|
||||
dateString: String,
|
||||
color: PaletteColor,
|
||||
callback: ListHabitsBehavior.CheckMarkDialogCallback
|
||||
) {
|
||||
checkMarkDialog.create(
|
||||
value,
|
||||
notes,
|
||||
dateString,
|
||||
color,
|
||||
callback,
|
||||
themeSwitcher.currentTheme!!,
|
||||
).show()
|
||||
}
|
||||
|
||||
private fun getExecuteString(command: Command): String? {
|
||||
|
||||
@@ -37,9 +37,9 @@ import org.isoron.uhabits.core.models.Entry.Companion.YES_AUTO
|
||||
import org.isoron.uhabits.core.models.Entry.Companion.YES_MANUAL
|
||||
import org.isoron.uhabits.core.preferences.Preferences
|
||||
import org.isoron.uhabits.inject.ActivityContext
|
||||
import org.isoron.uhabits.utils.dim
|
||||
import org.isoron.uhabits.utils.drawNotesIndicator
|
||||
import org.isoron.uhabits.utils.getFontAwesome
|
||||
import org.isoron.uhabits.utils.showMessage
|
||||
import org.isoron.uhabits.utils.sp
|
||||
import org.isoron.uhabits.utils.sres
|
||||
import org.isoron.uhabits.utils.toMeasureSpec
|
||||
import javax.inject.Inject
|
||||
@@ -71,7 +71,15 @@ class CheckmarkButtonView(
|
||||
invalidate()
|
||||
}
|
||||
|
||||
var hasNotes = false
|
||||
set(value) {
|
||||
field = value
|
||||
invalidate()
|
||||
}
|
||||
|
||||
var onToggle: (Int) -> Unit = {}
|
||||
|
||||
var onEdit: () -> Unit = {}
|
||||
private var drawer = Drawer()
|
||||
|
||||
init {
|
||||
@@ -81,11 +89,11 @@ class CheckmarkButtonView(
|
||||
}
|
||||
|
||||
fun performToggle() {
|
||||
value = if (preferences.isSkipEnabled) {
|
||||
Entry.nextToggleValueWithSkip(value)
|
||||
} else {
|
||||
Entry.nextToggleValueWithoutSkip(value)
|
||||
}
|
||||
value = Entry.nextToggleValue(
|
||||
value = value,
|
||||
isSkipEnabled = preferences.isSkipEnabled,
|
||||
areQuestionMarksEnabled = preferences.areQuestionMarksEnabled
|
||||
)
|
||||
onToggle(value)
|
||||
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
||||
invalidate()
|
||||
@@ -93,11 +101,12 @@ class CheckmarkButtonView(
|
||||
|
||||
override fun onClick(v: View) {
|
||||
if (preferences.isShortToggleEnabled) performToggle()
|
||||
else showMessage(resources.getString(R.string.long_press_to_toggle))
|
||||
else onEdit()
|
||||
}
|
||||
|
||||
override fun onLongClick(v: View): Boolean {
|
||||
performToggle()
|
||||
if (preferences.isShortToggleEnabled) onEdit()
|
||||
else performToggle()
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -145,6 +154,11 @@ class CheckmarkButtonView(
|
||||
}
|
||||
else -> R.string.fa_check
|
||||
}
|
||||
paint.textSize = when {
|
||||
id == R.string.fa_question -> sp(12.0f)
|
||||
value == YES_AUTO -> sp(13.0f)
|
||||
else -> sp(14.0f)
|
||||
}
|
||||
if (value == YES_AUTO) {
|
||||
paint.strokeWidth = 5f
|
||||
paint.style = Paint.Style.STROKE
|
||||
@@ -153,11 +167,6 @@ class CheckmarkButtonView(
|
||||
paint.style = Paint.Style.FILL
|
||||
}
|
||||
|
||||
paint.textSize = when (id) {
|
||||
UNKNOWN -> dim(R.dimen.smallerTextSize)
|
||||
else -> dim(R.dimen.smallTextSize)
|
||||
}
|
||||
|
||||
val label = resources.getString(id)
|
||||
val em = paint.measureText("m")
|
||||
|
||||
@@ -170,6 +179,8 @@ class CheckmarkButtonView(
|
||||
paint.style = Paint.Style.FILL
|
||||
canvas.drawText(label, rect.centerX(), rect.centerY(), paint)
|
||||
}
|
||||
|
||||
drawNotesIndicator(canvas, color, em, hasNotes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,12 +54,24 @@ class CheckmarkPanelView(
|
||||
setupButtons()
|
||||
}
|
||||
|
||||
var notesIndicators = BooleanArray(0)
|
||||
set(values) {
|
||||
field = values
|
||||
setupButtons()
|
||||
}
|
||||
|
||||
var onToggle: (Timestamp, Int) -> Unit = { _, _ -> }
|
||||
set(value) {
|
||||
field = value
|
||||
setupButtons()
|
||||
}
|
||||
|
||||
var onEdit: (Timestamp) -> Unit = {}
|
||||
set(value) {
|
||||
field = value
|
||||
setupButtons()
|
||||
}
|
||||
|
||||
override fun createButton(): CheckmarkButtonView = buttonFactory.create()
|
||||
|
||||
@Synchronized
|
||||
@@ -72,8 +84,13 @@ class CheckmarkPanelView(
|
||||
index + dataOffset < values.size -> values[index + dataOffset]
|
||||
else -> UNKNOWN
|
||||
}
|
||||
button.hasNotes = when {
|
||||
index + dataOffset < notesIndicators.size -> notesIndicators[index + dataOffset]
|
||||
else -> false
|
||||
}
|
||||
button.color = color
|
||||
button.onToggle = { value -> onToggle(timestamp, value) }
|
||||
button.onEdit = { onEdit(timestamp) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,8 +124,9 @@ class HabitCardListAdapter @Inject constructor(
|
||||
val habit = cache.getHabitByPosition(position)
|
||||
val score = cache.getScore(habit!!.id!!)
|
||||
val checkmarks = cache.getCheckmarks(habit.id!!)
|
||||
val notesIndicators = cache.getNoteIndicators(habit.id!!)
|
||||
val selected = selected.contains(habit)
|
||||
listView!!.bindCardView(holder, habit, score, checkmarks, selected)
|
||||
listView!!.bindCardView(holder, habit, score, checkmarks, notesIndicators, selected)
|
||||
}
|
||||
|
||||
override fun onViewAttachedToWindow(holder: HabitCardViewHolder) {
|
||||
|
||||
@@ -87,6 +87,7 @@ class HabitCardListView(
|
||||
habit: Habit,
|
||||
score: Double,
|
||||
checkmarks: IntArray,
|
||||
notesIndicators: BooleanArray,
|
||||
selected: Boolean
|
||||
): View {
|
||||
val cardView = holder.itemView as HabitCardView
|
||||
@@ -98,6 +99,7 @@ class HabitCardListView(
|
||||
cardView.score = score
|
||||
cardView.unit = habit.unit
|
||||
cardView.threshold = habit.targetValue / habit.frequency.denominator
|
||||
cardView.notesIndicators = notesIndicators
|
||||
|
||||
val detector = GestureDetector(context, CardViewGestureDetector(holder))
|
||||
cardView.setOnTouchListener { _, ev ->
|
||||
|
||||
@@ -57,6 +57,12 @@ class HabitCardViewFactory
|
||||
fun create() = HabitCardView(context, checkmarkPanelFactory, numberPanelFactory, behavior)
|
||||
}
|
||||
|
||||
data class DelayedToggle(
|
||||
var habit: Habit,
|
||||
var timestamp: Timestamp,
|
||||
var value: Int
|
||||
)
|
||||
|
||||
class HabitCardView(
|
||||
@ActivityContext context: Context,
|
||||
checkmarkPanelFactory: CheckmarkPanelViewFactory,
|
||||
@@ -115,12 +121,22 @@ class HabitCardView(
|
||||
numberPanel.threshold = value
|
||||
}
|
||||
|
||||
var notesIndicators
|
||||
get() = checkmarkPanel.notesIndicators
|
||||
set(values) {
|
||||
checkmarkPanel.notesIndicators = values
|
||||
numberPanel.notesIndicators = values
|
||||
}
|
||||
|
||||
var checkmarkPanel: CheckmarkPanelView
|
||||
private var numberPanel: NumberPanelView
|
||||
private var innerFrame: LinearLayout
|
||||
private var label: TextView
|
||||
private var scoreRing: RingView
|
||||
|
||||
private var currentToggleTaskId = 0
|
||||
private var queuedToggles = mutableListOf<DelayedToggle>()
|
||||
|
||||
init {
|
||||
scoreRing = RingView(context).apply {
|
||||
val thickness = dp(3f)
|
||||
@@ -143,7 +159,14 @@ class HabitCardView(
|
||||
checkmarkPanel = checkmarkPanelFactory.create().apply {
|
||||
onToggle = { timestamp, value ->
|
||||
triggerRipple(timestamp)
|
||||
habit?.let { behavior.onToggle(it, timestamp, value) }
|
||||
habit?.let {
|
||||
val taskId = queueToggle(it, timestamp, value);
|
||||
{ runPendingToggles(taskId) }.delay(TOGGLE_DELAY_MILLIS)
|
||||
}
|
||||
}
|
||||
onEdit = { timestamp ->
|
||||
triggerRipple(timestamp)
|
||||
habit?.let { behavior.onEdit(it, timestamp) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,6 +202,24 @@ class HabitCardView(
|
||||
addView(innerFrame)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun runPendingToggles(id: Int) {
|
||||
if (currentToggleTaskId != id) return
|
||||
for ((h, t, v) in queuedToggles) behavior.onToggle(h, t, v)
|
||||
queuedToggles.clear()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun queueToggle(
|
||||
it: Habit,
|
||||
timestamp: Timestamp,
|
||||
value: Int
|
||||
): Int {
|
||||
currentToggleTaskId += 1
|
||||
queuedToggles.add(DelayedToggle(it, timestamp, value))
|
||||
return currentToggleTaskId
|
||||
}
|
||||
|
||||
override fun onModelChange() {
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
habit?.let { copyAttributesFrom(it) }
|
||||
@@ -236,6 +277,7 @@ class HabitCardView(
|
||||
numberPanel.apply {
|
||||
color = c
|
||||
units = h.unit
|
||||
targetType = h.targetType
|
||||
threshold = h.targetValue
|
||||
visibility = when (h.isNumerical) {
|
||||
true -> View.VISIBLE
|
||||
@@ -262,4 +304,12 @@ class HabitCardView(
|
||||
}
|
||||
innerFrame.setBackgroundResource(background)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TOGGLE_DELAY_MILLIS = 2000L
|
||||
|
||||
fun (() -> Unit).delay(delayInMillis: Long) {
|
||||
Handler(Looper.getMainLooper()).postDelayed(this, delayInMillis)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,13 +29,15 @@ import android.view.View
|
||||
import android.view.View.OnClickListener
|
||||
import android.view.View.OnLongClickListener
|
||||
import org.isoron.uhabits.R
|
||||
import org.isoron.uhabits.core.models.NumericalHabitType
|
||||
import org.isoron.uhabits.core.preferences.Preferences
|
||||
import org.isoron.uhabits.inject.ActivityContext
|
||||
import org.isoron.uhabits.utils.InterfaceUtils.getDimension
|
||||
import org.isoron.uhabits.utils.StyledResources
|
||||
import org.isoron.uhabits.utils.dim
|
||||
import org.isoron.uhabits.utils.drawNotesIndicator
|
||||
import org.isoron.uhabits.utils.getFontAwesome
|
||||
import org.isoron.uhabits.utils.showMessage
|
||||
import org.isoron.uhabits.utils.sres
|
||||
import java.lang.Double.max
|
||||
import java.text.DecimalFormat
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -88,11 +90,22 @@ class NumberButtonView(
|
||||
invalidate()
|
||||
}
|
||||
|
||||
var targetType = NumericalHabitType.AT_LEAST
|
||||
set(value) {
|
||||
field = value
|
||||
invalidate()
|
||||
}
|
||||
|
||||
var units = ""
|
||||
set(value) {
|
||||
field = value
|
||||
invalidate()
|
||||
}
|
||||
var hasNotes = false
|
||||
set(value) {
|
||||
field = value
|
||||
invalidate()
|
||||
}
|
||||
|
||||
var onEdit: () -> Unit = {}
|
||||
private var drawer: Drawer = Drawer(context)
|
||||
@@ -103,8 +116,7 @@ class NumberButtonView(
|
||||
}
|
||||
|
||||
override fun onClick(v: View) {
|
||||
if (preferences.isShortToggleEnabled) onEdit()
|
||||
else showMessage(resources.getString(R.string.long_press_to_edit))
|
||||
onEdit()
|
||||
}
|
||||
|
||||
override fun onLongClick(v: View): Boolean {
|
||||
@@ -127,7 +139,6 @@ class NumberButtonView(
|
||||
|
||||
private val em: Float
|
||||
private val rect: RectF = RectF()
|
||||
private val sr = StyledResources(context)
|
||||
|
||||
private val lowContrast: Int
|
||||
private val mediumContrast: Int
|
||||
@@ -148,15 +159,23 @@ class NumberButtonView(
|
||||
|
||||
init {
|
||||
em = pNumber.measureText("m")
|
||||
lowContrast = sr.getColor(R.attr.contrast40)
|
||||
mediumContrast = sr.getColor(R.attr.contrast60)
|
||||
lowContrast = sres.getColor(R.attr.contrast40)
|
||||
mediumContrast = sres.getColor(R.attr.contrast60)
|
||||
}
|
||||
|
||||
fun draw(canvas: Canvas) {
|
||||
val activeColor = when {
|
||||
value <= 0.0 -> lowContrast
|
||||
value < threshold -> mediumContrast
|
||||
else -> color
|
||||
var activeColor = if (targetType == NumericalHabitType.AT_LEAST) {
|
||||
when {
|
||||
value < 0.0 && preferences.areQuestionMarksEnabled -> lowContrast
|
||||
max(0.0, value) >= threshold -> color
|
||||
else -> mediumContrast
|
||||
}
|
||||
} else {
|
||||
when {
|
||||
value < 0.0 && preferences.areQuestionMarksEnabled -> lowContrast
|
||||
value <= threshold -> color
|
||||
else -> mediumContrast
|
||||
}
|
||||
}
|
||||
|
||||
val label: String
|
||||
@@ -196,6 +215,8 @@ class NumberButtonView(
|
||||
rect.offset(0f, 1.3f * em)
|
||||
canvas.drawText(units, rect.centerX(), rect.centerY(), pUnit)
|
||||
}
|
||||
|
||||
drawNotesIndicator(canvas, color, em, hasNotes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
package org.isoron.uhabits.activities.habits.list.views
|
||||
|
||||
import android.content.Context
|
||||
import org.isoron.uhabits.core.models.NumericalHabitType
|
||||
import org.isoron.uhabits.core.models.Timestamp
|
||||
import org.isoron.uhabits.core.preferences.Preferences
|
||||
import org.isoron.uhabits.core.utils.DateUtils
|
||||
@@ -47,6 +48,12 @@ class NumberPanelView(
|
||||
setupButtons()
|
||||
}
|
||||
|
||||
var targetType = NumericalHabitType.AT_LEAST
|
||||
set(value) {
|
||||
field = value
|
||||
setupButtons()
|
||||
}
|
||||
|
||||
var threshold = 0.0
|
||||
set(value) {
|
||||
field = value
|
||||
@@ -65,6 +72,12 @@ class NumberPanelView(
|
||||
setupButtons()
|
||||
}
|
||||
|
||||
var notesIndicators = BooleanArray(0)
|
||||
set(values) {
|
||||
field = values
|
||||
setupButtons()
|
||||
}
|
||||
|
||||
var onEdit: (Timestamp) -> Unit = {}
|
||||
set(value) {
|
||||
field = value
|
||||
@@ -83,7 +96,12 @@ class NumberPanelView(
|
||||
index + dataOffset < values.size -> values[index + dataOffset]
|
||||
else -> 0.0
|
||||
}
|
||||
button.hasNotes = when {
|
||||
index + dataOffset < notesIndicators.size -> notesIndicators[index + dataOffset]
|
||||
else -> false
|
||||
}
|
||||
button.color = color
|
||||
button.targetType = targetType
|
||||
button.threshold = threshold
|
||||
button.units = units
|
||||
button.onEdit = { onEdit(timestamp) }
|
||||
|
||||
@@ -32,12 +32,14 @@ import org.isoron.uhabits.HabitsApplication
|
||||
import org.isoron.uhabits.R
|
||||
import org.isoron.uhabits.activities.AndroidThemeSwitcher
|
||||
import org.isoron.uhabits.activities.HabitsDirFinder
|
||||
import org.isoron.uhabits.activities.common.dialogs.CheckmarkDialog
|
||||
import org.isoron.uhabits.activities.common.dialogs.ConfirmDeleteDialog
|
||||
import org.isoron.uhabits.activities.common.dialogs.HistoryEditorDialog
|
||||
import org.isoron.uhabits.activities.common.dialogs.NumberPickerFactory
|
||||
import org.isoron.uhabits.core.commands.Command
|
||||
import org.isoron.uhabits.core.commands.CommandRunner
|
||||
import org.isoron.uhabits.core.models.Habit
|
||||
import org.isoron.uhabits.core.models.PaletteColor
|
||||
import org.isoron.uhabits.core.preferences.Preferences
|
||||
import org.isoron.uhabits.core.ui.callbacks.OnConfirmedCallback
|
||||
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
|
||||
@@ -164,9 +166,29 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
|
||||
override fun showNumberPicker(
|
||||
value: Double,
|
||||
unit: String,
|
||||
notes: String,
|
||||
dateString: String,
|
||||
callback: ListHabitsBehavior.NumberPickerCallback,
|
||||
) {
|
||||
NumberPickerFactory(this@ShowHabitActivity).create(value, unit, callback).show()
|
||||
NumberPickerFactory(this@ShowHabitActivity).create(value, unit, notes, dateString, callback).show()
|
||||
}
|
||||
|
||||
override fun showCheckmarkDialog(
|
||||
value: Int,
|
||||
notes: String,
|
||||
dateString: String,
|
||||
preferences: Preferences,
|
||||
color: PaletteColor,
|
||||
callback: ListHabitsBehavior.CheckMarkDialogCallback
|
||||
) {
|
||||
CheckmarkDialog(this@ShowHabitActivity, preferences).create(
|
||||
value,
|
||||
notes,
|
||||
dateString,
|
||||
color,
|
||||
callback,
|
||||
themeSwitcher.currentTheme!!,
|
||||
).show()
|
||||
}
|
||||
|
||||
override fun showEditHabitScreen(habit: Habit) {
|
||||
|
||||
@@ -43,6 +43,8 @@ class HistoryCardView(context: Context, attrs: AttributeSet) : LinearLayout(cont
|
||||
theme = state.theme,
|
||||
dateFormatter = JavaLocalDateFormatter(Locale.getDefault()),
|
||||
series = state.series,
|
||||
defaultSquare = state.defaultSquare,
|
||||
notesIndicators = state.notesIndicators,
|
||||
firstWeekday = state.firstWeekday,
|
||||
)
|
||||
binding.chart.postInvalidate()
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.isoron.platform.gui.toInt
|
||||
import org.isoron.uhabits.R
|
||||
import org.isoron.uhabits.activities.habits.edit.formatFrequency
|
||||
import org.isoron.uhabits.activities.habits.list.views.toShortString
|
||||
import org.isoron.uhabits.core.models.NumericalHabitType
|
||||
import org.isoron.uhabits.core.ui.screens.habits.show.views.SubtitleCardState
|
||||
import org.isoron.uhabits.databinding.ShowHabitSubtitleBinding
|
||||
import org.isoron.uhabits.utils.InterfaceUtils
|
||||
@@ -65,7 +66,12 @@ class SubtitleCardView(context: Context, attrs: AttributeSet) : LinearLayout(con
|
||||
binding.questionLabel.visibility = View.VISIBLE
|
||||
binding.targetIcon.visibility = View.VISIBLE
|
||||
binding.targetText.visibility = View.VISIBLE
|
||||
if (!state.isNumerical) {
|
||||
if (state.isNumerical) {
|
||||
binding.targetIcon.text = when (state.targetType) {
|
||||
NumericalHabitType.AT_LEAST -> resources.getString(R.string.fa_arrow_circle_up)
|
||||
else -> resources.getString(R.string.fa_arrow_circle_down)
|
||||
}
|
||||
} else {
|
||||
binding.targetIcon.visibility = View.GONE
|
||||
binding.targetText.visibility = View.GONE
|
||||
}
|
||||
|
||||
@@ -21,8 +21,9 @@ package org.isoron.uhabits.activities.intro
|
||||
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import com.github.paolorotolo.appintro.AppIntro2
|
||||
import com.github.paolorotolo.appintro.AppIntroFragment
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.github.appintro.AppIntro2
|
||||
import com.github.appintro.AppIntroFragment
|
||||
import org.isoron.uhabits.R
|
||||
|
||||
/**
|
||||
@@ -30,7 +31,9 @@ import org.isoron.uhabits.R
|
||||
* launched for the first time.
|
||||
*/
|
||||
class IntroActivity : AppIntro2() {
|
||||
override fun init(savedInstanceState: Bundle?) {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
showStatusBar(false)
|
||||
|
||||
addSlide(
|
||||
@@ -61,11 +64,13 @@ class IntroActivity : AppIntro2() {
|
||||
)
|
||||
}
|
||||
|
||||
override fun onNextPressed() {}
|
||||
|
||||
override fun onDonePressed() {
|
||||
override fun onDonePressed(currentFragment: Fragment?) {
|
||||
super.onDonePressed(currentFragment)
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onSlideChanged() {}
|
||||
override fun onSkipPressed(currentFragment: Fragment?) {
|
||||
super.onSkipPressed(currentFragment)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,17 +23,17 @@ import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import org.isoron.uhabits.HabitsApplication
|
||||
import org.isoron.uhabits.activities.AndroidThemeSwitcher
|
||||
import org.isoron.uhabits.core.models.HabitMatcherBuilder
|
||||
import org.isoron.uhabits.core.models.HabitMatcher
|
||||
|
||||
class EditSettingActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val app = applicationContext as HabitsApplication
|
||||
val habits = app.component.habitList.getFiltered(
|
||||
HabitMatcherBuilder()
|
||||
.setArchivedAllowed(false)
|
||||
.setCompletedAllowed(true)
|
||||
.build()
|
||||
HabitMatcher(
|
||||
isArchivedAllowed = false,
|
||||
isCompletedAllowed = true,
|
||||
)
|
||||
)
|
||||
AndroidThemeSwitcher(this, app.component.preferences).apply()
|
||||
|
||||
|
||||
@@ -25,8 +25,6 @@ import android.app.AlarmManager.RTC_WAKEUP
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Context.ALARM_SERVICE
|
||||
import android.os.Build.VERSION.SDK_INT
|
||||
import android.os.Build.VERSION_CODES.M
|
||||
import android.util.Log
|
||||
import org.isoron.uhabits.core.AppScope
|
||||
import org.isoron.uhabits.core.models.Habit
|
||||
@@ -58,10 +56,7 @@ class IntentScheduler
|
||||
)
|
||||
return SchedulerResult.IGNORED
|
||||
}
|
||||
if (SDK_INT >= M)
|
||||
manager.setExactAndAllowWhileIdle(alarmType, timestamp, intent)
|
||||
else
|
||||
manager.setExact(alarmType, timestamp, intent)
|
||||
manager.setExactAndAllowWhileIdle(alarmType, timestamp, intent)
|
||||
return SchedulerResult.OK
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
package org.isoron.uhabits.intents
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.app.PendingIntent.FLAG_IMMUTABLE
|
||||
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
||||
import android.app.PendingIntent.getBroadcast
|
||||
import android.content.Context
|
||||
@@ -49,7 +50,7 @@ class PendingIntentFactory
|
||||
action = WidgetReceiver.ACTION_ADD_REPETITION
|
||||
if (timestamp != null) putExtra("timestamp", timestamp.unixTime)
|
||||
},
|
||||
FLAG_UPDATE_CURRENT
|
||||
FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
|
||||
)
|
||||
|
||||
fun dismissNotification(habit: Habit): PendingIntent =
|
||||
@@ -60,7 +61,7 @@ class PendingIntentFactory
|
||||
action = WidgetReceiver.ACTION_DISMISS_REMINDER
|
||||
data = Uri.parse(habit.uriString)
|
||||
},
|
||||
FLAG_UPDATE_CURRENT
|
||||
FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
|
||||
)
|
||||
|
||||
fun removeRepetition(habit: Habit, timestamp: Timestamp?): PendingIntent =
|
||||
@@ -72,7 +73,7 @@ class PendingIntentFactory
|
||||
data = Uri.parse(habit.uriString)
|
||||
if (timestamp != null) putExtra("timestamp", timestamp.unixTime)
|
||||
},
|
||||
FLAG_UPDATE_CURRENT
|
||||
FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
|
||||
)
|
||||
|
||||
fun showHabit(habit: Habit): PendingIntent =
|
||||
@@ -84,7 +85,7 @@ class PendingIntentFactory
|
||||
habit
|
||||
)
|
||||
)
|
||||
.getPendingIntent(0, FLAG_UPDATE_CURRENT)!!
|
||||
.getPendingIntent(0, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT)!!
|
||||
|
||||
fun showReminder(
|
||||
habit: Habit,
|
||||
@@ -100,7 +101,7 @@ class PendingIntentFactory
|
||||
putExtra("timestamp", timestamp)
|
||||
putExtra("reminderTime", reminderTime)
|
||||
},
|
||||
FLAG_UPDATE_CURRENT
|
||||
FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
|
||||
)
|
||||
|
||||
fun snoozeNotification(habit: Habit): PendingIntent =
|
||||
@@ -111,7 +112,7 @@ class PendingIntentFactory
|
||||
data = Uri.parse(habit.uriString)
|
||||
action = ReminderReceiver.ACTION_SNOOZE_REMINDER
|
||||
},
|
||||
FLAG_UPDATE_CURRENT
|
||||
FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
|
||||
)
|
||||
|
||||
fun toggleCheckmark(habit: Habit, timestamp: Long?): PendingIntent =
|
||||
@@ -123,7 +124,7 @@ class PendingIntentFactory
|
||||
action = WidgetReceiver.ACTION_TOGGLE_REPETITION
|
||||
if (timestamp != null) putExtra("timestamp", timestamp)
|
||||
},
|
||||
FLAG_UPDATE_CURRENT
|
||||
FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
|
||||
)
|
||||
|
||||
fun setNumericalValue(
|
||||
@@ -142,7 +143,7 @@ class PendingIntentFactory
|
||||
putExtra("numericalValue", numericalValue)
|
||||
if (timestamp != null) putExtra("timestamp", timestamp)
|
||||
},
|
||||
FLAG_UPDATE_CURRENT
|
||||
FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
|
||||
)
|
||||
|
||||
fun updateWidgets(): PendingIntent =
|
||||
@@ -152,6 +153,6 @@ class PendingIntentFactory
|
||||
Intent(context, WidgetReceiver::class.java).apply {
|
||||
action = WidgetReceiver.ACTION_UPDATE_WIDGETS_VALUE
|
||||
},
|
||||
FLAG_UPDATE_CURRENT
|
||||
FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
|
||||
)
|
||||
}
|
||||
|
||||
@@ -153,13 +153,15 @@ class AndroidNotificationTray
|
||||
if (preferences.shouldMakeNotificationsLed())
|
||||
builder.setLights(Color.RED, 1000, 1000)
|
||||
|
||||
val snoozeAction = Action(
|
||||
R.drawable.ic_action_snooze,
|
||||
context.getString(R.string.snooze),
|
||||
pendingIntents.snoozeNotification(habit)
|
||||
)
|
||||
wearableExtender.addAction(snoozeAction)
|
||||
builder.addAction(snoozeAction)
|
||||
if (SDK_INT < Build.VERSION_CODES.S) {
|
||||
val snoozeAction = Action(
|
||||
R.drawable.ic_action_snooze,
|
||||
context.getString(R.string.snooze),
|
||||
pendingIntents.snoozeNotification(habit)
|
||||
)
|
||||
wearableExtender.addAction(snoozeAction)
|
||||
builder.addAction(snoozeAction)
|
||||
}
|
||||
|
||||
builder.extend(wearableExtender)
|
||||
return builder.build()
|
||||
|
||||
@@ -22,6 +22,8 @@ import android.content.BroadcastReceiver
|
||||
import android.content.ContentUris
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Build.VERSION.SDK_INT
|
||||
import android.util.Log
|
||||
import org.isoron.uhabits.HabitsApplication
|
||||
import org.isoron.uhabits.core.models.Habit
|
||||
@@ -76,8 +78,21 @@ class ReminderReceiver : BroadcastReceiver() {
|
||||
}
|
||||
ACTION_SNOOZE_REMINDER -> {
|
||||
if (habit == null) return
|
||||
Log.d("ReminderReceiver", String.format("onSnoozePressed habit=%d", habit.id))
|
||||
reminderController.onSnoozePressed(habit, context)
|
||||
if (SDK_INT < Build.VERSION_CODES.S) {
|
||||
Log.d(
|
||||
"ReminderReceiver",
|
||||
String.format("onSnoozePressed habit=%d", habit.id)
|
||||
)
|
||||
reminderController.onSnoozePressed(habit, context)
|
||||
} else {
|
||||
Log.w(
|
||||
"ReminderReceiver",
|
||||
String.format(
|
||||
"onSnoozePressed habit=%d, should be deactivated in recent versions.",
|
||||
habit.id
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
Intent.ACTION_BOOT_COMPLETED -> {
|
||||
Log.d("ReminderReceiver", "onBootCompleted")
|
||||
|
||||
@@ -22,7 +22,9 @@ package org.isoron.uhabits.utils
|
||||
import android.app.Activity
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.os.Handler
|
||||
import android.view.LayoutInflater
|
||||
@@ -199,5 +201,15 @@ fun View.dim(id: Int) = InterfaceUtils.getDimension(context, id)
|
||||
fun View.sp(value: Float) = InterfaceUtils.spToPixels(context, value)
|
||||
fun View.dp(value: Float) = InterfaceUtils.dpToPixels(context, value)
|
||||
fun View.str(id: Int) = resources.getString(id)
|
||||
|
||||
fun View.drawNotesIndicator(canvas: Canvas, color: Int, size: Float, hasNotes: Boolean) {
|
||||
val pNotesIndicator = Paint()
|
||||
pNotesIndicator.color = color
|
||||
if (hasNotes) {
|
||||
val cy = 0.8f * size
|
||||
canvas.drawCircle(width.toFloat() - cy, cy, 8f, pNotesIndicator)
|
||||
}
|
||||
}
|
||||
|
||||
val View.sres: StyledResources
|
||||
get() = StyledResources(context)
|
||||
|
||||
@@ -56,7 +56,10 @@ class HistoryWidget(
|
||||
theme = WidgetTheme(),
|
||||
)
|
||||
(widgetView.dataView as AndroidDataView).apply {
|
||||
(this.view as HistoryChart).series = model.series
|
||||
val historyChart = (this.view as HistoryChart)
|
||||
historyChart.series = model.series
|
||||
historyChart.defaultSquare = model.defaultSquare
|
||||
historyChart.notesIndicators = model.notesIndicators
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +74,8 @@ class HistoryWidget(
|
||||
dateFormatter = JavaLocalDateFormatter(Locale.getDefault()),
|
||||
firstWeekday = prefs.firstWeekday,
|
||||
series = listOf(),
|
||||
defaultSquare = HistoryChart.Square.OFF,
|
||||
notesIndicators = listOf(),
|
||||
)
|
||||
}
|
||||
).apply {
|
||||
|
||||
@@ -26,8 +26,8 @@ import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.view.View
|
||||
import android.widget.RemoteViews
|
||||
import org.isoron.platform.utils.StringUtils
|
||||
import org.isoron.uhabits.core.models.Habit
|
||||
import org.isoron.uhabits.core.utils.StringUtils
|
||||
|
||||
class StackWidget(
|
||||
context: Context,
|
||||
|
||||
@@ -27,11 +27,11 @@ import android.util.Log
|
||||
import android.widget.RemoteViews
|
||||
import android.widget.RemoteViewsService
|
||||
import android.widget.RemoteViewsService.RemoteViewsFactory
|
||||
import org.isoron.platform.utils.StringUtils.Companion.splitLongs
|
||||
import org.isoron.uhabits.HabitsApplication
|
||||
import org.isoron.uhabits.core.models.Habit
|
||||
import org.isoron.uhabits.core.models.HabitNotFoundException
|
||||
import org.isoron.uhabits.core.preferences.Preferences
|
||||
import org.isoron.uhabits.core.utils.StringUtils.Companion.splitLongs
|
||||
import org.isoron.uhabits.utils.InterfaceUtils.dpToPixels
|
||||
import java.util.ArrayList
|
||||
|
||||
|
||||
@@ -60,8 +60,8 @@ class NumericalCheckmarkWidgetActivity : Activity(), ListHabitsBehavior.NumberPi
|
||||
SystemUtils.unlockScreen(this)
|
||||
}
|
||||
|
||||
override fun onNumberPicked(newValue: Double) {
|
||||
behavior.setValue(data.habit, data.timestamp, (newValue * 1000).toInt())
|
||||
override fun onNumberPicked(newValue: Double, notes: String) {
|
||||
behavior.setValue(data.habit, data.timestamp, (newValue * 1000).toInt(), notes)
|
||||
widgetUpdater.updateWidgets()
|
||||
finish()
|
||||
}
|
||||
@@ -79,6 +79,8 @@ class NumericalCheckmarkWidgetActivity : Activity(), ListHabitsBehavior.NumberPi
|
||||
numberPickerFactory.create(
|
||||
entry.value / 1000.0,
|
||||
data.habit.unit,
|
||||
entry.notes,
|
||||
today.toDialogDateString(),
|
||||
this
|
||||
).show()
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="#FFFFFF"
|
||||
android:alpha="0.8">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M20,12l-1.41,-1.41L13,16.17V4h-2v12.17l-5.58,-5.59L4,12l8,8 8,-8z"/>
|
||||
</vector>
|
||||
@@ -1,11 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="#333333"
|
||||
android:alpha="0.6">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M20,12l-1.41,-1.41L13,16.17V4h-2v12.17l-5.58,-5.59L4,12l8,8 8,-8z"/>
|
||||
</vector>
|
||||
@@ -1,11 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="#FFFFFF"
|
||||
android:alpha="0.8">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M4,12l1.41,1.41L11,7.83V20h2V7.83l5.58,5.59L20,12l-8,-8 -8,8z"/>
|
||||
</vector>
|
||||
@@ -1,11 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="#333333"
|
||||
android:alpha="0.6">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M4,12l1.41,1.41L11,7.83V20h2V7.83l5.58,5.59L20,12l-8,-8 -8,8z"/>
|
||||
</vector>
|
||||
|
Before Width: | Height: | Size: 155 B |
|
Before Width: | Height: | Size: 338 B |
|
Before Width: | Height: | Size: 266 B |
|
Before Width: | Height: | Size: 432 B |
|
Before Width: | Height: | Size: 492 B |
|
Before Width: | Height: | Size: 563 B |
|
Before Width: | Height: | Size: 249 B |
|
Before Width: | Height: | Size: 304 B |
|
Before Width: | Height: | Size: 354 B |
|
Before Width: | Height: | Size: 111 B |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 337 B |
|
Before Width: | Height: | Size: 240 B |
|
Before Width: | Height: | Size: 237 B |
|
Before Width: | Height: | Size: 234 B |
|
Before Width: | Height: | Size: 246 B |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 618 B |
|
Before Width: | Height: | Size: 111 B |
|
Before Width: | Height: | Size: 214 B |
|
Before Width: | Height: | Size: 198 B |
|
Before Width: | Height: | Size: 337 B |
|
Before Width: | Height: | Size: 289 B |
|
Before Width: | Height: | Size: 319 B |
|
Before Width: | Height: | Size: 198 B |
|
Before Width: | Height: | Size: 183 B |
|
Before Width: | Height: | Size: 196 B |
|
Before Width: | Height: | Size: 90 B |
|
Before Width: | Height: | Size: 694 B |
|
Before Width: | Height: | Size: 207 B |
|
Before Width: | Height: | Size: 180 B |
|
Before Width: | Height: | Size: 182 B |
|
Before Width: | Height: | Size: 177 B |
|
Before Width: | Height: | Size: 182 B |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 435 B |
|
Before Width: | Height: | Size: 140 B |
|
Before Width: | Height: | Size: 338 B |
|
Before Width: | Height: | Size: 323 B |
|
Before Width: | Height: | Size: 488 B |
|
Before Width: | Height: | Size: 541 B |
|
Before Width: | Height: | Size: 602 B |
|
Before Width: | Height: | Size: 279 B |
|
Before Width: | Height: | Size: 267 B |
|
Before Width: | Height: | Size: 296 B |
|
Before Width: | Height: | Size: 103 B |
|
Before Width: | Height: | Size: 1.3 KiB |