mirror of
https://github.com/bitfireAT/davx5-ose.git
synced 2026-01-20 04:37:57 -05:00
Compare commits
36 Commits
synctools-
...
testing-sy
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f36e826d8 | ||
|
|
3737d69397 | ||
|
|
2dbd5c02b6 | ||
|
|
c12e9311f7 | ||
|
|
b663912feb | ||
|
|
3c484f253f | ||
|
|
de7f8d2964 | ||
|
|
a79a39c25d | ||
|
|
20675ed71b | ||
|
|
881588f8e8 | ||
|
|
0c31758880 | ||
|
|
9ffd59cd00 | ||
|
|
c40b2b38bc | ||
|
|
3025ea7491 | ||
|
|
b84a812d7a | ||
|
|
562afc5666 | ||
|
|
8992859b63 | ||
|
|
03013b5576 | ||
|
|
0028fc8722 | ||
|
|
1b4ebde896 | ||
|
|
0cc84dfd01 | ||
|
|
87239daaf6 | ||
|
|
81ceb57842 | ||
|
|
cd0b0c0804 | ||
|
|
48cbd4a05d | ||
|
|
beccc7a0d4 | ||
|
|
2b629c8b18 | ||
|
|
cd725479cd | ||
|
|
44666d2138 | ||
|
|
8e67db7d54 | ||
|
|
a58e3b9036 | ||
|
|
d63918ff42 | ||
|
|
f21c3de94a | ||
|
|
24d4ba65e5 | ||
|
|
ae96f1ffbb | ||
|
|
a08ecae635 |
5
.github/dependabot.yml
vendored
5
.github/dependabot.yml
vendored
@@ -8,4 +8,7 @@ updates:
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
commit-message:
|
||||
prefix: "[CI] "
|
||||
prefix: "[CI] "
|
||||
groups:
|
||||
ci-actions:
|
||||
patterns: ["*"]
|
||||
|
||||
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -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
|
||||
|
||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -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
|
||||
|
||||
12
.github/workflows/test-dev.yml
vendored
12
.github/workflows/test-dev.yml
vendored
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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].**
|
||||
*
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user