Compare commits
43 Commits
hotfix/2.1
...
458c9f3b15
| Author | SHA1 | Date | |
|---|---|---|---|
| 458c9f3b15 | |||
| dfa74960b3 | |||
| f082842fbe | |||
| d4d818a085 | |||
|
|
f074d0331d | ||
|
|
f94bc62a94 | ||
| b0097fa45e | |||
|
|
15fa1fea8c | ||
| 31368cff45 | |||
| b44dd96dd3 | |||
|
|
08fab0cd8d | ||
|
|
a142685d2e | ||
| a7a95f2030 | |||
|
|
d3c90481be | ||
| f9bb0d7d7b | |||
| f23a1bedee | |||
| dcf31ba115 | |||
|
|
5409a324e8 | ||
|
|
504362e680 | ||
|
|
0ce2f8fae2 | ||
| 2fc6c67432 | |||
| 459cf02642 | |||
| 44cb64601f | |||
|
|
dd47d4cf08 | ||
|
|
8912a9d73c | ||
|
|
9bd1c6f685 | ||
|
|
e15e06034c | ||
|
|
a6180a5049 | ||
|
|
7c69b17e77 | ||
|
|
602a40a532 | ||
|
|
e00998f913 | ||
|
|
af5914c2db | ||
|
|
6c5f70638a | ||
|
|
ba88552919 | ||
|
|
8c90c4f68d | ||
|
|
08eb1a600d | ||
|
|
8ea0480d4a | ||
|
|
36ee39589e | ||
|
|
ac7a721940 | ||
|
|
b36ca8673a | ||
|
|
7cfac486f9 | ||
|
|
21dd413ab5 | ||
|
|
39e10638b5 |
2
.github/workflows/main.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
|||||||
run: ./build.sh build
|
run: ./build.sh build
|
||||||
|
|
||||||
- name: Run Android tests
|
- name: Run Android tests
|
||||||
run: ./build.sh android-tests-parallel 24 25 26 28 30 31
|
run: ./build.sh android-tests-parallel 23 24 25 26 27 28 30 31
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
if: always()
|
if: always()
|
||||||
|
|||||||
23
CHANGELOG.md
@@ -1,25 +1,6 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [2.1.2] -- 2023-05-26
|
## [2.1.0] -- Unreleased
|
||||||
### Fixed
|
|
||||||
- Fix bug that caused widget to enter checkmark on wrong date (@iSoron, #1541)
|
|
||||||
- Fix widget corners on Android 12 (@iSoron)
|
|
||||||
- Fix bug that caused notes to be lost when editing a checkmark (@iSoron, #1566)
|
|
||||||
- Prevent soft keyboard from covering entry popup (@iSoron)
|
|
||||||
- Accept comma (instead of dot) in certain locales (@iSoron)
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Remove update delay after entering a checkmark (@iSoron)
|
|
||||||
|
|
||||||
### Removed
|
|
||||||
- Remove stack widgets (@iSoron)
|
|
||||||
|
|
||||||
|
|
||||||
## [2.1.1] -- 2022-09-24
|
|
||||||
### Fixed
|
|
||||||
- Fix Tasker plugin (@iSoron, #1503)
|
|
||||||
|
|
||||||
## [2.1.0] -- 2022-09-10
|
|
||||||
### Added
|
### Added
|
||||||
- Allow user to add notes to specific dates (@vbh, #1103)
|
- Allow user to add notes to specific dates (@vbh, #1103)
|
||||||
- Allow user to track "at most" numerical habits (@KristianTashkov, #1101)
|
- Allow user to track "at most" numerical habits (@KristianTashkov, #1101)
|
||||||
@@ -28,7 +9,6 @@
|
|||||||
- Improve number picker (@hiqua, @iSoron, #1082, #1370)
|
- Improve number picker (@hiqua, @iSoron, #1082, #1370)
|
||||||
- Add new checkmark and number picker (@iSoron, #1370)
|
- Add new checkmark and number picker (@iSoron, #1370)
|
||||||
- Allow user to import numerical habits from HabitBull (@hiqua, #1278)
|
- Allow user to import numerical habits from HabitBull (@hiqua, #1278)
|
||||||
- Add support for Android 13 themed icons (@cheeeeer, #1497)
|
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
- Hide snooze button Android 12 notifications (@hiqua, #1226)
|
- Hide snooze button Android 12 notifications (@hiqua, #1226)
|
||||||
@@ -48,7 +28,6 @@
|
|||||||
- Fix small issues in calendar chart (@kalina559, #1314)
|
- Fix small issues in calendar chart (@kalina559, #1314)
|
||||||
- Resort habit list after edit (@hiqua, #1350)
|
- Resort habit list after edit (@hiqua, #1350)
|
||||||
- Fix marker scaling in frequency display (@eduebernal, #1425)
|
- Fix marker scaling in frequency display (@eduebernal, #1425)
|
||||||
- Fix widgets not working correctly on API 33 (@iSoron, #1488)
|
|
||||||
|
|
||||||
### Refactoring & Testing
|
### Refactoring & Testing
|
||||||
- Replace raster icons by vector assets (@kalina559)
|
- Replace raster icons by vector assets (@kalina559)
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
plugins {
|
plugins {
|
||||||
val kotlinVersion = "1.6.10"
|
val kotlinVersion = "1.7.10"
|
||||||
id("com.android.application") version ("7.0.3") apply (false)
|
id("com.android.application") version ("7.3.0-rc01") 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.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 "10.2.1"
|
id("org.jlleitschuh.gradle.ktlint") version "10.3.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply {
|
apply {
|
||||||
|
|||||||
16
build.sh
@@ -217,28 +217,20 @@ android_test_parallel() {
|
|||||||
(
|
(
|
||||||
LOG=build/android-test-$API.log
|
LOG=build/android-test-$API.log
|
||||||
log_info "API $API: Running tests..."
|
log_info "API $API: Running tests..."
|
||||||
android_test $API 1>$LOG 2>&1
|
if android_test $API 1>$LOG 2>&1; then
|
||||||
ret_code=$?
|
|
||||||
if [ $ret_code = 0 ]; then
|
|
||||||
log_info "API $API: Passed"
|
log_info "API $API: Passed"
|
||||||
else
|
else
|
||||||
log_error "API $API: Failed"
|
log_error "API $API: Failed"
|
||||||
fi
|
fi
|
||||||
pkill -9 -f ${AVD_PREFIX}${API}
|
pkill -9 -f ${AVD_PREFIX}${API}
|
||||||
exit $ret_code
|
|
||||||
)&
|
)&
|
||||||
PIDS+=" $!"
|
PIDS+=" $!"
|
||||||
done
|
done
|
||||||
|
|
||||||
# Check exit codes
|
# Check exit codes
|
||||||
success=0
|
RET_CODE=0
|
||||||
for pid in $PIDS; do
|
for pid in $PIDS; do
|
||||||
wait $pid
|
wait $pid || RET_CODE=1
|
||||||
ret_code=$?
|
|
||||||
if [ $ret_code != 0 ]; then
|
|
||||||
success=1
|
|
||||||
fi
|
|
||||||
echo pid=$pid ret_code=$ret_code success=$success
|
|
||||||
done
|
done
|
||||||
|
|
||||||
# Print all logs
|
# Print all logs
|
||||||
@@ -248,7 +240,7 @@ android_test_parallel() {
|
|||||||
echo "::endgroup::"
|
echo "::endgroup::"
|
||||||
done
|
done
|
||||||
|
|
||||||
return $success
|
return $RET_CODE
|
||||||
}
|
}
|
||||||
|
|
||||||
android_build() {
|
android_build() {
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ The repository will be downloaded to the directory `uhabits`.
|
|||||||
2. When the IDE asks you for the project location, select `uhabits` and click "Ok".
|
2. When the IDE asks you for the project location, select `uhabits` and click "Ok".
|
||||||
3. Android Studio will spend some time indexing the project. When this is complete, click the toolbar icon "Sync Project with Gradle File", located near the right corner of the top toolbar.
|
3. Android Studio will spend some time indexing the project. When this is complete, click the toolbar icon "Sync Project with Gradle File", located near the right corner of the top toolbar.
|
||||||
4. The operation will likely fail several times due to missing Android SDK components. Each time it fails, click the link "Install missing platforms", "Install build tools", etc, and try again.
|
4. The operation will likely fail several times due to missing Android SDK components. Each time it fails, click the link "Install missing platforms", "Install build tools", etc, and try again.
|
||||||
5. To test the application, create a virtual Android device using the menu "Tools" and "AVD Manager". The default options should work fine, but free to customize the device.
|
5. To test the application, create a virtual Android device using the menu "Tools" and "AVD Manager". The default options should work fine, but feel free to customize the device.
|
||||||
6. Click the menu "Run" and "uhabits-android". The application should launch.
|
6. Click the menu "Run" and "uhabits-android". The application should launch.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
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.2-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
@@ -32,11 +32,11 @@ tasks.compileLint {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
|
|
||||||
compileSdk = 31
|
compileSdk = 32
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
versionCode = 20103
|
versionCode = 20100
|
||||||
versionName = "2.1.3"
|
versionName = "2.1.0"
|
||||||
minSdk = 23
|
minSdk = 23
|
||||||
targetSdk = 31
|
targetSdk = 31
|
||||||
applicationId = "org.isoron.uhabits"
|
applicationId = "org.isoron.uhabits"
|
||||||
@@ -68,12 +68,6 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lint {
|
|
||||||
isCheckReleaseBuilds = false
|
|
||||||
isAbortOnError = false
|
|
||||||
disable("GoogleAppIndexingWarning")
|
|
||||||
}
|
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
isCoreLibraryDesugaringEnabled = true
|
isCoreLibraryDesugaringEnabled = true
|
||||||
targetCompatibility(JavaVersion.VERSION_1_8)
|
targetCompatibility(JavaVersion.VERSION_1_8)
|
||||||
@@ -86,19 +80,19 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
val daggerVersion = "2.41"
|
val daggerVersion = "2.43.2"
|
||||||
val kotlinVersion = "1.6.21"
|
val kotlinVersion = "1.7.10"
|
||||||
val kxCoroutinesVersion = "1.6.1"
|
val kxCoroutinesVersion = "1.6.4"
|
||||||
val ktorVersion = "1.6.8"
|
val ktorVersion = "1.6.8"
|
||||||
val espressoVersion = "3.4.0"
|
val espressoVersion = "3.4.0"
|
||||||
|
|
||||||
androidTestImplementation("androidx.test.espresso:espresso-contrib:$espressoVersion")
|
androidTestImplementation("androidx.test.espresso:espresso-contrib:$espressoVersion")
|
||||||
androidTestImplementation("androidx.test.espresso:espresso-core:$espressoVersion")
|
androidTestImplementation("androidx.test.espresso:espresso-core:$espressoVersion")
|
||||||
androidTestImplementation("com.google.dagger:dagger:$daggerVersion")
|
androidTestImplementation("com.google.dagger:dagger:$daggerVersion")
|
||||||
androidTestImplementation("com.linkedin.dexmaker:dexmaker-mockito:2.28.1")
|
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.3.0")
|
androidTestImplementation("androidx.annotation:annotation:1.4.0")
|
||||||
androidTestImplementation("androidx.test.ext:junit:1.1.3")
|
androidTestImplementation("androidx.test.ext:junit:1.1.3")
|
||||||
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.2.0")
|
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.2.0")
|
||||||
androidTestImplementation("androidx.test:rules:1.4.0")
|
androidTestImplementation("androidx.test:rules:1.4.0")
|
||||||
@@ -116,10 +110,10 @@ 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.4.1")
|
implementation("androidx.appcompat:appcompat:1.5.0")
|
||||||
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.5.0")
|
implementation("com.google.android.material:material:1.6.1")
|
||||||
implementation("com.opencsv:opencsv:5.6")
|
implementation("com.opencsv:opencsv:5.6")
|
||||||
implementation(project(":uhabits-core"))
|
implementation(project(":uhabits-core"))
|
||||||
kapt("com.google.dagger:dagger-compiler:$daggerVersion")
|
kapt("com.google.dagger:dagger-compiler:$daggerVersion")
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 15 KiB |
@@ -53,6 +53,7 @@ object ListHabitsSteps {
|
|||||||
clickViewWithId(R.id.action_filter)
|
clickViewWithId(R.id.action_filter)
|
||||||
CommonSteps.clickText(R.string.hide_completed)
|
CommonSteps.clickText(R.string.hide_completed)
|
||||||
}
|
}
|
||||||
|
else -> throw RuntimeException()
|
||||||
}
|
}
|
||||||
device.waitForIdle()
|
device.waitForIdle()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class EntryButtonViewTest : BaseViewTest() {
|
|||||||
view = component.getEntryButtonViewFactory().create().apply {
|
view = component.getEntryButtonViewFactory().create().apply {
|
||||||
value = Entry.NO
|
value = Entry.NO
|
||||||
color = PaletteUtils.getAndroidTestColor(5)
|
color = PaletteUtils.getAndroidTestColor(5)
|
||||||
onToggle = { _, _ -> toggled = true }
|
onToggle = { _, _, _ -> toggled = true }
|
||||||
onEdit = { edited = true }
|
onEdit = { edited = true }
|
||||||
}
|
}
|
||||||
measureView(view, dpToPixels(48), dpToPixels(48))
|
measureView(view, dpToPixels(48), dpToPixels(48))
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ class EntryPanelViewTest : BaseViewTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun testToggle() {
|
fun testToggle() {
|
||||||
val timestamps = mutableListOf<Timestamp>()
|
val timestamps = mutableListOf<Timestamp>()
|
||||||
view.onToggle = { t, _, _ -> timestamps.add(t) }
|
view.onToggle = { t, _, _, _ -> timestamps.add(t) }
|
||||||
view.buttons[0].performLongClick()
|
view.buttons[0].performLongClick()
|
||||||
view.buttons[2].performLongClick()
|
view.buttons[2].performLongClick()
|
||||||
view.buttons[3].performLongClick()
|
view.buttons[3].performLongClick()
|
||||||
@@ -88,7 +88,7 @@ class EntryPanelViewTest : BaseViewTest() {
|
|||||||
fun testToggle_withOffset() {
|
fun testToggle_withOffset() {
|
||||||
val timestamps = mutableListOf<Timestamp>()
|
val timestamps = mutableListOf<Timestamp>()
|
||||||
view.dataOffset = 3
|
view.dataOffset = 3
|
||||||
view.onToggle = { t, _, _ -> timestamps += t }
|
view.onToggle = { t, _, _, _ -> timestamps += t }
|
||||||
view.buttons[0].performLongClick()
|
view.buttons[0].performLongClick()
|
||||||
view.buttons[2].performLongClick()
|
view.buttons[2].performLongClick()
|
||||||
view.buttons[3].performLongClick()
|
view.buttons[3].performLongClick()
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ class CheckmarkWidgetViewTest : BaseViewTest() {
|
|||||||
@Before
|
@Before
|
||||||
override fun setUp() {
|
override fun setUp() {
|
||||||
super.setUp()
|
super.setUp()
|
||||||
similarityCutoff = 0.00025
|
|
||||||
setTheme(R.style.WidgetTheme)
|
setTheme(R.style.WidgetTheme)
|
||||||
val habit = fixtures.createShortHabit()
|
val habit = fixtures.createShortHabit()
|
||||||
val computedEntries = habit.computedEntries
|
val computedEntries = habit.computedEntries
|
||||||
|
|||||||
@@ -270,7 +270,7 @@
|
|||||||
<!-- Locale/Tasker -->
|
<!-- Locale/Tasker -->
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".automation.FireSettingReceiver"
|
android:name=".automation.FireSettingReceiver"
|
||||||
android:exported="true">
|
android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING" />
|
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|||||||
@@ -20,60 +20,109 @@
|
|||||||
package org.isoron.uhabits.activities.common.dialogs
|
package org.isoron.uhabits.activities.common.dialogs
|
||||||
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.os.Bundle
|
import android.content.Context
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
import android.view.View.GONE
|
import android.view.View.GONE
|
||||||
import android.view.View.VISIBLE
|
import android.view.View.VISIBLE
|
||||||
import androidx.appcompat.app.AppCompatDialogFragment
|
|
||||||
import org.isoron.uhabits.HabitsApplication
|
|
||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
import org.isoron.uhabits.core.models.Entry.Companion.NO
|
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.SKIP
|
||||||
import org.isoron.uhabits.core.models.Entry.Companion.UNKNOWN
|
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.Entry.Companion.YES_MANUAL
|
||||||
|
import org.isoron.uhabits.core.preferences.Preferences
|
||||||
import org.isoron.uhabits.databinding.CheckmarkPopupBinding
|
import org.isoron.uhabits.databinding.CheckmarkPopupBinding
|
||||||
import org.isoron.uhabits.utils.InterfaceUtils.getFontAwesome
|
import org.isoron.uhabits.utils.InterfaceUtils.getFontAwesome
|
||||||
|
import org.isoron.uhabits.utils.dimBehind
|
||||||
|
import org.isoron.uhabits.utils.dismissCurrentAndShow
|
||||||
|
import org.isoron.uhabits.utils.dp
|
||||||
import org.isoron.uhabits.utils.sres
|
import org.isoron.uhabits.utils.sres
|
||||||
|
|
||||||
class CheckmarkDialog : AppCompatDialogFragment() {
|
const val POPUP_WIDTH = 4 * 48f + 16f
|
||||||
var onToggle: (Int, String) -> Unit = { _, _ -> }
|
const val POPUP_HEIGHT = 48f * 2.5f + 8f
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
class CheckmarkPopup(
|
||||||
val appComponent = (requireActivity().application as HabitsApplication).component
|
private val context: Context,
|
||||||
val prefs = appComponent.preferences
|
private val color: Int,
|
||||||
val view = CheckmarkPopupBinding.inflate(LayoutInflater.from(context))
|
private var notes: String,
|
||||||
|
private var value: Int,
|
||||||
|
private val prefs: Preferences,
|
||||||
|
private val anchor: View,
|
||||||
|
) {
|
||||||
|
var onToggle: (Int, String) -> Unit = { _, _ -> }
|
||||||
|
private lateinit var dialog: Dialog
|
||||||
|
|
||||||
|
private val view = CheckmarkPopupBinding.inflate(LayoutInflater.from(context)).apply {
|
||||||
|
// Required for round corners
|
||||||
|
container.clipToOutline = true
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
view.booleanButtons.visibility = VISIBLE
|
||||||
|
initColors()
|
||||||
|
initTypefaces()
|
||||||
|
hideDisabledButtons()
|
||||||
|
populate()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initColors() {
|
||||||
arrayOf(view.yesBtn, view.skipBtn).forEach {
|
arrayOf(view.yesBtn, view.skipBtn).forEach {
|
||||||
it.setTextColor(requireArguments().getInt("color"))
|
it.setTextColor(color)
|
||||||
}
|
}
|
||||||
arrayOf(view.noBtn, view.unknownBtn).forEach {
|
arrayOf(view.noBtn, view.unknownBtn).forEach {
|
||||||
it.setTextColor(view.root.sres.getColor(R.attr.contrast60))
|
it.setTextColor(view.root.sres.getColor(R.attr.contrast60))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initTypefaces() {
|
||||||
arrayOf(view.yesBtn, view.noBtn, view.skipBtn, view.unknownBtn).forEach {
|
arrayOf(view.yesBtn, view.noBtn, view.skipBtn, view.unknownBtn).forEach {
|
||||||
it.typeface = getFontAwesome(requireContext())
|
it.typeface = getFontAwesome(context)
|
||||||
}
|
}
|
||||||
view.notes.setText(requireArguments().getString("notes")!!)
|
}
|
||||||
|
|
||||||
|
private fun hideDisabledButtons() {
|
||||||
if (!prefs.isSkipEnabled) view.skipBtn.visibility = GONE
|
if (!prefs.isSkipEnabled) view.skipBtn.visibility = GONE
|
||||||
if (!prefs.areQuestionMarksEnabled) view.unknownBtn.visibility = GONE
|
if (!prefs.areQuestionMarksEnabled) view.unknownBtn.visibility = GONE
|
||||||
view.booleanButtons.visibility = VISIBLE
|
}
|
||||||
val dialog = Dialog(requireContext())
|
|
||||||
|
private fun populate() {
|
||||||
|
val selectedBtn = when (value) {
|
||||||
|
YES_MANUAL -> view.yesBtn
|
||||||
|
YES_AUTO -> view.noBtn
|
||||||
|
NO -> view.noBtn
|
||||||
|
UNKNOWN -> if (prefs.areQuestionMarksEnabled) view.unknownBtn else view.noBtn
|
||||||
|
SKIP -> if (prefs.isSkipEnabled) view.skipBtn else view.noBtn
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
view.notes.setText(notes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun show() {
|
||||||
|
dialog = Dialog(context, android.R.style.Theme_NoTitleBar)
|
||||||
dialog.setContentView(view.root)
|
dialog.setContentView(view.root)
|
||||||
dialog.window?.apply {
|
dialog.window?.apply {
|
||||||
|
setLayout(
|
||||||
|
view.root.dp(POPUP_WIDTH).toInt(),
|
||||||
|
view.root.dp(POPUP_HEIGHT).toInt()
|
||||||
|
)
|
||||||
setBackgroundDrawableResource(android.R.color.transparent)
|
setBackgroundDrawableResource(android.R.color.transparent)
|
||||||
}
|
}
|
||||||
fun onClick(v: Int) {
|
fun onClick(v: Int) {
|
||||||
val notes = view.notes.text.toString().trim()
|
this.value = v
|
||||||
onToggle(v, notes)
|
save()
|
||||||
requireDialog().dismiss()
|
|
||||||
}
|
}
|
||||||
view.yesBtn.setOnClickListener { onClick(YES_MANUAL) }
|
view.yesBtn.setOnClickListener { onClick(YES_MANUAL) }
|
||||||
view.noBtn.setOnClickListener { onClick(NO) }
|
view.noBtn.setOnClickListener { onClick(NO) }
|
||||||
view.skipBtn.setOnClickListener { onClick(SKIP) }
|
view.skipBtn.setOnClickListener { onClick(SKIP) }
|
||||||
view.unknownBtn.setOnClickListener { onClick(UNKNOWN) }
|
view.unknownBtn.setOnClickListener { onClick(UNKNOWN) }
|
||||||
view.notes.setOnEditorActionListener { v, actionId, event ->
|
dialog.setCanceledOnTouchOutside(true)
|
||||||
onClick(requireArguments().getInt("value"))
|
dialog.dimBehind()
|
||||||
true
|
dialog.dismissCurrentAndShow()
|
||||||
}
|
}
|
||||||
|
|
||||||
return dialog
|
fun save() {
|
||||||
|
onToggle(value, view.notes.text.toString().trim())
|
||||||
|
dialog.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -28,7 +28,7 @@ import org.isoron.uhabits.utils.toPaletteColor
|
|||||||
class ColorPickerDialog : ColorPickerDialog() {
|
class ColorPickerDialog : ColorPickerDialog() {
|
||||||
fun setListener(callback: OnColorPickedCallback) {
|
fun setListener(callback: OnColorPickedCallback) {
|
||||||
super.setOnColorSelectedListener { c: Int ->
|
super.setOnColorSelectedListener { c: Int ->
|
||||||
val pc = c.toPaletteColor(context!!)
|
val pc = c.toPaletteColor(requireContext())
|
||||||
callback.onColorPicked(pc)
|
callback.onColorPicked(pc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,12 +51,12 @@ class HistoryEditorDialog : AppCompatDialogFragment(), CommandRunner.Listener {
|
|||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
clearCurrentDialog()
|
clearCurrentDialog()
|
||||||
val component = (activity!!.application as HabitsApplication).component
|
val component = (requireActivity().application as HabitsApplication).component
|
||||||
commandRunner = component.commandRunner
|
commandRunner = component.commandRunner
|
||||||
habit = component.habitList.getById(arguments!!.getLong("habit"))!!
|
habit = component.habitList.getById(requireArguments().getLong("habit"))!!
|
||||||
preferences = component.preferences
|
preferences = component.preferences
|
||||||
|
|
||||||
val themeSwitcher = AndroidThemeSwitcher(activity!!, preferences)
|
val themeSwitcher = AndroidThemeSwitcher(requireActivity(), preferences)
|
||||||
themeSwitcher.apply()
|
themeSwitcher.apply()
|
||||||
|
|
||||||
chart = HistoryChart(
|
chart = HistoryChart(
|
||||||
@@ -71,10 +71,10 @@ class HistoryEditorDialog : AppCompatDialogFragment(), CommandRunner.Listener {
|
|||||||
onDateClickedListener = onDateClickedListener ?: object : OnDateClickedListener {},
|
onDateClickedListener = onDateClickedListener ?: object : OnDateClickedListener {},
|
||||||
padding = 10.0,
|
padding = 10.0,
|
||||||
)
|
)
|
||||||
dataView = AndroidDataView(context!!, null)
|
dataView = AndroidDataView(requireContext(), null)
|
||||||
dataView.view = chart!!
|
dataView.view = chart!!
|
||||||
|
|
||||||
val dialog = Dialog(context!!).apply {
|
val dialog = Dialog(requireContext()).apply {
|
||||||
val metrics = resources.displayMetrics
|
val metrics = resources.displayMetrics
|
||||||
val maxHeight = resources.getDimensionPixelSize(R.dimen.history_editor_max_height)
|
val maxHeight = resources.getDimensionPixelSize(R.dimen.history_editor_max_height)
|
||||||
setContentView(dataView)
|
setContentView(dataView)
|
||||||
|
|||||||
@@ -1,115 +0,0 @@
|
|||||||
package org.isoron.uhabits.activities.common.dialogs
|
|
||||||
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.provider.Settings
|
|
||||||
import android.text.method.DigitsKeyListener
|
|
||||||
import android.view.KeyEvent
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.view.View
|
|
||||||
import android.view.inputmethod.EditorInfo
|
|
||||||
import androidx.appcompat.app.AppCompatDialogFragment
|
|
||||||
import org.isoron.uhabits.HabitsApplication
|
|
||||||
import org.isoron.uhabits.R
|
|
||||||
import org.isoron.uhabits.core.models.Entry
|
|
||||||
import org.isoron.uhabits.databinding.CheckmarkPopupBinding
|
|
||||||
import org.isoron.uhabits.utils.InterfaceUtils
|
|
||||||
import org.isoron.uhabits.utils.requestFocusWithKeyboard
|
|
||||||
import org.isoron.uhabits.utils.sres
|
|
||||||
import java.text.DecimalFormat
|
|
||||||
import java.text.DecimalFormatSymbols
|
|
||||||
import java.text.NumberFormat
|
|
||||||
import java.text.ParseException
|
|
||||||
|
|
||||||
class NumberDialog : AppCompatDialogFragment() {
|
|
||||||
|
|
||||||
var onToggle: (Double, String) -> Unit = { _, _ -> }
|
|
||||||
var onDismiss: () -> Unit = {}
|
|
||||||
|
|
||||||
private var originalNotes: String = ""
|
|
||||||
private var originalValue: Double = 0.0
|
|
||||||
private lateinit var view: CheckmarkPopupBinding
|
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
|
||||||
val appComponent = (requireActivity().application as HabitsApplication).component
|
|
||||||
val prefs = appComponent.preferences
|
|
||||||
view = CheckmarkPopupBinding.inflate(LayoutInflater.from(context))
|
|
||||||
arrayOf(view.yesBtn, view.skipBtn).forEach {
|
|
||||||
it.setTextColor(requireArguments().getInt("color"))
|
|
||||||
}
|
|
||||||
arrayOf(view.noBtn, view.unknownBtn).forEach {
|
|
||||||
it.setTextColor(view.root.sres.getColor(R.attr.contrast60))
|
|
||||||
}
|
|
||||||
arrayOf(view.yesBtn, view.noBtn, view.skipBtn, view.unknownBtn).forEach {
|
|
||||||
it.typeface = InterfaceUtils.getFontAwesome(requireContext())
|
|
||||||
}
|
|
||||||
if (!prefs.isSkipEnabled) view.skipBtnNumber.visibility = View.GONE
|
|
||||||
view.numberButtons.visibility = View.VISIBLE
|
|
||||||
fixDecimalSeparator(view)
|
|
||||||
originalNotes = requireArguments().getString("notes")!!
|
|
||||||
originalValue = requireArguments().getDouble("value")
|
|
||||||
view.notes.setText(originalNotes)
|
|
||||||
view.value.setText(
|
|
||||||
when {
|
|
||||||
originalValue < 0.01 -> "0"
|
|
||||||
else -> DecimalFormat("#.##").format(originalValue)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
view.value.setOnKeyListener { _, keyCode, event ->
|
|
||||||
if (event.action == MotionEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) {
|
|
||||||
save()
|
|
||||||
return@setOnKeyListener true
|
|
||||||
}
|
|
||||||
return@setOnKeyListener false
|
|
||||||
}
|
|
||||||
view.saveBtn.setOnClickListener {
|
|
||||||
save()
|
|
||||||
}
|
|
||||||
view.skipBtnNumber.setOnClickListener {
|
|
||||||
view.value.setText(DecimalFormat("#.###").format((Entry.SKIP.toDouble() / 1000)))
|
|
||||||
save()
|
|
||||||
}
|
|
||||||
view.notes.setOnEditorActionListener { v, actionId, event ->
|
|
||||||
save()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
view.value.requestFocusWithKeyboard()
|
|
||||||
val dialog = Dialog(requireContext())
|
|
||||||
dialog.setContentView(view.root)
|
|
||||||
dialog.window?.apply {
|
|
||||||
setBackgroundDrawableResource(android.R.color.transparent)
|
|
||||||
}
|
|
||||||
dialog.setOnDismissListener { onDismiss() }
|
|
||||||
return dialog
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun fixDecimalSeparator(view: CheckmarkPopupBinding) {
|
|
||||||
// https://stackoverflow.com/a/34256139
|
|
||||||
val separator = DecimalFormatSymbols.getInstance().decimalSeparator
|
|
||||||
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() {
|
|
||||||
var value = originalValue
|
|
||||||
try {
|
|
||||||
val numberFormat = NumberFormat.getInstance()
|
|
||||||
val valueStr = view.value.text.toString()
|
|
||||||
value = numberFormat.parse(valueStr)!!.toDouble()
|
|
||||||
} catch (e: ParseException) {
|
|
||||||
// NOP
|
|
||||||
}
|
|
||||||
val notes = view.notes.text.toString()
|
|
||||||
onToggle(value, notes)
|
|
||||||
requireDialog().dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
||||||
|
*
|
||||||
|
* This file is part of Loop Habit Tracker.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.isoron.uhabits.activities.common.dialogs
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.KeyEvent.KEYCODE_ENTER
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.MotionEvent.ACTION_DOWN
|
||||||
|
import android.view.View
|
||||||
|
import android.view.View.GONE
|
||||||
|
import android.view.View.VISIBLE
|
||||||
|
import org.isoron.uhabits.core.models.Entry
|
||||||
|
import org.isoron.uhabits.core.preferences.Preferences
|
||||||
|
import org.isoron.uhabits.databinding.CheckmarkPopupBinding
|
||||||
|
import org.isoron.uhabits.utils.dimBehind
|
||||||
|
import org.isoron.uhabits.utils.dismissCurrentAndShow
|
||||||
|
import org.isoron.uhabits.utils.dp
|
||||||
|
import org.isoron.uhabits.utils.requestFocusWithKeyboard
|
||||||
|
import java.text.DecimalFormat
|
||||||
|
|
||||||
|
class NumberPopup(
|
||||||
|
private val context: Context,
|
||||||
|
private var notes: String,
|
||||||
|
private var value: Double,
|
||||||
|
private val prefs: Preferences,
|
||||||
|
private val anchor: View,
|
||||||
|
) {
|
||||||
|
var onToggle: (Double, String) -> Unit = { _, _ -> }
|
||||||
|
var onDismiss: () -> Unit = {}
|
||||||
|
private val originalValue = value
|
||||||
|
private lateinit var dialog: Dialog
|
||||||
|
|
||||||
|
private val view = CheckmarkPopupBinding.inflate(LayoutInflater.from(context)).apply {
|
||||||
|
// Required for round corners
|
||||||
|
container.clipToOutline = true
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
view.numberButtons.visibility = VISIBLE
|
||||||
|
hideDisabledButtons()
|
||||||
|
populate()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hideDisabledButtons() {
|
||||||
|
if (!prefs.isSkipEnabled) view.skipBtnNumber.visibility = GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun populate() {
|
||||||
|
view.notes.setText(notes)
|
||||||
|
view.value.setText(
|
||||||
|
when {
|
||||||
|
value < 0.01 -> "0"
|
||||||
|
else -> DecimalFormat("#.##").format(value)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun show() {
|
||||||
|
dialog = Dialog(context, android.R.style.Theme_NoTitleBar)
|
||||||
|
dialog.setContentView(view.root)
|
||||||
|
dialog.window?.apply {
|
||||||
|
setLayout(
|
||||||
|
view.root.dp(POPUP_WIDTH).toInt(),
|
||||||
|
view.root.dp(POPUP_HEIGHT).toInt()
|
||||||
|
)
|
||||||
|
setBackgroundDrawableResource(android.R.color.transparent)
|
||||||
|
}
|
||||||
|
dialog.setOnDismissListener {
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
view.value.setOnKeyListener { _, keyCode, event ->
|
||||||
|
if (event.action == ACTION_DOWN && keyCode == KEYCODE_ENTER) {
|
||||||
|
save()
|
||||||
|
return@setOnKeyListener true
|
||||||
|
}
|
||||||
|
return@setOnKeyListener false
|
||||||
|
}
|
||||||
|
view.saveBtn.setOnClickListener {
|
||||||
|
save()
|
||||||
|
}
|
||||||
|
view.skipBtnNumber.setOnClickListener {
|
||||||
|
view.value.setText((Entry.SKIP.toDouble() / 1000).toString())
|
||||||
|
save()
|
||||||
|
}
|
||||||
|
view.value.requestFocusWithKeyboard()
|
||||||
|
dialog.setCanceledOnTouchOutside(true)
|
||||||
|
dialog.dimBehind()
|
||||||
|
dialog.dismissCurrentAndShow()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun save() {
|
||||||
|
val value = view.value.text.toString().toDoubleOrNull() ?: originalValue
|
||||||
|
val notes = view.notes.text.toString()
|
||||||
|
onToggle(value, notes)
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -60,7 +60,7 @@ class WeekdayPickerDialog :
|
|||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
val builder = AlertDialog.Builder(
|
val builder = AlertDialog.Builder(
|
||||||
activity!!
|
requireActivity()
|
||||||
)
|
)
|
||||||
builder
|
builder
|
||||||
.setTitle(R.string.select_weekdays)
|
.setTitle(R.string.select_weekdays)
|
||||||
|
|||||||
@@ -63,9 +63,7 @@ class FrequencyChart : ScrollableChart {
|
|||||||
private var primaryColor = 0
|
private var primaryColor = 0
|
||||||
private var isBackgroundTransparent = false
|
private var isBackgroundTransparent = false
|
||||||
private lateinit var frequency: HashMap<Timestamp, Array<Int>>
|
private lateinit var frequency: HashMap<Timestamp, Array<Int>>
|
||||||
private var maxFreq = 0
|
|
||||||
private var firstWeekday = Calendar.SUNDAY
|
private var firstWeekday = Calendar.SUNDAY
|
||||||
private var isNumerical: Boolean = false
|
|
||||||
|
|
||||||
constructor(context: Context?) : super(context) {
|
constructor(context: Context?) : super(context) {
|
||||||
init()
|
init()
|
||||||
@@ -82,14 +80,8 @@ class FrequencyChart : ScrollableChart {
|
|||||||
postInvalidate()
|
postInvalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setIsNumerical(type: Boolean) {
|
|
||||||
isNumerical = type
|
|
||||||
postInvalidate()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setFrequency(frequency: java.util.HashMap<Timestamp, Array<Int>>) {
|
fun setFrequency(frequency: java.util.HashMap<Timestamp, Array<Int>>) {
|
||||||
this.frequency = frequency
|
this.frequency = frequency
|
||||||
maxFreq = getMaxFreq(frequency)
|
|
||||||
postInvalidate()
|
postInvalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,15 +90,6 @@ class FrequencyChart : ScrollableChart {
|
|||||||
postInvalidate()
|
postInvalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getMaxFreq(frequency: HashMap<Timestamp, Array<Int>>): Int {
|
|
||||||
var maxValue = 1
|
|
||||||
for (values in frequency.values) for (value in values) maxValue = max(
|
|
||||||
value,
|
|
||||||
maxValue
|
|
||||||
)
|
|
||||||
return maxValue
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setIsBackgroundTransparent(isBackgroundTransparent: Boolean) {
|
fun setIsBackgroundTransparent(isBackgroundTransparent: Boolean) {
|
||||||
this.isBackgroundTransparent = isBackgroundTransparent
|
this.isBackgroundTransparent = isBackgroundTransparent
|
||||||
initColors()
|
initColors()
|
||||||
@@ -230,7 +213,7 @@ class FrequencyChart : ScrollableChart {
|
|||||||
canvas.drawLine(rGrid.left, rGrid.top, rGrid.right, rGrid.top, pGrid!!)
|
canvas.drawLine(rGrid.left, rGrid.top, rGrid.right, rGrid.top, pGrid!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun drawMarker(canvas: Canvas, rect: RectF?, value: Int?, weekdayFrequency: Int) {
|
private fun drawMarker(canvas: Canvas, rect: RectF?, value: Int?, frequency: Int) {
|
||||||
// value can be negative when the entry is skipped
|
// value can be negative when the entry is skipped
|
||||||
val valueCopy = value?.let { max(0, it) }
|
val valueCopy = value?.let { max(0, it) }
|
||||||
|
|
||||||
@@ -238,8 +221,8 @@ class FrequencyChart : ScrollableChart {
|
|||||||
// maximal allowed mark radius
|
// maximal allowed mark radius
|
||||||
val maxRadius = (rect.height() - 2 * padding) / 2.0f
|
val maxRadius = (rect.height() - 2 * padding) / 2.0f
|
||||||
// the real mark radius is scaled down by a factor depending on the maximal frequency
|
// the real mark radius is scaled down by a factor depending on the maximal frequency
|
||||||
val scalingFactor = if (isNumerical) maxFreq else weekdayFrequency
|
|
||||||
val scale = 1.0f / scalingFactor * valueCopy!!
|
val scale = 1.0f / frequency * valueCopy!!
|
||||||
val radius = maxRadius * scale
|
val radius = maxRadius * scale
|
||||||
val colorIndex = min((colors.size - 1), ((colors.size - 1) * scale).roundToInt())
|
val colorIndex = min((colors.size - 1), ((colors.size - 1) * scale).roundToInt())
|
||||||
pGraph!!.color = colors[colorIndex]
|
pGraph!!.color = colors[colorIndex]
|
||||||
@@ -302,6 +285,5 @@ class FrequencyChart : ScrollableChart {
|
|||||||
frequency[Timestamp(date)] = values
|
frequency[Timestamp(date)] = values
|
||||||
date.add(Calendar.MONTH, -1)
|
date.add(Calendar.MONTH, -1)
|
||||||
}
|
}
|
||||||
maxFreq = getMaxFreq(frequency)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,13 +40,13 @@ class HabitTypeDialog : AppCompatDialogFragment() {
|
|||||||
val binding = SelectHabitTypeBinding.inflate(inflater, container, false)
|
val binding = SelectHabitTypeBinding.inflate(inflater, container, false)
|
||||||
|
|
||||||
binding.buttonYesNo.setOnClickListener {
|
binding.buttonYesNo.setOnClickListener {
|
||||||
val intent = IntentFactory().startEditActivity(activity!!, HabitType.YES_NO.value)
|
val intent = IntentFactory().startEditActivity(requireActivity(), HabitType.YES_NO.value)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.buttonMeasurable.setOnClickListener {
|
binding.buttonMeasurable.setOnClickListener {
|
||||||
val intent = IntentFactory().startEditActivity(activity!!, HabitType.NUMERICAL.value)
|
val intent = IntentFactory().startEditActivity(requireActivity(), HabitType.NUMERICAL.value)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ import org.isoron.uhabits.inject.ActivityContextModule
|
|||||||
import org.isoron.uhabits.inject.DaggerHabitsActivityComponent
|
import org.isoron.uhabits.inject.DaggerHabitsActivityComponent
|
||||||
import org.isoron.uhabits.inject.HabitsActivityComponent
|
import org.isoron.uhabits.inject.HabitsActivityComponent
|
||||||
import org.isoron.uhabits.inject.HabitsApplicationComponent
|
import org.isoron.uhabits.inject.HabitsApplicationComponent
|
||||||
import org.isoron.uhabits.utils.dismissCurrentDialog
|
|
||||||
import org.isoron.uhabits.utils.restartWithFade
|
import org.isoron.uhabits.utils.restartWithFade
|
||||||
|
|
||||||
class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
|
class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
|
||||||
@@ -86,13 +85,13 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
|
|||||||
Thread.setDefaultUncaughtExceptionHandler(BaseExceptionHandler(this))
|
Thread.setDefaultUncaughtExceptionHandler(BaseExceptionHandler(this))
|
||||||
component.listHabitsBehavior.onStartup()
|
component.listHabitsBehavior.onStartup()
|
||||||
setContentView(rootView)
|
setContentView(rootView)
|
||||||
|
parseIntents()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
midnightTimer.onPause()
|
midnightTimer.onPause()
|
||||||
screen.onDetached()
|
screen.onDetached()
|
||||||
adapter.cancelRefresh()
|
adapter.cancelRefresh()
|
||||||
dismissCurrentDialog()
|
|
||||||
super.onPause()
|
super.onPause()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,19 +100,16 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
|
|||||||
screen.onAttached()
|
screen.onAttached()
|
||||||
rootView.postInvalidate()
|
rootView.postInvalidate()
|
||||||
midnightTimer.onResume()
|
midnightTimer.onResume()
|
||||||
appComponent.reminderScheduler.scheduleAll()
|
|
||||||
taskRunner.run {
|
taskRunner.run {
|
||||||
try {
|
try {
|
||||||
AutoBackup(this@ListHabitsActivity).run()
|
AutoBackup(this@ListHabitsActivity).run()
|
||||||
appComponent.widgetUpdater.updateWidgets()
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("ListHabitActivity", "TaskRunner failed", e)
|
Log.e("ListHabitActivity", "AutoBackup task failed", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (prefs.theme == THEME_DARK && prefs.isPureBlackEnabled != pureBlack) {
|
if (prefs.theme == THEME_DARK && prefs.isPureBlackEnabled != pureBlack) {
|
||||||
restartWithFade(ListHabitsActivity::class.java)
|
restartWithFade(ListHabitsActivity::class.java)
|
||||||
}
|
}
|
||||||
parseIntents()
|
|
||||||
super.onResume()
|
super.onResume()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +129,6 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun parseIntents() {
|
private fun parseIntents() {
|
||||||
if (intent == null) return
|
|
||||||
if (intent.action == ACTION_EDIT) {
|
if (intent.action == ACTION_EDIT) {
|
||||||
val habitId = intent.extras?.getLong("habit")
|
val habitId = intent.extras?.getLong("habit")
|
||||||
val timestamp = intent.extras?.getLong("timestamp")
|
val timestamp = intent.extras?.getLong("timestamp")
|
||||||
@@ -142,12 +137,6 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
|
|||||||
component.listHabitsBehavior.onEdit(habit, Timestamp(timestamp))
|
component.listHabitsBehavior.onEdit(habit, Timestamp(timestamp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
intent = null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onNewIntent(intent: Intent?) {
|
|
||||||
super.onNewIntent(intent)
|
|
||||||
setIntent(intent)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -22,15 +22,14 @@ package org.isoron.uhabits.activities.habits.list
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import org.isoron.platform.gui.toInt
|
import org.isoron.platform.gui.toInt
|
||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
import org.isoron.uhabits.activities.common.dialogs.CheckmarkDialog
|
import org.isoron.uhabits.activities.common.dialogs.CheckmarkPopup
|
||||||
import org.isoron.uhabits.activities.common.dialogs.ColorPickerDialogFactory
|
import org.isoron.uhabits.activities.common.dialogs.ColorPickerDialogFactory
|
||||||
import org.isoron.uhabits.activities.common.dialogs.ConfirmDeleteDialog
|
import org.isoron.uhabits.activities.common.dialogs.ConfirmDeleteDialog
|
||||||
import org.isoron.uhabits.activities.common.dialogs.NumberDialog
|
import org.isoron.uhabits.activities.common.dialogs.NumberPopup
|
||||||
import org.isoron.uhabits.activities.habits.edit.HabitTypeDialog
|
import org.isoron.uhabits.activities.habits.edit.HabitTypeDialog
|
||||||
import org.isoron.uhabits.activities.habits.list.views.HabitCardListAdapter
|
import org.isoron.uhabits.activities.habits.list.views.HabitCardListAdapter
|
||||||
import org.isoron.uhabits.core.commands.ArchiveHabitsCommand
|
import org.isoron.uhabits.core.commands.ArchiveHabitsCommand
|
||||||
@@ -226,7 +225,7 @@ class ListHabitsScreen
|
|||||||
override fun showColorPicker(defaultColor: PaletteColor, callback: OnColorPickedCallback) {
|
override fun showColorPicker(defaultColor: PaletteColor, callback: OnColorPickedCallback) {
|
||||||
val picker = colorPickerFactory.create(defaultColor, themeSwitcher.currentTheme!!)
|
val picker = colorPickerFactory.create(defaultColor, themeSwitcher.currentTheme!!)
|
||||||
picker.setListener(callback)
|
picker.setListener(callback)
|
||||||
picker.dismissCurrentAndShow(activity.supportFragmentManager, "picker")
|
picker.dialog?.dismissCurrentAndShow()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showNumberPopup(
|
override fun showNumberPopup(
|
||||||
@@ -234,14 +233,17 @@ class ListHabitsScreen
|
|||||||
notes: String,
|
notes: String,
|
||||||
callback: ListHabitsBehavior.NumberPickerCallback
|
callback: ListHabitsBehavior.NumberPickerCallback
|
||||||
) {
|
) {
|
||||||
val fm = (context as AppCompatActivity).supportFragmentManager
|
val view = rootView.get()
|
||||||
val dialog = NumberDialog()
|
NumberPopup(
|
||||||
dialog.arguments = Bundle().apply {
|
context = context,
|
||||||
putDouble("value", value)
|
prefs = preferences,
|
||||||
putString("notes", notes)
|
anchor = view,
|
||||||
|
notes = notes,
|
||||||
|
value = value,
|
||||||
|
).apply {
|
||||||
|
onToggle = { value, notes -> callback.onNumberPicked(value, notes) }
|
||||||
|
show()
|
||||||
}
|
}
|
||||||
dialog.onToggle = { v, n -> callback.onNumberPicked(v, n) }
|
|
||||||
dialog.dismissCurrentAndShow(fm, "numberDialog")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showCheckmarkPopup(
|
override fun showCheckmarkPopup(
|
||||||
@@ -250,16 +252,18 @@ class ListHabitsScreen
|
|||||||
color: PaletteColor,
|
color: PaletteColor,
|
||||||
callback: ListHabitsBehavior.CheckMarkDialogCallback
|
callback: ListHabitsBehavior.CheckMarkDialogCallback
|
||||||
) {
|
) {
|
||||||
val theme = rootView.get().currentTheme()
|
val view = rootView.get()
|
||||||
val fm = (context as AppCompatActivity).supportFragmentManager
|
CheckmarkPopup(
|
||||||
val dialog = CheckmarkDialog()
|
context = context,
|
||||||
dialog.arguments = Bundle().apply {
|
prefs = preferences,
|
||||||
putInt("color", theme.color(color).toInt())
|
anchor = view,
|
||||||
putInt("value", selectedValue)
|
color = view.currentTheme().color(color).toInt(),
|
||||||
putString("notes", notes)
|
notes = notes,
|
||||||
|
value = selectedValue,
|
||||||
|
).apply {
|
||||||
|
onToggle = { value, notes -> callback.onNotesSaved(value, notes) }
|
||||||
|
show()
|
||||||
}
|
}
|
||||||
dialog.onToggle = { v, n -> callback.onNotesSaved(v, n) }
|
|
||||||
dialog.dismissCurrentAndShow(fm, "checkmarkDialog")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getExecuteString(command: Command): String? {
|
private fun getExecuteString(command: Command): String? {
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ import org.isoron.uhabits.utils.sres
|
|||||||
import org.isoron.uhabits.utils.toMeasureSpec
|
import org.isoron.uhabits.utils.toMeasureSpec
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
const val TOGGLE_DELAY_MILLIS = 2000L
|
||||||
|
|
||||||
class CheckmarkButtonViewFactory
|
class CheckmarkButtonViewFactory
|
||||||
@Inject constructor(
|
@Inject constructor(
|
||||||
@ActivityContext val context: Context,
|
@ActivityContext val context: Context,
|
||||||
@@ -77,7 +79,7 @@ class CheckmarkButtonView(
|
|||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
var onToggle: (Int, String) -> Unit = { _, _ -> }
|
var onToggle: (Int, String, Long) -> Unit = { _, _, _ -> }
|
||||||
|
|
||||||
var onEdit: () -> Unit = { }
|
var onEdit: () -> Unit = { }
|
||||||
|
|
||||||
@@ -88,25 +90,25 @@ class CheckmarkButtonView(
|
|||||||
setOnLongClickListener(this)
|
setOnLongClickListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun performToggle() {
|
fun performToggle(delay: Long) {
|
||||||
value = Entry.nextToggleValue(
|
value = Entry.nextToggleValue(
|
||||||
value = value,
|
value = value,
|
||||||
isSkipEnabled = preferences.isSkipEnabled,
|
isSkipEnabled = preferences.isSkipEnabled,
|
||||||
areQuestionMarksEnabled = preferences.areQuestionMarksEnabled
|
areQuestionMarksEnabled = preferences.areQuestionMarksEnabled
|
||||||
)
|
)
|
||||||
onToggle(value, notes)
|
onToggle(value, notes, delay)
|
||||||
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(v: View) {
|
override fun onClick(v: View) {
|
||||||
if (preferences.isShortToggleEnabled) performToggle()
|
if (preferences.isShortToggleEnabled) performToggle(TOGGLE_DELAY_MILLIS)
|
||||||
else onEdit()
|
else onEdit()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLongClick(v: View): Boolean {
|
override fun onLongClick(v: View): Boolean {
|
||||||
if (preferences.isShortToggleEnabled) onEdit()
|
if (preferences.isShortToggleEnabled) onEdit()
|
||||||
else performToggle()
|
else performToggle(TOGGLE_DELAY_MILLIS)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class CheckmarkPanelView(
|
|||||||
setupButtons()
|
setupButtons()
|
||||||
}
|
}
|
||||||
|
|
||||||
var onToggle: (Timestamp, Int, String) -> Unit = { _, _, _ -> }
|
var onToggle: (Timestamp, Int, String, Long) -> Unit = { _, _, _, _ -> }
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
setupButtons()
|
setupButtons()
|
||||||
@@ -89,7 +89,7 @@ class CheckmarkPanelView(
|
|||||||
else -> ""
|
else -> ""
|
||||||
}
|
}
|
||||||
button.color = color
|
button.color = color
|
||||||
button.onToggle = { value, notes -> onToggle(timestamp, value, notes) }
|
button.onToggle = { value, notes, delay -> onToggle(timestamp, value, notes, delay) }
|
||||||
button.onEdit = { onEdit(timestamp) }
|
button.onEdit = { onEdit(timestamp) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,13 @@ class HabitCardViewFactory
|
|||||||
fun create() = HabitCardView(context, checkmarkPanelFactory, numberPanelFactory, behavior)
|
fun create() = HabitCardView(context, checkmarkPanelFactory, numberPanelFactory, behavior)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class DelayedToggle(
|
||||||
|
var habit: Habit,
|
||||||
|
var timestamp: Timestamp,
|
||||||
|
var value: Int,
|
||||||
|
var notes: String
|
||||||
|
)
|
||||||
|
|
||||||
class HabitCardView(
|
class HabitCardView(
|
||||||
@ActivityContext context: Context,
|
@ActivityContext context: Context,
|
||||||
checkmarkPanelFactory: CheckmarkPanelViewFactory,
|
checkmarkPanelFactory: CheckmarkPanelViewFactory,
|
||||||
@@ -129,6 +136,7 @@ class HabitCardView(
|
|||||||
private var scoreRing: RingView
|
private var scoreRing: RingView
|
||||||
|
|
||||||
private var currentToggleTaskId = 0
|
private var currentToggleTaskId = 0
|
||||||
|
private var queuedToggles = mutableListOf<DelayedToggle>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
scoreRing = RingView(context).apply {
|
scoreRing = RingView(context).apply {
|
||||||
@@ -152,9 +160,12 @@ class HabitCardView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
checkmarkPanel = checkmarkPanelFactory.create().apply {
|
checkmarkPanel = checkmarkPanelFactory.create().apply {
|
||||||
onToggle = { timestamp, value, notes ->
|
onToggle = { timestamp, value, notes, delay ->
|
||||||
triggerRipple(timestamp)
|
if (delay > 0) triggerRipple(timestamp)
|
||||||
habit?.let { behavior.onToggle(it, timestamp, value, notes) }
|
habit?.let {
|
||||||
|
val taskId = queueToggle(it, timestamp, value, notes);
|
||||||
|
{ runPendingToggles(taskId) }.delay(delay)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
onEdit = { timestamp ->
|
onEdit = { timestamp ->
|
||||||
triggerRipple(timestamp)
|
triggerRipple(timestamp)
|
||||||
@@ -194,6 +205,25 @@ class HabitCardView(
|
|||||||
addView(innerFrame)
|
addView(innerFrame)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun runPendingToggles(id: Int) {
|
||||||
|
if (currentToggleTaskId != id) return
|
||||||
|
for ((h, t, v, n) in queuedToggles) behavior.onToggle(h, t, v, n)
|
||||||
|
queuedToggles.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun queueToggle(
|
||||||
|
it: Habit,
|
||||||
|
timestamp: Timestamp,
|
||||||
|
value: Int,
|
||||||
|
notes: String,
|
||||||
|
): Int {
|
||||||
|
currentToggleTaskId += 1
|
||||||
|
queuedToggles.add(DelayedToggle(it, timestamp, value, notes))
|
||||||
|
return currentToggleTaskId
|
||||||
|
}
|
||||||
|
|
||||||
override fun onModelChange() {
|
override fun onModelChange() {
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
habit?.let { copyAttributesFrom(it) }
|
habit?.let { copyAttributesFrom(it) }
|
||||||
|
|||||||
@@ -34,10 +34,10 @@ import org.isoron.uhabits.HabitsApplication
|
|||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
import org.isoron.uhabits.activities.AndroidThemeSwitcher
|
import org.isoron.uhabits.activities.AndroidThemeSwitcher
|
||||||
import org.isoron.uhabits.activities.HabitsDirFinder
|
import org.isoron.uhabits.activities.HabitsDirFinder
|
||||||
import org.isoron.uhabits.activities.common.dialogs.CheckmarkDialog
|
import org.isoron.uhabits.activities.common.dialogs.CheckmarkPopup
|
||||||
import org.isoron.uhabits.activities.common.dialogs.ConfirmDeleteDialog
|
import org.isoron.uhabits.activities.common.dialogs.ConfirmDeleteDialog
|
||||||
import org.isoron.uhabits.activities.common.dialogs.HistoryEditorDialog
|
import org.isoron.uhabits.activities.common.dialogs.HistoryEditorDialog
|
||||||
import org.isoron.uhabits.activities.common.dialogs.NumberDialog
|
import org.isoron.uhabits.activities.common.dialogs.NumberPopup
|
||||||
import org.isoron.uhabits.core.commands.Command
|
import org.isoron.uhabits.core.commands.Command
|
||||||
import org.isoron.uhabits.core.commands.CommandRunner
|
import org.isoron.uhabits.core.commands.CommandRunner
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
@@ -51,7 +51,6 @@ import org.isoron.uhabits.core.ui.views.OnDateClickedListener
|
|||||||
import org.isoron.uhabits.intents.IntentFactory
|
import org.isoron.uhabits.intents.IntentFactory
|
||||||
import org.isoron.uhabits.utils.currentTheme
|
import org.isoron.uhabits.utils.currentTheme
|
||||||
import org.isoron.uhabits.utils.dismissCurrentAndShow
|
import org.isoron.uhabits.utils.dismissCurrentAndShow
|
||||||
import org.isoron.uhabits.utils.dismissCurrentDialog
|
|
||||||
import org.isoron.uhabits.utils.showMessage
|
import org.isoron.uhabits.utils.showMessage
|
||||||
import org.isoron.uhabits.utils.showSendFileScreen
|
import org.isoron.uhabits.utils.showSendFileScreen
|
||||||
import org.isoron.uhabits.widgets.WidgetUpdater
|
import org.isoron.uhabits.widgets.WidgetUpdater
|
||||||
@@ -130,7 +129,6 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
dismissCurrentDialog()
|
|
||||||
commandRunner.removeListener(this)
|
commandRunner.removeListener(this)
|
||||||
super.onPause()
|
super.onPause()
|
||||||
}
|
}
|
||||||
@@ -172,32 +170,41 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
|
|||||||
override fun showNumberPopup(
|
override fun showNumberPopup(
|
||||||
value: Double,
|
value: Double,
|
||||||
notes: String,
|
notes: String,
|
||||||
|
preferences: Preferences,
|
||||||
callback: ListHabitsBehavior.NumberPickerCallback
|
callback: ListHabitsBehavior.NumberPickerCallback
|
||||||
) {
|
) {
|
||||||
val dialog = NumberDialog()
|
val anchor = getPopupAnchor() ?: return
|
||||||
dialog.arguments = Bundle().apply {
|
NumberPopup(
|
||||||
putDouble("value", value)
|
context = this@ShowHabitActivity,
|
||||||
putString("notes", notes)
|
prefs = preferences,
|
||||||
|
notes = notes,
|
||||||
|
anchor = anchor,
|
||||||
|
value = value,
|
||||||
|
).apply {
|
||||||
|
onToggle = { v, n -> callback.onNumberPicked(v, n) }
|
||||||
|
show()
|
||||||
}
|
}
|
||||||
dialog.onToggle = { v, n -> callback.onNumberPicked(v, n) }
|
|
||||||
dialog.dismissCurrentAndShow(supportFragmentManager, "numberDialog")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showCheckmarkPopup(
|
override fun showCheckmarkPopup(
|
||||||
selectedValue: Int,
|
selectedValue: Int,
|
||||||
notes: String,
|
notes: String,
|
||||||
|
preferences: Preferences,
|
||||||
color: PaletteColor,
|
color: PaletteColor,
|
||||||
callback: ListHabitsBehavior.CheckMarkDialogCallback
|
callback: ListHabitsBehavior.CheckMarkDialogCallback
|
||||||
) {
|
) {
|
||||||
val theme = view.currentTheme()
|
val anchor = getPopupAnchor() ?: return
|
||||||
val dialog = CheckmarkDialog()
|
CheckmarkPopup(
|
||||||
dialog.arguments = Bundle().apply {
|
context = this@ShowHabitActivity,
|
||||||
putInt("color", theme.color(color).toInt())
|
prefs = preferences,
|
||||||
putInt("value", selectedValue)
|
notes = notes,
|
||||||
putString("notes", notes)
|
color = view.currentTheme().color(color).toInt(),
|
||||||
|
anchor = anchor,
|
||||||
|
value = selectedValue,
|
||||||
|
).apply {
|
||||||
|
onToggle = { v, n -> callback.onNotesSaved(v, n) }
|
||||||
|
show()
|
||||||
}
|
}
|
||||||
dialog.onToggle = { v, n -> callback.onNotesSaved(v, n) }
|
|
||||||
dialog.dismissCurrentAndShow(supportFragmentManager, "checkmarkDialog")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getPopupAnchor(): View? {
|
private fun getPopupAnchor(): View? {
|
||||||
@@ -214,6 +221,7 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
|
|||||||
ShowHabitMenuPresenter.Message.COULD_NOT_EXPORT -> {
|
ShowHabitMenuPresenter.Message.COULD_NOT_EXPORT -> {
|
||||||
showMessage(resources.getString(R.string.could_not_export))
|
showMessage(resources.getString(R.string.could_not_export))
|
||||||
}
|
}
|
||||||
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ class FrequencyCardView(context: Context, attrs: AttributeSet) : LinearLayout(co
|
|||||||
fun setState(state: FrequencyCardState) {
|
fun setState(state: FrequencyCardState) {
|
||||||
val androidColor = state.theme.color(state.color).toInt()
|
val androidColor = state.theme.color(state.color).toInt()
|
||||||
binding.frequencyChart.setFrequency(state.frequency)
|
binding.frequencyChart.setFrequency(state.frequency)
|
||||||
binding.frequencyChart.setIsNumerical(state.isNumerical)
|
|
||||||
binding.frequencyChart.setFirstWeekday(state.firstWeekday)
|
binding.frequencyChart.setFirstWeekday(state.firstWeekday)
|
||||||
binding.title.setTextColor(androidColor)
|
binding.title.setTextColor(androidColor)
|
||||||
binding.frequencyChart.setColor(androidColor)
|
binding.frequencyChart.setColor(androidColor)
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
addPreferencesFromResource(R.xml.preferences)
|
addPreferencesFromResource(R.xml.preferences)
|
||||||
val appContext = context!!.applicationContext
|
val appContext = requireContext().applicationContext
|
||||||
if (appContext is HabitsApplication) {
|
if (appContext is HabitsApplication) {
|
||||||
prefs = appContext.component.preferences
|
prefs = appContext.component.preferences
|
||||||
widgetUpdater = appContext.component.widgetUpdater
|
widgetUpdater = appContext.component.widgetUpdater
|
||||||
@@ -99,9 +99,9 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis
|
|||||||
return true
|
return true
|
||||||
} else if (key == "reminderCustomize") {
|
} else if (key == "reminderCustomize") {
|
||||||
if (SDK_INT < Build.VERSION_CODES.O) return true
|
if (SDK_INT < Build.VERSION_CODES.O) return true
|
||||||
createAndroidNotificationChannel(context!!)
|
createAndroidNotificationChannel(requireContext())
|
||||||
val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
|
val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
|
||||||
intent.putExtra(Settings.EXTRA_APP_PACKAGE, context!!.packageName)
|
intent.putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName)
|
||||||
intent.putExtra(Settings.EXTRA_CHANNEL_ID, NotificationTray.REMINDERS_CHANNEL_ID)
|
intent.putExtra(Settings.EXTRA_CHANNEL_ID, NotificationTray.REMINDERS_CHANNEL_ID)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
return true
|
return true
|
||||||
@@ -111,7 +111,7 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis
|
|||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
ringtoneManager = RingtoneManager(activity!!)
|
ringtoneManager = RingtoneManager(requireActivity())
|
||||||
sharedPrefs = preferenceManager.sharedPreferences
|
sharedPrefs = preferenceManager.sharedPreferences
|
||||||
sharedPrefs!!.registerOnSharedPreferenceChangeListener(this)
|
sharedPrefs!!.registerOnSharedPreferenceChangeListener(this)
|
||||||
if (!prefs.isDeveloper) {
|
if (!prefs.isDeveloper) {
|
||||||
@@ -154,8 +154,8 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis
|
|||||||
val pref = findPreference(key)
|
val pref = findPreference(key)
|
||||||
pref.onPreferenceClickListener =
|
pref.onPreferenceClickListener =
|
||||||
Preference.OnPreferenceClickListener {
|
Preference.OnPreferenceClickListener {
|
||||||
activity!!.setResult(result)
|
requireActivity().setResult(result)
|
||||||
activity!!.finish()
|
requireActivity().finish()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ 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
|
||||||
@@ -57,10 +56,6 @@ 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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,16 +21,13 @@ package org.isoron.uhabits.intents
|
|||||||
|
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.app.PendingIntent.FLAG_IMMUTABLE
|
import android.app.PendingIntent.FLAG_IMMUTABLE
|
||||||
import android.app.PendingIntent.FLAG_MUTABLE
|
|
||||||
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
import android.app.PendingIntent.getActivity
|
import android.app.PendingIntent.getActivity
|
||||||
import android.app.PendingIntent.getBroadcast
|
import android.app.PendingIntent.getBroadcast
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
|
||||||
import org.isoron.uhabits.activities.habits.list.ListHabitsActivity
|
import org.isoron.uhabits.activities.habits.list.ListHabitsActivity
|
||||||
import org.isoron.uhabits.activities.habits.show.ShowHabitActivity
|
|
||||||
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
|
||||||
import org.isoron.uhabits.core.models.Timestamp
|
import org.isoron.uhabits.core.models.Timestamp
|
||||||
@@ -92,20 +89,6 @@ class PendingIntentFactory
|
|||||||
)
|
)
|
||||||
.getPendingIntent(0, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT)!!
|
.getPendingIntent(0, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT)!!
|
||||||
|
|
||||||
fun showHabitTemplate(): PendingIntent {
|
|
||||||
return getActivity(
|
|
||||||
context,
|
|
||||||
0,
|
|
||||||
Intent(context, ShowHabitActivity::class.java),
|
|
||||||
getIntentTemplateFlags()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun showHabitFillIn(habit: Habit) =
|
|
||||||
Intent().apply {
|
|
||||||
data = Uri.parse(habit.uriString)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun showReminder(
|
fun showReminder(
|
||||||
habit: Habit,
|
habit: Habit,
|
||||||
reminderTime: Long?,
|
reminderTime: Long?,
|
||||||
@@ -159,7 +142,7 @@ class PendingIntentFactory
|
|||||||
fun showNumberPicker(habit: Habit, timestamp: Timestamp): PendingIntent? {
|
fun showNumberPicker(habit: Habit, timestamp: Timestamp): PendingIntent? {
|
||||||
return getActivity(
|
return getActivity(
|
||||||
context,
|
context,
|
||||||
(habit.id!! % Integer.MAX_VALUE).toInt() + 1,
|
0,
|
||||||
Intent(context, ListHabitsActivity::class.java).apply {
|
Intent(context, ListHabitsActivity::class.java).apply {
|
||||||
action = ListHabitsActivity.ACTION_EDIT
|
action = ListHabitsActivity.ACTION_EDIT
|
||||||
putExtra("habit", habit.id)
|
putExtra("habit", habit.id)
|
||||||
@@ -168,43 +151,4 @@ class PendingIntentFactory
|
|||||||
FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
|
FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showNumberPickerTemplate(): PendingIntent {
|
|
||||||
return getActivity(
|
|
||||||
context,
|
|
||||||
1,
|
|
||||||
Intent(context, ListHabitsActivity::class.java).apply {
|
|
||||||
action = ListHabitsActivity.ACTION_EDIT
|
|
||||||
},
|
|
||||||
getIntentTemplateFlags()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun showNumberPickerFillIn(habit: Habit, timestamp: Timestamp) = Intent().apply {
|
|
||||||
putExtra("habit", habit.id)
|
|
||||||
putExtra("timestamp", timestamp.unixTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getIntentTemplateFlags(): Int {
|
|
||||||
var flags = 0
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
||||||
flags = flags or FLAG_MUTABLE
|
|
||||||
}
|
|
||||||
return flags
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toggleCheckmarkTemplate(): PendingIntent =
|
|
||||||
getBroadcast(
|
|
||||||
context,
|
|
||||||
2,
|
|
||||||
Intent(context, WidgetReceiver::class.java).apply {
|
|
||||||
action = WidgetReceiver.ACTION_TOGGLE_REPETITION
|
|
||||||
},
|
|
||||||
getIntentTemplateFlags()
|
|
||||||
)
|
|
||||||
|
|
||||||
fun toggleCheckmarkFillIn(habit: Habit, timestamp: Timestamp) = Intent().apply {
|
|
||||||
data = Uri.parse(habit.uriString)
|
|
||||||
putExtra("timestamp", timestamp.unixTime)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,24 +6,16 @@ import androidx.fragment.app.FragmentManager
|
|||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
|
|
||||||
var currentDialog: WeakReference<Dialog> = WeakReference(null)
|
var currentDialog: WeakReference<Dialog> = WeakReference(null)
|
||||||
var currentDialogFragment: WeakReference<DialogFragment> = WeakReference(null)
|
|
||||||
|
|
||||||
fun dismissCurrentDialog() {
|
|
||||||
currentDialog.get()?.dismiss()
|
|
||||||
currentDialog = WeakReference(null)
|
|
||||||
currentDialogFragment.get()?.dismiss()
|
|
||||||
currentDialogFragment = WeakReference(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Dialog.dismissCurrentAndShow() {
|
fun Dialog.dismissCurrentAndShow() {
|
||||||
dismissCurrentDialog()
|
currentDialog.get()?.dismiss()
|
||||||
currentDialog = WeakReference(this)
|
currentDialog = WeakReference(this)
|
||||||
show()
|
show()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun DialogFragment.dismissCurrentAndShow(fragmentManager: FragmentManager, tag: String) {
|
fun DialogFragment.dismissCurrentAndShow(fragmentManager: FragmentManager, tag: String) {
|
||||||
dismissCurrentDialog()
|
currentDialog.get()?.dismiss()
|
||||||
currentDialogFragment = WeakReference(this)
|
|
||||||
show(fragmentManager, tag)
|
show(fragmentManager, tag)
|
||||||
fragmentManager.executePendingTransactions()
|
fragmentManager.executePendingTransactions()
|
||||||
|
currentDialog = WeakReference(this.dialog)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ open class CheckmarkWidget(
|
|||||||
|
|
||||||
override fun getOnClickPendingIntent(context: Context): PendingIntent? {
|
override fun getOnClickPendingIntent(context: Context): PendingIntent? {
|
||||||
return if (habit.isNumerical) {
|
return if (habit.isNumerical) {
|
||||||
pendingIntentFactory.showNumberPicker(habit, DateUtils.getTodayWithOffset())
|
pendingIntentFactory.showNumberPicker(habit, DateUtils.getToday())
|
||||||
} else {
|
} else {
|
||||||
pendingIntentFactory.toggleCheckmark(habit, null)
|
pendingIntentFactory.toggleCheckmark(habit, null)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ class FrequencyWidget(
|
|||||||
(widgetView.dataView as FrequencyChart).apply {
|
(widgetView.dataView as FrequencyChart).apply {
|
||||||
setFirstWeekday(firstWeekday)
|
setFirstWeekday(firstWeekday)
|
||||||
setColor(WidgetTheme().color(habit.color).toInt())
|
setColor(WidgetTheme().color(habit.color).toInt())
|
||||||
setIsNumerical(habit.isNumerical)
|
|
||||||
setFrequency(habit.originalEntries.computeWeekdayFrequency(habit.isNumerical))
|
setFrequency(habit.originalEntries.computeWeekdayFrequency(habit.isNumerical))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,10 +73,6 @@ class StackWidget(
|
|||||||
StackWidgetType.getStackWidgetAdapterViewId(widgetType),
|
StackWidgetType.getStackWidgetAdapterViewId(widgetType),
|
||||||
StackWidgetType.getStackWidgetEmptyViewId(widgetType)
|
StackWidgetType.getStackWidgetEmptyViewId(widgetType)
|
||||||
)
|
)
|
||||||
remoteViews.setPendingIntentTemplate(
|
|
||||||
StackWidgetType.getStackWidgetAdapterViewId(widgetType),
|
|
||||||
StackWidgetType.getPendingIntentTemplate(pendingIntentFactory, widgetType, habits)
|
|
||||||
)
|
|
||||||
return remoteViews
|
return remoteViews
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,14 +29,11 @@ import android.widget.RemoteViewsService
|
|||||||
import android.widget.RemoteViewsService.RemoteViewsFactory
|
import android.widget.RemoteViewsService.RemoteViewsFactory
|
||||||
import org.isoron.platform.utils.StringUtils.Companion.splitLongs
|
import org.isoron.platform.utils.StringUtils.Companion.splitLongs
|
||||||
import org.isoron.uhabits.HabitsApplication
|
import org.isoron.uhabits.HabitsApplication
|
||||||
import org.isoron.uhabits.R
|
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
import org.isoron.uhabits.core.models.HabitNotFoundException
|
import org.isoron.uhabits.core.models.HabitNotFoundException
|
||||||
import org.isoron.uhabits.core.preferences.Preferences
|
import org.isoron.uhabits.core.preferences.Preferences
|
||||||
import org.isoron.uhabits.core.utils.DateUtils.Companion.getTodayWithOffset
|
|
||||||
import org.isoron.uhabits.intents.IntentFactory
|
|
||||||
import org.isoron.uhabits.intents.PendingIntentFactory
|
|
||||||
import org.isoron.uhabits.utils.InterfaceUtils.dpToPixels
|
import org.isoron.uhabits.utils.InterfaceUtils.dpToPixels
|
||||||
|
import java.util.ArrayList
|
||||||
|
|
||||||
class StackWidgetService : RemoteViewsService() {
|
class StackWidgetService : RemoteViewsService() {
|
||||||
override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
|
override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
|
||||||
@@ -57,6 +54,7 @@ internal class StackRemoteViewsFactory(private val context: Context, intent: Int
|
|||||||
)
|
)
|
||||||
private val habitIds: LongArray
|
private val habitIds: LongArray
|
||||||
private val widgetType: StackWidgetType
|
private val widgetType: StackWidgetType
|
||||||
|
private var remoteViews = ArrayList<RemoteViews>()
|
||||||
override fun onCreate() {}
|
override fun onCreate() {}
|
||||||
override fun onDestroy() {}
|
override fun onDestroy() {}
|
||||||
override fun getCount(): Int {
|
override fun getCount(): Int {
|
||||||
@@ -87,26 +85,8 @@ internal class StackRemoteViewsFactory(private val context: Context, intent: Int
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getViewAt(position: Int): RemoteViews? {
|
override fun getViewAt(position: Int): RemoteViews? {
|
||||||
Log.i("StackRemoteViewsFactory", "getViewAt $position started")
|
Log.i("StackRemoteViewsFactory", "getViewAt $position")
|
||||||
if (position < 0 || position >= habitIds.size) return null
|
return if (0 <= position && position < remoteViews.size) remoteViews[position] else null
|
||||||
val app = context.applicationContext as HabitsApplication
|
|
||||||
val prefs = app.component.preferences
|
|
||||||
val habitList = app.component.habitList
|
|
||||||
val options = AppWidgetManager.getInstance(context).getAppWidgetOptions(widgetId)
|
|
||||||
if (Looper.myLooper() == null) Looper.prepare()
|
|
||||||
val habits = habitIds.map { habitList.getById(it) ?: throw HabitNotFoundException() }
|
|
||||||
val h = habits[position]
|
|
||||||
val widget = constructWidget(h, prefs)
|
|
||||||
widget.setDimensions(getDimensionsFromOptions(context, options))
|
|
||||||
val landscapeViews = widget.landscapeRemoteViews
|
|
||||||
val portraitViews = widget.portraitRemoteViews
|
|
||||||
val factory = PendingIntentFactory(context, IntentFactory())
|
|
||||||
val intent = StackWidgetType.getIntentFillIn(factory, widgetType, h, habits, getTodayWithOffset())
|
|
||||||
landscapeViews.setOnClickFillInIntent(R.id.button, intent)
|
|
||||||
portraitViews.setOnClickFillInIntent(R.id.button, intent)
|
|
||||||
val remoteViews = RemoteViews(landscapeViews, portraitViews)
|
|
||||||
Log.i("StackRemoteViewsFactory", "getViewAt $position ended")
|
|
||||||
return remoteViews
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun constructWidget(
|
private fun constructWidget(
|
||||||
@@ -151,6 +131,24 @@ internal class StackRemoteViewsFactory(private val context: Context, intent: Int
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDataSetChanged() {
|
override fun onDataSetChanged() {
|
||||||
|
Log.i("StackRemoteViewsFactory", "onDataSetChanged started")
|
||||||
|
val app = context.applicationContext as HabitsApplication
|
||||||
|
val prefs = app.component.preferences
|
||||||
|
val habitList = app.component.habitList
|
||||||
|
val options = AppWidgetManager.getInstance(context).getAppWidgetOptions(widgetId)
|
||||||
|
val newRemoteViews = ArrayList<RemoteViews>()
|
||||||
|
if (Looper.myLooper() == null) Looper.prepare()
|
||||||
|
for (id in habitIds) {
|
||||||
|
val h = habitList.getById(id) ?: throw HabitNotFoundException()
|
||||||
|
val widget = constructWidget(h, prefs)
|
||||||
|
widget.setDimensions(getDimensionsFromOptions(context, options))
|
||||||
|
val landscapeViews = widget.landscapeRemoteViews
|
||||||
|
val portraitViews = widget.portraitRemoteViews
|
||||||
|
newRemoteViews.add(RemoteViews(landscapeViews, portraitViews))
|
||||||
|
Log.i("StackRemoteViewsFactory", "onDataSetChanged constructed widget $id")
|
||||||
|
}
|
||||||
|
remoteViews = newRemoteViews
|
||||||
|
Log.i("StackRemoteViewsFactory", "onDataSetChanged ended")
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|||||||
@@ -18,12 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.isoron.uhabits.widgets
|
package org.isoron.uhabits.widgets
|
||||||
|
|
||||||
import android.app.PendingIntent
|
|
||||||
import android.content.Intent
|
|
||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
import org.isoron.uhabits.core.models.Habit
|
|
||||||
import org.isoron.uhabits.core.models.Timestamp
|
|
||||||
import org.isoron.uhabits.intents.PendingIntentFactory
|
|
||||||
import java.lang.IllegalStateException
|
import java.lang.IllegalStateException
|
||||||
|
|
||||||
enum class StackWidgetType(val value: Int) {
|
enum class StackWidgetType(val value: Int) {
|
||||||
@@ -78,39 +73,5 @@ enum class StackWidgetType(val value: Int) {
|
|||||||
else -> throw IllegalStateException()
|
else -> throw IllegalStateException()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPendingIntentTemplate(
|
|
||||||
factory: PendingIntentFactory,
|
|
||||||
widgetType: StackWidgetType,
|
|
||||||
habits: List<Habit>
|
|
||||||
): PendingIntent {
|
|
||||||
val containsNumerical = habits.any { it.isNumerical }
|
|
||||||
return when (widgetType) {
|
|
||||||
CHECKMARK -> if (containsNumerical) {
|
|
||||||
factory.showNumberPickerTemplate()
|
|
||||||
} else {
|
|
||||||
factory.toggleCheckmarkTemplate()
|
|
||||||
}
|
|
||||||
FREQUENCY, SCORE, HISTORY, STREAKS, TARGET -> factory.showHabitTemplate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getIntentFillIn(
|
|
||||||
factory: PendingIntentFactory,
|
|
||||||
widgetType: StackWidgetType,
|
|
||||||
habit: Habit,
|
|
||||||
allHabitsInStackWidget: List<Habit>,
|
|
||||||
timestamp: Timestamp
|
|
||||||
): Intent {
|
|
||||||
val containsNumerical = allHabitsInStackWidget.any { it.isNumerical }
|
|
||||||
return when (widgetType) {
|
|
||||||
CHECKMARK -> if (containsNumerical) {
|
|
||||||
factory.showNumberPickerFillIn(habit, timestamp)
|
|
||||||
} else {
|
|
||||||
factory.toggleCheckmarkFillIn(habit, timestamp)
|
|
||||||
}
|
|
||||||
FREQUENCY, SCORE, HISTORY, STREAKS, TARGET -> factory.showHabitFillIn(habit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID
|
|||||||
import android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID
|
import android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.widget.AbsListView.CHOICE_MODE_MULTIPLE
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.ListView
|
import android.widget.ListView
|
||||||
@@ -33,6 +34,7 @@ import org.isoron.uhabits.R
|
|||||||
import org.isoron.uhabits.activities.AndroidThemeSwitcher
|
import org.isoron.uhabits.activities.AndroidThemeSwitcher
|
||||||
import org.isoron.uhabits.core.preferences.WidgetPreferences
|
import org.isoron.uhabits.core.preferences.WidgetPreferences
|
||||||
import org.isoron.uhabits.widgets.WidgetUpdater
|
import org.isoron.uhabits.widgets.WidgetUpdater
|
||||||
|
import java.util.ArrayList
|
||||||
|
|
||||||
class BooleanHabitPickerDialog : HabitPickerDialog() {
|
class BooleanHabitPickerDialog : HabitPickerDialog() {
|
||||||
override fun shouldHideNumerical() = true
|
override fun shouldHideNumerical() = true
|
||||||
@@ -86,12 +88,20 @@ open class HabitPickerDialog : Activity() {
|
|||||||
with(listView) {
|
with(listView) {
|
||||||
adapter = ArrayAdapter(
|
adapter = ArrayAdapter(
|
||||||
context,
|
context,
|
||||||
android.R.layout.simple_list_item_1,
|
android.R.layout.simple_list_item_multiple_choice,
|
||||||
habitNames
|
habitNames
|
||||||
)
|
)
|
||||||
setOnItemClickListener { parent, view, position, id ->
|
choiceMode = CHOICE_MODE_MULTIPLE
|
||||||
confirm(mutableListOf(habitIds[position]))
|
itemsCanFocus = false
|
||||||
|
}
|
||||||
|
saveButton.setOnClickListener {
|
||||||
|
val selectedIds = mutableListOf<Long>()
|
||||||
|
for (i in 0..listView.count) {
|
||||||
|
if (listView.isItemChecked(i)) {
|
||||||
|
selectedIds.add(habitIds[i])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
confirm(selectedIds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,21 +66,23 @@ class CheckmarkWidgetView : HabitWidgetView {
|
|||||||
val res = StyledResources(context)
|
val res = StyledResources(context)
|
||||||
val bgColor: Int
|
val bgColor: Int
|
||||||
val fgColor: Int
|
val fgColor: Int
|
||||||
setShadowAlpha(0x4f)
|
|
||||||
when (entryState) {
|
when (entryState) {
|
||||||
YES_MANUAL, SKIP -> {
|
YES_MANUAL, SKIP -> {
|
||||||
bgColor = activeColor
|
bgColor = activeColor
|
||||||
fgColor = res.getColor(R.attr.contrast0)
|
fgColor = res.getColor(R.attr.contrast0)
|
||||||
|
setShadowAlpha(0x4f)
|
||||||
backgroundPaint!!.color = bgColor
|
backgroundPaint!!.color = bgColor
|
||||||
frame!!.setBackgroundDrawable(background)
|
frame!!.setBackgroundDrawable(background)
|
||||||
}
|
}
|
||||||
YES_AUTO, NO, UNKNOWN -> {
|
YES_AUTO, NO, UNKNOWN -> {
|
||||||
bgColor = res.getColor(R.attr.cardBgColor)
|
bgColor = res.getColor(R.attr.cardBgColor)
|
||||||
fgColor = res.getColor(R.attr.contrast60)
|
fgColor = res.getColor(R.attr.contrast60)
|
||||||
|
setShadowAlpha(0x00)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
bgColor = res.getColor(R.attr.cardBgColor)
|
bgColor = res.getColor(R.attr.cardBgColor)
|
||||||
fgColor = res.getColor(R.attr.contrast60)
|
fgColor = res.getColor(R.attr.contrast60)
|
||||||
|
setShadowAlpha(0x00)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ring.setPercentage(percentage)
|
ring.setPercentage(percentage)
|
||||||
@@ -124,7 +126,7 @@ class CheckmarkWidgetView : HabitWidgetView {
|
|||||||
} else {
|
} else {
|
||||||
width = min(width, height)
|
width = min(width, height)
|
||||||
}
|
}
|
||||||
val textSize = min(0.175f * width, getDimension(context, R.dimen.smallTextSize))
|
val textSize = min(0.2f * width, getDimension(context, R.dimen.smallerTextSize))
|
||||||
label.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
label.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
||||||
if (isNumerical) {
|
if (isNumerical) {
|
||||||
ring.setTextSize(textSize * 0.9f)
|
ring.setTextSize(textSize * 0.9f)
|
||||||
@@ -139,8 +141,7 @@ class CheckmarkWidgetView : HabitWidgetView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun init() {
|
private fun init() {
|
||||||
val appComponent: HabitsApplicationComponent =
|
val appComponent: HabitsApplicationComponent = (context.applicationContext as HabitsApplication).component
|
||||||
(context.applicationContext as HabitsApplication).component
|
|
||||||
preferences = appComponent.preferences
|
preferences = appComponent.preferences
|
||||||
ring = findViewById<View>(R.id.scoreRing) as RingView
|
ring = findViewById<View>(R.id.scoreRing) as RingView
|
||||||
label = findViewById<View>(R.id.label) as TextView
|
label = findViewById<View>(R.id.label) as TextView
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ abstract class HabitWidgetView : FrameLayout {
|
|||||||
val shadowRadius = dpToPixels(context, 2f).toInt()
|
val shadowRadius = dpToPixels(context, 2f).toInt()
|
||||||
val shadowOffset = dpToPixels(context, 1f).toInt()
|
val shadowOffset = dpToPixels(context, 1f).toInt()
|
||||||
val shadowColor = Color.argb(shadowAlpha, 0, 0, 0)
|
val shadowColor = Color.argb(shadowAlpha, 0, 0, 0)
|
||||||
val cornerRadius = dpToPixels(context, 12f)
|
val cornerRadius = dpToPixels(context, 5f)
|
||||||
val radii = FloatArray(8)
|
val radii = FloatArray(8)
|
||||||
Arrays.fill(radii, cornerRadius)
|
Arrays.fill(radii, cornerRadius)
|
||||||
val shape = RoundRectShape(radii, null, null)
|
val shape = RoundRectShape(radii, null, null)
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
2.1.1:
|
|
||||||
* Fix Tasker plugin
|
|
||||||
|
|
||||||
2.1:
|
2.1:
|
||||||
* Add notes to specific dates
|
* Add notes to specific dates
|
||||||
* Track at-most measurable habits
|
* Track at-most measurable habits
|
||||||
* Add skips to measurable habits
|
* Add skips to measurable habits
|
||||||
* Bring back custom frequencies
|
* Bring back custom frequencies
|
||||||
* Other minor improvements and bug fixes
|
* Other minor improvements and bug fixes
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
|
|
||||||
<item android:id="@android:id/mask">
|
<item android:id="@android:id/mask">
|
||||||
<shape android:shape="rectangle">
|
<shape android:shape="rectangle">
|
||||||
<corners android:radius="12dp"/>
|
<corners android:radius="5dp"/>
|
||||||
<solid android:color="?android:colorPrimary"/>
|
<solid android:color="?android:colorPrimary"/>
|
||||||
</shape>
|
</shape>
|
||||||
<color android:color="@color/white"/>
|
<color android:color="@color/white"/>
|
||||||
|
|||||||
@@ -21,10 +21,8 @@
|
|||||||
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:id="@+id/container"
|
android:id="@+id/container"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:minHeight="128dp"
|
|
||||||
android:minWidth="208dp"
|
|
||||||
app:divider="@drawable/checkmark_dialog_divider"
|
app:divider="@drawable/checkmark_dialog_divider"
|
||||||
app:showDividers="middle"
|
app:showDividers="middle"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
|
|||||||
@@ -30,4 +30,10 @@
|
|||||||
android:layout_weight="1">
|
android:layout_weight="1">
|
||||||
</ListView>
|
</ListView>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/buttonSave"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:text="@string/save"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
android:paddingTop="4dp"
|
android:paddingTop="4dp"
|
||||||
android:paddingLeft="8dp"
|
android:paddingLeft="8dp"
|
||||||
android:paddingRight="8dp"
|
android:paddingRight="8dp"
|
||||||
android:paddingBottom="8dp"
|
android:paddingBottom="4dp"
|
||||||
tools:ignore="UselessParent">
|
tools:ignore="UselessParent">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@@ -44,7 +44,6 @@
|
|||||||
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>
|
||||||
|
|||||||
@@ -21,5 +21,4 @@
|
|||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
<monochrome android:drawable="@mipmap/ic_launcher_monochrome"/>
|
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 7.4 KiB |
@@ -43,11 +43,11 @@ kotlin {
|
|||||||
val jvmMain by getting {
|
val jvmMain by getting {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(kotlin("stdlib-jdk8"))
|
implementation(kotlin("stdlib-jdk8"))
|
||||||
compileOnly("com.google.dagger:dagger:2.41")
|
compileOnly("com.google.dagger:dagger:2.43.2")
|
||||||
implementation("com.google.guava:guava:31.1-android")
|
implementation("com.google.guava:guava:31.1-android")
|
||||||
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.6.21")
|
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.7.10")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.1")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.4")
|
||||||
implementation("androidx.annotation:annotation:1.3.0")
|
implementation("androidx.annotation:annotation:1.4.0")
|
||||||
implementation("com.google.code.findbugs:jsr305:3.0.2")
|
implementation("com.google.code.findbugs:jsr305:3.0.2")
|
||||||
implementation("com.opencsv:opencsv:5.6")
|
implementation("com.opencsv:opencsv:5.6")
|
||||||
implementation("commons-codec:commons-codec:1.15")
|
implementation("commons-codec:commons-codec:1.15")
|
||||||
@@ -59,7 +59,7 @@ kotlin {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation(kotlin("test"))
|
implementation(kotlin("test"))
|
||||||
implementation(kotlin("test-junit"))
|
implementation(kotlin("test-junit"))
|
||||||
implementation("org.xerial:sqlite-jdbc:3.36.0.3")
|
implementation("org.xerial:sqlite-jdbc:3.39.2.0")
|
||||||
implementation("org.hamcrest:hamcrest:2.2")
|
implementation("org.hamcrest:hamcrest:2.2")
|
||||||
implementation("org.apache.commons:commons-io:1.3.2")
|
implementation("org.apache.commons:commons-io:1.3.2")
|
||||||
implementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0")
|
implementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0")
|
||||||
|
|||||||
@@ -25,12 +25,6 @@ class StringUtils {
|
|||||||
|
|
||||||
fun joinLongs(values: LongArray): String = values.joinToString(separator = ",")
|
fun joinLongs(values: LongArray): String = values.joinToString(separator = ",")
|
||||||
|
|
||||||
fun splitLongs(str: String): LongArray {
|
fun splitLongs(str: String): LongArray = str.split(",").map { it.toLong() }.toLongArray()
|
||||||
return try {
|
|
||||||
str.split(",").map { it.toLong() }.toLongArray()
|
|
||||||
} catch (e: NumberFormatException) {
|
|
||||||
LongArray(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ class HabitsCSVExporter(
|
|||||||
|
|
||||||
val timeframe = getTimeframe()
|
val timeframe = getTimeframe()
|
||||||
val oldest = timeframe[0]
|
val oldest = timeframe[0]
|
||||||
val newest = DateUtils.getTodayWithOffset()
|
val newest = DateUtils.getToday()
|
||||||
val checkmarks: MutableList<ArrayList<Entry>> = ArrayList()
|
val checkmarks: MutableList<ArrayList<Entry>> = ArrayList()
|
||||||
val scores: MutableList<ArrayList<Score>> = ArrayList()
|
val scores: MutableList<ArrayList<Score>> = ArrayList()
|
||||||
for (habit in selectedHabits) {
|
for (habit in selectedHabits) {
|
||||||
|
|||||||
@@ -204,16 +204,9 @@ open class EntryList {
|
|||||||
// Copy original entries
|
// Copy original entries
|
||||||
original.forEach { entry ->
|
original.forEach { entry ->
|
||||||
val offset = entry.timestamp.daysUntil(to)
|
val offset = entry.timestamp.daysUntil(to)
|
||||||
val value = if (
|
if (result[offset].value == UNKNOWN || entry.value == SKIP || entry.value == YES_MANUAL) {
|
||||||
result[offset].value == UNKNOWN ||
|
result[offset] = entry
|
||||||
entry.value == SKIP ||
|
|
||||||
entry.value == YES_MANUAL
|
|
||||||
) {
|
|
||||||
entry.value
|
|
||||||
} else {
|
|
||||||
YES_AUTO
|
|
||||||
}
|
}
|
||||||
result[offset] = Entry(entry.timestamp, value, entry.notes)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class BarCardPresenter(
|
|||||||
} else {
|
} else {
|
||||||
boolBucketSizes[boolSpinnerPosition]
|
boolBucketSizes[boolSpinnerPosition]
|
||||||
}
|
}
|
||||||
val today = DateUtils.getTodayWithOffset()
|
val today = DateUtils.getToday()
|
||||||
val oldest = habit.computedEntries.getKnown().lastOrNull()?.timestamp ?: today
|
val oldest = habit.computedEntries.getKnown().lastOrNull()?.timestamp ?: today
|
||||||
val entries = habit.computedEntries.getByInterval(oldest, today).groupedSum(
|
val entries = habit.computedEntries.getByInterval(oldest, today).groupedSum(
|
||||||
truncateField = ScoreCardPresenter.getTruncateField(bucketSize),
|
truncateField = ScoreCardPresenter.getTruncateField(bucketSize),
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ data class FrequencyCardState(
|
|||||||
val firstWeekday: Int,
|
val firstWeekday: Int,
|
||||||
val frequency: HashMap<Timestamp, Array<Int>>,
|
val frequency: HashMap<Timestamp, Array<Int>>,
|
||||||
val theme: Theme,
|
val theme: Theme,
|
||||||
val isNumerical: Boolean
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class FrequencyCardPresenter {
|
class FrequencyCardPresenter {
|
||||||
@@ -41,7 +40,6 @@ class FrequencyCardPresenter {
|
|||||||
theme: Theme
|
theme: Theme
|
||||||
) = FrequencyCardState(
|
) = FrequencyCardState(
|
||||||
color = habit.color,
|
color = habit.color,
|
||||||
isNumerical = habit.isNumerical,
|
|
||||||
frequency = habit.originalEntries.computeWeekdayFrequency(
|
frequency = habit.originalEntries.computeWeekdayFrequency(
|
||||||
isNumerical = habit.isNumerical
|
isNumerical = habit.isNumerical
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ class HistoryCardPresenter(
|
|||||||
screen.showCheckmarkPopup(
|
screen.showCheckmarkPopup(
|
||||||
entry.value,
|
entry.value,
|
||||||
entry.notes,
|
entry.notes,
|
||||||
|
preferences,
|
||||||
habit.color,
|
habit.color,
|
||||||
) { newValue, newNotes ->
|
) { newValue, newNotes ->
|
||||||
commandRunner.run(
|
commandRunner.run(
|
||||||
@@ -129,6 +130,7 @@ class HistoryCardPresenter(
|
|||||||
screen.showNumberPopup(
|
screen.showNumberPopup(
|
||||||
value = oldValue / 1000.0,
|
value = oldValue / 1000.0,
|
||||||
notes = entry.notes,
|
notes = entry.notes,
|
||||||
|
preferences = preferences,
|
||||||
) { newValue: Double, newNotes: String ->
|
) { newValue: Double, newNotes: String ->
|
||||||
val thousands = (newValue * 1000).roundToInt()
|
val thousands = (newValue * 1000).roundToInt()
|
||||||
commandRunner.run(
|
commandRunner.run(
|
||||||
@@ -201,11 +203,13 @@ class HistoryCardPresenter(
|
|||||||
fun showNumberPopup(
|
fun showNumberPopup(
|
||||||
value: Double,
|
value: Double,
|
||||||
notes: String,
|
notes: String,
|
||||||
|
preferences: Preferences,
|
||||||
callback: ListHabitsBehavior.NumberPickerCallback,
|
callback: ListHabitsBehavior.NumberPickerCallback,
|
||||||
)
|
)
|
||||||
fun showCheckmarkPopup(
|
fun showCheckmarkPopup(
|
||||||
selectedValue: Int,
|
selectedValue: Int,
|
||||||
notes: String,
|
notes: String,
|
||||||
|
preferences: Preferences,
|
||||||
color: PaletteColor,
|
color: PaletteColor,
|
||||||
callback: ListHabitsBehavior.CheckMarkDialogCallback,
|
callback: ListHabitsBehavior.CheckMarkDialogCallback,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ class BarChart(
|
|||||||
val r = round(barWidth * 0.15)
|
val r = round(barWidth * 0.15)
|
||||||
if (2 * r < barHeight) {
|
if (2 * r < barHeight) {
|
||||||
canvas.fillRect(x, y + r, barWidth, barHeight - r)
|
canvas.fillRect(x, y + r, barWidth, barHeight - r)
|
||||||
canvas.fillRect(x + r, y, barWidth - 2 * r, r + 1)
|
canvas.fillRect(x + r, y, barWidth - 2 * r, r)
|
||||||
canvas.fillCircle(x + r, y + r, r)
|
canvas.fillCircle(x + r, y + r, r)
|
||||||
canvas.fillCircle(x + barWidth - r, y + r, r)
|
canvas.fillCircle(x + barWidth - r, y + r, r)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -125,30 +125,4 @@ class WidgetTheme : LightTheme() {
|
|||||||
override val highContrastTextColor = Color.WHITE
|
override val highContrastTextColor = Color.WHITE
|
||||||
override val mediumContrastTextColor = Color.WHITE.withAlpha(0.50)
|
override val mediumContrastTextColor = Color.WHITE.withAlpha(0.50)
|
||||||
override val lowContrastTextColor = Color.WHITE.withAlpha(0.10)
|
override val lowContrastTextColor = Color.WHITE.withAlpha(0.10)
|
||||||
|
|
||||||
override fun color(paletteIndex: Int): Color {
|
|
||||||
return when (paletteIndex) {
|
|
||||||
0 -> Color(0xD32F2F)
|
|
||||||
1 -> Color(0xE64A19)
|
|
||||||
2 -> Color(0xF57C00)
|
|
||||||
3 -> Color(0xFF8F00)
|
|
||||||
4 -> Color(0xF9A825)
|
|
||||||
5 -> Color(0xAFB42B)
|
|
||||||
6 -> Color(0x7CB342)
|
|
||||||
7 -> Color(0x388E3C)
|
|
||||||
8 -> Color(0x00897B)
|
|
||||||
9 -> Color(0x00ACC1)
|
|
||||||
10 -> Color(0x039BE5)
|
|
||||||
11 -> Color(0x1976D2)
|
|
||||||
12 -> Color(0x6275f0)
|
|
||||||
13 -> Color(0x5E35B1)
|
|
||||||
14 -> Color(0x8E24AA)
|
|
||||||
15 -> Color(0xD81B60)
|
|
||||||
16 -> Color(0x5D4037)
|
|
||||||
17 -> Color(0x757575)
|
|
||||||
18 -> Color(0x757575)
|
|
||||||
19 -> Color(0x9E9E9E)
|
|
||||||
else -> Color(0x000000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ abstract class DateUtils {
|
|||||||
fun getStartOfTodayWithOffset(): Long = getStartOfDayWithOffset(getLocalTime())
|
fun getStartOfTodayWithOffset(): Long = getStartOfDayWithOffset(getLocalTime())
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun millisecondsUntilTomorrowWithOffset(): Long = getStartOfTomorrowWithOffset() - applyTimezone(getLocalTime())
|
fun millisecondsUntilTomorrowWithOffset(): Long = getStartOfTomorrowWithOffset() - getLocalTime()
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getStartOfTodayCalendar(): GregorianCalendar = getCalendar(getStartOfToday())
|
fun getStartOfTodayCalendar(): GregorianCalendar = getCalendar(getStartOfToday())
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
package org.isoron.uhabits.core.utils
|
package org.isoron.uhabits.core.utils
|
||||||
|
|
||||||
import org.isoron.uhabits.core.AppScope
|
import org.isoron.uhabits.core.AppScope
|
||||||
import org.isoron.uhabits.core.io.Logging
|
|
||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.ScheduledExecutorService
|
import java.util.concurrent.ScheduledExecutorService
|
||||||
@@ -30,10 +29,9 @@ import javax.inject.Inject
|
|||||||
* A class that emits events when a new day starts.
|
* A class that emits events when a new day starts.
|
||||||
*/
|
*/
|
||||||
@AppScope
|
@AppScope
|
||||||
open class MidnightTimer @Inject constructor(logging: Logging) {
|
open class MidnightTimer @Inject constructor() {
|
||||||
private val listeners: MutableList<MidnightListener> = LinkedList()
|
private val listeners: MutableList<MidnightListener> = LinkedList()
|
||||||
private lateinit var executor: ScheduledExecutorService
|
private lateinit var executor: ScheduledExecutorService
|
||||||
private val logger = logging.getLogger("MidnightTimer")
|
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun addListener(listener: MidnightListener) {
|
fun addListener(listener: MidnightListener) {
|
||||||
@@ -41,10 +39,7 @@ open class MidnightTimer @Inject constructor(logging: Logging) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun onPause(): MutableList<Runnable>? {
|
fun onPause(): MutableList<Runnable>? = executor.shutdownNow()
|
||||||
logger.info("Pausing timer")
|
|
||||||
return executor.shutdownNow()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun onResume(
|
fun onResume(
|
||||||
@@ -52,11 +47,9 @@ open class MidnightTimer @Inject constructor(logging: Logging) {
|
|||||||
testExecutor: ScheduledExecutorService? = null
|
testExecutor: ScheduledExecutorService? = null
|
||||||
) {
|
) {
|
||||||
executor = testExecutor ?: Executors.newSingleThreadScheduledExecutor()
|
executor = testExecutor ?: Executors.newSingleThreadScheduledExecutor()
|
||||||
val initialDelay = DateUtils.millisecondsUntilTomorrowWithOffset() + delayOffsetInMillis
|
|
||||||
logger.info("Scheduling refresh for $initialDelay ms from now")
|
|
||||||
executor.scheduleAtFixedRate(
|
executor.scheduleAtFixedRate(
|
||||||
{ notifyListeners() },
|
{ notifyListeners() },
|
||||||
initialDelay,
|
DateUtils.millisecondsUntilTomorrowWithOffset() + delayOffsetInMillis,
|
||||||
DateUtils.DAY_LENGTH,
|
DateUtils.DAY_LENGTH,
|
||||||
TimeUnit.MILLISECONDS
|
TimeUnit.MILLISECONDS
|
||||||
)
|
)
|
||||||
@@ -67,7 +60,6 @@ open class MidnightTimer @Inject constructor(logging: Logging) {
|
|||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
private fun notifyListeners() {
|
private fun notifyListeners() {
|
||||||
logger.info("Midnight refresh")
|
|
||||||
for (l in listeners) {
|
for (l in listeners) {
|
||||||
l.atMidnight()
|
l.atMidnight()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ class EntryListTest {
|
|||||||
fun testAddFromInterval() {
|
fun testAddFromInterval() {
|
||||||
val entries = listOf(
|
val entries = listOf(
|
||||||
Entry(day(1), YES_MANUAL),
|
Entry(day(1), YES_MANUAL),
|
||||||
Entry(day(2), NO, "Test"),
|
Entry(day(2), NO),
|
||||||
Entry(day(4), NO),
|
Entry(day(4), NO),
|
||||||
Entry(day(5), YES_MANUAL),
|
Entry(day(5), YES_MANUAL),
|
||||||
Entry(day(10), YES_MANUAL),
|
Entry(day(10), YES_MANUAL),
|
||||||
@@ -230,7 +230,7 @@ class EntryListTest {
|
|||||||
)
|
)
|
||||||
val expected = listOf(
|
val expected = listOf(
|
||||||
Entry(day(1), YES_MANUAL),
|
Entry(day(1), YES_MANUAL),
|
||||||
Entry(day(2), YES_AUTO, "Test"),
|
Entry(day(2), YES_AUTO),
|
||||||
Entry(day(3), UNKNOWN),
|
Entry(day(3), UNKNOWN),
|
||||||
Entry(day(4), YES_AUTO),
|
Entry(day(4), YES_AUTO),
|
||||||
Entry(day(5), YES_MANUAL),
|
Entry(day(5), YES_MANUAL),
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import kotlinx.coroutines.asCoroutineDispatcher
|
|||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.isoron.uhabits.core.BaseUnitTest
|
import org.isoron.uhabits.core.BaseUnitTest
|
||||||
import org.isoron.uhabits.core.io.StandardLogging
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
import java.util.TimeZone
|
import java.util.TimeZone
|
||||||
@@ -35,7 +34,7 @@ class MidnightTimerTest : BaseUnitTest() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
val suspendedListener = suspendCoroutine<Boolean> { continuation ->
|
val suspendedListener = suspendCoroutine<Boolean> { continuation ->
|
||||||
MidnightTimer(StandardLogging()).apply {
|
MidnightTimer().apply {
|
||||||
addListener { continuation.resume(true) }
|
addListener { continuation.resume(true) }
|
||||||
// When
|
// When
|
||||||
onResume(1, executor)
|
onResume(1, executor)
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ application {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
val ktorVersion = "1.6.8"
|
val ktorVersion = "1.6.8"
|
||||||
val kotlinVersion = "1.6.21"
|
val kotlinVersion = "1.7.10"
|
||||||
val logbackVersion = "1.2.11"
|
val logbackVersion = "1.2.11"
|
||||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion")
|
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion")
|
||||||
implementation("io.ktor:ktor-server-netty:$ktorVersion")
|
implementation("io.ktor:ktor-server-netty:$ktorVersion")
|
||||||
@@ -43,9 +43,9 @@ dependencies {
|
|||||||
implementation("io.ktor:ktor-html-builder:$ktorVersion")
|
implementation("io.ktor:ktor-html-builder:$ktorVersion")
|
||||||
implementation("io.ktor:ktor-jackson:$ktorVersion")
|
implementation("io.ktor:ktor-jackson:$ktorVersion")
|
||||||
implementation("org.jetbrains:kotlin-css-jvm:1.0.0-pre.148-kotlin-1.4.30")
|
implementation("org.jetbrains:kotlin-css-jvm:1.0.0-pre.148-kotlin-1.4.30")
|
||||||
implementation("io.prometheus:simpleclient:0.15.0")
|
implementation("io.prometheus:simpleclient:0.16.0")
|
||||||
implementation("io.prometheus:simpleclient_httpserver:0.15.0")
|
implementation("io.prometheus:simpleclient_httpserver:0.16.0")
|
||||||
implementation("io.prometheus:simpleclient_hotspot:0.15.0")
|
implementation("io.prometheus:simpleclient_hotspot:0.16.0")
|
||||||
testImplementation("io.ktor:ktor-server-tests:$ktorVersion")
|
testImplementation("io.ktor:ktor-server-tests:$ktorVersion")
|
||||||
testImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0")
|
testImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0")
|
||||||
testImplementation(kotlin("test"))
|
testImplementation(kotlin("test"))
|
||||||
|
|||||||