Finish converting data module to kmp

pull/2898/head
Alex Baker 2 years ago
parent 19de0e08a5
commit 4ddfe937b0

@ -193,7 +193,7 @@ class TaskCreator @Inject constructor(
} catch (e: Throwable) { } catch (e: Throwable) {
Timber.e(e) Timber.e(e)
} }
task.setTags(tags) task.putTransitory(Tag.KEY, tags)
return task return task
} }

@ -17,6 +17,7 @@ import org.tasks.data.entity.CaldavAccount
import org.tasks.data.entity.CaldavCalendar import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.entity.Task import org.tasks.data.entity.Task
import org.tasks.data.inTransaction import org.tasks.data.inTransaction
import org.tasks.data.pictureUri
import org.tasks.data.withTransaction import org.tasks.data.withTransaction
import org.tasks.files.FileHelper import org.tasks.files.FileHelper
import org.tasks.location.GeofenceApi import org.tasks.location.GeofenceApi

@ -20,6 +20,7 @@ import org.tasks.caldav.iCalendar.Companion.order
import org.tasks.caldav.iCalendar.Companion.parent import org.tasks.caldav.iCalendar.Companion.parent
import org.tasks.data.CaldavTaskContainer import org.tasks.data.CaldavTaskContainer
import org.tasks.data.Location import org.tasks.data.Location
import org.tasks.data.convertPictureUri
import org.tasks.data.dao.CaldavDao import org.tasks.data.dao.CaldavDao
import org.tasks.data.dao.FilterDao import org.tasks.data.dao.FilterDao
import org.tasks.data.dao.GoogleTaskListDao import org.tasks.data.dao.GoogleTaskListDao

@ -22,6 +22,7 @@ import kotlinx.serialization.json.jsonPrimitive
import org.tasks.LocalBroadcastManager import org.tasks.LocalBroadcastManager
import org.tasks.R import org.tasks.R
import org.tasks.caldav.VtodoCache import org.tasks.caldav.VtodoCache
import org.tasks.data.convertPictureUri
import org.tasks.data.dao.AlarmDao import org.tasks.data.dao.AlarmDao
import org.tasks.data.dao.CaldavDao import org.tasks.data.dao.CaldavDao
import org.tasks.data.dao.FilterDao import org.tasks.data.dao.FilterDao

@ -2,7 +2,11 @@ package org.tasks.compose.edit
import android.net.Uri import android.net.Uri
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -15,7 +19,8 @@ import org.tasks.R
import org.tasks.compose.DeleteButton import org.tasks.compose.DeleteButton
import org.tasks.compose.TaskEditRow import org.tasks.compose.TaskEditRow
import org.tasks.data.entity.UserActivity import org.tasks.data.entity.UserActivity
import java.util.* import org.tasks.data.pictureUri
import java.util.Locale
@Composable @Composable
fun CommentsRow( fun CommentsRow(

@ -0,0 +1,41 @@
package org.tasks.data
import android.net.Uri
import org.json.JSONException
import org.json.JSONObject
import org.tasks.data.entity.UserActivity
import timber.log.Timber
import java.io.File
fun UserActivity.setPicture(uri: Uri?) {
picture = uri?.toString()
}
val UserActivity.pictureUri: Uri?
get() = if (picture.isNullOrBlank()) null else Uri.parse(picture)
fun UserActivity.convertPictureUri() {
setPicture(getLegacyPictureUri(picture))
}
private fun getLegacyPictureUri(value: String?): Uri? {
return try {
if (value.isNullOrBlank()) {
return null
}
if (value.contains("uri") || value.contains("path")) {
val json = JSONObject(value)
if (json.has("uri")) {
return Uri.parse(json.getString("uri"))
}
if (json.has("path")) {
val path = json.getString("path")
return Uri.fromFile(File(path))
}
}
null
} catch (e: JSONException) {
Timber.e(e)
null
}
}

@ -57,6 +57,7 @@ import org.tasks.data.entity.Task.Companion.NOTIFY_MODE_NONSTOP
import org.tasks.data.entity.Task.Companion.hasDueTime import org.tasks.data.entity.Task.Companion.hasDueTime
import org.tasks.data.entity.TaskAttachment import org.tasks.data.entity.TaskAttachment
import org.tasks.data.entity.UserActivity import org.tasks.data.entity.UserActivity
import org.tasks.data.setPicture
import org.tasks.date.DateTimeUtils.toDateTime import org.tasks.date.DateTimeUtils.toDateTime
import org.tasks.files.FileHelper import org.tasks.files.FileHelper
import org.tasks.location.GeofenceApi import org.tasks.location.GeofenceApi

@ -2,6 +2,7 @@ plugins {
alias(libs.plugins.ksp).apply(false) alias(libs.plugins.ksp).apply(false)
alias(libs.plugins.androidLibrary).apply(false) alias(libs.plugins.androidLibrary).apply(false)
alias(libs.plugins.kotlinMultiplatform).apply(false) alias(libs.plugins.kotlinMultiplatform).apply(false)
alias(libs.plugins.jetbrains.kotlin.jvm) apply false
} }
buildscript { buildscript {

@ -27,6 +27,7 @@ kotlin {
} }
} }
} }
jvm()
sourceSets { sourceSets {
commonMain.dependencies { commonMain.dependencies {
implementation(libs.androidx.room) implementation(libs.androidx.room)
@ -34,6 +35,7 @@ kotlin {
implementation(libs.kermit) implementation(libs.kermit)
} }
} }
task("testClasses")
} }
android { android {
namespace = "org.tasks.data" namespace = "org.tasks.data"

@ -0,0 +1,13 @@
@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
package org.tasks
import android.os.Parcelable
import kotlinx.parcelize.RawValue
import org.tasks.data.BuildConfig
actual typealias CommonParcelable = Parcelable
actual typealias CommonRawValue = RawValue
actual val IS_DEBUG = BuildConfig.DEBUG

@ -1,7 +0,0 @@
@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
package org.tasks
import android.os.Parcelable
actual typealias CommonParcelable = Parcelable

@ -6,4 +6,10 @@ package org.tasks
@Retention(AnnotationRetention.BINARY) @Retention(AnnotationRetention.BINARY)
annotation class CommonParcelize annotation class CommonParcelize
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.BINARY)
expect annotation class CommonRawValue()
expect interface CommonParcelable expect interface CommonParcelable
expect val IS_DEBUG: Boolean

@ -114,7 +114,7 @@ fun SQLiteStatement.getTasks(): List<TaskContainer> {
remoteOrder = getLong(_cursorIndexOfRemoteOrder), remoteOrder = getLong(_cursorIndexOfRemoteOrder),
) )
} }
val accountType = getInt(_cursorIndexOfAccountType) val accountType = getIntOrNull(_cursorIndexOfAccountType) ?: 0
val geofence = getLongOrNull(_cursorIndexOfId_2)?.takeIf { it > 0 }?.let { val geofence = getLongOrNull(_cursorIndexOfId_2)?.takeIf { it > 0 }?.let {
Geofence( Geofence(
id = it, id = it,
@ -156,7 +156,7 @@ fun SQLiteStatement.getTasks(): List<TaskContainer> {
children = getIntOrNull(_cursorIndexOfChildren) ?: 0, children = getIntOrNull(_cursorIndexOfChildren) ?: 0,
primarySort = getLongOrNull(_cursorIndexOfPrimarySort) ?: 0, primarySort = getLongOrNull(_cursorIndexOfPrimarySort) ?: 0,
secondarySort = getLongOrNull(_cursorIndexOfSecondarySort) ?: 0, secondarySort = getLongOrNull(_cursorIndexOfSecondarySort) ?: 0,
parentComplete = getBoolean(_cursorIndexOfParentComplete), parentComplete = getBooleanOrNull(_cursorIndexOfParentComplete) ?: false,
) )
) )
} }
@ -171,3 +171,6 @@ private fun SQLiteStatement.getLongOrNull(index: Int): Long? =
private fun SQLiteStatement.getIntOrNull(index: Int): Int? = private fun SQLiteStatement.getIntOrNull(index: Int): Int? =
if (index == -1 || isNull(index)) null else this.getInt(index) if (index == -1 || isNull(index)) null else this.getInt(index)
private fun SQLiteStatement.getBooleanOrNull(index: Int): Boolean? =
if (index == -1 || isNull(index)) null else this.getBoolean(index)

