Compare commits

..

1 Commits

Author SHA1 Message Date
Quentin Hibon
a96b7f0fba Remove dependency on kotlin stdlib
According to:
https://kotlinlang.org/docs/whatsnew14.html#dependency-on-the-standard-library-added-by-default
the explicit dependency is no longer needed.
2023-03-26 12:53:09 +02:00
205 changed files with 973 additions and 1019 deletions

View File

@@ -4,7 +4,3 @@ updates:
directory: "/"
schedule:
interval: "monthly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"

View File

@@ -12,7 +12,7 @@ jobs:
timeout-minutes: 30
steps:
- name: Check out source code
uses: actions/checkout@v3
uses: actions/checkout@v1
- name: Build project
run: ./build.sh build
@@ -22,7 +22,7 @@ jobs:
- name: Upload artifacts
if: always()
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v2
with:
name: build
path: |

View File

@@ -1,20 +1,5 @@
# Changelog
## [2.1.2] -- 2023-05-26
### 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)

View File

@@ -17,8 +17,8 @@ show you how your habits improved over time. It is completely ad-free and open
source.
<p align="center">
<a href="https://play.google.com/store/apps/details?id=org.isoron.uhabits&utm_source=global_co&utm_medium=prtnr&utm_content=Mar2515&utm_campaign=PartBadge&pcampaignid=MKT-AC-global-none-all-co-pr-py-PartBadges-Oct1515-1"><img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" height="80px"/></a>
<a href="https://f-droid.org/app/org.isoron.uhabits"><img alt="Get it on F-Droid" src="https://f-droid.org/badge/get-it-on.png" height="80px"/></a>
<a href="https://play.google.com/store/apps/details?id=org.isoron.uhabits&utm_source=global_co&utm_medium=prtnr&utm_content=Mar2515&utm_campaign=PartBadge&pcampaignid=MKT-AC-global-none-all-co-pr-py-PartBadges-Oct1515-1"><img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/apps/en-play-badge-border.png" height="75px"/></a>
<a href="https://f-droid.org/app/org.isoron.uhabits"><img alt="Get it on F-Droid" src="https://i.imgur.com/baSPE7X.png" height="75px"/></a>
</p>
## Screenshots

View File

