Merge branch 'main' into main

pull/3782/head
Milosch Füllgraf 4 months ago committed by GitHub
commit 0a490933ce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

1
app/proguard.pro vendored

@ -59,5 +59,6 @@
-dontwarn com.google.android.libraries.identity.** -dontwarn com.google.android.libraries.identity.**
-dontwarn edu.umd.cs.findbugs.annotations.** -dontwarn edu.umd.cs.findbugs.annotations.**
-dontwarn com.google.crypto.tink.subtle.** -dontwarn com.google.crypto.tink.subtle.**
-dontwarn net.jcip.annotations.**
-keepclassmembers class * extends com.google.protobuf.GeneratedMessageLite { <fields>; } -keepclassmembers class * extends com.google.protobuf.GeneratedMessageLite { <fields>; }

@ -25,7 +25,7 @@ class WearRefresherImpl(
init { init {
phoneDataLayerAppHelper phoneDataLayerAppHelper
.connectedAndInstalledNodes .connectedAndInstalledNodes
.catch { Timber.e(it) } .catch { Timber.e("${it.message}") }
.onEach { nodes -> .onEach { nodes ->
Timber.d("Connected nodes: ${nodes.joinToString()}") Timber.d("Connected nodes: ${nodes.joinToString()}")
watchConnected = nodes.isNotEmpty() watchConnected = nodes.isNotEmpty()

@ -24,7 +24,7 @@ import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
import androidx.compose.material3.adaptive.layout.PaneAdaptedValue import androidx.compose.material3.adaptive.layout.PaneAdaptedValue
import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole
import androidx.compose.material3.adaptive.layout.calculatePaneScaffoldDirective import androidx.compose.material3.adaptive.layout.calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth
import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
import androidx.compose.material3.rememberDrawerState import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@ -241,7 +241,7 @@ class MainActivity : AppCompatActivity() {
} }
) )
val navigator = rememberListDetailPaneScaffoldNavigator( val navigator = rememberListDetailPaneScaffoldNavigator(
calculatePaneScaffoldDirective( calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(
windowAdaptiveInfo = currentWindowAdaptiveInfo(), windowAdaptiveInfo = currentWindowAdaptiveInfo(),
).copy( ).copy(
horizontalPartitionSpacerSize = 0.dp, horizontalPartitionSpacerSize = 0.dp,

@ -7,6 +7,7 @@ import com.google.api.services.tasks.model.Task
import com.google.api.services.tasks.model.TaskList import com.google.api.services.tasks.model.TaskList
import com.google.api.services.tasks.model.TaskLists import com.google.api.services.tasks.model.TaskLists
import org.tasks.googleapis.BaseInvoker import org.tasks.googleapis.BaseInvoker
import timber.log.Timber
import java.io.IOException import java.io.IOException
/** /**
@ -43,21 +44,30 @@ class GtasksInvoker(
@Throws(IOException::class) @Throws(IOException::class)
suspend fun getAllPositions( suspend fun getAllPositions(
listId: String?, pageToken: String?): com.google.api.services.tasks.model.Tasks? = listId: String?,
execute( pageToken: String?,
service!! ): com.google.api.services.tasks.model.Tasks? =
.tasks() execute(
.list(listId) service!!
.setMaxResults(100) .tasks()
.setShowDeleted(false) .list(listId)
.setShowHidden(false) .setMaxResults(100)
.setPageToken(pageToken) .setShowDeleted(false)
.setFields("items(id,parent,position),nextPageToken")) .setShowHidden(false)
.setPageToken(pageToken)
.setFields("items(id,parent,position),nextPageToken")
)
@Throws(IOException::class) @Throws(IOException::class)
suspend fun createGtask( suspend fun createGtask(
listId: String?, task: Task?, parent: String?, previous: String?): Task? = listId: String?,
execute(service!!.tasks().insert(listId, task).setParent(parent).setPrevious(previous)) task: Task?,
parent: String?,
previous: String?,
): Task? {
Timber.d("createGtask(listId=$listId, task=<redacted>, parent=$parent, previous=$previous)")
return execute(service!!.tasks().insert(listId, task).setParent(parent).setPrevious(previous))
}
@Throws(IOException::class) @Throws(IOException::class)
suspend fun updateGtask(listId: String?, task: Task) = suspend fun updateGtask(listId: String?, task: Task) =
@ -65,19 +75,26 @@ class GtasksInvoker(
@Throws(IOException::class) @Throws(IOException::class)
suspend fun moveGtask( suspend fun moveGtask(
listId: String?, taskId: String?, parentId: String?, previousId: String?): Task? = listId: String?,
execute( taskId: String?,
service!! parentId: String?,
.tasks() previousId: String?,
.move(listId, taskId) ): Task? {
.setParent(parentId) Timber.d("moveGtask(listId=$listId, taskId=$taskId, parentId=$parentId, previousId=$previousId)")
.setPrevious(previousId)) return execute(
service!!
.tasks()
.move(listId, taskId)
.setParent(parentId)
.setPrevious(previousId)
)
}
@Throws(IOException::class) @Throws(IOException::class)
suspend fun deleteGtaskList(listId: String?) { suspend fun deleteGtaskList(listId: String?) {
try { try {
execute(service!!.tasklists().delete(listId)) execute(service!!.tasklists().delete(listId))
} catch (ignored: HttpNotFoundException) { } catch (_: HttpNotFoundException) {
} }
} }
@ -91,9 +108,10 @@ class GtasksInvoker(
@Throws(IOException::class) @Throws(IOException::class)
suspend fun deleteGtask(listId: String?, taskId: String?) { suspend fun deleteGtask(listId: String?, taskId: String?) {
Timber.d("deleteGtask(listId=$listId, taskId=$taskId)")
try { try {
execute(service!!.tasks().delete(listId, taskId)) execute(service!!.tasks().delete(listId, taskId))
} catch (ignored: HttpNotFoundException) { } catch (_: HttpNotFoundException) {
} }
} }
} }

@ -15,11 +15,12 @@ import com.todoroo.astrid.service.TaskCreator
import com.todoroo.astrid.service.TaskCreator.Companion.getDefaultAlarms import com.todoroo.astrid.service.TaskCreator.Companion.getDefaultAlarms
import com.todoroo.astrid.service.TaskDeleter import com.todoroo.astrid.service.TaskDeleter
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.delay
import org.tasks.LocalBroadcastManager import org.tasks.LocalBroadcastManager
import org.tasks.R import org.tasks.R
import org.tasks.Strings.isNullOrEmpty import org.tasks.Strings.isNullOrEmpty
import org.tasks.analytics.Firebase import org.tasks.analytics.Firebase
import org.tasks.data.* import org.tasks.data.createDueDate
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.GoogleTaskDao import org.tasks.data.dao.GoogleTaskDao
@ -38,7 +39,7 @@ import java.net.HttpRetryException
import java.net.SocketException import java.net.SocketException
import java.net.SocketTimeoutException import java.net.SocketTimeoutException
import java.net.UnknownHostException import java.net.UnknownHostException
import java.util.* import java.util.Collections
import javax.inject.Inject import javax.inject.Inject
import javax.net.ssl.SSLException import javax.net.ssl.SSLException
import kotlin.math.max import kotlin.math.max
@ -125,37 +126,50 @@ class GoogleTaskSynchronizer @Inject constructor(
preferences.setString(R.string.p_default_list, null) preferences.setString(R.string.p_default_list, null)
} }
} }
pushLocalChanges(account, gtasksInvoker) val failedTasks = mutableSetOf<Long>()
var retryTaskId = pushLocalChanges(account, gtasksInvoker)
while (retryTaskId != null) {
if (failedTasks.contains(retryTaskId)) {
throw IOException("Invalid Task ID: $retryTaskId")
}
failedTasks.add(retryTaskId)
Timber.d("Retrying push local changes due to stale task ID $retryTaskId (${failedTasks.size} total failed tasks)")
delay(1000)
retryTaskId = pushLocalChanges(account, gtasksInvoker)
}
for (list in caldavDao.getCalendarsByAccount(account.uuid!!)) { for (list in caldavDao.getCalendarsByAccount(account.uuid!!)) {
if (isNullOrEmpty(list.uuid)) { if (isNullOrEmpty(list.uuid)) {
firebase.reportException(RuntimeException("Empty remote id")) firebase.reportException(RuntimeException("Empty remote id"))
continue continue
} }
fetchAndApplyRemoteChanges(gtasksInvoker, list) fetchAndApplyRemoteChanges(gtasksInvoker, list)
if (!preferences.isPositionHackEnabled) { gtasksInvoker.updatePositions(list.uuid!!)
googleTaskDao.reposition(caldavDao, list.uuid!!)
}
}
if (preferences.isPositionHackEnabled) {
for (list in gtaskLists) {
val tasks = fetchPositions(gtasksInvoker, list.id)
for (task in tasks) {
googleTaskDao.updatePosition(task.id, task.parent, task.position)
}
googleTaskDao.reposition(caldavDao, list.id)
}
} }
// account.etag = eTag // account.etag = eTag
account.error = "" account.error = ""
} }
@Throws(IOException::class) @Throws(IOException::class)
private suspend fun fetchPositions( private suspend fun GtasksInvoker.updatePositions(list: String) {
gtasksInvoker: GtasksInvoker, listId: String): List<Task> { // Unfortunately this is necessary because Google broke the API
// https://issuetracker.google.com/issues/132432317
Timber.d("updatePositions(list=${list})")
fetchPositions(list).forEach { task ->
googleTaskDao.updatePosition(task.id, task.parent, task.position)
}
googleTaskDao.reposition(caldavDao, list)
}
@Throws(IOException::class)
private suspend fun GtasksInvoker.fetchPositions(listId: String): List<Task> {
val tasks: MutableList<Task> = ArrayList() val tasks: MutableList<Task> = ArrayList()
var nextPageToken: String? = null var nextPageToken: String? = null
do { do {
val taskList = gtasksInvoker.getAllPositions(listId, nextPageToken) val taskList = getAllPositions(listId, nextPageToken)
taskList?.items?.let { taskList?.items?.let {
tasks.addAll(it) tasks.addAll(it)
} }
@ -165,15 +179,19 @@ class GoogleTaskSynchronizer @Inject constructor(
} }
@Throws(IOException::class) @Throws(IOException::class)
private suspend fun pushLocalChanges(account: CaldavAccount, gtasksInvoker: GtasksInvoker) { private suspend fun pushLocalChanges(account: CaldavAccount, gtasksInvoker: GtasksInvoker): Long? {
val tasks = taskDao.getGoogleTasksToPush(account.uuid!!) val tasks = taskDao.getGoogleTasksToPush(account.uuid!!)
for (task in tasks) { for (task in tasks) {
pushTask(task, gtasksInvoker) val staleTaskId = pushTask(task, gtasksInvoker)
if (staleTaskId != null) {
return staleTaskId
}
} }
return null
} }
@Throws(IOException::class) @Throws(IOException::class)
private suspend fun pushTask(task: org.tasks.data.entity.Task, gtasksInvoker: GtasksInvoker) { private suspend fun pushTask(task: org.tasks.data.entity.Task, gtasksInvoker: GtasksInvoker): Long? {
for (deleted in googleTaskDao.getDeletedByTaskId(task.id)) { for (deleted in googleTaskDao.getDeletedByTaskId(task.id)) {
deleted.remoteId?.let { deleted.remoteId?.let {
try { try {
@ -187,7 +205,7 @@ class GoogleTaskSynchronizer @Inject constructor(
} }
googleTaskDao.delete(deleted) googleTaskDao.delete(deleted)
} }
val gtasksMetadata = googleTaskDao.getByTaskId(task.id) ?: return val gtasksMetadata = googleTaskDao.getByTaskId(task.id) ?: return null
val remoteModel = Task() val remoteModel = Task()
var newlyCreated = false var newlyCreated = false
val remoteId: String? val remoteId: String?
@ -208,7 +226,7 @@ class GoogleTaskSynchronizer @Inject constructor(
// creating a task which may end up being cancelled. Also don't sync new but already // creating a task which may end up being cancelled. Also don't sync new but already
// deleted tasks // deleted tasks
if (newlyCreated && (isNullOrEmpty(task.title) || task.deletionDate > 0)) { if (newlyCreated && (isNullOrEmpty(task.title) || task.deletionDate > 0)) {
return return null
} }
// Update the remote model's changed properties // Update the remote model's changed properties
@ -231,10 +249,11 @@ class GoogleTaskSynchronizer @Inject constructor(
val parent = task.parent val parent = task.parent
val localParent = if (parent > 0) googleTaskDao.getRemoteId(parent) else null val localParent = if (parent > 0) googleTaskDao.getRemoteId(parent) else null
val previous = googleTaskDao.getPrevious( val previous = googleTaskDao.getPrevious(
listId!!, if (isNullOrEmpty(localParent)) 0 else parent, task.order ?: 0) listId, if (isNullOrEmpty(localParent)) 0 else parent, task.order ?: 0)
val created: Task? = try { val created: Task? = try {
gtasksInvoker.createGtask(listId, remoteModel, localParent, previous) gtasksInvoker.createGtask(listId, remoteModel, localParent, previous)
} catch (e: HttpNotFoundException) { } catch (e: HttpNotFoundException) {
Timber.e(e, "Failed to create task, retry without parent or order")
gtasksInvoker.createGtask(listId, remoteModel, null, null) gtasksInvoker.createGtask(listId, remoteModel, null, null)
} }
if (created != null) { if (created != null) {
@ -242,8 +261,10 @@ class GoogleTaskSynchronizer @Inject constructor(
gtasksMetadata.remoteId = created.id gtasksMetadata.remoteId = created.id
gtasksMetadata.calendar = listId gtasksMetadata.calendar = listId
setOrderAndParent(gtasksMetadata, created, task) setOrderAndParent(gtasksMetadata, created, task)
Timber.d("Created new task: $gtasksMetadata")
} else { } else {
return Timber.e("Empty response when creating task")
return null
} }
} else { } else {
try { try {
@ -252,29 +273,64 @@ class GoogleTaskSynchronizer @Inject constructor(
val parent = task.parent val parent = task.parent
val localParent = if (parent > 0) googleTaskDao.getRemoteId(parent) else null val localParent = if (parent > 0) googleTaskDao.getRemoteId(parent) else null
val previous = googleTaskDao.getPrevious( val previous = googleTaskDao.getPrevious(
listId!!, listId,
if (localParent.isNullOrBlank()) 0 else parent, if (localParent.isNullOrBlank()) 0 else parent,
task.order ?: 0) task.order ?: 0,
)
gtasksInvoker gtasksInvoker
.moveGtask(listId, remoteModel.id, localParent, previous) .moveGtask(
?.let { setOrderAndParent(gtasksMetadata, it, task) } listId = listId,
taskId = remoteModel.id,
parentId = localParent,
previousId = previous,
)
?.let {
setOrderAndParent(
googleTask = gtasksMetadata,
task = it,
local = task,
)
}
} catch (e: GoogleJsonResponseException) { } catch (e: GoogleJsonResponseException) {
if (e.statusCode == 400) { if (e.statusCode == 400) {
Timber.e(e) Timber.w("HTTP 400: clearing parent and order")
firebase.reportException(e)
taskDao.setParent(0L, listOf(task.id))
taskDao.setOrder(task.id, 0L)
googleTaskDao.update(gtasksMetadata.copy(isMoved = false))
return task.id
} else { } else {
throw e throw e
} }
} }
} }
// TODO: don't updateGtask if it was only moved // TODO: don't updateGtask if it was only moved
gtasksInvoker.updateGtask(listId, remoteModel) try {
} catch (e: HttpNotFoundException) { gtasksInvoker.updateGtask(listId, remoteModel)
} catch (e: GoogleJsonResponseException) {
if (e.statusCode == 400 && e.details?.message == "Invalid task ID") {
Timber.w("HTTP 400: Invalid task ID for ${remoteModel.id}, clearing to recreate on next sync")
firebase.reportException(e)
googleTaskDao.update(
gtasksMetadata.copy(
remoteId = "",
isMoved = false,
)
)
return task.id
} else {
throw e
}
}
} catch (_: HttpNotFoundException) {
Timber.w("HTTP 404, deleting $gtasksMetadata")
googleTaskDao.delete(gtasksMetadata) googleTaskDao.delete(gtasksMetadata)
return return null
} }
} }
gtasksMetadata.isMoved = false gtasksMetadata.isMoved = false
write(task, gtasksMetadata) write(task, gtasksMetadata)
return null
} }
@Throws(IOException::class) @Throws(IOException::class)

@ -96,9 +96,6 @@ class SyncWork @AssistedInject constructor(
private val syncStatus = R.string.p_sync_ongoing private val syncStatus = R.string.p_sync_ongoing
private suspend fun doSync() { private suspend fun doSync() {
if (preferences.isManualSort) {
preferences.isPositionHackEnabled = true
}
val hasNetworkConnectivity = context.hasNetworkConnectivity() val hasNetworkConnectivity = context.hasNetworkConnectivity()
if (hasNetworkConnectivity) { if (hasNetworkConnectivity) {
googleTaskJobs().plus(caldavJobs()).awaitAll() googleTaskJobs().plus(caldavJobs()).awaitAll()

@ -26,15 +26,15 @@ class Device @Inject constructor(
val pm = context.packageManager val pm = context.packageManager
val activities = val activities =
pm.queryIntentActivities(Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 0) pm.queryIntentActivities(Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 0)
return (activities.size != 0) return activities.isNotEmpty()
} }
private fun isDontKeepActivitiesEnabled(): Boolean { private fun isDontKeepActivitiesEnabled(): Boolean? {
return try { return try {
Settings.Global.getInt(context.contentResolver, Settings.Global.ALWAYS_FINISH_ACTIVITIES) == 1 Settings.Global.getInt(context.contentResolver, Settings.Global.ALWAYS_FINISH_ACTIVITIES) == 1
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e) Timber.e("failed to fetch ${Settings.Global.ALWAYS_FINISH_ACTIVITIES}: ${e.message}")
false null
} }
} }

@ -467,10 +467,6 @@ class Preferences @JvmOverloads constructor(
fun <T> getPrefs(c: Class<T>): Map<String, T> = fun <T> getPrefs(c: Class<T>): Map<String, T> =
prefs.all.filter { (_, value) -> c.isInstance(value) } as Map<String, T> prefs.all.filter { (_, value) -> c.isInstance(value) } as Map<String, T>
var isPositionHackEnabled: Boolean
get() = getLong(R.string.p_google_tasks_position_hack, 0) > currentTimeMillis() - ONE_WEEK
set(value) { setLong(R.string.p_google_tasks_position_hack, if (value) currentTimeMillis() else 0) }
override var isManualSort: Boolean override var isManualSort: Boolean
get() = getBoolean(R.string.p_manual_sort, false) get() = getBoolean(R.string.p_manual_sort, false)
set(value) { setBoolean(R.string.p_manual_sort, value) } set(value) { setBoolean(R.string.p_manual_sort, value) }

@ -721,4 +721,5 @@
<string name="continue_without_sync">Folytatás szinkronizálás nélkül</string> <string name="continue_without_sync">Folytatás szinkronizálás nélkül</string>
<string name="help_me_choose">Segítséget kérek a választásban</string> <string name="help_me_choose">Segítséget kérek a választásban</string>
<string name="multiline_title_off">Kattints a Kész gombra a feladat elmentéséhez</string> <string name="multiline_title_off">Kattints a Kész gombra a feladat elmentéséhez</string>
<string name="widget_view_more_tasks">További feladatok megtekintése</string>
</resources> </resources>

@ -7,7 +7,7 @@
<string name="backup_BAc_export">Utwórz kopię zapasową teraz</string> <string name="backup_BAc_export">Utwórz kopię zapasową teraz</string>
<string name="export_toast">Zapisano %1$s do %2$s.</string> <string name="export_toast">Zapisano %1$s do %2$s.</string>
<string name="import_summary_title">Podsumowanie odzyskiwania</string> <string name="import_summary_title">Podsumowanie odzyskiwania</string>
<string name="import_summary_message">Plik %1$s zawiera %2$s.\n\n %3$s zaimportowanych,\n %4$s już istnieje\n %5$s zawiera błędy\n</string> <string name="import_summary_message">Plik %1$s zawiera %2$s.\n\n %3$s zaimportowanych,\n %4$s już istnieje\n %5$s zawiera błędy</string>
<string name="import_progress_read">Czytanie zadania %d…</string> <string name="import_progress_read">Czytanie zadania %d…</string>
<string name="read_permission_label">Uprawnienia Tasks</string> <string name="read_permission_label">Uprawnienia Tasks</string>
<string name="discard_confirmation">Czy jesteś pewien, że chcesz porzucić zmiany?</string> <string name="discard_confirmation">Czy jesteś pewien, że chcesz porzucić zmiany?</string>
@ -703,4 +703,20 @@
<string name="sort_ascending">Rosnąco</string> <string name="sort_ascending">Rosnąco</string>
<string name="sort_descending">Malejąco</string> <string name="sort_descending">Malejąco</string>
<string name="sort_completed">Wg czasu zakończenia</string> <string name="sort_completed">Wg czasu zakończenia</string>
<string name="app_settings">Ustawienia aplikacji</string>
<string name="swipe_to_snooze_title">Przesuń aby uśpić</string>
<string name="swipe_to_snooze_time_immediately">natychmiast</string>
<string name="swipe_to_snooze_time_15_minutes">po 15 minutach</string>
<string name="swipe_to_snooze_time_30_minutes">po 30 minutach</string>
<string name="swipe_to_snooze_time_1_hour">po 1 godzinie</string>
<string name="swipe_to_snooze_time_24_hours">po 24 godzinach</string>
<string name="swipe_to_snooze_description">Czas drzemki</string>
<string name="delete_comment">komentarz</string>
<string name="comment">Komentarz</string>
<string name="yesterday">Wczoraj</string>
<string name="send_application_logs">Wyślij logi aplikacji</string>
<string name="change_priority">Zmień priorytet</string>
<string name="theme_dynamic">Dynamiczny</string>
<string name="continue_without_sync">Kontynuuj bez synchronizacji</string>
<string name="help_me_choose">Pomóż mi wybrać</string>
</resources> </resources>

@ -750,4 +750,5 @@
<string name="continue_without_sync">Продолжить без синхронизации</string> <string name="continue_without_sync">Продолжить без синхронизации</string>
<string name="help_me_choose">Помогите мне выбрать</string> <string name="help_me_choose">Помогите мне выбрать</string>
<string name="delete_tasks_warning">%s будет удалена. Это нельзя отменить!</string> <string name="delete_tasks_warning">%s будет удалена. Это нельзя отменить!</string>
<string name="widget_view_more_tasks">Просмотреть больше задач</string>
</resources> </resources>

@ -373,7 +373,6 @@
<string name="p_linkify_task_edit">linkify_task_edit</string> <string name="p_linkify_task_edit">linkify_task_edit</string>
<string name="preference_screen">preference_screen</string> <string name="preference_screen">preference_screen</string>
<string name="p_add_to_top">google_tasks_add_to_top</string> <string name="p_add_to_top">google_tasks_add_to_top</string>
<string name="p_google_tasks_position_hack">google_tasks_position_hack</string>
<string name="p_wearable_notifications">wearable_notifications</string> <string name="p_wearable_notifications">wearable_notifications</string>
<string name="p_notified_oauth_error">notified_oauth_error_%1$s_%2$s</string> <string name="p_notified_oauth_error">notified_oauth_error_%1$s_%2$s</string>
<string name="p_chip_appearance">chip_appearance</string> <string name="p_chip_appearance">chip_appearance</string>

@ -765,16 +765,16 @@
+| \--- com.google.android.gms:play-services-wearable:19.0.0 (*) +| \--- com.google.android.gms:play-services-wearable:19.0.0 (*)
++--- com.google.android.horologist:horologist-datalayer:0.7.15 (*) ++--- com.google.android.horologist:horologist-datalayer:0.7.15 (*)
++--- com.google.android.gms:play-services-wearable:19.0.0 (*) ++--- com.google.android.gms:play-services-wearable:19.0.0 (*)
++--- com.microsoft.identity.client:msal:6.2.0 ++--- com.microsoft.identity.client:msal:7.0.0
+| +--- com.microsoft.identity:common:21.4.0 +| +--- com.microsoft.identity:common:22.0.0
+| | +--- com.microsoft.identity:common4j:21.4.0 +| | +--- com.microsoft.identity:common4j:22.0.0
+| | +--- org.jetbrains.kotlin:kotlin-stdlib:1.7.21 -> 2.2.0 (*) +| | +--- org.jetbrains.kotlin:kotlin-stdlib:1.8.0 -> 2.2.0 (*)
+| | +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4 -> 1.10.2 (*) +| | +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4 -> 1.10.2 (*)
+| | +--- androidx.lifecycle:lifecycle-runtime-ktx:2.5.1 -> 2.9.2 (*) +| | +--- androidx.lifecycle:lifecycle-runtime-ktx:2.5.1 -> 2.9.2 (*)
+| | +--- androidx.datastore:datastore-preferences:1.0.0 -> 1.1.7 (*) +| | +--- androidx.datastore:datastore-preferences:1.0.0 -> 1.1.7 (*)
+| | +--- org.apache.httpcomponents.core5:httpcore5:5.3 +| | +--- org.apache.httpcomponents.core5:httpcore5:5.3
+| | +--- com.nimbusds:nimbus-jose-jwt:9.37.3 +| | +--- com.nimbusds:nimbus-jose-jwt:10.0.2
+| | | \--- com.github.stephenc.jcip:jcip-annotations:1.0-1 +| | +--- androidx.activity:activity:1.8.2 -> 1.10.1 (*)
+| | +--- androidx.appcompat:appcompat:1.1.0 -> 1.7.1 (*) +| | +--- androidx.appcompat:appcompat:1.1.0 -> 1.7.1 (*)
+| | +--- com.google.code.gson:gson:2.8.9 -> 2.12.1 +| | +--- com.google.code.gson:gson:2.8.9 -> 2.12.1
+| | +--- com.squareup.moshi:moshi:1.14.0 +| | +--- com.squareup.moshi:moshi:1.14.0
@ -838,11 +838,11 @@
+| | +--- io.opentelemetry:opentelemetry-api:1.18.0 +| | +--- io.opentelemetry:opentelemetry-api:1.18.0
+| | | \--- io.opentelemetry:opentelemetry-context:1.18.0 +| | | \--- io.opentelemetry:opentelemetry-context:1.18.0
+| | \--- androidx.fragment:fragment:1.3.2 -> 1.8.8 (*) +| | \--- androidx.fragment:fragment:1.3.2 -> 1.8.8 (*)
+| +--- org.jetbrains.kotlin:kotlin-stdlib:1.7.21 -> 2.2.0 (*) +| +--- org.jetbrains.kotlin:kotlin-stdlib:1.8.0 -> 2.2.0 (*)
+| +--- androidx.appcompat:appcompat:1.1.0 -> 1.7.1 (*) +| +--- androidx.appcompat:appcompat:1.1.0 -> 1.7.1 (*)
+| +--- androidx.browser:browser:1.0.0 -> 1.3.0 (*) +| +--- androidx.browser:browser:1.0.0 -> 1.3.0 (*)
+| +--- com.google.code.gson:gson:2.8.9 -> 2.12.1 +| +--- com.google.code.gson:gson:2.8.9 -> 2.12.1
+| +--- com.nimbusds:nimbus-jose-jwt:9.37.3 (*) +| +--- com.nimbusds:nimbus-jose-jwt:10.0.2
+| +--- org.apache.httpcomponents.core5:httpcore5:5.3 +| +--- org.apache.httpcomponents.core5:httpcore5:5.3
+| +--- androidx.constraintlayout:constraintlayout:1.1.3 -> 2.2.1 (*) +| +--- androidx.constraintlayout:constraintlayout:1.1.3 -> 2.2.1 (*)
+| +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4 -> 1.10.2 (*) +| +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4 -> 1.10.2 (*)

@ -19,7 +19,7 @@ dav4jvm = "2.2.1"
desugar_jdk_libs = "2.1.5" desugar_jdk_libs = "2.1.5"
etebase = "2.3.2" etebase = "2.3.2"
firebase = "33.16.0" firebase = "33.16.0"
firebase-crashlytics-gradle = "3.0.5" firebase-crashlytics-gradle = "3.0.6"
google-oauth2 = "1.37.1" google-oauth2 = "1.37.1"
google-api-drive = "v3-rev20250723-2.0.0" google-api-drive = "v3-rev20250723-2.0.0"
google-api-tasks = "v1-rev20250518-2.0.0" google-api-tasks = "v1-rev20250518-2.0.0"
@ -46,7 +46,7 @@ mockito = "5.18.0"
okhttp = "4.12.0" okhttp = "4.12.0"
opentasks = "562fec5" opentasks = "562fec5"
osmdroid = "6.1.20" osmdroid = "6.1.20"
oss-licenses-plugin = "0.10.6" oss-licenses-plugin = "0.10.7"
persistent-cookiejar = "1.0.1" persistent-cookiejar = "1.0.1"
play-services-maps = "19.2.0" play-services-maps = "19.2.0"
play-services-location = "21.3.0" play-services-location = "21.3.0"
@ -159,7 +159,7 @@ markwon-strikethrough = { module = "io.noties.markwon:ext-strikethrough", versio
markwon-tables = { module = "io.noties.markwon:ext-tables", version.ref = "markwon" } markwon-tables = { module = "io.noties.markwon:ext-tables", version.ref = "markwon" }
markwon-tasklist = { module = "io.noties.markwon:ext-tasklist", version.ref = "markwon" } markwon-tasklist = { module = "io.noties.markwon:ext-tasklist", version.ref = "markwon" }
material = { module = "com.google.android.material:material", version.ref = "material" } material = { module = "com.google.android.material:material", version.ref = "material" }
microsoft-authentication = { module = "com.microsoft.identity.client:msal", version = "6.2.0" } microsoft-authentication = { module = "com.microsoft.identity.client:msal", version = "7.0.0" }
mockito-android = { module = "org.mockito:mockito-android", version.ref = "mockito" } mockito-android = { module = "org.mockito:mockito-android", version.ref = "mockito" }
mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito" } mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito" }
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }

@ -27,4 +27,5 @@
<string name="today_lowercase">dzisiaj</string> <string name="today_lowercase">dzisiaj</string>
<string name="show_completed">Pokaż ukończone</string> <string name="show_completed">Pokaż ukończone</string>
<string name="show_unstarted">Pokaż nierozpoczęte</string> <string name="show_unstarted">Pokaż nierozpoczęte</string>
<string name="requires_pro_subscription">Funkcja Pro</string>
</resources> </resources>

@ -94,7 +94,11 @@ fun TaskListDrawer(
BottomAppBar( BottomAppBar(
modifier = Modifier modifier = Modifier
.layout { measurable, constraints -> .layout { measurable, constraints ->
val placeable = measurable.measure(constraints) val safeConstraints = constraints.copy(
minHeight = constraints.minHeight.coerceAtLeast(0),
maxHeight = constraints.maxHeight.coerceAtLeast(0)
)
val placeable = measurable.measure(safeConstraints)
bottomAppBarScrollBehavior.state.heightOffsetLimit = bottomAppBarScrollBehavior.state.heightOffsetLimit =
-placeable.height.toFloat() -placeable.height.toFloat()
val height = val height =

Loading…
Cancel
Save