Compare commits

..

166 Commits

Author SHA1 Message Date
Ricki Hirner
47b4ecd705 Add OpenID AppAuth library 2026-02-05 20:06:37 +01:00
Ricki Hirner
bced7e5ee5 Update test configuration
- Add managed device for testing
- Set device to "Pixel 3" with API level 34
- Use AOSP system image source
2026-02-05 20:00:17 +01:00
Ricki Hirner
360c2249cf Update test workflows 2026-02-05 19:55:42 +01:00
Ricki Hirner
53791871c6 Split core and app-ose 2026-02-05 18:16:35 +01:00
Ricki Hirner
9bddf4e8d4 Rename "app" subproject to core 2026-02-05 17:30:18 +01:00
Ricki Hirner
eab054d1c3 Move OSE code to separate package (#1974)
- Move DebugInfoCrashHandler.kt to com.davx5.ose
- Move StandardLoginTypePage.kt to com.davx5.ose.ui.setup
- Move StandardLoginTypesProvider.kt to com.davx5.ose.ui.setup
- Move CustomCertManagerModule.kt to com.davx5.ose.di
- Move OseIntroPageFactory.kt to com.davx5.ose.ui.intro
- Move OseColorSchemesModule.kt to com.davx5.ose.di
- Move OseFlavorModule.kt to com.davx5.ose.di
- Move OpenSourceLicenseInfoProvider.kt to com.davx5.ose.ui.about
- Move OseTheme.kt to com.davx5.ose.ui
2026-02-05 17:21:57 +01:00
Ricki Hirner
5e84648fb4 Replace BuildConfig.allowCustomCerts by DI (#1971)
* Update IntroScreen colors

- Replace M3ColorScheme with MaterialTheme.colorScheme
- Update background and icon colors to use MaterialTheme

* Update color scheme references

- Replace `M3ColorScheme.primaryLight` with `MaterialTheme.colorScheme.primary` in `WelcomePage.kt` and `AccountsDrawerHandler.kt`.

* Update AppTheme to accept custom color schemes

- Add `lightColorScheme` and `darkColorScheme` parameters
- Replace hardcoded color schemes with the new parameters

* Add color scheme dependency injection

- Add `LightColorScheme` and `DarkColorScheme` qualifiers
- Create `OseColorSchemes` module for providing color schemes
- Update `AppTheme` to use injected color schemes

* Update glance material dependency to material3

- Update `androidx.glance.material` to `androidx.glance.material3`
- Adjust imports and usage in `IconSyncButtonWidget.kt` and `LabeledSyncButtonWidget.kt` to use `GlanceTheme` and `ColorProviders` for color schemes
- Replace deprecated `ColorProvider` with `GlanceTheme.colors` for primary and onPrimary colors

* Refactor widget receivers and widgets to use dependency injection more properly

- Update `LabeledSyncButtonWidgetReceiver` and `IconSyncButtonWidgetReceiver` to use Hilt for dependency injection.
- Inject `SyncWidgetModel`, `LightColorScheme`, and `DarkColorScheme` into both widget receivers.
- Remove the use of `EntryPoint` and `EntryPointAccessors` from `LabeledSyncButtonWidget` and `IconSyncButtonWidget`.
- Pass injected dependencies directly to the widget constructors.

* Rename ThemeColors to OseTheme

- Update imports and references to use OseTheme
- Rename object M3ColorScheme to OseTheme

* Move AppTheme to ui.composable package because it's a reusable Composable

* Update AboutApp to use dynamic version info instead of BuildConfig

- Pass versionName and versionCode from AboutModel to AboutApp
- Remove dependency on BuildConfig in AboutApp
- Update AboutApp_Preview with sample version info

* Update URI statistics parameters

- Update `withStatParams` to include package name and version
- Replace `BuildConfig.APPLICATION_ID` with `context.packageName`
- Add context parameter to `withStatParams` in various activities

* Don't depend on BuildConfig for application name and version

- Introduce `ProductIds` for managing product IDs and User-Agent
- Update various classes to use `ProductIds` for product ID generation
- Move `TextTable` class from `at.bitfire.davdroid` to `at.bitfire.davdroid.util`

* Refactor OAuth classes for dependency injection

- Convert `OAuthFastmail` and `OAuthGoogle` to injectable classes
- Update `FastmailLoginModel` and `GoogleLoginModel` to use injected instances
- Move `redirectUri` initialization to `OAuthIntegration` constructor

* Adapt DI

- Move CustomCertManagerModule to ose configuration
- Move coroutine scopes to scope package

* minor changes

* Remove custom certificate build config

- Remove `allowCustomCerts` build config field
- Replace `@Singleton` with `@Reusable` in CustomCertManagerModule

* Update imports and LogcatHandler initialization

- Update imports to use scoped dispatchers
- Replace BuildConfig.APPLICATION_ID with javaClass.name in LogcatHandler initialization
2026-02-05 13:59:31 +01:00
Ricki Hirner
490abcb88a Reduce BuildConfig dependencies (#1969)
* Update IntroScreen colors

- Replace M3ColorScheme with MaterialTheme.colorScheme
- Update background and icon colors to use MaterialTheme

* Update color scheme references

- Replace `M3ColorScheme.primaryLight` with `MaterialTheme.colorScheme.primary` in `WelcomePage.kt` and `AccountsDrawerHandler.kt`.

* Update AppTheme to accept custom color schemes

- Add `lightColorScheme` and `darkColorScheme` parameters
- Replace hardcoded color schemes with the new parameters

* Add color scheme dependency injection

- Add `LightColorScheme` and `DarkColorScheme` qualifiers
- Create `OseColorSchemes` module for providing color schemes
- Update `AppTheme` to use injected color schemes

* Update glance material dependency to material3

- Update `androidx.glance.material` to `androidx.glance.material3`
- Adjust imports and usage in `IconSyncButtonWidget.kt` and `LabeledSyncButtonWidget.kt` to use `GlanceTheme` and `ColorProviders` for color schemes
- Replace deprecated `ColorProvider` with `GlanceTheme.colors` for primary and onPrimary colors

* Refactor widget receivers and widgets to use dependency injection more properly

- Update `LabeledSyncButtonWidgetReceiver` and `IconSyncButtonWidgetReceiver` to use Hilt for dependency injection.
- Inject `SyncWidgetModel`, `LightColorScheme`, and `DarkColorScheme` into both widget receivers.
- Remove the use of `EntryPoint` and `EntryPointAccessors` from `LabeledSyncButtonWidget` and `IconSyncButtonWidget`.
- Pass injected dependencies directly to the widget constructors.

* Rename ThemeColors to OseTheme

- Update imports and references to use OseTheme
- Rename object M3ColorScheme to OseTheme

* Move AppTheme to ui.composable package because it's a reusable Composable

* Update AboutApp to use dynamic version info instead of BuildConfig

- Pass versionName and versionCode from AboutModel to AboutApp
- Remove dependency on BuildConfig in AboutApp
- Update AboutApp_Preview with sample version info

* Update URI statistics parameters

- Update `withStatParams` to include package name and version
- Replace `BuildConfig.APPLICATION_ID` with `context.packageName`
- Add context parameter to `withStatParams` in various activities

* Don't depend on BuildConfig for application name and version

- Introduce `ProductIds` for managing product IDs and User-Agent
- Update various classes to use `ProductIds` for product ID generation
- Move `TextTable` class from `at.bitfire.davdroid` to `at.bitfire.davdroid.util`

* Refactor OAuth classes for dependency injection

- Convert `OAuthFastmail` and `OAuthGoogle` to injectable classes
- Update `FastmailLoginModel` and `GoogleLoginModel` to use injected instances
- Move `redirectUri` initialization to `OAuthIntegration` constructor
2026-02-05 12:20:21 +01:00
Ricki Hirner
cca12e79d8 [UI] Properly provide color schemes over DI (#1966)
* Update IntroScreen colors

- Replace M3ColorScheme with MaterialTheme.colorScheme
- Update background and icon colors to use MaterialTheme

* Update color scheme references

- Replace `M3ColorScheme.primaryLight` with `MaterialTheme.colorScheme.primary` in `WelcomePage.kt` and `AccountsDrawerHandler.kt`.

* Update AppTheme to accept custom color schemes

- Add `lightColorScheme` and `darkColorScheme` parameters
- Replace hardcoded color schemes with the new parameters

* Add color scheme dependency injection

- Add `LightColorScheme` and `DarkColorScheme` qualifiers
- Create `OseColorSchemes` module for providing color schemes
- Update `AppTheme` to use injected color schemes

* Update glance material dependency to material3

- Update `androidx.glance.material` to `androidx.glance.material3`
- Adjust imports and usage in `IconSyncButtonWidget.kt` and `LabeledSyncButtonWidget.kt` to use `GlanceTheme` and `ColorProviders` for color schemes
- Replace deprecated `ColorProvider` with `GlanceTheme.colors` for primary and onPrimary colors

* Refactor widget receivers and widgets to use dependency injection more properly

- Update `LabeledSyncButtonWidgetReceiver` and `IconSyncButtonWidgetReceiver` to use Hilt for dependency injection.
- Inject `SyncWidgetModel`, `LightColorScheme`, and `DarkColorScheme` into both widget receivers.
- Remove the use of `EntryPoint` and `EntryPointAccessors` from `LabeledSyncButtonWidget` and `IconSyncButtonWidget`.
- Pass injected dependencies directly to the widget constructors.

* Rename ThemeColors to OseTheme

- Update imports and references to use OseTheme
- Rename object M3ColorScheme to OseTheme

* Move AppTheme to ui.composable package because it's a reusable Composable

* Always use Light Color Scheme for certain intro UI

- Inject `lightColorScheme` in `IntroActivity`
- Pass `lightColorScheme` to `IntroScreen`
- Use `lightColorScheme` for background and color in `IntroScreen` and `WelcomePage`

* Minor syntax
2026-02-05 11:43:55 +01:00
Ricki Hirner
915cf73027 Update version to 4.5.9 2026-02-04 10:58:57 +01:00
Ricki Hirner
53773eaf83 Go back to 4.5.9-rc.2 (4.5.9 with version code 405090003 was never released) 2026-02-03 16:00:55 +01:00
Ricki Hirner
9cd685982d Update synctools to correctly process tel: URIs in vCards (#1963) 2026-02-03 15:59:46 +01:00
Sunik Kupfer
d4902e84ce [synctools] Tasks rewrite: Use reader/writer (#1959)
* Update synctools; Use writer/reader

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Update synctools

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
2026-02-03 15:22:15 +01:00
dependabot[bot]
ec485fcfa5 Bump the app-dependencies group with 8 updates (#1961)
Bumps the app-dependencies group with 8 updates:

| Package | From | To |
| --- | --- | --- |
| gradle-wrapper | `9.3.0` | `9.3.1` |
| androidx.activity:activity-compose | `1.12.2` | `1.12.3` |
| androidx.compose:compose-bom | `2026.01.00` | `2026.01.01` |
| androidx.paging:paging-runtime-ktx | `3.3.6` | `3.4.0` |
| androidx.paging:paging-compose | `3.3.6` | `3.4.0` |
| androidx.work:work-runtime-ktx | `2.11.0` | `2.11.1` |
| androidx.work:work-testing | `2.11.0` | `2.11.1` |
| [com.google.devtools.ksp](https://github.com/google/ksp) | `2.3.4` | `2.3.5` |


Updates `gradle-wrapper` from 9.3.0 to 9.3.1

Updates `androidx.activity:activity-compose` from 1.12.2 to 1.12.3

Updates `androidx.compose:compose-bom` from 2026.01.00 to 2026.01.01

Updates `androidx.paging:paging-runtime-ktx` from 3.3.6 to 3.4.0

Updates `androidx.paging:paging-compose` from 3.3.6 to 3.4.0

Updates `androidx.paging:paging-compose` from 3.3.6 to 3.4.0

Updates `androidx.work:work-runtime-ktx` from 2.11.0 to 2.11.1

Updates `androidx.work:work-testing` from 2.11.0 to 2.11.1

Updates `androidx.work:work-testing` from 2.11.0 to 2.11.1

Updates `com.google.devtools.ksp` from 2.3.4 to 2.3.5
- [Release notes](https://github.com/google/ksp/releases)
- [Commits](https://github.com/google/ksp/compare/2.3.4...2.3.5)

---
updated-dependencies:
- dependency-name: gradle-wrapper
  dependency-version: 9.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.activity:activity-compose
  dependency-version: 1.12.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.compose:compose-bom
  dependency-version: 2026.01.01
  dependency-type: direct:production
  dependency-group: app-dependencies
- dependency-name: androidx.paging:paging-runtime-ktx
  dependency-version: 3.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.paging:paging-compose
  dependency-version: 3.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.paging:paging-compose
  dependency-version: 3.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.work:work-runtime-ktx
  dependency-version: 2.11.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.work:work-testing
  dependency-version: 2.11.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.work:work-testing
  dependency-version: 2.11.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.google.devtools.ksp
  dependency-version: 2.3.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-03 14:57:10 +01:00
Ricki Hirner
5709aaa2e5 Bump version to 4.5.9 2026-02-03 12:12:05 +01:00
Ricki Hirner
a19c397ef6 Sync files with davx5 repo (#1958)
* Sync changes with davx5 repo

* Remove unnecessary test launcher icons
2026-02-02 12:30:04 +01:00
Ricki Hirner
dad4298dd5 Bump version to 4.5.9-rc.1 2026-02-02 10:12:52 +01:00
Weblate (bot)
8e78e6e3ac Translations update from Hosted Weblate (#1955)
* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (430 of 430 strings)

Translation: davx5/DAVx⁵ app strings (main)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-strings/pt_BR/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (430 of 430 strings)

Translation: davx5/DAVx⁵ app strings (main)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-strings/zh_Hans/

* Translated using Weblate (Estonian)

Currently translated at 100.0% (430 of 430 strings)

Translation: davx5/DAVx⁵ app strings (main)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-strings/et/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (430 of 430 strings)

Translation: davx5/DAVx⁵ app strings (main)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-strings/nl/

* Added translation using Weblate (Lithuanian)

* Translated using Weblate (Lithuanian)

Currently translated at 7.6% (33 of 430 strings)

Translation: davx5/DAVx⁵ app strings (main)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-strings/lt/

* Translated using Weblate (Georgian)

Currently translated at 84.6% (364 of 430 strings)

Translation: davx5/DAVx⁵ app strings (main)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-strings/ka/

* Translated using Weblate (Romanian)

Currently translated at 100.0% (430 of 430 strings)

Translation: davx5/DAVx⁵ app strings (main)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-strings/ro/

* Translated using Weblate (Italian)

Currently translated at 88.1% (379 of 430 strings)

Translation: davx5/DAVx⁵ app strings (main)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-strings/it/

* Translated using Weblate (Italian)

Currently translated at 100.0% (4 of 4 strings)

Translation: davx5/DAVx⁵ app metadata (for F-Droid)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-fastlane/it/

* Translated using Weblate (Estonian)

Currently translated at 100.0% (437 of 437 strings)

Translation: davx5/DAVx⁵ app strings (main)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-strings/et/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (437 of 437 strings)

Translation: davx5/DAVx⁵ app strings (main)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-strings/zh_Hans/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (437 of 437 strings)

Translation: davx5/DAVx⁵ app strings (main)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-strings/nl/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (437 of 437 strings)

Translation: davx5/DAVx⁵ app strings (main)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-strings/pt_BR/

---------

Co-authored-by: LucasMZ <git@lucasmz.dev>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: Priit Jõerüüt <jrthwlate@users.noreply.hosted.weblate.org>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Vaclovas Intas <Gateway_31@protonmail.com>
Co-authored-by: Temuri Doghonadze <temuri.doghonadze@gmail.com>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Alì Mortacci <newscpq@vivaldi.net>
2026-02-02 10:12:10 +01:00
Ricki Hirner
fc878d519f Update synctools to process phone numbers with VALUE=URI (#1956) 2026-02-02 10:11:40 +01:00
Ricki Hirner
45d5d809fc Bump version to 4.5.9-alpha.2 2026-01-29 14:17:59 +01:00
Ricki Hirner
ef1d90f740 Cache SSLSocketFactories to allow okhttp HTTPS connection reuse (#1942)
* Reuse CustomCertManager

- Update bitfire-cert4android to 75cc6913fd
- Refactor HttpClientBuilder to use Optional for customTrustManager and customHostnameVerifier
- Add CustomCertManagerModule for dependency injection

* Implement connection security manager for HTTP client

- Introduce `ConnectionSecurityManager` and `ConnectionSecurityContext` classes
- Refactor `HttpClientBuilder` to use the new security manager for SSL context setup

* [WIP] Cache SSLContext by certificate alias

- Add context cache using Guava CacheBuilder
- Cache SSLContext in getContext method

* Update comments in HttpClientBuilder.kt for clarity

* Update ConnectionSecurityManager to use SSLSocketFactory caching

* Refactor socket factory caching logic for better clarity

* Add tests

* Refactor socket factory cache to store only SSLSocketFactory

* Minor changes
- Change socketFactoryCache to use LinkedHashMap instead of ConcurrentHashMap
- Update cache key handling to use String? instead of Optional<String>

* Add tests for caching

* Add logging

* Indenting

* Minor simplification

* Fix tests
2026-01-29 14:09:09 +01:00
Arnau Mora
5efcbfc5a3 Set Calendars.IS_PRIMARY to 0 by default (#1945)
* Start setting `IS_PRIMARY`

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

* Set `IS_PRIMARY` to `0`

* Remove unused injection

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

* Improve explanation

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

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
2026-01-29 12:30:23 +01:00
Sunik Kupfer
4f3ff69b43 Update synctools (#1952)
Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
2026-01-29 12:18:29 +01:00
Ricki Hirner
afe00c275e Update CODEOWNERS (#1949) 2026-01-28 13:18:28 +01:00
Sunik Kupfer
03c4aa9938 Ignore AccountSettingsMigration21Test.testCancelsSyncAndClearsPendingState with flaky behaviour in CI (#1948)
Ignore test with flaky behaviour in CI

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
2026-01-28 11:25:00 +01:00
dependabot[bot]
63a5359c06 Bump the app-dependencies group with 7 updates (#1944)
Bumps the app-dependencies group with 7 updates:

| Package | From | To |
| --- | --- | --- |
| [io.ktor:ktor-client-content-negotiation](https://github.com/ktorio/ktor) | `3.3.3` | `3.4.0` |
| [io.ktor:ktor-client-core](https://github.com/ktorio/ktor) | `3.3.3` | `3.4.0` |
| [io.ktor:ktor-client-okhttp](https://github.com/ktorio/ktor) | `3.3.3` | `3.4.0` |
| [io.ktor:ktor-serialization-kotlinx-json](https://github.com/ktorio/ktor) | `3.3.3` | `3.4.0` |
| [io.mockk:mockk](https://github.com/mockk/mockk) | `1.14.7` | `1.14.9` |
| [io.mockk:mockk-android](https://github.com/mockk/mockk) | `1.14.7` | `1.14.9` |
| [org.robolectric:robolectric](https://github.com/robolectric/robolectric) | `4.16` | `4.16.1` |


Updates `io.ktor:ktor-client-content-negotiation` from 3.3.3 to 3.4.0
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/3.3.3...3.4.0)

Updates `io.ktor:ktor-client-core` from 3.3.3 to 3.4.0
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/3.3.3...3.4.0)

Updates `io.ktor:ktor-client-okhttp` from 3.3.3 to 3.4.0
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/3.3.3...3.4.0)

Updates `io.ktor:ktor-serialization-kotlinx-json` from 3.3.3 to 3.4.0
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/3.3.3...3.4.0)

Updates `io.ktor:ktor-client-core` from 3.3.3 to 3.4.0
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/3.3.3...3.4.0)

Updates `io.ktor:ktor-client-okhttp` from 3.3.3 to 3.4.0
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/3.3.3...3.4.0)

Updates `io.ktor:ktor-serialization-kotlinx-json` from 3.3.3 to 3.4.0
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/3.3.3...3.4.0)

Updates `io.mockk:mockk` from 1.14.7 to 1.14.9
- [Release notes](https://github.com/mockk/mockk/releases)
- [Commits](https://github.com/mockk/mockk/compare/1.14.7...1.14.9)

Updates `io.mockk:mockk-android` from 1.14.7 to 1.14.9
- [Release notes](https://github.com/mockk/mockk/releases)
- [Commits](https://github.com/mockk/mockk/compare/1.14.7...1.14.9)

Updates `io.mockk:mockk-android` from 1.14.7 to 1.14.9
- [Release notes](https://github.com/mockk/mockk/releases)
- [Commits](https://github.com/mockk/mockk/compare/1.14.7...1.14.9)

Updates `org.robolectric:robolectric` from 4.16 to 4.16.1
- [Release notes](https://github.com/robolectric/robolectric/releases)
- [Commits](https://github.com/robolectric/robolectric/compare/robolectric-4.16...robolectric-4.16.1)

---
updated-dependencies:
- dependency-name: io.ktor:ktor-client-content-negotiation
  dependency-version: 3.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: io.ktor:ktor-client-core
  dependency-version: 3.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: io.ktor:ktor-client-okhttp
  dependency-version: 3.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: io.ktor:ktor-serialization-kotlinx-json
  dependency-version: 3.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: io.ktor:ktor-client-core
  dependency-version: 3.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: io.ktor:ktor-client-okhttp
  dependency-version: 3.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: io.ktor:ktor-serialization-kotlinx-json
  dependency-version: 3.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: io.mockk:mockk
  dependency-version: 1.14.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: io.mockk:mockk-android
  dependency-version: 1.14.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: io.mockk:mockk-android
  dependency-version: 1.14.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: org.robolectric:robolectric
  dependency-version: 4.16.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-26 17:26:59 +01:00
Weblate (bot)
89a7cd2885 Translations update from Hosted Weblate (#1943)
* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (430 of 430 strings)

Translation: davx5/DAVx⁵ app strings (main)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-strings/pt_BR/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (430 of 430 strings)

Translation: davx5/DAVx⁵ app strings (main)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-strings/zh_Hans/

* Translated using Weblate (Estonian)

Currently translated at 100.0% (430 of 430 strings)

Translation: davx5/DAVx⁵ app strings (main)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-strings/et/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (430 of 430 strings)

Translation: davx5/DAVx⁵ app strings (main)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-strings/nl/

* Added translation using Weblate (Lithuanian)

* Translated using Weblate (Lithuanian)

Currently translated at 7.6% (33 of 430 strings)

Translation: davx5/DAVx⁵ app strings (main)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-strings/lt/

* Translated using Weblate (Georgian)

Currently translated at 84.6% (364 of 430 strings)

Translation: davx5/DAVx⁵ app strings (main)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-strings/ka/

* Translated using Weblate (Romanian)

Currently translated at 100.0% (430 of 430 strings)

Translation: davx5/DAVx⁵ app strings (main)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-strings/ro/

* Translated using Weblate (Italian)

Currently translated at 88.1% (379 of 430 strings)

Translation: davx5/DAVx⁵ app strings (main)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-strings/it/

* Translated using Weblate (Italian)

Currently translated at 100.0% (4 of 4 strings)

Translation: davx5/DAVx⁵ app metadata (for F-Droid)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-fastlane/it/

---------

Co-authored-by: LucasMZ <git@lucasmz.dev>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: Priit Jõerüüt <jrthwlate@users.noreply.hosted.weblate.org>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Vaclovas Intas <Gateway_31@protonmail.com>
Co-authored-by: Temuri Doghonadze <temuri.doghonadze@gmail.com>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Alì Mortacci <newscpq@vivaldi.net>
2026-01-25 18:14:56 +01:00
Ricki Hirner
db25570581 Bump version to 4.5.9-alpha.1 2026-01-25 17:52:15 +01:00
Arnau Mora
3de34e53d0 Migrate translation contributors credits to Weblate (#1918)
* Update loading method for Weblate

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

* Add fetch script

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

* Add test function

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

* Add credits from Transifex

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

* Add workflow for updating Weblate credits

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

* Improve styling

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

* Add line break at the end

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

* Filter Ricki in translation contributions

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

* Display Transifex translations

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

* Modify PRs, not the base branch

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

* Fix paddings

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

* Rename weblateTranslations flow and loadWeblateTranslations function

* Rename variables and functions from `translations` to `translators`

* - Move AboutActivity.Model class to AboutModel

* Rename translators files

* Minor renaming

* Refactor sorting logic into `sortTranslators` function

* Rename script to fetch Weblate translators

* - Remove `AboutActivityTest` class
- Move `loadTransifexTranslators` function to `AboutModel`
- Update `loadWeblateTranslators` function in `AboutModel`

* Update Weblate workflow

* - Combine Weblate and Transifex translators into a single list
- Update AboutActivity to use the combined list
- Add tests for the new functionality

* Merge Transifex and Weblate translators by username, don't show language, add Engage widget

* Extract TranslationsTab

* AboutModel: update tests

* Add thanks message for translation contributors

* Move translation credits to Weblate

- Update `OpenSourceLicenseInfoProvider` path
- Add new strings for translations credits
- Update `TranslationsTab` with new strings

* Add Accept-Language, update Context

* Update Weblate Translators Workflow

- Allow workflow to run on specific branch for testing
- Remove unnecessary fetch-depth comment

* Update Weblate Translators Workflow (2)

* Remove workflow

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2026-01-25 17:51:34 +01:00
Ricki Hirner
af084fb5d0 Add "Backups reminder" intro screen (#1937)
* Add BackupsPage to intro UI

* Add BackupsPage UI

- Implement BackupsPage composable with UI elements
- Add strings for backups reminder and acceptance

* Add to "Reset hints"

* Add another paragraph

* Update backup screen hints

- Update backups reminder text
- Remove redundant versioning note
- Update acceptance message
2026-01-25 17:48:09 +01:00
Ricki Hirner
47685e6693 [WebDAV] Rewrite COPY/MOVE (including rename) to Ktor (#1940)
* [WebDAV] Refactor RenameDocumentOperation to Ktor

- Update imports to use Ktor-based classes
- Refactor `RenameDocumentOperation` to use Ktor HTTP client
- Add support for both HttpException types in `throwForDocumentProvider`

* Rewrite CopyDocumentOperation.kt to Ktor

* Refactor URLBuilder usage

- Update URLBuilder usage in RenameDocumentOperation.kt
- Update URLBuilder usage in CopyDocumentOperation.kt
- Update URLBuilder usage in MoveDocumentOperation.kt

* - Pass `ioDispatcher` to `runBlocking` in WebDAV operations
- Refactor timeout configuration in HttpClientBuilder for reusability

* Add logging to DocumentProviderUtils

- Introduce a logger instance
- Log URI when notifying folder changes
2026-01-24 19:28:50 +01:00
Ricki Hirner
2c7b36ecd5 Use Ktor for Push registration (#1930)
* Replace OkHttp with Ktor for push notifications

* Use Ktor HttpHeaders for Location and Expires
2026-01-23 10:27:16 +01:00
Ricki Hirner
cf80b11808 [WebDAV] Rewrite OpenDocumentThumbnailOperation to Ktor (#1931)
* Add Ktor HTTP client support

- Introduce `buildKtor` method in `DavHttpClientBuilder` for creating Ktor HTTP clients.
- Update `OpenDocumentThumbnailOperation` to use Ktor for downloading and creating thumbnails.

* Refactor HttpClientBuilder creation

- Extract common logic into `createBuilder` method
- Update `build` and `buildKtor` methods to use `createBuilder`

* Refactor OpenDocumentThumbnailOperation

- Remove unnecessary `withContext` call
- Use `HttpHeaders.Accept` and `ContentType.Image.Any` for HTTP header
- Simplify the function structure

* Refactor thumbnail generation

- Remove redundant `accessScope`
- Simplify and encapsulate thumbnail creation logic
- Ensure proper cancellation handling

* Update OpenDocumentThumbnailOperation logging

- Enhance cancellation log message with document ID
- Improve URL conversion warning message

* Update WebDAV operations and document handling

- Add `@MustBeClosed` annotation to `buildKtor` method in `DavHttpClientBuilder`
- Remove unnecessary imports and update URL conversion in `OpenDocumentThumbnailOperation`
- Add `toKtorUrl` method in `WebDavDocument` for URL conversion

* Use streaming bitmap decoding

* Add comments to OpenDocumentThumbnailOperation for future improvements

---------

Co-authored-by: Arnau Mora <arnyminerz@proton.me>
2026-01-22 17:05:32 +01:00
Sunik Kupfer
18649f711a DmfsTaskList refactoring (#1934)
* DmfsTaskList refactoring

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Update synctools

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
2026-01-21 13:17:34 +01:00
Sunik Kupfer
377a159e75 Ignore test with flaky behaviour in CI (#1936)
Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
2026-01-21 13:17:23 +01:00
Ricki Hirner
393d22f720 AGP 9.0: update Hilt, remove Kotlin Android plugin (#1935)
- Remove `android.builtInKotlin` from `gradle.properties`
- Update Hilt version to 2.59
- Remove Kotlin Android plugin from `libs.versions.toml` and build scripts
2026-01-21 11:28:40 +01:00
dependabot[bot]
5b12ecf6b6 Bump the app-dependencies group across 1 directory with 2 updates (#1933)
Bumps the app-dependencies group with 2 updates in the / directory: androidx.compose:compose-bom and [dnsjava:dnsjava](https://github.com/dnsjava/dnsjava).


Updates `androidx.compose:compose-bom` from 2025.12.01 to 2026.01.00

Updates `dnsjava:dnsjava` from 3.6.3 to 3.6.4
- [Release notes](https://github.com/dnsjava/dnsjava/releases)
- [Changelog](https://github.com/dnsjava/dnsjava/blob/master/Changelog)
- [Commits](https://github.com/dnsjava/dnsjava/compare/v3.6.3...v3.6.4)

---
updated-dependencies:
- dependency-name: androidx.compose:compose-bom
  dependency-version: 2026.01.00
  dependency-type: direct:production
  dependency-group: app-dependencies
- dependency-name: dnsjava:dnsjava
  dependency-version: 3.6.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-20 16:34:51 +01:00
Ricki Hirner
f8f6134640 Update AGP to 9.0.0 (#1929)
* Update gradle wrapper

* Update AGP to 9.0.0 (legacy mode) and synctools (which now also uses AGP 9.0.0)
2026-01-20 12:45:58 +01:00
Ricki Hirner
0f7908da23 Remove Transifex config/scripts (#1924) 2026-01-19 10:59:34 +01:00
Weblate (bot)
40741f52e1 Translations update from Hosted Weblate (#1928)
* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (430 of 430 strings)

Translation: davx5/DAVx⁵ app strings (main)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-strings/pt_BR/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (430 of 430 strings)

Translation: davx5/DAVx⁵ app strings (main)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-strings/zh_Hans/

* Translated using Weblate (Estonian)

Currently translated at 100.0% (430 of 430 strings)

Translation: davx5/DAVx⁵ app strings (main)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-strings/et/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (430 of 430 strings)

Translation: davx5/DAVx⁵ app strings (main)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-strings/nl/

* Added translation using Weblate (Lithuanian)

* Translated using Weblate (Lithuanian)

Currently translated at 7.6% (33 of 430 strings)

Translation: davx5/DAVx⁵ app strings (main)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-strings/lt/

* Translated using Weblate (Georgian)

Currently translated at 84.6% (364 of 430 strings)

Translation: davx5/DAVx⁵ app strings (main)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-strings/ka/

---------

Co-authored-by: LucasMZ <git@lucasmz.dev>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: Priit Jõerüüt <jrthwlate@users.noreply.hosted.weblate.org>
Co-authored-by: Stephan Paternotte <stephan@paternottes.net>
Co-authored-by: Vaclovas Intas <Gateway_31@protonmail.com>
Co-authored-by: Temuri Doghonadze <temuri.doghonadze@gmail.com>
2026-01-19 09:48:24 +01:00
Ricki Hirner
210a03bd1a Bump version to 4.5.8 2026-01-19 09:35:38 +01:00
Ricki Hirner
714c92b8d9 Bump version to 4.5.8-rc.2 2026-01-16 15:45:48 +01:00
Ricki Hirner
5ea937a0f9 Update dav4jvm to catch URLDecodeException when converting String to Ktor Url (#1923) 2026-01-16 15:43:54 +01:00
Ricki Hirner
126b742887 Decode data URIs of vCard 3 PHOTOs (#1921)
* Rename ResourceDownloader to ResourceRetriever (because it should support `data` URLs that don't have to be downloaded)

* Update `ResourceRetriever` to handle data URIs and HTTP/HTTPS URLs

* Handle invalid data URIs
2026-01-16 11:52:49 +01:00
Weblate (bot)
2de7e09c82 Translations update from Hosted Weblate (#1917)
* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (430 of 430 strings)

Translation: davx5/DAVx⁵ app strings (main)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-strings/pt_BR/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (430 of 430 strings)

Translation: davx5/DAVx⁵ app strings (main)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-strings/zh_Hans/

* Translated using Weblate (Estonian)

Currently translated at 100.0% (430 of 430 strings)

Translation: davx5/DAVx⁵ app strings (main)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-strings/et/

---------

Co-authored-by: LucasMZ <git@lucasmz.dev>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: Priit Jõerüüt <jrthwlate@users.noreply.hosted.weblate.org>
2026-01-14 12:03:49 +01:00
Ricki Hirner
0312f59aab Bump version to 4.5.8-rc.1 2026-01-14 12:00:05 +01:00
Weblate (bot)
b3682ded1a Translations update from Hosted Weblate (#1915)
* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Translation: davx5/DAVx⁵ strings (main)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-strings/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (430 of 430 strings)

Translation: davx5/DAVx⁵ strings (main)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-strings/pt_BR/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (430 of 430 strings)

Translation: davx5/DAVx⁵ strings (main)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-strings/zh_Hans/

* Translated using Weblate (Catalan)

Currently translated at 100.0% (4 of 4 strings)

Translation: davx5/DAVx⁵ app metadata (for F-Droid)
Translate-URL: https://hosted.weblate.org/projects/davx5/davx5-ose-fastlane/ca/

---------

Co-authored-by: LucasMZ <git@lucasmz.dev>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: Arnau Mora <arnyminer.z@gmail.com>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2026-01-13 16:00:16 +01:00
Weblate (bot)
529378000a Translations update from Hosted Weblate (#1913)
* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Translation: davx5/DAVx⁵ Main Strings
Translate-URL: https://hosted.weblate.org/projects/davx5/davx-main-strings/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (430 of 430 strings)

Translation: davx5/DAVx⁵ Main Strings
Translate-URL: https://hosted.weblate.org/projects/davx5/davx-main-strings/zh_Hans/

---------

Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2026-01-13 12:41:45 +01:00
Ricki Hirner
8644c94c34 [Backport from davx5] Remove empty fa-rIR translations 2026-01-13 12:07:08 +01:00
Ricki Hirner
5357bdefb8 Fastlane app descriptions: use fr-FR translation for French (#1912) 2026-01-12 14:43:36 +01:00
Arnau Mora
07b646e4e7 Remove r from lang names as per Fastlane requirement (#1897)
* Remove `r` from lang names as per Fastlane requirement

* Add `lang_map` to fastlane resources

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

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2026-01-12 14:31:09 +01:00
Sunik Kupfer
77cb3e659d Improve pagination (#1911)
* Cache paging data flow in view model

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Replace let with early return

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Move only-personal filtering decision to repository

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Move companion object to the end of class

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Use default dispatcher for account settings interaction

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Add androidx prefixes

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Add kdoc

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Move cachedIn to use-case

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Update kdoc

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
2026-01-12 13:48:02 +01:00
Ricki Hirner
42d65c872e Explain clear-text traffic in network security policy (bitfireAT/davx5#760)
Allow clear-text traffic for all variants; more documentation
2026-01-12 10:43:29 +01:00
Sunik Kupfer
c3fd28f820 Return null when owner account does not exist to skip flaky test (#1908)
Return null when owner account does not exist

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
2026-01-08 12:18:37 +01:00
Ricki Hirner
9ad98e4a16 Enable HTTP connection reuse for okhttp (#1907)
* Enable HTTP connection reuse

- Create a shared OkHttpClient instance for connection pooling
- Utilize sharedOkHttpClient.newBuilder() for new client instances

* Add test

* KDoc clarification
2026-01-06 13:28:48 +01:00
dependabot[bot]
27c1be948f Bump org.unifiedpush.android:connector from 3.1.2 to 3.2.0 in the app-dependencies group (#1906)
Bump org.unifiedpush.android:connector in the app-dependencies group

Bumps the app-dependencies group with 1 update: org.unifiedpush.android:connector.


Updates `org.unifiedpush.android:connector` from 3.1.2 to 3.2.0

---
updated-dependencies:
- dependency-name: org.unifiedpush.android:connector
  dependency-version: 3.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-05 16:50:08 +01:00
Ricki Hirner
ac7ef1a7e5 Update README and CONTRIBUTING (#1905) 2026-01-05 14:26:10 +01:00
Ricki Hirner
e0eb13f57b CodeQL: run for main branch PRs (#1904)
CodeQL: run for main branch PRs, adapt build command
2026-01-05 12:27:25 +01:00
dependabot[bot]
85c4fc76f2 Bump the app-dependencies group across 1 directory with 5 updates (#1902)
Bumps the app-dependencies group with 5 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| androidx.activity:activity-compose | `1.12.1` | `1.12.2` |
| androidx.compose:compose-bom | `2025.12.00` | `2025.12.01` |
| [com.mikepenz:aboutlibraries-compose-m3](https://github.com/mikepenz/AboutLibraries) | `13.1.0` | `13.2.1` |
| com.mikepenz.aboutlibraries.plugin.android | `13.1.0` | `13.2.1` |
| [com.google.devtools.ksp](https://github.com/google/ksp) | `2.3.3` | `2.3.4` |



Updates `androidx.activity:activity-compose` from 1.12.1 to 1.12.2

Updates `androidx.compose:compose-bom` from 2025.12.00 to 2025.12.01

Updates `com.mikepenz:aboutlibraries-compose-m3` from 13.1.0 to 13.2.1
- [Release notes](https://github.com/mikepenz/AboutLibraries/releases)
- [Commits](https://github.com/mikepenz/AboutLibraries/compare/13.1.0...13.2.1)

Updates `com.mikepenz.aboutlibraries.plugin.android` from 13.1.0 to 13.2.1

Updates `com.google.devtools.ksp` from 2.3.3 to 2.3.4
- [Release notes](https://github.com/google/ksp/releases)
- [Commits](https://github.com/google/ksp/compare/2.3.3...2.3.4)

Updates `com.mikepenz.aboutlibraries.plugin.android` from 13.1.0 to 13.2.1

---
updated-dependencies:
- dependency-name: androidx.activity:activity-compose
  dependency-version: 1.12.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.compose:compose-bom
  dependency-version: 2025.12.01
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.mikepenz:aboutlibraries-compose-m3
  dependency-version: 13.2.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: com.mikepenz.aboutlibraries.plugin.android
  dependency-version: 13.2.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: com.google.devtools.ksp
  dependency-version: 2.3.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.mikepenz.aboutlibraries.plugin.android
  dependency-version: 13.2.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-04 12:38:52 +01:00
Ricki Hirner
6bd9422f3b [CI] Don't require build cache secrets (#1900)
* Don't require build cache secrets

* Use our labels for Dependabot PRs
2026-01-04 12:06:28 +01:00
Ricki Hirner
9754770238 [CI] Fix pre-populating configuration cache 2025-12-18 10:36:29 +01:00
Ricki Hirner
6be15fd366 [CI] Actually use configuration cache (#1891)
* Cache configurations per job

* Use separate job for Dependency submission

* Use GRADLE_OPTS to enable build and configuration cache

* Test .android

* Cache .android for configuration cache

* Disable CodeQL for PRs

* Fix AVD path
2025-12-18 10:27:01 +01:00
Ricki Hirner
0cb27f0c2f [CI] Add gradle remote build cache (bitfireAT/davx5#752)
* [CI] Add gradle remote build cache

* Update workflow

* Don't cache local build cache; pre-populate configuration cache

* Allow configuration caching of tasks

* Free some disk space before running instrumented tests; cache whole .android (not only .android/avd)

* Allow branches to update configuration cache

* Use dry run to pre-populate configuration cache

* Test runs: don't cache

* Fix remote build cache configuration for non-CI builds

* Add comment
2025-12-17 18:06:17 +01:00
Sunik Kupfer
776305bd12 Rename dismissInvalidResource to dismissCollectionError (#1887)
Rename method for clarity

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
2025-12-17 13:14:06 +01:00
dependabot[bot]
01f54df3c0 [CI] Bump actions/cache from 4 to 5 in the ci-actions group (#1886)
Bumps the ci-actions group with 1 update: [actions/cache](https://github.com/actions/cache).


Updates `actions/cache` from 4 to 5
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-16 13:38:54 +01:00
Ricki Hirner
4944ce59b1 Version bump to 4.5.8-alpha.1 2025-12-12 15:32:30 +01:00
Sunik Kupfer
0e455d8371 LocalTaskList: Stop subclassing DmfsTaskList (#1882)
* LocalTaskList: Stop subclassing DmfsTaskList

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

# Conflicts:
#	gradle/libs.versions.toml

* Dont touch agp

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Update synctools

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
2025-12-11 16:22:02 +01:00
Ricki Hirner
a938b511cd Nextcloud Login Flow: handle non-success status codes (#1878)
* Nextcloud Login Flow: handle non-success status codes

* Update error message to use class name when localized message is null

* Update dav4jvm to get HTTP reason phrases in HttpException
2025-12-11 15:30:32 +01:00
Ricki Hirner
d32b86789b Update AGP 2025-12-11 15:13:15 +01:00
Sunik Kupfer
84d58f73db Assume initial state for test updatesOwnerAccount (#1874)
* Assume initial state

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Enhance comment

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Don't pass provider

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
2025-12-11 14:16:03 +01:00
dependabot[bot]
cc43998148 Bump the app-dependencies group with 4 updates (#1867)
* Bump the app-dependencies group with 4 updates

Bumps the app-dependencies group with 4 updates: androidx.activity:activity-compose, androidx.compose:compose-bom, [io.mockk:mockk](https://github.com/mockk/mockk) and [io.mockk:mockk-android](https://github.com/mockk/mockk).


Updates `androidx.activity:activity-compose` from 1.12.0 to 1.12.1

Updates `androidx.compose:compose-bom` from 2025.11.01 to 2025.12.00

Updates `io.mockk:mockk` from 1.14.5 to 1.14.7
- [Release notes](https://github.com/mockk/mockk/releases)
- [Commits](https://github.com/mockk/mockk/compare/1.14.5...1.14.7)

Updates `io.mockk:mockk-android` from 1.14.5 to 1.14.7
- [Release notes](https://github.com/mockk/mockk/releases)
- [Commits](https://github.com/mockk/mockk/compare/1.14.5...1.14.7)

Updates `io.mockk:mockk-android` from 1.14.5 to 1.14.7
- [Release notes](https://github.com/mockk/mockk/releases)
- [Commits](https://github.com/mockk/mockk/compare/1.14.5...1.14.7)

---
updated-dependencies:
- dependency-name: androidx.activity:activity-compose
  dependency-version: 1.12.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.compose:compose-bom
  dependency-version: 2025.12.00
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: io.mockk:mockk
  dependency-version: 1.14.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: io.mockk:mockk-android
  dependency-version: 1.14.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: io.mockk:mockk-android
  dependency-version: 1.14.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>

* Override SDK level

* Suppress lint warnings for LaunchedEffect / Context.getString

* Suppress lint warnings for other Context.getStrings (or replace by stringResource if possible)

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-12-11 11:27:37 +01:00
Sunik Kupfer
b354bfebc2 LocalTask: Don't subclass DmfsTask (#1862)
* LocalTask: Don't subclass DmfsTask

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Adapt usages

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Update synctools

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
2025-12-10 14:37:23 +01:00
Sunik Kupfer
29240ea16f Skip flaky test when not moving into anticipated forever pending sync state (#1872)
* Assume we moved into forever pending sync state

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Update comment

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
2025-12-10 10:25:58 +01:00
Ricki Hirner
e7b88f9aa8 Version bump to 4.5.7.1 2025-12-09 11:49:50 +01:00
Arnau Mora
4d71517cde Delegate fileName into DmfsTask.syncId (#1871)
Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2025-12-09 10:40:22 +01:00
Ricki Hirner
0d4f154baf Use Ktor for Nextcloud Login Flow (#1817)
* [WIP] Use Ktor for Nextcloud login flow

- Replace OkHttp with Ktor for HTTP requests
- Update URL handling to use Ktor's `Url` class
- Adjust `postForJson` method to use Ktor's HTTP client
- Refactor URL building logic for login flow initiation

* Use Ktor for Nextcloud login flow

- Migrate to Ktor's ContentNegotiation plugin for JSON handling
- Update dependencies and configuration for Ktor serialization
- Refactor `NextcloudLoginFlow` to use Ktor's JSON serialization

* Add tests

* Allow unit tests that mock/use HttpClient without Conscrypt

* KDoc

* Minor fixes

* Use toUrlOrNull from dav4jvm

* Don't change strings in this PR

* Update dav4jvm and synctools
2025-12-08 15:40:39 +01:00
Ricki Hirner
9eb70a5564 Bump version to 4.5.7 2025-12-08 12:19:38 +01:00
Ricki Hirner
24e0a864bd Update AVD caching in CI workflow (#1865)
- Split AVD cache handling into restore and save steps
- Add condition to save AVD cache only if restore misses
2025-12-05 16:25:25 +01:00
Ricki Hirner
10ec0c3b6d Fetch translations from Transifex 2025-12-05 15:20:46 +01:00
Ricki Hirner
bd3349cc38 Bump version to 4.5.7-rc.1 2025-12-05 15:19:43 +01:00
Ricki Hirner
c7bc2b317b Update synctools (#1864)
* Update synctools (fixes #1797, closes #1859)

* Use `com.github.bitfireAT:synctools` because `com.github.bitfireat:synctools` is not available on Jitpack for this commit
2025-12-05 15:16:29 +01:00
Sunik Kupfer
2d10cbb07d Improve closing of content provider in verify account owner test (#1838)
* Optimize imports

* Remove the ignore annotation

* Move provider use out of verify method

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Remove unnecessary provider.use blocks

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Add spaces

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Rename lambda param provider to client in LocalDataStore implementations

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Enhance kdoc

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Improve provider client usage

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Replace calling apply with assignment

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Remove whitespace

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Add nullable returns even though they never return null

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Apply WillNotClose annotation to client parameter instead of method

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
2025-12-02 10:34:39 +01:00
dependabot[bot]
88928792af Bump the app-dependencies group with 2 updates (#1860)
Bumps the app-dependencies group with 2 updates: [io.ktor:ktor-client-core](https://github.com/ktorio/ktor) and [io.ktor:ktor-client-okhttp](https://github.com/ktorio/ktor).


Updates `io.ktor:ktor-client-core` from 3.3.2 to 3.3.3
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/3.3.2...3.3.3)

Updates `io.ktor:ktor-client-okhttp` from 3.3.2 to 3.3.3
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/3.3.2...3.3.3)

Updates `io.ktor:ktor-client-okhttp` from 3.3.2 to 3.3.3
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/3.3.2...3.3.3)

---
updated-dependencies:
- dependency-name: io.ktor:ktor-client-core
  dependency-version: 3.3.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: io.ktor:ktor-client-okhttp
  dependency-version: 3.3.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: io.ktor:ktor-client-okhttp
  dependency-version: 3.3.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-01 18:36:53 +01:00
Sunik Kupfer
b5e8c80db1 Try to fix pending sync state test failures by using a hot flow (#1839)
* Remove the ignore annotation

* Turn inPendingState in to a hot state flow for the test duration

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Rename methods registering the sync state observer

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
2025-12-01 14:40:52 +01:00
Ricki Hirner
6f09f55e1a Update synctools (ignores DTEND < DTSTART and inverts negative event durations) (#1858) 2025-12-01 12:02:42 +01:00
Ricki Hirner
b08f10a98f HttpClientBuilder: clarify documentation for authDomain (#1857)
* Update authentication domain parameter

- Rename `onlyHost` to `authDomain` in `fromAccount`
- Update `authenticate` method to use `domain` instead of `host`
- Clarify documentation for `authDomain` parameter

* More KDoc

* Fix other calls / tests
2025-12-01 11:54:36 +01:00
Sunik Kupfer
a3a952d875 LocalTaskList/LocalTask: Consume fields provided by synctools (#1811)
* Move companion object to end of class

* Update synctools

* Make DmfsTaskList final

* Use DmfsTaskList SyncState

* Drop fields now provided in DmfsTask and adapt constructors

* Use column constants from DmfsTask instead

* Use DmfsTask column constants

* Update synctools

* Don't handle scheduleTag

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Update synctools

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-11-27 16:35:17 +01:00
Ricki Hirner
e9fc570895 Extract ResourceDownloader from ContactsSyncManager, add tests (#1849)
* Add ResourceDownloader and tests

- Introduce `ResourceDownloader` class for downloading external resources
- Add unit tests for `ResourceDownloader`
- Refactor `ContactsSyncManager` to use `ResourceDownloader`

* KDoc

- Add detailed documentation for `download` method
- Clarify authentication handling and return behavior

* Minor changes
2025-11-27 16:10:33 +01:00
Ricki Hirner
098b7d5b12 Log warning instead of throwing exception on multiple build calls (#1847)
Log warning instead of throwing IllegalStateException on multiple build calls

- Change `build()` to log a warning instead of throwing an exception on subsequent calls.
- Change `buildKtor()` to log a warning instead of throwing an exception on subsequent calls.
2025-11-27 16:08:52 +01:00
Ricki Hirner
a38dc29cca Update WebDAV property names (#1841)
* Update WebDAV property names according to new dav4jvm naming scheme

* Use new supported-report-set; fix `MaxResourceSize` property reference in CalendarSyncManager

* Remove comment
2025-11-27 11:46:10 +01:00
Ricki Hirner
84b9a14ba1 Update version to 4.5.6.1 2025-11-27 11:15:39 +01:00
Arnau Mora
cda95dc789 Fix HTTP Client provider for contact resource sync from URL (#1844)
Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2025-11-27 11:15:06 +01:00
dependabot[bot]
f64882ca2a Bump the app-dependencies group with 15 updates (#1840)
Bumps the app-dependencies group with 15 updates:

| Package | From | To |
| --- | --- | --- |
| androidx.activity:activity-compose | `1.11.0` | `1.12.0` |
| androidx.lifecycle:lifecycle-runtime-compose | `2.9.4` | `2.10.0` |
| androidx.lifecycle:lifecycle-viewmodel-ktx | `2.9.4` | `2.10.0` |
| androidx.lifecycle:lifecycle-viewmodel-compose | `2.9.4` | `2.10.0` |
| androidx.compose:compose-bom | `2025.11.00` | `2025.11.01` |
| [com.squareup.okhttp3:okhttp](https://github.com/square/okhttp) | `5.3.0` | `5.3.2` |
| [com.squareup.okhttp3:okhttp-brotli](https://github.com/square/okhttp) | `5.3.0` | `5.3.2` |
| [com.squareup.okhttp3:logging-interceptor](https://github.com/square/okhttp) | `5.3.0` | `5.3.2` |
| [com.squareup.okhttp3:mockwebserver](https://github.com/square/okhttp) | `5.3.0` | `5.3.2` |
| androidx.room:room-ktx | `2.8.3` | `2.8.4` |
| androidx.room:room-compiler | `2.8.3` | `2.8.4` |
| androidx.room:room-paging | `2.8.3` | `2.8.4` |
| androidx.room:room-runtime | `2.8.3` | `2.8.4` |
| androidx.room:room-testing | `2.8.3` | `2.8.4` |
| [com.google.devtools.ksp](https://github.com/google/ksp) | `2.3.2` | `2.3.3` |


Updates `androidx.activity:activity-compose` from 1.11.0 to 1.12.0

Updates `androidx.lifecycle:lifecycle-runtime-compose` from 2.9.4 to 2.10.0

Updates `androidx.lifecycle:lifecycle-viewmodel-ktx` from 2.9.4 to 2.10.0

Updates `androidx.lifecycle:lifecycle-viewmodel-compose` from 2.9.4 to 2.10.0

Updates `androidx.lifecycle:lifecycle-viewmodel-ktx` from 2.9.4 to 2.10.0

Updates `androidx.lifecycle:lifecycle-viewmodel-compose` from 2.9.4 to 2.10.0

Updates `androidx.compose:compose-bom` from 2025.11.00 to 2025.11.01

Updates `com.squareup.okhttp3:okhttp` from 5.3.0 to 5.3.2
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.3.0...parent-5.3.2)

Updates `com.squareup.okhttp3:okhttp-brotli` from 5.3.0 to 5.3.2
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.3.0...parent-5.3.2)

Updates `com.squareup.okhttp3:logging-interceptor` from 5.3.0 to 5.3.2
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.3.0...parent-5.3.2)

Updates `com.squareup.okhttp3:mockwebserver` from 5.3.0 to 5.3.2
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.3.0...parent-5.3.2)

Updates `com.squareup.okhttp3:okhttp-brotli` from 5.3.0 to 5.3.2
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.3.0...parent-5.3.2)

Updates `com.squareup.okhttp3:logging-interceptor` from 5.3.0 to 5.3.2
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.3.0...parent-5.3.2)

Updates `com.squareup.okhttp3:mockwebserver` from 5.3.0 to 5.3.2
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.3.0...parent-5.3.2)

Updates `androidx.room:room-ktx` from 2.8.3 to 2.8.4

Updates `androidx.room:room-compiler` from 2.8.3 to 2.8.4

Updates `androidx.room:room-paging` from 2.8.3 to 2.8.4

Updates `androidx.room:room-runtime` from 2.8.3 to 2.8.4

Updates `androidx.room:room-testing` from 2.8.3 to 2.8.4

Updates `androidx.room:room-compiler` from 2.8.3 to 2.8.4

Updates `androidx.room:room-paging` from 2.8.3 to 2.8.4

Updates `androidx.room:room-runtime` from 2.8.3 to 2.8.4

Updates `androidx.room:room-testing` from 2.8.3 to 2.8.4

Updates `com.google.devtools.ksp` from 2.3.2 to 2.3.3
- [Release notes](https://github.com/google/ksp/releases)
- [Commits](https://github.com/google/ksp/compare/2.3.2...2.3.3)

---
updated-dependencies:
- dependency-name: androidx.activity:activity-compose
  dependency-version: 1.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.lifecycle:lifecycle-runtime-compose
  dependency-version: 2.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.lifecycle:lifecycle-viewmodel-ktx
  dependency-version: 2.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.lifecycle:lifecycle-viewmodel-compose
  dependency-version: 2.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.lifecycle:lifecycle-viewmodel-ktx
  dependency-version: 2.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.lifecycle:lifecycle-viewmodel-compose
  dependency-version: 2.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.compose:compose-bom
  dependency-version: 2025.11.01
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:okhttp
  dependency-version: 5.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:okhttp-brotli
  dependency-version: 5.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:logging-interceptor
  dependency-version: 5.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:mockwebserver
  dependency-version: 5.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:okhttp-brotli
  dependency-version: 5.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:logging-interceptor
  dependency-version: 5.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:mockwebserver
  dependency-version: 5.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-ktx
  dependency-version: 2.8.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-compiler
  dependency-version: 2.8.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-paging
  dependency-version: 2.8.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-runtime
  dependency-version: 2.8.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-testing
  dependency-version: 2.8.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-compiler
  dependency-version: 2.8.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-paging
  dependency-version: 2.8.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-runtime
  dependency-version: 2.8.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-testing
  dependency-version: 2.8.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.google.devtools.ksp
  dependency-version: 2.3.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 18:42:28 +01:00
Ricki Hirner
7c2dcf3d70 Comment out failing tests (#1836) 2025-11-24 16:00:53 +01:00
dependabot[bot]
66a34ebd9f [CI] Bump actions/checkout from 5 to 6 in the ci-actions group (#1837)
Bumps the ci-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 5 to 6
- [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/v5...v6)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 16:00:24 +01:00
Ricki Hirner
365364aa89 Update version to 4.5.6 2025-11-24 13:26:18 +01:00
Ricki Hirner
114543f4c5 Fetch translations from Transifex 2025-11-24 13:22:09 +01:00
Ricki Hirner
3bd3f56e1b Fix DeadObjectException handling in SyncManager and Syncer (#1834)
- Update SyncManager to handle LocalStorageException with DeadObjectException cause
- Refactor Syncer to catch all exceptions and handle specific cases
2025-11-24 13:14:40 +01:00
Ricki Hirner
5263172376 [Ktor] Add MustBeClosed annotation to buildKtor method (#1829)
Add @MustBeClosed annotation to buildKtor method

This commit adds the `@MustBeClosed` annotation to the `buildKtor` method in `HttpClientBuilder.kt` to indicate that the returned `HttpClient` instance must be closed by the caller. It also updates the test in `HttpClientBuilderTest.kt` to use the `use` function to ensure proper resource management.
2025-11-21 12:48:04 +01:00
Ricki Hirner
babd52cfb1 Bump version to 4.5.6-rc.1 2025-11-20 16:45:40 +01:00
Ricki Hirner
3d4d533b92 Update synctools (#1825)
* Update synctools

* Update commit ID
2025-11-20 16:42:40 +01:00
Ricki Hirner
76fc024ef6 Lower default minimum log level from INFO to FINE (#1827)
* Increase logging level

* Adjust log levels for visibility in non-verbose logs

- Change log level from FINER to FINE in StreamingFileDescriptor
- Update log level from FINER to FINE in AccountSettingsMigration8
- Add note about log levels in LogManager documentation

* Fix KDoc typo

* Update comment
2025-11-20 11:37:24 +01:00
Sunik Kupfer
794b4c1c7f DebugInfo: Support viewing jtx Board resources (#1818)
* Support viewing jtxBoard resources from debug info

* Correct value of EXTRA_LOCAL_RESOURCE_URI

* Correct comment

* Use the same intent for journals, notes, calendar and tasks

* State working task authorities explicitly

* Use edit action to not crash opentasks

* Use getViewIntentUriFor for jtx Board tasks

* Remove explicit tasks authority for jtx Board

* Remove explicit tasks authority for jtx Board

* Remove early return statement

* Dont handle jtxBoard tasks in LocalTask which is only for Dmfs tasks

* Add some kdoc to LocalTask and LocalJtxICalObject

* Use when with in list

* Add FLAG_GRANT_READ_URI_PERMISSION to the correct intent

* Correct line endings to from CRLF to LF
2025-11-20 09:33:24 +01:00
Ricki Hirner
aac6356722 Delete app/src/main/kotlin/at/bitfire/davdroid/network/HttpClient.kt
Remove empty file
2025-11-19 16:52:35 +01:00
Ricki Hirner
9bc46d4194 Update dependencies, including our libs (#1822) 2025-11-19 15:35:06 +01:00
Ricki Hirner
a3aac44775 Ignore failing test: testVerifySyncAlwaysPending_wrongBehaviour_android14 (#1824)
Comment out test
2025-11-19 15:14:41 +01:00
Arnau Mora
084ba3b630 Update dav4jvm to new okhttp package (#1786)
* Upgrade dav4jvm

* Exclude ktor from dav4jvm

* Fix imports and fix usages

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

* Fix imports for instrumented tests

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

* Fix imports

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

* Upgrade dav4jvm

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

* Do not exclude ktor in dav4jvm

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

* Upgrade dav4jvm

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

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2025-11-17 13:38:10 +01:00
Ricki Hirner
28dcf90775 Refactor default reminder builder to dedicated class + unit tests (#1815)
* [WIP] Move default reminder builder to dedicated class + unit tests

* Add tests

* Just turn off Conscrypt for now

* Fix library name
2025-11-17 10:34:36 +01:00
Ricki Hirner
70766affd9 [Ktor] Allow building a Ktor client (#1810)
* Add Ktor dependency

* Add buildKtor method

* Add test and deprecation notice

* KDoc
2025-11-17 09:38:08 +01:00
Ricki Hirner
d00292f421 Only use cert4android when needed (#1802)
* Refactor ClientCertKeyManager and HttpClientBuilder

- Add logging to `ClientCertKeyManager` for better error handling.
- Update `HttpClientBuilder` to conditionally use custom trust manager and hostname verifier based on `allowCustomCerts` flag.
- Rename `customCertsUI` to `allowCustomCerts` in build configuration.

* Update trust manager and hostname verifier selection logic

- Improve logging and error handling in `ClientCertKeyManager`

* App settings: hide certificate settings when custom certificates are not allowed

* Typo
2025-11-12 11:04:13 +01:00
dependabot[bot]
6b5c4f191a Bump the app-dependencies group with 2 updates (#1803)
Bumps the app-dependencies group with 2 updates: androidx.compose:compose-bom and [com.google.devtools.ksp](https://github.com/google/ksp).


Updates `androidx.compose:compose-bom` from 2025.10.01 to 2025.11.00

Updates `com.google.devtools.ksp` from 2.3.0 to 2.3.2
- [Release notes](https://github.com/google/ksp/releases)
- [Commits](https://github.com/google/ksp/compare/2.3.0...2.3.2)

---
updated-dependencies:
- dependency-name: androidx.compose:compose-bom
  dependency-version: 2025.11.00
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: com.google.devtools.ksp
  dependency-version: 2.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-10 17:28:43 +01:00
Sunik Kupfer
5c7b792e7f Support sync adapter pending sync indication on Android 14+ (#1676)
* Add AccountSettingsMigration21 to cancel pending address book syncs

* Add application context annotation

* Add log statement

* Increase account settings current version

* Add and update kdoc

* Call cancelSync via integration

* Optimize imports

* Update kdoc

* Updating log statement

* Also cancel calendar syncs

* Don't infer authority from account type

* Update kdoc

* Cancel only on Android 14+

* Cancel for all authorities and update kdoc

* Use cancelSync directly in migration

* Enable forever pending sync workaround by canceling sync adapter framework syncs on Android 14+

* Stop always returning false for pending sync state of sync adapter framework

* Cancel by request and empty bundle

* Cancel syncs for calendar, tasks, and contacts separately

* Minor edits to log statement and kdoc

* Add migration test; Update migration

* Log all extras instead of just upload flag

* Use lazy on syncFrameworkIntegration injection

* Multiple changes

- don't cancel address book accounts of all main accounts
- merge loops

* Add authority to log statement

* Replace complex state verification logic by status changed flow

* Cancel syncs account wide across all authorities

* Add some delay to allow dummy sync requests to be created

* Reduce wait until pending

* Drop Thread.sleep()

* Use a callback flow instead of mutable state flow

* Shorten first true filter

* Shorten remaining first true filter
2025-11-10 15:46:58 +01:00
Ricki Hirner
c9da496142 Bump version to 4.5.6-beta.1 2025-11-06 14:30:07 +01:00
Ricki Hirner
ee098c4a83 Explicitly integrate Conscrypt (#1796)
* Merge HttpClient and HttpClientBuilder

- Remove `HttpClient` class and replace with `OkHttpClient`
- Update all references to `HttpClient` to use `OkHttpClient`
- Add new `HttpClientBuilder` class for building `OkHttpClient` instances
- Update all builder usages to use the new `HttpClientBuilder` class

* KDoc

* Integrate Conscrypt for TLS

- Add Conscrypt dependency
- Initialize Conscrypt in HttpClientBuilder
- Create ConscryptIntegration utility

* KDoc

* Make object a class, better test

* Update cert4android to the latest version (doesn't bundle Conscrypt anymore)
2025-11-06 10:07:40 +01:00
Ricki Hirner
a8bd296520 Merge HttpClient and HttpClientBuilder (#1795)
* Merge HttpClient and HttpClientBuilder

- Remove `HttpClient` class and replace with `OkHttpClient`
- Update all references to `HttpClient` to use `OkHttpClient`
- Add new `HttpClientBuilder` class for building `OkHttpClient` instances
- Update all builder usages to use the new `HttpClientBuilder` class

* KDoc
2025-11-05 13:20:02 +01:00
Sunik Kupfer
0959624dee Update Calendars.OWNER_ACCOUNT when renaming an account (#1751)
* Fix typo

* Also set OWNER_ACCOUNT when updating calendar because renaming account

* Add test

* Update comment clarifying content values

* Assume calendar provider is present and drop null checks
2025-11-05 12:40:34 +01:00
Ricki Hirner
bd13d27e38 HttpClient: remove unnecessary close() (#1792)
* Remove unnecessary AutoCloseable implementations and client.close() calls

- Remove AutoCloseable from NextcloudLoginFlow and DavResourceFinder
- Remove client.close() calls in various classes and tests
- Update HttpClient to remove close() method

* Fix test

* Fix annotations / KDoc
2025-11-04 16:38:22 +01:00
dependabot[bot]
85548163ca Bump the app-dependencies group with 4 updates (#1790)
Bumps the app-dependencies group with 4 updates: [com.squareup.okhttp3:okhttp](https://github.com/square/okhttp), [com.squareup.okhttp3:okhttp-brotli](https://github.com/square/okhttp), [com.squareup.okhttp3:logging-interceptor](https://github.com/square/okhttp) and [com.squareup.okhttp3:mockwebserver](https://github.com/square/okhttp).


Updates `com.squareup.okhttp3:okhttp` from 5.2.1 to 5.3.0
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.2.1...parent-5.3.0)

Updates `com.squareup.okhttp3:okhttp-brotli` from 5.2.1 to 5.3.0
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.2.1...parent-5.3.0)

Updates `com.squareup.okhttp3:logging-interceptor` from 5.2.1 to 5.3.0
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.2.1...parent-5.3.0)

Updates `com.squareup.okhttp3:mockwebserver` from 5.2.1 to 5.3.0
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.2.1...parent-5.3.0)

Updates `com.squareup.okhttp3:okhttp-brotli` from 5.2.1 to 5.3.0
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.2.1...parent-5.3.0)

Updates `com.squareup.okhttp3:logging-interceptor` from 5.2.1 to 5.3.0
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.2.1...parent-5.3.0)

Updates `com.squareup.okhttp3:mockwebserver` from 5.2.1 to 5.3.0
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.2.1...parent-5.3.0)

---
updated-dependencies:
- dependency-name: com.squareup.okhttp3:okhttp
  dependency-version: 5.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:okhttp-brotli
  dependency-version: 5.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:logging-interceptor
  dependency-version: 5.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:mockwebserver
  dependency-version: 5.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:okhttp-brotli
  dependency-version: 5.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:logging-interceptor
  dependency-version: 5.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:mockwebserver
  dependency-version: 5.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-03 16:07:44 +01:00
Ricki Hirner
026750eca3 Bump version to 4.5.6-alpha.2 2025-11-01 21:56:47 +01:00
Ricki Hirner
d365a504e8 Refactor sequence handling in calendar sync (#1789)
* Refactor sequence handling in calendar sync

- Move sequence update logic to SequenceUpdater
- Update LocalCalendar to use new SequenceUpdater
- Remove redundant methods from LocalCalendar
- Update tests and dependencies

* Minor KDoc
2025-11-01 21:55:28 +01:00
Ricki Hirner
c64cb1e7ec Remove okhttp caching (bitfireAT/davx5#715)
* Remove okhttp caching
* Simplification
2025-11-01 12:38:26 +01:00
Ricki Hirner
837b5e5d50 Bump version to 4.5.6-alpha.1 2025-10-30 14:29:29 +01:00
Ricki Hirner
98aefc4fee Synchronize without Event data class (#1783)
* [WIP] Proof of concept for syncing without `Event` data class

* Replace AndroidEvent2 with EventsContract

* Update synctools, refactor upload logic in `CalendarSyncManager`

* KDoc

* Update UID immediately in `ContactsSyncManager`, `CalendarSyncManager`, and `TasksSyncManager`

- Remove `OnSuccessContext.uid` from `GeneratedResource`

* Minor changes

* Handle multiple events in a single iCalendar

- Rename `processVEvent` to `processICalendar`
- Add default alarm for non-full-day events again
- Prevent NPE on null flags (used for debug info)

* Fix tests
2025-10-30 14:28:05 +01:00
Ricki Hirner
a8c8a8d2e0 Refactor SEQUENCE and UID handling on successful uploads (#1785)
* Define interfaces

* [WIP] Refactor sequence and UID handling in event uploads

- Refactor `generateUpload` method to return `GeneratedResource`.
- Update `SyncManager` to handle `GeneratedResource`.
- Implement sequence and UID management in `CalendarSyncManager`.
- Remove redundant `prepareForUpload` method from `LocalEvent`.

* Refactor sequence handling in uploads

- Move UID validation to `DavUtils.isGoodFileBaseName`
- Update sequence directly in iCalendar for group-scheduled events
- Rename `resourceBaseName` to `suggestedBaseName` for clarity

* Refactor sequence / UID handling in contact uploads

- Update `LocalAddress` interface to include `updateUid` method
- Modify `ContactsSyncManager` to handle UID generation and update
- Remove redundant UID handling in `LocalContact` and `LocalGroup`
- Adjust code style settings for right margin

* Remove redundant UID update from `ContactsSyncManager` and `CalendarSyncManager`

* Implement UID handling in `TasksSyncManager` for uploads

* Update JtxSyncManager

- Update `JtxSyncManager.kt` to implement the `generateUpload` function.
- Update `LocalJtxICalObject.kt` to include the `updateUid` method for updating UIDs in the collection.

* Remove deprecated `prepareForUpload` method from `LocalResource`

- Remove `prepareForUpload` implementations from `LocalContact`, `LocalEvent`, `LocalGroup`, and `LocalTask`

* Rename `isGoodFileBaseName` to `isGoodFileName` in `DavUtils`

* Fix tests

* Move UID generation logic to `DavUtils.generateUidIfNecessary`

- Use `DavUtils.fileNameFromUid` for generating file names
- Update `ContactsSyncManager`, `CalendarSyncManager`, `TasksSyncManager`, and `JtxSyncManager` to use new utility methods

* Add tests for DavUtils

* Some tests

* Refactor onSuccessfulUpload

* Update KDoc

* Logging

* Remove unnecessary LocalEvent method

* KDoc
2025-10-29 17:57:34 +01:00
Ricki Hirner
b839cbfe7f Simplify LocalResource interface (#1784)
* Simplify LocalResource interface

- Remove generic parameter from LocalResource interface
- Update all implementations to reflect the change
- Adjust related test cases and exception handling

* Fix tests
2025-10-29 08:50:09 +01:00
Ricki Hirner
f0f9f58e49 Bump version to 4.5.5 2025-10-28 11:20:00 +01:00
Ricki Hirner
66f6e48e3b Fetch translations from Transifex 2025-10-28 11:18:54 +01:00
Ricki Hirner
d6feda1142 Fix OkHttp3 crash in release builds (bitfireAT/davx5#712)
- Add ProGuard rules to keep OkHttp3 IDN mapping classes
2025-10-28 11:12:51 +01:00
Ricki Hirner
05f058ab3f Version bump to 4.5.5-rc.1 2025-10-27 11:16:51 +01:00
Sunik Kupfer
4e4c0f5e31 Move debug info notification action to debug info screen button (#1730)
* Update view item on sync error string

* Remove view item action from notification

* Show button in debug info screen to jump to problematic event resource

* Move companion object to the end of activity class

* Add local resource dump to intent

* Add kdoc

* Add some comments for not yet implemented resources

* Don't export DebugInfoActivity

* Send intent instead of URI and launch from DebugInfoActivity

* Add option to view problematic contact

* Extract intent builder logic to another method

* Add option to view problematic contact

* Minor changes for readability

* Extract dump string creation to interface method

* Pass Uri instead of intent and create view local resource intent in DebugInfoActivity

* Use androids existing getContactLookupUri method

* Remove extra variable

* Remove obsolete val declaration

* Rename dump to summary

* Refactor code structure for local resource URI handling

* Update code structure to use getDebugSummary for local resource summaries

* Update exception handling in SyncNotificationManager

Change the catch block to handle all `Throwable` exceptions instead of just `OutOfMemoryError`. This ensures that any potential issues arising from providing information about the local resource are caught and ignored.

* Add "copy remote URL" action

* Use string resource

* Truncate contact, task, and event strings to 1000 characters

* Fix tests

* Minor changes

- Replace `ContactsContract.RawContacts` with `RawContacts` in `LocalContact.kt`
- Remove unnecessary newline in `LocalJtxICalObject.kt`

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-10-27 11:15:21 +01:00
Arnau Mora
0304d7168a Migrate to SecureTextField (#1191)
* Using `SecureTextField`

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

* `OutlinedSecureTextField` doesn't support `readOnly`

* fixed string conversions

* - Update AddWebdavMountScreen to use enabled instead of readOnly
- Ensure onKeyboardAction checks canContinue before proceeding
- Update UrlLogin and EmailLogin to ensure onKeyboardAction checks canContinue before proceeding
- Update InputDialogs to ensure confirmEnabled is checked before proceeding

* Use get() for deriving things from a mutable state

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-10-27 11:15:11 +01:00
Ricki Hirner
19458aa95c Delete .github/workflows/dependent-issues.yml
Obsoleted by https://github.blog/changelog/2025-08-21-dependencies-on-issues/
2025-10-26 12:23:31 +01:00
dependabot[bot]
4412617079 Bump the app-dependencies group across 1 directory with 7 updates (#1778)
Bumps the app-dependencies group with 7 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| androidx.work:work-runtime-ktx | `2.10.5` | `2.11.0` |
| androidx.work:work-testing | `2.10.5` | `2.11.0` |
| androidx.room:room-ktx | `2.8.2` | `2.8.3` |
| androidx.room:room-compiler | `2.8.2` | `2.8.3` |
| androidx.room:room-paging | `2.8.2` | `2.8.3` |
| androidx.room:room-runtime | `2.8.2` | `2.8.3` |
| androidx.room:room-testing | `2.8.2` | `2.8.3` |



Updates `androidx.work:work-runtime-ktx` from 2.10.5 to 2.11.0

Updates `androidx.work:work-testing` from 2.10.5 to 2.11.0

Updates `androidx.work:work-testing` from 2.10.5 to 2.11.0

Updates `androidx.room:room-ktx` from 2.8.2 to 2.8.3

Updates `androidx.room:room-compiler` from 2.8.2 to 2.8.3

Updates `androidx.room:room-paging` from 2.8.2 to 2.8.3

Updates `androidx.room:room-runtime` from 2.8.2 to 2.8.3

Updates `androidx.room:room-testing` from 2.8.2 to 2.8.3

Updates `androidx.room:room-compiler` from 2.8.2 to 2.8.3

Updates `androidx.room:room-paging` from 2.8.2 to 2.8.3

Updates `androidx.room:room-runtime` from 2.8.2 to 2.8.3

Updates `androidx.room:room-testing` from 2.8.2 to 2.8.3

---
updated-dependencies:
- dependency-name: androidx.work:work-runtime-ktx
  dependency-version: 2.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.work:work-testing
  dependency-version: 2.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.work:work-testing
  dependency-version: 2.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-ktx
  dependency-version: 2.8.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-compiler
  dependency-version: 2.8.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-paging
  dependency-version: 2.8.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-runtime
  dependency-version: 2.8.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-testing
  dependency-version: 2.8.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-compiler
  dependency-version: 2.8.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-paging
  dependency-version: 2.8.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-runtime
  dependency-version: 2.8.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-testing
  dependency-version: 2.8.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-26 11:04:22 +01:00
Ricki Hirner
76277dbfd5 Update Kotlin and KSP versions (#1776)
- Update Kotlin version to 2.2.21
- Update KSP version to 2.3.0
- Remove specific Kotlin and KSP dependencies from Dependabot ignore list
2025-10-26 10:20:57 +01:00
Ricki Hirner
b0b99de56b Update Compose BOM and use PrimaryTabRow (#1772)
- Update Compose BOM version to 2025.10.01
- Replace `TabRow` with `PrimaryTabRow`
2025-10-25 11:15:01 +02:00
Ricki Hirner
b33c4750bb Bump version to 4.5.5-beta.1 2025-10-18 09:15:58 +02:00
Sunik Kupfer
25b749dd1b Rename account on background thread (#1727)
* Change context to default dispatcher on updating automatic sync when renaming account

* Minor changes
- add worker thread annotations
- use injected defaultDispatcher

* Add tests

* Apply withContext with default dispatcher on the whole rename method
2025-10-18 09:13:26 +02:00
Ricki Hirner
39a0fe3f98 Update AboutLibraries and other dependencies (#1760)
Update dependencies and modify AboutActivity to use dynamic library loading

- Remove outdated aboutlibraries.json
- Update AboutActivity to dynamically load libraries using LocalContext
- Replace InvalidRemoteResourceException with InvalidICalendarException in sync managers
- Bump dependency versions for various libraries including mikepenz-aboutLibraries, okhttp, and unifiedpush
- Adjust build.gradle.kts and gradle/libs.versions.toml for new plugin and library versions
2025-10-18 09:12:41 +02:00
Arnau Mora
dd798f8380 Update implementation to match guidelines (#1747) 2025-10-15 11:20:30 +02:00
Sunik Kupfer
47d380de62 Fix unreachable code possibly causing foreign key constraint violation exception (#1740)
* Add test

* Fix unreachable code possibly causing foreign key constraint violation exception

* Make code easier to understand

* Add comments

* Split up tests and add mockk verify
2025-10-14 10:57:15 +02:00
dependabot[bot]
019d32a9b7 [CI] Bump github/codeql-action from 3 to 4 in the ci-actions group (#1749)
Bumps the ci-actions group with 1 update: [github/codeql-action](https://github.com/github/codeql-action).


Updates `github/codeql-action` from 3 to 4
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: ci-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-13 20:04:01 +02:00
Sunik Kupfer
0acabd9c80 Update http error message strings (#1745) 2025-10-13 10:41:00 +02:00
dependabot[bot]
aa23980a59 Bump the app-dependencies group across 1 directory with 13 updates (#1744)
* Bump the app-dependencies group across 1 directory with 13 updates

Bumps the app-dependencies group with 13 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| androidx.compose:compose-bom | `2025.09.01` | `2025.10.00` |
| [io.mockk:mockk](https://github.com/mockk/mockk) | `1.14.5` | `1.14.6` |
| [io.mockk:mockk-android](https://github.com/mockk/mockk) | `1.14.5` | `1.14.6` |
| [com.squareup.okhttp3:okhttp](https://github.com/square/okhttp) | `5.1.0` | `5.2.0` |
| [com.squareup.okhttp3:okhttp-brotli](https://github.com/square/okhttp) | `5.1.0` | `5.2.0` |
| [com.squareup.okhttp3:logging-interceptor](https://github.com/square/okhttp) | `5.1.0` | `5.2.0` |
| [com.squareup.okhttp3:mockwebserver](https://github.com/square/okhttp) | `5.1.0` | `5.2.0` |
| androidx.room:room-ktx | `2.8.1` | `2.8.2` |
| androidx.room:room-compiler | `2.8.1` | `2.8.2` |
| androidx.room:room-paging | `2.8.1` | `2.8.2` |
| androidx.room:room-runtime | `2.8.1` | `2.8.2` |
| androidx.room:room-testing | `2.8.1` | `2.8.2` |
| org.unifiedpush.android:connector | `3.0.10` | `3.1.0` |



Updates `androidx.compose:compose-bom` from 2025.09.01 to 2025.10.00

Updates `io.mockk:mockk` from 1.14.5 to 1.14.6
- [Release notes](https://github.com/mockk/mockk/releases)
- [Commits](https://github.com/mockk/mockk/compare/1.14.5...1.14.6)

Updates `io.mockk:mockk-android` from 1.14.5 to 1.14.6
- [Release notes](https://github.com/mockk/mockk/releases)
- [Commits](https://github.com/mockk/mockk/compare/1.14.5...1.14.6)

Updates `io.mockk:mockk-android` from 1.14.5 to 1.14.6
- [Release notes](https://github.com/mockk/mockk/releases)
- [Commits](https://github.com/mockk/mockk/compare/1.14.5...1.14.6)

Updates `com.squareup.okhttp3:okhttp` from 5.1.0 to 5.2.0
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.1.0...parent-5.2.0)

Updates `com.squareup.okhttp3:okhttp-brotli` from 5.1.0 to 5.2.0
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.1.0...parent-5.2.0)

Updates `com.squareup.okhttp3:logging-interceptor` from 5.1.0 to 5.2.0
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.1.0...parent-5.2.0)

Updates `com.squareup.okhttp3:mockwebserver` from 5.1.0 to 5.2.0
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.1.0...parent-5.2.0)

Updates `com.squareup.okhttp3:okhttp-brotli` from 5.1.0 to 5.2.0
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.1.0...parent-5.2.0)

Updates `com.squareup.okhttp3:logging-interceptor` from 5.1.0 to 5.2.0
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.1.0...parent-5.2.0)

Updates `com.squareup.okhttp3:mockwebserver` from 5.1.0 to 5.2.0
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.1.0...parent-5.2.0)

Updates `androidx.room:room-ktx` from 2.8.1 to 2.8.2

Updates `androidx.room:room-compiler` from 2.8.1 to 2.8.2

Updates `androidx.room:room-paging` from 2.8.1 to 2.8.2

Updates `androidx.room:room-runtime` from 2.8.1 to 2.8.2

Updates `androidx.room:room-testing` from 2.8.1 to 2.8.2

Updates `androidx.room:room-compiler` from 2.8.1 to 2.8.2

Updates `androidx.room:room-paging` from 2.8.1 to 2.8.2

Updates `androidx.room:room-runtime` from 2.8.1 to 2.8.2

Updates `androidx.room:room-testing` from 2.8.1 to 2.8.2

Updates `org.unifiedpush.android:connector` from 3.0.10 to 3.1.0

---
updated-dependencies:
- dependency-name: androidx.compose:compose-bom
  dependency-version: 2025.10.00
  dependency-type: direct:production
  dependency-group: app-dependencies
- dependency-name: io.mockk:mockk
  dependency-version: 1.14.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: io.mockk:mockk-android
  dependency-version: 1.14.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: io.mockk:mockk-android
  dependency-version: 1.14.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:okhttp
  dependency-version: 5.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:okhttp-brotli
  dependency-version: 5.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:logging-interceptor
  dependency-version: 5.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:mockwebserver
  dependency-version: 5.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:okhttp-brotli
  dependency-version: 5.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:logging-interceptor
  dependency-version: 5.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:mockwebserver
  dependency-version: 5.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-ktx
  dependency-version: 2.8.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-compiler
  dependency-version: 2.8.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-paging
  dependency-version: 2.8.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-runtime
  dependency-version: 2.8.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-testing
  dependency-version: 2.8.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-compiler
  dependency-version: 2.8.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-paging
  dependency-version: 2.8.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-runtime
  dependency-version: 2.8.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-testing
  dependency-version: 2.8.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: org.unifiedpush.android:connector
  dependency-version: 3.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>

* Downgrade mockk version to 1.14.5

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-10-09 13:10:31 +02:00
Sunik Kupfer
5674d6b954 ExceptionInfoDialog: show explanation on http 405 and others (#1739)
* Show explanation on http 405

* Also show message for http 5xx

* Correct spelling
2025-10-09 12:05:31 +02:00
dependabot[bot]
df56c8628a [CI] Bump gradle/actions from 4 to 5 in the ci-actions group (#1736)
Bumps the ci-actions group with 1 update: [gradle/actions](https://github.com/gradle/actions).


Updates `gradle/actions` from 4 to 5
- [Release notes](https://github.com/gradle/actions/releases)
- [Commits](https://github.com/gradle/actions/compare/v4...v5)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-06 17:08:39 +02:00
dependabot[bot]
af4ecd3a1d Bump the app-dependencies group with 12 updates (#1732)
Bumps the app-dependencies group with 12 updates:

| Package | From | To |
| --- | --- | --- |
| androidx.work:work-runtime-ktx | `2.10.4` | `2.10.5` |
| androidx.work:work-testing | `2.10.4` | `2.10.5` |
| androidx.compose:compose-bom | `2025.09.00` | `2025.09.01` |
| [com.google.dagger:hilt-android](https://github.com/google/dagger) | `2.57.1` | `2.57.2` |
| [com.google.dagger:hilt-android-compiler](https://github.com/google/dagger) | `2.57.1` | `2.57.2` |
| [com.google.dagger:hilt-android-testing](https://github.com/google/dagger) | `2.57.1` | `2.57.2` |
| [com.google.dagger.hilt.android](https://github.com/google/dagger) | `2.57.1` | `2.57.2` |
| androidx.room:room-ktx | `2.8.0` | `2.8.1` |
| androidx.room:room-compiler | `2.8.0` | `2.8.1` |
| androidx.room:room-paging | `2.8.0` | `2.8.1` |
| androidx.room:room-runtime | `2.8.0` | `2.8.1` |
| androidx.room:room-testing | `2.8.0` | `2.8.1` |


Updates `androidx.work:work-runtime-ktx` from 2.10.4 to 2.10.5

Updates `androidx.work:work-testing` from 2.10.4 to 2.10.5

Updates `androidx.work:work-testing` from 2.10.4 to 2.10.5

Updates `androidx.compose:compose-bom` from 2025.09.00 to 2025.09.01

Updates `com.google.dagger:hilt-android` from 2.57.1 to 2.57.2
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57.1...dagger-2.57.2)

Updates `com.google.dagger:hilt-android-compiler` from 2.57.1 to 2.57.2
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57.1...dagger-2.57.2)

Updates `com.google.dagger:hilt-android-testing` from 2.57.1 to 2.57.2
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57.1...dagger-2.57.2)

Updates `com.google.dagger.hilt.android` from 2.57.1 to 2.57.2
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57.1...dagger-2.57.2)

Updates `com.google.dagger:hilt-android-compiler` from 2.57.1 to 2.57.2
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57.1...dagger-2.57.2)

Updates `com.google.dagger:hilt-android-testing` from 2.57.1 to 2.57.2
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57.1...dagger-2.57.2)

Updates `androidx.room:room-ktx` from 2.8.0 to 2.8.1

Updates `androidx.room:room-compiler` from 2.8.0 to 2.8.1

Updates `androidx.room:room-paging` from 2.8.0 to 2.8.1

Updates `androidx.room:room-runtime` from 2.8.0 to 2.8.1

Updates `androidx.room:room-testing` from 2.8.0 to 2.8.1

Updates `androidx.room:room-compiler` from 2.8.0 to 2.8.1

Updates `androidx.room:room-paging` from 2.8.0 to 2.8.1

Updates `androidx.room:room-runtime` from 2.8.0 to 2.8.1

Updates `androidx.room:room-testing` from 2.8.0 to 2.8.1

Updates `com.google.dagger.hilt.android` from 2.57.1 to 2.57.2
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57.1...dagger-2.57.2)

---
updated-dependencies:
- dependency-name: androidx.work:work-runtime-ktx
  dependency-version: 2.10.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.work:work-testing
  dependency-version: 2.10.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.work:work-testing
  dependency-version: 2.10.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.compose:compose-bom
  dependency-version: 2025.09.01
  dependency-type: direct:production
  dependency-group: app-dependencies
- dependency-name: com.google.dagger:hilt-android
  dependency-version: 2.57.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.google.dagger:hilt-android-compiler
  dependency-version: 2.57.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.google.dagger:hilt-android-testing
  dependency-version: 2.57.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.google.dagger.hilt.android
  dependency-version: 2.57.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.google.dagger:hilt-android-compiler
  dependency-version: 2.57.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.google.dagger:hilt-android-testing
  dependency-version: 2.57.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-ktx
  dependency-version: 2.8.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-compiler
  dependency-version: 2.8.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-paging
  dependency-version: 2.8.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-runtime
  dependency-version: 2.8.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-testing
  dependency-version: 2.8.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-compiler
  dependency-version: 2.8.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-paging
  dependency-version: 2.8.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-runtime
  dependency-version: 2.8.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-testing
  dependency-version: 2.8.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.google.dagger.hilt.android
  dependency-version: 2.57.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-29 22:15:39 +02:00
Arnau Mora
701e292ab5 Use IntentCompat for account fetching from intent extras (#1731) 2025-09-28 10:51:57 +02:00
Ricki Hirner
5da7d9e292 Add CODEOWNERS file (#1729) 2025-09-24 14:11:40 +02:00
Ricki Hirner
e6256764ac Bump version to 4.5.5-alpha.2 2025-09-24 14:00:30 +02:00
Ricki Hirner
e63f815416 Update synctools to work around eventStatus=null update problem (#1728)
Update synctools to work around eventStatus=null update problem; add synctools to Dependabot-ignore
2025-09-24 13:59:12 +02:00
Ricki Hirner
374dadfaaa Bump version to 4.5.5-alpha.1 2025-09-23 11:15:07 +02:00
Ricki Hirner
f6ef13f9fe Update synctools (new uses event builders/processors) and Kotlin/KSP (#1726) 2025-09-23 11:07:41 +02:00
dependabot[bot]
a88cfd2acf Bump the app-dependencies group with 4 updates (#1724)
Bumps the app-dependencies group with 4 updates: androidx.lifecycle:lifecycle-runtime-compose, androidx.lifecycle:lifecycle-viewmodel-ktx, androidx.lifecycle:lifecycle-viewmodel-compose and [com.google.guava:guava](https://github.com/google/guava).


Updates `androidx.lifecycle:lifecycle-runtime-compose` from 2.9.3 to 2.9.4

Updates `androidx.lifecycle:lifecycle-viewmodel-ktx` from 2.9.3 to 2.9.4

Updates `androidx.lifecycle:lifecycle-viewmodel-compose` from 2.9.3 to 2.9.4

Updates `androidx.lifecycle:lifecycle-viewmodel-ktx` from 2.9.3 to 2.9.4

Updates `androidx.lifecycle:lifecycle-viewmodel-compose` from 2.9.3 to 2.9.4

Updates `com.google.guava:guava` from 33.4.8-android to 33.5.0-android
- [Release notes](https://github.com/google/guava/releases)
- [Commits](https://github.com/google/guava/commits)

---
updated-dependencies:
- dependency-name: androidx.lifecycle:lifecycle-runtime-compose
  dependency-version: 2.9.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.lifecycle:lifecycle-viewmodel-ktx
  dependency-version: 2.9.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.lifecycle:lifecycle-viewmodel-compose
  dependency-version: 2.9.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.lifecycle:lifecycle-viewmodel-ktx
  dependency-version: 2.9.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.lifecycle:lifecycle-viewmodel-compose
  dependency-version: 2.9.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.google.guava:guava
  dependency-version: 33.5.0-android
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-22 18:40:14 +02:00
Michael Biebl
98fc946594 Remove duplicate lines in .gitignore (#1725) 2025-09-22 18:20:50 +02:00
Ricki Hirner
00523d9bc8 Fix state matching logic in AndroidSyncFrameworkTest (#1708)
* Fix state matching logic in AndroidSyncFrameworkTest

- Add `fullMatch` parameter to control whether all expected states must be present

* Ensure non-optional expected state matches actual state

* Remove unused rule / variable

* Adapt test

- Update `onStatusChanged` to override the interface method.
- Replace custom assertion with `assertTrue` for state comparison.
2025-09-16 10:42:11 +02:00
dependabot[bot]
53d338d03e Bump the app-dependencies group with 12 updates (#1714)
Bumps the app-dependencies group with 12 updates:

| Package | From | To |
| --- | --- | --- |
| androidx.activity:activity-compose | `1.10.1` | `1.11.0` |
| androidx.hilt:hilt-compiler | `1.2.0` | `1.3.0` |
| androidx.hilt:hilt-navigation-compose | `1.2.0` | `1.3.0` |
| androidx.hilt:hilt-work | `1.2.0` | `1.3.0` |
| androidx.work:work-runtime-ktx | `2.10.3` | `2.10.4` |
| androidx.work:work-testing | `2.10.3` | `2.10.4` |
| androidx.compose:compose-bom | `2025.08.01` | `2025.09.00` |
| androidx.room:room-ktx | `2.7.2` | `2.8.0` |
| androidx.room:room-compiler | `2.7.2` | `2.8.0` |
| androidx.room:room-paging | `2.7.2` | `2.8.0` |
| androidx.room:room-runtime | `2.7.2` | `2.8.0` |
| androidx.room:room-testing | `2.7.2` | `2.8.0` |


Updates `androidx.activity:activity-compose` from 1.10.1 to 1.11.0

Updates `androidx.hilt:hilt-compiler` from 1.2.0 to 1.3.0

Updates `androidx.hilt:hilt-navigation-compose` from 1.2.0 to 1.3.0

Updates `androidx.hilt:hilt-work` from 1.2.0 to 1.3.0

Updates `androidx.hilt:hilt-navigation-compose` from 1.2.0 to 1.3.0

Updates `androidx.hilt:hilt-work` from 1.2.0 to 1.3.0

Updates `androidx.work:work-runtime-ktx` from 2.10.3 to 2.10.4

Updates `androidx.work:work-testing` from 2.10.3 to 2.10.4

Updates `androidx.work:work-testing` from 2.10.3 to 2.10.4

Updates `androidx.compose:compose-bom` from 2025.08.01 to 2025.09.00

Updates `androidx.room:room-ktx` from 2.7.2 to 2.8.0

Updates `androidx.room:room-compiler` from 2.7.2 to 2.8.0

Updates `androidx.room:room-paging` from 2.7.2 to 2.8.0

Updates `androidx.room:room-runtime` from 2.7.2 to 2.8.0

Updates `androidx.room:room-testing` from 2.7.2 to 2.8.0

Updates `androidx.room:room-compiler` from 2.7.2 to 2.8.0

Updates `androidx.room:room-paging` from 2.7.2 to 2.8.0

Updates `androidx.room:room-runtime` from 2.7.2 to 2.8.0

Updates `androidx.room:room-testing` from 2.7.2 to 2.8.0

---
updated-dependencies:
- dependency-name: androidx.activity:activity-compose
  dependency-version: 1.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.hilt:hilt-compiler
  dependency-version: 1.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.hilt:hilt-navigation-compose
  dependency-version: 1.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.hilt:hilt-work
  dependency-version: 1.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.hilt:hilt-navigation-compose
  dependency-version: 1.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.hilt:hilt-work
  dependency-version: 1.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.work:work-runtime-ktx
  dependency-version: 2.10.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.work:work-testing
  dependency-version: 2.10.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.work:work-testing
  dependency-version: 2.10.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.compose:compose-bom
  dependency-version: 2025.09.00
  dependency-type: direct:production
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-ktx
  dependency-version: 2.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-compiler
  dependency-version: 2.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-paging
  dependency-version: 2.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-runtime
  dependency-version: 2.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-testing
  dependency-version: 2.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-compiler
  dependency-version: 2.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-paging
  dependency-version: 2.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-runtime
  dependency-version: 2.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-testing
  dependency-version: 2.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-12 10:35:17 +02:00
Ricki Hirner
0424999225 Update dav4jvm (#1709)
- Replace `code` with `statusCode` in multiple files to align with the updated library
- Add `dav4jvm` to Dependabot ignores
2025-09-12 10:25:26 +02:00
Ricki Hirner
fa09a0560f Use SensitiveString for passwords (#1692)
* Use SensitiveString for passwords to prevent them from being logged by `toString()`

* Add test

* Fix other tests

* Credentials: equals / hashCode not needed anymore

* Add tests for equals
2025-09-10 10:31:24 +02:00
Ricki Hirner
f4aa55d482 Version bump to 4.5.4 2025-09-09 11:38:10 +02:00
Ricki Hirner
1bffd5efe1 Version bump to 4.5.4-rc.3 2025-09-06 13:14:48 +02:00
Ricki Hirner
4850f2a5a5 Update synctools to fix #1701 (#1702) 2025-09-06 13:01:56 +02:00
Ricki Hirner
53f38ce2ec Rename Dependabot dependency group 2025-09-05 11:27:06 +02:00
Ricki Hirner
7cf6e30577 Bump version to 4.5.4-rc.2 2025-09-05 10:31:21 +02:00
Ricki Hirner
cec77c33cb Fix KotlinNotImplementedError in LocalGroup update method (#1696)
- Remove scheduleTag assignment in LocalGroup update method
- Refactor LocalGroupTest to use @Before and @After annotations
- Add new test case for update method in LocalGroupTest
2025-09-05 10:27:10 +02:00
dependabot[bot]
6c98e6d501 Bump the lib-dependencies group across 1 directory with 9 updates (#1695)
Bumps the lib-dependencies group with 9 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| androidx.lifecycle:lifecycle-runtime-compose | `2.9.2` | `2.9.3` |
| androidx.lifecycle:lifecycle-viewmodel-ktx | `2.9.2` | `2.9.3` |
| androidx.lifecycle:lifecycle-viewmodel-compose | `2.9.2` | `2.9.3` |
| androidx.compose:compose-bom | `2025.08.00` | `2025.08.01` |
| [com.google.dagger:hilt-android](https://github.com/google/dagger) | `2.57` | `2.57.1` |
| [com.google.dagger:hilt-android-compiler](https://github.com/google/dagger) | `2.57` | `2.57.1` |
| [com.google.dagger:hilt-android-testing](https://github.com/google/dagger) | `2.57` | `2.57.1` |
| [com.google.dagger.hilt.android](https://github.com/google/dagger) | `2.57` | `2.57.1` |
| com.android.application | `8.12.1` | `8.13.0` |



Updates `androidx.lifecycle:lifecycle-runtime-compose` from 2.9.2 to 2.9.3

Updates `androidx.lifecycle:lifecycle-viewmodel-ktx` from 2.9.2 to 2.9.3

Updates `androidx.lifecycle:lifecycle-viewmodel-compose` from 2.9.2 to 2.9.3

Updates `androidx.lifecycle:lifecycle-viewmodel-ktx` from 2.9.2 to 2.9.3

Updates `androidx.lifecycle:lifecycle-viewmodel-compose` from 2.9.2 to 2.9.3

Updates `androidx.compose:compose-bom` from 2025.08.00 to 2025.08.01

Updates `com.google.dagger:hilt-android` from 2.57 to 2.57.1
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57...dagger-2.57.1)

Updates `com.google.dagger:hilt-android-compiler` from 2.57 to 2.57.1
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57...dagger-2.57.1)

Updates `com.google.dagger:hilt-android-testing` from 2.57 to 2.57.1
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57...dagger-2.57.1)

Updates `com.google.dagger.hilt.android` from 2.57 to 2.57.1
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57...dagger-2.57.1)

Updates `com.google.dagger:hilt-android-compiler` from 2.57 to 2.57.1
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57...dagger-2.57.1)

Updates `com.google.dagger:hilt-android-testing` from 2.57 to 2.57.1
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57...dagger-2.57.1)

Updates `com.android.application` from 8.12.1 to 8.13.0

Updates `com.google.dagger.hilt.android` from 2.57 to 2.57.1
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57...dagger-2.57.1)

---
updated-dependencies:
- dependency-name: androidx.lifecycle:lifecycle-runtime-compose
  dependency-version: 2.9.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: lib-dependencies
- dependency-name: androidx.lifecycle:lifecycle-viewmodel-ktx
  dependency-version: 2.9.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: lib-dependencies
- dependency-name: androidx.lifecycle:lifecycle-viewmodel-compose
  dependency-version: 2.9.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: lib-dependencies
- dependency-name: androidx.lifecycle:lifecycle-viewmodel-ktx
  dependency-version: 2.9.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: lib-dependencies
- dependency-name: androidx.lifecycle:lifecycle-viewmodel-compose
  dependency-version: 2.9.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: lib-dependencies
- dependency-name: androidx.compose:compose-bom
  dependency-version: 2025.08.01
  dependency-type: direct:production
  dependency-group: lib-dependencies
- dependency-name: com.google.dagger:hilt-android
  dependency-version: 2.57.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: lib-dependencies
- dependency-name: com.google.dagger:hilt-android-compiler
  dependency-version: 2.57.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: lib-dependencies
- dependency-name: com.google.dagger:hilt-android-testing
  dependency-version: 2.57.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: lib-dependencies
- dependency-name: com.google.dagger.hilt.android
  dependency-version: 2.57.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: lib-dependencies
- dependency-name: com.google.dagger:hilt-android-compiler
  dependency-version: 2.57.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: lib-dependencies
- dependency-name: com.google.dagger:hilt-android-testing
  dependency-version: 2.57.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: lib-dependencies
- dependency-name: com.android.application
  dependency-version: 8.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: lib-dependencies
- dependency-name: com.google.dagger.hilt.android
  dependency-version: 2.57.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: lib-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-05 10:25:31 +02:00
Ricki Hirner
a7cd1cd49f Configure Dependabot for Gradle dependencies (#1680)
* Configure Dependabot for Gradle dependencies

Add Gradle dependency management configuration to Dependabot.

* Update dependabot.yml to ignore specific Kotlin and KSP dependencies
2025-09-05 09:26:30 +02:00
586 changed files with 10873 additions and 7531 deletions

8
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1,8 @@
# See https://docs.github.com/de/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
# For combination with "Require review from code owners" for main-ose branch.
# Dependabot
gradle/** @bitfireAT/app-dev
# everything else
* @rfc2822

View File

@@ -9,6 +9,24 @@ updates:
interval: "weekly"
commit-message:
prefix: "[CI] "
labels:
- "github_actions"
- "dependencies"
groups:
ci-actions:
patterns: ["*"]
- package-ecosystem: "gradle"
directory: "/"
schedule:
interval: "weekly"
labels: # don't create "java" label (default for gradle ecosystem)
- "dependencies"
groups:
app-dependencies:
patterns: ["*"]
ignore:
# dependencies without semantic versioning
- dependency-name: "com.github.bitfireat:cert4android"
- dependency-name: "com.github.bitfireat:dav4jvm"
- dependency-name: "com.github.bitfireat:synctools"

View File

@@ -8,6 +8,7 @@ on:
branches: [ main-ose ]
schedule:
- cron: '22 10 * * 1'
concurrency:
group: codeql-${{ github.ref }}
cancel-in-progress: true
@@ -21,38 +22,29 @@ jobs:
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'java' ]
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
- uses: gradle/actions/setup-gradle@v4
- uses: gradle/actions/setup-gradle@v5
with:
cache-encryption-key: ${{ secrets.gradle_encryption_key }}
cache-read-only: true # gradle user home cache is generated by test jobs
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
languages: java-kotlin
build-mode: manual # autobuild uses older JDK
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
#- name: Autobuild
# uses: github/codeql-action/autobuild@v2
- name: Build
run: ./gradlew --build-cache --configuration-cache --no-daemon app:assembleOseDebug
- name: Build # we must not use build cache here
run: ./gradlew --no-daemon --configuration-cache app:assembleDebug
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v4
with:
category: "/language:${{matrix.language}}"

View File

@@ -0,0 +1,24 @@
name: Dependency Submission
on:
push:
branches: [ 'main-ose' ]
permissions:
contents: write
jobs:
dependency-submission:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
- name: Generate and submit dependency graph
uses: gradle/actions/dependency-submission@v5
with:
cache-encryption-key: ${{ secrets.gradle_encryption_key }}
dependency-graph-exclude-configurations: '.*[Tt]est.* .*[cC]heck.*'

View File

@@ -1,55 +0,0 @@
name: Dependent Issues
on:
issues:
types:
- opened
- edited
- closed
- reopened
pull_request_target:
types:
- opened
- edited
- closed
- reopened
# Makes sure we always add status check for PRs. Useful only if
# this action is required to pass before merging. Otherwise, it
# can be removed.
- synchronize
# Schedule a daily check. Useful if you reference cross-repository
# issues or pull requests. Otherwise, it can be removed.
schedule:
- cron: '19 9 * * *'
permissions: write-all
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: z0al/dependent-issues@v1
env:
# (Required) The token to use to make API calls to GitHub.
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# (Optional) The token to use to make API calls to GitHub for remote repos.
GITHUB_READ_TOKEN: ${{ secrets.DEPENDENT_ISSUES_READ_TOKEN }}
with:
# (Optional) The label to use to mark dependent issues
# label: dependent
# (Optional) Enable checking for dependencies in issues.
# Enable by setting the value to "on". Default "off"
check_issues: on
# (Optional) A comma-separated list of keywords. Default
# "depends on, blocked by"
keywords: depends on, blocked by
# (Optional) A custom comment body. It supports `{{ dependencies }}` token.
comment: >
This PR/issue depends on:
{{ dependencies }}

View File

@@ -19,12 +19,12 @@ jobs:
discussions: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
- uses: gradle/actions/setup-gradle@v4
- uses: gradle/actions/setup-gradle@v5
- name: Prepare keystore
run: echo ${{ secrets.android_keystore_base64 }} | base64 -d >$GITHUB_WORKSPACE/keystore.jks

View File

@@ -9,74 +9,128 @@ concurrency:
group: test-dev-${{ github.ref }}
cancel-in-progress: true
# We provide a remote gradle build cache. Take the settings from the secrets and enable
# configuration and build cache for all gradle jobs.
#
# Note: The secrets are not available for forks and Dependabot PRs.
env:
GRADLE_BUILDCACHE_URL: ${{ secrets.gradle_buildcache_url }}
GRADLE_BUILDCACHE_USERNAME: ${{ secrets.gradle_buildcache_username }}
GRADLE_BUILDCACHE_PASSWORD: ${{ secrets.gradle_buildcache_password }}
GRADLE_OPTS: -Dorg.gradle.caching=true -Dorg.gradle.configuration-cache=true
jobs:
compile:
name: Compile for build cache
if: ${{ github.ref == 'refs/heads/main-ose' }}
name: Compile
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
# See https://community.gradle.org/github-actions/docs/setup-gradle/ for more information
- uses: gradle/actions/setup-gradle@v4 # creates build cache when on main branch
- uses: gradle/actions/setup-gradle@v5
with:
cache-encryption-key: ${{ secrets.gradle_encryption_key }}
dependency-graph: generate-and-submit # submit Github Dependency Graph info
cache-read-only: false # allow branches to update their configuration cache
gradle-home-cache-excludes: caches/build-cache-1 # don't cache local build cache because we use a remote cache
- run: ./gradlew --build-cache --configuration-cache app:compileOseDebugSource
- name: Cache Android environment
uses: actions/cache@v5
with:
path: ~/.config/.android # needs to be cached so that configuration cache can work
key: android-${{ hashFiles('app/build.gradle.kts') }}
test:
- name: Compile
run: ./gradlew app:compileOseDebugSource
# Cache configurations for the other jobs (including assemble for CodeQL)
- name: Populate configuration cache
run: |
./gradlew --dry-run core:assembleDebug app:assembleDebug
./gradlew --dry-run core:lintDebug app:lintOseDebug
./gradlew --dry-run core:testDebugUnitTest app:testOseDebugUnitTest
./gradlew --dry-run core:virtualDebugAndroidTest app:virtualOseDebugAndroidTest
unit_tests:
needs: compile
if: ${{ !cancelled() }} # even if compile didn't run (because not on main branch)
name: Lint and unit tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
- uses: gradle/actions/setup-gradle@v4
- uses: gradle/actions/setup-gradle@v5
with:
cache-encryption-key: ${{ secrets.gradle_encryption_key }}
cache-read-only: true
- name: Run lint
run: ./gradlew --build-cache --configuration-cache app:lintOseDebug
- name: Run unit tests
run: ./gradlew --build-cache --configuration-cache app:testOseDebugUnitTest
- name: Restore Android environment
uses: actions/cache/restore@v5
with:
path: ~/.config/.android
key: android-${{ hashFiles('app/build.gradle.kts') }}
test_on_emulator:
- name: Lint checks
run: ./gradlew core:lintDebug app:lintOseDebug
- name: Unit tests
run: ./gradlew core:testDebugUnitTest app:testOseDebugUnitTest
instrumented_tests:
needs: compile
if: ${{ !cancelled() }} # even if compile didn't run (because not on main branch)
name: Instrumented tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
- uses: gradle/actions/setup-gradle@v4
- uses: gradle/actions/setup-gradle@v5
with:
cache-encryption-key: ${{ secrets.gradle_encryption_key }}
cache-read-only: true
- name: Restore Android environment
uses: actions/cache/restore@v5
with:
path: ~/.config/.android
key: android-${{ hashFiles('app/build.gradle.kts') }}
# gradle and Android SDK often take more space than what is available on the default runner.
# We try to free a few GB here to make gradle-managed devices more reliable.
- name: Free some disk space
uses: jlumbroso/free-disk-space@main
with:
android: false # we need the Android SDK
large-packages: false # apt takes too long
swap-storage: false # gradle needs much memory
- name: Restore AVD
id: restore-avd
uses: actions/cache/restore@v5
with:
path: ~/.config/.android/avd # where AVD is stored
key: avd-${{ hashFiles('app/build.gradle.kts') }} # gradle-managed devices are defined there
# Enable virtualization for Android emulator
- name: Enable KVM group perms
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Cache AVD
uses: actions/cache@v4
with:
path: ~/.config/.android/avd
key: avd-${{ hashFiles('app/build.gradle.kts') }} # gradle-managed devices are defined there
- name: Instrumented tests
run: ./gradlew core:virtualDebugAndroidTest app:virtualOseDebugAndroidTest
- name: Run device tests
run: ./gradlew --build-cache --configuration-cache app:virtualCheck
- name: Cache AVD
uses: actions/cache/save@v5
if: steps.restore-avd.outputs.cache-hit != 'true'
with:
path: ~/.config/.android/avd # where AVD is stored
key: avd-${{ hashFiles('app/build.gradle.kts') }} # gradle-managed devices are defined there

4
.gitignore vendored
View File

@@ -16,10 +16,6 @@
bin/
gen/
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties

View File

@@ -1,30 +0,0 @@
[main]
host = https://www.transifex.com
lang_map = ar_SA: ar, en_GB: en-rGB, fa_IR: fa-rIR, fi_FI: fi, nb_NO: nb, sk_SK: sk, sl_SI: sl, tr_TR: tr, zh_CN: zh, zh_TW: zh-rTW
[o:bitfireAT:p:davx5:r:app]
file_filter = app/src/main/res/values-<lang>/strings.xml
source_file = app/src/main/res/values/strings.xml
source_lang = en
type = ANDROID
minimum_perc = 20
resource_name = App strings (all flavors)
# Attention: fastlane directories are like "en-us", not "en-rUS"!
[o:bitfireAT:p:davx5:r:metadata-short-description]
file_filter = fastlane/metadata/android/<lang>/short_description.txt
source_file = fastlane/metadata/android/en-US/short_description.txt
source_lang = en
type = TXT
minimum_perc = 100
resource_name = Metadata: short description
[o:bitfireAT:p:davx5:r:metadata-full-description]
file_filter = fastlane/metadata/android/<lang>/full_description.txt
source_file = fastlane/metadata/android/en-US/full_description.txt
source_lang = en
type = TXT
minimum_perc = 100
resource_name = Metadata: full description

View File

@@ -14,24 +14,11 @@ If you send us a pull request, our CLA bot will ask you to sign the
Contributor's License Agreement so that we can use your contribution.
# Copyright
# Copyright notice
Make sure that every file that contains significant work (at least every code file)
starts with the copyright header:
```
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
```
You can set this in Android Studio:
1. Settings / Editor / Copyright / Copyright Profiles
2. Paste the text above (without the stars).
3. Set Formatting so that the preview exactly looks like above; one blank line after the block.
4. Set this copyright profile as the default profile for the project.
5. Apply copyright: right-click in file tree / Update copyright.
starts with the copyright header. Android Studio should do so automatically because the
configuration is stored in the repository (`.idea/copyright`).
# Style guide
@@ -110,8 +97,3 @@ Test classes should be in the appropriate directory (see existing tests) and in
tested class. Tests are usually be named like `methodToBeTested_Condition()`, see
[Test apps on Android](https://developer.android.com/training/testing/).
# Authors
If you make significant contributions, feel free to add yourself to the [AUTHORS file](AUTHORS).

View File

@@ -1,9 +1,9 @@
[![Website](https://img.shields.io/website?style=flat-square&up_color=%237cb342&url=https%3A%2F%2Fwww.davx5.com)](https://www.davx5.com/)
[![F-Droid](https://img.shields.io/f-droid/v/at.bitfire.davdroid?style=flat-square)](https://f-droid.org/packages/at.bitfire.davdroid/)
[![License](https://img.shields.io/github/license/bitfireAT/davx5-ose?style=flat-square)](https://github.com/bitfireAT/davx5-ose/blob/main/LICENSE)
[![Follow @davx5app@fosstodon.org](https://img.shields.io/mastodon/follow/109598783742737223?domain=https%3A%2F%2Ffosstodon.org&style=flat-square)](https://fosstodon.org/@davx5app)
[![Development tests](https://github.com/bitfireAT/davx5-ose/actions/workflows/test-dev.yml/badge.svg)](https://github.com/bitfireAT/davx5-ose/actions/workflows/test-dev.yml)
[![Website](https://img.shields.io/website?style=flat-square&up_color=%237cb342&url=https%3A%2F%2Fwww.davx5.com)](https://www.davx5.com/)
[![License](https://img.shields.io/github/license/bitfireAT/davx5-ose?style=flat-square)](https://github.com/bitfireAT/davx5-ose/blob/main/LICENSE)
[![F-Droid](https://img.shields.io/f-droid/v/at.bitfire.davdroid?style=flat-square)](https://f-droid.org/packages/at.bitfire.davdroid/)
![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/bitfireAT/davx5-ose/total?label=GitHub%20downloads)
![DAVx⁵ logo](app/src/main/res/mipmap-xxxhdpi/ic_launcher.png)
@@ -11,8 +11,10 @@
DAVx⁵
========
Please see the [DAVx⁵ Web site](https://www.davx5.com) for
comprehensive information about DAVx⁵, including a list of services it has been tested with.
> [!IMPORTANT]
> Please see the [DAVx⁵ Web site](https://www.davx5.com) for
> comprehensive information about DAVx⁵, including a list of services it has been tested with,
> a manual and FAQ.
DAVx⁵ is licensed under the [GPLv3 License](LICENSE).

132
app-ose/build.gradle.kts Normal file
View File

@@ -0,0 +1,132 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.compose.compiler)
alias(libs.plugins.hilt)
alias(libs.plugins.ksp)
}
android {
compileSdk = 36
defaultConfig {
minSdk = 24 // Android 7.0
targetSdk = 36 // Android 16
applicationId = "at.bitfire.davdroid"
versionCode = 405090005
versionName = "4.5.9"
//base.archivesName = "davx5-$versionCode-$versionName"
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
compileOptions {
isCoreLibraryDesugaringEnabled = true
}
buildFeatures {
compose = true
}
// Java namespace for our classes (not to be confused with Android package ID)
namespace = "com.davx5.ose"
flavorDimensions += "distribution"
productFlavors {
create("ose") {
dimension = "distribution"
versionNameSuffix = "-ose"
}
}
sourceSets {
getByName("androidTest") {
assets.srcDir("$projectDir/schemas")
}
}
androidResources {
generateLocaleConfig = true
}
buildTypes {
getByName("release") {
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules-release.pro")
isShrinkResources = true
signingConfig = signingConfigs.findByName("bitfire")
}
}
signingConfigs {
create("bitfire") {
storeFile = file(System.getenv("ANDROID_KEYSTORE") ?: "/dev/null")
storePassword = System.getenv("ANDROID_KEYSTORE_PASSWORD")
keyAlias = System.getenv("ANDROID_KEY_ALIAS")
keyPassword = System.getenv("ANDROID_KEY_PASSWORD")
}
}
@Suppress("UnstableApiUsage")
testOptions {
managedDevices {
localDevices {
create("virtual") {
device = "Pixel 3"
// TBD: API level 35 and higher causes network tests to fail sometimes, see https://github.com/bitfireAT/davx5-ose/issues/1525
// Suspected reason: https://developer.android.com/about/versions/15/behavior-changes-all#background-network-access
apiLevel = 34
systemImageSource = "aosp-atd"
}
}
}
}
}
dependencies {
// include core module
implementation(project(":core"))
// Kotlin / Android
implementation(libs.kotlin.stdlib)
implementation(libs.kotlinx.coroutines)
coreLibraryDesugaring(libs.android.desugaring)
// Hilt
implementation(libs.hilt.android.base)
ksp(libs.androidx.hilt.compiler)
ksp(libs.hilt.android.compiler)
// support libs
implementation(libs.androidx.core)
implementation(libs.androidx.hilt.work)
implementation(libs.androidx.lifecycle.viewmodel.base)
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.androidx.work.base)
// Jetpack Compose
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.material3)
debugImplementation(libs.androidx.compose.ui.tooling)
implementation(libs.androidx.compose.ui.toolingPreview)
// own libraries
implementation(libs.bitfire.cert4android)
// third-party libs
implementation(libs.guava)
implementation(libs.okhttp.base)
implementation(libs.openid.appauth)
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:installLocation="internalOnly">
<application android:name=".App"/>
</manifest>

View File

@@ -2,18 +2,19 @@
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid
package com.davx5.ose
import android.app.Application
import androidx.hilt.work.HiltWorkerFactory
import androidx.work.Configuration
import at.bitfire.davdroid.di.scope.DefaultDispatcher
import at.bitfire.davdroid.log.LogManager
import at.bitfire.davdroid.startup.StartupPlugin
import at.bitfire.davdroid.sync.account.AccountsCleanupWorker
import at.bitfire.davdroid.ui.UiUtils
import dagger.hilt.android.HiltAndroidApp
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.util.logging.Logger
@@ -26,11 +27,15 @@ class App: Application(), Configuration.Provider {
lateinit var logger: Logger
/**
* Creates the [LogManager] singleton and thus initializes logging.
* Creates the [at.bitfire.davdroid.log.LogManager] singleton and thus initializes logging.
*/
@Inject
lateinit var logManager: LogManager
@Inject
@DefaultDispatcher
lateinit var defaultDispatcher: CoroutineDispatcher
@Inject
lateinit var plugins: Set<@JvmSuppressWildcards StartupPlugin>
@@ -60,9 +65,9 @@ class App: Application(), Configuration.Provider {
// don't block UI for some background checks
@OptIn(DelicateCoroutinesApi::class)
GlobalScope.launch(Dispatchers.Default) {
GlobalScope.launch(defaultDispatcher) {
// clean up orphaned accounts in DB from time to time
AccountsCleanupWorker.enable(this@App)
AccountsCleanupWorker.Companion.enable(this@App)
// create/update app shortcuts
UiUtils.updateShortcuts(this@App)

View File

@@ -1,8 +1,8 @@
/***************************************************************************************************
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
**************************************************************************************************/
*/
package at.bitfire.davdroid
package com.davx5.ose
import android.content.Context
import at.bitfire.davdroid.ui.DebugInfoActivity

View File

@@ -0,0 +1,61 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package com.davx5.ose.di
import android.content.Context
import at.bitfire.cert4android.CustomCertManager
import at.bitfire.cert4android.CustomCertStore
import at.bitfire.cert4android.SettingsProvider
import at.bitfire.davdroid.settings.Settings
import at.bitfire.davdroid.settings.SettingsManager
import at.bitfire.davdroid.ui.ForegroundTracker
import dagger.Module
import dagger.Provides
import dagger.Reusable
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import okhttp3.internal.tls.OkHostnameVerifier
import java.util.Optional
/**
* cert4android integration module
*/
@Module
@InstallIn(SingletonComponent::class)
class CustomCertManagerModule {
@Provides
fun customCertStore(@ApplicationContext context: Context): Optional<CustomCertStore> =
Optional.of(CustomCertStore.getInstance(context))
@Provides
@Reusable
fun customCertManager(
customCertStore: Optional<CustomCertStore>,
settings: SettingsManager
): Optional<CustomCertManager> =
Optional.of(
CustomCertManager(
certStore = customCertStore.get(),
settings = object : SettingsProvider {
override val appInForeground: Boolean
get() = ForegroundTracker.inForeground.value
override val trustSystemCerts: Boolean
get() = !settings.getBoolean(Settings.DISTRUST_SYSTEM_CERTIFICATES)
}
))
@Provides
@Reusable
fun customHostnameVerifier(
customCertManager: Optional<CustomCertManager>
): Optional<CustomCertManager.HostnameVerifier> =
Optional.of(customCertManager.get().HostnameVerifier(OkHostnameVerifier))
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package com.davx5.ose.di
import androidx.compose.material3.ColorScheme
import at.bitfire.davdroid.di.scope.DarkColorScheme
import at.bitfire.davdroid.di.scope.LightColorScheme
import com.davx5.ose.ui.OseTheme
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
class OseColorSchemesModule {
@Provides
@LightColorScheme
fun lightColorScheme(): ColorScheme = OseTheme.lightScheme
@Provides
@DarkColorScheme
fun darkColorScheme(): ColorScheme = OseTheme.darkScheme
}

View File

@@ -2,17 +2,16 @@
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.di
package com.davx5.ose.di
import at.bitfire.davdroid.ui.intro.OseIntroPageFactory
import at.bitfire.davdroid.ui.AboutActivity
import at.bitfire.davdroid.ui.AccountsDrawerHandler
import at.bitfire.davdroid.ui.OpenSourceLicenseInfoProvider
import at.bitfire.davdroid.ui.OseAccountsDrawerHandler
import at.bitfire.davdroid.ui.about.AboutActivity
import at.bitfire.davdroid.ui.intro.IntroPageFactory
import at.bitfire.davdroid.ui.setup.LoginTypesProvider
import at.bitfire.davdroid.ui.setup.StandardLoginTypesProvider
import com.davx5.ose.ui.about.OpenSourceLicenseInfoProvider
import com.davx5.ose.ui.intro.OseIntroPageFactory
import com.davx5.ose.ui.setup.StandardLoginTypesProvider
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn

View File

@@ -2,14 +2,13 @@
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.ui
package com.davx5.ose.ui
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.ui.graphics.Color
@Suppress("MemberVisibilityCanBePrivate")
object M3ColorScheme {
object OseTheme {
// All colors hand-crafted because Material Theme Builder generates unbelievably ugly colors

View File

@@ -2,9 +2,9 @@
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.ui
package com.davx5.ose.ui.about
import android.app.Application
import android.content.Context
import android.text.Spanned
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -14,15 +14,18 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.tooling.preview.Preview
import androidx.core.text.HtmlCompat
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
import at.bitfire.davdroid.di.scope.IoDispatcher
import at.bitfire.davdroid.ui.UiUtils.toAnnotatedString
import at.bitfire.davdroid.ui.about.AboutActivity
import com.google.common.io.CharStreams
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.launch
import javax.inject.Inject
class OpenSourceLicenseInfoProvider @Inject constructor(): AboutActivity.AppLicenseInfoProvider {
@@ -40,13 +43,16 @@ class OpenSourceLicenseInfoProvider @Inject constructor(): AboutActivity.AppLice
@HiltViewModel
class Model @Inject constructor(app: Application): AndroidViewModel(app) {
class Model @Inject constructor(
@ApplicationContext private val context: Context,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher
): ViewModel() {
var gpl by mutableStateOf<Spanned?>(null)
init {
viewModelScope.launch(Dispatchers.IO) {
app.resources.assets.open("gplv3.html").use { inputStream ->
viewModelScope.launch(ioDispatcher) {
context.resources.assets.open("gplv3.html").use { inputStream ->
val raw = CharStreams.toString(inputStream.bufferedReader())
gpl = HtmlCompat.fromHtml(raw, HtmlCompat.FROM_HTML_MODE_LEGACY)
}

View File

@@ -2,11 +2,19 @@
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.ui.intro
package com.davx5.ose.ui.intro
import at.bitfire.davdroid.ui.intro.BackupsPage
import at.bitfire.davdroid.ui.intro.BatteryOptimizationsPage
import at.bitfire.davdroid.ui.intro.IntroPageFactory
import at.bitfire.davdroid.ui.intro.OpenSourcePage
import at.bitfire.davdroid.ui.intro.PermissionsIntroPage
import at.bitfire.davdroid.ui.intro.TasksIntroPage
import at.bitfire.davdroid.ui.intro.WelcomePage
import javax.inject.Inject
class OseIntroPageFactory @Inject constructor(
backupsPage: BackupsPage,
batteryOptimizationsPage: BatteryOptimizationsPage,
openSourcePage: OpenSourcePage,
permissionsIntroPage: PermissionsIntroPage,
@@ -18,6 +26,7 @@ class OseIntroPageFactory @Inject constructor(
tasksIntroPage,
permissionsIntroPage,
batteryOptimizationsPage,
backupsPage,
openSourcePage
)

View File

@@ -2,7 +2,7 @@
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.ui.setup
package com.davx5.ose.ui.setup
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
@@ -15,6 +15,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@@ -24,6 +25,8 @@ import at.bitfire.davdroid.ui.ExternalUris
import at.bitfire.davdroid.ui.ExternalUris.withStatParams
import at.bitfire.davdroid.ui.UiUtils.toAnnotatedString
import at.bitfire.davdroid.ui.composable.Assistant
import at.bitfire.davdroid.ui.setup.LoginInfo
import at.bitfire.davdroid.ui.setup.LoginType
@Composable
fun StandardLoginTypePage(
@@ -67,9 +70,10 @@ fun StandardLoginTypePage(
HorizontalDivider(Modifier.padding(vertical = 12.dp))
val context = LocalContext.current
val privacyPolicy = ExternalUris.Homepage.baseUrl.buildUpon()
.appendPath(ExternalUris.Homepage.PATH_PRIVACY)
.withStatParams("StandardLoginTypePage")
.withStatParams(context, "StandardLoginTypePage")
.build().toString()
val privacy = HtmlCompat.fromHtml(
stringResource(R.string.login_privacy_hint, stringResource(R.string.app_name), privacyPolicy),

View File

@@ -2,12 +2,22 @@
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.ui.setup
package com.davx5.ose.ui.setup
import android.content.Intent
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import at.bitfire.davdroid.ui.setup.AdvancedLogin
import at.bitfire.davdroid.ui.setup.EmailLogin
import at.bitfire.davdroid.ui.setup.FastmailLogin
import at.bitfire.davdroid.ui.setup.GoogleLogin
import at.bitfire.davdroid.ui.setup.LoginActivity
import at.bitfire.davdroid.ui.setup.LoginInfo
import at.bitfire.davdroid.ui.setup.LoginType
import at.bitfire.davdroid.ui.setup.LoginTypesProvider
import at.bitfire.davdroid.ui.setup.LoginTypesProvider.LoginAction
import at.bitfire.davdroid.ui.setup.NextcloudLogin
import at.bitfire.davdroid.ui.setup.UrlLogin
import java.util.logging.Logger
import javax.inject.Inject

1
app/src/.gitignore vendored
View File

@@ -1 +0,0 @@
espressoTest

View File

@@ -1,265 +0,0 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.resource
import android.Manifest
import android.accounts.Account
import android.content.ContentProviderClient
import android.content.ContentUris
import android.content.ContentValues
import android.provider.CalendarContract
import android.provider.CalendarContract.ACCOUNT_TYPE_LOCAL
import android.provider.CalendarContract.Events
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.GrantPermissionRule
import at.bitfire.ical4android.Event
import at.bitfire.ical4android.util.MiscUtils.closeCompat
import at.bitfire.synctools.storage.calendar.AndroidCalendarProvider
import at.techbee.jtx.JtxContract.asSyncAdapter
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import net.fortuna.ical4j.model.property.DtStart
import net.fortuna.ical4j.model.property.RRule
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.Before
import org.junit.Rule
import org.junit.Test
import java.util.UUID
import javax.inject.Inject
@HiltAndroidTest
class LocalEventTest {
@get:Rule
val hiltRule = HiltAndroidRule(this)
@get:Rule
val permissionRule = GrantPermissionRule.grant(Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR)
@Inject
lateinit var localCalendarFactory: LocalCalendar.Factory
private val account = Account("LocalCalendarTest", ACCOUNT_TYPE_LOCAL)
private lateinit var client: ContentProviderClient
private lateinit var calendar: LocalCalendar
@Before
fun setUp() {
hiltRule.inject()
val context = InstrumentationRegistry.getInstrumentation().targetContext
client = context.contentResolver.acquireContentProviderClient(CalendarContract.AUTHORITY)!!
val provider = AndroidCalendarProvider(account, client)
calendar = localCalendarFactory.create(provider.createAndGetCalendar(ContentValues()))
}
@After
fun tearDown() {
calendar.androidCalendar.delete()
client.closeCompat()
}
@Test
fun testPrepareForUpload_NoUid() {
// create event
val event = Event().apply {
dtStart = DtStart("20220120T010203Z")
summary = "Event without uid"
}
calendar.add(
event = event,
fileName = "filename.ics",
eTag = null,
scheduleTag = null,
flags = LocalResource.FLAG_REMOTELY_PRESENT
)
val localEvent = calendar.findByName("filename.ics")!!
// prepare for upload - this should generate a new random uuid, returned as filename
val fileNameWithSuffix = localEvent.prepareForUpload()
val fileName = fileNameWithSuffix.removeSuffix(".ics")
// throws an exception if fileName is not an UUID
UUID.fromString(fileName)
// UID in calendar storage should be the same as file name
client.query(
ContentUris.withAppendedId(Events.CONTENT_URI, localEvent.id!!).asSyncAdapter(account),
arrayOf(Events.UID_2445), null, null, null
)!!.use { cursor ->
cursor.moveToFirst()
assertEquals(fileName, cursor.getString(0))
}
}
@Test
fun testPrepareForUpload_NormalUid() {
// create event
val event = Event().apply {
dtStart = DtStart("20220120T010203Z")
summary = "Event with normal uid"
uid = "some-event@hostname.tld" // old UID format, UUID would be new format
}
calendar.add(
event = event,
fileName = "filename.ics",
eTag = null,
scheduleTag = null,
flags = LocalResource.FLAG_REMOTELY_PRESENT
)
val localEvent = calendar.findByName("filename.ics")!!
// prepare for upload - this should use the UID for the file name
val fileNameWithSuffix = localEvent.prepareForUpload()
val fileName = fileNameWithSuffix.removeSuffix(".ics")
assertEquals(event.uid, fileName)
// UID in calendar storage should still be set, too
client.query(
ContentUris.withAppendedId(Events.CONTENT_URI, localEvent.id!!).asSyncAdapter(account),
arrayOf(Events.UID_2445), null, null, null
)!!.use { cursor ->
cursor.moveToFirst()
assertEquals(fileName, cursor.getString(0))
}
}
@Test
fun testPrepareForUpload_UidHasDangerousChars() {
// create event
val event = Event().apply {
dtStart = DtStart("20220120T010203Z")
summary = "Event with funny uid"
uid = "https://www.example.com/events/asdfewfe-cxyb-ewrws-sadfrwerxyvser-asdfxye-"
}
calendar.add(
event = event,
fileName = "filename.ics",
eTag = null,
scheduleTag = null,
flags = LocalResource.FLAG_REMOTELY_PRESENT
)
val localEvent = calendar.findByName("filename.ics")!!
// prepare for upload - this should generate a new random uuid, returned as filename
val fileNameWithSuffix = localEvent.prepareForUpload()
val fileName = fileNameWithSuffix.removeSuffix(".ics")
// throws an exception if fileName is not an UUID
UUID.fromString(fileName)
// UID in calendar storage shouldn't have been changed
client.query(
ContentUris.withAppendedId(Events.CONTENT_URI, localEvent.id!!).asSyncAdapter(account),
arrayOf(Events.UID_2445), null, null, null
)!!.use { cursor ->
cursor.moveToFirst()
assertEquals(event.uid, cursor.getString(0))
}
}
@Test
fun testDeleteDirtyEventsWithoutInstances_NoInstances_Exdate() {
// TODO
}
@Test
fun testDeleteDirtyEventsWithoutInstances_NoInstances_CancelledExceptions() {
// create recurring event with only deleted/cancelled instances
val event = Event().apply {
dtStart = DtStart("20220120T010203Z")
summary = "Event with 3 instances"
rRules.add(RRule("FREQ=DAILY;COUNT=3"))
exceptions.add(Event().apply {
recurrenceId = RecurrenceId("20220120T010203Z")
dtStart = DtStart("20220120T010203Z")
summary = "Cancelled exception on 1st day"
status = Status.VEVENT_CANCELLED
})
exceptions.add(Event().apply {
recurrenceId = RecurrenceId("20220121T010203Z")
dtStart = DtStart("20220121T010203Z")
summary = "Cancelled exception on 2nd day"
status = Status.VEVENT_CANCELLED
})
exceptions.add(Event().apply {
recurrenceId = RecurrenceId("20220122T010203Z")
dtStart = DtStart("20220122T010203Z")
summary = "Cancelled exception on 3rd day"
status = Status.VEVENT_CANCELLED
})
}
calendar.add(
event = event,
fileName = "filename.ics",
eTag = null,
scheduleTag = null,
flags = LocalResource.FLAG_REMOTELY_PRESENT
)
val localEvent = calendar.findByName("filename.ics")!!
val eventId = 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)
// this method should mark the event as deleted
calendar.deleteDirtyEventsWithoutInstances()
// verify that event is now marked as deleted
client.query(
ContentUris.withAppendedId(Events.CONTENT_URI.asSyncAdapter(account), eventId),
arrayOf(Events.DELETED), null, null, null
)!!.use { cursor ->
cursor.moveToNext()
assertEquals(1, cursor.getInt(0))
}
}
@Test
fun testDeleteDirtyEventsWithoutInstances_Recurring_Instances() {
val event = Event().apply {
dtStart = DtStart("20220120T010203Z")
summary = "Event with 3 instances"
rRules.add(RRule("FREQ=DAILY;COUNT=3"))
}
calendar.add(
event = event,
fileName = "filename.ics",
eTag = null,
scheduleTag = null,
flags = LocalResource.FLAG_REMOTELY_PRESENT
)
val localEvent = calendar.findByName("filename.ics")!!
val eventId = 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)
// 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 ->
cursor.moveToNext()
assertEquals(0, cursor.getInt(0))
}
}
}

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -1,23 +0,0 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid
import at.bitfire.synctools.icalendar.ical4jVersion
import ezvcard.Ezvcard
import net.fortuna.ical4j.model.property.ProdId
/**
* Brand-specific constants like (non-theme) colors, homepage URLs etc.
*/
object Constants {
const val DAVDROID_GREEN_RGBA = 0xFF8bc34a.toInt()
// product IDs for iCalendar/vCard
val iCalProdId = ProdId("DAVx5/${BuildConfig.VERSION_NAME} ical4j/$ical4jVersion")
const val vCardProdId = "+//IDN bitfire.at//DAVx5/${BuildConfig.VERSION_NAME} ez-vcard/${Ezvcard.VERSION}"
}

View File

@@ -1,47 +0,0 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.network
import android.content.Context
import android.security.KeyChain
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.qualifiers.ApplicationContext
import java.net.Socket
import java.security.Principal
import javax.net.ssl.X509ExtendedKeyManager
/**
* KeyManager that provides a client certificate and private key from the Android KeyChain.
*
* @throws IllegalArgumentException if the alias doesn't exist or is not accessible
*/
class ClientCertKeyManager @AssistedInject constructor(
@Assisted private val alias: String,
@ApplicationContext private val context: Context
): X509ExtendedKeyManager() {
@AssistedFactory
interface Factory {
fun create(alias: String): ClientCertKeyManager
}
val certs = KeyChain.getCertificateChain(context, alias) ?: throw IllegalArgumentException("Alias doesn't exist or not accessible: $alias")
val key = KeyChain.getPrivateKey(context, alias) ?: throw IllegalArgumentException("Alias doesn't exist or not accessible: $alias")
override fun getServerAliases(p0: String?, p1: Array<out Principal>?): Array<String>? = null
override fun chooseServerAlias(p0: String?, p1: Array<out Principal>?, p2: Socket?) = null
override fun getClientAliases(p0: String?, p1: Array<out Principal>?) = arrayOf(alias)
override fun chooseClientAlias(p0: Array<out String>?, p1: Array<out Principal>?, p2: Socket?) = alias
override fun getCertificateChain(forAlias: String?) =
certs.takeIf { forAlias == alias }
override fun getPrivateKey(forAlias: String?) =
key.takeIf { forAlias == alias }
}

View File

@@ -1,315 +0,0 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.network
import android.accounts.Account
import android.content.Context
import androidx.annotation.WorkerThread
import at.bitfire.cert4android.CustomCertManager
import at.bitfire.dav4jvm.BasicDigestAuthHandler
import at.bitfire.dav4jvm.UrlUtils
import at.bitfire.davdroid.BuildConfig
import at.bitfire.davdroid.di.IoDispatcher
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.settings.Credentials
import at.bitfire.davdroid.settings.Settings
import at.bitfire.davdroid.settings.SettingsManager
import at.bitfire.davdroid.ui.ForegroundTracker
import com.google.common.net.HttpHeaders
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
import net.openid.appauth.AuthState
import okhttp3.Authenticator
import okhttp3.Cache
import okhttp3.ConnectionSpec
import okhttp3.CookieJar
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Protocol
import okhttp3.brotli.BrotliInterceptor
import okhttp3.internal.tls.OkHostnameVerifier
import okhttp3.logging.HttpLoggingInterceptor
import java.io.File
import java.net.InetSocketAddress
import java.net.Proxy
import java.util.concurrent.TimeUnit
import java.util.logging.Level
import java.util.logging.Logger
import javax.inject.Inject
import javax.net.ssl.KeyManager
import javax.net.ssl.SSLContext
class HttpClient(
val okHttpClient: OkHttpClient
): AutoCloseable {
override fun close() {
okHttpClient.cache?.close()
}
// builder
/**
* Builder for the [HttpClient].
*
* **Attention:** If the builder is injected, it shouldn't be used from multiple locations to generate different clients because then
* there's only one [Builder] object and setting properties from one location would influence the others.
*
* To generate multiple clients, inject and use `Provider<HttpClient.Builder>` instead.
*/
class Builder @Inject constructor(
private val accountSettingsFactory: AccountSettings.Factory,
@ApplicationContext private val context: Context,
defaultLogger: Logger,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
private val keyManagerFactory: ClientCertKeyManager.Factory,
private val oAuthInterceptorFactory: OAuthInterceptor.Factory,
private val settingsManager: SettingsManager
) {
// property setters/getters
private var logger: Logger = defaultLogger
fun setLogger(logger: Logger): Builder {
this.logger = logger
return this
}
private var loggerInterceptorLevel: HttpLoggingInterceptor.Level = HttpLoggingInterceptor.Level.BODY
fun loggerInterceptorLevel(level: HttpLoggingInterceptor.Level): Builder {
loggerInterceptorLevel = level
return this
}
// default cookie store for non-persistent cookies (some services like Horde use cookies for session tracking)
private var cookieStore: CookieJar = MemoryCookieStore()
fun setCookieStore(cookieStore: CookieJar): Builder {
this.cookieStore = cookieStore
return this
}
private var authenticationInterceptor: Interceptor? = null
private var authenticator: Authenticator? = null
private var certificateAlias: String? = null
fun authenticate(host: String?, getCredentials: () -> Credentials, updateAuthState: ((AuthState) -> Unit)? = null): Builder {
val credentials = getCredentials()
if (credentials.authState != null) {
// OAuth
authenticationInterceptor = oAuthInterceptorFactory.create(
readAuthState = {
// We don't use the "credentials" object from above because it may contain an outdated access token
// when readAuthState is called. Instead, we fetch the up-to-date auth-state.
getCredentials().authState
},
writeAuthState = { authState ->
updateAuthState?.invoke(authState)
}
)
} else if (credentials.username != null && credentials.password != null) {
// basic/digest auth
val authHandler = BasicDigestAuthHandler(
domain = UrlUtils.hostToDomain(host),
username = credentials.username,
password = credentials.password,
insecurePreemptive = true
)
authenticationInterceptor = authHandler
authenticator = authHandler
}
// client certificate
if (credentials.certificateAlias != null)
certificateAlias = credentials.certificateAlias
return this
}
private var followRedirects = false
fun followRedirects(follow: Boolean): Builder {
followRedirects = follow
return this
}
private var cache: Cache? = null
@Suppress("unused")
fun withDiskCache(maxSize: Long = 10*1024*1024): Builder {
for (dir in arrayOf(context.externalCacheDir, context.cacheDir).filterNotNull()) {
if (dir.exists() && dir.canWrite()) {
val cacheDir = File(dir, "HttpClient")
cacheDir.mkdir()
logger.fine("Using disk cache: $cacheDir")
cache = Cache(cacheDir, maxSize)
break
}
}
return this
}
// convenience builders from other classes
/**
* Takes authentication (basic/digest or OAuth and client certificate) from a given account.
*
* **Must not be run on main thread, because it creates [AccountSettings]!** Use [fromAccountAsync] if possible.
*
* @param account the account to take authentication from
* @param onlyHost if set: only authenticate for this host name
*
* @throws at.bitfire.davdroid.sync.account.InvalidAccountException when the account doesn't exist
*/
@WorkerThread
fun fromAccount(account: Account, onlyHost: String? = null): Builder {
val accountSettings = accountSettingsFactory.create(account)
authenticate(
host = onlyHost,
getCredentials = {
accountSettings.credentials()
},
updateAuthState = { authState ->
accountSettings.updateAuthState(authState)
}
)
return this
}
/**
* Same as [fromAccount], but can be called on any thread.
*
* @throws at.bitfire.davdroid.sync.account.InvalidAccountException when the account doesn't exist
*/
suspend fun fromAccountAsync(account: Account, onlyHost: String? = null): Builder = withContext(ioDispatcher) {
fromAccount(account, onlyHost)
}
// actual builder
fun build(): HttpClient {
val okBuilder = OkHttpClient.Builder()
// Set timeouts. According to [AbstractThreadedSyncAdapter], when there is no network
// traffic within a minute, a sync will be cancelled.
.connectTimeout(15, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.readTimeout(120, TimeUnit.SECONDS)
.pingInterval(45, TimeUnit.SECONDS) // avoid cancellation because of missing traffic; only works for HTTP/2
// don't allow redirects by default because it would break PROPFIND handling
.followRedirects(followRedirects)
// add User-Agent to every request
.addInterceptor(UserAgentInterceptor)
// connection-private cookie store
.cookieJar(cookieStore)
// allow cleartext and TLS 1.2+
.connectionSpecs(listOf(
ConnectionSpec.CLEARTEXT,
ConnectionSpec.MODERN_TLS
))
// offer Brotli and gzip compression (can be disabled per request with `Accept-Encoding: identity`)
.addInterceptor(BrotliInterceptor)
// add cache, if requested
.cache(cache)
// app-wide custom proxy support
buildProxy(okBuilder)
// add authentication
buildAuthentication(okBuilder)
// add network logging, if requested
if (logger.isLoggable(Level.FINEST)) {
val loggingInterceptor = HttpLoggingInterceptor { message -> logger.finest(message) }
loggingInterceptor.redactHeader(HttpHeaders.AUTHORIZATION)
loggingInterceptor.redactHeader(HttpHeaders.COOKIE)
loggingInterceptor.redactHeader(HttpHeaders.SET_COOKIE)
loggingInterceptor.redactHeader(HttpHeaders.SET_COOKIE2)
loggingInterceptor.level = loggerInterceptorLevel
okBuilder.addNetworkInterceptor(loggingInterceptor)
}
return HttpClient(okBuilder.build())
}
private fun buildAuthentication(okBuilder: OkHttpClient.Builder) {
// basic/digest auth and OAuth
authenticationInterceptor?.let { okBuilder.addInterceptor(it) }
authenticator?.let { okBuilder.authenticator(it) }
// client certificate
val keyManager: KeyManager? = certificateAlias?.let { alias ->
try {
val manager = keyManagerFactory.create(alias)
logger.fine("Using certificate $alias for authentication")
// HTTP/2 doesn't support client certificates (yet)
// see https://tools.ietf.org/html/draft-ietf-httpbis-http2-secondary-certs-04
okBuilder.protocols(listOf(Protocol.HTTP_1_1))
manager
} catch (e: IllegalArgumentException) {
logger.log(Level.SEVERE, "Couldn't create KeyManager for certificate $alias", e)
null
}
}
// cert4android integration
val certManager = CustomCertManager(
context = context,
trustSystemCerts = !settingsManager.getBoolean(Settings.DISTRUST_SYSTEM_CERTIFICATES),
appInForeground = if (BuildConfig.customCertsUI)
ForegroundTracker.inForeground // interactive mode
else
null // non-interactive mode
)
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(
/* km = */ if (keyManager != null) arrayOf(keyManager) else null,
/* tm = */ arrayOf(certManager),
/* random = */ null
)
okBuilder
.sslSocketFactory(sslContext.socketFactory, certManager)
.hostnameVerifier(certManager.HostnameVerifier(OkHostnameVerifier))
}
private fun buildProxy(okBuilder: OkHttpClient.Builder) {
try {
val proxyTypeValue = settingsManager.getInt(Settings.PROXY_TYPE)
if (proxyTypeValue != Settings.PROXY_TYPE_SYSTEM) {
// we set our own proxy
val address by lazy { // lazy because not required for PROXY_TYPE_NONE
InetSocketAddress(
settingsManager.getString(Settings.PROXY_HOST),
settingsManager.getInt(Settings.PROXY_PORT)
)
}
val proxy =
when (proxyTypeValue) {
Settings.PROXY_TYPE_NONE -> Proxy.NO_PROXY
Settings.PROXY_TYPE_HTTP -> Proxy(Proxy.Type.HTTP, address)
Settings.PROXY_TYPE_SOCKS -> Proxy(Proxy.Type.SOCKS, address)
else -> throw IllegalArgumentException("Invalid proxy type")
}
okBuilder.proxy(proxy)
logger.log(Level.INFO, "Using proxy setting", proxy)
}
} catch (e: Exception) {
logger.log(Level.SEVERE, "Can't set proxy, ignoring", e)
}
}
}
}

View File

@@ -1,138 +0,0 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.network
import at.bitfire.dav4jvm.exception.DavException
import at.bitfire.dav4jvm.exception.HttpException
import at.bitfire.davdroid.settings.Credentials
import at.bitfire.davdroid.ui.setup.LoginInfo
import at.bitfire.davdroid.util.withTrailingSlash
import at.bitfire.vcard4android.GroupMethod
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.withContext
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject
import java.net.HttpURLConnection
import java.net.URI
import javax.inject.Inject
/**
* Implements Nextcloud Login Flow v2.
*
* See https://docs.nextcloud.com/server/latest/developer_manual/client_apis/LoginFlow/index.html#login-flow-v2
*/
class NextcloudLoginFlow @Inject constructor(
httpClientBuilder: HttpClient.Builder
): AutoCloseable {
companion object {
const val FLOW_V1_PATH = "index.php/login/flow"
const val FLOW_V2_PATH = "index.php/login/v2"
/** Path to DAV endpoint (e.g. `remote.php/dav`). Will be appended to the server URL returned by Login Flow. */
const val DAV_PATH = "remote.php/dav"
}
val httpClient = httpClientBuilder
.build()
override fun close() {
httpClient.close()
}
// Login flow state
var loginUrl: HttpUrl? = null
var pollUrl: HttpUrl? = null
var token: String? = null
suspend fun initiate(baseUrl: HttpUrl): HttpUrl? {
loginUrl = null
pollUrl = null
token = null
val json = postForJson(initiateUrl(baseUrl), "".toRequestBody())
loginUrl = json.getString("login").toHttpUrlOrNull()
json.getJSONObject("poll").let { poll ->
pollUrl = poll.getString("endpoint").toHttpUrl()
token = poll.getString("token")
}
return loginUrl
}
fun initiateUrl(baseUrl: HttpUrl): HttpUrl {
val path = baseUrl.encodedPath
if (path.endsWith(FLOW_V2_PATH))
// already a Login Flow v2 URL
return baseUrl
if (path.endsWith(FLOW_V1_PATH))
// Login Flow v1 URL, rewrite to v2
return baseUrl.newBuilder()
.encodedPath(path.replace(FLOW_V1_PATH, FLOW_V2_PATH))
.build()
// other URL, make it a Login Flow v2 URL
return baseUrl.newBuilder()
.addPathSegments(FLOW_V2_PATH)
.build()
}
suspend fun fetchLoginInfo(): LoginInfo {
val pollUrl = pollUrl ?: throw IllegalArgumentException("Missing pollUrl")
val token = token ?: throw IllegalArgumentException("Missing token")
// send HTTP request to request server, login name and app password
val json = postForJson(pollUrl, "token=$token".toRequestBody("application/x-www-form-urlencoded".toMediaType()))
// make sure server URL ends with a slash so that DAV_PATH can be appended
val serverUrl = json.getString("server").withTrailingSlash()
return LoginInfo(
baseUri = URI(serverUrl).resolve(DAV_PATH),
credentials = Credentials(
username = json.getString("loginName"),
password = json.getString("appPassword").toCharArray()
),
suggestedGroupMethod = GroupMethod.CATEGORIES
)
}
private suspend fun postForJson(url: HttpUrl, requestBody: RequestBody): JSONObject = withContext(Dispatchers.IO) {
val postRq = Request.Builder()
.url(url)
.post(requestBody)
.build()
val response = runInterruptible {
httpClient.okHttpClient.newCall(postRq).execute()
}
if (response.code != HttpURLConnection.HTTP_OK)
throw HttpException(response)
response.body.use { body ->
val mimeType = body.contentType() ?: throw DavException("Login Flow response without MIME type")
if (mimeType.type != "application" || mimeType.subtype != "json")
throw DavException("Invalid Login Flow response (not JSON)")
// decode JSON
return@withContext JSONObject(body.string())
}
}
}

View File

@@ -1,236 +0,0 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.resource
import android.content.ContentUris
import android.provider.CalendarContract.Calendars
import android.provider.CalendarContract.Events
import androidx.core.content.contentValuesOf
import at.bitfire.ical4android.Event
import at.bitfire.ical4android.util.MiscUtils.asSyncAdapter
import at.bitfire.synctools.mapping.calendar.LegacyAndroidEventBuilder2
import at.bitfire.synctools.storage.BatchOperation
import at.bitfire.synctools.storage.calendar.AndroidCalendar
import at.bitfire.synctools.storage.calendar.AndroidEvent2
import at.bitfire.synctools.storage.calendar.AndroidRecurringCalendar
import at.bitfire.synctools.storage.calendar.CalendarBatchOperation
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import java.util.LinkedList
import java.util.logging.Logger
/**
* Application-specific subclass of [AndroidCalendar] for local calendars.
*
* [Calendars._SYNC_ID] corresponds to the database collection ID ([at.bitfire.davdroid.db.Collection.id]).
*/
class LocalCalendar @AssistedInject constructor(
@Assisted internal val androidCalendar: AndroidCalendar,
private val logger: Logger
) : LocalCollection<LocalEvent> {
@AssistedFactory
interface Factory {
fun create(calendar: AndroidCalendar): LocalCalendar
}
// properties
override val dbCollectionId: Long?
get() = androidCalendar.syncId?.toLongOrNull()
override val tag: String
get() = "events-${androidCalendar.account.name}-${androidCalendar.id}"
override val title: String
get() = androidCalendar.displayName ?: androidCalendar.id.toString()
override val readOnly
get() = androidCalendar.accessLevel <= Calendars.CAL_ACCESS_READ
override var lastSyncState: SyncState?
get() = androidCalendar.readSyncState()?.let {
SyncState.fromString(it)
}
set(state) {
androidCalendar.writeSyncState(state.toString())
}
private val recurringCalendar = AndroidRecurringCalendar(androidCalendar)
fun add(event: Event, fileName: String, eTag: String?, scheduleTag: String?, flags: Int) {
val mapped = LegacyAndroidEventBuilder2(
calendar = androidCalendar,
event = event,
id = null,
syncId = fileName,
eTag = eTag,
scheduleTag = scheduleTag,
flags = flags
).build()
recurringCalendar.addEventAndExceptions(mapped)
}
override fun findDeleted(): List<LocalEvent> {
val result = LinkedList<LocalEvent>()
androidCalendar.iterateEvents( "${Events.DELETED} AND ${Events.ORIGINAL_ID} IS NULL", null) { entity ->
result += LocalEvent(recurringCalendar, AndroidEvent2(androidCalendar, entity))
}
return result
}
override fun findDirty(): List<LocalEvent> {
val dirty = LinkedList<LocalEvent>()
/*
* RFC 5545 3.8.7.4. Sequence Number
* When a calendar component is created, its sequence number is 0. It is monotonically incremented by the "Organizer's"
* CUA each time the "Organizer" makes a significant revision to the calendar component.
*/
androidCalendar.iterateEvents("${Events.DIRTY} AND ${Events.ORIGINAL_ID} IS NULL", null) { values ->
dirty += LocalEvent(recurringCalendar, AndroidEvent2(androidCalendar, values))
}
return dirty
}
override fun findByName(name: String) =
androidCalendar.findEvent("${Events._SYNC_ID}=? AND ${Events.ORIGINAL_SYNC_ID} IS null", arrayOf(name))?.let {
LocalEvent(recurringCalendar, it)
}
override fun markNotDirty(flags: Int) =
androidCalendar.updateEventRows(
contentValuesOf(AndroidEvent2.COLUMN_FLAGS to 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
""".trimIndent(),
arrayOf(androidCalendar.id.toString())
)
override fun removeNotDirtyMarked(flags: Int): Int {
// list all non-dirty events with the given flags and delete every row + its exceptions
val batch = CalendarBatchOperation(androidCalendar.client)
androidCalendar.iterateEventRows(
arrayOf(Events._ID),
// `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.getAsLong(Events._ID)
// delete event and possible exceptions (content provider doesn't delete exceptions itself)
batch += BatchOperation.CpoBuilder
.newDelete(androidCalendar.eventsUri)
.withSelection("${Events._ID}=? OR ${Events.ORIGINAL_ID}=?", arrayOf(id.toString(), id.toString()))
}
return batch.commit()
}
override fun forgetETags() {
androidCalendar.updateEventRows(
contentValuesOf(AndroidEvent2.COLUMN_ETAG to null),
"${Events.CALENDAR_ID}=?", arrayOf(androidCalendar.id.toString())
)
}
fun processDirtyExceptions() {
// process deleted exceptions
logger.info("Processing deleted exceptions")
androidCalendar.iterateEventRows(
arrayOf(Events._ID, Events.ORIGINAL_ID, AndroidEvent2.COLUMN_SEQUENCE),
"${Events.CALENDAR_ID}=? AND ${Events.DELETED} AND ${Events.ORIGINAL_ID} IS NOT NULL",
arrayOf(androidCalendar.id.toString())
) { values ->
logger.fine("Found deleted exception, removing and re-scheduling original event (if available)")
val id = values.getAsLong(Events._ID) // can't be null (by definition)
val originalID = values.getAsLong(Events.ORIGINAL_ID) // can't be null (by query)
val batch = CalendarBatchOperation(androidCalendar.client)
// enqueue: increase sequence of main event
val originalEventValues = androidCalendar.getEventRow(originalID, arrayOf(AndroidEvent2.COLUMN_SEQUENCE))
val originalSequence = originalEventValues?.getAsInteger(AndroidEvent2.COLUMN_SEQUENCE) ?: 0
batch += BatchOperation.CpoBuilder
.newUpdate(ContentUris.withAppendedId(Events.CONTENT_URI, originalID).asSyncAdapter(androidCalendar.account))
.withValue(AndroidEvent2.COLUMN_SEQUENCE, originalSequence + 1)
.withValue(Events.DIRTY, 1)
// completely remove deleted exception
batch += BatchOperation.CpoBuilder.newDelete(ContentUris.withAppendedId(Events.CONTENT_URI, id).asSyncAdapter(androidCalendar.account))
batch.commit()
}
// process dirty exceptions
logger.info("Processing dirty exceptions")
androidCalendar.iterateEventRows(
arrayOf(Events._ID, Events.ORIGINAL_ID, AndroidEvent2.COLUMN_SEQUENCE),
"${Events.CALENDAR_ID}=? AND ${Events.DIRTY} AND ${Events.ORIGINAL_ID} IS NOT NULL",
arrayOf(androidCalendar.id.toString())
) { values ->
logger.fine("Found dirty exception, increasing SEQUENCE to re-schedule")
val id = values.getAsLong(Events._ID) // can't be null (by definition)
val originalID = values.getAsLong(Events.ORIGINAL_ID) // can't be null (by query)
val sequence = values.getAsInteger(AndroidEvent2.COLUMN_SEQUENCE) ?: 0
val batch = CalendarBatchOperation(androidCalendar.client)
// enqueue: set original event to DIRTY
batch += BatchOperation.CpoBuilder
.newUpdate(androidCalendar.eventUri(originalID))
.withValue(Events.DIRTY, 1)
// enqueue: increase exception SEQUENCE and set DIRTY to 0
batch += BatchOperation.CpoBuilder
.newUpdate(androidCalendar.eventUri(id))
.withValue(AndroidEvent2.COLUMN_SEQUENCE, sequence + 1)
.withValue(Events.DIRTY, 0)
batch.commit()
}
}
/**
* Marks dirty events (which are not already marked as deleted) which got no valid instances as "deleted"
*
* @return number of affected events
*/
fun deleteDirtyEventsWithoutInstances() {
// Iterate dirty main events without exceptions
androidCalendar.iterateEventRows(
arrayOf(Events._ID),
"${Events.DIRTY} AND NOT ${Events.DELETED} AND ${Events.ORIGINAL_ID} IS NULL",
null
) { values ->
val eventId = values.getAsLong(Events._ID)
// get number of instances
val numEventInstances = androidCalendar.numInstances(eventId)
// delete event if there are no instances
if (numEventInstances == 0) {
logger.fine("Marking event #$eventId without instances as deleted")
androidCalendar.updateEventRow(eventId, contentValuesOf(Events.DELETED to 1))
}
}
}
}

View File

@@ -1,175 +0,0 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.resource
import android.provider.CalendarContract.Events
import androidx.core.content.contentValuesOf
import at.bitfire.ical4android.Event
import at.bitfire.ical4android.LegacyAndroidCalendar
import at.bitfire.synctools.mapping.calendar.LegacyAndroidEventBuilder2
import at.bitfire.synctools.storage.LocalStorageException
import at.bitfire.synctools.storage.calendar.AndroidEvent2
import at.bitfire.synctools.storage.calendar.AndroidRecurringCalendar
import java.util.Optional
import java.util.UUID
class LocalEvent(
val recurringCalendar: AndroidRecurringCalendar,
val androidEvent: AndroidEvent2
) : LocalResource<Event> {
override val id: Long
get() = androidEvent.id
override val fileName: String?
get() = androidEvent.syncId
override val eTag: String?
get() = androidEvent.eTag
override val scheduleTag: String?
get() = androidEvent.scheduleTag
override val flags: Int
get() = androidEvent.flags
override fun update(data: Event, fileName: String?, eTag: String?, scheduleTag: String?, flags: Int) {
val eventAndExceptions = LegacyAndroidEventBuilder2(
calendar = androidEvent.calendar,
event = data,
id = id,
syncId = fileName,
eTag = eTag,
scheduleTag = scheduleTag,
flags = flags
).build()
recurringCalendar.updateEventAndExceptions(id, eventAndExceptions)
}
private var _event: Event? = null
/**
* Retrieves the event from the content provider and converts it to a legacy data object.
*
* Caches the result: the content provider is only queried at the first call and then
* this method always returns the same object.
*
* @throws LocalStorageException if there is no local event with the ID from [androidEvent]
*/
@Synchronized
fun getCachedEvent(): Event {
_event?.let { return it }
val legacyCalendar = LegacyAndroidCalendar(androidEvent.calendar)
val event = legacyCalendar.getEvent(androidEvent.id)
?: throw LocalStorageException("Event ${androidEvent.id} not found")
_event = event
return event
}
/**
* Generates the [Event] that should actually be uploaded:
*
* 1. Takes the [getCachedEvent].
* 2. Calculates the new SEQUENCE.
*
* _Note: This method currently modifies the object returned by [getCachedEvent], but
* this may change in the future._
*
* @return data object that should be used for uploading
*/
fun eventToUpload(): Event {
val event = getCachedEvent()
val nonGroupScheduled = event.attendees.isEmpty()
val weAreOrganizer = event.isOrganizer == true
// Increase sequence (event.sequence null/non-null behavior is defined by the Event, see KDoc of event.sequence):
// - If it's null, the event has just been created in the database, so we can start with SEQUENCE:0 (default).
// - If it's non-null, the event already exists on the server, so increase by one.
val sequence = event.sequence
if (sequence != null && (nonGroupScheduled || weAreOrganizer))
event.sequence = sequence + 1
return event
}
/**
* Updates the SEQUENCE of the event in the content provider.
*
* @param sequence new sequence value
*/
fun updateSequence(sequence: Int?) {
androidEvent.update(contentValuesOf(
AndroidEvent2.COLUMN_SEQUENCE to sequence
))
}
/**
* Creates and sets a new UID in the calendar provider, if no UID is already set.
* It also returns the desired file name for the event for further processing in the sync algorithm.
*
* @return file name to use at upload
*/
override fun prepareForUpload(): String {
// make sure that UID is set
val uid: String = getCachedEvent().uid ?: run {
// generate new UID
val newUid = UUID.randomUUID().toString()
// persist to calendar provider
val values = contentValuesOf(Events.UID_2445 to newUid)
androidEvent.update(values)
// update in cached event data object
getCachedEvent().uid = newUid
newUid
}
val uidIsGoodFilename = uid.all { char ->
// see RFC 2396 2.2
char.isLetterOrDigit() || arrayOf( // allow letters and digits
';', ':', '@', '&', '=', '+', '$', ',', // allow reserved characters except '/' and '?'
'-', '_', '.', '!', '~', '*', '\'', '(', ')' // allow unreserved characters
).contains(char)
}
return if (uidIsGoodFilename)
"$uid.ics" // use UID as file name
else
"${UUID.randomUUID()}.ics" // UID would be dangerous as file name, use random UUID instead
}
override fun clearDirty(fileName: Optional<String>, eTag: String?, scheduleTag: String?) {
val values = contentValuesOf(
Events.DIRTY to 0,
AndroidEvent2.COLUMN_ETAG to eTag,
AndroidEvent2.COLUMN_SCHEDULE_TAG to scheduleTag
)
if (fileName.isPresent)
values.put(Events._SYNC_ID, fileName.get())
androidEvent.update(values)
}
override fun updateFlags(flags: Int) {
androidEvent.update(contentValuesOf(
AndroidEvent2.COLUMN_FLAGS to flags
))
}
override fun deleteLocal() {
recurringCalendar.deleteEventAndExceptions(id)
}
override fun resetDeleted() {
androidEvent.update(contentValuesOf(
Events.DELETED to 0
))
}
}

View File

@@ -1,129 +0,0 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.resource
import android.content.ContentValues
import androidx.core.content.contentValuesOf
import at.bitfire.ical4android.DmfsTask
import at.bitfire.ical4android.DmfsTaskFactory
import at.bitfire.ical4android.DmfsTaskList
import at.bitfire.ical4android.Task
import at.bitfire.synctools.storage.BatchOperation
import org.dmfs.tasks.contract.TaskContract.Tasks
import java.util.Optional
import java.util.UUID
class LocalTask: DmfsTask, LocalResource<Task> {
companion object {
const val COLUMN_ETAG = Tasks.SYNC1
const val COLUMN_FLAGS = Tasks.SYNC2
}
override var fileName: String? = null
override var scheduleTag: String? = null
override var eTag: String? = null
override var flags = 0
private set
constructor(taskList: DmfsTaskList<*>, task: Task, fileName: String?, eTag: String?, flags: Int)
: super(taskList, task) {
this.fileName = fileName
this.eTag = eTag
this.flags = flags
}
private constructor(taskList: DmfsTaskList<*>, values: ContentValues): super(taskList) {
id = values.getAsLong(Tasks._ID)
fileName = values.getAsString(Tasks._SYNC_ID)
eTag = values.getAsString(COLUMN_ETAG)
flags = values.getAsInteger(COLUMN_FLAGS) ?: 0
}
/* process LocalTask-specific fields */
override fun buildTask(builder: BatchOperation.CpoBuilder, update: Boolean) {
super.buildTask(builder, update)
builder .withValue(Tasks._SYNC_ID, fileName)
.withValue(COLUMN_ETAG, eTag)
.withValue(COLUMN_FLAGS, flags)
}
/* custom queries */
override fun prepareForUpload(): String {
val uid: String = task!!.uid ?: run {
// generate new UID
val newUid = UUID.randomUUID().toString()
// update in tasks provider
val values = contentValuesOf(Tasks._UID to newUid)
taskList.provider.update(taskSyncURI(), values, null, null)
// update this task
task!!.uid = newUid
newUid
}
return "$uid.ics"
}
override fun clearDirty(fileName: Optional<String>, eTag: String?, scheduleTag: String?) {
if (scheduleTag != null)
logger.fine("Schedule-Tag for tasks not supported yet, won't save")
val values = ContentValues(4)
if (fileName.isPresent)
values.put(Tasks._SYNC_ID, fileName.get())
values.put(COLUMN_ETAG, eTag)
values.put(Tasks.SYNC_VERSION, task!!.sequence)
values.put(Tasks._DIRTY, 0)
taskList.provider.update(taskSyncURI(), values, null, null)
if (fileName.isPresent)
this.fileName = fileName.get()
this.eTag = eTag
}
override fun update(data: Task, fileName: String?, eTag: String?, scheduleTag: String?, flags: Int) {
this.fileName = fileName
this.eTag = eTag
this.scheduleTag = scheduleTag
this.flags = flags
// processes this.{fileName, eTag, scheduleTag, flags} and resets DIRTY flag
update(data)
}
override fun updateFlags(flags: Int) {
if (id != null) {
val values = contentValuesOf(COLUMN_FLAGS to flags)
taskList.provider.update(taskSyncURI(), values, null, null)
}
this.flags = flags
}
override fun deleteLocal() {
delete()
}
override fun resetDeleted() {
throw NotImplementedError()
}
object Factory: DmfsTaskFactory<LocalTask> {
override fun fromProvider(taskList: DmfsTaskList<*>, values: ContentValues) =
LocalTask(taskList, values)
}
}

View File

@@ -1,129 +0,0 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.resource
import android.accounts.Account
import android.content.ContentProviderClient
import android.content.ContentValues
import androidx.core.content.contentValuesOf
import at.bitfire.ical4android.DmfsTaskList
import at.bitfire.ical4android.DmfsTaskListFactory
import at.bitfire.ical4android.TaskProvider
import org.dmfs.tasks.contract.TaskContract.TaskListColumns
import org.dmfs.tasks.contract.TaskContract.TaskLists
import org.dmfs.tasks.contract.TaskContract.Tasks
import java.util.logging.Level
import java.util.logging.Logger
/**
* App-specific implementation of a task list.
*
* [TaskLists._SYNC_ID] corresponds to the database collection ID ([at.bitfire.davdroid.db.Collection.id]).
*/
class LocalTaskList private constructor(
account: Account,
provider: ContentProviderClient,
providerName: TaskProvider.ProviderName,
id: Long
): DmfsTaskList<LocalTask>(account, provider, providerName, LocalTask.Factory, id), LocalCollection<LocalTask> {
private val logger = Logger.getGlobal()
private var accessLevel: Int = TaskListColumns.ACCESS_LEVEL_UNDEFINED
override val readOnly
get() =
accessLevel != TaskListColumns.ACCESS_LEVEL_UNDEFINED &&
accessLevel <= TaskListColumns.ACCESS_LEVEL_READ
override val dbCollectionId: Long?
get() = syncId?.toLongOrNull()
override val tag: String
get() = "tasks-${account.name}-$id"
override val title: String
get() = name ?: id.toString()
override var lastSyncState: SyncState?
get() {
try {
provider.query(taskListSyncUri(), arrayOf(TaskLists.SYNC_VERSION),
null, null, null)?.use { cursor ->
if (cursor.moveToNext())
cursor.getString(0)?.let {
return SyncState.fromString(it)
}
}
} catch (e: Exception) {
logger.log(Level.WARNING, "Couldn't read sync state", e)
}
return null
}
set(state) {
val values = contentValuesOf(TaskLists.SYNC_VERSION to state?.toString())
provider.update(taskListSyncUri(), values, null, null)
}
override fun populate(values: ContentValues) {
super.populate(values)
accessLevel = values.getAsInteger(TaskListColumns.ACCESS_LEVEL)
}
override fun findDeleted() = queryTasks(Tasks._DELETED, null)
override fun findDirty(): List<LocalTask> {
val tasks = queryTasks(Tasks._DIRTY, null)
for (localTask in tasks) {
try {
val task = requireNotNull(localTask.task)
val sequence = task.sequence
if (sequence == null) // sequence has not been assigned yet (i.e. this task was just locally created)
task.sequence = 0
else // task was modified, increase sequence
task.sequence = sequence + 1
} catch(e: Exception) {
logger.log(Level.WARNING, "Couldn't check/increase sequence", e)
}
}
return tasks
}
override fun findByName(name: String) =
queryTasks("${Tasks._SYNC_ID}=?", arrayOf(name)).firstOrNull()
override fun markNotDirty(flags: Int): Int {
val values = contentValuesOf(LocalTask.COLUMN_FLAGS to flags)
return provider.update(tasksSyncUri(), values,
"${Tasks.LIST_ID}=? AND ${Tasks._DIRTY}=0",
arrayOf(id.toString()))
}
override fun removeNotDirtyMarked(flags: Int) =
provider.delete(tasksSyncUri(),
"${Tasks.LIST_ID}=? AND NOT ${Tasks._DIRTY} AND ${LocalTask.COLUMN_FLAGS}=?",
arrayOf(id.toString(), flags.toString()))
override fun forgetETags() {
val values = contentValuesOf(LocalTask.COLUMN_ETAG to null)
provider.update(tasksSyncUri(), values, "${Tasks.LIST_ID}=?",
arrayOf(id.toString()))
}
object Factory: DmfsTaskListFactory<LocalTaskList> {
override fun newInstance(
account: Account,
provider: ContentProviderClient,
providerName: TaskProvider.ProviderName,
id: Long
) = LocalTaskList(account, provider, providerName, id)
}
}

View File

@@ -1,76 +0,0 @@
/*
* 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

@@ -1,354 +0,0 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.ui
import android.content.Context
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Home
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Tab
import androidx.compose.material3.TabRow
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import at.bitfire.davdroid.BuildConfig
import at.bitfire.davdroid.R
import at.bitfire.davdroid.di.IoDispatcher
import at.bitfire.davdroid.ui.ExternalUris.withStatParams
import at.bitfire.davdroid.ui.composable.PixelBoxes
import com.mikepenz.aboutlibraries.ui.compose.LibraryDefaults
import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer
import dagger.BindsOptionalOf
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.components.ActivityComponent
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.json.JSONObject
import java.text.Collator
import java.util.LinkedList
import java.util.Locale
import java.util.Optional
import java.util.logging.Level
import java.util.logging.Logger
import javax.inject.Inject
import kotlin.jvm.optionals.getOrNull
@AndroidEntryPoint
class AboutActivity: AppCompatActivity() {
val model by viewModels<Model>()
@Inject
lateinit var licenseInfoProvider: Optional<AppLicenseInfoProvider>
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AppTheme {
val uriHandler = LocalUriHandler.current
Scaffold(
topBar = {
TopAppBar(
navigationIcon = {
IconButton(onClick = { onSupportNavigateUp() }) {
Icon(
Icons.AutoMirrored.Default.ArrowBack,
contentDescription = stringResource(R.string.navigate_up)
)
}
},
title = {
Text(stringResource(R.string.navigation_drawer_about))
},
actions = {
IconButton(onClick = {
uriHandler.openUri(ExternalUris.Homepage.baseUrl
.buildUpon()
.withStatParams(javaClass.simpleName)
.build().toString())
}) {
Icon(
Icons.Default.Home,
contentDescription = stringResource(R.string.navigation_drawer_website)
)
}
}
)
}
) { paddingValues ->
Column(Modifier.padding(paddingValues)) {
val scope = rememberCoroutineScope()
val state = rememberPagerState(pageCount = { 3 })
TabRow(state.currentPage) {
Tab(state.currentPage == 0, onClick = {
scope.launch { state.scrollToPage(0) }
}) {
Text(
stringResource(R.string.app_name),
modifier = Modifier.padding(8.dp)
)
}
Tab(state.currentPage == 1, onClick = {
scope.launch { state.scrollToPage(1) }
}) {
Text(
stringResource(R.string.about_translations),
modifier = Modifier.padding(8.dp)
)
}
Tab(state.currentPage == 2, onClick = {
scope.launch { state.scrollToPage(2) }
}) {
Text(
stringResource(R.string.about_libraries),
modifier = Modifier.padding(8.dp)
)
}
}
HorizontalPager(
state,
modifier = Modifier
.fillMaxWidth()
.weight(1f),
verticalAlignment = Alignment.Top
) { index ->
when (index) {
0 -> AboutApp(licenseInfoProvider = licenseInfoProvider.getOrNull())
1 -> {
val translations = model.translations.collectAsStateWithLifecycle(emptyList())
TranslatorsGallery(translations.value)
}
2 -> LibrariesContainer(
modifier = Modifier.fillMaxSize(),
padding = LibraryDefaults.libraryPadding(
contentPadding = PaddingValues(8.dp)
),
dimensions = LibraryDefaults.libraryDimensions(
itemSpacing = 8.dp
)
)
}
}
}
}
}
}
}
@HiltViewModel
class Model @Inject constructor(
@ApplicationContext val context: Context,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
private val logger: Logger
): ViewModel() {
data class Translation(
val language: String,
val translators: Set<String>
)
val translations: Flow<List<Translation>> = flow {
val translations = loadTranslations()
emit(translations)
}
private suspend fun loadTranslations(): List<Translation> = withContext(ioDispatcher) {
try {
context.resources.assets.open("translators.json").use { stream ->
val jsonTranslations = JSONObject(stream.readBytes().decodeToString())
val result = LinkedList<Translation>()
for (langCode in jsonTranslations.keys()) {
val jsonTranslators = jsonTranslations.getJSONArray(langCode)
val translators = Array<String>(jsonTranslators.length()) { idx ->
jsonTranslators.getString(idx)
}
val langTag = langCode.replace('_', '-')
val language = Locale.forLanguageTag(langTag).displayName
result += Translation(language, translators.toSet())
}
// sort translations by localized language name
val collator = Collator.getInstance()
result.sortWith { o1, o2 ->
collator.compare(o1.language, o2.language)
}
result
}
} catch (e: Exception) {
logger.log(Level.WARNING, "Couldn't load translators", e)
emptyList()
}
}
}
interface AppLicenseInfoProvider {
@Composable
fun LicenseInfo()
}
@Module
@InstallIn(ActivityComponent::class)
interface AppLicenseInfoProviderModule {
@BindsOptionalOf
fun appLicenseInfoProvider(): AppLicenseInfoProvider
}
}
@Composable
fun AboutApp(licenseInfoProvider: AboutActivity.AppLicenseInfoProvider? = null) {
Column(
modifier = Modifier
.padding(8.dp)
.fillMaxWidth()
.verticalScroll(rememberScrollState())) {
Image(
UiUtils.adaptiveIconPainterResource(R.mipmap.ic_launcher),
contentDescription = stringResource(R.string.app_name),
modifier = Modifier
.size(128.dp)
.align(Alignment.CenterHorizontally)
)
Text(
stringResource(R.string.app_name),
style = MaterialTheme.typography.headlineMedium,
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
)
Text(
stringResource(R.string.about_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE),
style = MaterialTheme.typography.bodyLarge,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
Text(
stringResource(R.string.about_copyright),
style = MaterialTheme.typography.bodyLarge,
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
)
Text(
stringResource(R.string.about_license_info_no_warranty),
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp)
)
PixelBoxes(
arrayOf(Color(0xFFFCF434), Color.White, Color(0xFF9C59D1), Color.Black),
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(16.dp)
)
licenseInfoProvider?.LicenseInfo()
}
}
@Composable
@Preview
fun AboutApp_Preview() {
AboutApp(licenseInfoProvider = object : AboutActivity.AppLicenseInfoProvider {
@Composable
override fun LicenseInfo() {
Text("Some flavored License Info")
}
})
}
@Composable
fun TranslatorsGallery(
translations: List<AboutActivity.Model.Translation>
) {
val collator = Collator.getInstance()
LazyColumn(Modifier.padding(8.dp)) {
items(translations) { translation ->
Text(
translation.language,
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier.padding(vertical = 4.dp)
)
Text(
translation.translators
.sortedWith { a, b -> collator.compare(a, b) }
.joinToString(" · "),
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.padding(bottom = 16.dp)
)
}
}
}
@Composable
@Preview
fun TranslatorsGallery_Sample() {
TranslatorsGallery(listOf(
AboutActivity.Model.Translation("Some Language", setOf("User 1", "User 2")),
AboutActivity.Model.Translation("Another Language", setOf("User 3", "User 4"))
))
}

View File

@@ -1,72 +0,0 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.ui.account
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.map
import at.bitfire.davdroid.db.Collection
import at.bitfire.davdroid.db.CollectionType
import at.bitfire.davdroid.db.Service
import at.bitfire.davdroid.repository.DavCollectionRepository
import at.bitfire.davdroid.settings.Settings
import at.bitfire.davdroid.settings.SettingsManager
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import javax.inject.Inject
/**
* Gets a list of collections for a service and type, optionally filtered by "show only personal" setting.
*
* Takes the "force read-only address books" setting into account: if set, all address books will have "forceReadOnly" set.
*/
class GetServiceCollectionPagerUseCase @Inject constructor(
val collectionRepository: DavCollectionRepository,
val settings: SettingsManager
) {
companion object {
const val PAGER_SIZE = 20
}
val forceReadOnlyAddressBooksFlow = settings.getBooleanFlow(Settings.FORCE_READ_ONLY_ADDRESSBOOKS, false)
@OptIn(ExperimentalCoroutinesApi::class)
operator fun invoke(
serviceFlow: Flow<Service?>,
@CollectionType collectionType: String,
showOnlyPersonalFlow: Flow<Boolean>
): Flow<PagingData<Collection>> =
combine(serviceFlow, showOnlyPersonalFlow, forceReadOnlyAddressBooksFlow) { service, onlyPersonal, forceReadOnlyAddressBooks ->
service?.let { service ->
val dataFlow = Pager(
config = PagingConfig(PAGER_SIZE),
pagingSourceFactory = {
if (onlyPersonal == true)
collectionRepository.pagePersonalByServiceAndType(service.id, collectionType)
else
collectionRepository.pageByServiceAndType(service.id, collectionType)
}
).flow
// set "forceReadOnly" for every address book if requested
if (forceReadOnlyAddressBooks && collectionType == Collection.TYPE_ADDRESSBOOK)
dataFlow.map { pagingData ->
pagingData.map { collection ->
collection.copy(forceReadOnly = true)
}
}
else
dataFlow
} ?: flowOf(PagingData.empty())
}.flatMapLatest { it }
}

View File

@@ -1,12 +0,0 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.ui.widget
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.GlanceAppWidgetReceiver
class IconSyncButtonWidgetReceiver : GlanceAppWidgetReceiver() {
override val glanceAppWidget: GlanceAppWidget = IconSyncButtonWidget()
}

View File

@@ -1,12 +0,0 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.ui.widget
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.GlanceAppWidgetReceiver
class LabeledSyncButtonWidgetReceiver : GlanceAppWidgetReceiver() {
override val glanceAppWidget: GlanceAppWidget = LabeledSyncButtonWidget()
}

View File

@@ -1,49 +0,0 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.webdav
import at.bitfire.davdroid.network.HttpClient
import at.bitfire.davdroid.network.MemoryCookieStore
import okhttp3.CookieJar
import okhttp3.logging.HttpLoggingInterceptor
import javax.inject.Inject
import javax.inject.Provider
class DavHttpClientBuilder @Inject constructor(
private val credentialsStore: CredentialsStore,
private val httpClientBuilder: Provider<HttpClient.Builder>,
) {
/**
* Creates an HTTP client that can be used to access resources in the given mount.
*
* @param mountId ID of the mount to access
* @param logBody whether to log the body of HTTP requests (disable for potentially large files)
*/
fun build(mountId: Long, logBody: Boolean = true): HttpClient {
val cookieStore = cookieStores.getOrPut(mountId) {
MemoryCookieStore()
}
val builder = httpClientBuilder.get()
.loggerInterceptorLevel(if (logBody) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.HEADERS)
.setCookieStore(cookieStore)
credentialsStore.getCredentials(mountId)?.let { credentials ->
builder.authenticate(host = null, getCredentials = { credentials })
}
return builder.build()
}
companion object {
/** in-memory cookie stores (one per mount ID) that are available until the content
* provider (= process) is terminated */
private val cookieStores = mutableMapOf<Long, CookieJar>()
}
}

View File

@@ -1,89 +0,0 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.webdav.operation
import android.content.Context
import android.provider.DocumentsContract.Document
import at.bitfire.dav4jvm.DavResource
import at.bitfire.dav4jvm.exception.HttpException
import at.bitfire.davdroid.db.AppDatabase
import at.bitfire.davdroid.db.WebDavDocument
import at.bitfire.davdroid.di.IoDispatcher
import at.bitfire.davdroid.webdav.DavHttpClientBuilder
import at.bitfire.davdroid.webdav.DocumentProviderUtils
import at.bitfire.davdroid.webdav.DocumentProviderUtils.displayNameToMemberName
import at.bitfire.davdroid.webdav.throwForDocumentProvider
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.runInterruptible
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody
import java.io.FileNotFoundException
import java.util.logging.Logger
import javax.inject.Inject
class CreateDocumentOperation @Inject constructor(
@ApplicationContext private val context: Context,
private val db: AppDatabase,
private val httpClientBuilder: DavHttpClientBuilder,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
private val logger: Logger
) {
private val documentDao = db.webDavDocumentDao()
operator fun invoke(parentDocumentId: String, mimeType: String, displayName: String): String? = runBlocking {
logger.fine("WebDAV createDocument $parentDocumentId $mimeType $displayName")
val parent = documentDao.get(parentDocumentId.toLong()) ?: throw FileNotFoundException()
val createDirectory = mimeType == Document.MIME_TYPE_DIR
var docId: Long?
httpClientBuilder.build(parent.mountId).use { client ->
for (attempt in 0..DocumentProviderUtils.MAX_DISPLAYNAME_TO_MEMBERNAME_ATTEMPTS) {
val newName = displayNameToMemberName(displayName, attempt)
val parentUrl = parent.toHttpUrl(db)
val newLocation = parentUrl.newBuilder()
.addPathSegment(newName)
.build()
val doc = DavResource(client.okHttpClient, newLocation)
try {
runInterruptible(ioDispatcher) {
if (createDirectory)
doc.mkCol(null) {
// directory successfully created
}
else
doc.put(RequestBody.EMPTY, ifNoneMatch = true) {
// document successfully created
}
}
docId = documentDao.insertOrReplace(
WebDavDocument(
mountId = parent.mountId,
parentId = parent.id,
name = newName,
isDirectory = createDirectory,
mimeType = mimeType.toMediaTypeOrNull(),
eTag = null,
lastModified = null,
size = if (createDirectory) null else 0
)
)
DocumentProviderUtils.notifyFolderChanged(context, parentDocumentId)
return@runBlocking docId.toString()
} catch (e: HttpException) {
e.throwForDocumentProvider(context, ignorePreconditionFailed = true)
}
}
}
null
}
}

View File

@@ -1,3 +0,0 @@
{"metadata":{"generated":"2023-12-03T12:08:52.214Z"},"libraries":[
{"uniqueId":"com.example:sample","funding":[],"developers":[{"name":"Sample Developer"}],"artifactVersion":"1.0","description":"This list has to be updated at release build time by explicitly writing to R.raw.aboutlibraries.","name":"Sample Dependency","licenses":["Sample-License"]}
], "licenses":{}}

View File

@@ -1,480 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources xmlns:tools="http://schemas.android.com/tools">
<!--common strings-->
<string name="account_invalid">Kasutajakontot ei leidu (enam)</string>
<string name="account_title_address_book">DAVx⁵ aadressiraamat</string>
<string name="account_prefs_use_app">Palun ära muuda kasutajakontot siin! Selle asemel pruugi kasutajakontode halduseks otseselt rakendust.</string>
<string name="dialog_delete">Kustuta</string>
<string name="dialog_remove">Eemalda</string>
<string name="dialog_deny">Katkesta</string>
<string name="dialog_enable">Võta kasutusele</string>
<string name="field_required">See väli on kohustuslik</string>
<string name="help">Abiteave</string>
<string name="navigate_up">Liigu üles</string>
<string name="options_menu">Valikute menüü</string>
<string name="share">Jaga</string>
<string name="sync_started">Sünkroniseerimine algas või on tööde järjekorras</string>
<string name="database_destructive_migration_title">Andmebaas on vigane</string>
<string name="database_destructive_migration_text">Kõik kasutajakontod on kohalikust seadmest eemaldatud</string>
<string name="notification_channel_debugging">Silumine ja veaotsing</string>
<string name="notification_channel_general">Muud olulised sõnumid</string>
<string name="notification_channel_status">Väheolulised olekuteated</string>
<string name="notification_channel_sync">Sünkroniseerimine</string>
<string name="notification_channel_sync_errors">Sünkroniseerimisvead</string>
<string name="notification_channel_sync_errors_desc">Olulised vead, mis peatavad sünkroniseerimise, nagu näiteks ootamatud päringuvastused serverist</string>
<string name="notification_channel_sync_warnings">Sünkroniseerimishoiatused</string>
<string name="notification_channel_sync_warnings_desc">Vähetõsised sünkroniseerimisteated näiteks vigaste failide kohta</string>
<string name="notification_channel_sync_io_errors">Võrgu- ja sisend/väljundvead</string>
<string name="notification_channel_sync_io_errors_desc">Ühenduste aegumine ja muud sarnased probleemid (tihti ajutised)</string>
<!--IntroActivity-->
<string name="intro_slogan1">Sinu andmed. Sinu valik.</string>
<string name="intro_slogan2">Sina otsustad.</string>
<string name="intro_battery_title">Regulaarne sünkroniseerimisvälp</string>
<string name="intro_battery_text">Selleks, et sünkroniseerimine soovitud ajavahemike järel toimiks taustateenusena, vajab %s õigust töötada taustal. Vastasel juhul võib Android igal ajal sünkroniseerimise peatada.</string>
<string name="intro_battery_dont_show">Ma ei soovi kasutada regulaarset sünkroniseerimisvälpa. *</string>
<string name="intro_autostart_title">%s ühilduvus</string>
<string name="intro_autostart_text">Nutiseadme tootja poolt lisatud püsivara võib blokeerida sünkroniseerimist. Kui see sinu tegevust mõjutab, siis saad olukorra lahendada käsitsi.</string>
<string name="intro_autostart_dont_show">Ma juba kasutan nõutavaid seadistusi. Ära enam tuleta seda mulle meelde.*</string>
<string name="intro_leave_unchecked">* Kui soovid hilisemat meeldetuletust, jäta see märkimata. Lisaks saad seada muuta rakenduse seadistustest / %s.</string>
<string name="intro_more_info">Lisateave</string>
<string name="intro_tasks_jtx">jtx Board</string>
<string name="intro_tasks_jtx_info"><![CDATA[Toetab ülesannete, märkmete ja päevikute sünkroniseerimist.]]></string>
<string name="intro_tasks_title">Ülesannete tugi</string>
<string name="intro_tasks_text1">Kui sinu kasutatav server toetab ülesannete haldust, siis nende sünkroniseerimine on võimalik toetatud ülesannete rakendusega:</string>
<string name="intro_tasks_opentasks">OpenTasks</string>
<string name="intro_tasks_opentasks_info">Tundub, et arendus on lõppenud ja seega pole kasutamine enam mõistlik.</string>
<string name="intro_tasks_tasks_org">Tasks.org</string>
<string name="intro_tasks_tasks_org_info"><![CDATA[Mõned funktsionaalsused <a href="https://www.davx5.com/faq/tasks/advanced-task-features">pole toetatud</a>.]]></string>
<string name="intro_tasks_no_app_store">Rakendustepoodi pole saadaval</string>
<string name="intro_tasks_dont_show">Ma ei vaja ülesannete tuge.*</string>
<string name="intro_open_source_title">Avatud lähtekoodiga tarkvara</string>
<string name="intro_open_source_text">Me oleme rõõmsad, et kasutad avatud lähtekoodil põhinevat rakendust %s. Selle arendus, hooldus ja kasutajatugi nõuavad märgatavat tööd. Palun kaalu erinevaid võimalusi osalemiseks või rahalist toetamist. Me hindaksime seda väga!</string>
<string name="intro_open_source_details">Võimalused kaastööks või rahaliseks toetamiseks</string>
<string name="intro_open_source_dont_show">Ära näita seda uuesti</string>
<plurals name="intro_open_source_dont_show_months">
<item quantity="one">%d kuu jooksul</item>
<item quantity="other">%d kuu jooksul</item>
</plurals>
<string name="intro_next">Järgmine</string>
<!--PermissionsActivity-->
<string name="permissions_title">Õigused</string>
<string name="permissions_text">%s vajab korralikuks toimimiseks õigusi.</string>
<string name="permissions_all_title">Kõik alljärgnev</string>
<string name="permissions_all_status_off">Kasuta seda valikut kõikide funktsionaalsuste sisselülitamiseks (soovitatav)</string>
<string name="permissions_all_status_on">Rakenduse õigused on olemas</string>
<string name="permissions_contacts_title">Kontaktide õigused</string>
<string name="permissions_contacts_status_off">Kontaktide sünkroniseerimine puudub (pole soovitatud)</string>
<string name="permissions_contacts_status_on">Kontaktide sünkroniseerimine on võimalik</string>
<string name="permissions_calendar_title">Kalendri õigused</string>
<string name="permissions_calendar_status_off">Kalendri sünkroniseerimine puudub (pole soovitatud)</string>
<string name="permissions_calendar_status_on">Kalendri sünkroniseerimine on võimalik</string>
<string name="permissions_notification_title">Teavituste õigused</string>
<string name="permissions_notification_status_off">Teavitused pole kasutusel (pole soovitatav)</string>
<string name="permissions_notification_status_on">Teavitused on kasutusel</string>
<string name="permissions_jtx_title">Õigused - jtx Board</string>
<string name="permissions_opentasks_title">Õigused - OpenTasks</string>
<string name="permissions_tasksorg_title">Ülesannete õigused</string>
<string name="permissions_tasks_status_off">Ülesannete sünkroniseerimine puudub</string>
<string name="permissions_tasks_status_on">Ülesannete sünkroniseerimine on võimalik</string>
<string name="permissions_autoreset_title">Säilita õigused</string>
<string name="permissions_autoreset_status_off">Õigusi võib muuta automaatselt (pole soovitatud)</string>
<string name="permissions_autoreset_status_on">Õigused ei saa olema automaatselt muudetud</string>
<string name="permissions_autoreset_instruction">Klõpsi Õigused ja eemalda valik „Eemalda load, kui rakendust ei kasutata“</string>
<string name="permissions_app_settings_hint">Kui muutmine ei toimi, siis kasuta rakenduse õiguste seadistusi.</string>
<string name="permissions_app_settings">Rakenduse seadistused</string>
<!--WifiPermissionsActivity-->
<string name="wifi_permissions_label">WiFi SSID õigused</string>
<string name="wifi_permissions_intro">Selleks, et toimiks ligipääs hetkel kasutatavale WiFi võrgunimele (SSID), peavad olema täidetud järgnevad tingimused:</string>
<string name="wifi_permissions_location_permission">Õigused täpse asukoha tuvastamiseks</string>
<string name="wifi_permissions_location_permission_on">Õigused asukoha tuvastamiseks on olemas</string>
<string name="wifi_permissions_location_permission_off">Õigused asukoha tuvastamiseks on keelatud</string>
<string name="wifi_permissions_background_location_permission">Õigused asukoha tuvastamiseks taustal</string>
<string name="wifi_permissions_background_location_permission_label">Luba alati</string>
<string name="wifi_permissions_background_location_permission_on">Asukohaõigused on: %s</string>
<string name="wifi_permissions_background_location_permission_off">Asukohaõiguseid pole: %s</string>
<string name="wifi_permissions_background_location_disclaimer">%s kasutab asukohaandmeid (vaid WiFi SSID võrgutunnust) vaid sünkroniseerimise tagamiseks konkreetse WiFi-võrgu piires. See kehtib ka siis, kui sünkroniseerimine on seadistatud töötama taustal.</string>
<string name="wifi_permissions_background_location_disclaimer2">Kõik asukohaandmed (vaid WiFi SSId võrgutunnus) on kasutusel kohalikus nutiseadmes ega saadeta mitte kuhugile mujale.</string>
<string name="wifi_permissions_location_enabled">Asukohateenus on alati kasutusel</string>
<string name="wifi_permissions_location_enabled_on">Asukohateenus on lubatud</string>
<string name="wifi_permissions_location_enabled_off">Asukohateenus pole lubatud</string>
<!--AboutActivity-->
<string name="about_translations">Tõlked</string>
<string name="about_libraries">Teegid</string>
<string name="about_version">Versioon %1$s (%2$d)</string>
<string name="about_copyright">© Ricki Hirner, Bernhard Stockmann (bitfire web engineering GmbH) ja kaasautorid</string>
<string name="about_license_info_no_warranty">Selle rakenduse kasutamisega EI KAASNE MITTE ÜHTEGI GARANTIID. Tegemist on vaba ja avatud tarkvaraga ning sa võid seda levitada kindlate tingimuste alusel.</string>
<!--global settings-->
<string name="logging_couldnt_create_file">Logifaili loomine ei õnnestunud</string>
<string name="logging_notification_text">Nüüd logime kõiki %s rakenduse tegevusi</string>
<string name="logging_notification_view_share">Vaata/jaga</string>
<string name="logging_notification_disable">Lülita välja</string>
<!--AccountsScreen-->
<string name="navigation_drawer_subtitle">CalDAV/CardDAV sünkroniseerimise sobitaja</string>
<string name="navigation_drawer_about">Teave / litsents</string>
<string name="navigation_drawer_beta_feedback">Beetaversiooni tagasiside</string>
<string name="install_browser">Palun paigalda veebibrauser</string>
<string name="navigation_drawer_settings">Seadistused</string>
<string name="navigation_drawer_news_updates">Uudised ja uuendused</string>
<string name="navigation_drawer_tools">Tarvikud</string>
<string name="navigation_drawer_external_links">Välised lingid</string>
<string name="navigation_drawer_website">Veebisait</string>
<string name="navigation_drawer_manual">Käsiraamat</string>
<string name="navigation_drawer_faq">KKK</string>
<string name="navigation_drawer_managed">Organisatsioonide jaoks</string>
<string name="navigation_drawer_community">Kogukond</string>
<string name="navigation_drawer_support_project">Toeta projekti</string>
<string name="navigation_drawer_contribute">Osalemise viisid</string>
<string name="navigation_drawer_privacy_policy">Privaatsusreeglid</string>
<string name="account_list_welcome">Tere tulemast kasutama rakendust DAVx⁵!</string>
<string name="account_list_empty">Loo ühendus oma serveriga ja hoia kalendrid ning kontaktid sünkroniseerituna.</string>
<string name="accounts_sync_all">Sünkroniseeri kõik kasutajakontod</string>
<!--Sync warnings-->
<string name="sync_warning_no_notification_permission">Teavitused on välja lülitatud ja seega sünkroniseerimisvigade infot sa ei näe.</string>
<string name="sync_warning_no_internet">Automaatne sünkroniseerimine pole aktiivne (kontrollitud internetiühendus puudub)</string>
<string name="sync_warning_manage_connections">Halda ühendusi</string>
<string name="sync_warning_datasaver_enabled">Andmemahu piiraja on kasutusel. Taustal sünkroniseerimine võib toimida piirangutega.</string>
<string name="sync_warning_manage_datasaver">Halda andmemahu piirajat</string>
<string name="sync_warning_battery_saver_enabled">Akukasutuse piiraja on kasutusel. Taustal sünkroniseerimine võib toimida piirangutega.</string>
<string name="sync_warning_manage_battery_saver">Halda akukasutuse piirajat</string>
<string name="sync_warning_low_storage">Vaba andmeruumi napib. Android ei sünkroniseeri kohalikke muudatusi kohe, vaid järgmise regulaarse sünkroniseerimise ajal.</string>
<string name="sync_warning_manage_storage">Halda andmeruumi</string>
<string name="sync_warning_calendar_storage_disabled_title">Kalendri teenusepakkuja puudub. </string>
<string name="sync_warning_calendar_storage_disabled_description">Kas sa oled lülitanud välja süsteemse kalendri salvestusruumi rakenduse „Calendar storage“ välja?</string>
<string name="sync_warning_contacts_storage_disabled_title">Kontaktide teenusepakkuja puudub.</string>
<string name="sync_warning_contacts_storage_disabled_description">Kas sa oled lülitanud välja süsteemse kontaktide salvestusruumi rakenduse „Contacts storage“ välja?</string>
<string name="sync_warning_manage_apps">Halda rakendusi</string>
<!--RefreshCollectionsWorker-->
<string name="refresh_collections_worker_refresh_failed">Teenuse tuvastamine ei õnnestunud</string>
<string name="refresh_collections_worker_refresh_couldnt_refresh">Kogumike loendi uuendamine ei õnnestunud</string>
<!--Foreground service used by WorkManager on Android <12-->
<string name="foreground_service_notify_title">Töötame esiplaanil</string>
<string name="foreground_service_notify_text">See eelistus on vajalik sünkroniseerimiseks mõnedes seadmetes.</string>
<!--AppSettingsActivity-->
<string name="app_settings">Seadistused</string>
<string name="app_settings_debug">Silumine ja veaotsing</string>
<string name="app_settings_show_debug_info">Näita silumisteavet</string>
<string name="app_settings_show_debug_info_details">Vaata/jaga seadistuse üksikasju ja logisid</string>
<string name="app_settings_logging">Väga üksikasjalik logimine</string>
<string name="app_settings_logging_on">Logimine on kasutusel. Silumisteabe osana saad vaadata logisid.</string>
<string name="app_settings_logging_off">Logimine pole kasutusel</string>
<string name="app_settings_battery_optimization">Akukasutuse optimeerimine</string>
<string name="app_settings_battery_optimization_exempted">See rakendus ei allu akukasutuse optimeerimisele (soovitatav valik)</string>
<string name="app_settings_battery_optimization_optimized">Akukasutuse optimeerimise piirangud on kasutusel (mittesoovitatav valik)</string>
<string name="app_settings_connection">Ühendus</string>
<string name="app_settings_proxy">Proksiserveri tüüp</string>
<string-array name="app_settings_proxy_types">
<item>Süsteemi proksiserver</item>
<item>Proksiserver puudub</item>
<item>HTTP</item>
<item>SOCKS (Orboti jaoks)</item>
</string-array>
<string name="app_settings_proxy_host">Proksiserveri hostinimi</string>
<string name="app_settings_proxy_port">Proksiserveri port</string>
<string name="app_settings_security">Turvalisus</string>
<string name="app_settings_security_app_permissions">Rakenduse õigused</string>
<string name="app_settings_security_app_permissions_summary">Täpsusta sünkroniseerimiseks vajalike õigusi</string>
<string name="app_settings_distrust_system_certs">Ära usalda nutiseadme süsteemseid sertifikaate</string>
<string name="app_settings_distrust_system_certs_on">Süsteemsed ja kasutaja lisatud sertifitseerimiskeskused ei ole usaldatud</string>
<string name="app_settings_distrust_system_certs_off">Süsteemsed ja kasutaja lisatud sertifitseerimiskeskused on usaldatud (soovitatav valik)</string>
<string name="app_settings_distrust_system_certs_dialog_message">Kui see seadistus on aktiivne, siis operatsioonisüsteemis leiduvad sertifikaate ei loeta usaldusväärseteks. See tähendab, et iga kord pead sertifikaadiga käsitsi nõustuma (seda ka siis, kui server uuendab oma sertifikaate), vastasel juhul kasutajakonto seadistamine ja sünkroniseerimine ei toimi.</string>
<string name="app_settings_reset_certificates">Lähtesta (mitte)usaldatud sertifikaatide loend</string>
<string name="app_settings_reset_certificates_summary">Selle valikuga eemaldatakse kõik sinu lisatud sertifikaatide usaldusmärked</string>
<string name="app_settings_reset_certificates_success">Kõik sinu lisatud sertifikaatide usaldusmärked on eemaldatud</string>
<string name="app_settings_user_interface">Kasutajaliides</string>
<string name="app_settings_notification_settings">Teavituste seadistused</string>
<string name="app_settings_notification_settings_summary">Halda teavituskanaleid ja nende seadistusi</string>
<string name="app_settings_theme_title">Vali kujundus</string>
<string-array name="app_settings_theme_names">
<item>Süsteemi kujundus</item>
<item>Hele kujundus</item>
<item>Tume kujundus</item>
</string-array>
<string name="app_settings_reset_hints">Lähtesta vihjed</string>
<string name="app_settings_reset_hints_summary">Lülitab varem väljalülitatud vihtjete kuvamise uuesti sisse</string>
<string name="app_settings_reset_hints_success">Näitame jälle kõiki vihjeid</string>
<string name="app_settings_integration">Lõimimine</string>
<string name="app_settings_tasks_provider">Ülesannete rakendus</string>
<string name="app_settings_tasks_provider_none">Ühilduvat ülesannete rakendust ei leidu</string>
<string name="app_settings_unifiedpush">UnifiedPush (katseline)</string>
<string name="app_settings_unifiedpush_disable">Puudub (tõuketeenuseid pole)</string>
<string name="app_settings_unifiedpush_choose_distributor">Vali levitaja</string>
<string name="app_settings_unifiedpush_no_distributor">Ühtegi tõukesõnumite levitajat pole paigaldatud</string>
<string name="app_settings_unifiedpush_no_endpoint">Otspunkt on seadistamata</string>
<string name="app_settings_unifiedpush_ready">Valmis tõuketeadete vastuvõtmiseks %s vahendusel</string>
<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">Kasutajakonto on eemaldatud</string>
<string name="account_carddav">CardDAV</string>
<string name="account_caldav">CalDAV</string>
<string name="account_webcal">Webcal</string>
<string name="account_missing_permissions">Nende kogumike sünkroniseerimiseks on vajalikud täiendavad õigused.</string>
<string name="account_manage_permissions">Halda õigusi</string>
<string name="account_synchronize_now">Sünkroniseeri nüüd</string>
<string name="account_settings">Kasutajakonto seadistused</string>
<string name="account_rename">Muuda kasutajakonto nime</string>
<string name="account_rename_new_name_description">Salvestamata kohalik teave võib vahele jääda. Peale nime muutmist palun sünkroniseeri uuesti.</string>
<string name="account_rename_new_name">Kasutajakonto uus nimi</string>
<string name="account_rename_rename">Muuda nime</string>
<string name="account_rename_exists_already">Selline nimi on juba kasutusel</string>
<string name="account_rename_couldnt_rename">Kasutajakonto nime muutmine ei õnnestunud</string>
<string name="account_delete">Kustuta kasutajakonto</string>
<string name="account_delete_confirmation_title">Kas tõesti kustutame kasutajakonto?</string>
<string name="account_delete_confirmation_text">Sellega kustutame ka kõik aadresside, kalendrite ja ülesannete kohalikud koopiad.</string>
<string name="account_synchronize_this_collection">sünkroniseeri see kogumik</string>
<string name="account_read_only">ainult lugemisõigus</string>
<string name="account_calendar">kalender</string>
<string name="account_contacts">kontaktid</string>
<string name="account_journal">päevik</string>
<string name="account_task_list">ülesanded</string>
<string name="account_only_personal">Näita vaid isiklikke</string>
<string name="account_refresh_collections">Uuenda loendit</string>
<string name="account_webcal_external_app">Webcali tellimusi on võimalik sünkroniseerida väliste rakendustega.</string>
<string name="account_no_webcal_handler_found">Webcaliga ühilduvaid rakendusi ei leidu</string>
<string name="account_install_icsx5">Paigalda ICSx⁵</string>
<!--AddAccountActivity-->
<string name="login_title">Lisa kasutajakonto</string>
<string name="login_privacy_hint"><![CDATA[Kõik andmed liiguvad vaid sinu serveri ja sinu nutiseadme vahel. %1$s ei saada neid mitte kuhugile mujale. Lisateavet leiad <a href="%2$s">meie Privaatsusreeglitest</a>.]]></string>
<string name="login_generic_login">Üldine sisselogimine</string>
<string name="login_provider_login">Teenusepakkujakohane sisselogimine</string>
<string name="login_continue">Jätka</string>
<string name="login_login">Logi sisse</string>
<string name="login_type_email">Logi sisse e-posti aadressiga</string>
<string name="login_email_address">E-posti aadress</string>
<string name="login_email_address_error">Nõutav on korrektne e-posti aadress</string>
<string name="login_email_address_info"><![CDATA[E-posti aadressi domeeni alusel leiame alustuseks mõeldud võrguaadressi. <a href="%s">Teenused tuvastame</a> nimeserveri kirjete ning „.well-known“ tunnusaadresside abil.]]></string>
<string name="login_password">Salasõna</string>
<string name="login_password_hide">Peida salasõna</string>
<string name="login_password_show">Näita salasõna</string>
<string name="login_password_optional">Salasõna (kui on vaja)</string>
<string name="login_type_url">Logi sisse võrguaadressi ja kasutajanimega</string>
<string name="login_user_name">Kasutajanimi</string>
<string name="login_user_name_optional">Kasutajanimi (kui on vaja)</string>
<string name="login_base_url">Alustuseks mõeldud võrguaadress</string>
<string name="login_base_url_info"><![CDATA[Kontrollime alustuseks mõeldud võrguaadressi ka, aga lisaks <a href="%s">tuvastame teenuseid</a> nimeserveri kirjete ning „.well-known“ tunnusaadresside abil.]]></string>
<string name="login_select_certificate">Vali sertifikaat</string>
<string name="login_add_account">Lisa kasutajakonto</string>
<string name="login_account_name">Kasutajakonto nimi</string>
<string name="login_account_avoid_apostrophe">Ülakomade (\') kasutamine tundub mõnedes seadmetes tekitama probleeme.</string>
<string name="login_account_name_info">Kuna Android pruugib kasutajakonto nime sinu loodavate ürituste Korraldaja ehk ORGANIZER välja väärtustamiseks, siis soovitame, et sinu kasutajakonto nimi on sinu e-posti aadress. Palun arvesta, et sul ei saa olla kahte samanimelist kasutajakontot.</string>
<string name="login_account_contact_group_method">Kontaktgrupi meetod:</string>
<string name="login_account_name_required">Kasutajakonto nimi on nõutav</string>
<string name="login_account_name_already_taken">Selline nimi on juba kasutusel</string>
<string name="login_account_not_added">Kasutajakonto lisamine ei õnnestunud</string>
<string name="login_finish">Lõpeta</string>
<string name="login_type_advanced">Täiendavad sisselogimise seadistused</string>
<string name="login_no_client_certificate_optional">Kliendisertifikaat puudub (kui on vaja)</string>
<string name="login_client_certificate_selected">Kliendi sertifikaat: %s</string>
<string name="login_no_certificate_found">Kliendisertifikaati ei leidunud</string>
<string name="login_install_certificate">Paigalda sertifikaat</string>
<string name="login_fastmail">Fastmail</string>
<string name="login_fastmail_account">Fastmaili kasutajakonto</string>
<string name="login_fastmail_sign_in">Logi sisse Fastmaili kasutajakontoga</string>
<string name="login_type_google">Google\'i Kontaktid / Kalender</string>
<string name="login_google_account">Google\'i kasutajakonto</string>
<string name="login_google">Logi sisse Google\'i kasutajakontoga</string>
<string name="login_google_client_id">Klienditunnus (kui soovid lisada)</string>
<string name="login_google_client_privacy_policy"><![CDATA[%1$s teisaldab sinu Google\'i kontaktide ja kalendri andmeid vaid sünkroniseerimiseks selles seadmes. Lisateavet leiad meie <a href="%2$s">Privaatsusreeglitest</a>.]]></string>
<string name="login_google_client_limited_use"><![CDATA[%1$s järgib <a href="%2$s">Google\'i API teenuste kasutajaandmete poliitikat</a>, sealhulgas piiratud kasutuse nõudeid.]]></string>
<string name="login_oauth_couldnt_obtain_auth_code">Autoriseerimiskoodi saamine polnud võimalik</string>
<string name="login_type_nextcloud">Nextcloud</string>
<string name="login_nextcloud_login_with_nextcloud">Logi sisse Nextcloudi kontoga</string>
<string name="login_nextcloud_login_flow_text">Selle eelistusega käivitad Nextcloudi sisselogimise veebibrauseris.</string>
<string name="login_nextcloud_login_flow_server_address">Nextcloudi serveri aadress</string>
<string name="login_nextcloud_login_flow_sign_in">Logi sisse</string>
<string name="login_nextcloud_login_flow_no_login_url">Sisselogimise võrguaadressi tuvastamine polnud võimalik</string>
<string name="login_nextcloud_login_flow_no_login_data">Sisselogimisandmete tuvastamine polnud võimalik</string>
<string name="login_configuration_detection">Seadistuste tuvastamine</string>
<string name="login_querying_server">Palun oota, pärime andmeid serverist…</string>
<string name="login_no_service">Ei õnnestunud leida CalDAV või CardDAV teenust.</string>
<string name="login_no_service_info">Antud võrguaadress ei tundu olema ligipääsetav CalDAVi/CardDAVi võrguaadress ja teenuse tuvastamine ei õnnestunud.</string>
<string name="login_see_tested_services"><![CDATA[Lisateavet leidad oma teenusepakkuja juhendist ja <a href="%s">meie poolt testitud teenuste loendist</a> koos toimivate võrguaadressidega.]]></string>
<string name="login_check_credentials">Palun samuti topeltkontrolli autentimist (tavaliselt kasutajanimi ja salasõna)</string>
<string name="login_logs_available">Täiendav tehniline teade leidub logides.</string>
<string name="login_view_logs">Vaata logisid</string>
<!--AccountSettingsActivity-->
<string name="settings_sync">Sünkroniseerimine</string>
<string name="settings_sync_interval_contacts">Kontaktide sünkroniseerimise välp</string>
<string name="settings_sync_summary_manually">Vaid käsitsi</string>
<string name="settings_sync_summary_periodically" tools:ignore="PluralsCandidate">Iga %d minuti järel + kohalikud muudatused koheselt</string>
<string name="settings_sync_interval_calendars">Kalendrite sünkroniseerimise välp</string>
<string name="settings_sync_interval_tasks">Ülesannete sünkroniseerimise välp</string>
<string-array name="settings_sync_interval_names">
<item>Vaid käsitsi</item>
<item>Iga 15 minuti järel</item>
<item>Iga 30 minuti järel</item>
<item>Kord tunnis</item>
<item>Iga 2 tunni järel</item>
<item>Iga 4 tunni järel</item>
<item>Kord päevas</item>
</string-array>
<string name="settings_sync_wifi_only">Sünkroniseeri vaid WiFi ühendusega</string>
<string name="settings_sync_wifi_only_on">Sünkroniseerimine on lubatud vaid WiFi ühendusega</string>
<string name="settings_sync_wifi_only_off">Ühenduse liik pole oluline</string>
<string name="settings_sync_wifi_only_ssids">WiFi SSID piirangud</string>
<string name="settings_sync_wifi_only_ssids_on">Sünkroniseeri vaid %s võrgus</string>
<string name="settings_sync_wifi_only_ssids_off">Kasuta kõiki WiFi ühendusi</string>
<string name="settings_sync_wifi_only_ssids_message">Lubatud WiFi võrgunimede (SSID) komadega eraldatud loend (kui jätad tühjaks on kõik lubatud)</string>
<string name="settings_sync_wifi_only_ssids_permissions_required">WiFi SSID piirang vajab täiendavat saedistamist</string>
<string name="settings_sync_wifi_only_ssids_permissions_action">Halda</string>
<string name="settings_ignore_vpns">VPNi kasutamine eeldab, et võrguühendus toimib</string>
<string name="settings_ignore_vpns_on">VPN ilma toimiva ja kontrollitud internetiühenduseta pole piisav sünkroniseerimiseks (soovitatud)</string>
<string name="settings_ignore_vpns_off">VPN ilma toimiva ja kontrollitud internetiühenduseta on sünkroniseerimiseks piisav</string>
<string name="settings_authentication">Autentimine</string>
<string name="settings_username">Kasutajanimi</string>
<string name="settings_password">Salasõna või rakenduse salasõna</string>
<string name="settings_app_password_hint"><![CDATA[<a href="%1$s">Rakenduse salasõna</a> kasutamine peaks olema esimene eelistus.]]></string>
<string name="settings_new_password">Uus salasõna</string>
<string name="settings_password_summary">Uuenda salasõna vastavalt oma serveri juhendile.</string>
<string name="settings_reauthorize_oauth">Autoriseeri uuesti (OAuth)</string>
<string name="settings_reauthorize_oauth_summary">Kasuta olukorras, kus ligipääs on tühistatud</string>
<string name="settings_reauthorize_oauth_success">Autoriseerimine õnnestus</string>
<string name="settings_certificate_alias">Kliendi sertifikaat</string>
<string name="settings_certificate_alias_empty">Sertifikaati pole saadaval või paigaldatud</string>
<string name="settings_certificate_install">Paigalda sertifikaat</string>
<string name="settings_caldav">CalDAV</string>
<string name="settings_sync_time_range_past">Möödunud sündmuste ajapiir</string>
<string name="settings_sync_time_range_past_none">Kõik sündmused kuuluvad sünkroniseerimisele</string>
<plurals name="settings_sync_time_range_past_days">
<item quantity="one">Eira enam kui üks päev vanu sündmuseid</item>
<item quantity="other">Eira enam kui %d päeva vanu sündmuseid</item>
</plurals>
<string name="settings_sync_time_range_past_message">Sündmused, mis on vanemad, kui siin märgitud päevade arv, jäävad sünkroniseerimata (võib olla ka 0). Kõikide sündmuste sünkroniseerimiseks jäta tühjaks.</string>
<string name="settings_default_alarm">Vaikimisi meeldetuletus</string>
<plurals name="settings_default_alarm_on">
<item quantity="one">Vaikimisi meeldetuletus üks minutit enne sündmust</item>
<item quantity="other">Vaikimisi meeldetuletus %d minutit enne sündmust</item>
</plurals>
<string name="settings_default_alarm_off">Vaikimisi meeldetuletused puuduvad</string>
<string name="settings_default_alarm_message">Eelistus määrab, kas kasutame vaikimisi meeldetuletust sündmuste puhul, kus eraldi meeldetuletus on seadistamata. Aktiveerimiseks sisesta vaikimisi meeldetuletuse aeg minutites. Väljalülitamiseks jäta tühjaks.</string>
<string name="settings_manage_calendar_colors">Halda kalendrivärve</string>
<string name="settings_manage_calendar_colors_on">Kalendri värvid lähtestatakse igal sünkroniseerimisel</string>
<string name="settings_manage_calendar_colors_off">Muud rakendused võivad kalendrivärve seadistada</string>
<string name="settings_event_colors">Sündmuste värvide tugi</string>
<string name="settings_event_colors_on">Sündmuste värvid kuuluvad sünkroniseerimisele</string>
<string name="settings_event_colors_off">Sündmuste värvid ei kuulu sünkroniseerimisele</string>
<string name="settings_carddav">CardDAV</string>
<string name="settings_contact_group_method">Kontaktgrupi meetod</string>
<string-array name="settings_contact_group_method_entries">
<item>Grupid on eraldi vCard-kirjed</item>
<item>Grupid on kontaktikohased kategooriad</item>
</string-array>
<!--CreateAddressBookScreen, CreateCalendarScreen-->
<string name="create_addressbook">Loo aadressiraamat</string>
<string name="create_addressbook_maybe_not_supported">See server ei pruugi toetada aadressiraamatu loomist CardDAVi ühenduse abil.</string>
<string name="create_calendar">Loo kalender</string>
<string name="create_calendar_time_zone_optional">Vaikimisi ajavöönd (kui on vaja)</string>
<string name="create_calendar_time_zone_none"></string>
<string name="create_calendar_type">Võimalikud kalendrikirjed</string>
<string name="create_calendar_type_vevent">Sündmused</string>
<string name="create_calendar_type_vtodo">Ülesanded</string>
<string name="create_calendar_type_vjournal">Märkmed / päevik</string>
<string name="create_calendar_maybe_not_supported">See server ei pruugi toetada kalendri loomist CalDAVi ühenduse abil.</string>
<string name="create_collection_color">Värv</string>
<string name="create_collection_display_name">Pealkiri</string>
<string name="create_collection_home_set">Andmeruumi asukoht</string>
<string name="create_collection_description_optional">Kirjeldus (kui on vaja)</string>
<string name="create_collection_create">Loo</string>
<!--CollectionScreen-->
<string name="collection_datatype_contacts">kontaktid</string>
<string name="collection_datatype_events">sündmust</string>
<string name="collection_datatype_tasks">ülesanded</string>
<string name="collection_delete">Kustuta kogumik</string>
<string name="collection_delete_warning">See kogumik (%s) koos oma kõikide andmetega kustutatakse nüüd jäädavalt nii serverist, kui kohalikust nutiseadmest.</string>
<string name="collection_synchronization">Sünkroniseerimine</string>
<string name="collection_synchronization_on">Sünkroniseerimine on kasutusel</string>
<string name="collection_synchronization_off">Sünkroniseerimine pole kasutusel</string>
<string name="collection_read_only">Ainult lugemisõigus</string>
<string name="collection_read_only_by_server">Ainult lugemisõigus (serveri poolt)</string>
<string name="collection_read_only_by_setting">Ainult lugemisõigus (reeglite alusel)</string>
<string name="collection_read_only_forced">Ainult lugemisõigus (ainult kohalikus nutiseadmes)</string>
<string name="collection_read_write">Lugemis- ja kirjutamisõigus</string>
<string name="collection_title">Pealkiri</string>
<string name="collection_description">Kirjeldus</string>
<string name="collection_owner">Omanik</string>
<string name="collection_push_support">Tõuketeenuse tugi</string>
<string name="collection_push_web_push">Server teavitab tõuketeenuse toe olemasolust</string>
<string name="collection_push_subscribed_at">Tellitud %1$s, aegub %2$s</string>
<string name="collection_last_sync">Viimane sünkroniseerimine (%s)</string>
<string name="collection_url">Aadress (võrguaadress)</string>
<!--debugging and DebugInfoActivity-->
<string name="debug_info_title">Silumisteave</string>
<string name="debug_info_archive_caption">ZIP-arhiivifail</string>
<string name="debug_info_archive_subtitle">Sisaldab silumisteavet ja logisid</string>
<string name="debug_info_archive_text">Tõsta arhiiv uurimiseks arvutisse, saada huvilisele e-postiga või lisa veateatele meie veahalduses.</string>
<string name="debug_info_archive_share">Jaga arhiivi</string>
<string name="debug_info_attached">Sõnumile lisatud silumisteave (eeldab, et vastuvõttev rakendus oskab manuseid käsitleda).</string>
<string name="debug_info_http_error">HTTP-viga</string>
<string name="debug_info_server_error">Serveri viga</string>
<string name="debug_info_webdav_error">WebDAVi viga</string>
<string name="debug_info_io_error">Sisend-/väljundviga</string>
<string name="debug_info_http_403_description">Osapool keeldus päringule vastamast. Kontrolli asjaomaseid teenuseid ja tarvikuid ning üksikasjalikku teavet võid leida silumislogidest.</string>
<string name="debug_info_http_404_description">Soovitud teenust või tarvikud pole (enam) olemas. Kontrolli asjaomaseid teenuseid ja tarvikuid ning üksikasjalikku teavet võid leida silumislogidest.</string>
<string name="debug_info_http_5xx_description">Tekkis serveripoolne viga. Palun võta ühendust serveri haldajaga.</string>
<string name="debug_info_unexpected_error">Tekkis ootamatu viga. Lisainfot leiad silumisteabest.</string>
<string name="debug_info_view_details">Vaata üksikasju</string>
<string name="debug_info_subtitle">Silumisteave on kogutud</string>
<string name="debug_info_involved_caption">Seotud teenused ja tarvikud</string>
<string name="debug_info_involved_subtitle">Probleemi või veaga seotud teave</string>
<string name="debug_info_involved_remote">Serveris asuvad teenused ja tarvikud:</string>
<string name="debug_info_involved_local">Kohalikus nutiseadmes teenused ja tarvikud:</string>
<string name="debug_info_logs_caption">Logid</string>
<string name="debug_info_logs_subtitle">Saadaval on üksikasjalikud logid</string>
<string name="debug_info_logs_view">Vaata logisid</string>
<string name="debug_info_privacy_warning_title">Privaatsusteade</string>
<string name="debug_info_privacy_warning_description">Logid ja veaotsingu teave võivad sisaldada privaatset teavet. Nende andmete avalikul jagamisel palun arvesta sellega.</string>
<!--ExceptionInfoFragment-->
<string name="exception">Tekkis viga.</string>
<string name="exception_httpexception">Tekkis http-viga.</string>
<string name="exception_ioexception">Tekkis sisend-väljundviga.</string>
<string name="exception_show_details">Näita üksikasju</string>
<!--WebDAV accounts-->
<string name="webdav_mounts_title">WebDAVi haakepunktid</string>
<string name="webdav_mounts_quota_used_available">Kasutatud mahukvoot: %1$s / saadaval: %2$s</string>
<string name="webdav_mounts_share_content">Jaga sisu</string>
<string name="webdav_mounts_unmount">Eemalda haakimine</string>
<string name="webdav_add_mount_title">Lisa WebDAVi haakepunkt</string>
<string name="webdav_mounts_empty">Otseligipääs sinu failidele WebDAVi haakepunktist!</string>
<string name="webdav_add_mount_empty_more_info"><![CDATA[Vaata juhendist <a href="%1$s">kuidas WebDAVi haakepunktid toimivad</a>.]]></string>
<string name="webdav_add_mount_display_name">Kuvatav nimi</string>
<string name="webdav_add_mount_url">WebDAVi võrguaadress</string>
<string name="webdav_add_mount_url_invalid">Vigane võrguaadress</string>
<string name="webdav_add_mount_mountpoint_displayname">Haakepunkt ja kuvatav nimi</string>
<string name="webdav_add_mount_authentication">Autentimine</string>
<string name="webdav_add_mount_username">Kasutajanimi</string>
<string name="webdav_add_mount_password">Salasõna</string>
<string name="webdav_add_mount_username_optional">Kasutajanimi (kui on vaja)</string>
<string name="webdav_add_mount_password_optional">Salasõna (kui on vaja)</string>
<string name="webdav_add_mount_add">Lisa haakepunkt</string>
<string name="webdav_add_mount_no_support">Sellel võrguaadressil ei leidu WebDAVi teenust</string>
<string name="webdav_remove_mount_title">Eemalda haakepunkt</string>
<string name="webdav_remove_mount_text">Ühenduse andmed lähevad kaotsi, aga ühtegi faili ei kustutata.</string>
<string name="webdav_notification_access">Ligipääs WebDAVi failile</string>
<string name="webdav_notification_download">Laadime WebDAVi faili alla</string>
<string name="webdav_notification_upload">Laadime WebDAVi faili üles</string>
<string name="webdav_provider_root_title">WebDAVi haakepunkt</string>
<!--sync-->
<string name="sync_error_permissions">DAVx⁵ õigused</string>
<string name="sync_error_permissions_text">Vajalikud on täiendavad õigused</string>
<string name="sync_error_tasks_too_old">%s on liiga vana</string>
<string name="sync_error_tasks_required_version">Väikseim nõutav versioon: %1$s</string>
<string name="sync_error_authentication_failed">Autentimine ei õnnestunud (kontrolli, et kasutajanimi/salasõna oleksid õiged)</string>
<string name="sync_error_io">Võrgu- või sisend/väljundviga %s</string>
<string name="sync_error_http_dav">HTTP serveri viga %s</string>
<string name="sync_error_local_storage">Kohaliku salvestusruumi viga %s</string>
<string name="sync_error_retry_limit_reached">Pehme viga (korduspäringute arvu ülempiir on käes)</string>
<string name="sync_error_view_item">Vaata objekti</string>
<string name="sync_invalid_contact">Saime serverist vigase kontaktikirje</string>
<string name="sync_invalid_event">Saime serverist vigase sündmusekirje</string>
<string name="sync_invalid_task">Saime serverist vigase ülesandekirje</string>
<string name="sync_invalid_resources_ignoring">Eirame ühte või enamat teenust või tarvikut</string>
<string name="sync_notification_pending_push_title">Sünkroniseerimine on ootel</string>
<string name="sync_notification_pending_push_message">Serveris olevad andmed on muutunud</string>
<!--widgets-->
<string name="widget_sync_all">Sünkroniseeri kõik</string>
<string name="widget_sync_all_accounts">Sünkroniseeri kõik kasutajakontod</string>
<string name="widget_labeled_sync_label">Sildiga sünkroniseerimisnupp</string>
<string name="widget_icon_sync_label">Ikooniga sünkroniseerimisnupp</string>
<string name="widget_sync_description">Klõpsi sünkroniseerimise käsitsi käivitamiseks.</string>
<!--cert4android-->
</resources>

View File

@@ -1,42 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources xmlns:tools="http://schemas.android.com/tools">
<!--common strings-->
<string name="app_name">DAVx⁵</string>
<string name="account_title_address_book">DAVx⁵ Osoitekirja</string>
<string name="address_books_authority_title">Osoitekirjat</string>
<string name="help">Apua</string>
<string name="manage_accounts">Hallitse tilejä</string>
<string name="notification_channel_debugging">Debuggaus</string>
<string name="notification_channel_general">Muut tärkeät viestit</string>
<string name="notification_channel_sync">Synkronointi</string>
<string name="notification_channel_sync_errors">Synkronoinnin virheet</string>
<string name="notification_channel_sync_errors_desc">Huomattavat virheet jotka estävät synkronoinnin kuten palvelimen odottamattomat vastaukset </string>
<string name="notification_channel_sync_warnings">Synkronoinnin varoitukset</string>
<string name="notification_channel_sync_warnings_desc">Ei-kohtalokkaat synkronoinnin ongelmat kuten tietyt virheelliset tiedostot </string>
<string name="notification_channel_sync_io_errors">Verkko ja I/O virheet</string>
<string name="notification_channel_sync_io_errors_desc">Aikakatkaisut, yhteysvirheet, yms. (usein väliaikaisia)</string>
<!--IntroActivity-->
<!--PermissionsActivity-->
<!--WifiPermissionsActivity-->
<!--AboutActivity-->
<!--global settings-->
<!--AccountsActivity-->
<!--DavService-->
<!--ForegroundService-->
<!--AppSettingsActivity-->
<!--AccountActivity-->
<!--AddAccountActivity-->
<string name="login_type_email">Kirjaudu sähköpostilla</string>
<string name="login_email_address">Sähköpostiosoite</string>
<string name="login_password">Salasana</string>
<string name="login_type_url">Kirjaudu verkko-osoitteella ja käyttäjänimellä</string>
<string name="login_user_name">Käyttäjänimi</string>
<!--AccountSettingsActivity-->
<string name="settings_username">Käyttäjänimi</string>
<string name="settings_password">Salasana</string>
<!--collection management-->
<!--debugging and DebugInfoActivity-->
<!--ExceptionInfoFragment-->
<!--sync adapters-->
<!--cert4android-->
</resources>

View File

@@ -1,410 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources xmlns:tools="http://schemas.android.com/tools">
<!--common strings-->
<string name="account_invalid">Account inesistente (o cancellato)</string>
<string name="account_title_address_book">Rubrica DAVx⁵</string>
<string name="dialog_delete">Cancella</string>
<string name="dialog_remove">Elimina</string>
<string name="dialog_deny">Annulla</string>
<string name="dialog_enable">Attiva</string>
<string name="field_required">Questo campo è necessario</string>
<string name="help">Aiuto</string>
<string name="options_menu">Menu opzioni</string>
<string name="share">Condividi</string>
<string name="sync_started">Sincronizzazione avviata</string>
<string name="database_destructive_migration_title">Database danneggiato</string>
<string name="database_destructive_migration_text">Tutti gli account sono stati rimossi localmente.</string>
<string name="notification_channel_debugging">Debugging</string>
<string name="notification_channel_general">Altri messaggi importanti</string>
<string name="notification_channel_status">Messaggi di stato a bassa priorità</string>
<string name="notification_channel_sync">Sincronizzazione</string>
<string name="notification_channel_sync_errors">Errori di sincronizzazione</string>
<string name="notification_channel_sync_errors_desc">Errori importanti che bloccano la sincronizzazione, come risposte inattese del server</string>
<string name="notification_channel_sync_warnings">Avvisi di sincronizzazione</string>
<string name="notification_channel_sync_warnings_desc">Problemi di sincronizzazione non gravi come alcuni file non validi</string>
<string name="notification_channel_sync_io_errors">Errori di Rete e di I/O</string>
<string name="notification_channel_sync_io_errors_desc">Timeouts, problemi di connessione, ecc. (spesso temporanei)</string>
<!--IntroActivity-->
<string name="intro_slogan1">Tuoi i dati. Tua la scelta.</string>
<string name="intro_slogan2">Riprendi il controllo.</string>
<string name="intro_battery_title">Intervalli di sincronizzazione regolari.</string>
<string name="intro_battery_text">Per sincronizzare i dati a intervalli regolari, %s deve essere autorizzato a girare in background. Altrimenti Android può mettere in pausa gli aggiornamenti in qualunque momento.</string>
<string name="intro_battery_dont_show">Non ho bisogno di sincronizzare a intervalli di tempo regolari.*</string>
<string name="intro_autostart_title">%s compatibilità</string>
<string name="intro_autostart_dont_show">Ho settato le impostazioni richieste. Non ricordarmelo più.</string>
<string name="intro_leave_unchecked">* Lascia smarcato per fartelo ricordare dopo. Può essere reimpostato nelle impostazione dell\'app %s.</string>
<string name="intro_more_info">Maggiori informazioni</string>
<string name="intro_tasks_jtx_info"><![CDATA[Supporta la sincronizzazione di Attività, Diari e Note.]]></string>
<string name="intro_tasks_title">Supporto per le attività</string>
<string name="intro_tasks_text1">Se le attività sono supportate dal tuo server, possono essere sincronizzate con una app per attività supportata:</string>
<string name="intro_tasks_opentasks">OpenTasks</string>
<string name="intro_tasks_opentasks_info">Non sembra essere più sviluppato - non raccomandato.</string>
<string name="intro_tasks_tasks_org">Tasks.org</string>
<string name="intro_tasks_no_app_store">Nessun app store disponibile</string>
<string name="intro_tasks_dont_show">Non ho bisogno del supporto alle attività.*</string>
<string name="intro_open_source_title">Software open-source</string>
<string name="intro_open_source_text">Siamo felici che tu usi %s, che è un software open source. Lo sviluppo, la manutenzione e il supporto sono compiti duri. Per piacere prendi in considerazione di dare una mano (puoi farlo in molti modi) o una donazione. Sarebbe davvero apprezzato!</string>
<string name="intro_open_source_details">Come aiutare/donare</string>
<!--PermissionsActivity-->
<string name="permissions_title">Autorizzazioni</string>
<string name="permissions_text">%s richiede autorizzazioni per funzionare correttamente.</string>
<string name="permissions_all_title">Tutti i seguenti</string>
<string name="permissions_all_status_off">Usare questo per abilitare tutte le funzioni (consigliato)</string>
<string name="permissions_all_status_on">Concedi tutte le autorizzazioni</string>
<string name="permissions_contacts_title">Autorizzazioni per i contatti</string>
<string name="permissions_contacts_status_off">Non sincronizzare i contatti (sconsigliato)</string>
<string name="permissions_contacts_status_on">Possibilità di sincronizzare i contatti</string>
<string name="permissions_calendar_title">Autorizzazioni per il calendario</string>
<string name="permissions_calendar_status_off">Non sincronizzare il calendario (sconsigliato)</string>
<string name="permissions_calendar_status_on">Permette di sincronizzare il calendario</string>
<string name="permissions_notification_title">Autorizza notifiche</string>
<string name="permissions_notification_status_off">Notifiche disabilitate (non consigliato)</string>
<string name="permissions_notification_status_on">Notifiche attive</string>
<string name="permissions_opentasks_title">Autorizzazioni di OpenTasks</string>
<string name="permissions_tasksorg_title">Autorizzazioni delle attività</string>
<string name="permissions_tasks_status_on">Permette di sincronizzare le attività</string>
<string name="permissions_autoreset_title">Mantieni autorizzazioni</string>
<string name="permissions_autoreset_status_off">Le autorizzazioni possono essere reimpostate automaticamente (sconsigliato)</string>
<string name="permissions_autoreset_status_on">Le autorizzazioni non si reimposteranno automaticamente</string>
<string name="permissions_autoreset_instruction">Fai click su Autorizzazioni &gt; deseleziona \"Rimuovi autorizzazioni se l\'app non è in uso\"</string>
<string name="permissions_app_settings_hint">Se uno slider non funziona, vai a impostazioni app/ autorizzazioni.</string>
<string name="permissions_app_settings">Impostazioni app</string>
<!--WifiPermissionsActivity-->
<string name="wifi_permissions_label">Autorizzazioni per WiFi SSID</string>
<string name="wifi_permissions_intro">Per poter accedere al nome dell\'attuale nome del WIFI (SSID), devono essere soddfsfatte queste condizioni:</string>
<string name="wifi_permissions_location_permission">Autorizzazione precisa della localizzazione</string>
<string name="wifi_permissions_location_permission_on">Garantire l\'autorizzazione della posizione</string>
<string name="wifi_permissions_location_permission_off">Negare l\'autorizzazione della posizione</string>
<string name="wifi_permissions_background_location_permission">Autorizzazione della posizione in background</string>
<string name="wifi_permissions_background_location_permission_label">Permettere sempre</string>
<string name="wifi_permissions_background_location_permission_on">Permessi di localizzazione impostati a: %s</string>
<string name="wifi_permissions_background_location_permission_off">Permessi di localizzazione non impostati a: %s</string>
<string name="wifi_permissions_location_enabled">Posizione sempre disabilitata</string>
<string name="wifi_permissions_location_enabled_on">Servizio di posizione abiltato</string>
<string name="wifi_permissions_location_enabled_off">Servizio di posizione disabilitato</string>
<!--AboutActivity-->
<string name="about_translations">Traduzioni</string>
<string name="about_libraries">Librerie</string>
<string name="about_version">Versione %1$s (%2$d)</string>
<string name="about_copyright">© Ricki Hirner, Bernhard Stockmann (bitfire web engineering GmbH) e contibutori</string>
<string name="about_license_info_no_warranty">Il programma è distribuito SENZA ALCUNA GARANZIA. È software libero e può essere redistribuito sotto alcune condizioni.</string>
<!--global settings-->
<string name="logging_couldnt_create_file">Impossibile creare il file di log</string>
<string name="logging_notification_text">Adesso l\'accesso all\' %s delle attività </string>
<string name="logging_notification_view_share">Visualizza/condividi</string>
<string name="logging_notification_disable">Disabilita</string>
<!--AccountsScreen-->
<string name="navigation_drawer_subtitle">CalDAV/CardDAV adattatore di sincronizzazione</string>
<string name="navigation_drawer_about">Informazioni / Licenza</string>
<string name="navigation_drawer_beta_feedback">Feedback sulla beta</string>
<string name="install_browser">Installare un browser Web</string>
<string name="navigation_drawer_settings">Impostazioni</string>
<string name="navigation_drawer_news_updates">Notizie &amp; aggiornamenti</string>
<string name="navigation_drawer_tools">Strumenti</string>
<string name="navigation_drawer_external_links">Link esterni</string>
<string name="navigation_drawer_website">Sito web</string>
<string name="navigation_drawer_manual">Manuale</string>
<string name="navigation_drawer_faq">Domande Frequenti</string>
<string name="navigation_drawer_community">Comunità</string>
<string name="navigation_drawer_support_project">Supporta il progetto</string>
<string name="navigation_drawer_contribute">Come contribuire</string>
<string name="navigation_drawer_privacy_policy">Politica sulla riservatezza</string>
<string name="accounts_sync_all">Sincronizzazione di tutti gli account</string>
<!--Sync warnings-->
<string name="sync_warning_no_notification_permission">Notifiche non attive. Non sarai avvisato di eventuali errori di sincronizzazione</string>
<string name="sync_warning_manage_connections">Gestione connessioni</string>
<string name="sync_warning_datasaver_enabled">Risparmio dati attivo. La sincronizzazione in background è limitata,</string>
<string name="sync_warning_battery_saver_enabled">Risparmio energetico attivo. La sincronizzazione in background è limitata,</string>
<string name="sync_warning_manage_battery_saver">Gestisci risparmio energetico</string>
<string name="sync_warning_low_storage">Spazio di memorizzazione scarso. Androin non salverà immediatamente i cambiamente, ma alla prossima sincronizzazione programmata.</string>
<string name="sync_warning_manage_storage">Gestisci spazio di memorizzazione</string>
<!--RefreshCollectionsWorker-->
<string name="refresh_collections_worker_refresh_failed">Impossibile trovare il servizio</string>
<string name="refresh_collections_worker_refresh_couldnt_refresh">Impossibile aggiornare la lista delle raccolte</string>
<!--Foreground service used by WorkManager on Android <12-->
<string name="foreground_service_notify_title">Esecuzione in primo piano</string>
<string name="foreground_service_notify_text">Su alcuni dispositivi, questo è necessario per la sincronizzazione automatica.</string>
<!--AppSettingsActivity-->
<string name="app_settings">Impostazioni</string>
<string name="app_settings_debug">Debug</string>
<string name="app_settings_show_debug_info">Mostra informazioni di debug</string>
<string name="app_settings_logging">Log completo</string>
<string name="app_settings_logging_off">Log disabilitato</string>
<string name="app_settings_battery_optimization">Ottimizzazione batteria</string>
<string name="app_settings_connection">Connessione</string>
<string name="app_settings_proxy">Tipo di proxy</string>
<string-array name="app_settings_proxy_types">
<item>Predefinito di sistema</item>
<item>Nessun proxy</item>
<item>HTTP</item>
<item>SOCKS (per Orbot)</item>
</string-array>
<string name="app_settings_proxy_host">Nome host proxy</string>
<string name="app_settings_proxy_port">Porta proxy</string>
<string name="app_settings_security">Sicurezza</string>
<string name="app_settings_security_app_permissions">Autorizzazioni app</string>
<string name="app_settings_security_app_permissions_summary">Controlla le autorizzazioni per la sincronizzazione</string>
<string name="app_settings_distrust_system_certs">Non ti fidare dei certificati di sistema</string>
<string name="app_settings_distrust_system_certs_on">Le CA di sistema e quelle aggiunte dall\'utente non sono affidabili</string>
<string name="app_settings_distrust_system_certs_off">Le CA di sistema e quelle aggiunte dall\'utente sono affidabili (raccomandato)</string>
<string name="app_settings_reset_certificates">Reimposta la fiducia in tutti i certificati</string>
<string name="app_settings_reset_certificates_summary">Reimposta la fiducia nei certificati aggiunti</string>
<string name="app_settings_reset_certificates_success">Sono stati cancellati tutti i certificati aggiunti</string>
<string name="app_settings_user_interface">Interfaccia utente</string>
<string name="app_settings_notification_settings">Impostazioni di notifica</string>
<string name="app_settings_notification_settings_summary">Gestisci i canali di notifica e le loro impostazioni</string>
<string name="app_settings_theme_title">Seleziona il tema</string>
<string-array name="app_settings_theme_names">
<item> Sistema predefinito </item>
<item> Luce </item>
<item> Buio </item>
</string-array>
<string name="app_settings_reset_hints">Reimposta i suggerimenti</string>
<string name="app_settings_reset_hints_summary">Riabilita i suggerimenti precedentemente disabilitati</string>
<string name="app_settings_reset_hints_success">I suggerimenti verranno mostrati</string>
<string name="app_settings_integration">Integrazione</string>
<string name="app_settings_tasks_provider">Funzioni dell\'applicazione</string>
<string name="app_settings_tasks_provider_none">Nessuna applicazione compatibile con e funzionalità trovata</string>
<!--AccountScreen-->
<string name="account_carddav">CardDAV</string>
<string name="account_caldav">CalDAV</string>
<string name="account_webcal">Webcal</string>
<string name="account_missing_permissions">Per sincronizzare questi dati sono richiesti permessi aggiuntivi.</string>
<string name="account_manage_permissions">Gestisci permessi</string>
<string name="account_synchronize_now">Sincronizza adesso</string>
<string name="account_settings">Impostazioni account</string>
<string name="account_rename">Rinomina account</string>
<string name="account_rename_new_name_description">Dati locali non salvati potrebbero venir persi. Dopo il cambio nome è necessaria la ri-sincronizzazione.</string>
<string name="account_rename_new_name">Nuovo nome account</string>
<string name="account_rename_rename">Rinomina</string>
<string name="account_rename_exists_already">Nome account già usato</string>
<string name="account_rename_couldnt_rename">Impossibile rinominare l\'account</string>
<string name="account_delete">Elimina account</string>
<string name="account_delete_confirmation_title">Cancellare l\'account?</string>
<string name="account_delete_confirmation_text">Tutte le copie locali delle rubriche, dei calendari e degli elenchi attività verranno eliminate.</string>
<string name="account_synchronize_this_collection">Sincronizza questa raccolta</string>
<string name="account_read_only">sola lettura</string>
<string name="account_calendar">calendario</string>
<string name="account_contacts">contatti</string>
<string name="account_journal">diario</string>
<string name="account_task_list">attività</string>
<string name="account_only_personal">Mostra solo personale</string>
<string name="account_refresh_collections">Aggiorna lista</string>
<string name="account_webcal_external_app">Sottoscrizioni al Webcal possono essere sincronizzate con applicazioni esterne.</string>
<string name="account_no_webcal_handler_found">Non ho trovato nessuna applicazione abilitata per Webcal</string>
<string name="account_install_icsx5">Installa ICSx⁵</string>
<!--AddAccountActivity-->
<string name="login_title">Aggiungi account</string>
<string name="login_generic_login">Login generico</string>
<string name="login_provider_login">Login del Provider</string>
<string name="login_continue">Continua</string>
<string name="login_login">Login</string>
<string name="login_type_email">Accedi con indirizzo email</string>
<string name="login_email_address">Indirizzo email</string>
<string name="login_email_address_error">È necessario un indirizzo email valido</string>
<string name="login_email_address_info"><![CDATA[Viene usato il dominio dell\'email come URL base. <a href="%s">I servizi sono individuati </a> usando record DNS e le URL well-known.]]></string>
<string name="login_password">Password</string>
<string name="login_password_hide">Nascondi password</string>
<string name="login_password_show">Mostra password</string>
<string name="login_type_url">Accedi con URL e nome utente</string>
<string name="login_user_name">Nome utente</string>
<string name="login_base_url">Base URL</string>
<string name="login_base_url_info"><![CDATA[La URL base viene controllata direttamente, ma <a href="%s">i servizi sono individuati anche </a> usando record DNS records e le URL well-known.]]></string>
<string name="login_select_certificate">Seleziona certificato</string>
<string name="login_add_account">Aggiungi account</string>
<string name="login_account_name">Nome account</string>
<string name="login_account_avoid_apostrophe">L\'uso degli apostrofi (\') potrebbe causare problemi su alcuni dispositivi.</string>
<string name="login_account_name_info">Inserisci il tuo indirizzo email come nome dell\'account in quanto Android userà il nome dell\'account nel campo ORGANIZER degli eventi creati. Non è possibile avere due account con nome uguale.</string>
<string name="login_account_contact_group_method">Metodo del contact group:</string>
<string name="login_account_name_required">Richiesto il nome dell\'account</string>
<string name="login_account_name_already_taken">Nome account già usato</string>
<string name="login_type_advanced">Login avanzato</string>
<string name="login_client_certificate_selected">Certificato client: %s</string>
<string name="login_no_certificate_found">Nessun certificato trovato</string>
<string name="login_install_certificate">Installa il certificato</string>
<string name="login_type_google">Contatti Google / Calendario</string>
<string name="login_google_account">Account Google</string>
<string name="login_google">Accedi con Google</string>
<string name="login_google_client_id">ID Client (facoltativo)</string>
<string name="login_google_client_limited_use"><![CDATA[%1$s è conforme alla <a href="%2$s">Google API Services User Data Policy</a>, incluso il Limited Use requirements.]]></string>
<string name="login_oauth_couldnt_obtain_auth_code">Non posso ottenere il codice di autorizzazione</string>
<string name="login_type_nextcloud">Nextcloud</string>
<string name="login_nextcloud_login_with_nextcloud">Accedi con Nextcloud</string>
<string name="login_nextcloud_login_flow_text">Questo aprirà la pagina di login di Nextcloud nel browser.</string>
<string name="login_nextcloud_login_flow_server_address">Indirizzo del server Nextcloud</string>
<string name="login_nextcloud_login_flow_sign_in">Iscriviti</string>
<string name="login_nextcloud_login_flow_no_login_url">Non posso ottenere l\'URL di login</string>
<string name="login_nextcloud_login_flow_no_login_data">Non posso ottenere i dati di login</string>
<string name="login_configuration_detection">Rilevazione configurazione</string>
<string name="login_querying_server">Attendere, invio richiesta al server…</string>
<string name="login_no_service">Impossibile trovare servizi CalDAV o CardDAV.</string>
<string name="login_no_service_info">L\'URL base non sembra essere un URL CalDAV/CardDAV accessibile e i servizi di individuazione hanno fallito.</string>
<string name="login_check_credentials">Controlla attentamente i dati di autenticazione (normalmente username e password).</string>
<string name="login_logs_available">Informazioni tecniche aggiuntive sono reperibili nei log.</string>
<string name="login_view_logs">Vedi i registri</string>
<!--AccountSettingsActivity-->
<string name="settings_sync">Sincronizzazione</string>
<string name="settings_sync_interval_contacts">Intervallo sincr. Contatti</string>
<string name="settings_sync_summary_manually">Solo manualmente</string>
<string name="settings_sync_summary_periodically" tools:ignore="PluralsCandidate">Ogni %d minuti e a seguito di ogni cambiamento locale</string>
<string name="settings_sync_interval_calendars">Intervallo sincr. calendari</string>
<string name="settings_sync_interval_tasks">Intervallo sincr. attività</string>
<string-array name="settings_sync_interval_names">
<item>Solo manualmente</item>
<item>Ogni 15 minuti</item>
<item>Ogni 30 minuti</item>
<item>Ogni ora</item>
<item>Ogni 2 ore</item>
<item>Ogni 4 ore</item>
<item>Una volta al giorno</item>
</string-array>
<string name="settings_sync_wifi_only">Sincr. solo tramite WiFi</string>
<string name="settings_sync_wifi_only_on">La sincronizzazione è limitata alle connessioni WiFi</string>
<string name="settings_sync_wifi_only_off">Il tipo di connessione non è preso in considerazione</string>
<string name="settings_sync_wifi_only_ssids">Restrizione SSID WiFi</string>
<string name="settings_sync_wifi_only_ssids_on">Sincronizzeremo solo oltre %s</string>
<string name="settings_sync_wifi_only_ssids_off">Verranno utilizzate tutte le connessioni WIFI </string>
<string name="settings_sync_wifi_only_ssids_message">Nomi (SSID) delle reti WiFi autorizzate separati da virgola (lascia vuoto per autorizzarle tutte)</string>
<string name="settings_sync_wifi_only_ssids_permissions_required">Le restrizioni del SSID WIFI richiedono ulteriori impostazioni</string>
<string name="settings_sync_wifi_only_ssids_permissions_action">Riuscire</string>
<string name="settings_ignore_vpns">La VPN richiede connessione internet</string>
<string name="settings_ignore_vpns_on">La VPN senza una connessione internet validata non è sufficiente per lanciare la sincronizzazione (raccomandato)</string>
<string name="settings_ignore_vpns_off">La VPN senza una connessione internet validata è sufficiente per lanciare la sincronizzazione</string>
<string name="settings_authentication">Autenticazione</string>
<string name="settings_username">Nome utente</string>
<string name="settings_new_password">Nuova password</string>
<string name="settings_password_summary">Aggiorna la password come sul tuo server.</string>
<string name="settings_certificate_alias">Certificato client</string>
<string name="settings_certificate_alias_empty">Nessun certificato disponibile o selezionato</string>
<string name="settings_certificate_install">Installa il certificato</string>
<string name="settings_caldav">CalDAV</string>
<string name="settings_sync_time_range_past">Limite di tempo per gli eventi trascorsi</string>
<string name="settings_sync_time_range_past_none">Verranno sincronizzati tutti gli eventi</string>
<plurals name="settings_sync_time_range_past_days">
<item quantity="one">Eventi più vecchi di un giorno saranno ignorati</item>
<item quantity="many">Eventi più vecchi di %d giorni saranno ignorati</item>
<item quantity="other">Eventi più vecchi di %d giorni saranno ignorati</item>
</plurals>
<string name="settings_sync_time_range_past_message">Eventi più vecchi di questo numero di giorni verranno ignorati(può anche essere 0). Lasciare in bianco per sincronizzare tutti gli eventi.</string>
<string name="settings_default_alarm">Promemoria predefinito</string>
<plurals name="settings_default_alarm_on">
<item quantity="one">Promemoria predefinito un minuto prima dell\'evento</item>
<item quantity="many">Promemoria predefinito %d minuti prima dell\'evento</item>
<item quantity="other">Promemoria predefinito %d minuti prima dell\'evento</item>
</plurals>
<string name="settings_default_alarm_off">Nessun promemoria di default creato</string>
<string name="settings_default_alarm_message">Indicare il numero di minuti che si desidera per il promemoria predefinito.
Lasciare vuoto per non creare un promemoria predefinito.</string>
<string name="settings_manage_calendar_colors">Cambia il colore del calendario</string>
<string name="settings_manage_calendar_colors_on">I colori del calendario sono resettati ad ogni sincronizzazione</string>
<string name="settings_manage_calendar_colors_off">I colori del calendario possono essere scelti da altre applicazioni</string>
<string name="settings_event_colors">Supporto colore dell\'evento</string>
<string name="settings_event_colors_on">I colori degli eventi sono sincronizzati</string>
<string name="settings_event_colors_off">I colori degli eventi non sono sicnronizzati</string>
<string name="settings_carddav">CardDAV</string>
<string name="settings_contact_group_method">Organizzazione dei gruppi di contatto</string>
<string-array name="settings_contact_group_method_entries">
<item>I gruppi sono vCards separate</item>
<item>I gruppi sono categorie per ogni contatto</item>
</string-array>
<!--CreateAddressBookScreen, CreateCalendarScreen-->
<string name="create_addressbook">Crea rubrica</string>
<string name="create_addressbook_maybe_not_supported">La creazione di rubriche tramitte CardDAV potrebbe non essere supportata dal server.</string>
<string name="create_calendar">Crea calendario</string>
<string name="create_calendar_time_zone_none"></string>
<string name="create_calendar_type">Possibili voci del calendario</string>
<string name="create_calendar_type_vevent">Eventi</string>
<string name="create_calendar_type_vtodo">Attività</string>
<string name="create_calendar_type_vjournal">Note / diario</string>
<string name="create_calendar_maybe_not_supported">La creazione do calendari tramite CalDAV potrebbe non essere supportata dal server.</string>
<string name="create_collection_color">Colore</string>
<string name="create_collection_display_name">Titolo</string>
<string name="create_collection_home_set">Percorso di archiviazione</string>
<string name="create_collection_create">Crea</string>
<!--CollectionScreen-->
<string name="collection_datatype_contacts">contatti</string>
<string name="collection_datatype_tasks">attività</string>
<string name="collection_delete">Elimina raccolta</string>
<string name="collection_delete_warning">Questa raccolta (%s) e tutti i suoi dati saranno rimossi definitivamente, sia localmente che sul server.</string>
<string name="collection_synchronization">Sincronizzazione</string>
<string name="collection_synchronization_on">Sincronizzazione attivata</string>
<string name="collection_synchronization_off">Sincronizzazione disattivata</string>
<string name="collection_read_only">Sola lettura</string>
<string name="collection_read_only_by_server">Sola lettura (dal server)</string>
<string name="collection_read_only_forced">Sola lettura (locale)</string>
<string name="collection_read_write">Lettura/scrittura</string>
<string name="collection_title">Titolo</string>
<string name="collection_description">Descrizione</string>
<string name="collection_owner">Proprietario</string>
<string name="collection_push_support">Supporto push</string>
<string name="collection_last_sync">Ultima sincronizzazione %s</string>
<string name="collection_url">Indirizzo (URL)</string>
<!--debugging and DebugInfoActivity-->
<string name="debug_info_title">Informazioni di debug</string>
<string name="debug_info_archive_caption">Archivio ZIP</string>
<string name="debug_info_archive_subtitle">Contiene informazioni sui debug e sugli accessi</string>
<string name="debug_info_archive_text">Condividi l\'archivio per trasferirlo ad un computer, per inviarlo tramite email o per fissarlo ad un ticket di supporto.</string>
<string name="debug_info_archive_share">Condividi l\'archivio</string>
<string name="debug_info_attached">Informazioni sul debug fissate a questo messaggio (richiede un supporto di fissaggio dell\'applicazione di supporto). </string>
<string name="debug_info_http_error">Errore HTTP</string>
<string name="debug_info_server_error">Errore del Server</string>
<string name="debug_info_webdav_error">Errore WebDAV</string>
<string name="debug_info_io_error">Errore I/O</string>
<string name="debug_info_http_403_description">La richiesta è stata negata. Controlla le fonti coinvolte e le informazioni debug per dettagli.</string>
<string name="debug_info_http_404_description">La fonte richiesta non esiste (più). Controlla le fonti coinvolte e le informazioni debug per dettagli.</string>
<string name="debug_info_http_5xx_description">Si è verificato un problema del server. Per favore contatta il tuo server di supporto.</string>
<string name="debug_info_unexpected_error">Si è verificato un errore inaspettato. Vedi le informazioni di debug per maggiori dettagli.</string>
<string name="debug_info_view_details">Vedi dettagli</string>
<string name="debug_info_subtitle">Sono state raccolte informazioni di debug</string>
<string name="debug_info_involved_caption">Fonti coinvolte</string>
<string name="debug_info_involved_subtitle">Collegate con il problema</string>
<string name="debug_info_involved_remote">Fonti remote:</string>
<string name="debug_info_involved_local">Fonti locali:</string>
<string name="debug_info_logs_caption">Registri</string>
<string name="debug_info_logs_subtitle">Sono disponibili registri verbali</string>
<string name="debug_info_logs_view">Vedi i registri</string>
<!--ExceptionInfoFragment-->
<string name="exception">Si è verificato un errore.</string>
<string name="exception_httpexception">Si è verificato un errore HTTP.</string>
<string name="exception_ioexception">Si è verificato un errore di I/O.</string>
<string name="exception_show_details">Mostra dettagli</string>
<!--WebDAV accounts-->
<string name="webdav_mounts_title">Installazioni WebDAV</string>
<string name="webdav_mounts_quota_used_available">Quantità utilizzata: %1$s / disponibile: %2$s</string>
<string name="webdav_mounts_share_content">Condividi i contenuti</string>
<string name="webdav_mounts_unmount">Disinstallazioni</string>
<string name="webdav_add_mount_title">Aggiungi installazioni WedDAV</string>
<string name="webdav_mounts_empty">Accedi direttamente ai tuoi file nel cloud aggiungendo un supporto WebDAV!</string>
<string name="webdav_add_mount_display_name">Nome del display</string>
<string name="webdav_add_mount_url">URL WebDVA</string>
<string name="webdav_add_mount_url_invalid">URL non valido</string>
<string name="webdav_add_mount_authentication">Autenticazione</string>
<string name="webdav_add_mount_username">Nome utente</string>
<string name="webdav_add_mount_password">Password</string>
<string name="webdav_add_mount_add">Aggiungi installazioni</string>
<string name="webdav_add_mount_no_support">Nessun servizio WebDAV a questo URL</string>
<string name="webdav_remove_mount_title">Rimuovi punto di mont</string>
<string name="webdav_remove_mount_text">I dettagli della connessione saranno perduti, ma nessun file verrà cancellato.</string>
<string name="webdav_notification_access">File di accesso WebDAV</string>
<string name="webdav_notification_download">File di download WebDAV</string>
<string name="webdav_notification_upload">Caricare file WebDAV</string>
<string name="webdav_provider_root_title">Installazione WebDAV</string>
<!--sync-->
<string name="sync_error_permissions">Autorizzazioni DAVx⁵</string>
<string name="sync_error_permissions_text">Autorizzazioni addizionali richieste</string>
<string name="sync_error_tasks_too_old">%s troppo vecchio</string>
<string name="sync_error_tasks_required_version">Versione minima richiesta %1$s</string>
<string name="sync_error_authentication_failed">Autenticazione fallita (controlla credenziali login)</string>
<string name="sync_error_io">Errore di rete o di I/O %s</string>
<string name="sync_error_http_dav">Errore server HTTP %s</string>
<string name="sync_error_local_storage">Errore di archiviazione locale %s</string>
<string name="sync_error_view_item">Visualizza oggetto</string>
<string name="sync_invalid_contact">Contatto non valido ricevuto dal server</string>
<string name="sync_invalid_event">Evento non valido ricevuto dal server</string>
<string name="sync_invalid_task">Attività non valida ricevuta dal server</string>
<string name="sync_invalid_resources_ignoring">Una o più risorse non valide ignorate</string>
<!--widgets-->
<string name="widget_sync_all">Sincronizza tutto</string>
<string name="widget_sync_all_accounts">Sincronizzazione di tutti gli account</string>
<!--cert4android-->
</resources>

View File

@@ -1,418 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources xmlns:tools="http://schemas.android.com/tools">
<!--common strings-->
<string name="account_invalid">ანგარიში (აღარ) არსებობს</string>
<string name="account_title_address_book">DAVx⁵ მისამართთა წიგნაკი</string>
<string name="dialog_delete">წაშლა</string>
<string name="dialog_remove">ამოშლა</string>
<string name="dialog_deny">გაუქმება</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>
<string name="database_destructive_migration_text">ყველა ანგარიში წაშლილ იქნა ადგილობრივად.</string>
<string name="notification_channel_debugging">დებაგი</string>
<string name="notification_channel_general">სხვა მნიშვნელოვანი შეტყობინებები</string>
<string name="notification_channel_status">დაბალი პრიორიტეტის სტატუსის შეტყობინებები</string>
<string name="notification_channel_sync">სინქრონიზაცია</string>
<string name="notification_channel_sync_errors">სინქრონიზაციის შეცდომები</string>
<string name="notification_channel_sync_errors_desc">მნიშვნელოვანი შეცდომები, რომლებიც აჩერებს სინქრონიზაციას, მაგ., მოულოდნელი სერვერის პასუხები</string>
<string name="notification_channel_sync_warnings">სინქრონიზაციის გაფრთხილებები</string>
<string name="notification_channel_sync_warnings_desc">არა-ლეტალური სინქრონიზაციის პრობლემები, როგორც ზოგი არასწორი ფაილი</string>
<string name="notification_channel_sync_io_errors">ქსელის ან ჩაწერა/წაკითხვის შეცდომები</string>
<string name="notification_channel_sync_io_errors_desc">ვადის გასვლა, კავშირის პრობლემები, სხვა (ხშირად დროებითი)</string>
<!--IntroActivity-->
<string name="intro_slogan1">თქვენი მონაცემები. თქვენი არჩევანი.</string>
<string name="intro_slogan2">აიღეთ კონტროლი.</string>
<string name="intro_battery_title">რეგულარული სინქრონიზაციის ინტერვალები</string>
<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_dont_show">მე შევცვალე საჭირო პარამეტრები. აღარ შემახსენოთ.*</string>
<string name="intro_leave_unchecked">* დატოვეთ მოუნიშნელად მოგვიანებით შესახსენებლად. შეიძლება ჩამოგდებულ იქნას აპის პარამეტრებში /%s.</string>
<string name="intro_more_info">მეტი ინფორმაცია</string>
<string name="intro_tasks_jtx">jtx Board</string>
<string name="intro_tasks_jtx_info"><![CDATA[Supports sync of Tasks, Journals and Notes.]]></string>
<string name="intro_tasks_title">დავალებების მხარდაჭერა</string>
<string name="intro_tasks_text1">თუ დავალებები მხარდაჭერილია თქვენი სერვერის მიერ, მათი სინქრონიზირება შეიძლება მხარდაჭერილი დავალებათა აპით:</string>
<string name="intro_tasks_opentasks">OpenTasks</string>
<string name="intro_tasks_opentasks_info">აღარ მიმდინარეობს განვითარება - არ არის რეკომენდებული.</string>
<string name="intro_tasks_tasks_org">Tasks.org</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>
<!--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">დავალებების უფლებები</string>
<string name="permissions_tasks_status_off">დავალებების სინქრონიზაციის გარეშე</string>
<string name="permissions_tasks_status_on">დავალებების სინქრონიზაცია შესაძლებელია</string>
<string name="permissions_autoreset_title">Keep-ის უფლებები</string>
<string name="permissions_autoreset_status_off">უფლებები შეიძლება ავტომატურად ჩამოიყაროს (არა რეკომენდებული)</string>
<string name="permissions_autoreset_status_on">უფლებები ავტომატურად არ ჩამოიყრება</string>
<string name="permissions_autoreset_instruction">შეამოწმეთ უფლებები &gt; მოხსენით \"უფლებების ამოშლა, თუ აპი არ გამოიყენება\"-ს მონიშვნა</string>
<string name="permissions_app_settings_hint">თუ გადამრთველი არ მუშაობს, გამოიყენეთ აპის პარამეტრები / უფლებები.</string>
<string name="permissions_app_settings">აპის პარამეტრები</string>
<!--WifiPermissionsActivity-->
<string name="wifi_permissions_label">WiFi SSID-ს უფლებები</string>
<string name="wifi_permissions_intro">რათა მიწვდეთ მიმდინარე WiFi-ს სახელს (SSID), ეს პირობები უნდა შესრულდეს:</string>
<string name="wifi_permissions_location_permission">ზუსტი ადგილმდებარეობის უფლება</string>
<string name="wifi_permissions_location_permission_on">ადგილმდებარეობის უფლება დართულია</string>
<string name="wifi_permissions_location_permission_off">ადგილმდებარეობის უფლება უარყოფილია</string>
<string name="wifi_permissions_background_location_permission">ფონური ადგილმდებარეობის უფლება</string>
<string name="wifi_permissions_background_location_permission_label">ყოველთვის დაშვება</string>
<string name="wifi_permissions_background_location_permission_on">ადგილმდებარეობის უფლების მნიშვნელობა: %s</string>
<string name="wifi_permissions_background_location_permission_off">ადგილმდებარეობის უფლება არ არის შემდეგი: %s</string>
<string name="wifi_permissions_location_enabled">ადგილმდებარეობა ყოველთვის ჩართულია</string>
<string name="wifi_permissions_location_enabled_on">ადგილმდებარეობის სერვისი ჩართულია</string>
<string name="wifi_permissions_location_enabled_off">ადგილმდებარეობის სერვისი გათიშულია</string>
<!--AboutActivity-->
<string name="about_translations">თარგმანი</string>
<string name="about_libraries">ბიბლიოთეკები</string>
<string name="about_version">ვერსია %1$s (%2$d)</string>
<string name="about_copyright">© Ricki Hirner, Bernhard Stockmann (bitfire web engineering GmbH) და მონაწილეები</string>
<string name="about_license_info_no_warranty">ამ პროგრამას არ აქვს არანაირი გარანტია. იგი არის უფასო პროგრამული უზრუნველყოფა, ხოლო თქვენ შეგეძლეიათ იგი გაავრცელოთ გარკვეული პირობების გათვალისწინებით.</string>
<!--global settings-->
<string name="logging_couldnt_create_file">ჟურნალის ფაილი ვერ შეიქმნა</string>
<string name="logging_notification_text">აწი მიმდინარეობს ყველა %s აქტივობის ჟურნალში ჩაწერა</string>
<string name="logging_notification_view_share">ნახვა/გაზიარება</string>
<string name="logging_notification_disable">გათიშვა</string>
<!--AccountsScreen-->
<string name="navigation_drawer_subtitle">CalDAV/CardDAV სინქრონიზაციის ადაპტერი</string>
<string name="navigation_drawer_about">შესახებ / ლიცენზია</string>
<string name="navigation_drawer_beta_feedback">ბეტას უკუკავშირი</string>
<string name="install_browser">გთხოვთ, დააყენოთ ვებ ბრაუზერი</string>
<string name="navigation_drawer_settings">პარამეტრები</string>
<string name="navigation_drawer_news_updates">ახალი ამბები &amp; განახლებები</string>
<string name="navigation_drawer_tools">ხელსაწყოები</string>
<string name="navigation_drawer_external_links">გარე ბმულები</string>
<string name="navigation_drawer_website">ვებ საიტი</string>
<string name="navigation_drawer_manual">ინსტრუქცია</string>
<string name="navigation_drawer_faq">ხდკ</string>
<string name="navigation_drawer_community">საზოგადოება</string>
<string name="navigation_drawer_support_project">პროექტის მხარდაჭერა</string>
<string name="navigation_drawer_contribute">როგორ შევიტანო ღვაწლი</string>
<string name="navigation_drawer_privacy_policy">პირადულობის პოლიტიკა</string>
<string name="accounts_sync_all">ყველა ანგარიშის სინქრონიზაცია</string>
<!--Sync warnings-->
<string name="sync_warning_no_notification_permission">შეტყობინებები გათიშული. თქვენ არ მიიღებთ შეტყობინებებს სიქნრონიზაციის შეცდომების შესახებ.</string>
<string name="sync_warning_manage_connections">კავშირების მართვა</string>
<string name="sync_warning_datasaver_enabled">გააქტიურებულია მონაცემთა შემნახველი. ფონური სინქრონიზაცია შეზღუდულია.</string>
<string name="sync_warning_manage_datasaver">მონაცემთა შემნახველის მართვა</string>
<string name="sync_warning_battery_saver_enabled">გფააქტიურებულია კვების ელემენტის შემნახველი. სინქრონიზაცია შეიძლება შეზღუდულ იქნას.</string>
<string name="sync_warning_manage_battery_saver">კვების ელემენტის შემნახველის მართვა</string>
<string name="sync_warning_low_storage">მეხსიერება ცოტა დარჩა. Android არ დაასინქრონიზირებს ადგილობრივ ცვლილებებს დაუყონებლივ, ხოლო დაასინქრონიზირებს შემდეგი რეგულარული სინქრონიზაციის დროს.</string>
<string name="sync_warning_manage_storage">მეხსიერების მართვა</string>
<!--RefreshCollectionsWorker-->
<string name="refresh_collections_worker_refresh_failed">სერვისის აღმოჩენა ჩაიშალა</string>
<string name="refresh_collections_worker_refresh_couldnt_refresh">კოლექციათა სიის განახლება ვერ მოხერხდა</string>
<!--Foreground service used by WorkManager on Android <12-->
<string name="foreground_service_notify_title">მუშაობს ფონში</string>
<string name="foreground_service_notify_text">ზოგ მოწყობილობაზე, ეს საჭიროა ავტომატური სინქრონიზაციისთვის.</string>
<!--AppSettingsActivity-->
<string name="app_settings">პარამეტრები</string>
<string name="app_settings_debug">დებაგი</string>
<string name="app_settings_show_debug_info">დებაგის ინფორმაციის ჩვენება</string>
<string name="app_settings_logging">დეტალური ჟურნალში ჩაწერა</string>
<string name="app_settings_logging_off">ჟურნალში ჩაწერა გათიშულია</string>
<string name="app_settings_battery_optimization">კვების ელემენტის ოპტიმიზაცია</string>
<string name="app_settings_battery_optimization_exempted">აპი გამორიცხულია (რეკომენდებულია)</string>
<string name="app_settings_battery_optimization_optimized">გამოიყენება კვების ელემენტის შეზღუდვები (არა რეკომენდებულია)</string>
<string name="app_settings_connection">კავშირი</string>
<string name="app_settings_proxy">პროქსის ტიპი</string>
<string-array name="app_settings_proxy_types">
<item>ნაგულისხმევი სისტემის მიერ</item>
<item>პროქსის გარეშე</item>
<item>HTTP3</item>
<item>SOCKS (Orbot-სთვის)</item>
</string-array>
<string name="app_settings_proxy_host">პროქსის ჰოსტის სახელი</string>
<string name="app_settings_proxy_port">პროქსის პორტი</string>
<string name="app_settings_security">უსაფრთხოება</string>
<string name="app_settings_security_app_permissions">აპის ეფლებები</string>
<string name="app_settings_security_app_permissions_summary">გადახედეთ სინქრონიზაციისთვის საჭირო ეფლებებს</string>
<string name="app_settings_distrust_system_certs">სისტემური სერთიფიკატების ნდობის გაუქმება</string>
<string name="app_settings_distrust_system_certs_on">სისტემური და მომხმარებლის მიერ დამატებული სერთიფიცირების ავტორიტეტების ნდობა არ იქნება</string>
<string name="app_settings_distrust_system_certs_off">სისტემური და მომხმარებლის მიერ დამატებული სერთიფიცირების ავტორიტეტების ნდობა იქნება (რეკომენდებული)</string>
<string name="app_settings_reset_certificates">(არა) ნდობითი სერთიფიკატების ჩამოყრა</string>
<string name="app_settings_reset_certificates_summary">ნდობის ჩამოყრა ყველა კერძო სერთიფიკატზე</string>
<string name="app_settings_reset_certificates_success">ყველა კერძო სერთიფიკატი გასუფთავდა</string>
<string name="app_settings_user_interface">მომხმარებლის ინტერფეისი</string>
<string name="app_settings_notification_settings">შეტყობინებების პარამეტრები</string>
<string name="app_settings_notification_settings_summary">შეტყობინებების არხების და პარამეტრების მართვა</string>
<string name="app_settings_theme_title">აირჩიეთ თემა</string>
<string-array name="app_settings_theme_names">
<item>სისტემის მიერ ნაგულისხმევი</item>
<item>ღია</item>
<item>მუქი</item>
</string-array>
<string name="app_settings_reset_hints">მითითებების ჩამოყრა</string>
<string name="app_settings_reset_hints_summary">თავიდან ააქტიურებს მითითებებს, რომლებიც დამალულ იქნა წარსულში</string>
<string name="app_settings_reset_hints_success">ყველა მითითება თავიდან იქნება ნაჩვენები</string>
<string name="app_settings_integration">ინტეგრაცია</string>
<string name="app_settings_tasks_provider">დავალებათა აპი</string>
<string name="app_settings_tasks_provider_none">თავსებადი დავალებათა აპი ვერ მოიძებნა</string>
<!--AccountScreen-->
<string name="account_carddav">CardDAV</string>
<string name="account_caldav">CalDAV</string>
<string name="account_webcal">Webcal</string>
<string name="account_missing_permissions">საჭიროა დამატებითი უფლებები ამ კოლექციების სინქრონიზაციისთვის.</string>
<string name="account_manage_permissions">უფლებების მართვა</string>
<string name="account_synchronize_now">ახლავე სინქრონიზირება</string>
<string name="account_settings">ანგარიშის პარამეტრები</string>
<string name="account_rename">ანგარიშის სახელის შეცვლა</string>
<string name="account_rename_new_name_description">შეუნახავი ადგილობრივი მონაცემები შეიძლება გაუქმებულ იქნას. საჭიროა თავიდან სინქრონიზირება სახელის შეცვლის შემდეგ.</string>
<string name="account_rename_new_name">ახალი ანგარიშის სახელი</string>
<string name="account_rename_rename">სახელის შეცვლა</string>
<string name="account_rename_exists_already">ანგარიშის სახელი უკვე დაკავებულია</string>
<string name="account_rename_couldnt_rename">ანგარიშის სახელის შეცვლა ვერ მოხერხდა</string>
<string name="account_delete">ანგარიშის წაშლა</string>
<string name="account_delete_confirmation_title">მართლა წაიშალოს ანგარიში?</string>
<string name="account_delete_confirmation_text">წაიშლება მისამართთა წიგნაკების, კალენდრების და დავალებათა სიების ყველა ადგილობრივი ასლი.</string>
<string name="account_synchronize_this_collection">ამ კოლექციის სინქრონიზირება</string>
<string name="account_read_only">მხოლოდ წაკითხვადი</string>
<string name="account_calendar">კალენდარი</string>
<string name="account_contacts">კონტაქტები</string>
<string name="account_journal">ჟურნალი</string>
<string name="account_task_list">დავალებები</string>
<string name="account_only_personal">მხოლოდ პირადის ჩვენება</string>
<string name="account_refresh_collections">სიის განახლება</string>
<string name="account_webcal_external_app">Webcal გამოწერები შეიძ₾ება სინქრონიზირებულ იქნას გარე აპებთან.</string>
<string name="account_no_webcal_handler_found">Webcal-თან თავსებადი აპი ვერ მოიძებნა</string>
<string name="account_install_icsx5">ICSx⁵-ს დაყენება</string>
<!--AddAccountActivity-->
<string name="login_title">ანგარიშის დამატება</string>
<string name="login_generic_login">ზოგადი შესვლა</string>
<string name="login_provider_login">პროვაიდერის შესვლა</string>
<string name="login_continue">გაგრძელება</string>
<string name="login_login">შესვლა</string>
<string name="login_type_email">ელ. ფოსტის მისამართით შესვლა</string>
<string name="login_email_address">ელ. ფოსტის მისამართი</string>
<string name="login_email_address_error">საჭიროა სწორი ელ. ფოსტის მისამართი</string>
<string name="login_email_address_info"><![CDATA[ელ. ფოსტის დომენი გამოიყენება საბაზო URL-ად. <a href="%s">აღმოჩენილია სერვისები</a> DNS ჩანაწერების და კარგად ცნობილი URL-ების მეშვეობით.]]></string>
<string name="login_password">პაროლი</string>
<string name="login_password_hide">პაროლის დამალვა</string>
<string name="login_password_show">პაროლის ჩვენება</string>
<string name="login_type_url">URL-ით და მომხმარებლის სახელით შესვლა</string>
<string name="login_user_name">მომხმარებლის სახელი</string>
<string name="login_base_url">საბაზო URL</string>
<string name="login_base_url_info"><![CDATA[საბაზო URL-ი პირადპირ იქნება შემოწმებული, მაგრამ <a href="%s">ასევე აღმოჩენილია სერვისები</a> DNS ჩანაწერების და კარგად ცნობილი URL-ების მეშვეობით.]]></string>
<string name="login_select_certificate">სერტიფიკატის არჩევა</string>
<string name="login_add_account">ანგარიშის დამატება</string>
<string name="login_account_name">ანგარიშის სახელი</string>
<string name="login_account_avoid_apostrophe">აპოსტროფების (\') გამოყენება იწვევს პრობლემებს ზოგ მოწყობილობაზე.</string>
<string name="login_account_name_info">გამოიყენეთ თქვენი ელ. ფოსტის მსიამართი ანგარიშის სახელად, რადგან Android გამოიყენებს ანგარიშის სახელს ორგანიზატორის ველში თქვენს მიერ შექმნილ ღონისძიებებისთვის. თქვენ არ შეიძლება გქონდეთ ორი ანგარიში იგივე სახელით.</string>
<string name="login_account_contact_group_method">კონტაქტების დაჯგუფების მეთოდი:</string>
<string name="login_account_name_required">საჭიროა ანგარიშის სახელი</string>
<string name="login_account_name_already_taken">ანგარიშის სახელი უკვე დაკავებულია</string>
<string name="login_type_advanced">გაფართოებული შესვლა</string>
<string name="login_client_certificate_selected">კლიენტის სერტიფიკატი: %s</string>
<string name="login_no_certificate_found">სერტიფიკატი ვერ მოიძებნა</string>
<string name="login_install_certificate">სერტიფიკატის დაყენება</string>
<string name="login_type_google">Google კონტაქტები / კალენდარი</string>
<string name="login_google_account">Google ანგარიში</string>
<string name="login_google">Google-ით შესვლა</string>
<string name="login_google_client_id">კლიენტის ID (aრასავალდებულო)</string>
<string name="login_google_client_privacy_policy"><![CDATA[%1$sგადასცემს თქვენს Google კონტაქტებისა და კალენდარის მონაცემებს მხოლოდ სინქრონიზაციისთვის ამ მოწყობილობასთან. იხილეთ ჩვენი <a href="%2$s">პირადულობის პოლიტიკა</a> დეტალებისთვის.]]></string>
<string name="login_google_client_limited_use"><![CDATA[%1$s ექვემდებარება <a href="%2$s">Google API სერვისების მომხმარებელთა მონაცემების პოლიტიკას</a>, მათ შორის, შეზღუდული გამოყენების მოთხოვნებს.]]></string>
<string name="login_oauth_couldnt_obtain_auth_code">ავტორიზაციის კოდის მიღება ვერ მოხერხდა</string>
<string name="login_type_nextcloud">Nextcloud</string>
<string name="login_nextcloud_login_with_nextcloud">შესვლა Nextcloud-ისთ</string>
<string name="login_nextcloud_login_flow_text">ეს დაიწყებს Nextcloud-ის შესვლის პროცესს ვებ ბრაუზერში.</string>
<string name="login_nextcloud_login_flow_server_address">Nextcloud-ის სერვერის მისამართი</string>
<string name="login_nextcloud_login_flow_sign_in">შესვლა</string>
<string name="login_nextcloud_login_flow_no_login_url">შესვლის URL-ის მიღება ვერ მოხერხდა</string>
<string name="login_nextcloud_login_flow_no_login_data">შესვლის მონაცემების მიღება ვერ მოხერხდა</string>
<string name="login_configuration_detection">კონფიგურაციის აღმოჩენა</string>
<string name="login_querying_server">გთხოვთ, დაელოდოთ, მიმდინარეობს სერვერის გამოკითხვა...</string>
<string name="login_no_service">CalDAV-ის ან CardDAV-ის სერვისის მოძებნა ვერ მოხერხდა.</string>
<string name="login_no_service_info">საბაზო URL არ არის წვდომადი CalDAV/CardDAV URL და სერვერისის აღმოჩენა არ იყო წარმატებული.</string>
<string name="login_see_tested_services"><![CDATA[გთხოვთ, იხილოთ თქვენი მომსახურების მომწოდებლის ინსტრუქცია და <a href="%s">ჩვენს მიერ ტესტირებული სერვისების სია</a> და მათი საბაზო URL.]]></string>
<string name="login_check_credentials">გთხოვთ, ასევე გადაამოწმოთ აუთენტიფიკაცია (ზოგადად, მომხმარებლის სახელი დაპაროლი).</string>
<string name="login_logs_available">დამატებითი ტექნიკური ინფორმაცია ხელმისაწვდომია ჟურნალებში.</string>
<string name="login_view_logs">ჟურნალების ნახვა</string>
<!--AccountSettingsActivity-->
<string name="settings_sync">სინქრონიზაცია</string>
<string name="settings_sync_interval_contacts">კონტაქტების სინქრონიზაციის ინტერვალი</string>
<string name="settings_sync_summary_manually">მხოლოდ ხელით</string>
<string name="settings_sync_summary_periodically" tools:ignore="PluralsCandidate">ყოველ %d წუთში + დაუყონებლივ ადგილობრივი ცვლილებებისას</string>
<string name="settings_sync_interval_calendars">კალენდრების სინქრონიზაციის ინტერვალი</string>
<string name="settings_sync_interval_tasks">დავალებვათა სინქრონიზაციის ინტერვალი</string>
<string-array name="settings_sync_interval_names">
<item>მხოლოდ ხელით</item>
<item>ყოველ 15 წუთში</item>
<item>ყოველ 30 წუთში</item>
<item>ყოველ 1 საათში</item>
<item>ყოველ 2 საათში</item>
<item>ყოველ 4 საათში</item>
<item>ყოველდღე</item>
</string-array>
<string name="settings_sync_wifi_only">მხოლოდ WiFi-ით სინქრონიზაცია</string>
<string name="settings_sync_wifi_only_on">სინქრონიზაცია შეზღუდულია WiFi კავშირზე</string>
<string name="settings_sync_wifi_only_off">კავშირის ტიპი არ გაითვალისწინება</string>
<string name="settings_sync_wifi_only_ssids">WiFi SSID-ს შეზღუდვა</string>
<string name="settings_sync_wifi_only_ssids_on">დასინქრონიზირდება მხოლო %s-ით</string>
<string name="settings_sync_wifi_only_ssids_off">გამოიყენება ყველა WiFi კავშირი</string>
<string name="settings_sync_wifi_only_ssids_message">დაშვებული WiFi ქსელების მძიმეთი დაყოფილი სახელები (SSID) (დატოვეთ ცარიელად ყველასთვის)</string>
<string name="settings_sync_wifi_only_ssids_permissions_required">WiFi SSID-ს შეზღუდვას სჭირდება დამატებითი პარამეტრები</string>
<string name="settings_sync_wifi_only_ssids_permissions_action">მართვა</string>
<string name="settings_ignore_vpns">VPN-ს სჭირდება არსებული ინტერნეტ-კავშირი</string>
<string name="settings_ignore_vpns_on">VPN არსებული დადასტურებული ინტერნეტ-კავშირის გარეშე არ არის საკმარისი სინქრონიზაციის გასაშვებად (რეკომენდებული)</string>
<string name="settings_ignore_vpns_off">VPN არსებული დადასტურებული ინტერნეტ-კავშირის გარეშე არ არის საკმარისი სინქრონიზაციის გასაშვებად</string>
<string name="settings_authentication">აუთენტიფიკაცია</string>
<string name="settings_username">მომხმარებლის სახელი</string>
<string name="settings_new_password">ახალი პაროლი</string>
<string name="settings_password_summary">პაროლის განახლება თქვენი სერვერის მიხედვით</string>
<string name="settings_certificate_alias">კლიენტის სერთიფიკატი</string>
<string name="settings_certificate_alias_empty">სერთიფიკატი ხელმიუწვდომია ან არ არის არჩეული</string>
<string name="settings_certificate_install">სერტიფიკატის დაყენება</string>
<string name="settings_caldav">CalDAV</string>
<string name="settings_sync_time_range_past">გასული ღონისძიების დროის შეზღუდვა</string>
<string name="settings_sync_time_range_past_none">დასინქრონიზირდება ყველა ღონისძიება</string>
<plurals name="settings_sync_time_range_past_days">
<item quantity="one">ერთ დღეზე უფრო ძველი ღონისძიებები იქნება იგნორირებული</item>
<item quantity="other">%d დღეზე უფრო ძველი ღონისძიებები იქნება იგნორირებული</item>
</plurals>
<string name="settings_sync_time_range_past_message">ღონისძიებები, რომლებიც უფრო ძველია, ვიდრე დღეთა მითითებული რაოდენობა, იქნება იგნორირებული (შეიძლება იყოს 0). დატოვეთ ცარიელად ყველას სინქრონიზებისთვის.</string>
<string name="settings_default_alarm">ნაგულისხმევა შეხსენება</string>
<plurals name="settings_default_alarm_on">
<item quantity="one">ნაგულისხმევი შეხსენება ღონისძიებამდე ერთი წუთით ადრე</item>
<item quantity="other">ნაგულისხმევი შეხსენება ღონისძიებამდე %d წუთით ადრე</item>
</plurals>
<string name="settings_default_alarm_off">ნაგულისხმევი შეხსენება არ არის შექმნილი</string>
<string name="settings_default_alarm_message">თუ ნაგულისხმევი შეხსენება უნდა შეიქმნას შეხსენების გარეშე ღონისძიებებისთვის: ღონისძიებამდე წუთების სასურველი რიცხვი. დატოვეთ ცარიელად ნაგულისხმევი შეხსენებების გასათიშად.</string>
<string name="settings_manage_calendar_colors">კალენდარის ფერების მართვა</string>
<string name="settings_manage_calendar_colors_on">კალენდარის ფერები ჩამოიყრება ყოველ სინქრონიზაციაზე</string>
<string name="settings_manage_calendar_colors_off">კალენდარის ფერები შეიძლება დაყენებულ იქნას სხვა აპების მიერ</string>
<string name="settings_event_colors">ღონისძიების ფერის მხარდაჭერა</string>
<string name="settings_event_colors_on">ღონისძიების ფერები არის სინქრონიზირებული</string>
<string name="settings_event_colors_off">ღონისძიების ფერები არ არის სინქრონიზირებული</string>
<string name="settings_carddav">CardDAV</string>
<string name="settings_contact_group_method">კონტაქტების დაჯგუფების მეთოდი</string>
<string-array name="settings_contact_group_method_entries">
<item>ჯგუფები ცალკე vCard-ებია</item>
<item>ჯგუფები არის კონტაქტთა კატეგორია</item>
</string-array>
<!--CreateAddressBookScreen, CreateCalendarScreen-->
<string name="create_addressbook">მისამართთა წიგნაკის შექმნა</string>
<string name="create_addressbook_maybe_not_supported">მისამართთა წიგნაკის შექმნა CardDAV-ით შეიძლება არ იყოს მხარდაჭერილი სერვერის მიერ.</string>
<string name="create_calendar">კალენდარის შექმნა</string>
<string name="create_calendar_time_zone_none"></string>
<string name="create_calendar_type">დაშვებული კალენდარის ჩანაწერები</string>
<string name="create_calendar_type_vevent">ღონისძიებები</string>
<string name="create_calendar_type_vtodo">დავალებები</string>
<string name="create_calendar_type_vjournal">შენიშვნები / ჟურნალი</string>
<string name="create_calendar_maybe_not_supported">კალენდრის შექმნა CalDAV-ით შეიძლება არ იყოს მხარდაჭერილი სერვერის მიერ.</string>
<string name="create_collection_color">ფერი</string>
<string name="create_collection_display_name">სათაური</string>
<string name="create_collection_home_set">მეხსიერების ადგილმდებარეობა</string>
<string name="create_collection_description_optional">აღწერა (არასავალდებულო)</string>
<string name="create_collection_create">შექმნა</string>
<!--CollectionScreen-->
<string name="collection_datatype_contacts">კონტაქტები</string>
<string name="collection_datatype_tasks">დავალებები</string>
<string name="collection_delete">კოლექციის წაშლა</string>
<string name="collection_delete_warning">ეს კოლექცია (%s) და მისი ყველა მონაცემი სამუდამოდ წაიშლება, როგორც ადგილობრივად, ისე სერვერზეც.</string>
<string name="collection_synchronization">სინქრონიზაცია</string>
<string name="collection_synchronization_on">სინქრონიზაცია ჩართულია</string>
<string name="collection_synchronization_off">სინქრონიზაცია გამორთულია</string>
<string name="collection_read_only">მხოლოდ წაკითხვადი</string>
<string name="collection_read_only_by_server">მხოლოდ წაკითხვადი (სერვერის მიერ)</string>
<string name="collection_read_only_forced">მხოლოდ წაკითხვადი (მხოლოდ ადგილობრივად)</string>
<string name="collection_read_write">წაკითხვა/ჩაწერა</string>
<string name="collection_title">სათაური</string>
<string name="collection_description">აღწერა</string>
<string name="collection_owner">მფლობელი</string>
<string name="collection_push_support">Push-ის მხარდაჭერა</string>
<string name="collection_push_web_push">სერვერი გადმოსცემს Push-ის მხარდაჭერას</string>
<string name="collection_last_sync">ბოლო სინქრონიზაცია (%s)</string>
<string name="collection_url">მისამართი (URL)</string>
<!--debugging and DebugInfoActivity-->
<string name="debug_info_title">დებაგის ინფო</string>
<string name="debug_info_archive_caption">ZIP არქივი</string>
<string name="debug_info_archive_subtitle">შეიცავს დებაგის ინფოს და ჟურნალებს</string>
<string name="debug_info_archive_text">გააზიარეთ არქივი მისი კომპიუტერზე გადასაგზავნად, ელ. ფოსტით გასაგზავნად ან მისი მხარდაჭერის ბილეთზე მისაბმელად.</string>
<string name="debug_info_archive_share">არქივის გაზიარება</string>
<string name="debug_info_attached">დებაგის ინფო მიბმულია ამ შეტყობინებაზე (სჭირდება მიბმის მხარდაჭერა მიმღებ აპში).</string>
<string name="debug_info_http_error">HTTP შეცდომა</string>
<string name="debug_info_server_error">სერვერის შეცდომა</string>
<string name="debug_info_webdav_error">WebDAV შეცდომა</string>
<string name="debug_info_io_error">წაკითხვა/ჩაწერის შეცდომა</string>
<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_involved_subtitle">დაკავშირებული პრობლემასთან</string>
<string name="debug_info_involved_remote">დაშორებული რესურსი:</string>
<string name="debug_info_involved_local">ადგილობრივი რესურსი:</string>
<string name="debug_info_logs_caption">ჟურნალები</string>
<string name="debug_info_logs_subtitle">ხელმისაწვდომია დეტალური ჟურნალები</string>
<string name="debug_info_logs_view">ჟურნალების ნახვა</string>
<!--ExceptionInfoFragment-->
<string name="exception">მოხდა შეცდომა.</string>
<string name="exception_httpexception">მოხდა HTTP შეცდომა.</string>
<string name="exception_ioexception">მოხდა წაკითხვა/ჩაწერის შეცდომა.</string>
<string name="exception_show_details">დეტალების ჩვენება.</string>
<!--WebDAV accounts-->
<string name="webdav_mounts_title">WebDAV-ის მიბმები</string>
<string name="webdav_mounts_quota_used_available">გამოყენებული კვოტა: %1$s / ხელმისაწვდომი: %2$s</string>
<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_add_mount_display_name">ნაჩვენები სახელი</string>
<string name="webdav_add_mount_url">WebDAV URL</string>
<string name="webdav_add_mount_url_invalid">არასწორი URL</string>
<string name="webdav_add_mount_authentication">აუთენტიფიკაცია</string>
<string name="webdav_add_mount_username">მომხმარებლის სახელი</string>
<string name="webdav_add_mount_password">პაროლი</string>
<string name="webdav_add_mount_add">მიბმის დამატება</string>
<string name="webdav_add_mount_no_support">WebDAV სერვისი ამ URL-ზე არ არის</string>
<string name="webdav_remove_mount_title">მიბმის წერტილის ამოშლა</string>
<string name="webdav_remove_mount_text">კავშირის დეტალები დაიკარგება, მაგრამ ფაილები არ წაიშლება.</string>
<string name="webdav_notification_access">მიმდინარეობს WebDAV ფაილზე წვდომა</string>
<string name="webdav_notification_download">მიმდინარეობს WebDAV ფაილის გადმოტვირთვა</string>
<string name="webdav_notification_upload">მიმდინარეობს WebDAV ფაილის ატვირთვა</string>
<string name="webdav_provider_root_title">WebDAV-iს მიბმა</string>
<!--sync-->
<string name="sync_error_permissions">DAVx⁵-ის უფლებები</string>
<string name="sync_error_permissions_text">საჭიროა დამატებითი უფლებები</string>
<string name="sync_error_tasks_too_old">%s ნამეტანი ძველია</string>
<string name="sync_error_tasks_required_version">მინიმალური საჭირო ვერსია: %1$s</string>
<string name="sync_error_authentication_failed">აუთენტიფიკაცია ჩაიშალა (შეამოწმეთ შევლის იდენტიფიკატორები)</string>
<string name="sync_error_io">ქსელური ან ჩაწერა/წაკითხვის შეცდომა - %s</string>
<string name="sync_error_http_dav">HTTP სერვერის შეცდომა - %s</string>
<string name="sync_error_local_storage">ადგილობრივი მეხსიერების შეცდომა - %s</string>
<string name="sync_error_retry_limit_reached">რბილის შეცდომა (მიღწეულია თავიდან ცდის მაწსიმუმი)</string>
<string name="sync_error_view_item">ჩანაწერის ნახვა</string>
<string name="sync_invalid_contact">მიღებულია არასწორი კონტაქტი სერვერიდან</string>
<string name="sync_invalid_event">მიღებულია არასწორი ღონისძიება სერვერიდან</string>
<string name="sync_invalid_task">მიღებული არასწორი დავალება სერვერიდან</string>
<string name="sync_invalid_resources_ignoring">ერთი ან მეტი არასწორი რესურსის იგნორირება</string>
<!--widgets-->
<string name="widget_sync_all">ყველაფრის სინქრონიზირება</string>
<string name="widget_sync_all_accounts">ყველა ანგარიშის სინქრონიზაცია</string>
<!--cert4android-->
</resources>

View File

@@ -1,480 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources xmlns:tools="http://schemas.android.com/tools">
<!--common strings-->
<string name="account_invalid">Account bestaat niet (of niet meer)</string>
<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">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>
<string name="navigate_up">Navigeer omhoog</string>
<string name="options_menu">Opties menu</string>
<string name="share">Delen</string>
<string name="sync_started">Synchronisatie begonnen/in wachtrij geplaatst</string>
<string name="database_destructive_migration_title">Database beschadigd</string>
<string name="database_destructive_migration_text">Alle accounts zijn lokaal verwijderd.</string>
<string name="notification_channel_debugging">Debuggen</string>
<string name="notification_channel_general">Andere belangrijke berichten</string>
<string name="notification_channel_status">Statusberichten met lage prioriteit</string>
<string name="notification_channel_sync">Synchroniseren</string>
<string name="notification_channel_sync_errors">Synchronisatiefouten</string>
<string name="notification_channel_sync_errors_desc">Belangrijke fouten die het synchroniseren stoppen, zoals onverwachte server antwoorden</string>
<string name="notification_channel_sync_warnings">Synchronisatie waarschuwingen</string>
<string name="notification_channel_sync_warnings_desc">Niet-fatale problemen bij het synchroniseren zoals bepaalde ongeldige bestanden</string>
<string name="notification_channel_sync_io_errors">Netwerk en I/O fouten</string>
<string name="notification_channel_sync_io_errors_desc">Timeouts, connectie problemen, etc. (vaak tijdelijk).</string>
<!--IntroActivity-->
<string name="intro_slogan1">Jouw gegevens. Jouw keuze.</string>
<string name="intro_slogan2">Houd zelf de controle</string>
<string name="intro_battery_title">regelmatige sync-intervallen</string>
<string name="intro_battery_text">Om op gezette tijden te synchroniseren moet %s zonder beperking op de achtergrond kunnen draaien. Anders kan Android het synchroniseren op elk moment onderbreken.</string>
<string name="intro_battery_dont_show">Synchroniseren op gezette tijden is niet nodig.*</string>
<string name="intro_autostart_title">%s compatibiliteit</string>
<string name="intro_autostart_text">Leverancierspecifieke firmware kan de synchronisatie blokkeren. Als je hier last van hebt, kan dit alleen handmatig worden opgelost.</string>
<string name="intro_autostart_dont_show">De vereiste instellingen zijn verricht. Er aan herinneren is niet meer nodig.*</string>
<string name="intro_leave_unchecked">* Niet aanvinken om later herinnerd te worden. Kan teruggezet in app instellingen / %s.</string>
<string name="intro_more_info">Meer informatie</string>
<string name="intro_tasks_jtx">jtx Board</string>
<string name="intro_tasks_jtx_info"><![CDATA[Synchroniseert taken, agenda\'s en notities met elke geschikte CalDAV-server.]]></string>
<string name="intro_tasks_title">Ondersteunt taken</string>
<string name="intro_tasks_text1">Als de server taken ondersteunt, synchroniseert een geschikte taken-app ze:</string>
<string name="intro_tasks_opentasks">OpenTasks</string>
<string name="intro_tasks_opentasks_info">Schijnt niet meer ontwikkeld te worden - niet aanbevolen.</string>
<string name="intro_tasks_tasks_org">Tasks.org</string>
<string name="intro_tasks_tasks_org_info"><![CDATA[Enkele functies <a href="https://www.davx5.com/faq/tasks/advanced-task-features">worden niet ondersteund</a>.]]></string>
<string name="intro_tasks_no_app_store">Geen app-store beschikbaar</string>
<string name="intro_tasks_dont_show">Ik hoef geen ondersteuning van taken.*</string>
<string name="intro_open_source_title">Open-source software</string>
<string name="intro_open_source_text">We zijn blij dat de keuze valt op open source software %s. Ontwikkelen, onderhouden en ondersteunen is veel werk. Overweeg daarom bij te dragen (kan op vele manieren) of een donatie. Wij waarderen het zeer!</string>
<string name="intro_open_source_details">Hoe bijdragen/doneren</string>
<string name="intro_open_source_dont_show">Herinner me er niet aan voor</string>
<plurals name="intro_open_source_dont_show_months">
<item quantity="one">%d maand</item>
<item quantity="other">%d maanden</item>
</plurals>
<string name="intro_next">Volgende</string>
<!--PermissionsActivity-->
<string name="permissions_title">Rechten toestaan</string>
<string name="permissions_text">%s heeft rechten nodig om goed te werken.</string>
<string name="permissions_all_title">Alle onderstaande</string>
<string name="permissions_all_status_off">Gebruik dit om alle functies in te schakelen (aanbevolen)</string>
<string name="permissions_all_status_on">Alle rechten toegekend</string>
<string name="permissions_contacts_title">Contacten toestaan</string>
<string name="permissions_contacts_status_off">Geen contacten synchroniseren (niet aanbevolen)</string>
<string name="permissions_contacts_status_on">Contacten synchroniseren mogelijk</string>
<string name="permissions_calendar_title">Kalender machtigingen</string>
<string name="permissions_calendar_status_off">Geen kalenders synchroniseren (niet aanbevolen)</string>
<string name="permissions_calendar_status_on"> Kalenders synchroniseren mogelijk</string>
<string name="permissions_notification_title">Toestemming voor meldingen</string>
<string name="permissions_notification_status_off">Meldingen uitgeschakeld (niet aanbevolen)</string>
<string name="permissions_notification_status_on">Meldingen ingeschakeld</string>
<string name="permissions_jtx_title">jtx Board-rechten</string>
<string name="permissions_opentasks_title">OpenTasks rechten</string>
<string name="permissions_tasksorg_title">Rechten voor taken</string>
<string name="permissions_tasks_status_off">Geen taak-sync</string>
<string name="permissions_tasks_status_on">Taak-sync mogelijk</string>
<string name="permissions_autoreset_title">Rechten behouden</string>
<string name="permissions_autoreset_status_off">Rechten kunnen automatisch worden teruggezet (niet aanbevolen)</string>
<string name="permissions_autoreset_status_on">Rechten worden niet automatisch teruggezet</string>
<string name="permissions_autoreset_instruction">Klik op App Rechten &gt; vinkje uit bij \"Rechten intrekken\"</string>
<string name="permissions_app_settings_hint">Als een schakeloptie niet werkt, gebruik dan App-info / Rechten.</string>
<string name="permissions_app_settings">App instellingen</string>
<!--WifiPermissionsActivity-->
<string name="wifi_permissions_label">WiFi SSID rechten</string>
<string name="wifi_permissions_intro">Voor toegang tot de huidige WiFi-naam (SSID), moet aan deze voorwaarden worden voldaan:</string>
<string name="wifi_permissions_location_permission">Recht van toegang tot exacte locatie</string>
<string name="wifi_permissions_location_permission_on">Toegang tot locatie verleend</string>
<string name="wifi_permissions_location_permission_off">Toegang tot locatie geweigerd</string>
<string name="wifi_permissions_background_location_permission">Toegang tot locatie op de achtergrond</string>
<string name="wifi_permissions_background_location_permission_label">Onbeperkt toestaan</string>
<string name="wifi_permissions_background_location_permission_on">Locatietoestemming ingesteld op: %s</string>
<string name="wifi_permissions_background_location_permission_off">Locatietoestemming niet ingesteld op: %s</string>
<string name="wifi_permissions_background_location_disclaimer">%s gebruikt locatiegegevens (alleen WiFi SSID) uitsluitend om de synchronisatie te beperken tot een specifieke WiFi SSID. Dit gebeurt zelfs als de synchronisatie op de achtergrond wordt uitgevoerd.</string>
<string name="wifi_permissions_background_location_disclaimer2">Alle locatiegegevens (alleen WiFi SSID) worden alleen lokaal gebruikt en worden nergens naartoe verzonden.</string>
<string name="wifi_permissions_location_enabled">Toegang tot locatie altijd ingeschakeld</string>
<string name="wifi_permissions_location_enabled_on">Toegang tot locatie is ingeschakeld</string>
<string name="wifi_permissions_location_enabled_off">Toegang tot locatie is uitgeschakeld</string>
<!--AboutActivity-->
<string name="about_translations">Vertalingen</string>
<string name="about_libraries">Bibliotheken</string>
<string name="about_version">Versie%1$s (%2$d)</string>
<string name="about_copyright">© Ricki Hirner, Bernhard Stockmann (bitfire web engineering GmbH) en bijdragers</string>
<string name="about_license_info_no_warranty">Dit programma wordt geleverd met ABSOLUUT GEEN GARANTIE. Het is gratis software, en mag opnieuw worden verspreid onder bepaalde voorwaarden.</string>
<!--global settings-->
<string name="logging_couldnt_create_file">Kon geen logbestand aanmaken</string>
<string name="logging_notification_text">Logt nu alle %s activiteiten</string>
<string name="logging_notification_view_share">Bekijken/delen</string>
<string name="logging_notification_disable">Uitschakelen</string>
<!--AccountsScreen-->
<string name="navigation_drawer_subtitle">CalDAV/CardDAV Sync adapter</string>
<string name="navigation_drawer_about">Over / Licentie</string>
<string name="navigation_drawer_beta_feedback">Beta terugkoppeling</string>
<string name="install_browser">Webbrowser is vereist</string>
<string name="navigation_drawer_settings">Instellingen</string>
<string name="navigation_drawer_news_updates">Nieuws &amp; updates</string>
<string name="navigation_drawer_tools">Gereedschap</string>
<string name="navigation_drawer_external_links">Externe links</string>
<string name="navigation_drawer_website">Website</string>
<string name="navigation_drawer_manual">Handleiding</string>
<string name="navigation_drawer_faq">FAQ</string>
<string name="navigation_drawer_managed">Voor organisaties</string>
<string name="navigation_drawer_community">Community</string>
<string name="navigation_drawer_support_project">Ondersteun het project</string>
<string name="navigation_drawer_contribute">Hoe bijdragen</string>
<string name="navigation_drawer_privacy_policy">Privacybeleid</string>
<string name="account_list_welcome">Welkom bij DAVx⁵!</string>
<string name="account_list_empty">Maak verbinding met je server en houd je agenda\'s en contactpersonen gesynchroniseerd.</string>
<string name="accounts_sync_all">Alle accounts synchroniseren</string>
<!--Sync warnings-->
<string name="sync_warning_no_notification_permission">Meldingen uitgeschakeld. U krijgt geen meldingen over synchronisatiefouten.</string>
<string name="sync_warning_no_internet">Automatische synchronisatie niet actief (geen geverifieerde internetverbinding).</string>
<string name="sync_warning_manage_connections">Verbindingen beheren</string>
<string name="sync_warning_datasaver_enabled">Gegevensbesparing ingeschakeld. Synchronisatie op de achtergrond is beperkt.</string>
<string name="sync_warning_manage_datasaver">Beheer van gegevensbesparing</string>
<string name="sync_warning_battery_saver_enabled">Batterijbesparing ingeschakeld. Synchronisatie kan beperkt zijn.</string>
<string name="sync_warning_manage_battery_saver">Batterijbesparing beheren</string>
<string name="sync_warning_low_storage">Weinig opslagruimte. Android zal lokale wijzigingen niet onmiddellijk synchroniseren, maar tijdens de volgende reguliere synchronisatie.</string>
<string name="sync_warning_manage_storage">Opslag beheren</string>
<string name="sync_warning_calendar_storage_disabled_title">Aanbieder voor Kalender ontbreekt</string>
<string name="sync_warning_calendar_storage_disabled_description">Heb je de systeemapp \"Kalenderopslag\" uitgeschakeld?</string>
<string name="sync_warning_contacts_storage_disabled_title">Aanbieder voor Contactpersonen ontbreekt</string>
<string name="sync_warning_contacts_storage_disabled_description">Heb je de systeemapp \"Contactenopslag\" uitgeschakeld?</string>
<string name="sync_warning_manage_apps">Apps beheren</string>
<!--RefreshCollectionsWorker-->
<string name="refresh_collections_worker_refresh_failed">Service herkenning is mislukt</string>
<string name="refresh_collections_worker_refresh_couldnt_refresh">De collectielijst is niet bijgewerkt</string>
<!--Foreground service used by WorkManager on Android <12-->
<string name="foreground_service_notify_title">Draait op de voorgrond</string>
<string name="foreground_service_notify_text">Op sommige toestellen is dit nodig voor automatische synchronisatie.</string>
<!--AppSettingsActivity-->
<string name="app_settings">Instellingen</string>
<string name="app_settings_debug">Debuggen</string>
<string name="app_settings_show_debug_info">Debug-info</string>
<string name="app_settings_show_debug_info_details">Configuratiedetails en logbestanden bekijken/delen</string>
<string name="app_settings_logging">Uitgebreid loggen</string>
<string name="app_settings_logging_on">Loggen is actief. Je kunt de logs bekijken als onderdeel van de debug-info.</string>
<string name="app_settings_logging_off">Loggen is niet actief</string>
<string name="app_settings_battery_optimization">Batterijoptimalisatie</string>
<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-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 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>
<string name="app_settings_security_app_permissions_summary">De vereiste rechten om te synchroniseren controleren</string>
<string name="app_settings_distrust_system_certs">Wantrouw systeemcertificaten</string>
<string name="app_settings_distrust_system_certs_on">Door systeem en gebruiker toegevoegde CA certificaten niet vertrouwen</string>
<string name="app_settings_distrust_system_certs_off">Door systeem en gebruiker toegevoegde CA certificaten vertrouwen (aanbevolen)</string>
<string name="app_settings_distrust_system_certs_dialog_message">Als deze instelling actief is, worden systeemcertificaten niet als betrouwbaar beschouwd. Dit betekent dat je elk certificaat handmatig moet accepteren (ook wanneer de server zijn certificaat vernieuwt) anders werken accountinstelling en synchronisatie niet.</string>
<string name="app_settings_reset_certificates">(Niet-)vertrouwde certificaten terugzetten</string>
<string name="app_settings_reset_certificates_summary">Herstelt het vertrouwen van alle aangepaste certificaten</string>
<string name="app_settings_reset_certificates_success">Alle aangepaste certificaten zijn gewist</string>
<string name="app_settings_user_interface">Gebruikersinterface</string>
<string name="app_settings_notification_settings">App-meldingen</string>
<string name="app_settings_notification_settings_summary">Meldingskanalen en hun instellingen beheren</string>
<string name="app_settings_theme_title">Thema selecteren</string>
<string-array name="app_settings_theme_names">
<item>Systeem standaard</item>
<item>Licht</item>
<item>Donker</item>
</string-array>
<string name="app_settings_reset_hints">Hints opnieuw instellen</string>
<string name="app_settings_reset_hints_summary">Hints die al gezien zijn opnieuw weergeven</string>
<string name="app_settings_reset_hints_success">Alle hints opnieuw weergeven</string>
<string name="app_settings_integration">Integratie</string>
<string name="app_settings_tasks_provider">Taken app</string>
<string name="app_settings_tasks_provider_none">Geen compatibele taken app gevonden</string>
<string name="app_settings_unifiedpush">UnifiedPush (experimenteel)</string>
<string name="app_settings_unifiedpush_disable">Geen (push uitschakelen)</string>
<string name="app_settings_unifiedpush_choose_distributor">Kies een distributeur</string>
<string name="app_settings_unifiedpush_no_distributor">Geen push distributeur geïnstalleerd</string>
<string name="app_settings_unifiedpush_no_endpoint">Geen eindpunt geconfigureerd</string>
<string name="app_settings_unifiedpush_ready">Klaar om pushberichten te ontvangen via %s</string>
<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 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">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">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>
<string name="account_synchronize_this_collection">deze collectie synchroniseren</string>
<string name="account_read_only">alleen-lezen</string>
<string name="account_calendar">kalender</string>
<string name="account_contacts">contacten</string>
<string name="account_journal">logboek</string>
<string name="account_task_list">taken</string>
<string name="account_only_personal">Alleen persoonlijk tonen</string>
<string name="account_refresh_collections">Lijst verversen</string>
<string name="account_webcal_external_app">Webcal abonnementen kunnen worden gesynchroniseerd met externe apps.</string>
<string name="account_no_webcal_handler_found">Geen Webcal-app gevonden</string>
<string name="account_install_icsx5">ICSx⁵ installeren</string>
<!--AddAccountActivity-->
<string name="login_title">Account toevoegen</string>
<string name="login_privacy_hint"><![CDATA[Alle gegevens worden alleen overgedragen tussen je server en je apparaat. %1$s zal ze nergens anders naartoe sturen. Zie<a href="%2$s">privacybeleid</a>.]]></string>
<string name="login_generic_login">Algemeen inloggen</string>
<string name="login_provider_login">Aanbieder-specifieke login</string>
<string name="login_continue">Ga verder</string>
<string name="login_login">Login</string>
<string name="login_type_email">Inloggen met e-mailadres</string>
<string name="login_email_address">E-mailadres</string>
<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">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>
<string name="login_user_name">Gebruikersnaam</string>
<string name="login_user_name_optional">Gebruikersnaam (optioneel)</string>
<string name="login_base_url">Basis-URL</string>
<string name="login_base_url_info"><![CDATA[De basis URL wordt direct gecontroleerd, maar <a href="%s">services worden ook ontdekt</a> met behulp van DNS records en bekende URL\'s.]]></string>
<string name="login_select_certificate">Certificaat selecteren</string>
<string name="login_add_account">Account toevoegen</string>
<string name="login_account_name">Accountnaam</string>
<string name="login_account_avoid_apostrophe">Het gebruik van apostrofs (\') lijkt problemen te veroorzaken op sommige apparaten.</string>
<string name="login_account_name_info">Gebruik het eigen e-mailadres als accountnaam, want Android gebruikt het als ORGANIZER veld voor gebeurtenissen. Twee accounts met hetzelfde adres kan niet.</string>
<string name="login_account_contact_group_method">Methode voor contact-groepen:</string>
<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_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_sign_in">Inloggen met Fastmail</string>
<string name="login_type_google">Google Contacten / Kalender</string>
<string name="login_google_account">Google account</string>
<string name="login_google">Inloggen met Google</string>
<string name="login_google_client_id">Client ID (optioneel)</string>
<string name="login_google_client_privacy_policy"><![CDATA[%1$s draagt uw Google Contacten en Agenda gegevens uitsluitend over voor synchronisatie met dit apparaat. Zie ons Privacybeleid voor meer informatie. Zie ons <a href="%2$s">Privacybeleid</a> voor meer informatie.]]></string>
<string name="login_google_client_limited_use"><![CDATA[%1$s voldoet aan het <a href="%2$s">beleid voor gebruikersgegevens van Google API Services</a>, met inbegrip van de vereisten voor beperkt gebruik.]]></string>
<string name="login_oauth_couldnt_obtain_auth_code">Kon geen autorisatiecode verkrijgen</string>
<string name="login_type_nextcloud">Nextcloud</string>
<string name="login_nextcloud_login_with_nextcloud">Inloggen met Nextcloud</string>
<string name="login_nextcloud_login_flow_text">Hiermee wordt de Nextcloud Flow-aanmelding in een webbrowser gestart.</string>
<string name="login_nextcloud_login_flow_server_address">Nextcloud serveradres</string>
<string name="login_nextcloud_login_flow_sign_in">Aanmelden</string>
<string name="login_nextcloud_login_flow_no_login_url">Kan inlog-URL niet verkrijgen</string>
<string name="login_nextcloud_login_flow_no_login_data">Kan inlog-URL niet verkrijgen</string>
<string name="login_configuration_detection">Configuratie detecteren</string>
<string name="login_querying_server">Even geduld, verzoek naar server…</string>
<string name="login_no_service">Geen CalDAV- of CardDAV-service gevonden.</string>
<string name="login_no_service_info">De basis URL lijkt geen toegankelijke CalDAV/CardDAV URL te zijn en de detectie van de service was niet succesvol.</string>
<string name="login_see_tested_services"><![CDATA[Raadpleeg de handleiding van uw serviceprovider en <a href="%s">onze lijst met geteste services</a> en hun basis URL\'s.]]></string>
<string name="login_check_credentials">Controleer ook de authenticatie (meestal gebruikersnaam en wachtwoord).</string>
<string name="login_logs_available">Meer technische informatie is beschikbaar in de logboeken.</string>
<string name="login_view_logs">Details bekijken</string>
<!--AccountSettingsActivity-->
<string name="settings_sync">Synchronisatie</string>
<string name="settings_sync_interval_contacts">Contacten synchronisatie interval</string>
<string name="settings_sync_summary_manually">Alleen handmatig</string>
<string name="settings_sync_summary_periodically" tools:ignore="PluralsCandidate">Elke %d minuten + direct bij lokale veranderingen</string>
<string name="settings_sync_interval_calendars">Kalenders synchronisatie-interval</string>
<string name="settings_sync_interval_tasks">Taken synchronisatie-interval</string>
<string-array name="settings_sync_interval_names">
<item>Handmatig </item>
<item>Elke 15 minuten </item>
<item>Elke 30 minuten</item>
<item>Elk uur</item>
<item>Elke 2 uur</item>
<item>Elke 4 uur</item>
<item>Eenmaal daags</item>
</string-array>
<string name="settings_sync_wifi_only">Synchronisatie beperken tot WiFi</string>
<string name="settings_sync_wifi_only_on">Alleen verbinden via WiFi</string>
<string name="settings_sync_wifi_only_off">Type verbinding is niet relevant</string>
<string name="settings_sync_wifi_only_ssids">Tot bepaalde WiFi-SSID beperken</string>
<string name="settings_sync_wifi_only_ssids_on">Synchronisatie alleen via %s</string>
<string name="settings_sync_wifi_only_ssids_off">Elke WiFI-SSID toestaan</string>
<string name="settings_sync_wifi_only_ssids_message">Door komma\'s gescheiden namen (SSID\'s) van toegestane WiFi-netwerken (laat leeg voor alle)</string>
<string name="settings_sync_wifi_only_ssids_permissions_required">Beperking WiFi-SSID vereist verdere instellingen</string>
<string name="settings_sync_wifi_only_ssids_permissions_action">Beheren</string>
<string name="settings_ignore_vpns">VPN vereist onderliggend internet</string>
<string name="settings_ignore_vpns_on">VPN zonder onderliggende gevalideerde internetverbinding is niet voldoende om synchronisatie uit te voeren (aanbevolen)</string>
<string name="settings_ignore_vpns_off">VPN zonder onderliggende gevalideerde internetverbinding is voldoende om synchronisatie uit te voeren</string>
<string name="settings_authentication">Authenticatie</string>
<string name="settings_username">Gebruikersnaam</string>
<string name="settings_password">Wachtwoord of app-wachtwoord</string>
<string name="settings_app_password_hint"><![CDATA[Misschien gebruik je liever een <a href="%1$s">app-wachtwoord</a>.]]></string>
<string name="settings_new_password">Nieuw wachtwoord</string>
<string name="settings_password_summary">Gebruik het zelfde wachtwoord als op de server.</string>
<string name="settings_reauthorize_oauth">Opnieuw autoriseren (OAuth)</string>
<string name="settings_reauthorize_oauth_summary">Gebruiken wanneer de toegang is ingetrokken</string>
<string name="settings_reauthorize_oauth_success">Autorisatie geslaagd</string>
<string name="settings_certificate_alias">Cliëntcertificaat</string>
<string name="settings_certificate_alias_empty">Geen certificaat beschikbaar of geselecteerd</string>
<string name="settings_certificate_install">Certificaat installeren</string>
<string name="settings_caldav">CalDAV</string>
<string name="settings_sync_time_range_past">Gebeurtenissen in verleden tijd</string>
<string name="settings_sync_time_range_past_none">Worden alle gesynchroniseerd</string>
<plurals name="settings_sync_time_range_past_days">
<item quantity="one">Afspraken ouder dan een dag worden genegeerd</item>
<item quantity="other">Ouder dan %d dagen worden genegeerd</item>
</plurals>
<string name="settings_sync_time_range_past_message">Gebeurtenissen ouder dan ingevuld aantal dagen worden genegeerd (mag 0 zijn). Veld leeg laten om alle te synchroniseren.</string>
<string name="settings_default_alarm">Standaardherinnering</string>
<plurals name="settings_default_alarm_on">
<item quantity="one">Standaardherinnering één minut voor het evenement</item>
<item quantity="other"> %d minuten voor aanvang gebeurtenis</item>
</plurals>
<string name="settings_default_alarm_off">Wordt niet aangemaakt</string>
<string name="settings_default_alarm_message">Vul het gewenste aantal minuten in. Leeg laten om herinneringen uit te schakelen.</string>
<string name="settings_manage_calendar_colors">Kalender kleuren beheren</string>
<string name="settings_manage_calendar_colors_on">Worden bij elke sync teruggezet</string>
<string name="settings_manage_calendar_colors_off">Kunnen door andere apps worden ingesteld</string>
<string name="settings_event_colors">Gebeurtenis kleuren ondersteunen</string>
<string name="settings_event_colors_on">Worden gesynchroniseerd</string>
<string name="settings_event_colors_off">Worden niet gesynchroniseerd</string>
<string name="settings_carddav">CardDAV</string>
<string name="settings_contact_group_method">Methode voor contact-groepen:</string>
<string-array name="settings_contact_group_method_entries">
<item>Groepen zijn afzonderlijke vCards</item>
<item>Groepen zijn categorieën per contact</item>
</string-array>
<!--CreateAddressBookScreen, CreateCalendarScreen-->
<string name="create_addressbook">Adresboek aanmaken</string>
<string name="create_addressbook_maybe_not_supported">Het aanmaken van een adresboek via CardDAV wordt mogelijk niet ondersteund door de server.</string>
<string name="create_calendar">Kalender aanmaken</string>
<string name="create_calendar_time_zone_optional">Standaard tijdzone (optioneel)</string>
<string name="create_calendar_time_zone_none"></string>
<string name="create_calendar_type">Mogelijke kalender-items</string>
<string name="create_calendar_type_vevent">Gebeurtenissen</string>
<string name="create_calendar_type_vtodo">Taken</string>
<string name="create_calendar_type_vjournal">Notities / Dagboek</string>
<string name="create_calendar_maybe_not_supported">Het aanmaken van een kalender via CalDAV wordt mogelijk niet ondersteund door de server.</string>
<string name="create_collection_color">Kleur</string>
<string name="create_collection_display_name">Titel</string>
<string name="create_collection_home_set">Opslaglocatie</string>
<string name="create_collection_description_optional">Beschrijving (optioneel)</string>
<string name="create_collection_create">Aanmaken</string>
<!--CollectionScreen-->
<string name="collection_datatype_contacts">contacten</string>
<string name="collection_datatype_events">gebeurtenissen</string>
<string name="collection_datatype_tasks">taken</string>
<string name="collection_delete">Collectie verwijderen</string>
<string name="collection_delete_warning">Deze collectie (%s) en alle gegevens worden permanent verwijderd, zowel lokaal als op de server.</string>
<string name="collection_synchronization">Synchroniseren</string>
<string name="collection_synchronization_on">Synchronisatie ingeschakeld</string>
<string name="collection_synchronization_off">Synchronisatie uitgeschakeld</string>
<string name="collection_read_only">Alleen-lezen</string>
<string name="collection_read_only_by_server">Alleen-lezen (door server)</string>
<string name="collection_read_only_by_setting">Alleen-lezen (volgens beleid)</string>
<string name="collection_read_only_forced">Alleen-lezen (alleen lokaal)</string>
<string name="collection_read_write">Lezen/schrijven</string>
<string name="collection_title">Titel</string>
<string name="collection_description">Beschrijving</string>
<string name="collection_owner">Eigenaar</string>
<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 synchronisatie (%s)</string>
<string name="collection_url">Adres (URL)</string>
<!--debugging and DebugInfoActivity-->
<string name="debug_info_title">Debug informatie</string>
<string name="debug_info_archive_caption">ZIP archief</string>
<string name="debug_info_archive_subtitle">Bevat debuginformatie en logbestanden</string>
<string name="debug_info_archive_text">Deel het archief om over te zetten naar een computer, per e-mail te verzenden of als bijlage bij een supportticket te voegen..</string>
<string name="debug_info_archive_share">Archief delen</string>
<string name="debug_info_attached">Debug info als bijlage bij dit bericht (vereist ondersteuning voor bijlagen van de ontvangende app).</string>
<string name="debug_info_http_error">HTTP-fout</string>
<string name="debug_info_server_error">Serverfout</string>
<string name="debug_info_webdav_error">WebDAV fout</string>
<string name="debug_info_io_error">I/O-fout</string>
<string name="debug_info_http_403_description">Het verzoek is afgewezen. Controleer de betrokken bronnen en debug-info voor details.</string>
<string name="debug_info_http_404_description">De gevraagde bron bestaat niet (meer). Controleer de betrokken bronnen en debug-info voor details.</string>
<string name="debug_info_http_5xx_description">Er is bij de server een probleem opgetreden. Neem contact op met de server-ondersteuning.</string>
<string name="debug_info_unexpected_error">Er is een onverwachte fout opgetreden. Bekijk debug-info voor details.</string>
<string name="debug_info_view_details">Details bekijken</string>
<string name="debug_info_subtitle">Debug-info is verzameld</string>
<string name="debug_info_involved_caption">Betrokken bronnen</string>
<string name="debug_info_involved_subtitle">Gerelateerd aan het probleem</string>
<string name="debug_info_involved_remote">Externe bron:</string>
<string name="debug_info_involved_local">Lokale bron:</string>
<string name="debug_info_logs_caption">Logboeken</string>
<string name="debug_info_logs_subtitle">Uitgebreide logboeken zijn beschikbaar</string>
<string name="debug_info_logs_view">Details bekijken</string>
<string name="debug_info_privacy_warning_title">Privacyverklaring</string>
<string name="debug_info_privacy_warning_description">Logboeken en foutopsporingsgegevens kunnen privé-informatie bevatten. Houd hier rekening mee als u ze openbaar deelt.</string>
<!--ExceptionInfoFragment-->
<string name="exception">Er is een fout opgetreden.</string>
<string name="exception_httpexception">Een HTTP-fout is opgetreden.</string>
<string name="exception_ioexception">Een I/O fout is opgetreden.</string>
<string name="exception_show_details">Details weergeven</string>
<!--WebDAV accounts-->
<string name="webdav_mounts_title">WebDAV-koppelingen</string>
<string name="webdav_mounts_quota_used_available">Quotum gebruikt: %1$s / Beschikbaar: %2$s</string>
<string name="webdav_mounts_share_content">Inhoud delen</string>
<string name="webdav_mounts_unmount">Ontkoppelen</string>
<string name="webdav_add_mount_title">WebDAV-koppeling toevoegen</string>
<string name="webdav_mounts_empty">Verkrijg directe toegang tot cloudbestanden met een WebDAV-koppeling!</string>
<string name="webdav_add_mount_empty_more_info"><![CDATA[Zie de handleiding voor <a href="%1$s">hoe WebDAV-mounts werken</a>.]]></string>
<string name="webdav_add_mount_display_name">Weergavenaam</string>
<string name="webdav_add_mount_url">WebDAV-URL</string>
<string name="webdav_add_mount_url_invalid">Ongeldige URL</string>
<string name="webdav_add_mount_mountpoint_displayname">Koppelpunt en weergavenaam</string>
<string name="webdav_add_mount_authentication">Authenticatie</string>
<string name="webdav_add_mount_username">Gebruikersnaam</string>
<string name="webdav_add_mount_password">Wachtwoord</string>
<string name="webdav_add_mount_username_optional">Gebruikersnaam (optioneel)</string>
<string name="webdav_add_mount_password_optional">Wachtwoord (optioneel)</string>
<string name="webdav_add_mount_add">Koppeling toevoegen</string>
<string name="webdav_add_mount_no_support">Geen WebDAV-service op deze URL</string>
<string name="webdav_remove_mount_title">Verwijder het koppelpunt</string>
<string name="webdav_remove_mount_text">Verbindingsgegevens gaan verloren, maar er worden geen bestanden gewist.</string>
<string name="webdav_notification_access">WebDAV-bestand openen</string>
<string name="webdav_notification_download">WebDAV-bestand downloaden</string>
<string name="webdav_notification_upload">WebDAV-bestand uploaden</string>
<string name="webdav_provider_root_title">WebDAV-koppeling</string>
<!--sync-->
<string name="sync_error_permissions">DAVx⁵ rechten</string>
<string name="sync_error_permissions_text">Aanvullende rechten vereist</string>
<string name="sync_error_tasks_too_old">%ste oud</string>
<string name="sync_error_tasks_required_version">Minimaal vereiste versie: %1$s</string>
<string name="sync_error_authentication_failed">Verificatie mislukt (controleer aanmeldingsgegevens)</string>
<string name="sync_error_io">Netwerk of I/O error - %s</string>
<string name="sync_error_http_dav">HTTP-server fout - %s</string>
<string name="sync_error_local_storage">Lokale opslag fout - %s</string>
<string name="sync_error_retry_limit_reached">Soft error (max. aantal pogingen bereikt)</string>
<string name="sync_error_view_item">Item bekijken</string>
<string name="sync_invalid_contact">Ongeldig contact ontvangen van server</string>
<string name="sync_invalid_event">Ongeldige gebeurtenis ontvangen van server</string>
<string name="sync_invalid_task">Ongeldige taak ontvangen van server</string>
<string name="sync_invalid_resources_ignoring">Een of meer ongeldige bronnen negeren</string>
<string name="sync_notification_pending_push_title">Synchronisatie in afwachting</string>
<string name="sync_notification_pending_push_message">De gegevens op afstand zijn veranderd</string>
<!--widgets-->
<string name="widget_sync_all">Alles synchroniseren</string>
<string name="widget_sync_all_accounts">Alle accounts synchroniseren</string>
<string name="widget_labeled_sync_label">Gelabelde synchronisatieknop</string>
<string name="widget_icon_sync_label">Pictogram synchronisatieknop</string>
<string name="widget_sync_description">Tik om de synchronisatie handmatig uit te voeren.</string>
<!--cert4android-->
</resources>

View File

@@ -1,483 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources xmlns:tools="http://schemas.android.com/tools">
<!--common strings-->
<string name="account_invalid">Contul nu (mai) există</string>
<string name="account_title_address_book">Agenda DAVx⁵</string>
<string name="account_prefs_use_app">Nu schimba contul aici! Utilizează direct aplicația pentru a gestiona conturile în schimb.</string>
<string name="dialog_delete">Șterge</string>
<string name="dialog_remove">Elimină</string>
<string name="dialog_deny">Anulează</string>
<string name="dialog_enable">Activează</string>
<string name="field_required">Acest câmp este obligatoriu</string>
<string name="help">Ajutor</string>
<string name="navigate_up">Navigare în sus</string>
<string name="options_menu">Meniul Opțiuni</string>
<string name="share">Distribuie</string>
<string name="sync_started">Sincronizare începută/pusă în coadă</string>
<string name="database_destructive_migration_title">Bază de date deteriorată</string>
<string name="database_destructive_migration_text">Toate conturile au fost eliminate local.</string>
<string name="notification_channel_debugging">Depanare</string>
<string name="notification_channel_general">Alte mesaje importante</string>
<string name="notification_channel_status">Mesaje de stare cu prioritate redusă</string>
<string name="notification_channel_sync">Sincronizare</string>
<string name="notification_channel_sync_errors">Erori de sincronizare</string>
<string name="notification_channel_sync_errors_desc">Erori importante care opresc sincronizarea, cum ar fi răspunsurile neașteptate ale serverului</string>
<string name="notification_channel_sync_warnings">Avertismente de sincronizare</string>
<string name="notification_channel_sync_warnings_desc">Probleme de sincronizare non-fatale, cum ar fi anumite fișiere nevalide</string>
<string name="notification_channel_sync_io_errors">Erori de rețea și I/O</string>
<string name="notification_channel_sync_io_errors_desc">Expirare, probleme de conexiune etc. (adesea temporare)</string>
<!--IntroActivity-->
<string name="intro_slogan1">Datele tale. Alegerea ta.</string>
<string name="intro_slogan2">Preia controlul.</string>
<string name="intro_battery_title">Intervale regulate de sincronizare</string>
<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>
<string name="intro_tasks_jtx">Placă de bază jtx</string>
<string name="intro_tasks_jtx_info"><![CDATA[Acceptă sincronizarea sarcinilor, jurnalelor și notelor.]]></string>
<string name="intro_tasks_title">Suport pentru sarcini</string>
<string name="intro_tasks_text1">Dacă sarcinile sunt acceptate de server, acestea pot fi sincronizate cu o aplicație de sarcini acceptată:</string>
<string name="intro_tasks_opentasks">OpenTasks</string>
<string name="intro_tasks_opentasks_info">Nu pare a mai fi dezvoltat nu este recomandat.</string>
<string name="intro_tasks_tasks_org">Tasks.org</string>
<string name="intro_tasks_tasks_org_info"><![CDATA[Unele caracteristici <a href="https://www.davx5.com/faq/tasks/advanced-task-features">nu sunt acceptate</a>.]]></string>
<string name="intro_tasks_no_app_store">Nu există un magazin de aplicații disponibil</string>
<string name="intro_tasks_dont_show">Nu am nevoie de suport pentru sarcini.*</string>
<string name="intro_open_source_title">Software cu sursă deschisă</string>
<string name="intro_open_source_text">Ne bucurăm că utilizezi %s, care este un software open-source. Dezvoltarea, întreținerea și suportul sunt o muncă grea. Ia în considerare contribuția (există mai multe moduri) sau o donație. Ar fi foarte apreciat!</string>
<string name="intro_open_source_details">Cum să contribui/donezi</string>
<string name="intro_open_source_dont_show">Nu-mi aminti</string>
<plurals name="intro_open_source_dont_show_months">
<item quantity="one">%d lună</item>
<item quantity="few">%d luni</item>
<item quantity="other">%d luni</item>
</plurals>
<string name="intro_next">Înainte</string>
<!--PermissionsActivity-->
<string name="permissions_title">Permisiuni</string>
<string name="permissions_text">%s necesită permisiuni pentru a funcționa corect.</string>
<string name="permissions_all_title">Toate cele de mai jos</string>
<string name="permissions_all_status_off">Utilizează aceasta pentru a activa toate funcțiile (recomandat)</string>
<string name="permissions_all_status_on">Toate permisiunile sunt acordate</string>
<string name="permissions_contacts_title">Permisiuni Contacte</string>
<string name="permissions_contacts_status_off">Fără sincronizare de contacte (nu este recomandat)</string>
<string name="permissions_contacts_status_on">Este posibilă sincronizarea contactelor</string>
<string name="permissions_calendar_title">Permisiuni pentru calendar</string>
<string name="permissions_calendar_status_off">Fără sincronizare calendar (nu este recomandat)</string>
<string name="permissions_calendar_status_on">Sincronizarea calendarului este posibilă</string>
<string name="permissions_notification_title">Permisiune de notificare</string>
<string name="permissions_notification_status_off">Notificări dezactivate (nu este recomandat)</string>
<string name="permissions_notification_status_on">Notificări activate</string>
<string name="permissions_jtx_title">Permisiuni pentru jtx Board</string>
<string name="permissions_opentasks_title">Permisiuni OpenTasks</string>
<string name="permissions_tasksorg_title">Permisiuni pentru sarcini</string>
<string name="permissions_tasks_status_off">Nicio sincronizare a sarcinilor</string>
<string name="permissions_tasks_status_on">Este posibilă sincronizarea sarcinilor</string>
<string name="permissions_autoreset_title">Păstrează permisiunile</string>
<string name="permissions_autoreset_status_off">Permisiunile pot fi resetate automat (nu este recomandat)</string>
<string name="permissions_autoreset_status_on">Permisiunile nu vor fi resetate automat</string>
<string name="permissions_autoreset_instruction">Clic pe Permisiuni &gt; debifează „Elimină permisiunile dacă aplicația nu este utilizată”</string>
<string name="permissions_app_settings_hint">Dacă un comutator nu funcționează, utilizează setările/permisiunile aplicației.</string>
<string name="permissions_app_settings">Setările aplicației</string>
<!--WifiPermissionsActivity-->
<string name="wifi_permissions_label">Permisiuni SSID WiFi</string>
<string name="wifi_permissions_intro">Pentru a putea accesa numele actual WiFi (SSID), trebuie îndeplinite următoarele condiții:</string>
<string name="wifi_permissions_location_permission">Permisiune de locație precisă</string>
<string name="wifi_permissions_location_permission_on">Permisiunea de locație acordată</string>
<string name="wifi_permissions_location_permission_off">Permisiunea de locație refuzată</string>
<string name="wifi_permissions_background_location_permission">Permisiunea de locație în fundal</string>
<string name="wifi_permissions_background_location_permission_label">Permite tot timpul</string>
<string name="wifi_permissions_background_location_permission_on">Permisiunea locației setată la: %s</string>
<string name="wifi_permissions_background_location_permission_off">Permisiunea de locație nu este setată la: %s</string>
<string name="wifi_permissions_background_location_disclaimer">%s folosește datele locației (doar WiFi SSID) numai pentru a restricționa sincronizarea la un anumit SSID WiFi. Acest lucru se va întâmpla chiar și atunci când sincronizarea rulează în fundal.</string>
<string name="wifi_permissions_background_location_disclaimer2">Toate datele locației (doar WiFi SSID) sunt folosite doar local și nu sunt trimise nicăieri.</string>
<string name="wifi_permissions_location_enabled">Locația este întotdeauna activată</string>
<string name="wifi_permissions_location_enabled_on">Serviciul de localizare este activat</string>
<string name="wifi_permissions_location_enabled_off">Serviciul de localizare este dezactivat</string>
<!--AboutActivity-->
<string name="about_translations">Traduceri</string>
<string name="about_libraries">Biblioteci</string>
<string name="about_version">Versiune %1$s (%2$d)</string>
<string name="about_copyright">© Ricki Hirner, Bernhard Stockmann (inginerie web bitfire GmbH) și contribuitori</string>
<string name="about_license_info_no_warranty">Acest program vine cu ABSOLUT NICIO GARANȚIE. Este software gratuit și ești binevenit să îl redistribui în anumite condiții.</string>
<!--global settings-->
<string name="logging_couldnt_create_file">Nu s-a putut crea fișierul jurnal</string>
<string name="logging_notification_text">Acum se înregistrează toate activitățile %s</string>
<string name="logging_notification_view_share">Vizualizare/distribuire</string>
<string name="logging_notification_disable">Dezactivează</string>
<!--AccountsScreen-->
<string name="navigation_drawer_subtitle">Adaptor de sincronizare CalDAV/CardDAV</string>
<string name="navigation_drawer_about">Despre / Licență</string>
<string name="navigation_drawer_beta_feedback">Feedback beta</string>
<string name="install_browser">Instalează un browser web</string>
<string name="navigation_drawer_settings">Setări</string>
<string name="navigation_drawer_news_updates">Știri și actualizări</string>
<string name="navigation_drawer_tools">Instrumente</string>
<string name="navigation_drawer_external_links">Link-uri externe</string>
<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>
<string name="navigation_drawer_privacy_policy">Politica de confidențialitate</string>
<string name="account_list_welcome">Bun venit la DAVx⁵!</string>
<string name="account_list_empty">Conectează-te la server și păstrează calendarele și contactele sincronizate.</string>
<string name="accounts_sync_all">Sincronizează toate conturile</string>
<!--Sync warnings-->
<string name="sync_warning_no_notification_permission">Notificări dezactivate. Nu vei fi notificat despre erorile de sincronizare.</string>
<string name="sync_warning_no_internet">Sincronizarea automată nu este activă (fără conexiune la internet verificată).</string>
<string name="sync_warning_manage_connections">Gestionează conexiunile</string>
<string name="sync_warning_datasaver_enabled">Economizorul de date este activat. Sincronizarea în fundal este restricționată.</string>
<string name="sync_warning_manage_datasaver">Gestionează economizorul de date</string>
<string name="sync_warning_battery_saver_enabled">Economisirea bateriei este activată. Sincronizarea poate fi restricționată.</string>
<string name="sync_warning_manage_battery_saver">Gestionează economisirea bateriei</string>
<string name="sync_warning_low_storage">Spațiu de depozitare redus. Android nu va sincroniza modificările locale imediat, ci în timpul următoarei sincronizări obișnuite.</string>
<string name="sync_warning_manage_storage">Gestionează stocarea</string>
<string name="sync_warning_calendar_storage_disabled_title">Furnizorul de calendar lipsește</string>
<string name="sync_warning_calendar_storage_disabled_description">Ai dezactivat aplicația de sistem „Stocare Calendar”?</string>
<string name="sync_warning_contacts_storage_disabled_title">Furnizorul de contacte lipsește</string>
<string name="sync_warning_contacts_storage_disabled_description">Ai dezactivat aplicația de sistem „Stocare Contacte”?</string>
<string name="sync_warning_manage_apps">Gestionează aplicațiile</string>
<!--RefreshCollectionsWorker-->
<string name="refresh_collections_worker_refresh_failed">Detectarea serviciului a eșuat</string>
<string name="refresh_collections_worker_refresh_couldnt_refresh">Lista de colecții nu a putut fi actualizată</string>
<!--Foreground service used by WorkManager on Android <12-->
<string name="foreground_service_notify_title">Rulează în prim-plan</string>
<string name="foreground_service_notify_text">Pe unele dispozitive, acest lucru este necesar pentru sincronizarea automată.</string>
<!--AppSettingsActivity-->
<string name="app_settings">Setări</string>
<string name="app_settings_debug">Depanare</string>
<string name="app_settings_show_debug_info">Afișează informațiile de depanare</string>
<string name="app_settings_show_debug_info_details">Vizualizează/partajează detaliile de configurare și jurnalele</string>
<string name="app_settings_logging">Jurnalizare detaliată</string>
<string name="app_settings_logging_on">Înregistrarea este activă. Poți vizualiza jurnalele ca parte a informațiilor de depanare.</string>
<string name="app_settings_logging_off">Înregistrarea este dezactivată</string>
<string name="app_settings_battery_optimization">Optimizarea bateriei</string>
<string name="app_settings_battery_optimization_exempted">Aplicația este exclusă (recomandat)</string>
<string name="app_settings_battery_optimization_optimized">Se aplică restricții pentru baterie (nu este recomandat)</string>
<string name="app_settings_connection">Conexiune</string>
<string name="app_settings_proxy">Tip proxy</string>
<string-array name="app_settings_proxy_types">
<item>Implicit</item>
<item>Fără proxy</item>
<item>HTTP</item>
<item>SOCKS (pentru Orbot)</item>
</string-array>
<string name="app_settings_proxy_host">Nume gazdă proxy</string>
<string name="app_settings_proxy_port">Port proxy</string>
<string name="app_settings_security">Securitate</string>
<string name="app_settings_security_app_permissions">Permisiunile aplicației</string>
<string name="app_settings_security_app_permissions_summary">Examinează permisiunile necesare pentru sincronizare</string>
<string name="app_settings_distrust_system_certs">Nu avea încredere în certificatele de sistem</string>
<string name="app_settings_distrust_system_certs_on">CA de sistem și de utilizator nu vor fi de încredere</string>
<string name="app_settings_distrust_system_certs_off">CA de sistem și de utilizator vor fi de încredere (recomandat)</string>
<string name="app_settings_distrust_system_certs_dialog_message">Dacă această setare este activă, certificatele de sistem nu sunt considerate ca fiind de încredere. Aceasta înseamnă că va trebui să accepți manual fiecare certificat (de asemenea, atunci când serverul își reînnoiește certificatul) sau configurarea contului și sincronizarea nu va funcționa.</string>
<string name="app_settings_reset_certificates">Resetează certificatele de (ne)încredere</string>
<string name="app_settings_reset_certificates_summary">Resetează încrederea tuturor certificatelor personalizate</string>
<string name="app_settings_reset_certificates_success">Toate certificatele personalizate au fost șterse</string>
<string name="app_settings_user_interface">Interfață de utilizator</string>
<string name="app_settings_notification_settings">Setări de notificare</string>
<string name="app_settings_notification_settings_summary">Gestionează canalele de notificare și setările acestora</string>
<string name="app_settings_theme_title">Selectează tema</string>
<string-array name="app_settings_theme_names">
<item>Ca în sistem</item>
<item>Luminoasă</item>
<item>Întunecată</item>
</string-array>
<string name="app_settings_reset_hints">Resetează sugestiile</string>
<string name="app_settings_reset_hints_summary">Reactivează sugestiile care au fost respinse anterior</string>
<string name="app_settings_reset_hints_success">Toate sugestiile vor fi afișate din nou</string>
<string name="app_settings_integration">Integrare</string>
<string name="app_settings_tasks_provider">Aplicația de sarcini</string>
<string name="app_settings_tasks_provider_none">Nu a fost găsită nicio aplicație de sarcini compatibilă</string>
<string name="app_settings_unifiedpush">UnifiedPush (experimental)</string>
<string name="app_settings_unifiedpush_disable">Nimic (dezactivare Push)</string>
<string name="app_settings_unifiedpush_choose_distributor">Alege un distribuitor</string>
<string name="app_settings_unifiedpush_no_distributor">Nu este instalat un distribuitor push</string>
<string name="app_settings_unifiedpush_no_endpoint">Niciun punct final configurat</string>
<string name="app_settings_unifiedpush_ready">Gata să primească mesaje push peste %s</string>
<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 a fost eliminat</string>
<string name="account_carddav">CardDAV</string>
<string name="account_caldav">CalDAV</string>
<string name="account_webcal">Webcal</string>
<string name="account_missing_permissions">Sunt necesare permisiuni suplimentare pentru a sincroniza aceste colecții.</string>
<string name="account_manage_permissions">Gestionează permisiunile</string>
<string name="account_synchronize_now">Sincronizează acum</string>
<string name="account_settings">Setările contului</string>
<string name="account_rename">Redenumește contul</string>
<string name="account_rename_new_name_description">Datele locale nesalvate pot fi respinse. Resincronizarea este necesară după redenumire.</string>
<string name="account_rename_new_name">Nume cont nou</string>
<string name="account_rename_rename">Redenumește</string>
<string name="account_rename_exists_already">Numele contului este deja luat</string>
<string name="account_rename_couldnt_rename">Nu s-a putut redenumi contul</string>
<string name="account_delete">Șterge contul</string>
<string name="account_delete_confirmation_title">Chiar ștergi contul?</string>
<string name="account_delete_confirmation_text">Toate copiile locale ale agendelor, calendarelor și listelor de sarcini vor fi șterse.</string>
<string name="account_synchronize_this_collection">sincronizează această colecție</string>
<string name="account_read_only">numai pentru citire</string>
<string name="account_calendar">calendar</string>
<string name="account_contacts">contacte</string>
<string name="account_journal">jurnal</string>
<string name="account_task_list">sarcini</string>
<string name="account_only_personal">Afișează numai personal</string>
<string name="account_refresh_collections">Actualizează lista</string>
<string name="account_webcal_external_app">Abonamentele Webcal pot fi sincronizate cu aplicații externe.</string>
<string name="account_no_webcal_handler_found">Nu a fost găsită nicio aplicație compatibilă cu Webcal</string>
<string name="account_install_icsx5">Instalează ICSx⁵</string>
<!--AddAccountActivity-->
<string name="login_title">Adaugă contul</string>
<string name="login_privacy_hint"><![CDATA[Toate datele vor fi transferate numai între server și dispozitiv. %1$s nu le voi trimite altundeva. Vezi <a href="%2$s">Politica de confidențialitate</a>.]]></string>
<string name="login_generic_login">Autentificare generică</string>
<string name="login_provider_login">Autentificare specifică furnizorului</string>
<string name="login_continue">Continuă</string>
<string name="login_login">Autentificare</string>
<string name="login_type_email">Conectează-te cu adresa de e-mail</string>
<string name="login_email_address">Adresa de e-mail</string>
<string name="login_email_address_error">Este necesară o adresă de e-mail validă</string>
<string name="login_email_address_info"><![CDATA[Domeniul de e-mail este folosit ca URL de bază. <a href="%s">Serviciile sunt descoperite</a> folosind înregistrări DNS și adrese URL bine-cunoscute.]]></string>
<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>
<string name="login_add_account">Adaugă contul</string>
<string name="login_account_name">Nume de cont</string>
<string name="login_account_avoid_apostrophe">Utilizarea apostrofelor (\') pare să cauzeze probleme pe unele dispozitive.</string>
<string name="login_account_name_info">Utilizează adresa de e-mail ca nume de cont, deoarece Android va folosi numele contului ca câmp ORGANIZATOR pentru evenimentele pe care le creezi. Nu poți avea două conturi cu același nume.</string>
<string name="login_account_contact_group_method">Metoda de grupare a contactelor:</string>
<string name="login_account_name_required">Numele contului este necesar</string>
<string name="login_account_name_already_taken">Numele contului este deja luat</string>
<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>
<string name="login_fastmail">Fastmail</string>
<string name="login_fastmail_account">Cont Fastmail</string>
<string name="login_fastmail_sign_in">Conectează-te cu Fastmail</string>
<string name="login_type_google">Contacte Google / Calendar</string>
<string name="login_google_account">Cont Google</string>
<string name="login_google">Conectează-te cu Google</string>
<string name="login_google_client_id">ID client (opțional)</string>
<string name="login_google_client_privacy_policy"><![CDATA[%1$s transferă datele din Agendă Google și din Calendar numai pentru sincronizare cu acest dispozitiv. Vezi <a href="%2$s">Politica de confidențialitate</a> pentru detalii.]]></string>
<string name="login_google_client_limited_use"><![CDATA[%1$s respectă <a href="%2$s">Politica privind datele utilizatorilor serviciilor API Google</a>, inclusiv cerințele de utilizare limitată.]]></string>
<string name="login_oauth_couldnt_obtain_auth_code">Nu s-a putut obține codul de autorizare</string>
<string name="login_type_nextcloud">Nextcloud</string>
<string name="login_nextcloud_login_with_nextcloud">Conectare cu Nextcloud</string>
<string name="login_nextcloud_login_flow_text">Aceasta va porni fluxul de conectare Nextcloud într-un browser web.</string>
<string name="login_nextcloud_login_flow_server_address">Adresa serverului Nextcloud</string>
<string name="login_nextcloud_login_flow_sign_in">Conectare</string>
<string name="login_nextcloud_login_flow_no_login_url">Nu s-a putut obține adresa URL de conectare</string>
<string name="login_nextcloud_login_flow_no_login_data">Nu s-au putut obține datele de conectare</string>
<string name="login_configuration_detection">Detectarea configurației</string>
<string name="login_querying_server">Se interoghează serverul…</string>
<string name="login_no_service">Nu s-a putut găsi serviciul CalDAV sau CardDAV.</string>
<string name="login_no_service_info">Adresa URL de bază nu pare să fie o adresă URL CalDAV/CardDAV accesibilă, iar detectarea serviciului nu a avut succes.</string>
<string name="login_see_tested_services"><![CDATA[Consultă manualul furnizorului de servicii, <a href="%s">lista de servicii testate</a> și adresele lor URL de bază.]]></string>
<string name="login_check_credentials">Verifică, de asemenea, și autentificarea (de obicei, numele de utilizator și parola).</string>
<string name="login_logs_available">Informații tehnice suplimentare sunt disponibile în jurnale.</string>
<string name="login_view_logs">Vezi jurnalele</string>
<!--AccountSettingsActivity-->
<string name="settings_sync">Sincronizare</string>
<string name="settings_sync_interval_contacts">Interval de sincronizare a contactelor</string>
<string name="settings_sync_summary_manually">Doar manual</string>
<string name="settings_sync_summary_periodically" tools:ignore="PluralsCandidate">La fiecare %d minute + imediat la modificări locale</string>
<string name="settings_sync_interval_calendars">Interval de sincronizare a calendarelor</string>
<string name="settings_sync_interval_tasks">Interval de sincronizare a sarcinilor</string>
<string-array name="settings_sync_interval_names">
<item>Doar manual</item>
<item>La fiecare 15 minute</item>
<item>La fiecare 30 de minute</item>
<item>La fiecare oră</item>
<item>La fiecare 2 ore</item>
<item>La fiecare 4 ore</item>
<item>O dată pe zi</item>
</string-array>
<string name="settings_sync_wifi_only">Sincronizare numai prin WiFi</string>
<string name="settings_sync_wifi_only_on">Sincronizarea este limitată la conexiunile WiFi</string>
<string name="settings_sync_wifi_only_off">Tipul de conexiune nu este luat în considerare</string>
<string name="settings_sync_wifi_only_ssids">Restricție SSID WiFi</string>
<string name="settings_sync_wifi_only_ssids_on">Se va sincroniza numai prin %s</string>
<string name="settings_sync_wifi_only_ssids_off">Toate conexiunile WiFi vor fi utilizate</string>
<string name="settings_sync_wifi_only_ssids_message">Nume separate prin virgulă (SSID) ale rețelelor WiFi permise (lasă necompletat pentru toate)</string>
<string name="settings_sync_wifi_only_ssids_permissions_required">Restricția SSID WiFi necesită setări suplimentare</string>
<string name="settings_sync_wifi_only_ssids_permissions_action">Gestionează</string>
<string name="settings_ignore_vpns">VPN necesită internetul de bază</string>
<string name="settings_ignore_vpns_on">VPN fără conexiune validată la Internet nu este suficient pentru a rula sincronizarea (recomandat)</string>
<string name="settings_ignore_vpns_off">VPN fără conexiune validată la Internet este suficient pentru a rula sincronizarea</string>
<string name="settings_authentication">Autentificare</string>
<string name="settings_username">Nume de utilizator</string>
<string name="settings_password">Parolă sau parola aplicației</string>
<string name="settings_app_password_hint"><![CDATA[Poate preferi să utilizezi <a href="%1$s">parola aplicației</a>.]]></string>
<string name="settings_new_password">Parolă nouă</string>
<string name="settings_password_summary">Actualizează parola în funcție de server.</string>
<string name="settings_reauthorize_oauth">Autorizează din nou (OAuth)</string>
<string name="settings_reauthorize_oauth_summary">Utilizează atunci când accesul a fost revocat</string>
<string name="settings_reauthorize_oauth_success">Autorizare cu succes</string>
<string name="settings_certificate_alias">Certificat de client</string>
<string name="settings_certificate_alias_empty">Niciun certificat disponibil sau selectat</string>
<string name="settings_certificate_install">Instalare certificat</string>
<string name="settings_caldav">CalDAV</string>
<string name="settings_sync_time_range_past">Limită de timp pentru evenimentele din trecut</string>
<string name="settings_sync_time_range_past_none">Toate evenimentele vor fi sincronizate</string>
<plurals name="settings_sync_time_range_past_days">
<item quantity="one">Evenimentele cu mai mult de o zi în trecut vor fi ignorate</item>
<item quantity="few">Evenimentele cu peste %d zile în trecut vor fi ignorate</item>
<item quantity="other">Evenimentele cu peste %d zile în trecut vor fi ignorate</item>
</plurals>
<string name="settings_sync_time_range_past_message">Evenimentele care depășesc acest număr de zile în trecut vor fi ignorate (poate fi 0). Lasă necompletat pentru a sincroniza toate evenimentele.</string>
<string name="settings_default_alarm">Memento implicit</string>
<plurals name="settings_default_alarm_on">
<item quantity="one">Memento implicit cu un minut înainte de eveniment</item>
<item quantity="few">Memento implicit cu %d minute înainte de eveniment</item>
<item quantity="other">Memento implicit cu %d minute înainte de eveniment</item>
</plurals>
<string name="settings_default_alarm_off">Nu sunt create mementouri implicite</string>
<string name="settings_default_alarm_message">Dacă vor fi create memento-uri implicite pentru evenimente fără memento: numărul dorit de minute înainte de eveniment. Lasă necompletat pentru a dezactiva memento-urile implicite.</string>
<string name="settings_manage_calendar_colors">Gestionează culorile calendarului</string>
<string name="settings_manage_calendar_colors_on">Culorile calendarului sunt resetate la fiecare sincronizare</string>
<string name="settings_manage_calendar_colors_off">Culorile calendarului pot fi setate de alte aplicații</string>
<string name="settings_event_colors">Suport pentru culoarea evenimentului</string>
<string name="settings_event_colors_on">Culorile evenimentelor sunt sincronizate</string>
<string name="settings_event_colors_off">Culorile evenimentelor nu sunt sincronizate</string>
<string name="settings_carddav">CardDAV</string>
<string name="settings_contact_group_method">Metoda de grupare a contactelor</string>
<string-array name="settings_contact_group_method_entries">
<item>Grupurile sunt vCard-uri separate</item>
<item>Grupurile sunt categorii per-contact</item>
</string-array>
<!--CreateAddressBookScreen, CreateCalendarScreen-->
<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>
<string name="create_calendar_type_vtodo">Sarcini</string>
<string name="create_calendar_type_vjournal">Note/jurnal</string>
<string name="create_calendar_maybe_not_supported">Crearea calendarului prin CalDAV poate să nu fie acceptată de server.</string>
<string name="create_collection_color">Culoare</string>
<string name="create_collection_display_name">Titlu</string>
<string name="create_collection_home_set">Locația de stocare</string>
<string name="create_collection_description_optional">Descriere (opțional)</string>
<string name="create_collection_create">Crează</string>
<!--CollectionScreen-->
<string name="collection_datatype_contacts">contacte</string>
<string name="collection_datatype_events">evenimente</string>
<string name="collection_datatype_tasks">sarcini</string>
<string name="collection_delete">Șterge colecția</string>
<string name="collection_delete_warning">Această colecție (%s) și toate datele sale vor fi șterse definitiv, atât local, cât și de pe server.</string>
<string name="collection_synchronization">Sincronizare</string>
<string name="collection_synchronization_on">Sincronizarea este activată</string>
<string name="collection_synchronization_off">Sincronizarea este dezactivată</string>
<string name="collection_read_only">Numai citire</string>
<string name="collection_read_only_by_server">Numai citire (de pe server)</string>
<string name="collection_read_only_by_setting">Numai citire (după politică)</string>
<string name="collection_read_only_forced">Numai citire (doar local)</string>
<string name="collection_read_write">Citire/scriere</string>
<string name="collection_title">Titlu</string>
<string name="collection_description">Descriere</string>
<string name="collection_owner">Proprietar</string>
<string name="collection_push_support">Suport Push</string>
<string name="collection_push_web_push">Serverul informează despre suportul Push</string>
<string name="collection_push_subscribed_at">Abonat la %1$s, expiră la %2$s</string>
<string name="collection_last_sync">Ultima sincronizare (%s)</string>
<string name="collection_url">Adresă (URL)</string>
<!--debugging and DebugInfoActivity-->
<string name="debug_info_title">Informații de depanare</string>
<string name="debug_info_archive_caption">Arhivă ZIP</string>
<string name="debug_info_archive_subtitle">Conține informații de depanare și jurnale</string>
<string name="debug_info_archive_text">Partajează arhiva pentru a o transfera pe un computer, pentru a o trimite prin e-mail sau pentru a o atașa la un bilet de asistență.</string>
<string name="debug_info_archive_share">Partajează arhiva</string>
<string name="debug_info_attached">Informații de depanare atașate la acest mesaj (necesită suport pentru atașamentele aplicației care primește).</string>
<string name="debug_info_http_error">Eroare HTTP</string>
<string name="debug_info_server_error">Eroare de server</string>
<string name="debug_info_webdav_error">Eroare WebDAV</string>
<string name="debug_info_io_error">Eroare I/O</string>
<string name="debug_info_http_403_description">Solicitarea a fost respinsă. Verifică resursele implicate și informațiile de depanare pentru detalii.</string>
<string name="debug_info_http_404_description">Resursa solicitată nu mai există (mai mult). Verifică resursele implicate și informațiile de depanare pentru detalii.</string>
<string name="debug_info_http_5xx_description">A apărut o problemă la nivelul serverului. Contactează asistența serverului.</string>
<string name="debug_info_unexpected_error">A apărut o eroare neașteptată. Vezi informațiile de depanare pentru detalii.</string>
<string name="debug_info_view_details">Vezi detaliile</string>
<string name="debug_info_subtitle">Au fost colectate informații de depanare</string>
<string name="debug_info_involved_caption">Resurse implicate</string>
<string name="debug_info_involved_subtitle">Legat de problema</string>
<string name="debug_info_involved_remote">Resursa de la distanță:</string>
<string name="debug_info_involved_local">Resursa locală:</string>
<string name="debug_info_logs_caption">Jurnale</string>
<string name="debug_info_logs_subtitle">Jurnalele detaliate sunt disponibile</string>
<string name="debug_info_logs_view">Vezi jurnalele</string>
<string name="debug_info_privacy_warning_title">Notificare de confidențialitate</string>
<string name="debug_info_privacy_warning_description">Jurnalele și informațiile de depanare pot conține informații private. Fii conștient de acest lucru atunci când îl publici.</string>
<!--ExceptionInfoFragment-->
<string name="exception">A avut loc o eroare.</string>
<string name="exception_httpexception">A apărut o eroare HTTP.</string>
<string name="exception_ioexception">A apărut o eroare I/O.</string>
<string name="exception_show_details">Afișează detaliile</string>
<!--WebDAV accounts-->
<string name="webdav_mounts_title">Montări WebDAV</string>
<string name="webdav_mounts_quota_used_available">Cotă utilizată: %1$s / disponibilă: %2$s</string>
<string name="webdav_mounts_share_content">Partajează conținutul</string>
<string name="webdav_mounts_unmount">Demontează</string>
<string name="webdav_add_mount_title">Adaugă o montare WebDAV</string>
<string name="webdav_mounts_empty">Accesează direct fișierele din cloud adăugând o montare WebDAV!</string>
<string name="webdav_add_mount_empty_more_info"><![CDATA[Vezi manualul pentru a afla <a href="%1$s">cum funcționează montările WebDAV</a>.]]></string>
<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>
<string name="webdav_remove_mount_text">Detaliile conexiunii se vor pierde, dar niciun fișier nu va fi șters.</string>
<string name="webdav_notification_access">Se accesează fișierul WebDAV</string>
<string name="webdav_notification_download">Se descarcă fișierul WebDAV</string>
<string name="webdav_notification_upload">Se actualizează fișierul WebDAV</string>
<string name="webdav_provider_root_title">Montare WebDAV</string>
<!--sync-->
<string name="sync_error_permissions">Permisiuni DAVx⁵</string>
<string name="sync_error_permissions_text">Sunt necesare permisiuni suplimentare</string>
<string name="sync_error_tasks_too_old">%s prea vechi</string>
<string name="sync_error_tasks_required_version">Versiunea minimă necesară: %1$s</string>
<string name="sync_error_authentication_failed">Autentificare eșuată (verifică datele de conectare)</string>
<string name="sync_error_io">Eroare de rețea sau I/O %s</string>
<string name="sync_error_http_dav">Eroare de server HTTP %s</string>
<string name="sync_error_local_storage">Eroare de stocare locală %s</string>
<string name="sync_error_retry_limit_reached">Eroare soft (încercări maxime atinse)</string>
<string name="sync_error_view_item">Vezi elementul</string>
<string name="sync_invalid_contact">S-a primit contact nevalid de la server</string>
<string name="sync_invalid_event">S-a primit eveniment nevalid de la server</string>
<string name="sync_invalid_task">S-a primit sarcină nevalidă de la server</string>
<string name="sync_invalid_resources_ignoring">Ignorarea uneia sau mai multor resurse nevalide</string>
<string name="sync_notification_pending_push_title">Sincronizare în așteptare</string>
<string name="sync_notification_pending_push_message">Datele de la distanță s-au schimbat</string>
<!--widgets-->
<string name="widget_sync_all">Sincronizează tot</string>
<string name="widget_sync_all_accounts">Sincronizează toate conturile</string>
<string name="widget_labeled_sync_label">Eticheta butonului de sincronizare</string>
<string name="widget_icon_sync_label">Pictograma butonului de sincronizare</string>
<string name="widget_sync_description">Atinge pentru a rula sincronizarea manual.</string>
<!--cert4android-->
</resources>

View File

@@ -1,477 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources xmlns:tools="http://schemas.android.com/tools">
<!--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>
<string name="database_destructive_migration_text">所有帐户已在本地删除。</string>
<string name="notification_channel_debugging">调试</string>
<string name="notification_channel_general">其它重要消息</string>
<string name="notification_channel_status">低优先级状态消息</string>
<string name="notification_channel_sync">同步</string>
<string name="notification_channel_sync_errors">同步错误</string>
<string name="notification_channel_sync_errors_desc">导致同步停止的重要错误,如异常的服务器响应</string>
<string name="notification_channel_sync_warnings">同步警告</string>
<string name="notification_channel_sync_warnings_desc">不重要的同步问题,如某文件无效</string>
<string name="notification_channel_sync_io_errors">网络或 I/O 错误</string>
<string name="notification_channel_sync_io_errors_desc">超时、连接异常等问题(通常是临时错误)</string>
<!--IntroActivity-->
<string name="intro_slogan1">您的数据。您的选择。</string>
<string name="intro_slogan2">获得控制。</string>
<string name="intro_battery_title">定期同步间隔</string>
<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>
<string name="intro_tasks_jtx">jtx Board</string>
<string name="intro_tasks_jtx_info"><![CDATA[支持任务、日记和笔记同步]]></string>
<string name="intro_tasks_title">任务支持</string>
<string name="intro_tasks_text1">如果你的服务器支持任务,它们可以通过一个受支持的任务应用进行同步:</string>
<string name="intro_tasks_opentasks">OpenTasks </string>
<string name="intro_tasks_opentasks_info">似乎已不再开发 — 不推荐</string>
<string name="intro_tasks_tasks_org">Tasks.org</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>
<string name="permissions_autoreset_instruction">点击权限 &gt; 取消选择 “移除权限,如果应用未使用”</string>
<string name="permissions_app_settings_hint">如果切换没有正常工作,请使用应用程序设置/权限</string>
<string name="permissions_app_settings">应用设置</string>
<!--WifiPermissionsActivity-->
<string name="wifi_permissions_label">WiFi SSID权限</string>
<string name="wifi_permissions_intro">要访问当前的WiFi名称(SSID),必须满足以下条件: </string>
<string name="wifi_permissions_location_permission">精确位置权限</string>
<string name="wifi_permissions_location_permission_on">已授予位置权限</string>
<string name="wifi_permissions_location_permission_off">位置权限被拒</string>
<string name="wifi_permissions_background_location_permission">后台位置权限</string>
<string name="wifi_permissions_background_location_permission_label">始终允许</string>
<string name="wifi_permissions_background_location_permission_on">位置权限已设为:%s</string>
<string name="wifi_permissions_background_location_permission_off">位置权限未设为:%s</string>
<string name="wifi_permissions_background_location_disclaimer">%s 使用位置数据 (仅 WiFi SSID) 的目的只是为了将同步限制到特定的 WiFi SSID。即使当同步在后台运行时这也会发生。</string>
<string name="wifi_permissions_background_location_disclaimer2">所有位置数据(仅 WiFi SSID)只在本地使用,不会被发送到任何地方。</string>
<string name="wifi_permissions_location_enabled">始终允许定位</string>
<string name="wifi_permissions_location_enabled_on">位置服务已启用</string>
<string name="wifi_permissions_location_enabled_off">位置服务已禁用</string>
<!--AboutActivity-->
<string name="about_translations">翻译</string>
<string name="about_libraries">程序库</string>
<string name="about_version">版本 %1$s (%2$d)</string>
<string name="about_copyright">©Ricki Hirner, Bernhard Stockmann (bitfire web engineering GmbH) 及贡献者</string>
<string name="about_license_info_no_warranty">本程序不附带任何担保。这是一款自由软件,你可以有条件地传播它。</string>
<!--global settings-->
<string name="logging_couldnt_create_file">无法创建日志文件</string>
<string name="logging_notification_text">正记录%s的所有活动</string>
<string name="logging_notification_view_share">查看/分享</string>
<string name="logging_notification_disable">禁用</string>
<!--AccountsScreen-->
<string name="navigation_drawer_subtitle">CalDAV/CardDAV 同步器</string>
<string name="navigation_drawer_about">关于 / 许可</string>
<string name="navigation_drawer_beta_feedback">测试版反馈</string>
<string name="install_browser">请安装网页浏览器</string>
<string name="navigation_drawer_settings">设置</string>
<string name="navigation_drawer_news_updates">最新消息</string>
<string name="navigation_drawer_tools">工具</string>
<string name="navigation_drawer_external_links">外部链接</string>
<string name="navigation_drawer_website">应用网站</string>
<string name="navigation_drawer_manual">手册</string>
<string name="navigation_drawer_faq">常见问题</string>
<string name="navigation_drawer_managed">面向机构</string>
<string name="navigation_drawer_community">社区</string>
<string name="navigation_drawer_support_project">支持项目</string>
<string name="navigation_drawer_contribute">如何作贡献</string>
<string name="navigation_drawer_privacy_policy">隐私政策</string>
<string name="account_list_welcome">欢迎来到 DAVx⁵</string>
<string name="account_list_empty">连接到你的服务器,保持日历和联系人同步</string>
<string name="accounts_sync_all">同步所有账户</string>
<!--Sync warnings-->
<string name="sync_warning_no_notification_permission">已禁用通知。你将不会收到同步出错的通知</string>
<string name="sync_warning_no_internet">自动同步不活跃(无已验证的互联网连接)</string>
<string name="sync_warning_manage_connections">管理连接</string>
<string name="sync_warning_datasaver_enabled">启用了流量节省程序。后台同步受限</string>
<string name="sync_warning_manage_datasaver">管理流量节省程序</string>
<string name="sync_warning_battery_saver_enabled">启用了节电程序。同步可能受限。</string>
<string name="sync_warning_manage_battery_saver">管理节电程序</string>
<string name="sync_warning_low_storage">低存储空间。Android 不会立即同步本地更改,但会在下次定期同步时进行</string>
<string name="sync_warning_manage_storage">管理存储</string>
<string name="sync_warning_calendar_storage_disabled_title">缺少日历程序</string>
<string name="sync_warning_calendar_storage_disabled_description">你禁用了“日历存储”系统应用吗?</string>
<string name="sync_warning_contacts_storage_disabled_title">缺少联系人程序</string>
<string name="sync_warning_contacts_storage_disabled_description">你禁用了“联系人存储”系统应用吗?</string>
<string name="sync_warning_manage_apps">管理应用</string>
<!--RefreshCollectionsWorker-->
<string name="refresh_collections_worker_refresh_failed">服务配置检测失败</string>
<string name="refresh_collections_worker_refresh_couldnt_refresh">无法刷新集合列表</string>
<!--Foreground service used by WorkManager on Android <12-->
<string name="foreground_service_notify_title">运行于前台</string>
<string name="foreground_service_notify_text">在某些设备上,这是自动同步所必需的。 </string>
<!--AppSettingsActivity-->
<string name="app_settings">设置</string>
<string name="app_settings_debug">调试</string>
<string name="app_settings_show_debug_info">显示调试信息</string>
<string name="app_settings_show_debug_info_details">查看/分享配置详情和日志</string>
<string name="app_settings_logging">记录完整日志</string>
<string name="app_settings_logging_on">日志记录处于活跃状态。你可以将日志作为调试信息的一部分来查看</string>
<string name="app_settings_logging_off">日志记录已禁用</string>
<string name="app_settings_battery_optimization">电池优化</string>
<string name="app_settings_battery_optimization_exempted">排除本应用(推荐)</string>
<string name="app_settings_battery_optimization_optimized">施加电池限制(不推荐)</string>
<string name="app_settings_connection">连接</string>
<string name="app_settings_proxy">代理类型</string>
<string-array name="app_settings_proxy_types">
<item>系统默认</item>
<item>无代理</item>
<item>HTTP</item>
<item>SOCKS (用于 Orbot)</item>
</string-array>
<string name="app_settings_proxy_host">代理主机名称</string>
<string name="app_settings_proxy_port">代理端口</string>
<string name="app_settings_security">安全</string>
<string name="app_settings_security_app_permissions">应用权限</string>
<string name="app_settings_security_app_permissions_summary">查看同步所需权限</string>
<string name="app_settings_distrust_system_certs">不信任系统证书</string>
<string name="app_settings_distrust_system_certs_on">系统和用户增加的发布者不会被信任</string>
<string name="app_settings_distrust_system_certs_off">系统和用户增加的发布者会被信任(推荐)</string>
<string name="app_settings_distrust_system_certs_dialog_message">如果此设置处于开启状态,系统证书不会被认为是可信的。这表示你必须手动接受每一个证书(服务器更新其证书时也必须加以确认)或账户设置,且同步不会工作。</string>
<string name="app_settings_reset_certificates">重设证书信任状态</string>
<string name="app_settings_reset_certificates_summary">重设所有自定义证书的信任状态</string>
<string name="app_settings_reset_certificates_success">所有自定义证书已清除</string>
<string name="app_settings_user_interface">用户界面</string>
<string name="app_settings_notification_settings">通知设置</string>
<string name="app_settings_notification_settings_summary">管理通知渠道等设置</string>
<string name="app_settings_theme_title">选择主题</string>
<string-array name="app_settings_theme_names">
<item>系统默认</item>
<item>浅色</item>
<item>深色</item>
</string-array>
<string name="app_settings_reset_hints">重设提示</string>
<string name="app_settings_reset_hints_summary">重新显示之前忽略过的提示</string>
<string name="app_settings_reset_hints_success">所有提示将会再次显示</string>
<string name="app_settings_integration">集成</string>
<string name="app_settings_tasks_provider">Tasks 应用</string>
<string name="app_settings_tasks_provider_none">未找到兼容的任务应用</string>
<string name="app_settings_unifiedpush">UnifiedPush (实验性)</string>
<string name="app_settings_unifiedpush_disable">无(停用推送)</string>
<string name="app_settings_unifiedpush_choose_distributor">选择分发程序</string>
<string name="app_settings_unifiedpush_no_distributor">未安装推送分发程序</string>
<string name="app_settings_unifiedpush_no_endpoint">未配置端点</string>
<string name="app_settings_unifiedpush_ready">准备好通过 %s 接收推送消息</string>
<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>
<string name="account_missing_permissions">需要额外权限来同步这些集合</string>
<string name="account_manage_permissions">管理权限</string>
<string name="account_synchronize_now"> 立即同步</string>
<string name="account_settings">账户设置</string>
<string name="account_rename">重命名账户</string>
<string name="account_rename_new_name_description">未保存的本地数据可能会消失。重命名后需要重新同步。</string>
<string name="account_rename_new_name">新账户名</string>
<string name="account_rename_rename">重命名</string>
<string name="account_rename_exists_already">账户名已被占用</string>
<string name="account_rename_couldnt_rename">无法重命名账户</string>
<string name="account_delete">删除账户</string>
<string name="account_delete_confirmation_title">真的要删除账户吗?</string>
<string name="account_delete_confirmation_text">所有通讯录、日历和任务列表的本机存储将被删除。</string>
<string name="account_synchronize_this_collection">同步该集合</string>
<string name="account_read_only">只读</string>
<string name="account_calendar">日历</string>
<string name="account_contacts">联系人</string>
<string name="account_journal">日记</string>
<string name="account_task_list">任务</string>
<string name="account_only_personal">只显示个人</string>
<string name="account_refresh_collections">刷新列表</string>
<string name="account_webcal_external_app">可以用外部应用来同步 Webcal 订阅</string>
<string name="account_no_webcal_handler_found">找不到支持 Webcal 的应用</string>
<string name="account_install_icsx5">安装 ICSx⁵</string>
<!--AddAccountActivity-->
<string name="login_title">增加账户</string>
<string name="login_privacy_hint"><![CDATA[所有数据只会在你的服务器和设备之间传输。%1$s不会把它们发送到任何其他地方。 参见 <a href="%2$s">隐私政策</a>。]]></string>
<string name="login_generic_login">常规登录</string>
<string name="login_provider_login">特定服务商的登录</string>
<string name="login_continue">继续</string>
<string name="login_login">登录</string>
<string name="login_type_email">使用邮箱地址登录</string>
<string name="login_email_address">Email 地址</string>
<string name="login_email_address_error">请输入有效 Email 地址</string>
<string name="login_email_address_info"><![CDATA[该邮件域被用作基URL。<a href="%s">服务发现</a> 通过 DNS 记录和已知URLs 进行。]]></string>
<string name="login_password">密码</string>
<string name="login_password_hide">隐藏密码</string>
<string name="login_password_show">显示密码</string>
<string name="login_password_optional">密码(可选)</string>
<string name="login_type_url">使用 URL 和用户名登录</string>
<string name="login_user_name">用户名</string>
<string name="login_user_name_optional">用户名(可选)</string>
<string name="login_base_url">根地址</string>
<string name="login_base_url_info"><![CDATA[此基URL将被直接检查但 <a href="%s">服务发现也将</a>使用 DNS 记录 和已知 URLs 进行。]]></string>
<string name="login_select_certificate">选择证书</string>
<string name="login_add_account">增加账户</string>
<string name="login_account_name">账户显示名</string>
<string name="login_account_avoid_apostrophe">使用撇号(\')似乎会在一些设备上造成问题</string>
<string name="login_account_name_info">请使用你的邮箱地址作为帐户名,因为 Android 会将你创建的日历事件的创建者项设置为帐户名。你不能拥有多个帐户名相同的账户。</string>
<string name="login_account_contact_group_method">联系人分组方式</string>
<string name="login_account_name_required">请输入账户名</string>
<string name="login_account_name_already_taken">账户名已被占用</string>
<string name="login_account_not_added">无法添加账户</string>
<string name="login_finish">完成</string>
<string name="login_type_advanced">高级登录</string>
<string name="login_no_client_certificate_optional">无客户端证书(可选)</string>
<string name="login_client_certificate_selected">客户端证书:%s</string>
<string name="login_no_certificate_found">没有找到证书</string>
<string name="login_install_certificate">安装证书</string>
<string name="login_fastmail">Fastmail</string>
<string name="login_fastmail_account">Fastmail 账户</string>
<string name="login_fastmail_sign_in">使用 Fastmail 登录</string>
<string name="login_type_google">Google 联系人/日历</string>
<string name="login_google_account">Google 账户</string>
<string name="login_google">使用 Google 账户登录</string>
<string name="login_google_client_id">Client ID (可选)</string>
<string name="login_google_client_privacy_policy"><![CDATA[%1$s传输你的 Google 联系人和日历数据的目的仅是为了与此设备同步。详情见我们的 <a href="%2$s">隐私政策</a> 。]]></string>
<string name="login_google_client_limited_use"><![CDATA[%1$s遵守 <a href="%2$s">Google API 服务用户数据政策</a>,包括有限使用的要求。]]></string>
<string name="login_oauth_couldnt_obtain_auth_code">无法获得身份验证码</string>
<string name="login_type_nextcloud">Nextcloud</string>
<string name="login_nextcloud_login_with_nextcloud">用 Nextcloud 登录</string>
<string name="login_nextcloud_login_flow_text">这会在网页浏览器中开启 Nextcloud 登录流程</string>
<string name="login_nextcloud_login_flow_server_address">Nextcloud 服务器地址</string>
<string name="login_nextcloud_login_flow_sign_in">登录</string>
<string name="login_nextcloud_login_flow_no_login_url">无法获取登录 URL</string>
<string name="login_nextcloud_login_flow_no_login_data">无法获得登陆数据</string>
<string name="login_configuration_detection">正在配置</string>
<string name="login_querying_server">正在与服务器通信,请稍等…</string>
<string name="login_no_service">找不到 CalDAV 或 CardDAV 服务。</string>
<string name="login_no_service_info">基URL似乎不是可访问的CalDAV/CardDAV URL 且服务检测不成功。</string>
<string name="login_see_tested_services"><![CDATA[请查看服务供应商手册和 <a href="%s">我们的已测试服务列表</a> 及它们的基础 URLs.]]></string>
<string name="login_check_credentials">也请仔细核查身份验证数据(通常是用户名和密码)。</string>
<string name="login_logs_available">可以在日志中看到进一步的技术信息</string>
<string name="login_view_logs">查看日志</string>
<!--AccountSettingsActivity-->
<string name="settings_sync">同步</string>
<string name="settings_sync_interval_contacts">通讯录自动同步间隔</string>
<string name="settings_sync_summary_manually">手动同步</string>
<string name="settings_sync_summary_periodically" tools:ignore="PluralsCandidate">每 %d 分钟或本地修改后</string>
<string name="settings_sync_interval_calendars">日历自动同步间隔</string>
<string name="settings_sync_interval_tasks">任务自动同步间隔</string>
<string-array name="settings_sync_interval_names">
<item>手动同步</item>
<item>每 15 分钟</item>
<item>每 30 分钟</item>
<item>每小时</item>
<item>每 2 小时</item>
<item>每 4 小时</item>
<item>每天一次</item>
</string-array>
<string name="settings_sync_wifi_only">只在 WiFi 下同步</string>
<string name="settings_sync_wifi_only_on">同步只在 WiFi 连接下进行</string>
<string name="settings_sync_wifi_only_off">同步不受数据连接类型限制</string>
<string name="settings_sync_wifi_only_ssids">WiFi SSID 限制</string>
<string name="settings_sync_wifi_only_ssids_on">只使用 %s 网络同步</string>
<string name="settings_sync_wifi_only_ssids_off">任意 WiFi 网络均可同步</string>
<string name="settings_sync_wifi_only_ssids_message">请用半角逗号分隔允许同步的 WiFi 网络名SSID留空则允许任意网络</string>
<string name="settings_sync_wifi_only_ssids_permissions_required">WiFi SSID 限制需要进一步设置</string>
<string name="settings_sync_wifi_only_ssids_permissions_action">管理</string>
<string name="settings_ignore_vpns">VPN 需要底层互联网</string>
<string name="settings_ignore_vpns_on">没有底层验证的互联网连接的 VPN 不足以运行同步(推荐选项)</string>
<string name="settings_ignore_vpns_off">没有底层验证的互联网连接的 VPN 足以运行同步了</string>
<string name="settings_authentication">认证</string>
<string name="settings_username">用户名</string>
<string name="settings_password">密码或应用密码</string>
<string name="settings_app_password_hint"><![CDATA[你可能偏好使用 <a href="%1$s">应用密码</a>.]]></string>
<string name="settings_new_password">新密码</string>
<string name="settings_password_summary">修改服务器密码</string>
<string name="settings_reauthorize_oauth">再次授权 (OAuth)</string>
<string name="settings_reauthorize_oauth_summary">当访问权被撤销时使用</string>
<string name="settings_reauthorize_oauth_success">授权成功</string>
<string name="settings_certificate_alias">客户端证书</string>
<string name="settings_certificate_alias_empty">无证书可用或未选择证书</string>
<string name="settings_certificate_install">安装证书</string>
<string name="settings_caldav">CalDAV</string>
<string name="settings_sync_time_range_past">旧日程时间限制</string>
<string name="settings_sync_time_range_past_none">同步所有日程</string>
<plurals name="settings_sync_time_range_past_days">
<item quantity="other">%d 天前的日程不会被同步</item>
</plurals>
<string name="settings_sync_time_range_past_message">超过这个数字的天数的旧日程将会被忽略(可以为 0。留空则同步所有日程。</string>
<string name="settings_default_alarm">默认提醒</string>
<plurals name="settings_default_alarm_on">
<item quantity="other">默认事件开始前 %d 分钟提醒</item>
</plurals>
<string name="settings_default_alarm_off">默认提醒未创建</string>
<string name="settings_default_alarm_message">当没有提醒的事件需增加默认提醒时,事件开始前多少分钟触发提醒。留空以禁用默认提醒。</string>
<string name="settings_manage_calendar_colors">管理日历颜色</string>
<string name="settings_manage_calendar_colors_on">日历的颜色会在每次同步时被重置 </string>
<string name="settings_manage_calendar_colors_off">日历的颜色可以由其他应用程序设置 </string>
<string name="settings_event_colors">事件日历颜色支持</string>
<string name="settings_event_colors_on">事件颜色已同步</string>
<string name="settings_event_colors_off">事件颜色未同步</string>
<string name="settings_carddav">CardDAV</string>
<string name="settings_contact_group_method">联系人分组方式</string>
<string-array name="settings_contact_group_method_entries">
<item>按 VCard 文件分组</item>
<item>按联系人分类分组</item>
</string-array>
<!--CreateAddressBookScreen, CreateCalendarScreen-->
<string name="create_addressbook">创建通讯录</string>
<string name="create_addressbook_maybe_not_supported">服务器可能不支持通过 CalDAV 创建通讯录</string>
<string name="create_calendar">创建日历</string>
<string name="create_calendar_time_zone_optional">默认时区(可选)</string>
<string name="create_calendar_time_zone_none"></string>
<string name="create_calendar_type">可能使用的日历类型</string>
<string name="create_calendar_type_vevent">事件</string>
<string name="create_calendar_type_vtodo">任务</string>
<string name="create_calendar_type_vjournal">笔记 / 日志</string>
<string name="create_calendar_maybe_not_supported">服务器可能不支持通过 CalDAV 创建日历</string>
<string name="create_collection_color">颜色</string>
<string name="create_collection_display_name">标题</string>
<string name="create_collection_home_set">存储位置</string>
<string name="create_collection_description_optional">描述(可选)</string>
<string name="create_collection_create">创建</string>
<!--CollectionScreen-->
<string name="collection_datatype_contacts">联系人</string>
<string name="collection_datatype_events">活动</string>
<string name="collection_datatype_tasks">任务</string>
<string name="collection_delete">删除集合</string>
<string name="collection_delete_warning">此集合(%s)及其所有数据将从本地和服务器被永久删除</string>
<string name="collection_synchronization">同步</string>
<string name="collection_synchronization_on">同步已启用</string>
<string name="collection_synchronization_off">已停用同步</string>
<string name="collection_read_only">只读</string>
<string name="collection_read_only_by_server">只读(服务器)</string>
<string name="collection_read_only_by_setting">只读(设置决定)</string>
<string name="collection_read_only_forced">只读 (仅本地)</string>
<string name="collection_read_write">读/写</string>
<string name="collection_title">标题</string>
<string name="collection_description">描述</string>
<string name="collection_owner">所有者</string>
<string name="collection_push_support">推送支持</string>
<string name="collection_push_web_push">服务器宣告推送支持</string>
<string name="collection_push_subscribed_at">订阅于 %1$s过期于 %2$s</string>
<string name="collection_last_sync">上次同步(%s)</string>
<string name="collection_url">地址(URL)</string>
<!--debugging and DebugInfoActivity-->
<string name="debug_info_title">调试信息</string>
<string name="debug_info_archive_caption">ZIP 压缩文件</string>
<string name="debug_info_archive_subtitle">包含调试信息和日志</string>
<string name="debug_info_archive_text">共享压缩文件以将其传输到计算机上,通过电子邮件发送或将其附加到支持请求。</string>
<string name="debug_info_archive_share">分享压缩文件</string>
<string name="debug_info_attached">已附加调试信息到此消息(需要接收应用支持附件功能)</string>
<string name="debug_info_http_error">HTTP错误</string>
<string name="debug_info_server_error">服务器错误</string>
<string name="debug_info_webdav_error">WebDAV错误</string>
<string name="debug_info_io_error">I/O错误</string>
<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_involved_subtitle">与此问题有关</string>
<string name="debug_info_involved_remote">远程资源:</string>
<string name="debug_info_involved_local">本地资源:</string>
<string name="debug_info_logs_caption">日志</string>
<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>
<!--ExceptionInfoFragment-->
<string name="exception">出现错误</string>
<string name="exception_httpexception">出现 HTTP 错误</string>
<string name="exception_ioexception">出现 I/O 错误</string>
<string name="exception_show_details">显示详情</string>
<!--WebDAV accounts-->
<string name="webdav_mounts_title">WebDAV 文件系统</string>
<string name="webdav_mounts_quota_used_available">已用配额:%1$s/可用容量:%2$s</string>
<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_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>
<string name="webdav_add_mount_url_invalid">无效 URL</string>
<string name="webdav_add_mount_mountpoint_displayname">挂载点和显示名称</string>
<string name="webdav_add_mount_authentication">认证</string>
<string name="webdav_add_mount_username">用户名</string>
<string name="webdav_add_mount_password">密码</string>
<string name="webdav_add_mount_username_optional">用户名(可选)</string>
<string name="webdav_add_mount_password_optional">密码(可选)</string>
<string name="webdav_add_mount_add">添加 WebDAV 网址</string>
<string name="webdav_add_mount_no_support">此 URL 无 WebDAV 服务</string>
<string name="webdav_remove_mount_title">删除装载点</string>
<string name="webdav_remove_mount_text">将丢失连接详情,但不会删除文件</string>
<string name="webdav_notification_access">正在访问 WebDAV 文件</string>
<string name="webdav_notification_download">正在下载 WebDAV 文件</string>
<string name="webdav_notification_upload">正在上传 WebDAV 文件</string>
<string name="webdav_provider_root_title">WebDAV 文件系统</string>
<!--sync-->
<string name="sync_error_permissions">DAVx⁵ 权限</string>
<string name="sync_error_permissions_text">需要额外权限</string>
<string name="sync_error_tasks_too_old">%s太旧</string>
<string name="sync_error_tasks_required_version">最低要求版本: %1$s</string>
<string name="sync_error_authentication_failed">认证失败(请检查登录凭据,如用户名密码)</string>
<string name="sync_error_io">网络或 I/O 错误 %s</string>
<string name="sync_error_http_dav">HTTP 服务器错误 %s</string>
<string name="sync_error_local_storage">本地存储错误 %s</string>
<string name="sync_error_retry_limit_reached">软错误(达到最大重试次数)</string>
<string name="sync_error_view_item">显示项目</string>
<string name="sync_invalid_contact">从服务器收到无效的通讯录</string>
<string name="sync_invalid_event">从服务器收到无效的日历事件</string>
<string name="sync_invalid_task">从服务器收到无效的任务项</string>
<string name="sync_invalid_resources_ignoring">正在忽略若干无效资源</string>
<string name="sync_notification_pending_push_title">待同步</string>
<string name="sync_notification_pending_push_message">远程数据已更改</string>
<!--widgets-->
<string name="widget_sync_all">同步所有</string>
<string name="widget_sync_all_accounts">同步所有账户</string>
<string name="widget_labeled_sync_label">带标签的同步按钮</string>
<string name="widget_icon_sync_label">同步按钮图标</string>
<string name="widget_sync_description">轻按手动运行同步</string>
<!--cert4android-->
</resources>

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config xmlns:tools="http://schemas.android.com/tools">
<base-config cleartextTrafficPermitted="true" tools:ignore="InsecureBaseConfiguration">
<trust-anchors>
<certificates src="system"/>
<certificates src="user" tools:ignore="AcceptsUserCertificates" />
</trust-anchors>
</base-config>
</network-security-config>

View File

@@ -4,10 +4,10 @@
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.android.library) apply false
alias(libs.plugins.compose.compiler) apply false
alias(libs.plugins.hilt) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.ksp) apply false
alias(libs.plugins.mikepenz.aboutLibraries) apply false
alias(libs.plugins.mikepenz.aboutLibraries.android) apply false
}

View File

View File

@@ -3,13 +3,12 @@
*/
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.android.library)
alias(libs.plugins.compose.compiler)
alias(libs.plugins.hilt)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.ksp)
alias(libs.plugins.mikepenz.aboutLibraries)
alias(libs.plugins.mikepenz.aboutLibraries.android)
}
// Android configuration
@@ -17,17 +16,7 @@ android {
compileSdk = 36
defaultConfig {
applicationId = "at.bitfire.davdroid"
versionCode = 405040002
versionName = "4.5.4-rc.1"
base.archivesName = "davx5-ose-$versionName"
minSdk = 24 // Android 7.0
targetSdk = 36 // Android 16
buildConfigField("boolean", "customCertsUI", "true")
testInstrumentationRunner = "at.bitfire.davdroid.HiltTestRunner"
}
@@ -53,37 +42,9 @@ android {
// Java namespace for our classes (not to be confused with Android package ID)
namespace = "at.bitfire.davdroid"
flavorDimensions += "distribution"
productFlavors {
create("ose") {
dimension = "distribution"
versionNameSuffix = "-ose"
}
}
sourceSets {
getByName("androidTest") {
assets.srcDir("$projectDir/schemas")
}
}
signingConfigs {
create("bitfire") {
storeFile = file(System.getenv("ANDROID_KEYSTORE") ?: "/dev/null")
storePassword = System.getenv("ANDROID_KEYSTORE_PASSWORD")
keyAlias = System.getenv("ANDROID_KEY_ALIAS")
keyPassword = System.getenv("ANDROID_KEY_PASSWORD")
}
}
buildTypes {
getByName("release") {
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules-release.pro")
isShrinkResources = true
signingConfig = signingConfigs.findByName("bitfire")
isMinifyEnabled = false
}
}
@@ -91,10 +52,6 @@ android {
disable += arrayOf("GoogleAppIndexingWarning", "ImpliedQuantity", "MissingQuantity", "MissingTranslation", "ExtraTranslation", "RtlEnabled", "RtlHardcoded", "Typos")
}
androidResources {
generateLocaleConfig = true
}
packaging {
resources {
// multiple (test) dependencies have LICENSE files at same location
@@ -123,12 +80,14 @@ ksp {
}
aboutLibraries {
// exclude timestamps for reproducible builds [https://github.com/bitfireAT/davx5-ose/issues/994]
excludeFields = arrayOf("generated")
export {
// exclude timestamps for reproducible builds [https://github.com/bitfireAT/davx5-ose/issues/994]
excludeFields.add("generated")
}
}
dependencies {
// core
// Kotlin / Android
implementation(libs.kotlin.stdlib)
implementation(libs.kotlinx.coroutines)
coreLibraryDesugaring(libs.android.desugaring)
@@ -156,21 +115,22 @@ dependencies {
// Jetpack Compose
implementation(libs.compose.accompanist.permissions)
implementation(platform(libs.compose.bom))
implementation(libs.compose.material3)
implementation(libs.compose.materialIconsExtended)
debugImplementation(libs.compose.ui.tooling)
implementation(libs.compose.ui.toolingPreview)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.material3.adaptive)
implementation(libs.androidx.compose.materialIconsExtended)
debugImplementation(libs.androidx.compose.ui.tooling)
implementation(libs.androidx.compose.ui.toolingPreview)
// Glance Widgets
implementation(libs.glance.base)
implementation(libs.glance.material)
implementation(libs.androidx.glance.base)
implementation(libs.androidx.glance.material3)
// Jetpack Room
implementation(libs.room.runtime)
implementation(libs.room.base)
implementation(libs.room.paging)
ksp(libs.room.compiler)
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.room.base)
implementation(libs.androidx.room.paging)
ksp(libs.androidx.room.compiler)
// own libraries
implementation(libs.bitfire.cert4android)
@@ -184,10 +144,14 @@ dependencies {
}
// third-party libs
@Suppress("RedundantSuppression")
implementation(libs.conscrypt)
implementation(libs.dnsjava)
implementation(libs.guava)
implementation(libs.mikepenz.aboutLibraries)
implementation(libs.ktor.client.content.negotiation)
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.okhttp)
implementation(libs.ktor.serialization.kotlinx.json)
implementation(libs.mikepenz.aboutLibraries.m3)
implementation(libs.okhttp.base)
implementation(libs.okhttp.brotli)
implementation(libs.okhttp.logging)
@@ -206,6 +170,7 @@ dependencies {
// for tests
androidTestImplementation(libs.androidx.arch.core.testing)
androidTestImplementation(libs.androidx.room.testing)
androidTestImplementation(libs.androidx.test.core)
androidTestImplementation(libs.androidx.test.junit)
androidTestImplementation(libs.androidx.test.rules)
@@ -216,10 +181,10 @@ dependencies {
androidTestImplementation(libs.kotlinx.coroutines.test)
androidTestImplementation(libs.mockk.android)
androidTestImplementation(libs.okhttp.mockwebserver)
androidTestImplementation(libs.room.testing)
testImplementation(libs.bitfire.dav4jvm)
testImplementation(libs.junit)
testImplementation(libs.mockk)
testImplementation(libs.okhttp.mockwebserver)
testImplementation(libs.robolectric)
}

View File

View File

@@ -24,3 +24,8 @@
-dontwarn sun.net.spi.nameservice.NameService
-dontwarn sun.net.spi.nameservice.NameServiceDescriptor
-dontwarn org.xbill.DNS.spi.DnsjavaInetAddressResolverProvider
# okhttp
# https://github.com/bitfireAT/davx5/issues/711 / https://github.com/square/okhttp/issues/8574
-keep class okhttp3.internal.idn.IdnaMappingTable { *; }
-keep class okhttp3.internal.idn.IdnaMappingTableInstanceKt{ *; }

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:installLocation="internalOnly">
<!-- account management permissions not required for own accounts since API level 22 -->
@@ -7,4 +8,9 @@
<uses-permission android:name="android.permission.GET_ACCOUNTS" android:maxSdkVersion="22"/>
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" android:maxSdkVersion="22"/>
<!--
Since Mockk 1.14.7 it's required to use minSdk 26. We use 24, so override for tests.
-->
<uses-sdk tools:overrideLibrary="io.mockk.android,io.mockk.proxy.android" />
</manifest>

View File

@@ -10,7 +10,6 @@ import android.os.Build
import android.os.Bundle
import androidx.test.runner.AndroidJUnitRunner
import at.bitfire.davdroid.di.TestCoroutineDispatchersModule
import at.bitfire.davdroid.test.BuildConfig
import at.bitfire.synctools.log.LogcatHandler
import dagger.hilt.android.testing.HiltTestApplication
import java.util.logging.Level
@@ -29,7 +28,7 @@ class HiltTestRunner : AndroidJUnitRunner() {
val rootLogger = Logger.getLogger("")
rootLogger.level = Level.ALL
rootLogger.handlers.forEach { rootLogger.removeHandler(it) }
rootLogger.addHandler(LogcatHandler(BuildConfig.APPLICATION_ID))
rootLogger.addHandler(LogcatHandler(javaClass.name))
// MockK requirements
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P)

View File

@@ -4,22 +4,20 @@
package at.bitfire.davdroid.db
import android.security.NetworkSecurityPolicy
import androidx.test.filters.SmallTest
import at.bitfire.dav4jvm.DavResource
import at.bitfire.dav4jvm.property.webdav.ResourceType
import at.bitfire.davdroid.network.HttpClient
import at.bitfire.dav4jvm.okhttp.DavResource
import at.bitfire.dav4jvm.property.webdav.WebDAV
import at.bitfire.davdroid.network.HttpClientBuilder
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Assume
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -29,12 +27,12 @@ import javax.inject.Inject
class CollectionTest {
@Inject
lateinit var httpClientBuilder: HttpClient.Builder
lateinit var httpClientBuilder: HttpClientBuilder
@get:Rule
val hiltRule = HiltAndroidRule(this)
private lateinit var httpClient: HttpClient
private lateinit var httpClient: OkHttpClient
private val server = MockWebServer()
@Before
@@ -42,12 +40,6 @@ class CollectionTest {
hiltRule.inject()
httpClient = httpClientBuilder.build()
Assume.assumeTrue(NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted)
}
@After
fun teardown() {
httpClient.close()
}
@@ -69,8 +61,8 @@ class CollectionTest {
"</multistatus>"))
lateinit var info: Collection
DavResource(httpClient.okHttpClient, server.url("/"))
.propfind(0, ResourceType.NAME) { response, _ ->
DavResource(httpClient, server.url("/"))
.propfind(0, WebDAV.ResourceType) { response, _ ->
info = Collection.fromDavResponse(response) ?: throw IllegalArgumentException()
}
assertEquals(Collection.TYPE_ADDRESSBOOK, info.type)
@@ -125,8 +117,8 @@ class CollectionTest {
"</multistatus>"))
lateinit var info: Collection
DavResource(httpClient.okHttpClient, server.url("/"))
.propfind(0, ResourceType.NAME) { response, _ ->
DavResource(httpClient, server.url("/"))
.propfind(0, WebDAV.ResourceType) { response, _ ->
info = Collection.fromDavResponse(response)!!
}
assertEquals(Collection.TYPE_CALENDAR, info.type)
@@ -161,8 +153,8 @@ class CollectionTest {
"</multistatus>"))
lateinit var info: Collection
DavResource(httpClient.okHttpClient, server.url("/"))
.propfind(0, ResourceType.NAME) { response, _ ->
DavResource(httpClient, server.url("/"))
.propfind(0, WebDAV.ResourceType) { response, _ ->
info = Collection.fromDavResponse(response)!!
}
assertEquals(Collection.TYPE_CALENDAR, info.type)
@@ -195,8 +187,8 @@ class CollectionTest {
"</multistatus>"))
lateinit var info: Collection
DavResource(httpClient.okHttpClient, server.url("/"))
.propfind(0, ResourceType.NAME) { response, _ ->
DavResource(httpClient, server.url("/"))
.propfind(0, WebDAV.ResourceType) { response, _ ->
info = Collection.fromDavResponse(response) ?: throw IllegalArgumentException()
}
assertEquals(Collection.TYPE_WEBCAL, info.type)

View File

@@ -0,0 +1,96 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.db
import android.database.sqlite.SQLiteConstraintException
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import io.mockk.junit4.MockKRule
import io.mockk.spyk
import io.mockk.verify
import kotlinx.coroutines.test.runTest
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject
@HiltAndroidTest
class PrincipalDaoTest {
@get:Rule
val hiltRule = HiltAndroidRule(this)
@get:Rule
val mockKRule = MockKRule(this)
@Inject
lateinit var db: AppDatabase
private lateinit var principalDao: PrincipalDao
private lateinit var service: Service
private val url = "https://example.com/dav/principal".toHttpUrl()
@Before
fun setUp() {
hiltRule.inject()
principalDao = spyk(db.principalDao())
service = Service(id = 1, accountName = "account", type = "webdav")
db.serviceDao().insertOrReplace(service)
}
@Test
fun insertOrUpdate_insertsIfNotExisting() = runTest {
val principal = Principal(serviceId = service.id, url = url, displayName = "principal")
val id = principalDao.insertOrUpdate(service.id, principal)
assertTrue(id > 0)
val stored = principalDao.get(id)
assertEquals("principal", stored.displayName)
verify(exactly = 0) { principalDao.update(any()) }
}
@Test
fun insertOrUpdate_doesNotUpdateIfDisplayNameIsEqual() = runTest {
val principalOld = Principal(serviceId = service.id, url = url, displayName = "principalOld")
val idOld = principalDao.insertOrUpdate(service.id, principalOld)
val principalNew = Principal(serviceId = service.id, url = url, displayName = "principalOld")
val idNew = principalDao.insertOrUpdate(service.id, principalNew)
assertEquals(idOld, idNew)
val stored = principalDao.get(idOld)
assertEquals("principalOld", stored.displayName)
verify(exactly = 0) { principalDao.update(any()) }
}
@Test
fun insertOrUpdate_updatesIfDisplayNameIsDifferent() = runTest {
val principalOld = Principal(serviceId = service.id, url = url, displayName = "principalOld")
val idOld = principalDao.insertOrUpdate(service.id, principalOld)
val principalNew = Principal(serviceId = service.id, url = url, displayName = "principalNew")
val idNew = principalDao.insertOrUpdate(service.id, principalNew)
assertEquals(idOld, idNew)
val updated = principalDao.get(idOld)
assertEquals("principalNew", updated.displayName)
verify(exactly = 1) { principalDao.update(any()) }
}
@Test(expected = SQLiteConstraintException::class)
fun insertOrUpdate_throwsForeignKeyConstraintViolationException() = runTest {
// throws on non-existing service
val url = "https://example.com/dav/principal".toHttpUrl()
val principal1 = Principal(serviceId = 999, url = url, displayName = "p1")
principalDao.insertOrUpdate(999, principal1)
}
}

View File

@@ -5,6 +5,10 @@
package at.bitfire.davdroid.di
import at.bitfire.davdroid.di.TestCoroutineDispatchersModule.standardTestDispatcher
import at.bitfire.davdroid.di.scope.DefaultDispatcher
import at.bitfire.davdroid.di.scope.IoDispatcher
import at.bitfire.davdroid.di.scope.MainDispatcher
import at.bitfire.davdroid.di.scope.SyncDispatcher
import dagger.Module
import dagger.Provides
import dagger.hilt.components.SingletonComponent

View File

@@ -0,0 +1,31 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.network
import org.conscrypt.Conscrypt
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import java.security.Security
class ConscryptIntegrationTest {
val integration = ConscryptIntegration()
@Test
fun testInitialize_InstallsConscrypt() {
uninstallConscrypt()
assertFalse(integration.conscryptInstalled())
integration.initialize()
assertTrue(integration.conscryptInstalled())
}
private fun uninstallConscrypt() {
for (conscrypt in Security.getProviders().filter { Conscrypt.isConscrypt(it) })
Security.removeProvider(conscrypt.name)
}
}

View File

@@ -4,9 +4,11 @@
package at.bitfire.davdroid.network
import android.security.NetworkSecurityPolicy
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
import kotlinx.coroutines.test.runTest
import okhttp3.Request
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
@@ -14,30 +16,27 @@ import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Assume
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject
import javax.inject.Provider
@HiltAndroidTest
class HttpClientTest {
class HttpClientBuilderTest {
@get:Rule
var hiltRule = HiltAndroidRule(this)
@Inject
lateinit var httpClientBuilder: HttpClient.Builder
lateinit var httpClientBuilder: Provider<HttpClientBuilder>
lateinit var httpClient: HttpClient
lateinit var server: MockWebServer
@Before
fun setUp() {
hiltRule.inject()
httpClient = httpClientBuilder.build()
server = MockWebServer()
server.start(30000)
}
@@ -45,13 +44,32 @@ class HttpClientTest {
@After
fun tearDown() {
server.shutdown()
httpClient.close()
}
@Test
fun testBuild_SharesConnectionPoolAndDispatcher() {
val client1 = httpClientBuilder.get().build()
val client2 = httpClientBuilder.get().build()
assertEquals(client1.connectionPool, client2.connectionPool)
assertEquals(client1.dispatcher, client2.dispatcher)
}
@Test
fun testBuildKtor_CreatesWorkingClient() = runTest {
server.enqueue(MockResponse()
.setResponseCode(200)
.setBody("Some Content"))
httpClientBuilder.get().buildKtor().use { client ->
val response = client.get(server.url("/").toString())
assertEquals(200, response.status.value)
assertEquals("Some Content", response.bodyAsText())
}
}
@Test
fun testCookies() {
Assume.assumeTrue(NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted)
val url = server.url("/test")
// set cookie for root path (/) and /test path in first response
@@ -60,7 +78,9 @@ class HttpClientTest {
.addHeader("Set-Cookie", "cookie1=1; path=/")
.addHeader("Set-Cookie", "cookie2=2")
.setBody("Cookie set"))
httpClient.okHttpClient.newCall(Request.Builder()
val httpClient = httpClientBuilder.get().build()
httpClient.newCall(Request.Builder()
.get().url(url)
.build()).execute()
assertNull(server.takeRequest().getHeader("Cookie"))
@@ -71,7 +91,7 @@ class HttpClientTest {
.addHeader("Set-Cookie", "cookie1=1a; path=/; Max-Age=0")
.addHeader("Set-Cookie", "cookie2=2a")
.setResponseCode(200))
httpClient.okHttpClient.newCall(Request.Builder()
httpClient.newCall(Request.Builder()
.get().url(url)
.build()).execute()
val header = server.takeRequest().getHeader("Cookie")
@@ -79,7 +99,7 @@ class HttpClientTest {
server.enqueue(MockResponse()
.setResponseCode(200))
httpClient.okHttpClient.newCall(Request.Builder()
httpClient.newCall(Request.Builder()
.get().url(url)
.build()).execute()
assertEquals("cookie2=2a", server.takeRequest().getHeader("Cookie"))

View File

@@ -17,7 +17,7 @@ import javax.inject.Inject
class OkhttpClientTest {
@Inject
lateinit var httpClientBuilder: HttpClient.Builder
lateinit var httpClientBuilder: HttpClientBuilder
@get:Rule
val hiltRule = HiltAndroidRule(this)
@@ -31,16 +31,15 @@ class OkhttpClientTest {
@Test
@SdkSuppress(maxSdkVersion = 34)
fun testIcloudWithSettings() {
httpClientBuilder.build().use { client ->
client.okHttpClient
.newCall(
Request.Builder()
.get()
.url("https://icloud.com")
.build()
)
.execute()
}
val client = httpClientBuilder.build()
client
.newCall(
Request.Builder()
.get()
.url("https://icloud.com")
.build()
)
.execute()
}
}

View File

@@ -0,0 +1,214 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.repository
import android.accounts.Account
import android.accounts.AccountManager
import android.content.Context
import androidx.hilt.work.HiltWorkerFactory
import at.bitfire.davdroid.R
import at.bitfire.davdroid.TestUtils
import at.bitfire.davdroid.resource.LocalAddressBookStore
import at.bitfire.davdroid.resource.LocalCalendarStore
import at.bitfire.davdroid.resource.LocalDataStore
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.sync.AutomaticSyncManager
import at.bitfire.davdroid.sync.SyncDataType
import at.bitfire.davdroid.sync.TasksAppManager
import at.bitfire.davdroid.sync.account.AccountsCleanupWorker
import at.bitfire.davdroid.sync.account.TestAccount
import at.bitfire.davdroid.sync.worker.SyncWorkerManager
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.testing.BindValue
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import io.mockk.clearAllMocks
import io.mockk.coVerify
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.junit4.MockKRule
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.unmockkObject
import io.mockk.verify
import junit.framework.TestCase.assertTrue
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject
@HiltAndroidTest
class AccountRepositoryTest {
@get:Rule
val hiltRule = HiltAndroidRule(this)
@get:Rule
val mockKRule = MockKRule(this)
// System under test
@Inject
lateinit var accountRepository: AccountRepository
// Real injections
@Inject
@ApplicationContext
lateinit var context: Context
@Inject
lateinit var accountSettingsFactory: AccountSettings.Factory
@Inject
lateinit var workerFactory: HiltWorkerFactory
// Dependency overrides
@BindValue @MockK(relaxed = true)
lateinit var automaticSyncManager: AutomaticSyncManager
@BindValue @MockK(relaxed = true)
lateinit var localAddressBookStore: LocalAddressBookStore
@BindValue @MockK(relaxed = true)
lateinit var localCalendarStore: LocalCalendarStore
@BindValue @MockK(relaxed = true)
lateinit var serviceRepository: DavServiceRepository
@BindValue @MockK(relaxed = true)
lateinit var syncWorkerManager: SyncWorkerManager
@BindValue @MockK(relaxed = true)
lateinit var tasksAppManager: TasksAppManager
// Account setup
private val newName = "Renamed Account"
lateinit var am: AccountManager
lateinit var accountType: String
lateinit var account: Account
@Before
fun setUp() {
hiltRule.inject()
TestUtils.setUpWorkManager(context, workerFactory)
// Account setup
am = AccountManager.get(context)
accountType = context.getString(R.string.account_type)
account = TestAccount.create()
// AccountsCleanupWorker static mocking
mockkObject(AccountsCleanupWorker)
every { AccountsCleanupWorker.lockAccountsCleanup() } returns Unit
}
@After
fun tearDown() {
am.getAccountsByType(accountType).forEach { account ->
am.removeAccountExplicitly(account)
}
unmockkObject(AccountsCleanupWorker)
clearAllMocks()
}
// testRename
@Test(expected = IllegalArgumentException::class)
fun testRename_checksForAlreadyExisting() = runTest {
val existing = Account("Existing Account", accountType)
am.addAccountExplicitly(existing, null, null)
accountRepository.rename(account.name, existing.name)
}
@Test
fun testRename_locksAccountsCleanup() = runTest {
accountRepository.rename(account.name, newName)
verify { AccountsCleanupWorker.lockAccountsCleanup() }
}
@Test
fun testRename_renamesAccountInAndroid() = runTest {
accountRepository.rename(account.name, newName)
val accountsAfter = am.getAccountsByType(accountType)
assertTrue(accountsAfter.any { it.name == newName })
}
@Test
fun testRename_cancelsRunningSynchronizationOfOldAccount() = runTest {
accountRepository.rename(account.name, newName)
coVerify { syncWorkerManager.cancelAllWork(account) }
}
@Test
fun testRename_disablesPeriodicSyncsForOldAccount() = runTest {
accountRepository.rename(account.name, newName)
for (dataType in SyncDataType.entries)
coVerify(exactly = 1) {
syncWorkerManager.disablePeriodic(account, dataType)
}
}
@Test
fun testRename_updatesAccountNameReferencesInDatabase() = runTest {
accountRepository.rename(account.name, newName)
coVerify { serviceRepository.renameAccount(account.name, newName) }
}
@Test
fun testRename_updatesAddressBooks() = runTest {
accountRepository.rename(account.name, newName)
val newAccount = accountRepository.fromName(newName)
coVerify { localAddressBookStore.updateAccount(account, newAccount, any()) }
}
@Test
fun testRename_updatesCalendarEvents() = runTest {
accountRepository.rename(account.name, newName)
val newAccount = accountRepository.fromName(newName)
coVerify { localCalendarStore.updateAccount(account, newAccount, any()) }
}
@Test
fun testRename_updatesAccountNameOfLocalTasks() = runTest {
val mockDataStore = mockk<LocalDataStore<*>>(relaxed = true)
every { tasksAppManager.getDataStore() } returns mockDataStore
accountRepository.rename(account.name, newName)
val newAccount = accountRepository.fromName(newName)
coVerify { mockDataStore.updateAccount(account, newAccount, any()) }
}
@Test
fun testRename_updatesAutomaticSync() = runTest {
accountRepository.rename(account.name, newName)
val newAccount = accountRepository.fromName(newName)
coVerify { automaticSyncManager.updateAutomaticSync(newAccount) }
}
@Test
fun testRename_releasesAccountsCleanupWorkerMutex() = runTest {
accountRepository.rename(account.name, newName)
verify { AccountsCleanupWorker.lockAccountsCleanup() }
coVerify { serviceRepository.renameAccount(account.name, newName) }
}
}

View File

@@ -0,0 +1,114 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.resource
import android.accounts.Account
import android.content.ContentProviderClient
import android.content.Context
import android.net.Uri
import android.provider.CalendarContract
import android.provider.CalendarContract.Calendars
import androidx.core.content.contentValuesOf
import at.bitfire.davdroid.sync.account.TestAccount
import at.bitfire.ical4android.util.MiscUtils.asSyncAdapter
import at.bitfire.ical4android.util.MiscUtils.closeCompat
import at.bitfire.synctools.test.InitCalendarProviderRule
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assume
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TestRule
import javax.inject.Inject
@HiltAndroidTest
class LocalCalendarStoreTest {
@get:Rule
val hiltRule = HiltAndroidRule(this)
@get:Rule
val initCalendarProviderRule: TestRule = InitCalendarProviderRule.initialize()
@Inject @ApplicationContext
lateinit var context: Context
@Inject
lateinit var localCalendarStore: LocalCalendarStore
private lateinit var provider: ContentProviderClient
private lateinit var account: Account
private lateinit var calendarUri: Uri
@Before
fun setUp() {
hiltRule.inject()
provider = context.contentResolver.acquireContentProviderClient(CalendarContract.AUTHORITY)!!
account = TestAccount.create(accountName = "InitialAccountName")
calendarUri = createCalendarForAccount(account)
}
@After
fun tearDown() {
provider.delete(calendarUri, null, null)
TestAccount.remove(account)
provider.closeCompat()
}
@Ignore("Flaky in CI")
@Test
fun testUpdateAccount_updatesOwnerAccount() {
// Verify initial state (assume to skip and prevent flaky test failures)
Assume.assumeTrue("InitialAccountName" == getOwnerAccount())
// Rename account
val oldAccount = account
account = TestAccount.rename(account, "ChangedAccountName")
// Update account name in local calendar
localCalendarStore.updateAccount(oldAccount, account, provider)
// Verify [Calendar.OWNER_ACCOUNT] of local calendar was updated
assertEquals("ChangedAccountName", getOwnerAccount())
}
// helpers
private fun createCalendarForAccount(account: Account): Uri =
provider.insert(
Calendars.CONTENT_URI.asSyncAdapter(account),
contentValuesOf(
Calendars.ACCOUNT_NAME to account.name,
Calendars.ACCOUNT_TYPE to account.type,
Calendars.OWNER_ACCOUNT to account.name,
Calendars.VISIBLE to 1,
Calendars.SYNC_EVENTS to 1,
Calendars._SYNC_ID to 999,
Calendars.CALENDAR_DISPLAY_NAME to "displayName",
)
)!!.asSyncAdapter(account)
private fun getOwnerAccount(): String? {
provider.query(
calendarUri,
arrayOf(Calendars.OWNER_ACCOUNT),
"${Calendars.ACCOUNT_NAME}=?",
arrayOf(account.name),
null
)!!.use { cursor ->
if (!cursor.moveToNext())
return null
return cursor.getString(0)
}
}
}

View File

@@ -6,7 +6,6 @@ package at.bitfire.davdroid.resource
import android.accounts.Account
import android.content.ContentProviderClient
import android.content.ContentUris
import android.content.ContentValues
import android.content.Entity
import android.provider.CalendarContract
@@ -14,22 +13,15 @@ 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.storage.calendar.EventsContract
import at.bitfire.synctools.test.InitCalendarProviderRule
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import net.fortuna.ical4j.model.property.DtStart
import net.fortuna.ical4j.model.property.RRule
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
@@ -73,93 +65,6 @@ class LocalCalendarTest {
}
@Test
fun testDeleteDirtyEventsWithoutInstances_NoInstances_CancelledExceptions() {
// create recurring event with only deleted/cancelled instances
val event = Event().apply {
dtStart = DtStart("20220120T010203Z")
summary = "Event with 3 instances"
rRules.add(RRule("FREQ=DAILY;COUNT=3"))
exceptions.add(Event().apply {
recurrenceId = RecurrenceId("20220120T010203Z")
dtStart = DtStart("20220120T010203Z")
summary = "Cancelled exception on 1st day"
status = Status.VEVENT_CANCELLED
})
exceptions.add(Event().apply {
recurrenceId = RecurrenceId("20220121T010203Z")
dtStart = DtStart("20220121T010203Z")
summary = "Cancelled exception on 2nd day"
status = Status.VEVENT_CANCELLED
})
exceptions.add(Event().apply {
recurrenceId = RecurrenceId("20220122T010203Z")
dtStart = DtStart("20220122T010203Z")
summary = "Cancelled exception on 3rd day"
status = Status.VEVENT_CANCELLED
})
}
calendar.add(
event = event,
fileName = "filename.ics",
eTag = null,
scheduleTag = null,
flags = LocalResource.FLAG_REMOTELY_PRESENT
)
val localEvent = calendar.findByName("filename.ics")!!
val eventId = 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)
// this method should mark the event as deleted
calendar.deleteDirtyEventsWithoutInstances()
// verify that event is now marked as deleted
client.query(
ContentUris.withAppendedId(Events.CONTENT_URI.asSyncAdapter(account), eventId),
arrayOf(Events.DELETED), null, null, null
)!!.use { cursor ->
cursor.moveToNext()
assertEquals(1, cursor.getInt(0))
}
}
@Test
// Needs InitCalendarProviderRule
fun testDeleteDirtyEventsWithoutInstances_Recurring_Instances() {
val event = Event().apply {
dtStart = DtStart("20220120T010203Z")
summary = "Event with 3 instances"
rRules.add(RRule("FREQ=DAILY;COUNT=3"))
}
calendar.add(
event = event,
fileName = "filename.ics",
eTag = null,
scheduleTag = null,
flags = LocalResource.FLAG_REMOTELY_PRESENT
)
val localEvent = calendar.findByName("filename.ics")!!
val eventUrl = androidCalendar.eventUri(localEvent.id)
// set event as dirty
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(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:
@@ -167,15 +72,16 @@ class LocalCalendarTest {
* - [Events.DIRTY]
*/
private fun testRemoveNotDirtyMarked(contentValues: ContentValues) {
val id = androidCalendar.addEvent(Entity(
val entity = 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
EventsContract.COLUMN_FLAGS to 123
).apply { putAll(contentValues) }
))
)
val id = androidCalendar.addEvent(entity)
calendar.removeNotDirtyMarked(123)
@@ -210,13 +116,13 @@ class LocalCalendarTest {
Events.DTSTART to System.currentTimeMillis(),
Events.DTEND to System.currentTimeMillis(),
Events.TITLE to "Some Event",
AndroidEvent2.COLUMN_FLAGS to 123
EventsContract.COLUMN_FLAGS to 123
).apply { putAll(contentValues) }
))
val updated = calendar.markNotDirty(321)
assertEquals(1, updated)
assertEquals(321, androidCalendar.getEvent(id)?.flags)
assertEquals(321, androidCalendar.getEvent(id)?.entityValues?.getAsInteger(EventsContract.COLUMN_FLAGS))
}
@Test

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