@ -4,13 +4,13 @@ import androidx.room.Dao
import androidx.room.Delete import androidx.room.Delete
import androidx.room.Insert import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update import androidx.room.Update
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import org.tasks.data.CaldavFilters import org.tasks.data.CaldavFilters
import org.tasks.data.CaldavTaskContainer import org.tasks.data.CaldavTaskContainer
import org.tasks.data.NO_ORDER import org.tasks.data.NO_ORDER
import org.tasks.data.TaskContainer import org.tasks.data.TaskContainer
import org.tasks.data.db.Database
import org.tasks.data.db.DbUtils.dbchunk import org.tasks.data.db.DbUtils.dbchunk
import org.tasks.data.db.SuspendDbUtils.chunkedMap import org.tasks.data.db.SuspendDbUtils.chunkedMap
import org.tasks.data.entity.CaldavAccount import org.tasks.data.entity.CaldavAccount
@ -22,12 +22,13 @@ import org.tasks.data.entity.CaldavAccount.Companion.TYPE_TASKS
import org.tasks.data.entity.CaldavCalendar import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.entity.CaldavTask import org.tasks.data.entity.CaldavTask
import org.tasks.data.entity.Task import org.tasks.data.entity.Task
import org.tasks.data.withTransaction
import org.tasks.time.DateTimeUtils2.currentTimeMillis import org.tasks.time.DateTimeUtils2.currentTimeMillis
const val APPLE_EPOCH = 978307200000L // 1/1/2001 GMT const val APPLE_EPOCH = 978307200000L // 1/1/2001 GMT
@Dao @Dao
abstract class CaldavDao { abstract class CaldavDao(private val database: Database) {
@Query("SELECT COUNT(*) FROM caldav_lists WHERE cdl_account = :account") @Query("SELECT COUNT(*) FROM caldav_lists WHERE cdl_account = :account")
abstract suspend fun listCount(account: String): Int abstract suspend fun listCount(account: String): Int
@ -98,24 +99,24 @@ ORDER BY CASE cda_account_type
@Update @Update
abstract suspend fun update(caldavCalendar: CaldavCalendar) abstract suspend fun update(caldavCalendar: CaldavCalendar)
@Transaction suspend fun insert(task: Task, caldavTask: CaldavTask, addToTop: Boolean): Long =
open suspend fun insert(task: Task, caldavTask: CaldavTask, addToTop: Boolean): Long { database.withTransaction {
if (task.order != null) { if (task.order != null) {
return insert(caldavTask) return@withTransaction insert(caldavTask)
} }
if (addToTop) { if (addToTop) {
task.order = findFirstTask(caldavTask.calendar!!, task.parent) task.order = findFirstTask(caldavTask.calendar!!, task.parent)
?.takeIf { task.creationDate.toAppleEpoch() >= it } ?.takeIf { task.creationDate.toAppleEpoch() >= it }
?.minus(1) ?.minus(1)
} else { } else {
task.order = findLastTask(caldavTask.calendar!!, task.parent) task.order = findLastTask(caldavTask.calendar!!, task.parent)
?.takeIf { task.creationDate.toAppleEpoch() <= it } ?.takeIf { task.creationDate.toAppleEpoch() <= it }
?.plus(1) ?.plus(1)
}
val id = insert(caldavTask)
update(task)
id
} }
val id = insert(caldavTask)
update(task)
return id
}
@Query(""" @Query("""
SELECT MIN(IFNULL(`order`, (created - $APPLE_EPOCH) / 1000)) SELECT MIN(IFNULL(`order`, (created - $APPLE_EPOCH) / 1000))
@ -311,46 +312,49 @@ GROUP BY caldav_lists.cdl_uuid
+ "WHERE _id IN (SELECT _id FROM tasks INNER JOIN caldav_tasks ON _id = cd_task WHERE cd_deleted = 0 AND cd_calendar = :calendar)") + "WHERE _id IN (SELECT _id FROM tasks INNER JOIN caldav_tasks ON _id = cd_task WHERE cd_deleted = 0 AND cd_calendar = :calendar)")
abstract suspend fun updateParents(calendar: String) abstract suspend fun updateParents(calendar: String)
@Transaction suspend fun move(
open suspend fun move(
task: TaskContainer, task: TaskContainer,
previousParent: Long, previousParent: Long,
newParent: Long, newParent: Long,
newPosition: Long?, newPosition: Long?,
) { ) {
val previousPosition = task.caldavSortOrder database.withTransaction {
if (newPosition != null) { val previousPosition = task.caldavSortOrder
if (newParent == previousParent && newPosition < previousPosition) { if (newPosition != null) {
shiftDown(task.caldav!!, newParent, newPosition, previousPosition) if (newParent == previousParent && newPosition < previousPosition) {
} else { shiftDown(task.caldav!!, newParent, newPosition, previousPosition)
val list = newParent.takeIf { it > 0 }?.let { getTask(it)?.calendar } ?: task.caldav!! } else {
shiftDown(list, newParent, newPosition) val list =
newParent.takeIf { it > 0 }?.let { getTask(it)?.calendar } ?: task.caldav!!
shiftDown(list, newParent, newPosition)
}
} }
task.task.order = newPosition
setTaskOrder(task.id, newPosition)
} }
task.task.order = newPosition
setTaskOrder(task.id, newPosition)
} }
@Transaction suspend fun shiftDown(calendar: String, parent: Long, from: Long, to: Long? = null) {
open suspend fun shiftDown(calendar: String, parent: Long, from: Long, to: Long? = null) { database.withTransaction {
val updated = ArrayList<Task>() val updated = ArrayList<Task>()
val tasks = getTasksToShift(calendar, parent, from, to) val tasks = getTasksToShift(calendar, parent, from, to)
for (i in tasks.indices) { for (i in tasks.indices) {
val task = tasks[i] val task = tasks[i]
val current = from + i val current = from + i
if (task.sortOrder == current) { if (task.sortOrder == current) {
val task = task.task val task = task.task
task.order = current + 1 task.order = current + 1
updated.add(task) updated.add(task)
} else if (task.sortOrder > current) { } else if (task.sortOrder > current) {
break break
}
} }
} updateTasks(updated)
updateTasks(updated) updated
updated
.map(Task::id) .map(Task::id)
.dbchunk() .dbchunk()
.forEach { touchInternal(it) } .forEach { touchInternal(it) }
}
} }
@Query("UPDATE tasks SET modified = :modificationTime WHERE _id in (:ids)") @Query("UPDATE tasks SET modified = :modificationTime WHERE _id in (:ids)")

@ -3,15 +3,16 @@ package org.tasks.data.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete import androidx.room.Delete
import androidx.room.Query import androidx.room.Query
import androidx.room.Transaction
import org.tasks.data.dao.CaldavDao.Companion.LOCAL import org.tasks.data.dao.CaldavDao.Companion.LOCAL
import org.tasks.data.entity.CaldavAccount import org.tasks.data.db.Database
import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.db.SuspendDbUtils.chunkedMap import org.tasks.data.db.SuspendDbUtils.chunkedMap
import org.tasks.data.db.SuspendDbUtils.eachChunk import org.tasks.data.db.SuspendDbUtils.eachChunk
import org.tasks.data.entity.CaldavAccount
import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.withTransaction
@Dao @Dao
abstract class DeletionDao { abstract class DeletionDao(private val database: Database) {
@Query("DELETE FROM tasks WHERE _id IN(:ids)") @Query("DELETE FROM tasks WHERE _id IN(:ids)")
internal abstract suspend fun deleteTasks(ids: List<Long>) internal abstract suspend fun deleteTasks(ids: List<Long>)
@ -41,12 +42,7 @@ WHERE recurring = 1
""") """)
abstract suspend fun internalHasRecurringAncestors(ids: List<Long>): List<Long> abstract suspend fun internalHasRecurringAncestors(ids: List<Long>): List<Long>
@Transaction suspend fun delete(ids: List<Long>) { ids.eachChunk { deleteTasks(it) } }
open suspend fun delete(ids: List<Long>) {
ids.eachChunk {
deleteTasks(it)
}
}
@Query("UPDATE tasks " @Query("UPDATE tasks "
+ "SET modified = (strftime('%s','now')*1000), deleted = (strftime('%s','now')*1000)" + "SET modified = (strftime('%s','now')*1000), deleted = (strftime('%s','now')*1000)"
@ -63,13 +59,13 @@ WHERE recurring = 1
@Delete @Delete
internal abstract suspend fun deleteCaldavCalendar(caldavCalendar: CaldavCalendar) internal abstract suspend fun deleteCaldavCalendar(caldavCalendar: CaldavCalendar)
@Transaction suspend fun delete(caldavCalendar: CaldavCalendar): List<Long> =
open suspend fun delete(caldavCalendar: CaldavCalendar): List<Long> { database.withTransaction {
val tasks = getActiveCaldavTasks(caldavCalendar.uuid!!) val tasks = getActiveCaldavTasks(caldavCalendar.uuid!!)
delete(tasks) delete(tasks)
deleteCaldavCalendar(caldavCalendar) deleteCaldavCalendar(caldavCalendar)
return tasks tasks
} }
@Query("SELECT * FROM caldav_lists WHERE cdl_account = :account") @Query("SELECT * FROM caldav_lists WHERE cdl_account = :account")
abstract suspend fun getCalendars(account: String): List<CaldavCalendar> abstract suspend fun getCalendars(account: String): List<CaldavCalendar>
@ -80,13 +76,13 @@ WHERE recurring = 1
@Query("DELETE FROM tasks WHERE _id IN (SELECT _id FROM tasks INNER JOIN caldav_tasks ON _id = cd_task INNER JOIN caldav_lists ON cdl_uuid = cd_calendar WHERE cdl_account = '$LOCAL' AND deleted > 0 AND cd_deleted = 0)") @Query("DELETE FROM tasks WHERE _id IN (SELECT _id FROM tasks INNER JOIN caldav_tasks ON _id = cd_task INNER JOIN caldav_lists ON cdl_uuid = cd_calendar WHERE cdl_account = '$LOCAL' AND deleted > 0 AND cd_deleted = 0)")
abstract suspend fun purgeDeleted() abstract suspend fun purgeDeleted()
@Transaction suspend fun delete(caldavAccount: CaldavAccount): List<Long> =
open suspend fun delete(caldavAccount: CaldavAccount): List<Long> { database.withTransaction {
val deleted = ArrayList<Long>() val deleted = ArrayList<Long>()
for (calendar in getCalendars(caldavAccount.uuid!!)) { for (calendar in getCalendars(caldavAccount.uuid!!)) {
deleted.addAll(delete(calendar)) deleted.addAll(delete(calendar))
}
deleteCaldavAccount(caldavAccount)
deleted
} }
deleteCaldavAccount(caldavAccount)
return deleted
}
} }

@ -4,30 +4,32 @@ import androidx.room.Dao
import androidx.room.Delete import androidx.room.Delete
import androidx.room.Insert import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update import androidx.room.Update
import org.tasks.data.entity.Task import org.tasks.data.db.Database
import org.tasks.data.entity.CaldavAccount.Companion.TYPE_GOOGLE_TASKS import org.tasks.data.entity.CaldavAccount.Companion.TYPE_GOOGLE_TASKS
import org.tasks.data.entity.CaldavTask import org.tasks.data.entity.CaldavTask
import org.tasks.data.entity.Task
import org.tasks.data.withTransaction
@Dao @Dao
abstract class GoogleTaskDao { abstract class GoogleTaskDao(private val database: Database) {
@Insert @Insert
abstract suspend fun insert(task: CaldavTask): Long abstract suspend fun insert(task: CaldavTask): Long
@Insert @Insert
abstract suspend fun insert(tasks: Iterable<CaldavTask>) abstract suspend fun insert(tasks: Iterable<CaldavTask>)
@Transaction suspend fun insertAndShift(task: Task, caldavTask: CaldavTask, top: Boolean) {
open suspend fun insertAndShift(task: Task, caldavTask: CaldavTask, top: Boolean) { database.withTransaction {
if (top) { if (top) {
task.order = 0 task.order = 0
shiftDown(caldavTask.calendar!!, task.parent, 0) shiftDown(caldavTask.calendar!!, task.parent, 0)
} else { } else {
task.order = getBottom(caldavTask.calendar!!, task.parent) task.order = getBottom(caldavTask.calendar!!, task.parent)
}
insert(caldavTask)
update(task)
} }
insert(caldavTask)
update(task)
} }
@Query("UPDATE tasks SET `order` = `order` + 1 WHERE parent = :parent AND `order` >= :position AND _id IN (SELECT cd_task FROM caldav_tasks WHERE cd_calendar = :listId)") @Query("UPDATE tasks SET `order` = `order` + 1 WHERE parent = :parent AND `order` >= :position AND _id IN (SELECT cd_task FROM caldav_tasks WHERE cd_calendar = :listId)")
@ -42,24 +44,25 @@ abstract class GoogleTaskDao {
@Query("UPDATE tasks SET `order` = `order` - 1 WHERE parent = :parent AND `order` >= :position AND _id IN (SELECT cd_task FROM caldav_tasks WHERE cd_calendar = :listId)") @Query("UPDATE tasks SET `order` = `order` - 1 WHERE parent = :parent AND `order` >= :position AND _id IN (SELECT cd_task FROM caldav_tasks WHERE cd_calendar = :listId)")
internal abstract suspend fun shiftUp(listId: String, parent: Long, position: Long) internal abstract suspend fun shiftUp(listId: String, parent: Long, position: Long)
@Transaction suspend fun move(task: Task, list: String, newParent: Long, newPosition: Long) {
open suspend fun move(task: Task, list: String, newParent: Long, newPosition: Long) { database.withTransaction {
val previousParent = task.parent val previousParent = task.parent
val previousPosition = task.order!! val previousPosition = task.order!!
if (newParent == previousParent) { if (newParent == previousParent) {
if (previousPosition < newPosition) { if (previousPosition < newPosition) {
shiftUp(list, newParent, previousPosition, newPosition) shiftUp(list, newParent, previousPosition, newPosition)
} else {
shiftDown(list, newParent, previousPosition, newPosition)
}
} else { } else {
shiftDown(list, newParent, previousPosition, newPosition) shiftUp(list, previousParent, previousPosition)
shiftDown(list, newParent, newPosition)
} }
} else { task.parent = newParent
shiftUp(list, previousParent, previousPosition) task.order = newPosition
shiftDown(list, newParent, newPosition) update(task)
setMoved(task.id, list)
} }
task.parent = newParent
task.order = newPosition
update(task)
setMoved(task.id, list)
} }
@Query("UPDATE caldav_tasks SET gt_moved = 1 WHERE cd_task = :task and cd_calendar = :list") @Query("UPDATE caldav_tasks SET gt_moved = 1 WHERE cd_task = :task and cd_calendar = :list")
@ -165,26 +168,27 @@ WHERE cd_remote_id = :id
""") """)
abstract suspend fun updatePosition(id: String, parent: String?, position: String) abstract suspend fun updatePosition(id: String, parent: String?, position: String)
@Transaction suspend fun reposition(caldavDao: CaldavDao, listId: String) {
open suspend fun reposition(caldavDao: CaldavDao, listId: String) { database.withTransaction {
caldavDao.updateParents(listId) caldavDao.updateParents(listId)
val orderedTasks = getByRemoteOrder(listId) val orderedTasks = getByRemoteOrder(listId)
var subtasks = 0L var subtasks = 0L
var parent = 0L var parent = 0L
for (task in orderedTasks) { for (task in orderedTasks) {
if (task.parent > 0) { if (task.parent > 0) {
if (task.order != subtasks) { if (task.order != subtasks) {
task.order = subtasks task.order = subtasks
update(task) update(task)
} }
subtasks++ subtasks++
} else { } else {
subtasks = 0 subtasks = 0
if (task.order != parent) { if (task.order != parent) {
task.order = parent task.order = parent
update(task) update(task)
}
parent++
} }
parent++
} }
} }
} }

@ -4,39 +4,43 @@ import androidx.room.Dao
import androidx.room.Delete import androidx.room.Delete
import androidx.room.Insert import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update import androidx.room.Update
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import org.tasks.data.PrincipalWithAccess import org.tasks.data.PrincipalWithAccess
import org.tasks.data.db.Database
import org.tasks.data.entity.CaldavAccount import org.tasks.data.entity.CaldavAccount
import org.tasks.data.entity.CaldavCalendar import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.entity.Principal import org.tasks.data.entity.Principal
import org.tasks.data.entity.PrincipalAccess import org.tasks.data.entity.PrincipalAccess
import org.tasks.data.withTransaction
@Dao @Dao
interface PrincipalDao { abstract class PrincipalDao(private val database: Database) {
@Insert @Insert
suspend fun insert(principal: Principal): Long abstract suspend fun insert(principal: Principal): Long
@Insert @Insert
suspend fun insert(access: PrincipalAccess): Long abstract suspend fun insert(access: PrincipalAccess): Long
@Update @Update
suspend fun update(access: PrincipalAccess) abstract suspend fun update(access: PrincipalAccess)
@Query(""" @Query("""
DELETE DELETE
FROM principal_access FROM principal_access
WHERE list = :list WHERE list = :list
AND id NOT IN (:access)""") AND id NOT IN (:access)""")
suspend fun deleteRemoved(list: Long, access: List<Long>) abstract suspend fun deleteRemoved(list: Long, access: List<Long>)
@Delete @Delete
suspend fun delete(access: PrincipalAccess) abstract suspend fun delete(access: PrincipalAccess)
suspend fun getAll(): List<PrincipalWithAccess> = database.withTransaction {
getAllInternal()
}
@Transaction
@Query("SELECT * FROM principal_access") @Query("SELECT * FROM principal_access")
suspend fun getAll(): List<PrincipalWithAccess> internal abstract suspend fun getAllInternal(): List<PrincipalWithAccess>
suspend fun getOrCreatePrincipal(account: CaldavAccount, href: String, displayName: String? = null) = suspend fun getOrCreatePrincipal(account: CaldavAccount, href: String, displayName: String? = null) =
findPrincipal(account.id, href) findPrincipal(account.id, href)
@ -65,12 +69,12 @@ WHERE list = :list
).apply { id = insert(this) } ).apply { id = insert(this) }
@Query("SELECT * FROM principals WHERE account = :account AND href = :href") @Query("SELECT * FROM principals WHERE account = :account AND href = :href")
suspend fun findPrincipal(account: Long, href: String): Principal? abstract suspend fun findPrincipal(account: Long, href: String): Principal?
@Query("SELECT * FROM principal_access WHERE list = :list and principal = :principal") @Query("SELECT * FROM principal_access WHERE list = :list and principal = :principal")
suspend fun findAccess(list: Long, principal: Long): PrincipalAccess? abstract suspend fun findAccess(list: Long, principal: Long): PrincipalAccess?
@Transaction
@Query("SELECT * FROM principal_access WHERE list = :id") @Query("SELECT * FROM principal_access WHERE list = :id")
fun getPrincipals(id: Long): Flow<List<PrincipalWithAccess>> abstract fun getPrincipals(id: Long): Flow<List<PrincipalWithAccess>>
} }

@ -4,13 +4,14 @@ import androidx.room.Dao
import androidx.room.Delete import androidx.room.Delete
import androidx.room.Insert import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import androidx.room.Transaction import org.tasks.data.db.Database
import org.tasks.data.entity.Tag import org.tasks.data.entity.Tag
import org.tasks.data.entity.TagData import org.tasks.data.entity.TagData
import org.tasks.data.entity.Task import org.tasks.data.entity.Task
import org.tasks.data.withTransaction
@Dao @Dao
abstract class TagDao { abstract class TagDao(private val database: Database) {
@Query("UPDATE tags SET name = :name WHERE tag_uid = :tagUid") @Query("UPDATE tags SET name = :name WHERE tag_uid = :tagUid")
abstract suspend fun rename(tagUid: String, name: String) abstract suspend fun rename(tagUid: String, name: String)
@ -35,15 +36,16 @@ abstract class TagDao {
@Delete @Delete
abstract suspend fun delete(tags: List<Tag>) abstract suspend fun delete(tags: List<Tag>)
@Transaction
open suspend fun applyTags(task: Task, tagDataDao: TagDataDao, current: List<TagData>) { open suspend fun applyTags(task: Task, tagDataDao: TagDataDao, current: List<TagData>) {
val taskId = task.id database.withTransaction {
val existing = HashSet(tagDataDao.getTagDataForTask(taskId)) val taskId = task.id
val selected = HashSet<TagData>(current) val existing = HashSet(tagDataDao.getTagDataForTask(taskId))
val added = selected subtract existing val selected = HashSet<TagData>(current)
val removed = existing subtract selected val added = selected subtract existing
deleteTags(taskId, removed.map { td -> td.remoteId!! }) val removed = existing subtract selected
insert(task, added) deleteTags(taskId, removed.map { td -> td.remoteId!! })
insert(task, added)
}
} }
suspend fun insert(task: Task, tags: Collection<TagData>) { suspend fun insert(task: Task, tags: Collection<TagData>) {

@ -4,19 +4,20 @@ import androidx.room.Dao
import androidx.room.Delete import androidx.room.Delete
import androidx.room.Insert import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update import androidx.room.Update
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import org.tasks.data.NO_ORDER import org.tasks.data.NO_ORDER
import org.tasks.data.TagFilters import org.tasks.data.TagFilters
import org.tasks.data.db.Database
import org.tasks.data.db.DbUtils import org.tasks.data.db.DbUtils
import org.tasks.data.entity.Tag import org.tasks.data.entity.Tag
import org.tasks.data.entity.TagData import org.tasks.data.entity.TagData
import org.tasks.data.entity.Task import org.tasks.data.entity.Task
import org.tasks.data.withTransaction
import org.tasks.time.DateTimeUtils2.currentTimeMillis import org.tasks.time.DateTimeUtils2.currentTimeMillis
@Dao @Dao
abstract class TagDataDao { abstract class TagDataDao(private val database: Database) {
@Query("SELECT * FROM tagdata") @Query("SELECT * FROM tagdata")
abstract fun subscribeToTags(): Flow<List<TagData>> abstract fun subscribeToTags(): Flow<List<TagData>>
@ -79,9 +80,11 @@ abstract class TagDataDao {
+ " GROUP BY tasks._id") + " GROUP BY tasks._id")
internal abstract suspend fun getAllTags(tasks: List<Long>): List<String?> internal abstract suspend fun getAllTags(tasks: List<Long>): List<String?>
@Transaction suspend fun applyTags(
open suspend fun applyTags( tasks: List<Task>,
tasks: List<Task>, partiallySelected: List<TagData>, selected: List<TagData>): List<Long> { partiallySelected: List<TagData>,
selected: List<TagData>
): List<Long> = database.withTransaction {
val modified = HashSet<Long>() val modified = HashSet<Long>()
val keep = partiallySelected.plus(selected).map { it.remoteId!! } val keep = partiallySelected.plus(selected).map { it.remoteId!! }
for (sublist in tasks.chunked(DbUtils.MAX_SQLITE_ARGS - keep.size)) { for (sublist in tasks.chunked(DbUtils.MAX_SQLITE_ARGS - keep.size)) {
@ -105,13 +108,14 @@ abstract class TagDataDao {
) )
} }
} }
return ArrayList(modified) ArrayList(modified)
} }
@Transaction suspend fun delete(tagData: TagData) {
open suspend fun delete(tagData: TagData) { database.withTransaction {
deleteTags(tagData.remoteId!!) deleteTags(tagData.remoteId!!)
deleteTagData(tagData) deleteTagData(tagData)
}
} }
@Delete @Delete

@ -6,7 +6,7 @@ import androidx.room.Query
import androidx.room.Update import androidx.room.Update
import androidx.room.execSQL import androidx.room.execSQL
import co.touchlab.kermit.Logger import co.touchlab.kermit.Logger
import org.tasks.data.BuildConfig import org.tasks.IS_DEBUG
import org.tasks.data.TaskContainer import org.tasks.data.TaskContainer
import org.tasks.data.UUIDHelper import org.tasks.data.UUIDHelper
import org.tasks.data.db.Database import org.tasks.data.db.Database
@ -111,7 +111,7 @@ FROM (
open suspend fun fetchTasks(callback: suspend () -> List<String>): List<TaskContainer> = open suspend fun fetchTasks(callback: suspend () -> List<String>): List<TaskContainer> =
database.withTransaction { database.withTransaction {
val start = if (BuildConfig.DEBUG) DateTimeUtils2.currentTimeMillis() else 0 val start = if (IS_DEBUG) DateTimeUtils2.currentTimeMillis() else 0
val queries = callback() val queries = callback()
val last = queries.size - 1 val last = queries.size - 1
for (i in 0 until last) { for (i in 0 until last) {
@ -205,7 +205,7 @@ FROM recursive_tasks
if (Task.isUuidEmpty(task.remoteId)) { if (Task.isUuidEmpty(task.remoteId)) {
task.remoteId = UUIDHelper.newUUID() task.remoteId = UUIDHelper.newUUID()
} }
if (BuildConfig.DEBUG) { if (IS_DEBUG) {
require(task.remoteId?.isNotBlank() == true && task.remoteId != "0") require(task.remoteId?.isNotBlank() == true && task.remoteId != "0")
} }
val insert = insert(task) val insert = insert(task)

@ -1,18 +1,18 @@
package org.tasks.data.entity package org.tasks.data.entity
import android.os.Parcelable
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.ForeignKey import androidx.room.ForeignKey
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
import org.tasks.CommonParcelable
import org.tasks.CommonParcelize
import org.tasks.data.db.Table import org.tasks.data.db.Table
import org.tasks.time.printTimestamp import org.tasks.time.printTimestamp
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@Parcelize @CommonParcelize
@Serializable @Serializable
@Entity( @Entity(
tableName = Alarm.TABLE_NAME, tableName = Alarm.TABLE_NAME,
@ -41,7 +41,7 @@ data class Alarm(
val repeat: Int = 0, val repeat: Int = 0,
@ColumnInfo(name = "interval", defaultValue = "0") @ColumnInfo(name = "interval", defaultValue = "0")
val interval: Long = 0, val interval: Long = 0,
) : Parcelable { ) : CommonParcelable {
fun same(other: Alarm) = fun same(other: Alarm) =
type == other.type && type == other.type &&
time == other.time && time == other.time &&

@ -1,17 +1,17 @@
package org.tasks.data.entity package org.tasks.data.entity
import android.os.Parcelable
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
import org.tasks.CommonParcelable
import org.tasks.CommonParcelize
import org.tasks.data.db.Table import org.tasks.data.db.Table
import java.net.HttpURLConnection import java.net.HttpURLConnection
@Serializable @Serializable
@Parcelize @CommonParcelize
@Entity(tableName = "caldav_accounts") @Entity(tableName = "caldav_accounts")
data class CaldavAccount( data class CaldavAccount(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
@ -38,7 +38,7 @@ data class CaldavAccount(
val isCollapsed: Boolean = false, val isCollapsed: Boolean = false,
@ColumnInfo(name = "cda_server_type") @ColumnInfo(name = "cda_server_type")
var serverType: Int = SERVER_UNKNOWN, var serverType: Int = SERVER_UNKNOWN,
) : Parcelable { ) : CommonParcelable {
val isCaldavAccount: Boolean val isCaldavAccount: Boolean
get() = accountType == TYPE_CALDAV get() = accountType == TYPE_CALDAV

@ -1,18 +1,18 @@
package org.tasks.data.entity package org.tasks.data.entity
import android.os.Parcelable
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
import org.tasks.CommonParcelable
import org.tasks.CommonParcelize
import org.tasks.data.LIST import org.tasks.data.LIST
import org.tasks.data.NO_ORDER import org.tasks.data.NO_ORDER
import org.tasks.data.db.Table import org.tasks.data.db.Table
@Serializable @Serializable
@Parcelize @CommonParcelize
@Entity(tableName = "caldav_lists") @Entity(tableName = "caldav_lists")
data class CaldavCalendar( data class CaldavCalendar(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
@ -28,7 +28,7 @@ data class CaldavCalendar(
@ColumnInfo(name = "cdl_order") val order: Int = NO_ORDER, @ColumnInfo(name = "cdl_order") val order: Int = NO_ORDER,
@ColumnInfo(name = "cdl_access") var access: Int = ACCESS_OWNER, @ColumnInfo(name = "cdl_access") var access: Int = ACCESS_OWNER,
@ColumnInfo(name = "cdl_last_sync") val lastSync: Long = 0, @ColumnInfo(name = "cdl_last_sync") val lastSync: Long = 0,
) : Parcelable { ) : CommonParcelable {
@Suppress("RedundantNullableReturnType") @Suppress("RedundantNullableReturnType")
fun getIcon(): Int? { fun getIcon(): Int? {
return (if (icon == null) LIST else icon!!) return (if (icon == null) LIST else icon!!)

@ -1,16 +1,16 @@
package org.tasks.data.entity package org.tasks.data.entity
import android.os.Parcelable
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
import org.tasks.CommonParcelable
import org.tasks.CommonParcelize
import org.tasks.data.NO_ORDER import org.tasks.data.NO_ORDER
@Serializable @Serializable
@Parcelize @CommonParcelize
@Entity(tableName = "filters") @Entity(tableName = "filters")
data class Filter( data class Filter(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
@ -31,4 +31,4 @@ data class Filter(
val icon: Int? = -1, val icon: Int? = -1,
@ColumnInfo(name = "f_order") @ColumnInfo(name = "f_order")
val order: Int = NO_ORDER, val order: Int = NO_ORDER,
) : Parcelable ) : CommonParcelable

@ -1,6 +1,5 @@
package org.tasks.data.entity package org.tasks.data.entity
import android.os.Parcelable
import androidx.annotation.IntDef import androidx.annotation.IntDef
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
@ -8,13 +7,14 @@ import androidx.room.Ignore
import androidx.room.Index import androidx.room.Index
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import co.touchlab.kermit.Logger import co.touchlab.kermit.Logger
import kotlinx.parcelize.Parcelize
import kotlinx.parcelize.RawValue
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
import kotlinx.serialization.json.JsonNames import kotlinx.serialization.json.JsonNames
import org.tasks.CommonParcelable
import org.tasks.CommonParcelize
import org.tasks.CommonRawValue
import org.tasks.data.db.Table import org.tasks.data.db.Table
import org.tasks.data.sql.Field import org.tasks.data.sql.Field
@ -22,7 +22,7 @@ const val SUPPRESS_SYNC = "suppress_sync"
const val FORCE_CALDAV_SYNC = "force_caldav_sync" const val FORCE_CALDAV_SYNC = "force_caldav_sync"
@Serializable @Serializable
@Parcelize @CommonParcelize
@Entity( @Entity(
tableName = Task.TABLE_NAME, tableName = Task.TABLE_NAME,
indices = [ indices = [
@ -82,8 +82,8 @@ data class Task @OptIn(ExperimentalSerializationApi::class) constructor(
var readOnly: Boolean = false, var readOnly: Boolean = false,
@Ignore @Ignore
@Transient @Transient
private var transitoryData: @RawValue HashMap<String, Any>? = null, private var transitoryData: @CommonRawValue HashMap<String, Any>? = null,
) : Parcelable { ) : CommonParcelable {
var uuid: String var uuid: String
get() = if (remoteId.isNullOrEmpty()) NO_UUID else remoteId!! get() = if (remoteId.isNullOrEmpty()) NO_UUID else remoteId!!
set(uuid) { set(uuid) {
@ -237,15 +237,8 @@ data class Task @OptIn(ExperimentalSerializationApi::class) constructor(
return getTransitory(Tag.KEY) ?: ArrayList() return getTransitory(Tag.KEY) ?: ArrayList()
} }
fun setTags(tags: ArrayList<String>) {
if (transitoryData == null) {
transitoryData = HashMap()
}
transitoryData!![Tag.KEY] = tags
}
fun hasTransitory(key: String?): Boolean { fun hasTransitory(key: String?): Boolean {
return transitoryData != null && transitoryData!!.containsKey(key) return transitoryData?.containsKey(key) == true
} }
fun <T> getTransitory(key: String?): T? = transitoryData?.get(key) as T? fun <T> getTransitory(key: String?): T? = transitoryData?.get(key) as T?

@ -1,16 +1,16 @@
package org.tasks.data.entity package org.tasks.data.entity
import android.os.Parcelable
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
import org.tasks.CommonParcelable
import org.tasks.CommonParcelize
import org.tasks.data.UUIDHelper import org.tasks.data.UUIDHelper
@Serializable @Serializable
@Parcelize @CommonParcelize
@Entity(tableName = "attachment_file") @Entity(tableName = "attachment_file")
data class TaskAttachment( data class TaskAttachment(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
@ -23,7 +23,7 @@ data class TaskAttachment(
val name: String, val name: String,
@ColumnInfo(name = "uri") @ColumnInfo(name = "uri")
val uri: String, val uri: String,
) : Parcelable { ) : CommonParcelable {
companion object { companion object {
const val KEY = "attachment" const val KEY = "attachment"
const val FILES_DIRECTORY_DEFAULT = "attachments" const val FILES_DIRECTORY_DEFAULT = "attachments"

@ -1,112 +1,37 @@
package org.tasks.data.entity package org.tasks.data.entity
import android.net.Uri
import android.os.Parcel
import android.os.Parcelable
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import co.touchlab.kermit.Logger
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
import org.json.JSONException import org.tasks.CommonParcelable
import org.json.JSONObject import org.tasks.CommonParcelize
import org.tasks.data.db.Table import org.tasks.data.db.Table
import java.io.File
@Serializable @Serializable
@CommonParcelize
@Entity(tableName = "userActivity") @Entity(tableName = "userActivity")
class UserActivity : Parcelable { data class UserActivity(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "_id") @ColumnInfo(name = "_id")
@Transient @Transient
var id: Long? = null var id: Long? = null,
@ColumnInfo(name = "remoteId") @ColumnInfo(name = "remoteId")
var remoteId: String? = Task.NO_UUID var remoteId: String? = Task.NO_UUID,
@ColumnInfo(name = "message") @ColumnInfo(name = "message")
var message: String? = "" var message: String? = "",
@ColumnInfo(name = "picture") @ColumnInfo(name = "picture")
var picture: String? = "" var picture: String? = "",
@ColumnInfo(name = "target_id") @ColumnInfo(name = "target_id")
@Transient @Transient
var targetId: String? = Task.NO_UUID var targetId: String? = Task.NO_UUID,
@ColumnInfo(name = "created_at") @ColumnInfo(name = "created_at")
var created: Long? = 0L var created: Long? = 0L,
) : CommonParcelable {
constructor()
@Ignore
private constructor(parcel: Parcel) {
with(parcel) {
id = readLong()
remoteId = readString()
message = readString()
picture = readString()
targetId = readString()
created = readLong()
}
}
fun setPicture(uri: Uri?) {
picture = uri?.toString()
}
val pictureUri: Uri?
get() = if (picture.isNullOrBlank()) null else Uri.parse(picture)
fun convertPictureUri() {
setPicture(getLegacyPictureUri(picture))
}
override fun describeContents() = 0
override fun writeToParcel(dest: Parcel, flags: Int) {
with(dest) {
writeLong(id!!)
writeString(remoteId)
writeString(message)
writeString(picture)
writeString(targetId)
writeLong(created!!)
}
}
companion object { companion object {
@JvmField val TABLE = Table("userActivity") @JvmField val TABLE = Table("userActivity")
@JvmField val TASK = TABLE.column("target_id") @JvmField val TASK = TABLE.column("target_id")
@JvmField val MESSAGE = TABLE.column("message") @JvmField val MESSAGE = TABLE.column("message")
@JvmField val CREATOR: Parcelable.Creator<UserActivity> = object : Parcelable.Creator<UserActivity> {
override fun createFromParcel(source: Parcel): UserActivity = UserActivity(source)
override fun newArray(size: Int): Array<UserActivity?> = arrayOfNulls(size)
}
private fun getLegacyPictureUri(value: String?): Uri? {
return try {
if (value.isNullOrBlank()) {
return null
}
if (value.contains("uri") || value.contains("path")) {
val json = JSONObject(value)
if (json.has("uri")) {
return Uri.parse(json.getString("uri"))
}
if (json.has("path")) {
val path = json.getString("path")
return Uri.fromFile(File(path))
}
}
null
} catch (e: JSONException) {
Logger.e("Failed to parse picture uri", e, tag = "UserActivity")
null
}
}
} }
} }

@ -1,6 +1,6 @@
package org.tasks.time package org.tasks.time
import org.tasks.data.BuildConfig import org.tasks.IS_DEBUG
import java.util.Date import java.util.Date
object DateTimeUtils2 { object DateTimeUtils2 {
@ -24,4 +24,4 @@ object DateTimeUtils2 {
} }
fun printTimestamp(timestamp: Long): String = fun printTimestamp(timestamp: Long): String =
if (BuildConfig.DEBUG) Date(timestamp).toString() else timestamp.toString() if (IS_DEBUG) Date(timestamp).toString() else timestamp.toString()

@ -0,0 +1,13 @@
@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
package org.tasks
actual interface CommonParcelable
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.BINARY)
annotation class RawValue
actual typealias CommonRawValue = RawValue
actual val IS_DEBUG = false

@ -164,3 +164,4 @@ kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref =
kotlin-compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } kotlin-compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version = "2.0.0-1.0.21" } ksp = { id = "com.google.devtools.ksp", version = "2.0.0-1.0.21" }
jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }

Loading…
Cancel
Save