Compare commits

...

36 Commits

Author SHA1 Message Date
Sunik Kupfer
7f36e826d8 Cancel syncs for calendar, tasks, and contacts separately 2025-09-04 12:13:18 +02:00
Sunik Kupfer
3737d69397 Add FAB to cancel sync adapter syncs 2025-09-04 11:43:10 +02:00
Sunik Kupfer
2dbd5c02b6 Cancel by request and empty bundle 2025-09-04 11:09:23 +02:00
Sunik Kupfer
c12e9311f7 Stop always returning false for pending sync state of sync adapter framework 2025-09-04 10:58:40 +02:00
Sunik Kupfer
b663912feb Enable forever pending sync workaround by canceling sync adapter framework syncs on Android 14+ 2025-09-04 10:56:54 +02:00
Sunik Kupfer
3c484f253f Use cancelSync directly in migration 2025-09-04 10:53:50 +02:00
Sunik Kupfer
de7f8d2964 Cancel for all authorities and update kdoc 2025-09-04 10:53:50 +02:00
Sunik Kupfer
a79a39c25d Cancel only on Android 14+ 2025-09-04 10:53:50 +02:00
Sunik Kupfer
20675ed71b Update kdoc 2025-09-04 10:53:50 +02:00
Sunik Kupfer
881588f8e8 Don't infer authority from account type 2025-09-04 10:53:50 +02:00
Sunik Kupfer
0c31758880 Also cancel calendar syncs 2025-09-04 10:53:50 +02:00
Sunik Kupfer
9ffd59cd00 Updating log statement 2025-09-04 10:53:50 +02:00
Sunik Kupfer
c40b2b38bc Update kdoc 2025-09-04 10:53:50 +02:00
Sunik Kupfer
3025ea7491 Optimize imports 2025-09-04 10:53:50 +02:00
Sunik Kupfer
b84a812d7a Call cancelSync via integration 2025-09-04 10:53:50 +02:00
Sunik Kupfer
562afc5666 Add and update kdoc 2025-09-04 10:53:50 +02:00
Sunik Kupfer
8992859b63 Increase account settings current version 2025-09-04 10:53:50 +02:00
Sunik Kupfer
03013b5576 Add log statement 2025-09-04 10:53:50 +02:00
Sunik Kupfer
0028fc8722 Add application context annotation 2025-09-04 10:53:50 +02:00
Sunik Kupfer
1b4ebde896 Add AccountSettingsMigration21 to cancel pending address book syncs 2025-09-04 10:53:50 +02:00
Ricki Hirner
0cc84dfd01 Update synctools (#1690) 2025-09-03 16:47:40 +02:00
Ricki Hirner
87239daaf6 Fix invalid translation 2025-09-03 15:53:43 +02:00
Ricki Hirner
81ceb57842 Version bump to 4.5.4-rc.1 2025-09-03 15:41:31 +02:00
Ricki Hirner
cd0b0c0804 Fetch translations from Transifex 2025-09-03 15:41:12 +02:00
Sunik Kupfer
48cbd4a05d Disable pending sync indicator on Android 14+ (#1689)
* Disable pending sync indicator on Android 14+

* Disable forever pending sync workaround on Android 14+
2025-09-03 15:32:29 +02:00
Ricki Hirner
beccc7a0d4 Version bump to 4.5.4-beta.1 2025-09-01 11:09:52 +02:00
Sunik Kupfer
2b629c8b18 Add comments on foreign key constraint enforcement (#1675)
* Add comments on foreign key constraint enforcement

* Fix grammar

* Add foreign key constraint comment to AppDatabase

* Update kdoc with better description
2025-08-28 11:47:49 +02:00
Arnau Mora
cd725479cd Handle null cases for Events.DIRTY (#1663)
* Handled nullity of `Calendar.dirty`

* Fixed ids

* Use multiline SQL queries

* Fix kdoc links

* Improve kdoc

* Fix capitalization

* Simplify kdoc

* Improved `LocalCalendarTest`
2025-08-28 11:25:57 +02:00
Ricki Hirner
44666d2138 Add groups section to dependabot configuration (#1677)
* Add groups section to dependabot configuration

* Fix patterns syntax in dependabot.yml
2025-08-27 15:56:34 +02:00
dependabot[bot]
8e67db7d54 [CI] Bump actions/setup-java from 4 to 5 (#1672)
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4 to 5.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-26 10:10:21 +02:00
Sunik Kupfer
a58e3b9036 Don't disable sync-ability of address books when set to manual only (#1662)
Only disable sync on content changes; but leave syncable
2025-08-25 10:15:56 +02:00
Ricki Hirner
d63918ff42 Update dependencies to latest versions (including AGP) (#1665) 2025-08-19 16:45:27 +02:00
dependabot[bot]
f21c3de94a [CI] Bump actions/checkout from 4 to 5 (#1664)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-19 13:54:15 +02:00
Ricki Hirner
24d4ba65e5 Fix NPE when Event._ID is larger than Integer.MAX_VALUE (#1661)
* Add test that reproduces NPE

* Fix NPE when Event ID is larger than Integer.MAX_VALUE
2025-08-14 17:19:04 +02:00
Arnau Mora
ae96f1ffbb Using new suggested icons for tabs (#1657) 2025-08-14 10:20:08 +02:00
Arnau Mora
a08ecae635 Change "Account doesn't exist" toast message to "Account has been removed" for deleting accounts (#1650)
* Finish activity after deleting

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added proper toast for when the account is deleted

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Simplify logic

* Missing fix

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2025-08-13 08:55:26 +02:00
33 changed files with 370 additions and 77 deletions

View File

@@ -8,4 +8,7 @@ updates:
schedule:
interval: "weekly"
commit-message:
prefix: "[CI] "
prefix: "[CI] "
groups:
ci-actions:
patterns: ["*"]

View File

@@ -28,8 +28,8 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
- uses: actions/setup-java@v4
uses: actions/checkout@v5
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21

View File

@@ -19,8 +19,8 @@ jobs:
discussions: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
- uses: actions/checkout@v5
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21

View File

@@ -15,8 +15,8 @@ jobs:
if: ${{ github.ref == 'refs/heads/main-ose' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
- uses: actions/checkout@v5
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
@@ -35,8 +35,8 @@ jobs:
name: Lint and unit tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
- uses: actions/checkout@v5
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
@@ -56,8 +56,8 @@ jobs:
name: Instrumented tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
- uses: actions/checkout@v5
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21

View File

@@ -19,8 +19,8 @@ android {
defaultConfig {
applicationId = "at.bitfire.davdroid"
versionCode = 405040000
versionName = "4.5.4-alpha.1"
versionCode = 405040002
versionName = "4.5.4-rc.1"
base.archivesName = "davx5-ose-$versionName"

View File

@@ -44,6 +44,11 @@ abstract class DatabaseMigrationTest(
/**
* Used for testing the migration process from [toVersion]-1 to [toVersion].
*
* Note: SQLite's foreign key constraint enforcement is not enabled in tests. We need
* to enable it ourselves using setting "PRAGMA foreign_keys=ON" directly after opening
* a new database connection (works per connection). In tests it's usually more practical
* not to do so, however. In production database connections room enables it for us.
*
* @param prepare Callback to prepare the database. Will be run with database schema in version [toVersion] - 1.
* @param validate Callback to validate the migration result. Will be run with database schema in version [toVersion].
*/
@@ -61,6 +66,8 @@ abstract class DatabaseMigrationTest(
// Prepare the database with the initial version.
val dbName = "test"
helper.createDatabase(dbName, version = toVersion - 1).apply {
// We could enable foreign key constraint enforcement here
// by setting "PRAGMA foreign_keys=ON".
prepare(this)
close()
}

View File

@@ -8,14 +8,18 @@ import android.accounts.Account
import android.content.ContentProviderClient
import android.content.ContentUris
import android.content.ContentValues
import android.content.Entity
import android.provider.CalendarContract
import android.provider.CalendarContract.ACCOUNT_TYPE_LOCAL
import android.provider.CalendarContract.Events
import androidx.core.content.contentValuesOf
import androidx.test.platform.app.InstrumentationRegistry
import at.bitfire.ical4android.Event
import at.bitfire.ical4android.util.MiscUtils.asSyncAdapter
import at.bitfire.ical4android.util.MiscUtils.closeCompat
import at.bitfire.synctools.storage.calendar.AndroidCalendar
import at.bitfire.synctools.storage.calendar.AndroidCalendarProvider
import at.bitfire.synctools.storage.calendar.AndroidEvent2
import at.bitfire.synctools.test.InitCalendarProviderRule
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
@@ -25,6 +29,8 @@ import net.fortuna.ical4j.model.property.RecurrenceId
import net.fortuna.ical4j.model.property.Status
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -44,6 +50,7 @@ class LocalCalendarTest {
lateinit var localCalendarFactory: LocalCalendar.Factory
private val account = Account("LocalCalendarTest", ACCOUNT_TYPE_LOCAL)
private lateinit var androidCalendar: AndroidCalendar
private lateinit var client: ContentProviderClient
private lateinit var calendar: LocalCalendar
@@ -55,12 +62,13 @@ class LocalCalendarTest {
client = context.contentResolver.acquireContentProviderClient(CalendarContract.AUTHORITY)!!
val provider = AndroidCalendarProvider(account, client)
calendar = localCalendarFactory.create(provider.createAndGetCalendar(ContentValues()))
androidCalendar = provider.createAndGetCalendar(ContentValues())
calendar = localCalendarFactory.create(androidCalendar)
}
@After
fun tearDown() {
calendar.androidCalendar.delete()
androidCalendar.delete()
client.closeCompat()
}
@@ -135,24 +143,94 @@ class LocalCalendarTest {
flags = LocalResource.FLAG_REMOTELY_PRESENT
)
val localEvent = calendar.findByName("filename.ics")!!
val eventId = localEvent.id
val eventUrl = androidCalendar.eventUri(localEvent.id)
// set event as dirty
client.update(ContentUris.withAppendedId(Events.CONTENT_URI.asSyncAdapter(account), eventId), ContentValues(1).apply {
put(Events.DIRTY, 1)
}, null, null)
client.update(eventUrl, contentValuesOf(
Events.DIRTY to 1
), null, null)
// this method should mark the event as deleted
calendar.deleteDirtyEventsWithoutInstances()
// verify that event is not marked as deleted
client.query(
ContentUris.withAppendedId(Events.CONTENT_URI.asSyncAdapter(account), eventId),
arrayOf(Events.DELETED), null, null, null
)!!.use { cursor ->
client.query(eventUrl, arrayOf(Events.DELETED), null, null, null)!!.use { cursor ->
cursor.moveToNext()
assertEquals(0, cursor.getInt(0))
}
}
/**
* Verifies that [LocalCalendar.removeNotDirtyMarked] works as expected.
* @param contentValues values to set on the event. Required:
* - [Events._ID]
* - [Events.DIRTY]
*/
private fun testRemoveNotDirtyMarked(contentValues: ContentValues) {
val id = androidCalendar.addEvent(Entity(
contentValuesOf(
Events.CALENDAR_ID to androidCalendar.id,
Events.DTSTART to System.currentTimeMillis(),
Events.DTEND to System.currentTimeMillis(),
Events.TITLE to "Some Event",
AndroidEvent2.COLUMN_FLAGS to 123
).apply { putAll(contentValues) }
))
calendar.removeNotDirtyMarked(123)
assertNull(androidCalendar.getEvent(id))
}
@Test
fun testRemoveNotDirtyMarked_IdLargerThanIntMaxValue() = testRemoveNotDirtyMarked(
contentValuesOf(Events._ID to Int.MAX_VALUE.toLong() + 10, Events.DIRTY to 0)
)
@Test
fun testRemoveNotDirtyMarked_DirtyIs0() = testRemoveNotDirtyMarked(
contentValuesOf(Events._ID to 1, Events.DIRTY to 0)
)
@Test
fun testRemoveNotDirtyMarked_DirtyNull() = testRemoveNotDirtyMarked(
contentValuesOf(Events._ID to 1, Events.DIRTY to null)
)
/**
* Verifies that [LocalCalendar.markNotDirty] works as expected.
* @param contentValues values to set on the event. Required:
* - [Events.DIRTY]
*/
private fun testMarkNotDirty(contentValues: ContentValues) {
val id = androidCalendar.addEvent(Entity(
contentValuesOf(
Events.CALENDAR_ID to androidCalendar.id,
Events._ID to 1,
Events.DTSTART to System.currentTimeMillis(),
Events.DTEND to System.currentTimeMillis(),
Events.TITLE to "Some Event",
AndroidEvent2.COLUMN_FLAGS to 123
).apply { putAll(contentValues) }
))
val updated = calendar.markNotDirty(321)
assertEquals(1, updated)
assertEquals(321, androidCalendar.getEvent(id)?.flags)
}
@Test
fun test_markNotDirty_DirtyIs0() = testMarkNotDirty(
contentValuesOf(
Events.DIRTY to 0
)
)
@Test
fun test_markNotDirty_DirtyIsNull() = testMarkNotDirty(
contentValuesOf(
Events.DIRTY to null
)
)
}

View File

@@ -35,6 +35,13 @@ import dagger.hilt.components.SingletonComponent
import java.io.Writer
import javax.inject.Singleton
/**
* The app database. Managed via android jetpack room. Room provides an abstraction
* layer over SQLite.
*
* Note: In SQLite PRAGMA foreign_keys is off by default. Room activates it for
* production (non-test) databases.
*/
@Database(entities = [
Service::class,
HomeSet::class,

View File

@@ -234,7 +234,7 @@ open class LocalAddressBook @AssistedInject constructor(
if (syncInterval != null)
syncFramework.enableSyncOnContentChange(addressBookAccount, ContactsContract.AUTHORITY)
else
syncFramework.disableSyncAbility(addressBookAccount, ContactsContract.AUTHORITY)
syncFramework.disableSyncOnContentChange(addressBookAccount, ContactsContract.AUTHORITY)
}

View File

@@ -107,7 +107,12 @@ class LocalCalendar @AssistedInject constructor(
override fun markNotDirty(flags: Int) =
androidCalendar.updateEventRows(
contentValuesOf(AndroidEvent2.COLUMN_FLAGS to flags),
"${Events.CALENDAR_ID}=? AND NOT ${Events.DIRTY} AND ${Events.ORIGINAL_ID} IS NULL",
// `dirty` can be 0, 1, or null. "NOT dirty" is not enough.
"""
${Events.CALENDAR_ID}=?
AND (${Events.DIRTY} IS NULL OR ${Events.DIRTY}=0)
AND ${Events.ORIGINAL_ID} IS NULL
""".trimIndent(),
arrayOf(androidCalendar.id.toString())
)
@@ -116,14 +121,20 @@ class LocalCalendar @AssistedInject constructor(
val batch = CalendarBatchOperation(androidCalendar.client)
androidCalendar.iterateEventRows(
arrayOf(Events._ID),
"${Events.CALENDAR_ID}=? AND NOT ${Events.DIRTY} AND ${Events.ORIGINAL_ID} IS NULL AND ${AndroidEvent2.COLUMN_FLAGS}=?",
// `dirty` can be 0, 1, or null. "NOT dirty" is not enough.
"""
${Events.CALENDAR_ID}=?
AND (${Events.DIRTY} IS NULL OR ${Events.DIRTY}=0)
AND ${Events.ORIGINAL_ID} IS NULL
AND ${AndroidEvent2.COLUMN_FLAGS}=?
""".trimIndent(),
arrayOf(androidCalendar.id.toString(), flags.toString())
) { values ->
val id = values.getAsInteger(Events._ID)
val id = values.getAsLong(Events._ID)
// delete event and possible exceptions (content provider doesn't delete exceptions itself)
batch += BatchOperation.CpoBuilder
.newDelete(Events.CONTENT_URI.asSyncAdapter(androidCalendar.account))
.newDelete(androidCalendar.eventsUri)
.withSelection("${Events._ID}=? OR ${Events.ORIGINAL_ID}=?", arrayOf(id.toString(), id.toString()))
}
return batch.commit()

View File

@@ -49,8 +49,7 @@ interface LocalCollection<out T: LocalResource<*>> {
fun findByName(name: String): T?
/**
* Sets the [LocalEvent.COLUMN_FLAGS] value for entries which are not dirty ([Events.DIRTY] is 0)
* and have an [Events.ORIGINAL_ID] of null.
* Updates the flags value for entries which are not dirty.
*
* @param flags value of flags to set (for instance, [LocalResource.FLAG_REMOTELY_PRESENT]])
*
@@ -59,8 +58,7 @@ interface LocalCollection<out T: LocalResource<*>> {
fun markNotDirty(flags: Int): Int
/**
* Removes entries which are not dirty ([Events.DIRTY] is 0 and an [Events.ORIGINAL_ID] is null) with
* a given flag combination.
* Removes entries which are not dirty with a given flag combination.
*
* @param flags exact flags value to remove entries with (for instance, if this is [LocalResource.FLAG_REMOTELY_PRESENT]],
* all entries with exactly this flag will be removed)

View File

@@ -354,7 +354,12 @@ class AccountSettings @AssistedInject constructor(
companion object {
const val CURRENT_VERSION = 20
/**
* Current (usually the newest) account settings version. It's used to
* determine whether a migration ([AccountSettingsMigration])
* should be performed.
*/
const val CURRENT_VERSION = 21
const val KEY_SETTINGS_VERSION = "version"
const val KEY_SYNC_INTERVAL_ADDRESSBOOKS = "sync_interval_addressbooks"

View File

@@ -10,7 +10,8 @@ import at.bitfire.davdroid.settings.AccountSettings
interface AccountSettingsMigration {
/**
* Migrate the account settings from the old version to the new version.
* Migrate the account settings from the old version to the new version which
* is set in [AccountSettings.CURRENT_VERSION].
*
* **The new (target) version number is registered in the Hilt module as [Int] key of the multi-binding of [AccountSettings].**
*

View File

@@ -0,0 +1,76 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.settings.migration
import android.accounts.Account
import android.accounts.AccountManager
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.provider.CalendarContract
import android.provider.ContactsContract
import at.bitfire.davdroid.R
import at.bitfire.davdroid.sync.adapter.SyncFrameworkIntegration
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import dagger.multibindings.IntKey
import dagger.multibindings.IntoMap
import java.util.logging.Logger
import javax.inject.Inject
/**
* On Android 14+ the pending sync state of the Sync Adapter Framework is not handled correctly.
* As a workaround we cancel incoming sync requests (clears pending flag) after enqueuing our own
* sync worker (work manager). With version 4.5.3 we started cancelling pending syncs for DAVx5
* accounts, but forgot to do that for address book accounts. With version 4.5.4 we also cancel
* those, but only when contact data of an address book has been edited.
*
* This migration cancels (once only) any possibly still wrongly pending address book and calendar
* (+tasks) account syncs.
*/
class AccountSettingsMigration21 @Inject constructor(
@ApplicationContext private val context: Context,
private val syncFrameworkIntegration: SyncFrameworkIntegration,
private val logger: Logger
): AccountSettingsMigration {
private val accountManager = AccountManager.get(context)
private val calendarAccountType = context.getString(R.string.account_type)
private val addressBookAccountType = context.getString(R.string.account_type_address_book)
override fun migrate(account: Account) {
if (Build.VERSION.SDK_INT >= 34) {
// Cancel any (after an update) possibly forever pending calendar (+tasks) account syncs
cancelSyncs(calendarAccountType, CalendarContract.AUTHORITY)
// Cancel any (after an update) possibly forever pending address book account syncs
cancelSyncs(addressBookAccountType, ContactsContract.AUTHORITY)
}
}
/**
* Cancels any (possibly forever pending) syncs for the accounts of given account type for all
* authorities.
*/
private fun cancelSyncs(accountType: String, authority: String) {
accountManager.getAccountsByType(accountType).forEach { account ->
logger.info("Android 14+: Canceling all (possibly forever pending) syncs for $account")
syncFrameworkIntegration.cancelSync(account, authority, Bundle())
}
}
@Module
@InstallIn(SingletonComponent::class)
abstract class AccountSettingsMigrationModule {
@Binds @IntoMap
@IntKey(21)
abstract fun provide(impl: AccountSettingsMigration21): AccountSettingsMigration
}
}

View File

@@ -8,6 +8,7 @@ import android.accounts.Account
import android.content.ContentResolver
import android.content.Context
import android.content.SyncRequest
import android.os.Build
import android.os.Bundle
import androidx.annotation.WorkerThread
import at.bitfire.davdroid.resource.LocalAddressBookStore
@@ -100,11 +101,9 @@ class SyncFrameworkIntegration @Inject constructor(
}
/**
* Cancels the sync request in the Sync Framework for Android 14+.
* This is a workaround for the bug that the sync framework does not handle pending syncs correctly
* on Android 14+ (API level 34+).
*
* See: https://github.com/bitfireAT/davx5-ose/issues/1458
* Cancels the sync request in the Sync Adapter Framework by sync request. This
* is the defensive approach canceling only one specific sync request with matching
* sync extras.
*
* @param account The account for which the sync request should be canceled.
* @param authority The authority for which the sync request should be canceled.

View File

@@ -5,6 +5,8 @@
package at.bitfire.davdroid.ui
import android.accounts.Account
import android.accounts.AccountManager
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
@@ -13,6 +15,7 @@ import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.Build
import android.os.PowerManager
import android.provider.CalendarContract
import android.provider.ContactsContract
@@ -22,6 +25,7 @@ import androidx.lifecycle.ViewModel
import androidx.work.WorkInfo
import androidx.work.WorkManager
import androidx.work.WorkQuery
import at.bitfire.davdroid.R
import at.bitfire.davdroid.db.AppDatabase
import at.bitfire.davdroid.repository.AccountRepository
import at.bitfire.davdroid.servicedetection.RefreshCollectionsWorker
@@ -296,4 +300,34 @@ class AccountsModel @AssistedInject constructor(
false
}
fun cancelSyncAdapterSyncs() {
if (Build.VERSION.SDK_INT >= 34) {
val calendarAccountType = context.getString(R.string.account_type)
val addressBookAccountType = context.getString(R.string.account_type_address_book)
// Cancel any (after an update) possibly forever pending calendar account syncs
cancelSyncs(calendarAccountType, SyncDataType.EVENTS.possibleAuthorities())
// Cancel any (after an update) possibly forever pending tasks account syncs
cancelSyncs(calendarAccountType, SyncDataType.TASKS.possibleAuthorities())
// Cancel any (after an update) possibly forever pending address book account syncs
cancelSyncs(addressBookAccountType, SyncDataType.CONTACTS.possibleAuthorities())
}
}
/**
* Cancels any (possibly forever pending) syncs for the accounts of given account type for all
* authorities.
*/
private fun cancelSyncs(accountType: String, authorities: List<String>) {
val accountManager = AccountManager.get(context)
accountManager.getAccountsByType(accountType).forEach { account ->
logger.info("Android 14+: Canceling all (possibly forever pending) syncs for $account")
for (authority in authorities)
ContentResolver.cancelSync(account, authority)
}
}
}
}

View File

@@ -23,6 +23,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.BatterySaver
import androidx.compose.material.icons.filled.CancelScheduleSend
import androidx.compose.material.icons.filled.DataSaverOn
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.NotificationsOff
@@ -111,6 +112,7 @@ fun AccountsScreen(
}
AccountsScreen(
cancelSyncAdapterSyncs = { model.cancelSyncAdapterSyncs() },
accountsDrawerHandler = accountsDrawerHandler,
accounts = accounts,
showSyncAll = showSyncAll,
@@ -131,6 +133,7 @@ fun AccountsScreen(
@OptIn(ExperimentalMaterial3Api::class, ExperimentalPermissionsApi::class)
@Composable
fun AccountsScreen(
cancelSyncAdapterSyncs: () -> Unit,
accountsDrawerHandler: AccountsDrawerHandler,
accounts: List<AccountsModel.AccountInfo>,
showSyncAll: Boolean = true,
@@ -228,6 +231,17 @@ fun AccountsScreen(
contentDescription = stringResource(R.string.accounts_sync_all)
)
}
FloatingActionButton(
onClick = cancelSyncAdapterSyncs,
containerColor = MaterialTheme.colorScheme.secondary,
contentColor = MaterialTheme.colorScheme.onSecondary,
modifier = Modifier.padding(top = 24.dp)
) {
Icon(
Icons.Default.CancelScheduleSend,
contentDescription = stringResource(R.string.accounts_sync_all)
)
}
}
},
snackbarHost = { SnackbarHost(snackbarHostState) }
@@ -321,6 +335,7 @@ fun AccountsScreen(
@Preview
fun AccountsScreen_Preview_Empty() {
AccountsScreen(
cancelSyncAdapterSyncs = {},
accountsDrawerHandler = object: AccountsDrawerHandler() {
@Composable
override fun MenuEntries(snackbarHostState: SnackbarHostState) {
@@ -337,6 +352,7 @@ fun AccountsScreen_Preview_Empty() {
@Preview
fun AccountsScreen_Preview_OneAccount() {
AccountsScreen(
cancelSyncAdapterSyncs = {},
accountsDrawerHandler = object: AccountsDrawerHandler() {
@Composable
override fun MenuEntries(snackbarHostState: SnackbarHostState) {

View File

@@ -26,6 +26,8 @@ import androidx.compose.material.icons.filled.CalendarToday
import androidx.compose.material.icons.filled.CreateNewFolder
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.DriveFileRenameOutline
import androidx.compose.material.icons.filled.Group
import androidx.compose.material.icons.filled.Link
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.Settings
@@ -357,7 +359,7 @@ fun AccountScreen(
AccountScreen_Tab(
selected = idxCurrentPage == idxCardDav,
showIcon = showIcon,
icon = Icons.Default.Person,
icon = Icons.Default.Group,
text = stringResource(R.string.account_carddav),
animatedVisibilityScope = this@AnimatedContent,
sharedTransitionScope = this@SharedTransitionLayout,
@@ -371,7 +373,7 @@ fun AccountScreen(
AccountScreen_Tab(
selected = idxCurrentPage == idxWebcal,
showIcon = showIcon,
icon = Icons.Default.CalendarImport,
icon = Icons.Default.Link,
text = stringResource(R.string.account_webcal),
animatedVisibilityScope = this@AnimatedContent,
sharedTransitionScope = this@SharedTransitionLayout,

View File

@@ -204,7 +204,7 @@
<string name="app_settings_unifiedpush_distributor_fcm">FCM (Google Play)</string>
<string name="app_settings_unifiedpush_encrypted">Отдалечено подадените съобщения са винаги шифровани.</string>
<!--AccountScreen-->
<string name="account_invalid_account">Регистрацията не съществува</string>
<string name="account_invalid_account">Профилът е премахнат</string>
<string name="account_carddav">CardDAV</string>
<string name="account_caldav">CalDAV</string>
<string name="account_webcal">Webcal</string>

View File

@@ -204,7 +204,7 @@
<string name="app_settings_unifiedpush_distributor_fcm">FCM (Google Play)</string>
<string name="app_settings_unifiedpush_encrypted">Els missatges «push» sempre són xifrats.</string>
<!--AccountScreen-->
<string name="account_invalid_account">El compte no existeix</string>
<string name="account_invalid_account">S\'ha eliminat el compte</string>
<string name="account_carddav">CardDAV</string>
<string name="account_caldav">CalDAV</string>
<string name="account_webcal">Webcal</string>
@@ -246,8 +246,10 @@
<string name="login_password">Contrasenya</string>
<string name="login_password_hide">Oculta la contrasenya</string>
<string name="login_password_show">Mostra la contrasenya</string>
<string name="login_password_optional">Contrasenya (opcional)</string>
<string name="login_type_url">Inici de sessió amb un URL i un nom d\'usuari/ària</string>
<string name="login_user_name">Nom d\'usuari/ària</string>
<string name="login_user_name_optional">Nom d\'usuari (opcional)</string>
<string name="login_base_url">URL base</string>
<string name="login_base_url_info"><![CDATA[L\'URL base es comprovarà directament, però els <a href="%s">serveis també es descobreixen</a> utilitzant els registres de DNS i els URL ben coneguts.]]></string>
<string name="login_select_certificate">Selecciona el certificat</string>
@@ -261,6 +263,7 @@
<string name="login_account_not_added">No s\'ha pogut afegir el compte</string>
<string name="login_finish">Finalitza</string>
<string name="login_type_advanced">Inici de sessió avançat</string>
<string name="login_no_client_certificate_optional">Sense certificat del client (opcional)</string>
<string name="login_client_certificate_selected">Certificat del client: %s</string>
<string name="login_no_certificate_found">No s\'ha trobat cap certificat</string>
<string name="login_install_certificate">Instal·la un certificat</string>
@@ -360,6 +363,7 @@
<string name="create_addressbook">Crea una llibreta d\'adreces</string>
<string name="create_addressbook_maybe_not_supported">La creació de llibretes d\'adreces sobre CardDAV pot no ser suportada pel servidor.</string>
<string name="create_calendar">Crea un calendari</string>
<string name="create_calendar_time_zone_optional">Fus horari predeterminat (opcional)</string>
<string name="create_calendar_time_zone_none"></string>
<string name="create_calendar_type">Possibles entrades de calendari</string>
<string name="create_calendar_type_vevent">Esdeveniments</string>
@@ -435,9 +439,12 @@
<string name="webdav_add_mount_display_name">Nom a mostrar</string>
<string name="webdav_add_mount_url">URL del WebDAV</string>
<string name="webdav_add_mount_url_invalid">URL no vàlid</string>
<string name="webdav_add_mount_mountpoint_displayname">Punt de muntatge i nom de visualització</string>
<string name="webdav_add_mount_authentication">Autentificació</string>
<string name="webdav_add_mount_username">Nom d\'usuari/ària</string>
<string name="webdav_add_mount_password">Contrasenya</string>
<string name="webdav_add_mount_username_optional">Nom d\'usuari (opcional)</string>
<string name="webdav_add_mount_password_optional">Contrasenya (opcional)</string>
<string name="webdav_add_mount_add">Afegeix un muntatge</string>
<string name="webdav_add_mount_no_support">No hi ha cap servei WebDAV en aquest URL</string>
<string name="webdav_remove_mount_title">Elimina el punt de muntatge</string>

View File

@@ -204,7 +204,7 @@
<string name="app_settings_unifiedpush_distributor_fcm">FCM (Google Play)</string>
<string name="app_settings_unifiedpush_encrypted">Push-Nachrichten sind immer verschlüsselt.</string>
<!--AccountScreen-->
<string name="account_invalid_account">Konto nicht vorhanden</string>
<string name="account_invalid_account">Konto wurde entfernt</string>
<string name="account_carddav">CardDAV</string>
<string name="account_caldav">CalDAV</string>
<string name="account_webcal">Webcal</string>

View File

@@ -204,7 +204,7 @@
<string name="app_settings_unifiedpush_distributor_fcm">FCM (Google Play)</string>
<string name="app_settings_unifiedpush_encrypted">Tõuketeavituste sõnumid on alati krüptitud.</string>
<!--AccountScreen-->
<string name="account_invalid_account">Kasutajakontot pole olemas</string>
<string name="account_invalid_account">Kasutajakonto on eemaldatud</string>
<string name="account_carddav">CardDAV</string>
<string name="account_caldav">CalDAV</string>
<string name="account_webcal">Webcal</string>

View File

@@ -33,6 +33,7 @@
<string name="intro_battery_text">Sinkronizazioa tarte erregularretan ahalbidetzeko, %s atzeko planoan exekutatzen utzi behar da. Bestela, Androidek sinkronizazioa gelditu dezake edozein unean.</string>
<string name="intro_battery_dont_show">Ez ditut sinkronizazio tarte erregularrak behar.*</string>
<string name="intro_autostart_title">%s bateragarritasuna</string>
<string name="intro_autostart_text">Salatzailearen firmwareak sinkronizazioa blokeatu dezake. Kaltetua bazara, eskuz bakarrik konpon dezakezu.</string>
<string name="intro_autostart_dont_show">Beharrezko ezarpenak bukatu ditut. Ez gogorarazi berriro.*</string>
<string name="intro_leave_unchecked">* Utzi aktibatu gabe gero gogorarazteko. Aplikazioaren ezarpenetan berrezarri daiteke / %s</string>
<string name="intro_more_info">Informazio gehiago</string>
@@ -120,6 +121,7 @@
<string name="navigation_drawer_website">Webgunea</string>
<string name="navigation_drawer_manual">Manuala</string>
<string name="navigation_drawer_faq">FAQ</string>
<string name="navigation_drawer_managed">Erakundeentzat</string>
<string name="navigation_drawer_community">Komunitatea</string>
<string name="navigation_drawer_support_project">Lagundu proiektuari</string>
<string name="navigation_drawer_contribute">Nola lagundu</string>
@@ -203,7 +205,7 @@
<string name="app_settings_unifiedpush_distributor_fcm">FCM (Google Play)</string>
<string name="app_settings_unifiedpush_encrypted">Bultzatutako mezuak beti zifratzen dira.</string>
<!--AccountScreen-->
<string name="account_invalid_account">Kontua ez da existitzen</string>
<string name="account_invalid_account">Kontua ezabatu da</string>
<string name="account_carddav">CardDAV</string>
<string name="account_caldav">CalDAV</string>
<string name="account_webcal">Webcal</string>
@@ -245,8 +247,10 @@
<string name="login_password">Pasahitza</string>
<string name="login_password_hide">Ezkutatu pasahitza</string>
<string name="login_password_show">Erakutsi pasahitza</string>
<string name="login_password_optional">Pasahitza (aukerakoa)</string>
<string name="login_type_url">Saioa hasi URL eta erabiltzaile izenarekin</string>
<string name="login_user_name">Erabiltzaile izena</string>
<string name="login_user_name_optional">Erabiltzaile izena (aukerakoa)</string>
<string name="login_base_url">Oinarri URL</string>
<string name="login_base_url_info"><![CDATA[Oinarrizko URLa zuzenean egiaztatuko da, baina <a href="%s">zerbitzuak ere aurkitzen dira</a> DNS erregistroak eta URL ezagunak erabilita.]]></string>
<string name="login_select_certificate">Aukeratu ziurtagiria</string>
@@ -260,6 +264,7 @@
<string name="login_account_not_added">Ezin izan da kontua gehitu</string>
<string name="login_finish">Bukatu</string>
<string name="login_type_advanced">Saio-hasiera aurreratua</string>
<string name="login_no_client_certificate_optional">Bezero-ziurtagiririk gabe (aukerakoa)</string>
<string name="login_client_certificate_selected">Bezeroaren ziurtagiria: %s</string>
<string name="login_no_certificate_found">Ez da ziurtagiririk aurkitu</string>
<string name="login_install_certificate">Instalatu ziurtagiria</string>
@@ -359,6 +364,7 @@
<string name="create_addressbook">Sortu helbide liburua</string>
<string name="create_addressbook_maybe_not_supported">Baliteke zerbitzariak ez onartzea CardDAV bidez sortutako helbide-liburua.</string>
<string name="create_calendar">Sortu egutegia</string>
<string name="create_calendar_time_zone_optional">Lehenetsitako ordu-zona (aukerakoa)</string>
<string name="create_calendar_time_zone_none"></string>
<string name="create_calendar_type">Egutegi sarrera posibleak</string>
<string name="create_calendar_type_vevent">Gertaerak</string>
@@ -434,9 +440,12 @@
<string name="webdav_add_mount_display_name">Bistaratze-izena</string>
<string name="webdav_add_mount_url">WebDAV URL</string>
<string name="webdav_add_mount_url_invalid">URL baliogabea</string>
<string name="webdav_add_mount_mountpoint_displayname">Muntatu puntua eta bistaratu izena</string>
<string name="webdav_add_mount_authentication">Autentifikazioa</string>
<string name="webdav_add_mount_username">Erabiltzaile izena</string>
<string name="webdav_add_mount_password">Pasahitza</string>
<string name="webdav_add_mount_username_optional">Erabiltzaile izena (aukerakoa)</string>
<string name="webdav_add_mount_password_optional">Pasahitza (aukerakoa)</string>
<string name="webdav_add_mount_add">Gehitu muntaia</string>
<string name="webdav_add_mount_no_support">Ez dago WebDAV zerbitzurik URL honetan</string>
<string name="webdav_remove_mount_title">Kendu muntaia-puntua</string>

View File

@@ -203,7 +203,7 @@
<string name="app_settings_unifiedpush_distributor_fcm">FCM (Google Play)</string>
<string name="app_settings_unifiedpush_encrypted">プッシュメッセージは常に暗号化されます。</string>
<!--AccountScreen-->
<string name="account_invalid_account">アカウントが存在しません</string>
<string name="account_invalid_account">アカウントが削除されました</string>
<string name="account_carddav">CardDAV</string>
<string name="account_caldav">CalDAV</string>
<string name="account_webcal">Webcal</string>

View File

@@ -201,7 +201,6 @@
<string name="app_settings_unifiedpush_distributor_fcm">FCM (Google Play)</string>
<string name="app_settings_unifiedpush_encrypted">푸시 메시지는 항상 암호화됩니다.</string>
<!--AccountScreen-->
<string name="account_invalid_account">계정이 존재하지 않습니다.</string>
<string name="account_carddav">CardDAV</string>
<string name="account_caldav">CalDAV</string>
<string name="account_webcal">Webcal</string>

View File

@@ -5,8 +5,8 @@
<string name="account_title_address_book">DAVx⁵ Adresboek</string>
<string name="account_prefs_use_app">Verander hier niet van account! Gebruik in plaats daarvan direct de app om accounts te beheren.</string>
<string name="dialog_delete">Verwijderen</string>
<string name="dialog_remove">Verwijder</string>
<string name="dialog_deny">Annuleer</string>
<string name="dialog_remove">Verwijderen</string>
<string name="dialog_deny">Annuleren</string>
<string name="dialog_enable">Inschakelen</string>
<string name="field_required">Dit veld is verplicht</string>
<string name="help">Hulp</string>
@@ -161,14 +161,14 @@
<string name="app_settings_battery_optimization_exempted">App is vrijgesteld (aanbevolen)</string>
<string name="app_settings_battery_optimization_optimized">Batterijbeperkingen van toepassing (niet aanbevolen)</string>
<string name="app_settings_connection">Verbinding</string>
<string name="app_settings_proxy">Proxy type</string>
<string name="app_settings_proxy">Proxy-type</string>
<string-array name="app_settings_proxy_types">
<item>Systeem standaard</item>
<item>Geen proxy</item>
<item>HTTP</item>
<item>SOCKS (voor Orbot)</item>
</string-array>
<string name="app_settings_proxy_host">Proxy host naam</string>
<string name="app_settings_proxy_host">Proxy hostnaam</string>
<string name="app_settings_proxy_port">Proxy poort</string>
<string name="app_settings_security">Beveiliging</string>
<string name="app_settings_security_app_permissions">App rechten</string>
@@ -204,20 +204,20 @@
<string name="app_settings_unifiedpush_distributor_fcm">FCM (Google Play)</string>
<string name="app_settings_unifiedpush_encrypted">Pushberichten zijn altijd versleuteld.</string>
<!--AccountScreen-->
<string name="account_invalid_account">Account bestaat niet</string>
<string name="account_invalid_account">Account is verwijderd</string>
<string name="account_carddav">CardDAV</string>
<string name="account_caldav">CalDAV</string>
<string name="account_webcal">Webcal</string>
<string name="account_missing_permissions">Er zijn extra rechten nodig om deze collecties te synchroniseren.</string>
<string name="account_manage_permissions">Machtigingen beheren</string>
<string name="account_synchronize_now">Nu synchroniseren</string>
<string name="account_settings">Account instellingen</string>
<string name="account_rename">Naam account wijzigen</string>
<string name="account_settings">Account-instellingen</string>
<string name="account_rename">Accountnaam wijzigen</string>
<string name="account_rename_new_name_description">Niet opgeslagen lokale gegevens kunnen worden verwijderd. Na het hernoemen is opnieuw synchroniseren vereist.</string>
<string name="account_rename_new_name">Nieuwe accountnaam</string>
<string name="account_rename_rename">Naam wijzigen</string>
<string name="account_rename_exists_already">Accountnaam is al in gebruik</string>
<string name="account_rename_couldnt_rename">Naam account is niet gewijzigd</string>
<string name="account_rename_couldnt_rename">Accountnaam is niet gewijzigd</string>
<string name="account_delete">Account verwijderen</string>
<string name="account_delete_confirmation_title">Account echt verwijderen?</string>
<string name="account_delete_confirmation_text">Alle lokale kopieën van adresboeken, kalenders en takenlijsten worden verwijderd.</string>
@@ -244,7 +244,7 @@
<string name="login_email_address_error">Geldig e-mailadres vereist</string>
<string name="login_email_address_info"><![CDATA[Het e-maildomein wordt gebruikt als basis-URL. <a href="%s">Diensten worden ontdekt</a> met behulp van DNS-records en bekende URL\'s.]]></string>
<string name="login_password">Wachtwoord</string>
<string name="login_password_hide">Verberg wachtwoord</string>
<string name="login_password_hide">Wachtwoord verbergen</string>
<string name="login_password_show">Wachtwoord tonen</string>
<string name="login_password_optional">Wachtwoord (optioneel)</string>
<string name="login_type_url">Inloggen met URL en gebruikersnaam</string>
@@ -261,14 +261,14 @@
<string name="login_account_name_required">Accountnaam verplicht</string>
<string name="login_account_name_already_taken">Accountnaam is al in gebruik</string>
<string name="login_account_not_added">Account kon niet worden toegevoegd</string>
<string name="login_finish">afwerken</string>
<string name="login_finish">Afwerken</string>
<string name="login_type_advanced">Geavanceerd inloggen</string>
<string name="login_no_client_certificate_optional">Geen cliëntcertificaat (optioneel)</string>
<string name="login_client_certificate_selected">Cliëntcertificaat: %s</string>
<string name="login_no_certificate_found">Geen certificaat gevonden</string>
<string name="login_install_certificate">Certificaat installeren</string>
<string name="login_fastmail">Fastmail</string>
<string name="login_fastmail_account">Fastmail account</string>
<string name="login_fastmail_account">Fastmail-account</string>
<string name="login_fastmail_sign_in">Inloggen met Fastmail</string>
<string name="login_type_google">Google Contacten / Kalender</string>
<string name="login_google_account">Google account</string>
@@ -395,7 +395,7 @@
<string name="collection_push_support">Push-ondersteuning</string>
<string name="collection_push_web_push">Server adverteert Push-ondersteuning</string>
<string name="collection_push_subscribed_at">Ingeschreven op %1$s, vervalt op %2$s</string>
<string name="collection_last_sync">Laatste gesynchroniseerd (%s)</string>
<string name="collection_last_sync">Laatste synchronisatie (%s)</string>
<string name="collection_url">Adres (URL)</string>
<!--debugging and DebugInfoActivity-->
<string name="debug_info_title">Debug informatie</string>

View File

@@ -33,6 +33,7 @@
<string name="intro_battery_text">Pentru sincronizare la intervale regulate, %s trebuie să aibă voie să ruleze în fundal. În caz contrar, Android poate întrerupe sincronizarea în orice moment.</string>
<string name="intro_battery_dont_show">Nu am nevoie de intervale regulate de sincronizare.*</string>
<string name="intro_autostart_title">Compatibilitate %s </string>
<string name="intro_autostart_text">Firmware-ul specific vendorului poate bloca sincronizarea. Dacă ești afectat, poți rezolva acest lucru manual.</string>
<string name="intro_autostart_dont_show">Am făcut setările necesare. Nu-mi mai aminti.*</string>
<string name="intro_leave_unchecked">* Lasă nebifat pentru a fi reamintit mai târziu. Poate fi resetat în setările aplicației / %s.</string>
<string name="intro_more_info">Mai multe informații</string>
@@ -120,6 +121,7 @@
<string name="navigation_drawer_website">Pagină web</string>
<string name="navigation_drawer_manual">Manual</string>
<string name="navigation_drawer_faq">Întrebări frecvente</string>
<string name="navigation_drawer_managed">Pentru organizații</string>
<string name="navigation_drawer_community">Comunitate</string>
<string name="navigation_drawer_support_project">Susține proiectul</string>
<string name="navigation_drawer_contribute">Cum să contribui</string>
@@ -203,7 +205,7 @@
<string name="app_settings_unifiedpush_distributor_fcm">FCM (Google Play)</string>
<string name="app_settings_unifiedpush_encrypted">Mesajele push sunt întotdeauna criptate.</string>
<!--AccountScreen-->
<string name="account_invalid_account">Contul nu există</string>
<string name="account_invalid_account">Contul a fost eliminat</string>
<string name="account_carddav">CardDAV</string>
<string name="account_caldav">CalDAV</string>
<string name="account_webcal">Webcal</string>
@@ -245,8 +247,10 @@
<string name="login_password">Parolă</string>
<string name="login_password_hide">Ascunde parola</string>
<string name="login_password_show">Afișează parola</string>
<string name="login_password_optional">Parolă (opțional)</string>
<string name="login_type_url">Conecteează-te cu adresa URL și numele de utilizator</string>
<string name="login_user_name">Nume de utilizator</string>
<string name="login_user_name_optional">Nume de utilizator (opțional)</string>
<string name="login_base_url">Adresa URL de bază</string>
<string name="login_base_url_info"><![CDATA[Adresa URL de bază va fi verificată direct, dar <a href="%s">serviciile sunt de asemenea descoperite</a> folosind înregistrări DNS și adrese URL bine-cunoscute.]]></string>
<string name="login_select_certificate">Selectează certificatul</string>
@@ -260,6 +264,7 @@
<string name="login_account_not_added">Contul nu a putut fi adăugat</string>
<string name="login_finish">Finalizează</string>
<string name="login_type_advanced">Autentificare avansată</string>
<string name="login_no_client_certificate_optional">Fără certificat de client (opțional)</string>
<string name="login_client_certificate_selected">Certificat de client: %s</string>
<string name="login_no_certificate_found">Nu a fost găsit niciun certificat</string>
<string name="login_install_certificate">Instalare certificat</string>
@@ -361,6 +366,7 @@
<string name="create_addressbook">Creează agendă de adrese</string>
<string name="create_addressbook_maybe_not_supported">Crearea agendei prin CardDAV poate să nu fie acceptată de server.</string>
<string name="create_calendar">Creează un calendar</string>
<string name="create_calendar_time_zone_optional">Fus orar implicit (opțional)</string>
<string name="create_calendar_time_zone_none"></string>
<string name="create_calendar_type">Posibile intrări din calendar</string>
<string name="create_calendar_type_vevent">Evenimente</string>
@@ -436,9 +442,12 @@
<string name="webdav_add_mount_display_name">Numele afișat</string>
<string name="webdav_add_mount_url">URL WebDAV</string>
<string name="webdav_add_mount_url_invalid">URL greșit</string>
<string name="webdav_add_mount_mountpoint_displayname">Punctul de montare și numele de afișare</string>
<string name="webdav_add_mount_authentication">Autentificare</string>
<string name="webdav_add_mount_username">Nume de utilizator</string>
<string name="webdav_add_mount_password">Parolă</string>
<string name="webdav_add_mount_username_optional">Nume de utilizator (opțional)</string>
<string name="webdav_add_mount_password_optional">Parolă (opțional)</string>
<string name="webdav_add_mount_add">Adaugă montare</string>
<string name="webdav_add_mount_no_support">Niciun serviciu WebDAV la această adresă URL</string>
<string name="webdav_remove_mount_title">Elimină punctul de montare</string>

View File

@@ -206,7 +206,7 @@
<string name="app_settings_unifiedpush_distributor_fcm">FCM (Google Play)</string>
<string name="app_settings_unifiedpush_encrypted">Push-сообщения всегда зашифрованы.</string>
<!--AccountScreen-->
<string name="account_invalid_account">Аккаунт не существует</string>
<string name="account_invalid_account">Аккаунт удален</string>
<string name="account_carddav">CardDAV</string>
<string name="account_caldav">CalDAV</string>
<string name="account_webcal">WebСal</string>
@@ -417,10 +417,10 @@
<string name="debug_info_http_403_description">Запрос был отклонен. Для получения подробной информации проверьте задействованные ресурсы и отладочную информацию.</string>
<string name="debug_info_http_404_description">Запрошенного ресурса не существует (больше не существует). Проверьте задействованные ресурсы и отладочную информацию для получения подробной информации.</string>
<string name="debug_info_http_5xx_description">Возникла проблема на стороне сервера. Пожалуйста, свяжитесь со службой поддержки вашего сервера.</string>
<string name="debug_info_unexpected_error">Произошла неожиданная ошибка. Просмотрите отладочную информацию для получения подробностей.</string>
<string name="debug_info_view_details">Просмотреть информацию</string>
<string name="debug_info_subtitle">Собрана отладочная информация</string>
<string name="debug_info_involved_caption">Вовлеченные ресурсы</string>
<string name="debug_info_unexpected_error">Произошла неожиданная ошибка. Просмотрите отладочную информацию, чтобы узнать подробности.</string>
<string name="debug_info_view_details">Просмотр</string>
<string name="debug_info_subtitle">Отладочная информация собрана</string>
<string name="debug_info_involved_caption">Задействованные ресурсы</string>
<string name="debug_info_involved_subtitle">Связанная с этим проблема</string>
<string name="debug_info_involved_remote">Удаленный ресурс:</string>
<string name="debug_info_involved_local">Локальный ресурс:</string>
@@ -428,7 +428,7 @@
<string name="debug_info_logs_subtitle">Доступны подробные логи</string>
<string name="debug_info_logs_view">Просмотр логов</string>
<string name="debug_info_privacy_warning_title"> Предупреждение о конфиденциальности</string>
<string name="debug_info_privacy_warning_description">Журналы и отладочная информация могут содержать конфиденциальную информацию. Пожалуйста, помните об этом при публичном использовании.</string>
<string name="debug_info_privacy_warning_description">Журналы и отладочная информация могут содержать конфиденциальную информацию. Пожалуйста, помните об этом, когда делитесь ими.</string>
<!--ExceptionInfoFragment-->
<string name="exception">Произошла ошибка.</string>
<string name="exception_httpexception">Произошла ошибка HTTP</string>
@@ -440,7 +440,7 @@
<string name="webdav_mounts_share_content">Поделиться контентом</string>
<string name="webdav_mounts_unmount">Отмонтировать</string>
<string name="webdav_add_mount_title">Добавление точки монтирования WebDAV</string>
<string name="webdav_mounts_empty">Прямой доступ к вашим облачным файлам с помощью точки монтирования WebDAV!</string>
<string name="webdav_mounts_empty">Прямой доступ к вашим облачным файлам с помощью точек монтирования WebDAV!</string>
<string name="webdav_add_mount_empty_more_info"><![CDATA[Ознакомьтесь с руководством, чтобы узнать <a href="%1$s">как работают точки монтирования WebDAV</a>.]]></string>
<string name="webdav_add_mount_display_name">Отображаемое имя</string>
<string name="webdav_add_mount_url">WebDAV URL</string>

View File

@@ -201,7 +201,6 @@
<string name="app_settings_unifiedpush_ready">Redo att ta emot push meddelanden över %s</string>
<string name="app_settings_unifiedpush_encrypted">Push-meddelanden är alltid krypterade.</string>
<!--AccountScreen-->
<string name="account_invalid_account">Kontot finns inte</string>
<string name="account_carddav">CardDAV</string>
<string name="account_caldav">CalDAV</string>
<string name="account_webcal">Webcal</string>

View File

@@ -3,10 +3,15 @@
<!--common strings-->
<string name="account_invalid">帳號(已)不存在</string>
<string name="account_title_address_book">DAVx⁵ 通訊錄</string>
<string name="account_prefs_use_app">別在這裡更改帳戶!請直接使用應用程式管理帳戶。</string>
<string name="dialog_delete">刪除</string>
<string name="dialog_remove">移除</string>
<string name="dialog_deny">取消</string>
<string name="dialog_enable">啟用</string>
<string name="field_required">此為必填欄位</string>
<string name="help">幫助</string>
<string name="navigate_up">向上導航</string>
<string name="options_menu">選項選單</string>
<string name="share">分享</string>
<string name="sync_started">同步已開始或排入佇列</string>
<string name="database_destructive_migration_title">資料庫損毀</string>
@@ -28,6 +33,7 @@
<string name="intro_battery_text">為了定期進行同步,必須允許 %s 在背景運行,否則 Android 可能會隨時暫停同步。</string>
<string name="intro_battery_dont_show">我不需要定期同步間隔*</string>
<string name="intro_autostart_title">%s 相容性</string>
<string name="intro_autostart_text">特定廠商的韌體可能會阻止同步。如果您受到影響,您只能手動解決這一問題。</string>
<string name="intro_autostart_dont_show">所需設定已完成,不用再提醒我*</string>
<string name="intro_leave_unchecked">* 取消勾選則稍後會再次提醒,可於設定中重置 / %s</string>
<string name="intro_more_info">更多資訊</string>
@@ -35,13 +41,40 @@
<string name="intro_tasks_title">待辦事項支援</string>
<string name="intro_tasks_text1">如果你的服務器支持任務它們可以與支援任務的app同步:</string>
<string name="intro_tasks_opentasks_info">似乎已不再繼續開發 - 不建議使用。</string>
<string name="intro_tasks_tasks_org_info"><![CDATA[某些功能 <a href="https://www.davx5.com/faq/tasks/advanced-task-features">不被支援</a>。]]></string>
<string name="intro_tasks_no_app_store">沒有應用商店可用</string>
<string name="intro_tasks_dont_show">我不需要任務支援。*</string>
<string name="intro_open_source_title">開源軟體</string>
<string name="intro_open_source_text">我們很高興您使用 %s 開源軟體。開發、維護和支持是艱苦的工作。請考慮透過多種方式提供貢獻或捐款。不勝感激!</string>
<string name="intro_open_source_details">如何貢獻或捐款</string>
<string name="intro_open_source_dont_show">不要提醒時長</string>
<plurals name="intro_open_source_dont_show_months">
<item quantity="other">%d 個月</item>
</plurals>
<string name="intro_next">繼續</string>
<!--PermissionsActivity-->
<string name="permissions_title">權限</string>
<string name="permissions_text">%s需要權限才能正常工作</string>
<string name="permissions_all_title">以下所有</string>
<string name="permissions_all_status_off">使用它來啟用所有功能(推薦)</string>
<string name="permissions_all_status_on">已授予所有權限</string>
<string name="permissions_contacts_title">通訊錄權限</string>
<string name="permissions_contacts_status_off">無聯絡人同步(不推薦)</string>
<string name="permissions_contacts_status_on">可同步聯絡人</string>
<string name="permissions_calendar_title">行事曆權限</string>
<string name="permissions_calendar_status_off">無日曆同步(不推薦)</string>
<string name="permissions_calendar_status_on">可同步日曆</string>
<string name="permissions_notification_title">通知權限</string>
<string name="permissions_notification_status_off">已關閉通知(不推薦)</string>
<string name="permissions_notification_status_on">已啟用通知</string>
<string name="permissions_jtx_title">jtx Board 權限</string>
<string name="permissions_opentasks_title">OpenTasks 權限</string>
<string name="permissions_tasksorg_title">Tasks 權限</string>
<string name="permissions_tasks_status_off">無任務同步</string>
<string name="permissions_tasks_status_on">可同步任步</string>
<string name="permissions_autoreset_title">保持權限</string>
<string name="permissions_autoreset_status_off">權限可能會被自動重設(不推薦)</string>
<string name="permissions_autoreset_status_on">權限不會被自動重設</string>
<!--WifiPermissionsActivity-->
<!--AboutActivity-->
<string name="about_translations">翻譯</string>

View File

@@ -203,7 +203,7 @@
<string name="app_settings_unifiedpush_distributor_fcm">FCM (Google Play)</string>
<string name="app_settings_unifiedpush_encrypted">推送消息始终是加密的</string>
<!--AccountScreen-->
<string name="account_invalid_account">账户不存在</string>
<string name="account_invalid_account">账户已被删除</string>
<string name="account_carddav">CardDAV</string>
<string name="account_caldav">CalDAV</string>
<string name="account_webcal">Webcal</string>

View File

@@ -238,7 +238,7 @@
<string name="app_settings_unifiedpush_encrypted">Push messages are always encrypted.</string>
<!-- AccountScreen -->
<string name="account_invalid_account">Account doesn\'t exist</string>
<string name="account_invalid_account">Account has been removed</string>
<string name="account_carddav">CardDAV</string>
<string name="account_caldav">CalDAV</string>
<string name="account_webcal">Webcal</string>

View File

@@ -1,13 +1,13 @@
# Comments apply to next line
[versions]
android-agp = "8.12.0"
android-agp = "8.12.1"
android-desugaring = "2.1.5"
androidx-activityCompose = "1.10.1"
androidx-appcompat = "1.7.1"
androidx-arch = "2.2.0"
androidx-browser = "1.9.0"
androidx-core = "1.16.0"
androidx-core = "1.17.0"
androidx-hilt = "1.2.0"
androidx-lifecycle = "2.9.2"
androidx-paging = "3.3.6"
@@ -20,18 +20,18 @@ androidx-test-junit = "1.3.0"
androidx-work = "2.10.3"
bitfire-cert4android = "41009d48ed"
bitfire-dav4jvm = "cb6065b262"
bitfire-synctools = "54557601b8"
bitfire-synctools = "f12d9a4982"
compose-accompanist = "0.37.3"
compose-bom = "2025.07.00"
compose-bom = "2025.08.00"
dnsjava = "3.6.3"
glance = "1.1.1"
guava = "33.4.8-android"
hilt = "2.57"
# keep in sync with ksp version
kotlin = "2.2.0"
kotlin = "2.2.10"
kotlinx-coroutines = "1.10.2"
# see https://github.com/google/ksp/releases for version numbers
ksp = "2.2.0-2.0.2"
ksp = "2.2.10-2.0.2"
mikepenz-aboutLibraries = "12.2.4"
mockk = "1.14.5"
okhttp = "5.1.0"