@@ -1,10 +1,11 @@
plugins {
val kotlinVersion = "1.7.21"
id("com.android.application") version "7.4.2" apply (false)
id("com.android.application") version ("7.3.1") apply (false)
id("org.jetbrains.kotlin.android") version kotlinVersion apply (false)
id("org.jetbrains.kotlin.kapt") version kotlinVersion apply (false)
id("org.jetbrains.kotlin.android.extensions") version kotlinVersion apply (false)
id("org.jetbrains.kotlin.multiplatform") version kotlinVersion apply (false)
id("org.jlleitschuh.gradle.ktlint") version "11.4.2"
id("org.jlleitschuh.gradle.ktlint") version "11.0.0"
}
apply {

View File

@@ -217,28 +217,20 @@ android_test_parallel() {
(
LOG=build/android-test-$API.log
log_info "API $API: Running tests..."
android_test $API 1>$LOG 2>&1
ret_code=$?
if [ $ret_code = 0 ]; then
if android_test $API 1>$LOG 2>&1; then
log_info "API $API: Passed"
else
log_error "API $API: Failed"
fi
pkill -9 -f ${AVD_PREFIX}${API}
exit $ret_code
)&
PIDS+=" $!"
done
# Check exit codes
success=0
RET_CODE=0
for pid in $PIDS; do
wait $pid
ret_code=$?
if [ $ret_code != 0 ]; then
success=1
fi
echo pid=$pid ret_code=$ret_code success=$success
wait $pid || RET_CODE=1
done
# Print all logs
@@ -248,7 +240,7 @@ android_test_parallel() {
echo "::endgroup::"
done
return $success
return $RET_CODE
}
android_build() {

View File

@@ -19,9 +19,10 @@
plugins {
id("com.github.triplet.play") version "3.7.0"
id("com.android.application") version "7.4.2"
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("org.jetbrains.kotlin.kapt")
id("org.jetbrains.kotlin.android.extensions")
id("org.jlleitschuh.gradle.ktlint")
}
@@ -34,10 +35,10 @@ android {
compileSdk = 32
defaultConfig {
versionCode = 20200
versionName = "2.2.0"
versionCode = 20101
versionName = "2.1.1"
minSdk = 28
targetSdk = 32
targetSdk = 31
applicationId = "org.isoron.uhabits"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
@@ -79,11 +80,11 @@ android {
}
dependencies {
val daggerVersion = "2.46"
val daggerVersion = "2.44.2"
val kotlinVersion = "1.7.21"
val kxCoroutinesVersion = "1.6.4"
val ktorVersion = "1.6.8"
val espressoVersion = "3.5.1"
val espressoVersion = "3.5.0"
androidTestImplementation("androidx.test.espresso:espresso-contrib:$espressoVersion")
androidTestImplementation("androidx.test.espresso:espresso-core:$espressoVersion")
@@ -92,12 +93,12 @@ dependencies {
androidTestImplementation("io.ktor:ktor-client-mock:$ktorVersion")
androidTestImplementation("io.ktor:ktor-jackson:$ktorVersion")
androidTestImplementation("androidx.annotation:annotation:1.5.0")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.ext:junit:1.1.4")
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.2.0")
androidTestImplementation("androidx.test:rules:1.5.0")
androidTestImplementation("org.mockito.kotlin:mockito-kotlin:2.2.11")
androidTestImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0")
compileOnly("javax.annotation:jsr250-api:1.0")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.3")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.2.2")
implementation("com.github.AppIntro:AppIntro:6.2.0")
implementation("com.google.code.findbugs:jsr305:3.0.2")
implementation("com.google.dagger:dagger:$daggerVersion")
@@ -119,7 +120,7 @@ dependencies {
kaptAndroidTest("com.google.dagger:dagger-compiler:$daggerVersion")
testImplementation("com.google.dagger:dagger:$daggerVersion")
testImplementation("junit:junit:4.13.2")
testImplementation("org.mockito.kotlin:mockito-kotlin:2.2.11")
testImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0")
}
kapt {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -39,7 +39,6 @@ open class BaseUserInterfaceTest {
private lateinit var prefs: Preferences
private lateinit var fixtures: HabitFixtures
private lateinit var cache: HabitCardListCache
@Before
@Throws(Exception::class)
fun setUp() {

View File

@@ -153,22 +153,18 @@ open class BaseViewTest : BaseAndroidTest() {
var filename = filename
var dir = getSDCardDir("test-screenshots")
if (dir == null) dir = AndroidDirFinder(targetContext).getFilesDir("test-screenshots")
if (dir == null) {
throw RuntimeException(
if (dir == null) throw RuntimeException(
"Could not find suitable dir for screenshots"
)
}
filename = filename.replace("\\.png$".toRegex(), "$suffix.png")
val absolutePath = String.format("%s/%s", dir.absolutePath, filename)
val parent = File(absolutePath).parentFile
if (!parent.exists() && !parent.mkdirs()) {
throw RuntimeException(
if (!parent.exists() && !parent.mkdirs()) throw RuntimeException(
String.format(
"Could not create dir: %s",
parent.absolutePath
)
)
}
val out = FileOutputStream(absolutePath)
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)
return absolutePath

View File

@@ -19,6 +19,7 @@
package org.isoron.uhabits
import com.nhaarman.mockitokotlin2.mock
import dagger.Component
import dagger.Module
import dagger.Provides
@@ -34,7 +35,6 @@ import org.isoron.uhabits.inject.ActivityScope
import org.isoron.uhabits.inject.HabitModule
import org.isoron.uhabits.inject.HabitsActivityModule
import org.isoron.uhabits.inject.HabitsApplicationComponent
import org.mockito.kotlin.mock
@Module
class TestModule {

View File

@@ -20,6 +20,7 @@ package org.isoron.uhabits.acceptance.steps
import android.os.Build
import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES
import androidx.annotation.StringRes
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso
@@ -32,11 +33,11 @@ import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.Until
import junit.framework.Assert.assertTrue
import org.hamcrest.CoreMatchers
import org.isoron.uhabits.BaseUserInterfaceTest
import org.isoron.uhabits.R
import org.isoron.uhabits.activities.habits.list.ListHabitsActivity
import org.junit.Assert.assertTrue
object CommonSteps : BaseUserInterfaceTest() {
fun pressBack() {
@@ -147,19 +148,15 @@ object CommonSteps : BaseUserInterfaceTest() {
Screen.LIST_HABITS ->
Espresso.onView(ViewMatchers.withClassName(CoreMatchers.endsWith("ListHabitsRootView")))
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
Screen.SHOW_HABIT ->
Espresso.onView(ViewMatchers.withId(R.id.subtitleCard))
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
Screen.EDIT_HABIT ->
Espresso.onView(ViewMatchers.withId(R.id.questionInput))
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
Screen.SELECT_HABIT_TYPE ->
Espresso.onView(ViewMatchers.withText(R.string.yes_or_no_example))
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
else -> throw IllegalStateException()
}
}

View File

@@ -21,9 +21,9 @@ package org.isoron.uhabits.acceptance.steps
import android.os.Build.VERSION.SDK_INT
import androidx.test.uiautomator.UiScrollable
import androidx.test.uiautomator.UiSelector
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import org.isoron.uhabits.BaseUserInterfaceTest
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
object WidgetSteps {
@Throws(Exception::class)

View File

@@ -44,7 +44,7 @@ class ScoreChartTest : BaseViewTest() {
habit = habit,
firstWeekday = prefs.firstWeekdayInt,
spinnerPosition = 0,
theme = LightTheme()
theme = LightTheme(),
)
view = ScoreChart(targetContext).apply {
setScores(state.scores)

View File

@@ -30,7 +30,6 @@ import org.junit.runner.RunWith
@MediumTest
class StreakChartTest : BaseViewTest() {
private lateinit var view: StreakChart
@Before
override fun setUp() {
super.setUp()

View File

@@ -44,7 +44,7 @@ class EntryButtonViewTest : BaseViewTest() {
view = component.getEntryButtonViewFactory().create().apply {
value = Entry.NO
color = PaletteUtils.getAndroidTestColor(5)
onToggle = { _, _ -> toggled = true }
onToggle = { _, _, _ -> toggled = true }
onEdit = { edited = true }
}
measureView(view, dpToPixels(48), dpToPixels(48))

View File

@@ -77,7 +77,7 @@ class EntryPanelViewTest : BaseViewTest() {
@Test
fun testToggle() {
val timestamps = mutableListOf<Timestamp>()
view.onToggle = { t, _, _ -> timestamps.add(t) }
view.onToggle = { t, _, _, _ -> timestamps.add(t) }
view.buttons[0].performLongClick()
view.buttons[2].performLongClick()
view.buttons[3].performLongClick()
@@ -88,7 +88,7 @@ class EntryPanelViewTest : BaseViewTest() {
fun testToggle_withOffset() {
val timestamps = mutableListOf<Timestamp>()
view.dataOffset = 3
view.onToggle = { t, _, _ -> timestamps += t }
view.onToggle = { t, _, _, _ -> timestamps += t }
view.buttons[0].performLongClick()
view.buttons[2].performLongClick()
view.buttons[3].performLongClick()

View File

@@ -20,15 +20,15 @@ package org.isoron.uhabits.activities.habits.list.views
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions
import com.nhaarman.mockitokotlin2.whenever
import org.isoron.uhabits.BaseViewTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
@MediumTest

View File

@@ -20,6 +20,9 @@ package org.isoron.uhabits.activities.habits.list.views
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.whenever
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.isoron.uhabits.BaseViewTest
@@ -27,9 +30,6 @@ import org.isoron.uhabits.core.ui.screens.habits.list.HintList
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
@MediumTest

View File

@@ -48,7 +48,7 @@ class FrequencyCardViewTest : BaseViewTest() {
FrequencyCardPresenter.buildState(
habit = habit,
firstWeekday = 0,
theme = LightTheme()
theme = LightTheme(),
)
)
measureView(view, 800f, 600f)

View File

@@ -49,7 +49,7 @@ class HistoryCardViewTest : BaseViewTest() {
HistoryCardPresenter.buildState(
habit = habit,
firstWeekday = SUNDAY,
theme = LightTheme()
theme = LightTheme(),
)
)
measureView(view, 800f, 600f)

View File

@@ -51,7 +51,7 @@ class OverviewCardViewTest : BaseViewTest() {
scoreYearDiff = 0.74f,
totalCount = 44,
color = PaletteColor(7),
theme = LightTheme()
theme = LightTheme(),
)
)
measureView(view, 800f, 300f)

View File

@@ -49,7 +49,7 @@ class ScoreCardViewTest : BaseViewTest() {
habit = habit,
firstWeekday = 0,
spinnerPosition = 0,
theme = LightTheme()
theme = LightTheme(),
)
)
measureView(view, 800f, 600f)

View File

@@ -48,7 +48,7 @@ class StreakCardViewTest : BaseViewTest() {
StreakCardState(
bestStreaks = habit.streaks.getBest(10),
color = habit.color,
theme = LightTheme()
theme = LightTheme(),
)
)
measureView(view, 800f, 600f)

View File

@@ -53,7 +53,7 @@ class SubtitleCardViewTest : BaseViewTest() {
isNumerical = false,
question = "Did you meditate this morning?",
reminder = Reminder(8, 30, EVERY_DAY),
theme = LightTheme()
theme = LightTheme(),
)
)
measureView(view, 800f, 200f)

View File

@@ -119,7 +119,7 @@ class ListHabitsRegressionTest : BaseUserInterfaceTest() {
"Wake up early",
"Meditate",
"Read books",
"Track time"
"Track time",
)
}
}

View File

@@ -33,11 +33,9 @@ import java.io.IOException
@MediumTest
class CheckmarkWidgetViewTest : BaseViewTest() {
private lateinit var view: CheckmarkWidgetView
@Before
override fun setUp() {
super.setUp()
similarityCutoff = 0.00025
setTheme(R.style.WidgetTheme)
val habit = fixtures.createShortHabit()
val computedEntries = habit.computedEntries

View File

@@ -19,6 +19,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.isoron.uhabits">
<uses-permission android:name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

View File

@@ -60,7 +60,7 @@ class AndroidCanvas : Canvas {
y1.toDp(),
x2.toDp(),
y2.toDp(),
paint
paint,
)
}
@@ -69,7 +69,7 @@ class AndroidCanvas : Canvas {
text,
x.toDp(),
y.toDp() + 0.6f * mHeight,
textPaint
textPaint,
)
}
@@ -83,7 +83,7 @@ class AndroidCanvas : Canvas {
y: Double,
width: Double,
height: Double,
cornerRadius: Double
cornerRadius: Double,
) {
paint.style = Paint.Style.FILL
innerCanvas.drawRoundRect(
@@ -93,7 +93,7 @@ class AndroidCanvas : Canvas {
(y + height).toDp(),
cornerRadius.toDp(),
cornerRadius.toDp(),
paint
paint,
)
}
@@ -108,7 +108,7 @@ class AndroidCanvas : Canvas {
y.toDp(),
(x + width).toDp(),
(y + height).toDp(),
paint
paint,
)
}
@@ -148,7 +148,7 @@ class AndroidCanvas : Canvas {
centerY: Double,
radius: Double,
startAngle: Double,
swipeAngle: Double
swipeAngle: Double,
) {
paint.style = Paint.Style.FILL
innerCanvas.drawArc(
@@ -159,14 +159,14 @@ class AndroidCanvas : Canvas {
-startAngle.toFloat(),
-swipeAngle.toFloat(),
true,
paint
paint,
)
}
override fun fillCircle(
centerX: Double,
centerY: Double,
radius: Double
radius: Double,
) {
paint.style = Paint.Style.FILL
innerCanvas.drawCircle(centerX.toDp(), centerY.toDp(), radius.toDp(), paint)

View File

@@ -33,7 +33,7 @@ import kotlin.math.max
*/
class AndroidDataView(
context: Context,
attrs: AttributeSet? = null
attrs: AttributeSet? = null,
) : AndroidView<DataView>(context, attrs),
GestureDetector.OnGestureListener,
ValueAnimator.AnimatorUpdateListener {
@@ -60,7 +60,7 @@ class AndroidDataView(
e1: MotionEvent?,
e2: MotionEvent?,
dx: Float,
dy: Float
dy: Float,
): Boolean {
if (abs(dx) > abs(dy)) {
val parent = parent
@@ -82,7 +82,7 @@ class AndroidDataView(
e1: MotionEvent?,
e2: MotionEvent?,
velocityX: Float,
velocityY: Float
velocityY: Float,
): Boolean {
scroller.fling(
scroller.currX,
@@ -140,11 +140,8 @@ class AndroidDataView(
// e.getPointerId.
return false
}
if (isSingleTap) {
view?.onClick(x / canvas.innerDensity, y / canvas.innerDensity)
} else {
view?.onLongClick(x / canvas.innerDensity, y / canvas.innerDensity)
}
if (isSingleTap) view?.onClick(x / canvas.innerDensity, y / canvas.innerDensity)
else view?.onLongClick(x / canvas.innerDensity, y / canvas.innerDensity)
return true
}
}

View File

@@ -47,6 +47,6 @@ fun Color.toInt(): Int {
(255 * this.alpha).roundToInt(),
(255 * this.red).roundToInt(),
(255 * this.green).roundToInt(),
(255 * this.blue).roundToInt()
(255 * this.blue).roundToInt(),
)
}

View File

@@ -24,7 +24,7 @@ import android.util.AttributeSet
open class AndroidView<T : View>(
context: Context,
attrs: AttributeSet? = null
attrs: AttributeSet? = null,
) : android.view.View(context, attrs) {
var view: T? = null

View File

@@ -39,7 +39,7 @@ import org.isoron.uhabits.inject.ActivityScope
class AndroidThemeSwitcher
constructor(
@ActivityContext val context: Context,
preferences: Preferences
preferences: Preferences,
) : ThemeSwitcher(preferences) {
override var currentTheme: Theme = LightTheme()

View File

@@ -27,7 +27,7 @@ import org.isoron.uhabits.utils.startActivitySafely
class AboutScreen(
private val activity: AboutActivity,
private val intents: IntentFactory,
private val prefs: Preferences
private val prefs: Preferences,
) {
private var developerCountdown = 5

View File

@@ -32,7 +32,7 @@ import org.isoron.uhabits.utils.setupToolbar
@SuppressLint("ViewConstructor")
class AboutView(
context: Context,
private val screen: AboutScreen
private val screen: AboutScreen,
) : FrameLayout(context) {
private var binding = AboutBinding.inflate(LayoutInflater.from(context))
@@ -43,7 +43,7 @@ class AboutView(
toolbar = binding.toolbar,
color = PaletteColor(11),
title = resources.getString(R.string.about),
theme = currentTheme()
theme = currentTheme(),
)
val version = resources.getString(R.string.version_n)
binding.tvContributors.setOnClickListener { screen.showCodeContributorsWebsite() }

View File

@@ -20,60 +20,109 @@
package org.isoron.uhabits.activities.common.dialogs
import android.app.Dialog
import android.os.Bundle
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import androidx.appcompat.app.AppCompatDialogFragment
import org.isoron.uhabits.HabitsApplication
import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.Entry.Companion.NO
import org.isoron.uhabits.core.models.Entry.Companion.SKIP
import org.isoron.uhabits.core.models.Entry.Companion.UNKNOWN
import org.isoron.uhabits.core.models.Entry.Companion.YES_AUTO
import org.isoron.uhabits.core.models.Entry.Companion.YES_MANUAL
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.databinding.CheckmarkPopupBinding
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
class CheckmarkDialog : AppCompatDialogFragment() {
var onToggle: (Int, String) -> Unit = { _, _ -> }
const val POPUP_WIDTH = 4 * 48f + 16f
const val POPUP_HEIGHT = 48f * 2.5f + 8f
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val appComponent = (requireActivity().application as HabitsApplication).component
val prefs = appComponent.preferences
val view = CheckmarkPopupBinding.inflate(LayoutInflater.from(context))
class CheckmarkPopup(
private val context: Context,
private val color: Int,
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 {
it.setTextColor(requireArguments().getInt("color"))
it.setTextColor(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 = getFontAwesome(requireContext())
}
view.notes.setText(requireArguments().getString("notes")!!)
private fun initTypefaces() {
arrayOf(view.yesBtn, view.noBtn, view.skipBtn, view.unknownBtn).forEach {
it.typeface = getFontAwesome(context)
}
}
private fun hideDisabledButtons() {
if (!prefs.isSkipEnabled) view.skipBtn.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.window?.apply {
setLayout(
view.root.dp(POPUP_WIDTH).toInt(),
view.root.dp(POPUP_HEIGHT).toInt()
)
setBackgroundDrawableResource(android.R.color.transparent)
}
fun onClick(v: Int) {
val notes = view.notes.text.toString().trim()
onToggle(v, notes)
requireDialog().dismiss()
this.value = v
save()
}
view.yesBtn.setOnClickListener { onClick(YES_MANUAL) }
view.noBtn.setOnClickListener { onClick(NO) }
view.skipBtn.setOnClickListener { onClick(SKIP) }
view.unknownBtn.setOnClickListener { onClick(UNKNOWN) }
view.notes.setOnEditorActionListener { v, actionId, event ->
onClick(requireArguments().getInt("value"))
true
dialog.setCanceledOnTouchOutside(true)
dialog.dimBehind()
dialog.dismissCurrentAndShow()
}
return dialog
fun save() {
onToggle(value, view.notes.text.toString().trim())
dialog.dismiss()
}
}

View File

@@ -22,101 +22,97 @@ package org.isoron.uhabits.activities.common.dialogs
import android.app.Dialog
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.RadioButton
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatDialogFragment
import kotlinx.android.synthetic.main.frequency_picker_dialog.view.*
import org.isoron.uhabits.R
import org.isoron.uhabits.databinding.FrequencyPickerDialogBinding
class FrequencyPickerDialog(
var freqNumerator: Int,
var freqDenominator: Int
) : AppCompatDialogFragment() {
private var _binding: FrequencyPickerDialogBinding? = null
private val binding get() = _binding!!
lateinit var contentView: View
var onFrequencyPicked: (num: Int, den: Int) -> Unit = { _, _ -> }
constructor() : this(1, 1)
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
_binding = FrequencyPickerDialogBinding.inflate(LayoutInflater.from(requireActivity()))
val inflater = LayoutInflater.from(requireActivity())
contentView = inflater.inflate(R.layout.frequency_picker_dialog, null)
addBeforeAfterText(
this.getString(R.string.every_x_days),
binding.everyXDaysContainer
contentView.everyXDaysContainer,
)
addBeforeAfterText(
this.getString(R.string.x_times_per_week),
binding.xTimesPerWeekContainer
contentView.xTimesPerWeekContainer,
)
addBeforeAfterText(
this.getString(R.string.x_times_per_month),
binding.xTimesPerMonthContainer
contentView.xTimesPerMonthContainer,
)
addBeforeAfterText(
this.getString(R.string.x_times_per_y_days),
binding.xTimesPerYDaysContainer
contentView.xTimesPerYDaysContainer,
)
binding.everyDayRadioButton.setOnClickListener {
check(binding.everyDayRadioButton)
contentView.everyDayRadioButton.setOnClickListener {
check(contentView.everyDayRadioButton)
}
binding.everyXDaysRadioButton.setOnClickListener {
check(binding.everyXDaysRadioButton)
val everyXDaysTextView = binding.everyXDaysTextView
contentView.everyXDaysRadioButton.setOnClickListener {
check(contentView.everyXDaysRadioButton)
val everyXDaysTextView = contentView.everyXDaysTextView
selectInputField(everyXDaysTextView)
}
binding.everyXDaysTextView.setOnFocusChangeListener { v, hasFocus ->
if (hasFocus) check(binding.everyXDaysRadioButton)
contentView.everyXDaysTextView.setOnFocusChangeListener { v, hasFocus ->
if (hasFocus) check(contentView.everyXDaysRadioButton)
}
binding.xTimesPerWeekRadioButton.setOnClickListener {
check(binding.xTimesPerWeekRadioButton)
selectInputField(binding.xTimesPerWeekTextView)
contentView.xTimesPerWeekRadioButton.setOnClickListener {
check(contentView.xTimesPerWeekRadioButton)
selectInputField(contentView.xTimesPerWeekTextView)
}
binding.xTimesPerWeekTextView.setOnFocusChangeListener { v, hasFocus ->
if (hasFocus) check(binding.xTimesPerWeekRadioButton)
contentView.xTimesPerWeekTextView.setOnFocusChangeListener { v, hasFocus ->
if (hasFocus) check(contentView.xTimesPerWeekRadioButton)
}
binding.xTimesPerMonthRadioButton.setOnClickListener {
check(binding.xTimesPerMonthRadioButton)
selectInputField(binding.xTimesPerMonthTextView)
contentView.xTimesPerMonthRadioButton.setOnClickListener {
check(contentView.xTimesPerMonthRadioButton)
selectInputField(contentView.xTimesPerMonthTextView)
}
binding.xTimesPerMonthTextView.setOnFocusChangeListener { v, hasFocus ->
if (hasFocus) check(binding.xTimesPerMonthRadioButton)
contentView.xTimesPerMonthTextView.setOnFocusChangeListener { v, hasFocus ->
if (hasFocus) check(contentView.xTimesPerMonthRadioButton)
}
binding.xTimesPerYDaysRadioButton.setOnClickListener {
check(binding.xTimesPerYDaysRadioButton)
selectInputField(binding.xTimesPerYDaysXTextView)
contentView.xTimesPerYDaysRadioButton.setOnClickListener {
check(contentView.xTimesPerYDaysRadioButton)
selectInputField(contentView.xTimesPerYDaysXTextView)
}
binding.xTimesPerYDaysXTextView.setOnFocusChangeListener { v, hasFocus ->
if (hasFocus) check(binding.xTimesPerYDaysRadioButton)
contentView.xTimesPerYDaysXTextView.setOnFocusChangeListener { v, hasFocus ->
if (hasFocus) check(contentView.xTimesPerYDaysRadioButton)
}
binding.xTimesPerYDaysYTextView.setOnFocusChangeListener { v, hasFocus ->
if (hasFocus) check(binding.xTimesPerYDaysRadioButton)
contentView.xTimesPerYDaysYTextView.setOnFocusChangeListener { v, hasFocus ->
if (hasFocus) check(contentView.xTimesPerYDaysRadioButton)
}
return AlertDialog.Builder(requireActivity())
.setView(binding.root)
.setView(contentView)
.setPositiveButton(R.string.save) { _, _ -> onSaveClicked() }
.create()
}
@@ -128,8 +124,7 @@ class FrequencyPickerDialog(
val parts = str.split("%d")
for (i in parts.indices) {
container.addView(
TextView(activity).apply { text = parts[i].trim() },
2 * i + 1
TextView(activity).apply { text = parts[i].trim() }, 2 * i + 1,
)
}
}
@@ -138,35 +133,31 @@ class FrequencyPickerDialog(
var numerator = 1
var denominator = 1
when {
binding.everyDayRadioButton.isChecked -> {
contentView.everyDayRadioButton.isChecked -> {
// NOP
}
binding.everyXDaysRadioButton.isChecked -> {
if (binding.everyXDaysTextView.text.isNotEmpty()) {
denominator = Integer.parseInt(binding.everyXDaysTextView.text.toString())
contentView.everyXDaysRadioButton.isChecked -> {
if (contentView.everyXDaysTextView.text.isNotEmpty()) {
denominator = Integer.parseInt(contentView.everyXDaysTextView.text.toString())
}
}
binding.xTimesPerWeekRadioButton.isChecked -> {
if (binding.xTimesPerWeekTextView.text.isNotEmpty()) {
numerator = Integer.parseInt(binding.xTimesPerWeekTextView.text.toString())
contentView.xTimesPerWeekRadioButton.isChecked -> {
if (contentView.xTimesPerWeekTextView.text.isNotEmpty()) {
numerator = Integer.parseInt(contentView.xTimesPerWeekTextView.text.toString())
denominator = 7
}
}
binding.xTimesPerYDaysRadioButton.isChecked -> {
if (binding.xTimesPerYDaysXTextView.text.isNotEmpty() && binding.xTimesPerYDaysYTextView.text.isNotEmpty()) {
contentView.xTimesPerYDaysRadioButton.isChecked -> {
if (contentView.xTimesPerYDaysXTextView.text.isNotEmpty() && contentView.xTimesPerYDaysYTextView.text.isNotEmpty()) {
numerator =
Integer.parseInt(binding.xTimesPerYDaysXTextView.text.toString())
Integer.parseInt(contentView.xTimesPerYDaysXTextView.text.toString())
denominator =
Integer.parseInt(binding.xTimesPerYDaysYTextView.text.toString())
Integer.parseInt(contentView.xTimesPerYDaysYTextView.text.toString())
}
}
else -> {
if (binding.xTimesPerMonthTextView.text.isNotEmpty()) {
numerator = Integer.parseInt(binding.xTimesPerMonthTextView.text.toString())
if (contentView.xTimesPerMonthTextView.text.isNotEmpty()) {
numerator = Integer.parseInt(contentView.xTimesPerMonthTextView.text.toString())
denominator = 30
}
}
@@ -193,27 +184,27 @@ class FrequencyPickerDialog(
private fun populateViews() {
uncheckAll()
if (freqDenominator == 30 || freqDenominator == 31) {
binding.xTimesPerMonthRadioButton.isChecked = true
binding.xTimesPerMonthTextView.setText(freqNumerator.toString())
selectInputField(binding.xTimesPerMonthTextView)
contentView.xTimesPerMonthRadioButton.isChecked = true
contentView.xTimesPerMonthTextView.setText(freqNumerator.toString())
selectInputField(contentView.xTimesPerMonthTextView)
} else {
if (freqNumerator == 1) {
if (freqDenominator == 1) {
binding.everyDayRadioButton.isChecked = true
contentView.everyDayRadioButton.isChecked = true
} else {
binding.everyXDaysRadioButton.isChecked = true
binding.everyXDaysTextView.setText(freqDenominator.toString())
selectInputField(binding.everyXDaysTextView)
contentView.everyXDaysRadioButton.isChecked = true
contentView.everyXDaysTextView.setText(freqDenominator.toString())
selectInputField(contentView.everyXDaysTextView)
}
} else {
if (freqDenominator == 7) {
binding.xTimesPerWeekRadioButton.isChecked = true
binding.xTimesPerWeekTextView.setText(freqNumerator.toString())
selectInputField(binding.xTimesPerWeekTextView)
contentView.xTimesPerWeekRadioButton.isChecked = true
contentView.xTimesPerWeekTextView.setText(freqNumerator.toString())
selectInputField(contentView.xTimesPerWeekTextView)
} else {
binding.xTimesPerYDaysRadioButton.isChecked = true
binding.xTimesPerYDaysXTextView.setText(freqNumerator.toString())
binding.xTimesPerYDaysYTextView.setText(freqDenominator.toString())
contentView.xTimesPerYDaysRadioButton.isChecked = true
contentView.xTimesPerYDaysXTextView.setText(freqNumerator.toString())
contentView.xTimesPerYDaysYTextView.setText(freqDenominator.toString())
}
}
}
@@ -224,10 +215,10 @@ class FrequencyPickerDialog(
}
private fun uncheckAll() {
binding.everyDayRadioButton.isChecked = false
binding.everyXDaysRadioButton.isChecked = false
binding.xTimesPerWeekRadioButton.isChecked = false
binding.xTimesPerMonthRadioButton.isChecked = false
binding.xTimesPerYDaysRadioButton.isChecked = false
contentView.everyDayRadioButton.isChecked = false
contentView.everyXDaysRadioButton.isChecked = false
contentView.xTimesPerWeekRadioButton.isChecked = false
contentView.xTimesPerMonthRadioButton.isChecked = false
contentView.xTimesPerYDaysRadioButton.isChecked = false
}
}

View File

@@ -69,7 +69,7 @@ class HistoryEditorDialog : AppCompatDialogFragment(), CommandRunner.Listener {
theme = themeSwitcher.currentTheme,
today = DateUtils.getTodayWithOffset().toLocalDate(),
onDateClickedListener = onDateClickedListener ?: object : OnDateClickedListener {},
padding = 10.0
padding = 10.0,
)
dataView = AndroidDataView(requireContext(), null)
dataView.view = chart!!

View File

@@ -1,104 +0,0 @@
package org.isoron.uhabits.activities.common.dialogs
import android.app.Dialog
import android.os.Bundle
import android.text.method.DigitsKeyListener
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
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((Entry.SKIP.toDouble() / 1000).toString())
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")
}
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()
}
}

View File

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

View File

@@ -181,9 +181,8 @@ class FrequencyChart : ScrollableChart {
rect[0f, 0f, baseSize.toFloat()] = baseSize.toFloat()
rect.offset(prevRect!!.left, prevRect!!.top + baseSize * j)
val i = localeWeekdayList[j] % 7
if (values != null) {
if (values != null)
drawMarker(canvas, rect, values[i], weekDaysInMonth[i])
}
rect.offset(0f, rowHeight)
}
drawFooter(canvas, rect, date)
@@ -197,15 +196,13 @@ class FrequencyChart : ScrollableChart {
rect.centerY() - 0.1f * em,
pText!!
)
if (date[Calendar.MONTH] == 1) {
canvas.drawText(
if (date[Calendar.MONTH] == 1) canvas.drawText(
dfYear!!.format(time),
rect.centerX(),
rect.centerY() + 0.9f * em,
pText!!
)
}
}
private fun drawGrid(canvas: Canvas, rGrid: RectF?) {
val nRows = 7

View File

@@ -148,12 +148,8 @@ class RingView : View {
pRing!!.color = inactiveColor!!
activeCanvas.drawArc(rect!!, angle - 90, 360 - angle, true, pRing!!)
if (thickness > 0) {
if (isTransparencyEnabled) {
pRing!!.xfermode = XFERMODE_CLEAR
} else {
pRing!!.color =
if (isTransparencyEnabled) pRing!!.xfermode = XFERMODE_CLEAR else pRing!!.color =
backgroundColor!!
}
rect!!.inset(thickness, thickness)
activeCanvas.drawArc(rect!!, 0f, 360f, true, pRing!!)
pRing!!.xfermode = null

View File

@@ -35,6 +35,11 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.DialogFragment
import com.android.datetimepicker.time.RadialPickerLayout
import com.android.datetimepicker.time.TimePickerDialog
import kotlinx.android.synthetic.main.activity_edit_habit.nameInput
import kotlinx.android.synthetic.main.activity_edit_habit.notesInput
import kotlinx.android.synthetic.main.activity_edit_habit.questionInput
import kotlinx.android.synthetic.main.activity_edit_habit.targetInput
import kotlinx.android.synthetic.main.activity_edit_habit.unitInput
import org.isoron.platform.gui.toInt
import org.isoron.uhabits.HabitsApplication
import org.isoron.uhabits.R
@@ -266,9 +271,9 @@ class EditHabitActivity : AppCompatActivity() {
habit.copyFrom(original)
}
habit.name = binding.nameInput.text.trim().toString()
habit.question = binding.questionInput.text.trim().toString()
habit.description = binding.notesInput.text.trim().toString()
habit.name = nameInput.text.trim().toString()
habit.question = questionInput.text.trim().toString()
habit.description = notesInput.text.trim().toString()
habit.color = color
if (reminderHour >= 0) {
habit.reminder = Reminder(reminderHour, reminderMin, reminderDays)
@@ -278,9 +283,9 @@ class EditHabitActivity : AppCompatActivity() {
habit.frequency = Frequency(freqNum, freqDen)
if (habitType == HabitType.NUMERICAL) {
habit.targetValue = binding.targetInput.text.toString().toDouble()
habit.targetValue = targetInput.text.toString().toDouble()
habit.targetType = targetType
habit.unit = binding.unitInput.text.trim().toString()
habit.unit = unitInput.text.trim().toString()
}
habit.type = habitType
@@ -303,13 +308,13 @@ class EditHabitActivity : AppCompatActivity() {
private fun validate(): Boolean {
var isValid = true
if (binding.nameInput.text.isEmpty()) {
binding.nameInput.error = getFormattedValidationError(R.string.validation_cannot_be_blank)
if (nameInput.text.isEmpty()) {
nameInput.error = getFormattedValidationError(R.string.validation_cannot_be_blank)
isValid = false
}
if (habitType == HabitType.NUMERICAL) {
if (binding.targetInput.text.isEmpty()) {
binding.targetInput.error = getString(R.string.validation_cannot_be_blank)
if (targetInput.text.isEmpty()) {
targetInput.error = getString(R.string.validation_cannot_be_blank)
isValid = false
}
}

View File

@@ -40,7 +40,6 @@ import org.isoron.uhabits.inject.ActivityContextModule
import org.isoron.uhabits.inject.DaggerHabitsActivityComponent
import org.isoron.uhabits.inject.HabitsActivityComponent
import org.isoron.uhabits.inject.HabitsApplicationComponent
import org.isoron.uhabits.utils.dismissCurrentDialog
import org.isoron.uhabits.utils.restartWithFade
class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
@@ -92,7 +91,6 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
midnightTimer.onPause()
screen.onDetached()
adapter.cancelRefresh()
dismissCurrentDialog()
super.onPause()
}
@@ -101,7 +99,6 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
screen.onAttached()
rootView.postInvalidate()
midnightTimer.onResume()
appComponent.reminderScheduler.scheduleAll()
taskRunner.run {
try {
AutoBackup(this@ListHabitsActivity).run()

View File

@@ -94,7 +94,7 @@ class ListHabitsRootView @Inject constructor(
title = resources.getString(R.string.main_activity_title),
color = PaletteColor(17),
displayHomeAsUpEnabled = false,
theme = currentTheme()
theme = currentTheme(),
)
addView(rootView, MATCH_PARENT, MATCH_PARENT)
listAdapter.setListView(listView)

View File

@@ -22,15 +22,14 @@ package org.isoron.uhabits.activities.habits.list
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import dagger.Lazy
import org.isoron.platform.gui.toInt
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.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.list.views.HabitCardListAdapter
import org.isoron.uhabits.core.commands.ArchiveHabitsCommand
@@ -96,7 +95,7 @@ class ListHabitsScreen
private val colorPickerFactory: ColorPickerDialogFactory,
private val behavior: Lazy<ListHabitsBehavior>,
private val preferences: Preferences,
private val rootView: Lazy<ListHabitsRootView>
private val rootView: Lazy<ListHabitsRootView>,
) : CommandRunner.Listener,
ListHabitsBehavior.Screen,
ListHabitsMenuBehavior.Screen,
@@ -234,14 +233,17 @@ class ListHabitsScreen
notes: String,
callback: ListHabitsBehavior.NumberPickerCallback
) {
val fm = (context as AppCompatActivity).supportFragmentManager
val dialog = NumberDialog()
dialog.arguments = Bundle().apply {
putDouble("value", value)
putString("notes", notes)
val view = rootView.get()
NumberPopup(
context = context,
prefs = preferences,
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(
@@ -250,16 +252,18 @@ class ListHabitsScreen
color: PaletteColor,
callback: ListHabitsBehavior.CheckMarkDialogCallback
) {
val theme = rootView.get().currentTheme()
val fm = (context as AppCompatActivity).supportFragmentManager
val dialog = CheckmarkDialog()
dialog.arguments = Bundle().apply {
putInt("color", theme.color(color).toInt())
putInt("value", selectedValue)
putString("notes", notes)
val view = rootView.get()
CheckmarkPopup(
context = context,
prefs = preferences,
anchor = view,
color = view.currentTheme().color(color).toInt(),
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? {
@@ -321,11 +325,8 @@ class ListHabitsScreen
private fun onExportDB() {
taskRunner.execute(
exportDBFactory.create { filename ->
if (filename != null) {
activity.showSendFileScreen(filename)
} else {
activity.showMessage(activity.resources.getString(R.string.could_not_export))
}
if (filename != null) activity.showSendFileScreen(filename)
else activity.showMessage(activity.resources.getString(R.string.could_not_export))
}
)
}

View File

@@ -60,11 +60,8 @@ abstract class ButtonPanelView<T : View>(
repeat(buttonCount) { buttons.add(createButton()) }
removeAllViews()
if (reverse) {
buttons.reversed().forEach { addView(it) }
} else {
buttons.forEach { addView(it) }
}
if (reverse) buttons.reversed().forEach { addView(it) }
else buttons.forEach { addView(it) }
setupButtons()
requestLayout()
}

View File

@@ -44,6 +44,8 @@ import org.isoron.uhabits.utils.sres
import org.isoron.uhabits.utils.toMeasureSpec
import javax.inject.Inject
const val TOGGLE_DELAY_MILLIS = 2000L
class CheckmarkButtonViewFactory
@Inject constructor(
@ActivityContext val context: Context,
@@ -77,7 +79,7 @@ class CheckmarkButtonView(
invalidate()
}
var onToggle: (Int, String) -> Unit = { _, _ -> }
var onToggle: (Int, String, Long) -> Unit = { _, _, _ -> }
var onEdit: () -> Unit = { }
@@ -88,31 +90,25 @@ class CheckmarkButtonView(
setOnLongClickListener(this)
}
fun performToggle() {
fun performToggle(delay: Long) {
value = Entry.nextToggleValue(
value = value,
isSkipEnabled = preferences.isSkipEnabled,
areQuestionMarksEnabled = preferences.areQuestionMarksEnabled
)
onToggle(value, notes)
onToggle(value, notes, delay)
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
invalidate()
}
override fun onClick(v: View) {
if (preferences.isShortToggleEnabled) {
performToggle()
} else {
onEdit()
}
if (preferences.isShortToggleEnabled) performToggle(TOGGLE_DELAY_MILLIS)
else onEdit()
}
override fun onLongClick(v: View): Boolean {
if (preferences.isShortToggleEnabled) {
onEdit()
} else {
performToggle()
}
if (preferences.isShortToggleEnabled) onEdit()
else performToggle(TOGGLE_DELAY_MILLIS)
return true
}
@@ -146,11 +142,8 @@ class CheckmarkButtonView(
paint.color = when (value) {
YES_MANUAL, YES_AUTO, SKIP -> color
NO -> {
if (preferences.areQuestionMarksEnabled) {
mediumContrastColor
} else {
lowContrastColor
}
if (preferences.areQuestionMarksEnabled) mediumContrastColor
else lowContrastColor
}
else -> lowContrastColor
}
@@ -158,11 +151,8 @@ class CheckmarkButtonView(
SKIP -> R.string.fa_skipped
NO -> R.string.fa_times
UNKNOWN -> {
if (preferences.areQuestionMarksEnabled) {
R.string.fa_question
} else {
R.string.fa_times
}
if (preferences.areQuestionMarksEnabled) R.string.fa_question
else R.string.fa_times
}
else -> R.string.fa_check
}

View File

@@ -60,7 +60,7 @@ class CheckmarkPanelView(
setupButtons()
}
var onToggle: (Timestamp, Int, String) -> Unit = { _, _, _ -> }
var onToggle: (Timestamp, Int, String, Long) -> Unit = { _, _, _, _ -> }
set(value) {
field = value
setupButtons()
@@ -89,7 +89,7 @@ class CheckmarkPanelView(
else -> ""
}
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) }
}
}

View File

@@ -156,11 +156,10 @@ class HabitCardListController @Inject constructor(
}
private fun notifyListener() {
if (activeMode is SelectionMode) {
if (activeMode is SelectionMode)
selectionMenu.get().onSelectionChange()
} else {
else
selectionMenu.get().onSelectionFinish()
}
}
}
}

View File

@@ -57,6 +57,13 @@ class HabitCardViewFactory
fun create() = HabitCardView(context, checkmarkPanelFactory, numberPanelFactory, behavior)
}
data class DelayedToggle(
var habit: Habit,
var timestamp: Timestamp,
var value: Int,
var notes: String
)
class HabitCardView(
@ActivityContext context: Context,
checkmarkPanelFactory: CheckmarkPanelViewFactory,
@@ -129,6 +136,7 @@ class HabitCardView(
private var scoreRing: RingView
private var currentToggleTaskId = 0
private var queuedToggles = mutableListOf<DelayedToggle>()
init {
scoreRing = RingView(context).apply {
@@ -152,9 +160,12 @@ class HabitCardView(
}
checkmarkPanel = checkmarkPanelFactory.create().apply {
onToggle = { timestamp, value, notes ->
triggerRipple(timestamp)
habit?.let { behavior.onToggle(it, timestamp, value, notes) }
onToggle = { timestamp, value, notes, delay ->
if (delay > 0) triggerRipple(timestamp)
habit?.let {
val taskId = queueToggle(it, timestamp, value, notes);
{ runPendingToggles(taskId) }.delay(delay)
}
}
onEdit = { timestamp ->
triggerRipple(timestamp)
@@ -194,6 +205,25 @@ class HabitCardView(
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() {
Handler(Looper.getMainLooper()).post {
habit?.let { copyAttributesFrom(it) }
@@ -225,6 +255,7 @@ class HabitCardView(
}
private fun copyAttributesFrom(h: Habit) {
fun getActiveColor(habit: Habit): Int {
return when (habit.isArchived) {
true -> sres.getColor(R.attr.contrast60)
@@ -270,6 +301,7 @@ class HabitCardView(
}
private fun updateBackground(isSelected: Boolean) {
val background = when (isSelected) {
true -> R.drawable.selected_box
false -> R.drawable.ripple

View File

@@ -124,20 +124,15 @@ class HeaderView(
rect.set(0f, 0f, width, height)
rect.offset(canvas.width.toFloat() - dp(3.0f), 0f)
if (isReversed) {
rect.offset(-(index + 1) * width, 0f)
} else {
rect.offset((index - buttonCount) * width, 0f)
}
if (isReversed) rect.offset(-(index + 1) * width, 0f)
else rect.offset((index - buttonCount) * width, 0f)
if (isRTL()) {
rect.set(
if (isRTL()) rect.set(
canvas.width - rect.right,
rect.top,
canvas.width - rect.left,
rect.bottom
)
}
val y1 = rect.centerY() - 0.25 * em
val y2 = rect.centerY() + 1.25 * em

View File

@@ -34,10 +34,10 @@ import org.isoron.uhabits.HabitsApplication
import org.isoron.uhabits.R
import org.isoron.uhabits.activities.AndroidThemeSwitcher
import org.isoron.uhabits.activities.HabitsDirFinder
import org.isoron.uhabits.activities.common.dialogs.CheckmarkDialog
import org.isoron.uhabits.activities.common.dialogs.CheckmarkPopup
import org.isoron.uhabits.activities.common.dialogs.ConfirmDeleteDialog
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.CommandRunner
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.utils.currentTheme
import org.isoron.uhabits.utils.dismissCurrentAndShow
import org.isoron.uhabits.utils.dismissCurrentDialog
import org.isoron.uhabits.utils.showMessage
import org.isoron.uhabits.utils.showSendFileScreen
import org.isoron.uhabits.widgets.WidgetUpdater
@@ -88,7 +87,7 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
habit = habit,
habitList = habitList,
preferences = preferences,
screen = screen
screen = screen,
)
view = ShowHabitView(this)
@@ -99,13 +98,13 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
habitList = habitList,
screen = screen,
system = HabitsDirFinder(AndroidDirFinder(this)),
taskRunner = appComponent.taskRunner
taskRunner = appComponent.taskRunner,
)
menu = ShowHabitMenu(
activity = this,
presenter = menuPresenter,
preferences = preferences
preferences = preferences,
)
view.setListener(presenter)
@@ -130,7 +129,6 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
}
override fun onPause() {
dismissCurrentDialog()
commandRunner.removeListener(this)
super.onPause()
}
@@ -150,7 +148,7 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
ShowHabitPresenter.buildState(
habit = habit,
preferences = preferences,
theme = themeSwitcher.currentTheme
theme = themeSwitcher.currentTheme,
)
)
}
@@ -172,32 +170,41 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
override fun showNumberPopup(
value: Double,
notes: String,
preferences: Preferences,
callback: ListHabitsBehavior.NumberPickerCallback
) {
val dialog = NumberDialog()
dialog.arguments = Bundle().apply {
putDouble("value", value)
putString("notes", notes)
val anchor = getPopupAnchor() ?: return
NumberPopup(
context = this@ShowHabitActivity,
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(
selectedValue: Int,
notes: String,
preferences: Preferences,
color: PaletteColor,
callback: ListHabitsBehavior.CheckMarkDialogCallback
) {
val theme = view.currentTheme()
val dialog = CheckmarkDialog()
dialog.arguments = Bundle().apply {
putInt("color", theme.color(color).toInt())
putInt("value", selectedValue)
putString("notes", notes)
val anchor = getPopupAnchor() ?: return
CheckmarkPopup(
context = this@ShowHabitActivity,
prefs = preferences,
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? {

View File

@@ -28,7 +28,7 @@ import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitMenuPresenter
class ShowHabitMenu(
val activity: ShowHabitActivity,
val presenter: ShowHabitMenuPresenter,
val preferences: Preferences
val preferences: Preferences,
) {
fun onCreateOptionsMenu(menu: Menu): Boolean {
activity.menuInflater.inflate(R.menu.show_habit, menu)

View File

@@ -39,7 +39,7 @@ class ShowHabitView(context: Context) : FrameLayout(context) {
binding.toolbar,
title = data.title,
color = data.color,
theme = data.theme
theme = data.theme,
)
binding.subtitleCard.setState(data.subtitle)
binding.overviewCard.setState(data.overview)

View File

@@ -63,7 +63,7 @@ class BarCardView(context: Context, attrs: AttributeSet) : LinearLayout(context,
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
id: Long,
) {
presenter.onBoolSpinnerPosition(position)
}
@@ -77,7 +77,7 @@ class BarCardView(context: Context, attrs: AttributeSet) : LinearLayout(context,
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
id: Long,
) {
presenter.onNumericalSpinnerPosition(position)
}

View File

@@ -45,7 +45,7 @@ class HistoryCardView(context: Context, attrs: AttributeSet) : LinearLayout(cont
series = state.series,
defaultSquare = state.defaultSquare,
notesIndicators = state.notesIndicators,
firstWeekday = state.firstWeekday
firstWeekday = state.firstWeekday,
)
binding.chart.postInvalidate()
}

View File

@@ -52,7 +52,7 @@ class SubtitleCardView(context: Context, attrs: AttributeSet) : LinearLayout(con
binding.frequencyLabel.text = formatFrequency(
state.frequency.numerator,
state.frequency.denominator,
resources
resources,
)
binding.questionLabel.setTextColor(color)
binding.questionLabel.text = state.question

View File

@@ -40,7 +40,7 @@ class SettingsActivity : AppCompatActivity() {
toolbar = binding.toolbar,
title = resources.getString(R.string.settings),
color = PaletteColor(11),
theme = themeSwitcher.currentTheme
theme = themeSwitcher.currentTheme,
)
setContentView(binding.root)
}

View File

@@ -32,7 +32,7 @@ class EditSettingActivity : AppCompatActivity() {
val habits = app.component.habitList.getFiltered(
HabitMatcher(
isArchivedAllowed = false,
isCompletedAllowed = true
isCompletedAllowed = true,
)
)
AndroidThemeSwitcher(this, app.component.preferences).apply()
@@ -43,7 +43,7 @@ class EditSettingActivity : AppCompatActivity() {
context = this,
habitList = app.component.habitList,
onSave = controller::onSave,
args = args
args = args,
)
setContentView(view)
}

View File

@@ -54,7 +54,7 @@ class EditSettingRootView(
title = resources.getString(R.string.app_name),
color = PaletteColor(11),
displayHomeAsUpEnabled = false,
theme = currentTheme()
theme = currentTheme(),
)
populateHabitSpinner()
binding.habitSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
@@ -69,7 +69,7 @@ class EditSettingRootView(
val habit = habitList.getByPosition(binding.habitSpinner.selectedItemPosition)
val action = mapSpinnerPositionToAction(
isNumerical = habit.isNumerical,
itemPosition = binding.actionSpinner.selectedItemPosition
itemPosition = binding.actionSpinner.selectedItemPosition,
)
onSave(habit, action)
}

View File

@@ -27,34 +27,22 @@ class AndroidCursor(private val cursor: android.database.Cursor) : Cursor {
override fun moveToNext() = cursor.moveToNext()
override fun getInt(index: Int): Int? {
return if (cursor.isNull(index)) {
null
} else {
cursor.getInt(index)
}
return if (cursor.isNull(index)) null
else cursor.getInt(index)
}
override fun getLong(index: Int): Long? {
return if (cursor.isNull(index)) {
null
} else {
cursor.getLong(index)
}
return if (cursor.isNull(index)) null
else cursor.getLong(index)
}
override fun getDouble(index: Int): Double? {
return if (cursor.isNull(index)) {
null
} else {
cursor.getDouble(index)
}
return if (cursor.isNull(index)) null
else cursor.getDouble(index)
}
override fun getString(index: Int): String? {
return if (cursor.isNull(index)) {
null
} else {
cursor.getString(index)
}
return if (cursor.isNull(index)) null
else cursor.getString(index)
}
}

View File

@@ -26,7 +26,7 @@ import java.io.File
class AndroidDatabase(
private val db: SQLiteDatabase,
override val file: File?
override val file: File?,
) : Database {
override fun beginTransaction() = db.beginTransaction()
@@ -45,7 +45,7 @@ class AndroidDatabase(
tableName: String,
values: Map<String, Any?>,
where: String,
vararg params: String
vararg params: String,
): Int {
val contValues = mapToContentValues(values)
return db.update(tableName, contValues, where, params)
@@ -59,7 +59,7 @@ class AndroidDatabase(
override fun delete(
tableName: String,
where: String,
vararg params: String
vararg params: String,
) {
db.delete(tableName, where, params)
}

View File

@@ -30,7 +30,7 @@ class AndroidDatabaseOpener @Inject constructor() : DatabaseOpener {
db = SQLiteDatabase.openDatabase(
file.absolutePath,
null,
SQLiteDatabase.OPEN_READWRITE
SQLiteDatabase.OPEN_READWRITE,
),
file = file
)

View File

@@ -25,6 +25,5 @@ import dagger.Provides
@Module
class ActivityContextModule(
@get:Provides
@get:ActivityContext
val context: Context
@get:ActivityContext val context: Context
)

View File

@@ -26,6 +26,5 @@ import dagger.Provides
class AppContextModule(
@get:Provides
@get:AppContext
@param:AppContext
val context: Context
@param:AppContext val context: Context
)

View File

@@ -53,9 +53,8 @@ class IntentParser
var timestamp = intent.getLongExtra("timestamp", today)
timestamp = DateUtils.getStartOfDay(timestamp)
if (timestamp < 0 || timestamp > today) {
if (timestamp < 0 || timestamp > today)
throw IllegalArgumentException("timestamp is not valid")
}
return Timestamp(timestamp)
}

View File

@@ -96,6 +96,7 @@ class AndroidNotificationTray
timestamp: Timestamp,
disableSound: Boolean = false
): Notification {
val addRepetitionAction = Action(
R.drawable.ic_action_check,
context.getString(R.string.yes),
@@ -145,9 +146,8 @@ class AndroidNotificationTray
.addAction(removeRepetitionAction)
}
if (!disableSound) {
if (!disableSound)
builder.setSound(ringtoneManager.getURI())
}
if (SDK_INT < Build.VERSION_CODES.S) {
val snoozeAction = Action(

View File

@@ -61,9 +61,8 @@ class RingtoneManager
"pref_ringtone_uri",
defaultRingtoneUri.toString()
)!!
if (prefRingtoneUri.isNotEmpty()) {
if (prefRingtoneUri.isNotEmpty())
ringtoneUri = Uri.parse(prefRingtoneUri)
}
return ringtoneUri
}

View File

@@ -103,9 +103,7 @@ class SnoozeDelayPickerActivity : FragmentActivity(), OnItemClickListener {
if (snoozeValues[position] >= 0) {
reminderController!!.onSnoozeDelayPicked(habit!!, snoozeValues[position])
finish()
} else {
showTimePicker()
}
} else showTimePicker()
}
override fun finish() {

View File

@@ -27,7 +27,7 @@ import javax.inject.Inject
class ExportDBTaskFactory
@Inject constructor(
@AppContext private val context: Context,
private val system: AndroidDirFinder
private val system: AndroidDirFinder,
) {
fun create(listener: ExportDBTask.Listener) = ExportDBTask(context, system, listener)
}

View File

@@ -27,7 +27,7 @@ import javax.inject.Inject
class ImportDataTaskFactory
@Inject constructor(
private val importer: GenericImporter,
private val modelFactory: ModelFactory
private val modelFactory: ModelFactory,
) {
fun create(file: File, listener: ImportDataTask.Listener) =
ImportDataTask(importer, modelFactory, file, listener)

View File

@@ -24,7 +24,6 @@ import org.jetbrains.annotations.Contract
object AttributeSetUtils {
const val ISORON_NAMESPACE = "http://isoron.org/android"
@JvmStatic
fun getAttribute(
context: Context,

View File

@@ -33,7 +33,6 @@ import java.text.SimpleDateFormat
object DatabaseUtils {
private var opener: HabitsDatabaseOpener? = null
@JvmStatic
fun getDatabaseFile(context: Context): File {
val databaseFilename = databaseFilename

View File

@@ -6,24 +6,16 @@ import androidx.fragment.app.FragmentManager
import java.lang.ref.WeakReference
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() {
dismissCurrentDialog()
currentDialog.get()?.dismiss()
currentDialog = WeakReference(this)
show()
}
fun DialogFragment.dismissCurrentAndShow(fragmentManager: FragmentManager, tag: String) {
dismissCurrentDialog()
currentDialogFragment = WeakReference(this)
currentDialog.get()?.dismiss()
show(fragmentManager, tag)
fragmentManager.executePendingTransactions()
currentDialog = WeakReference(this.dialog)
}

View File

@@ -61,6 +61,7 @@ fun RelativeLayout.addBelow(
height: Int = WRAP_CONTENT,
applyCustomRules: (params: RelativeLayout.LayoutParams) -> Unit = {}
) {
view.layoutParams = RelativeLayout.LayoutParams(width, height).apply {
addRule(BELOW, subject.id)
applyCustomRules(this)
@@ -74,6 +75,7 @@ fun RelativeLayout.addAtBottom(
width: Int = MATCH_PARENT,
height: Int = WRAP_CONTENT
) {
view.layoutParams = RelativeLayout.LayoutParams(width, height).apply {
addRule(ALIGN_PARENT_BOTTOM)
}
@@ -86,6 +88,7 @@ fun RelativeLayout.addAtTop(
width: Int = MATCH_PARENT,
height: Int = WRAP_CONTENT
) {
view.layoutParams = RelativeLayout.LayoutParams(width, height).apply {
addRule(ALIGN_PARENT_TOP)
}
@@ -156,7 +159,7 @@ fun Activity.restartWithFade(cls: Class<*>?) {
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
startActivity(Intent(this, cls))
},
500
500,
) // HACK: Let the menu disappear first
}
@@ -165,7 +168,7 @@ fun View.setupToolbar(
title: String,
color: PaletteColor,
theme: Theme,
displayHomeAsUpEnabled: Boolean = true
displayHomeAsUpEnabled: Boolean = true,
) {
toolbar.elevation = InterfaceUtils.dpToPixels(context, 2f)
val res = StyledResources(context)

View File

@@ -147,12 +147,11 @@ abstract class BaseWidget(val context: Context, val id: Int, val stacked: Boolea
protected val preferedBackgroundAlpha: Int
get() {
return if (stacked) {
return if (stacked)
255
} else {
else
prefs.widgetOpacity
}
}
init {
val app = context.applicationContext as HabitsApplication

View File

@@ -33,7 +33,7 @@ open class CheckmarkWidget(
context: Context,
widgetId: Int,
protected val habit: Habit,
stacked: Boolean = false
stacked: Boolean = false,
) : BaseWidget(context, widgetId, stacked) {
override val defaultHeight: Int = 125
@@ -41,7 +41,7 @@ open class CheckmarkWidget(
override fun getOnClickPendingIntent(context: Context): PendingIntent? {
return if (habit.isNumerical) {
pendingIntentFactory.showNumberPicker(habit, DateUtils.getTodayWithOffset())
pendingIntentFactory.showNumberPicker(habit, DateUtils.getToday())
} else {
pendingIntentFactory.toggleCheckmark(habit, null)
}

View File

@@ -23,10 +23,7 @@ import android.content.Context
class CheckmarkWidgetProvider : BaseWidgetProvider() {
override fun getWidgetFromId(context: Context, id: Int): BaseWidget {
val habits = getHabitsFromWidgetId(id)
return if (habits.size == 1) {
CheckmarkWidget(context, id, habits[0])
} else {
StackWidget(context, id, StackWidgetType.CHECKMARK, habits)
}
return if (habits.size == 1) CheckmarkWidget(context, id, habits[0])
else StackWidget(context, id, StackWidgetType.CHECKMARK, habits)
}
}

View File

@@ -27,7 +27,7 @@ import org.isoron.uhabits.widgets.views.EmptyWidgetView
class EmptyWidget(
context: Context,
widgetId: Int,
stacked: Boolean = false
stacked: Boolean = false,
) : BaseWidget(context, widgetId, stacked) {
override val defaultHeight: Int = 200
override val defaultWidth: Int = 200

View File

@@ -33,7 +33,7 @@ class FrequencyWidget(
widgetId: Int,
private val habit: Habit,
private val firstWeekday: Int,
stacked: Boolean = false
stacked: Boolean = false,
) : BaseWidget(context, widgetId, stacked) {
override val defaultHeight: Int = 200
override val defaultWidth: Int = 200

View File

@@ -24,15 +24,12 @@ import android.content.Context
class FrequencyWidgetProvider : BaseWidgetProvider() {
override fun getWidgetFromId(context: Context, id: Int): BaseWidget {
val habits = getHabitsFromWidgetId(id)
return if (habits.size == 1) {
FrequencyWidget(
return if (habits.size == 1) FrequencyWidget(
context,
id,
habits[0],
preferences.firstWeekdayInt
)
} else {
StackWidget(context, id, StackWidgetType.FREQUENCY, habits)
}
else StackWidget(context, id, StackWidgetType.FREQUENCY, habits)
}
}

View File

@@ -36,7 +36,7 @@ class HistoryWidget(
context: Context,
id: Int,
private val habit: Habit,
stacked: Boolean = false
stacked: Boolean = false,
) : BaseWidget(context, id, stacked) {
override val defaultHeight: Int = 250
@@ -53,7 +53,7 @@ class HistoryWidget(
val model = HistoryCardPresenter.buildState(
habit = habit,
firstWeekday = prefs.firstWeekday,
theme = WidgetTheme()
theme = WidgetTheme(),
)
(widgetView.dataView as AndroidDataView).apply {
val historyChart = (this.view as HistoryChart)
@@ -75,7 +75,7 @@ class HistoryWidget(
firstWeekday = prefs.firstWeekday,
series = listOf(),
defaultSquare = HistoryChart.Square.OFF,
notesIndicators = listOf()
notesIndicators = listOf(),
)
}
).apply {

View File

@@ -23,14 +23,11 @@ import android.content.Context
class HistoryWidgetProvider : BaseWidgetProvider() {
override fun getWidgetFromId(context: Context, id: Int): BaseWidget {
val habits = getHabitsFromWidgetId(id)
return if (habits.size == 1) {
HistoryWidget(
return if (habits.size == 1) HistoryWidget(
context,
id,
habits[0]
)
} else {
StackWidget(context, id, StackWidgetType.HISTORY, habits)
}
else StackWidget(context, id, StackWidgetType.HISTORY, habits)
}
}

View File

@@ -33,7 +33,7 @@ class ScoreWidget(
context: Context,
id: Int,
private val habit: Habit,
stacked: Boolean = false
stacked: Boolean = false,
) : BaseWidget(context, id, stacked) {
override val defaultHeight: Int = 300
override val defaultWidth: Int = 300
@@ -46,7 +46,7 @@ class ScoreWidget(
habit = habit,
firstWeekday = prefs.firstWeekdayInt,
spinnerPosition = prefs.scoreCardSpinnerPosition,
theme = WidgetTheme()
theme = WidgetTheme(),
)
val widgetView = view as GraphWidgetView
widgetView.setBackgroundAlpha(preferedBackgroundAlpha)

View File

@@ -23,10 +23,7 @@ import android.content.Context
class ScoreWidgetProvider : BaseWidgetProvider() {
override fun getWidgetFromId(context: Context, id: Int): BaseWidget {
val habits = getHabitsFromWidgetId(id)
return if (habits.size == 1) {
ScoreWidget(context, id, habits[0])
} else {
StackWidget(context, id, StackWidgetType.SCORE, habits)
}
return if (habits.size == 1) ScoreWidget(context, id, habits[0])
else StackWidget(context, id, StackWidgetType.SCORE, habits)
}
}

View File

@@ -34,7 +34,7 @@ class StackWidget(
widgetId: Int,
private val widgetType: StackWidgetType,
private val habits: List<Habit>,
stacked: Boolean = true
stacked: Boolean = true,
) : BaseWidget(context, widgetId, stacked) {
override val defaultHeight: Int = 0
override val defaultWidth: Int = 0

View File

@@ -33,7 +33,7 @@ import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitNotFoundException
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.utils.DateUtils.Companion.getTodayWithOffset
import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday
import org.isoron.uhabits.intents.IntentFactory
import org.isoron.uhabits.intents.PendingIntentFactory
import org.isoron.uhabits.utils.InterfaceUtils.dpToPixels
@@ -101,7 +101,7 @@ internal class StackRemoteViewsFactory(private val context: Context, intent: Int
val landscapeViews = widget.landscapeRemoteViews
val portraitViews = widget.portraitRemoteViews
val factory = PendingIntentFactory(context, IntentFactory())
val intent = StackWidgetType.getIntentFillIn(factory, widgetType, h, habits, getTodayWithOffset())
val intent = StackWidgetType.getIntentFillIn(factory, widgetType, h, habits, getToday())
landscapeViews.setOnClickFillInIntent(R.id.button, intent)
portraitViews.setOnClickFillInIntent(R.id.button, intent)
val remoteViews = RemoteViews(landscapeViews, portraitViews)

View File

@@ -34,7 +34,7 @@ class StreakWidget(
context: Context,
id: Int,
private val habit: Habit,
stacked: Boolean = false
stacked: Boolean = false,
) : BaseWidget(context, id, stacked) {
override val defaultHeight: Int = 200
override val defaultWidth: Int = 200

View File

@@ -23,10 +23,7 @@ import android.content.Context
class StreakWidgetProvider : BaseWidgetProvider() {
override fun getWidgetFromId(context: Context, id: Int): BaseWidget {
val habits = getHabitsFromWidgetId(id)
return if (habits.size == 1) {
StreakWidget(context, id, habits[0])
} else {
StackWidget(context, id, StackWidgetType.STREAKS, habits)
}
return if (habits.size == 1) StreakWidget(context, id, habits[0])
else StackWidget(context, id, StackWidgetType.STREAKS, habits)
}
}

View File

@@ -37,7 +37,7 @@ class TargetWidget(
context: Context,
id: Int,
private val habit: Habit,
stacked: Boolean = false
stacked: Boolean = false,
) : BaseWidget(context, id, stacked) {
override val defaultHeight: Int = 200
override val defaultWidth: Int = 200
@@ -53,7 +53,7 @@ class TargetWidget(
val data = TargetCardPresenter.buildState(
habit = habit,
firstWeekday = prefs.firstWeekdayInt,
theme = WidgetTheme()
theme = WidgetTheme(),
)
chart.setColor(WidgetTheme().color(habit.color).toInt())
chart.setTargets(data.targets)

View File

@@ -23,10 +23,7 @@ import android.content.Context
class TargetWidgetProvider : BaseWidgetProvider() {
override fun getWidgetFromId(context: Context, id: Int): BaseWidget {
val habits = getHabitsFromWidgetId(id)
return if (habits.size == 1) {
TargetWidget(context, id, habits[0])
} else {
StackWidget(context, id, StackWidgetType.TARGET, habits)
}
return if (habits.size == 1) TargetWidget(context, id, habits[0])
else StackWidget(context, id, StackWidgetType.TARGET, habits)
}
}

View File

@@ -24,6 +24,7 @@ import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID
import android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID
import android.content.Intent
import android.os.Bundle
import android.widget.AbsListView.CHOICE_MODE_MULTIPLE
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.ListView
@@ -33,6 +34,7 @@ import org.isoron.uhabits.R
import org.isoron.uhabits.activities.AndroidThemeSwitcher
import org.isoron.uhabits.core.preferences.WidgetPreferences
import org.isoron.uhabits.widgets.WidgetUpdater
import java.util.ArrayList
class BooleanHabitPickerDialog : HabitPickerDialog() {
override fun shouldHideNumerical() = true
@@ -86,12 +88,20 @@ open class HabitPickerDialog : Activity() {
with(listView) {
adapter = ArrayAdapter(
context,
android.R.layout.simple_list_item_1,
android.R.layout.simple_list_item_multiple_choice,
habitNames
)
setOnItemClickListener { parent, view, position, id ->
confirm(mutableListOf(habitIds[position]))
choiceMode = CHOICE_MODE_MULTIPLE
itemsCanFocus = false
}
saveButton.setOnClickListener {
val selectedIds = mutableListOf<Long>()
for (i in 0..listView.count) {
if (listView.isItemChecked(i)) {
selectedIds.add(habitIds[i])
}
}
confirm(selectedIds)
}
}

View File

@@ -66,21 +66,23 @@ class CheckmarkWidgetView : HabitWidgetView {
val res = StyledResources(context)
val bgColor: Int
val fgColor: Int
setShadowAlpha(0x4f)
when (entryState) {
YES_MANUAL, SKIP -> {
bgColor = activeColor
fgColor = res.getColor(R.attr.contrast0)
setShadowAlpha(0x4f)
backgroundPaint!!.color = bgColor
frame!!.setBackgroundDrawable(background)
}
YES_AUTO, NO, UNKNOWN -> {
bgColor = res.getColor(R.attr.cardBgColor)
fgColor = res.getColor(R.attr.contrast60)
setShadowAlpha(0x00)
}
else -> {
bgColor = res.getColor(R.attr.cardBgColor)
fgColor = res.getColor(R.attr.contrast60)
setShadowAlpha(0x00)
}
}
ring.setPercentage(percentage)
@@ -96,8 +98,7 @@ class CheckmarkWidgetView : HabitWidgetView {
private val text: String
get() = if (isNumerical) {
(max(0, entryValue) / 1000.0).toShortString()
} else {
when (entryState) {
} else when (entryState) {
YES_MANUAL, YES_AUTO -> resources.getString(R.string.fa_check)
SKIP -> resources.getString(R.string.fa_skipped)
UNKNOWN -> {
@@ -113,7 +114,6 @@ class CheckmarkWidgetView : HabitWidgetView {
NO -> resources.getString(R.string.fa_times)
else -> resources.getString(R.string.fa_times)
}
}
override val innerLayoutId: Int
get() = R.layout.widget_checkmark
@@ -126,7 +126,7 @@ class CheckmarkWidgetView : HabitWidgetView {
} else {
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)
if (isNumerical) {
ring.setTextSize(textSize * 0.9f)
@@ -141,8 +141,7 @@ class CheckmarkWidgetView : HabitWidgetView {
}
private fun init() {
val appComponent: HabitsApplicationComponent =
(context.applicationContext as HabitsApplication).component
val appComponent: HabitsApplicationComponent = (context.applicationContext as HabitsApplication).component
preferences = appComponent.preferences
ring = findViewById<View>(R.id.scoreRing) as RingView
label = findViewById<View>(R.id.label) as TextView

Some files were not shown because too many files have changed in this diff Show More