Reformat all Kotlin files with ktlint; add check to build script

pull/699/head
Alinson S. Xavier 5 years ago
parent 9a5263e508
commit 9087025418

@ -2,6 +2,9 @@ buildscript {
repositories {
google()
jcenter()
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
@ -9,6 +12,7 @@ buildscript {
classpath "com.neenbedankt.gradle.plugins:android-apt:1.8"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$KOTLIN_VERSION"
classpath "org.ajoberstar:grgit:1.5.0"
classpath "org.jlleitschuh.gradle:ktlint-gradle:9.4.1"
}
}
@ -18,4 +22,5 @@ allprojects {
maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
jcenter()
}
apply plugin: "org.jlleitschuh.gradle.ktlint"
}

@ -67,6 +67,11 @@ run_adb_as_root() {
$ADB root
}
lint() {
log_info "Running ktlint..."
$GRADLE ktlintCheck || fail
}
build_apk() {
log_info "Removing old APKs..."
rm -vf build/*.apk
@ -258,6 +263,7 @@ case "$1" in
build)
shift; parse_opts $*
lint
build_apk
build_instrumentation_apk
run_jvm_tests

@ -19,12 +19,22 @@
package org.isoron.uhabits
import dagger.*
import org.isoron.uhabits.activities.habits.list.*
import org.isoron.uhabits.activities.habits.list.views.*
import org.isoron.uhabits.core.ui.screens.habits.list.*
import org.isoron.uhabits.inject.*
import org.mockito.Mockito.*
import dagger.Component
import dagger.Module
import dagger.Provides
import org.isoron.uhabits.activities.habits.list.ListHabitsModule
import org.isoron.uhabits.activities.habits.list.views.CheckmarkButtonViewFactory
import org.isoron.uhabits.activities.habits.list.views.CheckmarkPanelViewFactory
import org.isoron.uhabits.activities.habits.list.views.HabitCardViewFactory
import org.isoron.uhabits.activities.habits.list.views.NumberButtonViewFactory
import org.isoron.uhabits.activities.habits.list.views.NumberPanelViewFactory
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
import org.isoron.uhabits.inject.ActivityContextModule
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.Mockito.mock
@Module
class TestModule {
@ -32,13 +42,16 @@ class TestModule {
}
@ActivityScope
@Component(modules = arrayOf(
@Component(
modules = arrayOf(
ActivityContextModule::class,
HabitsActivityModule::class,
ListHabitsModule::class,
HabitModule::class,
TestModule::class
), dependencies = arrayOf(HabitsApplicationComponent::class))
),
dependencies = arrayOf(HabitsApplicationComponent::class)
)
interface HabitsActivityTestComponent {
fun getCheckmarkPanelViewFactory(): CheckmarkPanelViewFactory
fun getHabitCardViewFactory(): HabitCardViewFactory

@ -19,11 +19,20 @@
package org.isoron.uhabits.acceptance
import androidx.test.filters.*
import org.isoron.uhabits.*
import org.isoron.uhabits.acceptance.steps.*
import org.isoron.uhabits.acceptance.steps.CommonSteps.*
import org.junit.*
import androidx.test.filters.LargeTest
import org.isoron.uhabits.BaseUserInterfaceTest
import org.isoron.uhabits.acceptance.steps.CommonSteps.clickText
import org.isoron.uhabits.acceptance.steps.CommonSteps.launchApp
import org.isoron.uhabits.acceptance.steps.CommonSteps.longClickText
import org.isoron.uhabits.acceptance.steps.CommonSteps.verifyDisplaysText
import org.isoron.uhabits.acceptance.steps.CommonSteps.verifyDoesNotDisplayText
import org.isoron.uhabits.acceptance.steps.ListHabitsSteps
import org.isoron.uhabits.acceptance.steps.clearBackupFolder
import org.isoron.uhabits.acceptance.steps.clearDownloadFolder
import org.isoron.uhabits.acceptance.steps.copyBackupToDownloadFolder
import org.isoron.uhabits.acceptance.steps.exportFullBackup
import org.isoron.uhabits.acceptance.steps.importBackupFromDownloadFolder
import org.junit.Test
@LargeTest
class BackupTest : BaseUserInterfaceTest() {

@ -19,10 +19,11 @@
package org.isoron.uhabits.acceptance.steps
import androidx.test.uiautomator.*
import org.isoron.uhabits.acceptance.steps.CommonSteps.*
import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.*
import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.MenuItem.*
import androidx.test.uiautomator.UiSelector
import org.isoron.uhabits.acceptance.steps.CommonSteps.clickText
import org.isoron.uhabits.acceptance.steps.CommonSteps.device
import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.MenuItem.SETTINGS
import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.clickMenu
const val BACKUP_FOLDER = "/sdcard/Android/data/org.isoron.uhabits/files/Backups/"
const val DOWNLOAD_FOLDER = "/sdcard/Download/"

@ -20,12 +20,13 @@
package org.isoron.uhabits.activities.habits.list.views
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.utils.*
import org.junit.*
import org.junit.runner.*
import androidx.test.filters.MediumTest
import org.isoron.uhabits.BaseViewTest
import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.utils.PaletteUtils
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@MediumTest

@ -19,18 +19,20 @@
package org.isoron.uhabits.activities.habits.list.views
import androidx.test.ext.junit.runners.*
import androidx.test.filters.*
import org.hamcrest.CoreMatchers.*
import org.hamcrest.MatcherAssert.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.models.*
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.isoron.uhabits.BaseViewTest
import org.isoron.uhabits.core.models.Entry.Companion.NO
import org.isoron.uhabits.core.models.Entry.Companion.YES_AUTO
import org.isoron.uhabits.core.models.Entry.Companion.YES_MANUAL
import org.isoron.uhabits.utils.*
import org.junit.*
import org.junit.runner.*
import org.isoron.uhabits.core.models.Timestamp
import org.isoron.uhabits.utils.PaletteUtils
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@MediumTest
@ -44,13 +46,15 @@ class EntryPanelViewTest : BaseViewTest() {
super.setUp()
prefs.isCheckmarkSequenceReversed = false
val checkmarks = intArrayOf(YES_MANUAL,
val checkmarks = intArrayOf(
YES_MANUAL,
YES_MANUAL,
YES_AUTO,
NO,
NO,
NO,
YES_MANUAL)
YES_MANUAL
)
view = component.getCheckmarkPanelViewFactory().create().apply {
values = checkmarks

@ -19,22 +19,24 @@
package org.isoron.uhabits.activities.habits.list.views
import androidx.test.ext.junit.runners.*
import androidx.test.filters.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.utils.*
import org.junit.*
import org.junit.runner.*
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import org.isoron.uhabits.BaseViewTest
import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.utils.DateUtils
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@MediumTest
class HabitCardViewTest : BaseViewTest() {
val PATH = "habits/list/HabitCardView"
lateinit private var view: HabitCardView
lateinit private var habit1: Habit
lateinit private var habit2: Habit
private lateinit var view: HabitCardView
private lateinit var habit1: Habit
private lateinit var habit2: Habit
override fun setUp() {
super.setUp()

@ -20,14 +20,14 @@
package org.isoron.uhabits.activities.habits.list.views
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.*
import androidx.test.runner.*
import org.hamcrest.CoreMatchers.*
import org.hamcrest.MatcherAssert.*
import org.isoron.uhabits.*
import org.isoron.uhabits.utils.*
import org.junit.*
import org.junit.runner.*
import androidx.test.filters.MediumTest
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.isoron.uhabits.BaseViewTest
import org.isoron.uhabits.utils.PaletteUtils
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@MediumTest

@ -20,15 +20,16 @@
package org.isoron.uhabits.activities.habits.list.views
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.*
import androidx.test.runner.*
import org.hamcrest.CoreMatchers.*
import org.hamcrest.MatcherAssert.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.utils.*
import org.junit.*
import org.junit.runner.*
import androidx.test.filters.MediumTest
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.isoron.uhabits.BaseViewTest
import org.isoron.uhabits.core.models.Timestamp
import org.isoron.uhabits.utils.PaletteUtils
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@MediumTest
@ -42,8 +43,14 @@ class NumberPanelViewTest : BaseViewTest() {
super.setUp()
prefs.isCheckmarkSequenceReversed = false
val checkmarks = doubleArrayOf(1400.0, 5300.0, 0.0,
14600.0, 2500.0, 45000.0)
val checkmarks = doubleArrayOf(
1400.0,
5300.0,
0.0,
14600.0,
2500.0,
45000.0
)
view = component.getNumberPanelViewFactory().create().apply {
values = checkmarks

@ -18,12 +18,15 @@
*/
package org.isoron.uhabits.activities.habits.show.views
import android.view.*
import androidx.test.ext.junit.runners.*
import androidx.test.filters.*
import org.isoron.uhabits.*
import org.junit.*
import org.junit.runner.*
import android.view.LayoutInflater
import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import org.isoron.uhabits.BaseViewTest
import org.isoron.uhabits.R
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@MediumTest

@ -18,12 +18,15 @@
*/
package org.isoron.uhabits.activities.habits.show.views
import android.view.*
import androidx.test.ext.junit.runners.*
import androidx.test.filters.*
import org.isoron.uhabits.*
import org.junit.*
import org.junit.runner.*
import android.view.LayoutInflater
import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import org.isoron.uhabits.BaseViewTest
import org.isoron.uhabits.R
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@MediumTest
@ -39,9 +42,13 @@ class HistoryCardTest : BaseViewTest() {
.from(targetContext)
.inflate(R.layout.show_habit, null)
.findViewById<View>(R.id.historyCard) as HistoryCard
view.update(HistoryCardPresenter(habit = habit,
view.update(
HistoryCardPresenter(
habit = habit,
firstWeekday = 1,
isSkipEnabled = false).present())
isSkipEnabled = false
).present()
)
measureView(view, 800f, 600f)
}

@ -18,15 +18,17 @@
*/
package org.isoron.uhabits.activities.habits.show.views
import android.view.*
import android.view.View.*
import androidx.test.ext.junit.runners.*
import androidx.test.filters.*
import org.hamcrest.Matchers.*
import org.isoron.uhabits.*
import org.junit.*
import org.junit.Assert.*
import org.junit.runner.*
import android.view.LayoutInflater
import android.view.View.GONE
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import org.hamcrest.Matchers.equalTo
import org.isoron.uhabits.BaseViewTest
import org.isoron.uhabits.R
import org.junit.Assert.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@MediumTest

@ -18,13 +18,16 @@
*/
package org.isoron.uhabits.activities.habits.show.views
import android.view.*
import androidx.test.ext.junit.runners.*
import androidx.test.filters.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.models.*
import org.junit.*
import org.junit.runner.*
import android.view.LayoutInflater
import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import org.isoron.uhabits.BaseViewTest
import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.PaletteColor
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@MediumTest
@ -39,13 +42,15 @@ class OverviewCardViewTest : BaseViewTest() {
.from(targetContext)
.inflate(R.layout.show_habit, null)
.findViewById<View>(R.id.overviewCard) as OverviewCardView
view.update(OverviewCardViewModel(
view.update(
OverviewCardViewModel(
scoreToday = 0.74f,
scoreMonthDiff = 0.23f,
scoreYearDiff = 0.74f,
totalCount = 44,
color = PaletteColor(7),
))
)
)
measureView(view, 800f, 300f)
}

@ -18,12 +18,15 @@
*/
package org.isoron.uhabits.activities.habits.show.views
import android.view.*
import androidx.test.ext.junit.runners.*
import androidx.test.filters.*
import org.isoron.uhabits.*
import org.junit.*
import org.junit.runner.*
import android.view.LayoutInflater
import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import org.isoron.uhabits.BaseViewTest
import org.isoron.uhabits.R
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@MediumTest

@ -18,17 +18,15 @@
*/
package org.isoron.uhabits.activities.habits.show.views
import android.view.*
import androidx.test.ext.junit.runners.*
import org.junit.runner.RunWith
import android.view.LayoutInflater
import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import org.isoron.uhabits.BaseViewTest
import org.isoron.uhabits.activities.habits.show.views.StreakCardView
import org.isoron.uhabits.R
import org.isoron.uhabits.activities.habits.show.views.StreakCardViewTest
import org.isoron.uhabits.core.models.*
import org.junit.*
import java.lang.Exception
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@MediumTest
@ -44,10 +42,12 @@ class StreakCardViewTest : BaseViewTest() {
.from(targetContext)
.inflate(R.layout.show_habit, null)
.findViewById<View>(R.id.streakCard) as StreakCardView
view.update(StreakCardViewModel(
view.update(
StreakCardViewModel(
bestStreaks = habit.streaks.getBest(10),
color = habit.color,
))
)
)
measureView(view, 800f, 600f)
}

@ -18,13 +18,15 @@
*/
package org.isoron.uhabits.activities.habits.show.views
import android.view.*
import androidx.test.ext.junit.runners.*
import androidx.test.filters.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.models.*
import org.junit.*
import org.junit.runner.*
import android.view.LayoutInflater
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import org.isoron.uhabits.BaseViewTest
import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.PaletteColor
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@MediumTest
@ -39,14 +41,16 @@ class SubtitleCardViewTest : BaseViewTest() {
.from(targetContext)
.inflate(R.layout.show_habit, null)
.findViewById(R.id.subtitleCard)
view.update(SubtitleCardViewModel(
view.update(
SubtitleCardViewModel(
color = PaletteColor(7),
frequencyText = "3 times in 7 days",
isNumerical = false,
question = "Did you meditate this morning?",
reminderText = "8:30 AM",
targetText = "",
))
)
)
measureView(view, 800f, 200f)
}

@ -19,10 +19,12 @@
package org.isoron.uhabits.database
import org.isoron.uhabits.*
import org.isoron.uhabits.core.utils.*
import org.junit.*
import java.io.*
import org.isoron.uhabits.AndroidDirFinder
import org.isoron.uhabits.BaseAndroidTest
import org.isoron.uhabits.core.utils.DateUtils
import org.junit.Test
import java.io.File
import java.io.FileOutputStream
class AutoBackupTest : BaseAndroidTest() {
@Test
@ -32,7 +34,7 @@ class AutoBackupTest : BaseAndroidTest() {
createTestFiles(basedir, 30)
val autoBackup = AutoBackup(targetContext)
autoBackup.run(keep=5)
autoBackup.run(keep = 5)
for (k in 1..25) assertDoesNotExist("${basedir.path}/test-$k.txt")
for (k in 26..30) assertExists("${basedir.path}/test-$k.txt")

@ -18,18 +18,27 @@
*/
package org.isoron.uhabits.intents
import android.content.ContentUris.*
import androidx.test.ext.junit.runners.*
import androidx.test.filters.*
import org.hamcrest.Matchers.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.reminders.ReminderScheduler.SchedulerResult.*
import org.isoron.uhabits.receivers.*
import org.junit.*
import org.junit.Assert.*
import org.junit.runner.*
import java.util.*
import java.util.Calendar.*
import android.content.ContentUris.parseId
import androidx.test.filters.MediumTest
import org.hamcrest.Matchers.equalTo
import org.isoron.uhabits.BaseAndroidTest
import org.isoron.uhabits.core.reminders.ReminderScheduler.SchedulerResult.OK
import org.isoron.uhabits.receivers.ReminderReceiver
import org.isoron.uhabits.receivers.WidgetReceiver
import org.junit.After
import org.junit.Assert.assertThat
import org.junit.Before
import org.junit.Test
import java.util.Calendar.DAY_OF_MONTH
import java.util.Calendar.DECEMBER
import java.util.Calendar.HOUR_OF_DAY
import java.util.Calendar.JUNE
import java.util.Calendar.MAY
import java.util.Calendar.MINUTE
import java.util.Calendar.MONTH
import java.util.Calendar.YEAR
import java.util.GregorianCalendar
import java.util.TimeZone
class IntentSchedulerTest : BaseAndroidTest() {

@ -19,15 +19,23 @@
package org.isoron.uhabits.regression
import androidx.test.filters.*
import org.isoron.uhabits.*
import org.isoron.uhabits.acceptance.steps.CommonSteps.*
import org.isoron.uhabits.acceptance.steps.CommonSteps.Screen.*
import org.isoron.uhabits.acceptance.steps.EditHabitSteps.*
import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.*
import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.MenuItem.*
import org.junit.*
import androidx.test.filters.LargeTest
import org.isoron.uhabits.BaseUserInterfaceTest
import org.isoron.uhabits.acceptance.steps.CommonSteps.Screen.EDIT_HABIT
import org.isoron.uhabits.acceptance.steps.CommonSteps.Screen.LIST_HABITS
import org.isoron.uhabits.acceptance.steps.CommonSteps.Screen.SELECT_HABIT_TYPE
import org.isoron.uhabits.acceptance.steps.CommonSteps.clickText
import org.isoron.uhabits.acceptance.steps.CommonSteps.launchApp
import org.isoron.uhabits.acceptance.steps.CommonSteps.longClickText
import org.isoron.uhabits.acceptance.steps.CommonSteps.verifyDisplaysText
import org.isoron.uhabits.acceptance.steps.CommonSteps.verifyShowsScreen
import org.isoron.uhabits.acceptance.steps.EditHabitSteps.clickSave
import org.isoron.uhabits.acceptance.steps.EditHabitSteps.typeName
import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.MenuItem.ADD
import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.MenuItem.DELETE
import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.clickMenu
import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.longPressCheckmarks
import org.junit.Test
@LargeTest
class ListHabitsRegressionTest : BaseUserInterfaceTest() {

@ -19,12 +19,12 @@
package org.isoron.uhabits.regression
import androidx.test.filters.*
import org.isoron.uhabits.*
import org.isoron.uhabits.acceptance.steps.CommonSteps.*
import org.isoron.uhabits.activities.about.*
import org.junit.*
import java.lang.Thread.*
import androidx.test.filters.LargeTest
import org.isoron.uhabits.BaseUserInterfaceTest
import org.isoron.uhabits.acceptance.steps.CommonSteps.launchApp
import org.isoron.uhabits.activities.about.AboutActivity
import org.junit.Test
import java.lang.Thread.sleep
@LargeTest
class SavedStateTest : BaseUserInterfaceTest() {

@ -19,18 +19,29 @@
package org.isoron.uhabits.sync
import androidx.test.filters.*
import com.fasterxml.jackson.databind.*
import io.ktor.client.*
import io.ktor.client.engine.mock.*
import io.ktor.client.features.json.*
import io.ktor.client.request.*
import io.ktor.http.*
import junit.framework.Assert.*
import kotlinx.coroutines.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.sync.*
import org.junit.*
import androidx.test.filters.MediumTest
import com.fasterxml.jackson.databind.ObjectMapper
import io.ktor.client.HttpClient
import io.ktor.client.engine.mock.MockEngine
import io.ktor.client.engine.mock.MockRequestHandleScope
import io.ktor.client.engine.mock.respond
import io.ktor.client.engine.mock.respondError
import io.ktor.client.engine.mock.respondOk
import io.ktor.client.features.json.JsonFeature
import io.ktor.client.request.HttpRequestData
import io.ktor.client.request.HttpResponseData
import io.ktor.http.HttpStatusCode
import io.ktor.http.fullPath
import io.ktor.http.headersOf
import kotlinx.coroutines.runBlocking
import org.isoron.uhabits.BaseAndroidTest
import org.isoron.uhabits.core.sync.AbstractSyncServer
import org.isoron.uhabits.core.sync.GetDataVersionResponse
import org.isoron.uhabits.core.sync.KeyNotFoundException
import org.isoron.uhabits.core.sync.RegisterReponse
import org.isoron.uhabits.core.sync.ServiceUnavailable
import org.isoron.uhabits.core.sync.SyncData
import org.junit.Test
@MediumTest
class RemoteSyncServerTest : BaseAndroidTest() {
@ -115,10 +126,12 @@ class RemoteSyncServerTest : BaseAndroidTest() {
return@runBlocking
}
private fun server(expectedPath: String,
private fun server(
expectedPath: String,
action: MockRequestHandleScope.(HttpRequestData) -> HttpResponseData
): AbstractSyncServer {
return RemoteSyncServer(httpClient = HttpClient(MockEngine) {
return RemoteSyncServer(
httpClient = HttpClient(MockEngine) {
install(JsonFeature)
engine {
addHandler { request ->
@ -128,10 +141,14 @@ class RemoteSyncServerTest : BaseAndroidTest() {
}
}
}
}, preferences = prefs)
},
preferences = prefs
)
}
private fun MockRequestHandleScope.respondWithJson(content: Any) =
respond(mapper.writeValueAsBytes(content),
headers = headersOf("Content-Type" to listOf("application/json")))
respond(
mapper.writeValueAsBytes(content),
headers = headersOf("Content-Type" to listOf("application/json"))
)
}

@ -18,14 +18,21 @@
*/
package org.isoron.uhabits
import android.content.*
import android.os.*
import android.view.*
import org.isoron.uhabits.inject.*
import java.io.*
import java.text.*
import java.util.*
import javax.inject.*
import android.content.Context
import android.os.Build
import android.os.Environment
import android.view.WindowManager
import org.isoron.uhabits.inject.AppContext
import java.io.BufferedReader
import java.io.File
import java.io.FileWriter
import java.io.IOException
import java.io.InputStreamReader
import java.text.SimpleDateFormat
import java.util.Date
import java.util.LinkedList
import java.util.Locale
import javax.inject.Inject
open class AndroidBugReporter @Inject constructor(@AppContext private val context: Context) {
@ -56,7 +63,7 @@ open class AndroidBugReporter @Inject constructor(@AppContext private val contex
var line: String?
while (true) {
line = bufferedReader.readLine()
if (line == null) break;
if (line == null) break
log.addLast(line)
if (log.size > maxLineCount) log.removeFirst()
}
@ -106,5 +113,4 @@ open class AndroidBugReporter @Inject constructor(@AppContext private val contex
appendln()
}
}
}

@ -20,7 +20,7 @@ package org.isoron.uhabits
import android.content.Context
import androidx.core.content.ContextCompat
import org.isoron.uhabits.inject.*
import org.isoron.uhabits.inject.AppContext
import org.isoron.uhabits.utils.FileUtils
import java.io.File
import javax.inject.Inject

@ -18,7 +18,7 @@
*/
package org.isoron.uhabits
import android.app.*
import android.app.Activity
class BaseExceptionHandler(private val activity: Activity) : Thread.UncaughtExceptionHandler {

@ -19,16 +19,19 @@
package org.isoron.uhabits
import android.app.*
import android.content.*
import org.isoron.uhabits.core.database.*
import org.isoron.uhabits.core.reminders.*
import org.isoron.uhabits.core.ui.*
import org.isoron.uhabits.core.utils.*
import org.isoron.uhabits.inject.*
import org.isoron.uhabits.utils.*
import org.isoron.uhabits.widgets.*
import java.io.*
import android.app.Application
import android.content.Context
import org.isoron.uhabits.core.database.UnsupportedDatabaseVersionException
import org.isoron.uhabits.core.reminders.ReminderScheduler
import org.isoron.uhabits.core.ui.NotificationTray
import org.isoron.uhabits.core.utils.DateUtils
import org.isoron.uhabits.inject.AppContextModule
import org.isoron.uhabits.inject.DaggerHabitsApplicationComponent
import org.isoron.uhabits.inject.HabitsApplicationComponent
import org.isoron.uhabits.inject.HabitsModule
import org.isoron.uhabits.utils.DatabaseUtils
import org.isoron.uhabits.widgets.WidgetUpdater
import java.io.File
/**
* The Android application for Loop Habit Tracker.

@ -19,7 +19,9 @@
package org.isoron.uhabits
import android.app.backup.*
import android.app.backup.BackupAgentHelper
import android.app.backup.FileBackupHelper
import android.app.backup.SharedPreferencesBackupHelper
/**
* An Android BackupAgentHelper customized for this application.

@ -21,12 +21,13 @@
package org.isoron.uhabits
import android.content.*
import android.database.sqlite.*
import org.isoron.uhabits.core.database.*
import org.isoron.uhabits.database.*
import java.io.*
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import org.isoron.uhabits.core.database.MigrationHelper
import org.isoron.uhabits.core.database.UnsupportedDatabaseVersionException
import org.isoron.uhabits.database.AndroidDatabase
import java.io.File
class HabitsDatabaseOpener(
context: Context,
@ -45,18 +46,22 @@ class HabitsDatabaseOpener(
db.disableWriteAheadLogging()
}
override fun onUpgrade(db: SQLiteDatabase,
override fun onUpgrade(
db: SQLiteDatabase,
oldVersion: Int,
newVersion: Int) {
newVersion: Int
) {
db.disableWriteAheadLogging()
if (db.version < 8) throw UnsupportedDatabaseVersionException()
val helper = MigrationHelper(AndroidDatabase(db, File(databaseFilename)))
helper.migrateTo(newVersion)
}
override fun onDowngrade(db: SQLiteDatabase,
override fun onDowngrade(
db: SQLiteDatabase,
oldVersion: Int,
newVersion: Int) {
newVersion: Int
) {
throw UnsupportedDatabaseVersionException()
}
}

@ -19,15 +19,17 @@
package org.isoron.uhabits.activities
import android.app.*
import android.content.*
import android.content.res.Configuration.*
import android.os.Build.VERSION.*
import androidx.core.content.*
import android.app.Activity
import android.content.Context
import android.content.res.Configuration.UI_MODE_NIGHT_MASK
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import android.os.Build.VERSION.SDK_INT
import androidx.core.content.ContextCompat
import org.isoron.uhabits.R
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.ui.*
import org.isoron.uhabits.inject.*
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.ui.ThemeSwitcher
import org.isoron.uhabits.inject.ActivityContext
import org.isoron.uhabits.inject.ActivityScope
@ActivityScope
class AndroidThemeSwitcher
@ -37,12 +39,12 @@ constructor(
) : ThemeSwitcher(preferences) {
override fun getSystemTheme(): Int {
if (SDK_INT < 29) return THEME_LIGHT;
if (SDK_INT < 29) return THEME_LIGHT
val uiMode = context.resources.configuration.uiMode
return if ((uiMode and UI_MODE_NIGHT_MASK) == UI_MODE_NIGHT_YES) {
THEME_DARK;
THEME_DARK
} else {
THEME_LIGHT;
THEME_LIGHT
}
}

@ -18,11 +18,11 @@
*/
package org.isoron.uhabits.activities
import org.isoron.uhabits.*
import org.isoron.uhabits.core.ui.screens.habits.list.*
import org.isoron.uhabits.core.ui.screens.habits.show.*
import java.io.*
import javax.inject.*
import org.isoron.uhabits.AndroidDirFinder
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitMenuBehavior
import java.io.File
import javax.inject.Inject
class HabitsDirFinder @Inject
constructor(

@ -18,10 +18,10 @@
*/
package org.isoron.uhabits.activities.about
import android.os.*
import androidx.appcompat.app.*
import org.isoron.uhabits.*
import org.isoron.uhabits.activities.*
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import org.isoron.uhabits.HabitsApplication
import org.isoron.uhabits.activities.AndroidThemeSwitcher
/**
* Activity that allows the user to see information about the app itself.
@ -31,9 +31,11 @@ class AboutActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val app = application as HabitsApplication
val screen = AboutScreen(this,
val screen = AboutScreen(
this,
app.component.intentFactory,
app.component.preferences)
app.component.preferences
)
AndroidThemeSwitcher(this, app.component.preferences).apply()
setContentView(AboutView(this, screen))
}

@ -18,10 +18,11 @@
*/
package org.isoron.uhabits.activities.about
import org.isoron.uhabits.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.intents.*
import org.isoron.uhabits.utils.*
import org.isoron.uhabits.R
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.intents.IntentFactory
import org.isoron.uhabits.utils.showMessage
import org.isoron.uhabits.utils.startActivitySafely
class AboutScreen(
private val activity: AboutActivity,

@ -18,14 +18,15 @@
*/
package org.isoron.uhabits.activities.about
import android.annotation.*
import android.content.*
import android.view.*
import android.widget.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.databinding.*
import org.isoron.uhabits.utils.*
import android.annotation.SuppressLint
import android.content.Context
import android.view.LayoutInflater
import android.widget.FrameLayout
import org.isoron.uhabits.BuildConfig
import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.databinding.AboutBinding
import org.isoron.uhabits.utils.setupToolbar
@SuppressLint("ViewConstructor")
class AboutView(

@ -19,23 +19,25 @@
package org.isoron.uhabits.activities.common.dialogs
import android.app.*
import android.os.*
import android.util.*
import android.view.*
import android.widget.*
import androidx.appcompat.app.*
import android.app.Dialog
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.widget.EditText
import android.widget.RadioButton
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatDialogFragment
import kotlinx.android.synthetic.main.frequency_picker_dialog.view.*
import org.isoron.uhabits.*
import org.isoron.uhabits.R
class FrequencyPickerDialog(var freqNumerator: Int,
class FrequencyPickerDialog(
var freqNumerator: Int,
var freqDenominator: Int
) : AppCompatDialogFragment() {
) : AppCompatDialogFragment() {
lateinit var contentView: View
var onFrequencyPicked: (num: Int, den: Int) -> Unit = {_,_ -> }
var onFrequencyPicked: (num: Int, den: Int) -> Unit = { _, _ -> }
constructor() : this(1, 1)
@ -55,7 +57,7 @@ class FrequencyPickerDialog(var freqNumerator: Int,
}
contentView.everyXDaysTextView.setOnFocusChangeListener { v, hasFocus ->
if(hasFocus) check(contentView.everyXDaysRadioButton)
if (hasFocus) check(contentView.everyXDaysRadioButton)
}
contentView.xTimesPerWeekRadioButton.setOnClickListener {
@ -64,7 +66,7 @@ class FrequencyPickerDialog(var freqNumerator: Int,
}
contentView.xTimesPerWeekTextView.setOnFocusChangeListener { v, hasFocus ->
if(hasFocus) check(contentView.xTimesPerWeekRadioButton)
if (hasFocus) check(contentView.xTimesPerWeekRadioButton)
}
contentView.xTimesPerMonthRadioButton.setOnClickListener {
@ -73,7 +75,7 @@ class FrequencyPickerDialog(var freqNumerator: Int,
}
contentView.xTimesPerMonthTextView.setOnFocusChangeListener { v, hasFocus ->
if(hasFocus) check(contentView.xTimesPerMonthRadioButton)
if (hasFocus) check(contentView.xTimesPerMonthRadioButton)
}
return AlertDialog.Builder(activity!!)
@ -129,7 +131,7 @@ class FrequencyPickerDialog(var freqNumerator: Int,
private fun populateViews() {
uncheckAll()
if (freqNumerator == 1) {
if(freqDenominator == 1) {
if (freqDenominator == 1) {
contentView.everyDayRadioButton.isChecked = true
} else {
contentView.everyXDaysRadioButton.isChecked = true
@ -137,7 +139,7 @@ class FrequencyPickerDialog(var freqNumerator: Int,
focus(contentView.everyXDaysTextView)
}
} else {
if(freqDenominator == 7) {
if (freqDenominator == 7) {
contentView.xTimesPerWeekRadioButton.isChecked = true
contentView.xTimesPerWeekTextView.setText(freqNumerator.toString())
focus(contentView.xTimesPerWeekTextView)

@ -19,26 +19,31 @@
package org.isoron.uhabits.activities.common.dialogs
import android.content.*
import androidx.appcompat.app.*
import android.text.*
import android.view.*
import android.view.WindowManager.LayoutParams.*
import android.view.inputmethod.*
import android.widget.*
import android.content.Context
import android.content.DialogInterface
import android.text.InputFilter
import android.view.LayoutInflater
import android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE
import android.view.inputmethod.EditorInfo
import android.widget.EditText
import android.widget.NumberPicker
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import org.isoron.uhabits.R
import org.isoron.uhabits.core.ui.screens.habits.list.*
import org.isoron.uhabits.inject.*
import org.isoron.uhabits.utils.*
import javax.inject.*
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
import org.isoron.uhabits.inject.ActivityContext
import org.isoron.uhabits.utils.InterfaceUtils
import javax.inject.Inject
class NumberPickerFactory
@Inject constructor(
@ActivityContext private val context: Context
) {
fun create(value: Double,
fun create(
value: Double,
unit: String,
callback: ListHabitsBehavior.NumberPickerCallback): AlertDialog {
callback: ListHabitsBehavior.NumberPickerCallback
): AlertDialog {
val inflater = LayoutInflater.from(context)
val view = inflater.inflate(R.layout.number_picker_dialog, null)
@ -70,7 +75,7 @@ class NumberPickerFactory
val v = picker.value + 0.05 * picker2.value
callback.onNumberPicked(v)
}
.setOnDismissListener{
.setOnDismissListener {
callback.onNumberPickerDismissed()
}
.create()
@ -80,11 +85,14 @@ class NumberPickerFactory
dialog.window?.setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_VISIBLE)
}
InterfaceUtils.setupEditorAction(picker, TextView.OnEditorActionListener { _, actionId, _ ->
InterfaceUtils.setupEditorAction(
picker,
TextView.OnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE)
dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick()
false
})
}
)
return dialog
}

@ -19,10 +19,11 @@
package org.isoron.uhabits.activities.common.views
import android.content.*
import android.view.*
import android.widget.*
import org.isoron.uhabits.core.tasks.*
import android.content.Context
import android.view.View
import android.widget.ProgressBar
import org.isoron.uhabits.core.tasks.Task
import org.isoron.uhabits.core.tasks.TaskRunner
class TaskProgressBar(
context: Context,
@ -31,7 +32,8 @@ class TaskProgressBar(
context,
null,
android.R.attr.progressBarStyleHorizontal
), TaskRunner.Listener {
),
TaskRunner.Listener {
init {
visibility = View.GONE

@ -19,25 +19,38 @@
package org.isoron.uhabits.activities.habits.edit
import android.annotation.*
import android.content.res.*
import android.graphics.*
import android.os.*
import android.text.format.*
import android.view.*
import android.widget.*
import androidx.appcompat.app.*
import androidx.fragment.app.*
import com.android.datetimepicker.time.*
import android.annotation.SuppressLint
import android.content.res.ColorStateList
import android.graphics.Color
import android.os.Bundle
import android.text.format.DateFormat
import android.view.View
import android.widget.ArrayAdapter
import androidx.appcompat.app.AlertDialog
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.*
import org.isoron.uhabits.*
import org.isoron.uhabits.activities.*
import org.isoron.uhabits.activities.common.dialogs.*
import org.isoron.uhabits.core.commands.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.databinding.*
import org.isoron.uhabits.utils.*
import org.isoron.uhabits.HabitsApplication
import org.isoron.uhabits.R
import org.isoron.uhabits.activities.AndroidThemeSwitcher
import org.isoron.uhabits.activities.common.dialogs.ColorPickerDialogFactory
import org.isoron.uhabits.activities.common.dialogs.FrequencyPickerDialog
import org.isoron.uhabits.activities.common.dialogs.WeekdayPickerDialog
import org.isoron.uhabits.core.commands.CommandRunner
import org.isoron.uhabits.core.commands.CreateHabitCommand
import org.isoron.uhabits.core.commands.EditHabitCommand
import org.isoron.uhabits.core.models.Frequency
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.models.Reminder
import org.isoron.uhabits.core.models.WeekdayList
import org.isoron.uhabits.databinding.ActivityEditHabitBinding
import org.isoron.uhabits.utils.ColorUtils
import org.isoron.uhabits.utils.formatTime
import org.isoron.uhabits.utils.toFormattedString
import org.isoron.uhabits.utils.toThemedAndroidColor
class EditHabitActivity : AppCompatActivity() {
@ -143,7 +156,7 @@ class EditHabitActivity : AppCompatActivity() {
arrayAdapter.add(getString(R.string.every_week))
arrayAdapter.add(getString(R.string.every_month))
builder.setAdapter(arrayAdapter) { dialog, which ->
freqDen = when(which) {
freqDen = when (which) {
1 -> 7
2 -> 30
else -> 1
@ -159,7 +172,8 @@ class EditHabitActivity : AppCompatActivity() {
val currentHour = if (reminderHour >= 0) reminderHour else 8
val currentMin = if (reminderMin >= 0) reminderMin else 0
val is24HourMode = DateFormat.is24HourFormat(this)
val dialog = TimePickerDialog.newInstance(object : TimePickerDialog.OnTimeSetListener {
val dialog = TimePickerDialog.newInstance(
object : TimePickerDialog.OnTimeSetListener {
override fun onTimeSet(view: RadialPickerLayout?, hourOfDay: Int, minute: Int) {
reminderHour = hourOfDay
reminderMin = minute
@ -172,7 +186,12 @@ class EditHabitActivity : AppCompatActivity() {
reminderDays = WeekdayList.EVERY_DAY
populateReminder()
}
}, currentHour, currentMin, is24HourMode, androidColor)
},
currentHour,
currentMin,
is24HourMode,
androidColor
)
dialog.show(supportFragmentManager, "timePicker")
}
@ -188,7 +207,7 @@ class EditHabitActivity : AppCompatActivity() {
}
binding.buttonSave.setOnClickListener {
if(validate()) save()
if (validate()) save()
}
for (fragment in supportFragmentManager.fragments) {
@ -228,12 +247,14 @@ class EditHabitActivity : AppCompatActivity() {
EditHabitCommand(
component.habitList,
habitId,
habit)
habit
)
} else {
CreateHabitCommand(
component.modelFactory,
component.habitList,
habit)
habit
)
}
component.commandRunner.run(command)
finish()
@ -246,11 +267,11 @@ class EditHabitActivity : AppCompatActivity() {
isValid = false
}
if (habitType == Habit.NUMBER_HABIT) {
if(unitInput.text.isEmpty()) {
if (unitInput.text.isEmpty()) {
unitInput.error = getString(R.string.validation_cannot_be_blank)
isValid = false
}
if(targetInput.text.isEmpty()) {
if (targetInput.text.isEmpty()) {
targetInput.error = getString(R.string.validation_cannot_be_blank)
isValid = false
}
@ -282,7 +303,7 @@ class EditHabitActivity : AppCompatActivity() {
freqDen == 31 -> getString(R.string.x_times_per_month, freqNum)
else -> "Unknown"
}
binding.numericalFrequencyPicker.text = when(freqDen) {
binding.numericalFrequencyPicker.text = when (freqDen) {
1 -> getString(R.string.every_day)
7 -> getString(R.string.every_week)
30 -> getString(R.string.every_month)

@ -19,20 +19,24 @@
package org.isoron.uhabits.activities.habits.edit
import android.os.*
import android.view.*
import androidx.appcompat.app.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.databinding.*
import org.isoron.uhabits.intents.*
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatDialogFragment
import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.databinding.SelectHabitTypeBinding
import org.isoron.uhabits.intents.IntentFactory
class HabitTypeDialog : AppCompatDialogFragment() {
override fun getTheme() = R.style.Translucent
override fun onCreateView(inflater: LayoutInflater,
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? {
savedInstanceState: Bundle?
): View? {
val binding = SelectHabitTypeBinding.inflate(inflater, container, false)
binding.buttonYesNo.setOnClickListener {

@ -19,22 +19,26 @@
package org.isoron.uhabits.activities.habits.list
import android.content.*
import android.os.*
import android.view.*
import androidx.appcompat.app.*
import kotlinx.coroutines.*
import org.isoron.uhabits.*
import org.isoron.uhabits.activities.*
import org.isoron.uhabits.activities.habits.list.views.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.sync.*
import org.isoron.uhabits.core.tasks.*
import org.isoron.uhabits.core.ui.ThemeSwitcher.*
import org.isoron.uhabits.core.utils.*
import org.isoron.uhabits.database.*
import org.isoron.uhabits.inject.*
import org.isoron.uhabits.utils.*
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.isoron.uhabits.BaseExceptionHandler
import org.isoron.uhabits.HabitsApplication
import org.isoron.uhabits.activities.habits.list.views.HabitCardListAdapter
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.sync.SyncManager
import org.isoron.uhabits.core.tasks.TaskRunner
import org.isoron.uhabits.core.ui.ThemeSwitcher.THEME_DARK
import org.isoron.uhabits.core.utils.MidnightTimer
import org.isoron.uhabits.database.AutoBackup
import org.isoron.uhabits.inject.ActivityContextModule
import org.isoron.uhabits.inject.DaggerHabitsActivityComponent
import org.isoron.uhabits.utils.restartWithFade
class ListHabitsActivity : AppCompatActivity() {

@ -19,15 +19,18 @@
package org.isoron.uhabits.activities.habits.list
import android.content.*
import android.view.*
import androidx.appcompat.app.*
import android.content.Context
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import org.isoron.uhabits.R
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.ui.*
import org.isoron.uhabits.core.ui.screens.habits.list.*
import org.isoron.uhabits.inject.*
import javax.inject.*
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.ui.ThemeSwitcher
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsMenuBehavior
import org.isoron.uhabits.inject.ActivityContext
import org.isoron.uhabits.inject.ActivityScope
import javax.inject.Inject
@ActivityScope
class ListHabitsMenu @Inject constructor(

@ -19,14 +19,17 @@
package org.isoron.uhabits.activities.habits.list
import android.content.*
import dagger.*
import org.isoron.uhabits.*
import org.isoron.uhabits.activities.*
import org.isoron.uhabits.activities.habits.list.views.*
import org.isoron.uhabits.core.ui.screens.habits.list.*
import org.isoron.uhabits.inject.*
import javax.inject.*
import android.content.Context
import dagger.Binds
import dagger.Module
import org.isoron.uhabits.AndroidBugReporter
import org.isoron.uhabits.activities.HabitsDirFinder
import org.isoron.uhabits.activities.habits.list.views.HabitCardListAdapter
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsMenuBehavior
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsSelectionMenuBehavior
import org.isoron.uhabits.inject.AppContext
import javax.inject.Inject
class BugReporterProxy
@Inject constructor(

@ -19,21 +19,38 @@
package org.isoron.uhabits.activities.habits.list
import android.content.*
import android.view.ViewGroup.LayoutParams.*
import android.widget.*
import android.content.Context
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.widget.FrameLayout
import android.widget.RelativeLayout
import org.isoron.uhabits.R
import org.isoron.uhabits.activities.common.views.*
import org.isoron.uhabits.activities.habits.list.views.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.tasks.*
import org.isoron.uhabits.core.ui.screens.habits.list.*
import org.isoron.uhabits.core.utils.*
import org.isoron.uhabits.inject.*
import org.isoron.uhabits.utils.*
import java.lang.Math.*
import javax.inject.*
import org.isoron.uhabits.activities.common.views.ScrollableChart
import org.isoron.uhabits.activities.common.views.TaskProgressBar
import org.isoron.uhabits.activities.habits.list.views.EmptyListView
import org.isoron.uhabits.activities.habits.list.views.HabitCardListAdapter
import org.isoron.uhabits.activities.habits.list.views.HabitCardListView
import org.isoron.uhabits.activities.habits.list.views.HabitCardListViewFactory
import org.isoron.uhabits.activities.habits.list.views.HeaderView
import org.isoron.uhabits.activities.habits.list.views.HintView
import org.isoron.uhabits.core.models.ModelObservable
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.tasks.TaskRunner
import org.isoron.uhabits.core.ui.screens.habits.list.HintListFactory
import org.isoron.uhabits.core.utils.MidnightTimer
import org.isoron.uhabits.inject.ActivityContext
import org.isoron.uhabits.inject.ActivityScope
import org.isoron.uhabits.utils.addAtBottom
import org.isoron.uhabits.utils.addAtTop
import org.isoron.uhabits.utils.addBelow
import org.isoron.uhabits.utils.buildToolbar
import org.isoron.uhabits.utils.dim
import org.isoron.uhabits.utils.dp
import org.isoron.uhabits.utils.setupToolbar
import org.isoron.uhabits.utils.sres
import java.lang.Math.max
import java.lang.Math.min
import javax.inject.Inject
const val MAX_CHECKMARK_COUNT = 60
@ -86,11 +103,13 @@ class ListHabitsRootView @Inject constructor(
}
private fun setupControllers() {
header.setScrollController(object : ScrollableChart.ScrollController {
header.setScrollController(
object : ScrollableChart.ScrollController {
override fun onDataOffsetChanged(newDataOffset: Int) {
listView.dataOffset = newDataOffset
}
})
}
)
}
override fun onAttachedToWindow() {

@ -19,29 +19,58 @@
package org.isoron.uhabits.activities.habits.list
import android.app.*
import android.content.*
import android.util.*
import androidx.annotation.*
import androidx.appcompat.app.*
import dagger.*
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import dagger.Lazy
import org.isoron.uhabits.R
import org.isoron.uhabits.activities.common.dialogs.*
import org.isoron.uhabits.activities.habits.edit.*
import org.isoron.uhabits.activities.habits.list.views.*
import org.isoron.uhabits.core.commands.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.tasks.*
import org.isoron.uhabits.core.ui.*
import org.isoron.uhabits.core.ui.callbacks.*
import org.isoron.uhabits.core.ui.screens.habits.list.*
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message.*
import org.isoron.uhabits.inject.*
import org.isoron.uhabits.intents.*
import org.isoron.uhabits.tasks.*
import org.isoron.uhabits.utils.*
import java.io.*
import javax.inject.*
import org.isoron.uhabits.activities.common.dialogs.ColorPickerDialogFactory
import org.isoron.uhabits.activities.common.dialogs.ConfirmDeleteDialogFactory
import org.isoron.uhabits.activities.common.dialogs.ConfirmSyncKeyDialogFactory
import org.isoron.uhabits.activities.common.dialogs.NumberPickerFactory
import org.isoron.uhabits.activities.habits.edit.HabitTypeDialog
import org.isoron.uhabits.activities.habits.list.views.HabitCardListAdapter
import org.isoron.uhabits.core.commands.ArchiveHabitsCommand
import org.isoron.uhabits.core.commands.ChangeHabitColorCommand
import org.isoron.uhabits.core.commands.Command
import org.isoron.uhabits.core.commands.CommandRunner
import org.isoron.uhabits.core.commands.CreateHabitCommand
import org.isoron.uhabits.core.commands.DeleteHabitsCommand
import org.isoron.uhabits.core.commands.EditHabitCommand
import org.isoron.uhabits.core.commands.UnarchiveHabitsCommand
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.tasks.TaskRunner
import org.isoron.uhabits.core.ui.ThemeSwitcher
import org.isoron.uhabits.core.ui.callbacks.OnColorPickedCallback
import org.isoron.uhabits.core.ui.callbacks.OnConfirmedCallback
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message.COULD_NOT_EXPORT
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message.COULD_NOT_GENERATE_BUG_REPORT
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message.DATABASE_REPAIRED
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message.FILE_NOT_RECOGNIZED
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message.IMPORT_FAILED
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message.IMPORT_SUCCESSFUL
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message.SYNC_ENABLED
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message.SYNC_KEY_ALREADY_INSTALLED
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsMenuBehavior
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsSelectionMenuBehavior
import org.isoron.uhabits.inject.ActivityContext
import org.isoron.uhabits.inject.ActivityScope
import org.isoron.uhabits.intents.IntentFactory
import org.isoron.uhabits.tasks.ExportDBTaskFactory
import org.isoron.uhabits.tasks.ImportDataTask
import org.isoron.uhabits.tasks.ImportDataTaskFactory
import org.isoron.uhabits.utils.copyTo
import org.isoron.uhabits.utils.restartWithFade
import org.isoron.uhabits.utils.showMessage
import org.isoron.uhabits.utils.showSendEmailScreen
import org.isoron.uhabits.utils.showSendFileScreen
import java.io.File
import java.io.IOException
import javax.inject.Inject
const val RESULT_IMPORT_DATA = 101
const val RESULT_EXPORT_CSV = 102
@ -76,7 +105,7 @@ class ListHabitsScreen
fun onAttached() {
commandRunner.addListener(this)
if(activity.intent.action == "android.intent.action.VIEW") {
if (activity.intent.action == "android.intent.action.VIEW") {
val uri = activity.intent.data!!.toString()
val parts = uri.replace(Regex("^.*sync/"), "").split("#")
val syncKey = parts[0]
@ -172,7 +201,9 @@ class ListHabitsScreen
}
override fun showMessage(m: ListHabitsBehavior.Message) {
activity.showMessage(activity.resources.getString(when (m) {
activity.showMessage(
activity.resources.getString(
when (m) {
COULD_NOT_EXPORT -> R.string.could_not_export
IMPORT_SUCCESSFUL -> R.string.habits_imported
IMPORT_FAILED -> R.string.could_not_import
@ -181,7 +212,9 @@ class ListHabitsScreen
FILE_NOT_RECOGNIZED -> R.string.file_not_recognized
SYNC_ENABLED -> R.string.sync_enabled
SYNC_KEY_ALREADY_INSTALLED -> R.string.sync_key_already_installed
}))
}
)
)
}
override fun showSendBugReportToDeveloperScreen(log: String) {
@ -199,16 +232,20 @@ class ListHabitsScreen
activity.startActivityForResult(intent, REQUEST_SETTINGS)
}
override fun showColorPicker(defaultColor: PaletteColor,
callback: OnColorPickedCallback) {
override fun showColorPicker(
defaultColor: PaletteColor,
callback: OnColorPickedCallback
) {
val picker = colorPickerFactory.create(defaultColor)
picker.setListener(callback)
picker.show(activity.supportFragmentManager, "picker")
}
override fun showNumberPicker(value: Double,
override fun showNumberPicker(
value: Double,
unit: String,
callback: ListHabitsBehavior.NumberPickerCallback) {
callback: ListHabitsBehavior.NumberPickerCallback
) {
numberPickerFactory.create(value, unit, callback).show()
}
@ -219,33 +256,42 @@ class ListHabitsScreen
private fun getExecuteString(command: Command): String? {
when (command) {
is ArchiveHabitsCommand -> {
return activity.resources.getQuantityString(R.plurals.toast_habits_archived,
command.selected.size)
return activity.resources.getQuantityString(
R.plurals.toast_habits_archived,
command.selected.size
)
}
is ChangeHabitColorCommand -> {
return activity.resources.getQuantityString(R.plurals.toast_habits_changed,
command.selected.size)
return activity.resources.getQuantityString(
R.plurals.toast_habits_changed,
command.selected.size
)
}
is CreateHabitCommand -> {
return activity.resources.getString(R.string.toast_habit_created)
}
is DeleteHabitsCommand -> {
return activity.resources.getQuantityString(R.plurals.toast_habits_deleted,
command.selected.size)
return activity.resources.getQuantityString(
R.plurals.toast_habits_deleted,
command.selected.size
)
}
is EditHabitCommand -> {
return activity.resources.getQuantityString(R.plurals.toast_habits_changed, 1)
}
is UnarchiveHabitsCommand -> {
return activity.resources.getQuantityString(R.plurals.toast_habits_unarchived,
command.selected.size)
return activity.resources.getQuantityString(
R.plurals.toast_habits_unarchived,
command.selected.size
)
}
else -> return null
}
}
private fun onImportData(file: File, onFinished: () -> Unit) {
taskRunner.execute(importTaskFactory.create(file) { result ->
taskRunner.execute(
importTaskFactory.create(file) { result ->
if (result == ImportDataTask.SUCCESS) {
adapter.refresh()
activity.showMessage(activity.resources.getString(R.string.habits_imported))
@ -255,13 +301,16 @@ class ListHabitsScreen
activity.showMessage(activity.resources.getString(R.string.could_not_import))
}
onFinished()
})
}
)
}
private fun onExportDB() {
taskRunner.execute(exportDBFactory.create { filename ->
taskRunner.execute(
exportDBFactory.create { filename ->
if (filename != null) activity.showSendFileScreen(filename)
else activity.showMessage(activity.resources.getString(R.string.could_not_export))
})
}
)
}
}

@ -19,20 +19,23 @@
package org.isoron.uhabits.activities.habits.list
import android.content.*
import android.view.*
import androidx.appcompat.app.*
import android.content.Context
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import dagger.*
import dagger.Lazy
import org.isoron.uhabits.R
import org.isoron.uhabits.activities.habits.list.views.*
import org.isoron.uhabits.core.commands.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.ui.*
import org.isoron.uhabits.core.ui.screens.habits.list.*
import org.isoron.uhabits.core.utils.*
import org.isoron.uhabits.inject.*
import javax.inject.*
import org.isoron.uhabits.activities.habits.list.views.HabitCardListAdapter
import org.isoron.uhabits.activities.habits.list.views.HabitCardListController
import org.isoron.uhabits.core.commands.CommandRunner
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.ui.NotificationTray
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsSelectionMenuBehavior
import org.isoron.uhabits.core.utils.DateUtils
import org.isoron.uhabits.inject.ActivityContext
import org.isoron.uhabits.inject.ActivityScope
import javax.inject.Inject
@ActivityScope
class ListHabitsSelectionMenu @Inject constructor(

@ -19,13 +19,14 @@
package org.isoron.uhabits.activities.habits.list.views
import android.content.*
import android.view.*
import android.view.View.MeasureSpec.*
import android.widget.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.utils.*
import android.content.Context
import android.view.View
import android.view.View.MeasureSpec.EXACTLY
import android.widget.LinearLayout
import org.isoron.uhabits.R
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.utils.dim
import org.isoron.uhabits.utils.toMeasureSpec
abstract class ButtonPanelView<T : View>(
context: Context,
@ -79,8 +80,10 @@ abstract class ButtonPanelView<T : View>(
val buttonWidth = dim(R.dimen.checkmarkWidth)
val buttonHeight = dim(R.dimen.checkmarkHeight)
val width = (buttonWidth * buttonCount)
super.onMeasure(width.toMeasureSpec(EXACTLY),
buttonHeight.toMeasureSpec(EXACTLY))
super.onMeasure(
width.toMeasureSpec(EXACTLY),
buttonHeight.toMeasureSpec(EXACTLY)
)
}
protected abstract fun setupButtons()

@ -19,21 +19,30 @@
package org.isoron.uhabits.activities.habits.list.views
import android.content.*
import android.graphics.*
import android.text.*
import android.view.*
import android.view.View.MeasureSpec.*
import com.google.auto.factory.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.models.*
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.RectF
import android.text.TextPaint
import android.view.HapticFeedbackConstants
import android.view.View
import android.view.View.MeasureSpec.EXACTLY
import com.google.auto.factory.AutoFactory
import com.google.auto.factory.Provided
import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.Entry
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_MANUAL
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.inject.*
import org.isoron.uhabits.utils.*
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.inject.ActivityContext
import org.isoron.uhabits.utils.dim
import org.isoron.uhabits.utils.getFontAwesome
import org.isoron.uhabits.utils.showMessage
import org.isoron.uhabits.utils.sres
import org.isoron.uhabits.utils.toMeasureSpec
@AutoFactory
class CheckmarkButtonView(
@ -65,7 +74,7 @@ class CheckmarkButtonView(
}
fun performToggle() {
value = if(preferences.isSkipEnabled) {
value = if (preferences.isSkipEnabled) {
Entry.nextToggleValueWithSkip(value)
} else {
Entry.nextToggleValueWithoutSkip(value)
@ -93,8 +102,10 @@ class CheckmarkButtonView(
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val height = resources.getDimensionPixelSize(R.dimen.checkmarkHeight)
val width = resources.getDimensionPixelSize(R.dimen.checkmarkWidth)
super.onMeasure(width.toMeasureSpec(EXACTLY),
height.toMeasureSpec(EXACTLY))
super.onMeasure(
width.toMeasureSpec(EXACTLY),
height.toMeasureSpec(EXACTLY)
)
}
private inner class Drawer {
@ -118,7 +129,7 @@ class CheckmarkButtonView(
SKIP -> R.string.fa_skipped
NO -> R.string.fa_times
UNKNOWN -> {
if(preferences.areQuestionMarksEnabled()) R.string.fa_question
if (preferences.areQuestionMarksEnabled()) R.string.fa_question
else R.string.fa_times
}
else -> R.string.fa_check

@ -19,13 +19,14 @@
package org.isoron.uhabits.activities.habits.list.views
import android.content.*
import com.google.auto.factory.*
import org.isoron.uhabits.core.models.*
import android.content.Context
import com.google.auto.factory.AutoFactory
import com.google.auto.factory.Provided
import org.isoron.uhabits.core.models.Entry.Companion.UNKNOWN
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.utils.*
import org.isoron.uhabits.inject.*
import org.isoron.uhabits.core.models.Timestamp
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.utils.DateUtils
import org.isoron.uhabits.inject.ActivityContext
@AutoFactory
class CheckmarkPanelView(
@ -46,7 +47,7 @@ class CheckmarkPanelView(
setupButtons()
}
var onToggle: (Timestamp, Int) -> Unit = {_, _ ->}
var onToggle: (Timestamp, Int) -> Unit = { _, _ -> }
set(value) {
field = value
setupButtons()

@ -19,13 +19,19 @@
package org.isoron.uhabits.activities.habits.list.views
import android.content.*
import android.view.*
import android.view.Gravity.*
import android.view.ViewGroup.LayoutParams.*
import android.widget.*
import org.isoron.uhabits.*
import org.isoron.uhabits.utils.*
import android.content.Context
import android.view.Gravity.CENTER
import android.view.View
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.LinearLayout
import android.widget.TextView
import org.isoron.uhabits.R
import org.isoron.uhabits.utils.dp
import org.isoron.uhabits.utils.getFontAwesome
import org.isoron.uhabits.utils.sp
import org.isoron.uhabits.utils.sres
import org.isoron.uhabits.utils.str
class EmptyListView(context: Context) : LinearLayout(context) {
init {
@ -33,19 +39,27 @@ class EmptyListView(context: Context) : LinearLayout(context) {
gravity = CENTER
visibility = View.GONE
addView(TextView(context).apply {
addView(
TextView(context).apply {
text = str(R.string.fa_star_half_o)
typeface = getFontAwesome()
textSize = sp(40.0f)
gravity = CENTER
setTextColor(sres.getColor(R.attr.mediumContrastTextColor))
}, MATCH_PARENT, WRAP_CONTENT)
},
MATCH_PARENT,
WRAP_CONTENT
)
addView(TextView(context).apply {
addView(
TextView(context).apply {
text = str(R.string.no_habits_found)
gravity = CENTER
setPadding(0, dp(20.0f).toInt(), 0, 0)
setTextColor(sres.getColor(R.attr.mediumContrastTextColor))
}, MATCH_PARENT, WRAP_CONTENT)
},
MATCH_PARENT,
WRAP_CONTENT
)
}
}

@ -19,12 +19,13 @@
package org.isoron.uhabits.activities.habits.list.views
import dagger.*
import org.isoron.uhabits.activities.habits.list.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.ui.screens.habits.list.*
import org.isoron.uhabits.inject.*
import javax.inject.*
import dagger.Lazy
import org.isoron.uhabits.activities.habits.list.ListHabitsSelectionMenu
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.ModelObservable
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
import org.isoron.uhabits.inject.ActivityScope
import javax.inject.Inject
/**
* Controller responsible for receiving and processing the events generated by a
@ -53,7 +54,7 @@ class HabitCardListController @Inject constructor(
val habitFrom = adapter.getItem(from)
val habitTo = adapter.getItem(to)
if(habitFrom == null || habitTo == null) return
if (habitFrom == null || habitTo == null) return
adapter.performReorder(from, to)
behavior.onReorderHabit(habitFrom, habitTo)

@ -19,17 +19,26 @@
package org.isoron.uhabits.activities.habits.list.views
import android.content.*
import android.os.*
import androidx.recyclerview.widget.*
import androidx.recyclerview.widget.ItemTouchHelper.*
import android.view.*
import com.google.auto.factory.*
import dagger.*
import android.content.Context
import android.os.Bundle
import android.os.Parcelable
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.ItemTouchHelper.DOWN
import androidx.recyclerview.widget.ItemTouchHelper.END
import androidx.recyclerview.widget.ItemTouchHelper.START
import androidx.recyclerview.widget.ItemTouchHelper.UP
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.auto.factory.AutoFactory
import com.google.auto.factory.Provided
import dagger.Lazy
import org.isoron.uhabits.R
import org.isoron.uhabits.activities.common.views.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.inject.*
import org.isoron.uhabits.activities.common.views.BundleSavedState
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.inject.ActivityContext
@AutoFactory
class HabitCardListView(
@ -65,11 +74,13 @@ class HabitCardListView(
return cardViewFactory.create()
}
fun bindCardView(holder: HabitCardViewHolder,
fun bindCardView(
holder: HabitCardViewHolder,
habit: Habit,
score: Double,
checkmarks: IntArray,
selected: Boolean): View {
selected: Boolean
): View {
val cardView = holder.itemView as HabitCardView
cardView.habit = habit
cardView.isSelected = selected
@ -149,20 +160,26 @@ class HabitCardListView(
}
inner class TouchHelperCallback : ItemTouchHelper.Callback() {
override fun getMovementFlags(recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder): Int {
override fun getMovementFlags(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
): Int {
return makeMovementFlags(UP or DOWN, START or END)
}
override fun onMove(recyclerView: RecyclerView,
override fun onMove(
recyclerView: RecyclerView,
from: RecyclerView.ViewHolder,
to: RecyclerView.ViewHolder): Boolean {
to: RecyclerView.ViewHolder
): Boolean {
controller.get().drop(from.adapterPosition, to.adapterPosition)
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder,
direction: Int) {
override fun onSwiped(
viewHolder: RecyclerView.ViewHolder,
direction: Int
) {
}
override fun isItemViewSwipeEnabled() = false

@ -19,22 +19,34 @@
package org.isoron.uhabits.activities.habits.list.views
import android.content.*
import android.os.*
import android.os.Build.VERSION.*
import android.os.Build.VERSION_CODES.*
import android.text.*
import android.view.*
import android.view.ViewGroup.LayoutParams.*
import android.widget.*
import com.google.auto.factory.*
import android.content.Context
import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES.LOLLIPOP
import android.os.Build.VERSION_CODES.M
import android.os.Handler
import android.os.Looper
import android.text.Layout
import android.text.TextUtils
import android.view.Gravity
import android.view.View
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.TextView
import com.google.auto.factory.AutoFactory
import com.google.auto.factory.Provided
import org.isoron.uhabits.R
import org.isoron.uhabits.activities.common.views.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.ui.screens.habits.list.*
import org.isoron.uhabits.core.utils.*
import org.isoron.uhabits.inject.*
import org.isoron.uhabits.utils.*
import org.isoron.uhabits.activities.common.views.RingView
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.ModelObservable
import org.isoron.uhabits.core.models.Timestamp
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
import org.isoron.uhabits.core.utils.DateUtils
import org.isoron.uhabits.inject.ActivityContext
import org.isoron.uhabits.utils.dp
import org.isoron.uhabits.utils.sres
import org.isoron.uhabits.utils.toThemedAndroidColor
@AutoFactory
class HabitCardView(
@ -228,8 +240,10 @@ class HabitCardView(
private fun triggerRipple(x: Float, y: Float) {
val background = innerFrame.background
if (SDK_INT >= LOLLIPOP) background.setHotspot(x, y)
background.state = intArrayOf(android.R.attr.state_pressed,
android.R.attr.state_enabled)
background.state = intArrayOf(
android.R.attr.state_pressed,
android.R.attr.state_enabled
)
Handler().postDelayed({ background.state = intArrayOf() }, 25)
}

@ -19,8 +19,7 @@
package org.isoron.uhabits.activities.habits.list.views
import androidx.appcompat.widget.*
import android.view.*
import android.view.View
import androidx.recyclerview.widget.RecyclerView
class HabitCardViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)

@ -19,19 +19,28 @@
package org.isoron.uhabits.activities.habits.list.views
import android.content.*
import android.graphics.*
import android.os.Build.VERSION.*
import android.os.Build.VERSION_CODES.*
import android.text.*
import android.view.View.MeasureSpec.*
import org.isoron.uhabits.*
import org.isoron.uhabits.activities.common.views.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.utils.*
import org.isoron.uhabits.core.utils.DateUtils.*
import org.isoron.uhabits.utils.*
import java.util.*
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.RectF
import android.graphics.Typeface
import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES.LOLLIPOP
import android.text.TextPaint
import android.view.View.MeasureSpec.EXACTLY
import org.isoron.uhabits.R
import org.isoron.uhabits.activities.common.views.ScrollableChart
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.utils.DateUtils.formatHeaderDate
import org.isoron.uhabits.core.utils.DateUtils.getStartOfTodayCalendarWithOffset
import org.isoron.uhabits.core.utils.MidnightTimer
import org.isoron.uhabits.utils.dim
import org.isoron.uhabits.utils.dp
import org.isoron.uhabits.utils.isRTL
import org.isoron.uhabits.utils.sres
import org.isoron.uhabits.utils.toMeasureSpec
import java.util.GregorianCalendar
class HeaderView(
context: Context,
@ -121,8 +130,12 @@ class HeaderView(
if (isReversed) rect.offset(-(index + 1) * width, 0f)
else rect.offset((index - buttonCount) * width, 0f)
if (isRTL()) rect.set(canvas.width - rect.right, rect.top,
canvas.width - rect.left, rect.bottom)
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

@ -19,16 +19,17 @@
package org.isoron.uhabits.activities.habits.list.views
import android.animation.*
import android.content.*
import android.graphics.*
import android.graphics.Color.*
import android.view.*
import android.view.ViewGroup.LayoutParams.*
import android.widget.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.ui.screens.habits.list.*
import org.isoron.uhabits.utils.*
import android.animation.AnimatorListenerAdapter
import android.content.Context
import android.graphics.Color.WHITE
import android.graphics.Typeface
import android.view.View
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.LinearLayout
import android.widget.TextView
import org.isoron.uhabits.R
import org.isoron.uhabits.core.ui.screens.habits.list.HintList
import org.isoron.uhabits.utils.dp
class HintView(
context: Context,

@ -19,18 +19,25 @@
package org.isoron.uhabits.activities.habits.list.views
import android.content.*
import android.graphics.*
import android.text.*
import android.view.*
import android.view.View.*
import com.google.auto.factory.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.inject.*
import org.isoron.uhabits.utils.*
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RectF
import android.graphics.Typeface
import android.text.TextPaint
import android.view.View
import android.view.View.OnClickListener
import android.view.View.OnLongClickListener
import com.google.auto.factory.AutoFactory
import com.google.auto.factory.Provided
import org.isoron.uhabits.R
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.inject.ActivityContext
import org.isoron.uhabits.utils.InterfaceUtils.getDimension
import java.text.*
import org.isoron.uhabits.utils.StyledResources
import org.isoron.uhabits.utils.getFontAwesome
import org.isoron.uhabits.utils.showMessage
import java.text.DecimalFormat
private val BOLD_TYPEFACE = Typeface.create("sans-serif-condensed", Typeface.BOLD)
private val NORMAL_TYPEFACE = Typeface.create("sans-serif-condensed", Typeface.NORMAL)
@ -148,10 +155,10 @@ class NumberButtonView(
val label: String
val typeface: Typeface
if(value >= 0) {
if (value >= 0) {
label = value.toShortString()
typeface = BOLD_TYPEFACE
} else if(preferences.areQuestionMarksEnabled()) {
} else if (preferences.areQuestionMarksEnabled()) {
label = resources.getString(R.string.fa_question)
typeface = getFontAwesome()
} else {

@ -19,12 +19,13 @@
package org.isoron.uhabits.activities.habits.list.views
import android.content.*
import com.google.auto.factory.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.utils.*
import org.isoron.uhabits.inject.*
import android.content.Context
import com.google.auto.factory.AutoFactory
import com.google.auto.factory.Provided
import org.isoron.uhabits.core.models.Timestamp
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.utils.DateUtils
import org.isoron.uhabits.inject.ActivityContext
@AutoFactory
class NumberPanelView(

@ -19,11 +19,12 @@
package org.isoron.uhabits.activities.habits.list.views
import android.content.*
import android.view.*
import android.view.View.MeasureSpec.*
import org.isoron.uhabits.*
import org.isoron.uhabits.utils.*
import android.content.Context
import android.view.View
import android.view.View.MeasureSpec.EXACTLY
import org.isoron.uhabits.R
import org.isoron.uhabits.utils.dp
import org.isoron.uhabits.utils.toMeasureSpec
@Suppress("DEPRECATION")
class ShadowView(context: Context) : View(context) {
@ -33,7 +34,9 @@ class ShadowView(context: Context) : View(context) {
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec,
dp(2.0f).toInt().toMeasureSpec(EXACTLY))
super.onMeasure(
widthMeasureSpec,
dp(2.0f).toInt().toMeasureSpec(EXACTLY)
)
}
}

@ -18,17 +18,25 @@
*/
package org.isoron.uhabits.activities.habits.show
import android.content.*
import android.os.*
import android.view.*
import androidx.appcompat.app.*
import kotlinx.coroutines.*
import org.isoron.uhabits.*
import org.isoron.uhabits.activities.*
import org.isoron.uhabits.activities.common.dialogs.*
import org.isoron.uhabits.core.commands.*
import org.isoron.uhabits.core.ui.screens.habits.show.*
import org.isoron.uhabits.intents.*
import android.content.ContentUris
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.isoron.uhabits.AndroidDirFinder
import org.isoron.uhabits.HabitsApplication
import org.isoron.uhabits.activities.AndroidThemeSwitcher
import org.isoron.uhabits.activities.HabitsDirFinder
import org.isoron.uhabits.activities.common.dialogs.ConfirmDeleteDialogFactory
import org.isoron.uhabits.activities.common.dialogs.NumberPickerFactory
import org.isoron.uhabits.core.commands.Command
import org.isoron.uhabits.core.commands.CommandRunner
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitBehavior
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitMenuBehavior
import org.isoron.uhabits.intents.IntentFactory
class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
@ -125,4 +133,3 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
}
}
}

@ -19,10 +19,11 @@
package org.isoron.uhabits.activities.habits.show
import android.view.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.ui.screens.habits.show.*
import android.view.Menu
import android.view.MenuItem
import org.isoron.uhabits.R
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitMenuBehavior
class ShowHabitMenu(
val activity: ShowHabitActivity,
@ -38,7 +39,7 @@ class ShowHabitMenu(
}
fun onOptionsItemSelected(item: MenuItem): Boolean {
when(item.itemId) {
when (item.itemId) {
R.id.action_edit_habit -> {
behavior.onEditHabit()
return true

@ -19,16 +19,20 @@
package org.isoron.uhabits.activities.habits.show
import org.isoron.uhabits.*
import org.isoron.uhabits.activities.common.dialogs.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.ui.callbacks.*
import org.isoron.uhabits.core.ui.screens.habits.list.*
import org.isoron.uhabits.core.ui.screens.habits.show.*
import org.isoron.uhabits.intents.*
import org.isoron.uhabits.utils.*
import org.isoron.uhabits.widgets.*
import org.isoron.uhabits.R
import org.isoron.uhabits.activities.common.dialogs.ConfirmDeleteDialogFactory
import org.isoron.uhabits.activities.common.dialogs.HistoryEditorDialog
import org.isoron.uhabits.activities.common.dialogs.NumberPickerFactory
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.ui.callbacks.OnConfirmedCallback
import org.isoron.uhabits.core.ui.callbacks.OnToggleCheckmarkListener
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitBehavior
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitMenuBehavior
import org.isoron.uhabits.intents.IntentFactory
import org.isoron.uhabits.utils.showMessage
import org.isoron.uhabits.utils.showSendFileScreen
import org.isoron.uhabits.widgets.WidgetUpdater
class ShowHabitScreen(
val activity: ShowHabitActivity,
@ -40,7 +44,7 @@ class ShowHabitScreen(
) : ShowHabitBehavior.Screen, ShowHabitMenuBehavior.Screen {
override fun showNumberPicker(value: Double, unit: String, callback: ListHabitsBehavior.NumberPickerCallback) {
numberPickerFactory.create(value, unit, callback).show();
numberPickerFactory.create(value, unit, callback).show()
}
override fun updateWidgets() {

@ -19,14 +19,32 @@
package org.isoron.uhabits.activities.habits.show
import android.content.*
import android.view.*
import android.widget.*
import org.isoron.uhabits.activities.habits.show.views.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.databinding.*
import org.isoron.uhabits.utils.*
import android.content.Context
import android.view.LayoutInflater
import android.widget.FrameLayout
import org.isoron.uhabits.activities.habits.show.views.BarCardPresenter
import org.isoron.uhabits.activities.habits.show.views.BarCardViewModel
import org.isoron.uhabits.activities.habits.show.views.FrequencyCardPresenter
import org.isoron.uhabits.activities.habits.show.views.FrequencyCardViewModel
import org.isoron.uhabits.activities.habits.show.views.HistoryCardPresenter
import org.isoron.uhabits.activities.habits.show.views.HistoryCardViewModel
import org.isoron.uhabits.activities.habits.show.views.NotesCardPresenter
import org.isoron.uhabits.activities.habits.show.views.NotesCardViewModel
import org.isoron.uhabits.activities.habits.show.views.OverviewCardPresenter
import org.isoron.uhabits.activities.habits.show.views.OverviewCardViewModel
import org.isoron.uhabits.activities.habits.show.views.ScoreCardPresenter
import org.isoron.uhabits.activities.habits.show.views.ScoreCardViewModel
import org.isoron.uhabits.activities.habits.show.views.StreakCardViewModel
import org.isoron.uhabits.activities.habits.show.views.StreakCartPresenter
import org.isoron.uhabits.activities.habits.show.views.SubtitleCardPresenter
import org.isoron.uhabits.activities.habits.show.views.SubtitleCardViewModel
import org.isoron.uhabits.activities.habits.show.views.TargetCardPresenter
import org.isoron.uhabits.activities.habits.show.views.TargetCardViewModel
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.databinding.ShowHabitBinding
import org.isoron.uhabits.utils.setupToolbar
data class ShowHabitViewModel(
val title: String = "",

@ -18,15 +18,19 @@
*/
package org.isoron.uhabits.activities.habits.show.views
import android.content.*
import android.util.*
import android.view.*
import android.widget.*
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.AdapterView
import android.widget.LinearLayout
import org.isoron.uhabits.activities.habits.show.views.ScoreCardPresenter.Companion.getTruncateField
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.utils.*
import org.isoron.uhabits.databinding.*
import org.isoron.uhabits.utils.*
import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.utils.DateUtils
import org.isoron.uhabits.databinding.ShowHabitBarBinding
import org.isoron.uhabits.utils.toThemedAndroidColor
data class BarCardViewModel(
val entries: List<Entry>,
@ -88,7 +92,7 @@ class BarCardPresenter(
numericalSpinnerPosition: Int,
boolSpinnerPosition: Int,
): BarCardViewModel {
val bucketSize = if(habit.isNumerical) {
val bucketSize = if (habit.isNumerical) {
numericalBucketSizes[numericalSpinnerPosition]
} else {
boolBucketSizes[boolSpinnerPosition]

@ -18,14 +18,16 @@
*/
package org.isoron.uhabits.activities.habits.show.views
import android.content.*
import android.util.*
import android.view.*
import android.widget.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.databinding.*
import org.isoron.uhabits.utils.*
import java.util.*
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.LinearLayout
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.models.Timestamp
import org.isoron.uhabits.databinding.ShowHabitFrequencyBinding
import org.isoron.uhabits.utils.toThemedAndroidColor
import java.util.HashMap
data class FrequencyCardViewModel(
val frequency: HashMap<Timestamp, Array<Int>>,

@ -18,13 +18,14 @@
*/
package org.isoron.uhabits.activities.habits.show.views
import android.content.*
import android.util.*
import android.view.*
import android.widget.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.databinding.*
import org.isoron.uhabits.utils.*
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.LinearLayout
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.databinding.ShowHabitHistoryBinding
import org.isoron.uhabits.utils.toThemedAndroidColor
data class HistoryCardViewModel(
val entries: IntArray,
@ -54,7 +55,6 @@ class HistoryCard(context: Context, attrs: AttributeSet) : LinearLayout(context,
if (data.isNumerical) {
binding.historyChart.setNumerical(true)
}
}
}

@ -19,12 +19,12 @@
package org.isoron.uhabits.activities.habits.show.views
import android.content.*
import android.util.*
import android.view.*
import android.widget.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.databinding.*
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.LinearLayout
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.databinding.ShowHabitNotesBinding
data class NotesCardViewModel(val description: String)

@ -18,17 +18,20 @@
*/
package org.isoron.uhabits.activities.habits.show.views
import android.content.*
import android.util.*
import android.view.*
import android.widget.*
import kotlinx.coroutines.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.models.*
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.LinearLayout
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.invoke
import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.Entry.Companion.YES_MANUAL
import org.isoron.uhabits.core.utils.*
import org.isoron.uhabits.databinding.*
import org.isoron.uhabits.utils.*
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.utils.DateUtils
import org.isoron.uhabits.databinding.ShowHabitOverviewBinding
import org.isoron.uhabits.utils.StyledResources
import org.isoron.uhabits.utils.toThemedAndroidColor
data class OverviewCardViewModel(
val color: PaletteColor,
@ -43,8 +46,11 @@ class OverviewCardView(context: Context, attrs: AttributeSet) : LinearLayout(con
private val binding = ShowHabitOverviewBinding.inflate(LayoutInflater.from(context), this)
private fun formatPercentageDiff(percentageDiff: Float): String {
return String.format("%s%.0f%%", if (percentageDiff >= 0) "+" else "\u2212",
Math.abs(percentageDiff) * 100)
return String.format(
"%s%.0f%%",
if (percentageDiff >= 0) "+" else "\u2212",
Math.abs(percentageDiff) * 100
)
}
fun update(data: OverviewCardViewModel) {

@ -18,15 +18,23 @@
*/
package org.isoron.uhabits.activities.habits.show.views
import android.content.*
import android.util.*
import android.view.*
import android.widget.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.utils.*
import org.isoron.uhabits.core.utils.DateUtils.TruncateField.*
import org.isoron.uhabits.databinding.*
import org.isoron.uhabits.utils.*
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.AdapterView
import android.widget.LinearLayout
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.models.Score
import org.isoron.uhabits.core.utils.DateUtils
import org.isoron.uhabits.core.utils.DateUtils.TruncateField.DAY
import org.isoron.uhabits.core.utils.DateUtils.TruncateField.MONTH
import org.isoron.uhabits.core.utils.DateUtils.TruncateField.QUARTER
import org.isoron.uhabits.core.utils.DateUtils.TruncateField.WEEK_NUMBER
import org.isoron.uhabits.core.utils.DateUtils.TruncateField.YEAR
import org.isoron.uhabits.databinding.ShowHabitScoreBinding
import org.isoron.uhabits.utils.toThemedAndroidColor
data class ScoreCardViewModel(
val scores: List<Score>,
@ -86,9 +94,12 @@ class ScoreCardPresenter(
val scores = habit.scores.getByInterval(oldest, today).groupBy {
DateUtils.truncate(field, it.timestamp, firstWeekday)
}.map { (timestamp, scores) ->
Score(timestamp, scores.map {
Score(
timestamp,
scores.map {
it.value
}.average())
}.average()
)
}.sortedBy {
it.timestamp
}.reversed()

@ -18,14 +18,17 @@
*/
package org.isoron.uhabits.activities.habits.show.views
import android.content.*
import android.util.*
import android.view.*
import android.widget.*
import kotlinx.coroutines.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.databinding.*
import org.isoron.uhabits.utils.*
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.LinearLayout
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.invoke
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.models.Streak
import org.isoron.uhabits.databinding.ShowHabitStreakBinding
import org.isoron.uhabits.utils.toThemedAndroidColor
data class StreakCardViewModel(
val color: PaletteColor,

@ -18,18 +18,23 @@
*/
package org.isoron.uhabits.activities.habits.show.views
import android.annotation.*
import android.content.*
import android.content.res.*
import android.util.*
import android.view.*
import android.widget.*
import org.isoron.uhabits.*
import org.isoron.uhabits.activities.habits.list.views.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.databinding.*
import org.isoron.uhabits.utils.*
import java.util.*
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Resources
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout
import org.isoron.uhabits.R
import org.isoron.uhabits.activities.habits.list.views.toShortString
import org.isoron.uhabits.core.models.Frequency
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.databinding.ShowHabitSubtitleBinding
import org.isoron.uhabits.utils.InterfaceUtils
import org.isoron.uhabits.utils.formatTime
import org.isoron.uhabits.utils.toThemedAndroidColor
import java.util.Locale
data class SubtitleCardViewModel(
val color: PaletteColor,

@ -18,18 +18,21 @@
*/
package org.isoron.uhabits.activities.habits.show.views
import android.content.*
import android.content.res.*
import android.util.*
import android.view.*
import android.widget.*
import kotlinx.coroutines.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.utils.*
import org.isoron.uhabits.databinding.*
import org.isoron.uhabits.utils.*
import java.util.*
import android.content.Context
import android.content.res.Resources
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.LinearLayout
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.invoke
import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.utils.DateUtils
import org.isoron.uhabits.databinding.ShowHabitTargetBinding
import org.isoron.uhabits.utils.toThemedAndroidColor
import java.util.ArrayList
import java.util.Calendar
data class TargetCardViewModel(
val color: PaletteColor,

@ -19,11 +19,10 @@
package org.isoron.uhabits.activities.intro
import android.graphics.*
import android.os.*
import com.github.paolorotolo.appintro.*
import android.graphics.Color
import android.os.Bundle
import com.github.paolorotolo.appintro.AppIntro2
import com.github.paolorotolo.appintro.AppIntroFragment
import org.isoron.uhabits.R
/**
@ -34,17 +33,32 @@ class IntroActivity : AppIntro2() {
override fun init(savedInstanceState: Bundle?) {
showStatusBar(false)
addSlide(AppIntroFragment.newInstance(getString(R.string.intro_title_1),
getString(R.string.intro_description_1), R.drawable.intro_icon_1,
Color.parseColor("#194673")))
addSlide(
AppIntroFragment.newInstance(
getString(R.string.intro_title_1),
getString(R.string.intro_description_1),
R.drawable.intro_icon_1,
Color.parseColor("#194673")
)
)
addSlide(AppIntroFragment.newInstance(getString(R.string.intro_title_2),
getString(R.string.intro_description_2), R.drawable.intro_icon_2,
Color.parseColor("#ffa726")))
addSlide(
AppIntroFragment.newInstance(
getString(R.string.intro_title_2),
getString(R.string.intro_description_2),
R.drawable.intro_icon_2,
Color.parseColor("#ffa726")
)
)
addSlide(AppIntroFragment.newInstance(getString(R.string.intro_title_4),
getString(R.string.intro_description_4), R.drawable.intro_icon_4,
Color.parseColor("#9575cd")))
addSlide(
AppIntroFragment.newInstance(
getString(R.string.intro_title_4),
getString(R.string.intro_description_4),
R.drawable.intro_icon_4,
Color.parseColor("#9575cd")
)
)
}
override fun onNextPressed() {}

@ -18,14 +18,15 @@
*/
package org.isoron.uhabits.activities.settings
import android.os.*
import android.view.*
import androidx.appcompat.app.*
import org.isoron.uhabits.*
import org.isoron.uhabits.activities.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.databinding.*
import org.isoron.uhabits.utils.*
import android.os.Bundle
import android.view.LayoutInflater
import androidx.appcompat.app.AppCompatActivity
import org.isoron.uhabits.HabitsApplication
import org.isoron.uhabits.R
import org.isoron.uhabits.activities.AndroidThemeSwitcher
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.databinding.SettingsActivityBinding
import org.isoron.uhabits.utils.setupToolbar
class SettingsActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {

@ -19,25 +19,31 @@
package org.isoron.uhabits.activities.sync
import android.content.*
import android.content.ClipData
import android.content.ClipboardManager
import android.graphics.*
import android.os.*
import android.text.*
import android.view.*
import androidx.appcompat.app.*
import com.google.zxing.*
import com.google.zxing.qrcode.*
import kotlinx.coroutines.*
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Color
import android.os.Bundle
import android.text.Html
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.google.zxing.BarcodeFormat
import com.google.zxing.qrcode.QRCodeWriter
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.invoke
import kotlinx.coroutines.launch
import org.isoron.uhabits.HabitsApplication
import org.isoron.uhabits.R
import org.isoron.uhabits.activities.AndroidThemeSwitcher
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.ui.screens.sync.SyncBehavior
import org.isoron.uhabits.databinding.ActivitySyncBinding
import org.isoron.uhabits.sync.RemoteSyncServer
import org.isoron.uhabits.utils.InterfaceUtils.getFontAwesome
import org.isoron.uhabits.*
import org.isoron.uhabits.activities.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.ui.screens.sync.*
import org.isoron.uhabits.databinding.*
import org.isoron.uhabits.sync.*
import org.isoron.uhabits.utils.*
import org.isoron.uhabits.utils.setupToolbar
import org.isoron.uhabits.utils.showMessage
class SyncActivity : AppCompatActivity(), SyncBehavior.Screen {
@ -100,7 +106,6 @@ class SyncActivity : AppCompatActivity(), SyncBehavior.Screen {
binding.progress.visibility = View.GONE
binding.qrCode.visibility = View.VISIBLE
binding.qrCode.setImageBitmap(generateQR(msg))
}
override suspend fun showLoadingScreen() {

@ -19,11 +19,11 @@
package org.isoron.uhabits.automation
import android.os.*
import androidx.appcompat.app.*
import org.isoron.uhabits.*
import org.isoron.uhabits.activities.*
import org.isoron.uhabits.core.models.*
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import org.isoron.uhabits.HabitsApplication
import org.isoron.uhabits.activities.AndroidThemeSwitcher
import org.isoron.uhabits.core.models.HabitMatcherBuilder
class EditSettingActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
@ -33,7 +33,8 @@ class EditSettingActivity : AppCompatActivity() {
HabitMatcherBuilder()
.setArchivedAllowed(false)
.setCompletedAllowed(true)
.build())
.build()
)
AndroidThemeSwitcher(this, app.component.preferences).apply()
val args = SettingUtils.parseIntent(this.intent, habits)

@ -19,11 +19,11 @@
package org.isoron.uhabits.automation
import android.app.*
import android.content.*
import android.os.*
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.models.Habit
class EditSettingController(private val activity: Activity) {
@ -36,10 +36,13 @@ class EditSettingController(private val activity: Activity) {
bundle.putInt("action", action)
bundle.putLong("habit", habit.id!!)
activity.setResult(Activity.RESULT_OK, Intent().apply {
activity.setResult(
Activity.RESULT_OK,
Intent().apply {
putExtra(EXTRA_STRING_BLURB, blurb)
putExtra(EXTRA_BUNDLE, bundle)
})
}
)
activity.finish()
}

@ -19,16 +19,22 @@
package org.isoron.uhabits.automation
import android.R.layout.*
import android.annotation.*
import android.content.*
import android.view.*
import android.widget.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.databinding.*
import org.isoron.uhabits.utils.*
import java.util.*
import android.R.layout.simple_spinner_dropdown_item
import android.R.layout.simple_spinner_item
import android.annotation.SuppressLint
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.FrameLayout
import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitList
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.databinding.AutomationBinding
import org.isoron.uhabits.utils.setupToolbar
import java.util.LinkedList
@SuppressLint("ViewConstructor")
class EditSettingRootView(

@ -19,14 +19,16 @@
package org.isoron.uhabits.automation
import android.content.*
import dagger.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.ui.widgets.*
import org.isoron.uhabits.core.utils.*
import org.isoron.uhabits.inject.*
import org.isoron.uhabits.receivers.*
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import dagger.Component
import org.isoron.uhabits.HabitsApplication
import org.isoron.uhabits.core.models.HabitList
import org.isoron.uhabits.core.ui.widgets.WidgetBehavior
import org.isoron.uhabits.core.utils.DateUtils
import org.isoron.uhabits.inject.HabitsApplicationComponent
import org.isoron.uhabits.receivers.ReceiverScope
const val ACTION_CHECK = 0
const val ACTION_UNCHECK = 1

@ -19,7 +19,7 @@
package org.isoron.uhabits.database
import org.isoron.uhabits.core.database.*
import org.isoron.uhabits.core.database.Cursor
class AndroidCursor(private val cursor: android.database.Cursor) : Cursor {

@ -19,10 +19,10 @@
package org.isoron.uhabits.database
import android.content.*
import android.database.sqlite.*
import org.isoron.uhabits.core.database.*
import java.io.*
import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase
import org.isoron.uhabits.core.database.Database
import java.io.File
class AndroidDatabase(
private val db: SQLiteDatabase,

@ -19,10 +19,10 @@
package org.isoron.uhabits.database
import android.database.sqlite.*
import org.isoron.uhabits.core.database.*
import java.io.*
import javax.inject.*
import android.database.sqlite.SQLiteDatabase
import org.isoron.uhabits.core.database.DatabaseOpener
import java.io.File
import javax.inject.Inject
class AndroidDatabaseOpener @Inject constructor() : DatabaseOpener {
override fun open(file: File): AndroidDatabase {
@ -32,6 +32,7 @@ class AndroidDatabaseOpener @Inject constructor() : DatabaseOpener {
null,
SQLiteDatabase.OPEN_READWRITE,
),
file = file)
file = file
)
}
}

@ -19,12 +19,12 @@
package org.isoron.uhabits.database
import android.content.*
import android.util.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.utils.*
import org.isoron.uhabits.utils.*
import java.io.*
import android.content.Context
import android.util.Log
import org.isoron.uhabits.AndroidDirFinder
import org.isoron.uhabits.core.utils.DateUtils
import org.isoron.uhabits.utils.DatabaseUtils
import java.io.File
class AutoBackup(private val context: Context) {
@ -33,7 +33,7 @@ class AutoBackup(private val context: Context) {
fun run(keep: Int = 5) {
Log.i("AutoBackup", "Starting automatic backups...")
val files = listBackupFiles()
var newestTimestamp = 0L;
var newestTimestamp = 0L
if (files.isNotEmpty()) {
newestTimestamp = files.last().lastModified()
}

@ -18,7 +18,7 @@
*/
package org.isoron.uhabits.inject
import javax.inject.*
import javax.inject.Qualifier
@Qualifier
@MustBeDocumented

@ -18,7 +18,7 @@
*/
package org.isoron.uhabits.inject
import javax.inject.*
import javax.inject.Scope
/**
* Scope used by objects that live as long as the activity is alive.

@ -19,8 +19,9 @@
package org.isoron.uhabits.inject
import dagger.*
import org.isoron.uhabits.core.models.*
import dagger.Module
import dagger.Provides
import org.isoron.uhabits.core.models.Habit
@Module
class HabitModule(private val habit: Habit) {

@ -19,20 +19,27 @@
package org.isoron.uhabits.inject
import dagger.*
import org.isoron.uhabits.activities.common.dialogs.*
import org.isoron.uhabits.activities.habits.list.*
import org.isoron.uhabits.activities.habits.list.views.*
import org.isoron.uhabits.core.ui.*
import org.isoron.uhabits.core.ui.screens.habits.list.*
import dagger.Component
import org.isoron.uhabits.activities.common.dialogs.ColorPickerDialogFactory
import org.isoron.uhabits.activities.habits.list.ListHabitsMenu
import org.isoron.uhabits.activities.habits.list.ListHabitsModule
import org.isoron.uhabits.activities.habits.list.ListHabitsRootView
import org.isoron.uhabits.activities.habits.list.ListHabitsScreen
import org.isoron.uhabits.activities.habits.list.ListHabitsSelectionMenu
import org.isoron.uhabits.activities.habits.list.views.HabitCardListAdapter
import org.isoron.uhabits.core.ui.ThemeSwitcher
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
@ActivityScope
@Component(modules = arrayOf(
@Component(
modules = arrayOf(
ActivityContextModule::class,
HabitsActivityModule::class,
ListHabitsModule::class,
HabitModule::class
), dependencies = arrayOf(HabitsApplicationComponent::class))
),
dependencies = arrayOf(HabitsApplicationComponent::class)
)
interface HabitsActivityComponent {
val colorPickerDialogFactory: ColorPickerDialogFactory
val habitCardListAdapter: HabitCardListAdapter

@ -19,11 +19,12 @@
package org.isoron.uhabits.inject
import android.content.*
import dagger.*
import org.isoron.uhabits.activities.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.ui.*
import android.content.Context
import dagger.Module
import dagger.Provides
import org.isoron.uhabits.activities.AndroidThemeSwitcher
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.ui.ThemeSwitcher
@Module
class HabitsActivityModule {

@ -19,28 +19,35 @@
package org.isoron.uhabits.inject
import android.content.*
import dagger.*
import org.isoron.uhabits.core.*
import org.isoron.uhabits.core.commands.*
import org.isoron.uhabits.core.database.*
import org.isoron.uhabits.core.io.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.models.sqlite.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.reminders.*
import org.isoron.uhabits.core.sync.*
import org.isoron.uhabits.core.tasks.*
import org.isoron.uhabits.core.ui.*
import org.isoron.uhabits.database.*
import org.isoron.uhabits.inject.*
import org.isoron.uhabits.intents.*
import org.isoron.uhabits.io.*
import org.isoron.uhabits.notifications.*
import org.isoron.uhabits.preferences.*
import org.isoron.uhabits.sync.*
import org.isoron.uhabits.utils.*
import java.io.*
import android.content.Context
import dagger.Module
import dagger.Provides
import org.isoron.uhabits.core.AppScope
import org.isoron.uhabits.core.commands.CommandRunner
import org.isoron.uhabits.core.database.Database
import org.isoron.uhabits.core.database.DatabaseOpener
import org.isoron.uhabits.core.io.Logging
import org.isoron.uhabits.core.models.HabitList
import org.isoron.uhabits.core.models.ModelFactory
import org.isoron.uhabits.core.models.sqlite.SQLModelFactory
import org.isoron.uhabits.core.models.sqlite.SQLiteHabitList
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.preferences.WidgetPreferences
import org.isoron.uhabits.core.reminders.ReminderScheduler
import org.isoron.uhabits.core.sync.AbstractSyncServer
import org.isoron.uhabits.core.sync.NetworkManager
import org.isoron.uhabits.core.tasks.TaskRunner
import org.isoron.uhabits.core.ui.NotificationTray
import org.isoron.uhabits.database.AndroidDatabase
import org.isoron.uhabits.database.AndroidDatabaseOpener
import org.isoron.uhabits.intents.IntentScheduler
import org.isoron.uhabits.io.AndroidLogging
import org.isoron.uhabits.notifications.AndroidNotificationTray
import org.isoron.uhabits.preferences.SharedPreferencesStorage
import org.isoron.uhabits.sync.AndroidNetworkManager
import org.isoron.uhabits.sync.RemoteSyncServer
import org.isoron.uhabits.utils.DatabaseUtils
import java.io.File
@Module
class HabitsModule(dbFile: File) {
@ -115,7 +122,7 @@ class HabitsModule(dbFile: File) {
@Provides
@AppScope
fun getSyncServer(preferences: Preferences) : AbstractSyncServer {
fun getSyncServer(preferences: Preferences): AbstractSyncServer {
return RemoteSyncServer(preferences)
}
@ -125,4 +132,3 @@ class HabitsModule(dbFile: File) {
return db
}
}

@ -19,17 +19,18 @@
package org.isoron.uhabits.intents
import android.content.*
import android.net.*
import org.isoron.uhabits.*
import org.isoron.uhabits.activities.about.*
import org.isoron.uhabits.activities.habits.edit.*
import org.isoron.uhabits.activities.habits.show.*
import org.isoron.uhabits.activities.intro.*
import org.isoron.uhabits.activities.settings.*
import org.isoron.uhabits.activities.sync.*
import org.isoron.uhabits.core.models.*
import javax.inject.*
import android.content.Context
import android.content.Intent
import android.net.Uri
import org.isoron.uhabits.R
import org.isoron.uhabits.activities.about.AboutActivity
import org.isoron.uhabits.activities.habits.edit.EditHabitActivity
import org.isoron.uhabits.activities.habits.show.ShowHabitActivity
import org.isoron.uhabits.activities.intro.IntroActivity
import org.isoron.uhabits.activities.settings.SettingsActivity
import org.isoron.uhabits.activities.sync.SyncActivity
import org.isoron.uhabits.core.models.Habit
import javax.inject.Inject
class IntentFactory
@Inject constructor() {

@ -19,13 +19,15 @@
package org.isoron.uhabits.intents
import android.content.*
import android.content.ContentUris.*
import android.net.*
import org.isoron.uhabits.core.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.utils.*
import javax.inject.*
import android.content.ContentUris.parseId
import android.content.Intent
import android.net.Uri
import org.isoron.uhabits.core.AppScope
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitList
import org.isoron.uhabits.core.models.Timestamp
import org.isoron.uhabits.core.utils.DateUtils
import javax.inject.Inject
@AppScope
class IntentParser
@ -37,13 +39,13 @@ class IntentParser
}
fun copyIntentData(source: Intent, destination: Intent) {
destination.data = source.data;
destination.data = source.data
destination.putExtra("timestamp", source.getLongExtra("timestamp", DateUtils.getTodayWithOffset().unixTime))
}
private fun parseHabit(uri: Uri): Habit {
val habit = habits.getById(parseId(uri)) ?:
throw IllegalArgumentException("habit not found")
val habit = habits.getById(parseId(uri))
?: throw IllegalArgumentException("habit not found")
return habit
}

@ -19,20 +19,23 @@
package org.isoron.uhabits.intents
import android.app.*
import android.app.AlarmManager.*
import android.content.*
import android.content.Context.*
import android.os.Build.VERSION.*
import android.os.Build.VERSION_CODES.*
import android.util.*
import org.isoron.uhabits.core.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.reminders.ReminderScheduler.*
import org.isoron.uhabits.core.utils.*
import org.isoron.uhabits.inject.*
import java.util.*
import javax.inject.*
import android.app.AlarmManager
import android.app.AlarmManager.RTC
import android.app.AlarmManager.RTC_WAKEUP
import android.app.PendingIntent
import android.content.Context
import android.content.Context.ALARM_SERVICE
import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES.M
import android.util.Log
import org.isoron.uhabits.core.AppScope
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.reminders.ReminderScheduler.SchedulerResult
import org.isoron.uhabits.core.reminders.ReminderScheduler.SystemScheduler
import org.isoron.uhabits.core.utils.DateFormats
import org.isoron.uhabits.inject.AppContext
import java.util.Date
import javax.inject.Inject
@AppScope
class IntentScheduler
@ -48,8 +51,10 @@ class IntentScheduler
val now = System.currentTimeMillis()
Log.d("IntentScheduler", "timestamp=$timestamp now=$now")
if (timestamp < now) {
Log.e("IntentScheduler",
"Ignoring attempt to schedule intent in the past.")
Log.e(
"IntentScheduler",
"Ignoring attempt to schedule intent in the past."
)
return SchedulerResult.IGNORED
}
if (SDK_INT >= M)
@ -59,9 +64,11 @@ class IntentScheduler
return SchedulerResult.OK
}
override fun scheduleShowReminder(reminderTime: Long,
override fun scheduleShowReminder(
reminderTime: Long,
habit: Habit,
timestamp: Long): SchedulerResult {
timestamp: Long
): SchedulerResult {
val intent = pendingIntents.showReminder(habit, reminderTime, timestamp)
logReminderScheduled(habit, reminderTime)
return schedule(reminderTime, intent, RTC_WAKEUP)
@ -81,7 +88,9 @@ class IntentScheduler
val name = habit.name.substring(0, min)
val df = DateFormats.getBackupDateFormat()
val time = df.format(Date(reminderTime))
Log.i("ReminderHelper",
String.format("Setting alarm (%s): %s", time, name))
Log.i(
"ReminderHelper",
String.format("Setting alarm (%s): %s", time, name)
)
}
}

@ -19,61 +19,77 @@
package org.isoron.uhabits.intents
import android.app.*
import android.app.PendingIntent.*
import android.content.*
import android.net.*
import org.isoron.uhabits.core.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.inject.*
import org.isoron.uhabits.receivers.*
import javax.inject.*
import android.app.PendingIntent
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.app.PendingIntent.getBroadcast
import android.content.Context
import android.content.Intent
import android.net.Uri
import org.isoron.uhabits.core.AppScope
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.Timestamp
import org.isoron.uhabits.inject.AppContext
import org.isoron.uhabits.receivers.ReminderReceiver
import org.isoron.uhabits.receivers.WidgetReceiver
import javax.inject.Inject
@AppScope
class PendingIntentFactory
@Inject constructor(
@AppContext private val context: Context,
private val intentFactory: IntentFactory) {
private val intentFactory: IntentFactory
) {
fun addCheckmark(habit: Habit, timestamp: Timestamp?): PendingIntent =
PendingIntent.getBroadcast(
context, 1,
context,
1,
Intent(context, WidgetReceiver::class.java).apply {
data = Uri.parse(habit.uriString)
action = WidgetReceiver.ACTION_ADD_REPETITION
if (timestamp != null) putExtra("timestamp", timestamp.unixTime)
},
FLAG_UPDATE_CURRENT)
FLAG_UPDATE_CURRENT
)
fun dismissNotification(habit: Habit): PendingIntent =
PendingIntent.getBroadcast(
context, 0,
context,
0,
Intent(context, ReminderReceiver::class.java).apply {
action = WidgetReceiver.ACTION_DISMISS_REMINDER
data = Uri.parse(habit.uriString)
},
FLAG_UPDATE_CURRENT)
FLAG_UPDATE_CURRENT
)
fun removeRepetition(habit: Habit): PendingIntent =
PendingIntent.getBroadcast(
context, 3,
context,
3,
Intent(context, WidgetReceiver::class.java).apply {
action = WidgetReceiver.ACTION_REMOVE_REPETITION
data = Uri.parse(habit.uriString)
},
FLAG_UPDATE_CURRENT)
FLAG_UPDATE_CURRENT
)
fun showHabit(habit: Habit): PendingIntent =
androidx.core.app.TaskStackBuilder
.create(context)
.addNextIntentWithParentStack(
intentFactory.startShowHabitActivity(
context, habit))
context,
habit
)
)
.getPendingIntent(0, FLAG_UPDATE_CURRENT)!!
fun showReminder(habit: Habit,
fun showReminder(
habit: Habit,
reminderTime: Long?,
timestamp: Long): PendingIntent =
timestamp: Long
): PendingIntent =
PendingIntent.getBroadcast(
context,
(habit.id!! % Integer.MAX_VALUE).toInt() + 1,
@ -83,47 +99,58 @@ class PendingIntentFactory
putExtra("timestamp", timestamp)
putExtra("reminderTime", reminderTime)
},
FLAG_UPDATE_CURRENT)
FLAG_UPDATE_CURRENT
)
fun snoozeNotification(habit: Habit): PendingIntent =
PendingIntent.getBroadcast(
context, 0,
context,
0,
Intent(context, ReminderReceiver::class.java).apply {
data = Uri.parse(habit.uriString)
action = ReminderReceiver.ACTION_SNOOZE_REMINDER
},
FLAG_UPDATE_CURRENT)
FLAG_UPDATE_CURRENT
)
fun toggleCheckmark(habit: Habit, timestamp: Long?): PendingIntent =
PendingIntent.getBroadcast(
context, 2,
context,
2,
Intent(context, WidgetReceiver::class.java).apply {
data = Uri.parse(habit.uriString)
action = WidgetReceiver.ACTION_TOGGLE_REPETITION
if (timestamp != null) putExtra("timestamp", timestamp)
},
FLAG_UPDATE_CURRENT)
FLAG_UPDATE_CURRENT
)
fun setNumericalValue(widgetContext: Context,
fun setNumericalValue(
widgetContext: Context,
habit: Habit,
numericalValue: Int,
timestamp: Long?):
timestamp: Long?
):
PendingIntent =
getBroadcast(
widgetContext, 2,
widgetContext,
2,
Intent(widgetContext, WidgetReceiver::class.java).apply {
data = Uri.parse(habit.uriString)
action = WidgetReceiver.ACTION_SET_NUMERICAL_VALUE
putExtra("numericalValue", numericalValue);
putExtra("numericalValue", numericalValue)
if (timestamp != null) putExtra("timestamp", timestamp)
},
FLAG_UPDATE_CURRENT)
FLAG_UPDATE_CURRENT
)
fun updateWidgets(): PendingIntent =
PendingIntent.getBroadcast(
context, 0,
context,
0,
Intent(context, WidgetReceiver::class.java).apply {
action = WidgetReceiver.ACTION_UPDATE_WIDGETS_VALUE
},
FLAG_UPDATE_CURRENT)
FLAG_UPDATE_CURRENT
)
}

@ -19,8 +19,9 @@
package org.isoron.uhabits.io
import android.util.*
import org.isoron.uhabits.core.io.*
import android.util.Log
import org.isoron.uhabits.core.io.Logger
import org.isoron.uhabits.core.io.Logging
class AndroidLogging : Logging {
override fun getLogger(name: String): Logger {
@ -44,5 +45,4 @@ class AndroidLogger(val name: String) : Logger {
override fun error(exception: Exception) {
Log.e(name, "Exception", exception)
}
}

@ -19,23 +19,29 @@
package org.isoron.uhabits.notifications
import android.app.*
import android.content.*
import android.graphics.*
import android.graphics.BitmapFactory.*
import android.os.*
import android.os.Build.VERSION.*
import android.util.*
import androidx.core.app.*
import androidx.core.app.NotificationCompat.*
import android.app.Activity
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.graphics.BitmapFactory.decodeResource
import android.graphics.Color
import android.os.Build
import android.os.Build.VERSION.SDK_INT
import android.util.Log
import androidx.core.app.NotificationCompat.Action
import androidx.core.app.NotificationCompat.Builder
import androidx.core.app.NotificationCompat.WearableExtender
import androidx.core.app.NotificationManagerCompat
import org.isoron.uhabits.R
import org.isoron.uhabits.core.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.ui.*
import org.isoron.uhabits.inject.*
import org.isoron.uhabits.intents.*
import javax.inject.*
import org.isoron.uhabits.core.AppScope
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.Timestamp
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.ui.NotificationTray
import org.isoron.uhabits.inject.AppContext
import org.isoron.uhabits.intents.PendingIntentFactory
import javax.inject.Inject
@AppScope
class AndroidNotificationTray
@ -57,10 +63,12 @@ class AndroidNotificationTray
active.remove(id)
}
override fun showNotification(habit: Habit,
override fun showNotification(
habit: Habit,
notificationId: Int,
timestamp: Timestamp,
reminderTime: Long) {
reminderTime: Long
) {
val notificationManager = NotificationManagerCompat.from(context)
val notification = buildNotification(habit, reminderTime, timestamp)
createAndroidNotificationChannel(context)
@ -68,37 +76,45 @@ class AndroidNotificationTray
notificationManager.notify(notificationId, notification)
} catch (e: RuntimeException) {
// Some Xiaomi phones produce a RuntimeException if custom notification sounds are used.
Log.i("AndroidNotificationTray",
"Failed to show notification. Retrying without sound.")
val n = buildNotification(habit,
Log.i(
"AndroidNotificationTray",
"Failed to show notification. Retrying without sound."
)
val n = buildNotification(
habit,
reminderTime,
timestamp,
disableSound = true)
disableSound = true
)
notificationManager.notify(notificationId, n)
}
active.add(notificationId)
}
fun buildNotification(habit: Habit,
fun buildNotification(
habit: Habit,
reminderTime: Long,
timestamp: Timestamp,
disableSound: Boolean = false): Notification {
disableSound: Boolean = false
): Notification {
val addRepetitionAction = Action(
R.drawable.ic_action_check,
context.getString(R.string.yes),
pendingIntents.addCheckmark(habit, timestamp))
pendingIntents.addCheckmark(habit, timestamp)
)
val removeRepetitionAction = Action(
R.drawable.ic_action_cancel,
context.getString(R.string.no),
pendingIntents.removeRepetition(habit))
pendingIntents.removeRepetition(habit)
)
val enterAction = Action(
R.drawable.ic_action_check,
context.getString(R.string.enter),
pendingIntents.setNumericalValue(context, habit, 0, null))
pendingIntents.setNumericalValue(context, habit, 0, null)
)
val wearableBg = decodeResource(context.resources, R.drawable.stripe)
@ -111,7 +127,7 @@ class AndroidNotificationTray
val builder = Builder(context, REMINDERS_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(habit.name)
.setContentText(if(habit.question.isBlank()) defaultText else habit.question)
.setContentText(if (habit.question.isBlank()) defaultText else habit.question)
.setContentIntent(pendingIntents.showHabit(habit))
.setDeleteIntent(pendingIntents.dismissNotification(habit))
.setSound(null)
@ -137,9 +153,11 @@ class AndroidNotificationTray
if (preferences.shouldMakeNotificationsLed())
builder.setLights(Color.RED, 1000, 1000)
val snoozeAction = Action(R.drawable.ic_action_snooze,
val snoozeAction = Action(
R.drawable.ic_action_snooze,
context.getString(R.string.snooze),
pendingIntents.snoozeNotification(habit))
pendingIntents.snoozeNotification(habit)
)
wearableExtender.addAction(snoozeAction)
builder.addAction(snoozeAction)
@ -153,12 +171,13 @@ class AndroidNotificationTray
val notificationManager = context.getSystemService(Activity.NOTIFICATION_SERVICE)
as NotificationManager
if (SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(REMINDERS_CHANNEL_ID,
val channel = NotificationChannel(
REMINDERS_CHANNEL_ID,
context.resources.getString(R.string.reminder),
NotificationManager.IMPORTANCE_DEFAULT)
NotificationManager.IMPORTANCE_DEFAULT
)
notificationManager.createNotificationChannel(channel)
}
}
}
}

@ -19,15 +19,18 @@
package org.isoron.uhabits.notifications
import android.content.*
import android.media.RingtoneManager.*
import android.net.*
import android.preference.*
import android.provider.*
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.media.RingtoneManager.EXTRA_RINGTONE_PICKED_URI
import android.media.RingtoneManager.getRingtone
import android.net.Uri
import android.preference.PreferenceManager
import android.provider.Settings
import org.isoron.uhabits.R
import org.isoron.uhabits.core.*
import org.isoron.uhabits.inject.*
import javax.inject.*
import org.isoron.uhabits.core.AppScope
import org.isoron.uhabits.inject.AppContext
import javax.inject.Inject
@AppScope
class RingtoneManager
@ -54,8 +57,10 @@ class RingtoneManager
fun getURI(): Uri? {
var ringtoneUri: Uri? = null
val defaultRingtoneUri = Settings.System.DEFAULT_NOTIFICATION_URI
val prefRingtoneUri = prefs.getString("pref_ringtone_uri",
defaultRingtoneUri.toString())!!
val prefRingtoneUri = prefs.getString(
"pref_ringtone_uri",
defaultRingtoneUri.toString()
)!!
if (prefRingtoneUri.isNotEmpty())
ringtoneUri = Uri.parse(prefRingtoneUri)

@ -19,13 +19,14 @@
package org.isoron.uhabits.preferences
import android.content.*
import android.preference.*
import android.content.Context
import android.content.SharedPreferences
import android.preference.PreferenceManager
import org.isoron.uhabits.R
import org.isoron.uhabits.core.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.inject.*
import javax.inject.*
import org.isoron.uhabits.core.AppScope
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.inject.AppContext
import javax.inject.Inject
@AppScope
class SharedPreferencesStorage
@ -76,8 +77,10 @@ class SharedPreferencesStorage
override fun remove(key: String) =
sharedPrefs.edit().remove(key).apply()
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences,
key: String) {
override fun onSharedPreferenceChanged(
sharedPreferences: SharedPreferences,
key: String
) {
val preferences = this.preferences ?: return
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
when (key) {

@ -19,9 +19,11 @@
package org.isoron.uhabits.sync
import android.content.*
import android.net.*
import org.isoron.uhabits.core.sync.*
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkRequest
import org.isoron.uhabits.core.sync.NetworkManager
class AndroidNetworkManager(
val context: Context,

@ -19,15 +19,26 @@
package org.isoron.uhabits.sync
import android.util.*
import io.ktor.client.*
import io.ktor.client.engine.android.*
import io.ktor.client.features.*
import io.ktor.client.features.json.*
import io.ktor.client.request.*
import kotlinx.coroutines.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.sync.*
import android.util.Log
import io.ktor.client.HttpClient
import io.ktor.client.engine.android.Android
import io.ktor.client.features.ClientRequestException
import io.ktor.client.features.ServerResponseException
import io.ktor.client.features.json.JsonFeature
import io.ktor.client.request.get
import io.ktor.client.request.header
import io.ktor.client.request.post
import io.ktor.client.request.put
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.invoke
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.sync.AbstractSyncServer
import org.isoron.uhabits.core.sync.EditConflictException
import org.isoron.uhabits.core.sync.GetDataVersionResponse
import org.isoron.uhabits.core.sync.KeyNotFoundException
import org.isoron.uhabits.core.sync.RegisterReponse
import org.isoron.uhabits.core.sync.ServiceUnavailable
import org.isoron.uhabits.core.sync.SyncData
class RemoteSyncServer(
private val preferences: Preferences,
@ -42,7 +53,7 @@ class RemoteSyncServer(
Log.i("RemoteSyncServer", "POST $url")
val response: RegisterReponse = httpClient.post(url)
return@IO response.key
} catch(e: ServerResponseException) {
} catch (e: ServerResponseException) {
throw ServiceUnavailable()
}
}
@ -59,8 +70,8 @@ class RemoteSyncServer(
throw ServiceUnavailable()
} catch (e: ClientRequestException) {
Log.w("RemoteSyncServer", "ClientRequestException", e)
if(e.message!!.contains("409")) throw EditConflictException()
if(e.message!!.contains("404")) throw KeyNotFoundException()
if (e.message!!.contains("409")) throw EditConflictException()
if (e.message!!.contains("404")) throw KeyNotFoundException()
throw e
}
}
@ -85,7 +96,7 @@ class RemoteSyncServer(
Log.i("RemoteSyncServer", "GET $url")
val response: GetDataVersionResponse = httpClient.get(url)
return@IO response.version
} catch(e: ServerResponseException) {
} catch (e: ServerResponseException) {
throw ServiceUnavailable()
} catch (e: ClientRequestException) {
Log.w("RemoteSyncServer", "ClientRequestException", e)

@ -19,14 +19,17 @@
package org.isoron.uhabits.utils
import android.content.*
import android.content.Context
import android.text.format.DateFormat
import org.isoron.uhabits.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.utils.*
import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.WeekdayList
import org.isoron.uhabits.core.utils.DateFormats
import org.isoron.uhabits.core.utils.DateUtils
import java.text.*
import java.util.*
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import java.util.Locale
import java.util.TimeZone
fun String.toSimpleDataFormat(): SimpleDateFormat {
val locale = Locale.getDefault()

@ -20,8 +20,11 @@ package org.isoron.uhabits.utils
import android.os.Environment
import android.util.Log
import java.io.*
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.InputStream
import java.io.OutputStream
fun File.copyTo(dst: File) {
val inStream = FileInputStream(this)
@ -50,7 +53,7 @@ object FileUtils {
Log.e("FileUtils", "getDir: all potential parents are null or non-writable")
return null
}
val dir = File("${chosenDir.absolutePath}/${relativePath}/")
val dir = File("${chosenDir.absolutePath}/$relativePath/")
if (!dir.exists() && !dir.mkdirs()) {
Log.e("FileUtils", "getDir: chosen dir does not exist and cannot be created")
return null

@ -18,13 +18,14 @@
*/
package org.isoron.uhabits.utils
import android.content.*
import android.graphics.*
import android.util.*
import android.view.*
import android.widget.*
import android.widget.TextView.*
import androidx.core.view.*
import android.content.Context
import android.graphics.Typeface
import android.util.TypedValue
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import android.widget.TextView.OnEditorActionListener
import androidx.core.view.ViewCompat
object InterfaceUtils {
private var fontAwesome: Typeface? = null
@ -46,17 +47,21 @@ object InterfaceUtils {
@JvmStatic
fun dpToPixels(context: Context, dp: Float): Float {
if (fixedResolution != null) return dp * fixedResolution!!
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dp,
context.resources.displayMetrics)
context.resources.displayMetrics
)
}
@JvmStatic
fun spToPixels(context: Context, sp: Float): Float {
if (fixedResolution != null) return sp * fixedResolution!!
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP,
sp,
context.resources.displayMetrics)
context.resources.displayMetrics
)
}
@JvmStatic
@ -69,8 +74,10 @@ object InterfaceUtils {
return dim
}
fun setupEditorAction(parent: ViewGroup,
listener: OnEditorActionListener) {
fun setupEditorAction(
parent: ViewGroup,
listener: OnEditorActionListener
) {
for (i in 0 until parent.childCount) {
val child = parent.getChildAt(i)
if (child is ViewGroup) setupEditorAction(child, listener)

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

Loading…
Cancel
Save