Compare commits

...

469 Commits

Author SHA1 Message Date
Arnau Mora Gras
a25c8d2aa4 Merge branch 'main-ose' into move-gplay-variant-to-ose
# Conflicts:
#	gradle/libs.versions.toml
2025-12-03 16:03:47 +01:00
Arnau Mora
7c98085770 Import the gplay tests 2025-10-25 14:28:53 +02:00
Arnau Mora
81f96025b2 Merge remote-tracking branch 'origin/move-gplay-variant-to-ose' into move-gplay-variant-to-ose 2025-10-25 14:16:56 +02:00
Arnau Mora
1fd30f46dc Import the davdroid folder 2025-10-25 14:16:06 +02:00
Arnau Mora
51735d336e Added standard module 2025-10-25 14:10:12 +02:00
Arnau Mora
7cb3c9ecb9 Merge remote-tracking branch 'origin/move-gplay-variant-to-ose' into move-gplay-variant-to-ose 2025-10-25 14:09:58 +02:00
Arnau Mora
4bb4906662 Import standard module 2025-10-25 14:08:57 +02:00
Arnau Mora
135b35d32b Added missing gplay dependencies 2025-10-25 14:01:45 +02:00
Arnau Mora
b43b65c0de Add gplay flavor to build.gradle.kts 2025-10-25 13:54:37 +02:00
Arnau Mora
5cab10bb1f Move gplay variant to ose 2025-10-25 13:42:27 +02:00
Arnau Mora
913999b565 Show confetti after purchasing (#697)
* Add confetti after purchasing

* Updated sizes and shapes

* Fix confetti depth
2025-10-14 11:38:23 +02:00
Sunik Kupfer
7b42fbc419 Update billing API to 8.0 (#687)
* Update the Play Billing Library dependency version

* Update the implementation of the onProductDetailsResponse method.

* Maintain same functionality of enablePendingPurchases

* Enable automatic service reconnection

* Query product details as well when model initializes

* Use activity directly as context

* Warn instead of throwing when unable to acknowledge all purchases

* Show error messages when billing client unavailable

* Fix typo

* Rename method

* Simplify billingResult response code handling

* Add error message on purchase failure

* Move methods around; improve log statements and kdoc

* Return early in onProductDetailsResponse and add comments

* Refactor acknowledge process to use coroutine and notify user on failure.  Return early in processPurchases and update log statements.

* Change deprecated URI parse to toUri

* Update kdoc

* Show success message on acknowledged purchase

* Query product details with kotlin extensions

* Minor changes

- rename method
- rearrange
- update comment and kdoc

* Update kdoc

* Use activity directly as context replacement

* Add only distinct new purchases

* Remove descriptive variable

* Use injected io dispatcher
2025-09-18 14:59:57 +02:00
Ricki Hirner
ce3b04c4fc Fetch translations from Transifex 2025-09-03 15:42:54 +02:00
Ricki Hirner
92a865d990 Move external URIs from Constants to ExternalUris object (bitfireAT/davx5-ose#1574)
* [WIP] Move external URIs from Constants to ExternalUris object

* Update external URIs to use class names or screen name for stat params
2025-07-10 16:26:32 +02:00
Ricki Hirner
af631ba3d4 Move external URIs from Constants to ExternalUris object (bitfireAT/davx5-ose#1574)
* [WIP] Move external URIs from Constants to ExternalUris object

* Update external URIs to use class names or screen name for stat params
2025-07-10 16:26:32 +02:00
Ricki Hirner
e9e0ee051a Support Fastmail OAuth (bitfireAT/davx5-ose#1509)
* Add Fastmail OAuth login implementation

* [CI] Run tests on API level 36, too

* Add Fastmail OAuth login support

* Remove logging and move companion object to bottom

* Remove FastmailLogin and GoogleLogin to OAuthLogin and OAuthGoogle

- Remove FastmailLogin class
- Refactor GoogleLogin class to OAuthGoogle object
- Update AndroidManifest.xml to use ${applicationId} for OAuth redirect URI
- Add OAuthFastmail object for Fastmail OAuth integration
- Update GoogleLoginModel and FastmailLoginModel to use OAuthGoogle and OAuthFastmail respectively
- Add OAuthIntegration object for shared OAuth functionality

* Update Fastmail authentication error message and add redirect URI documentation

* Add error handling for refresh token exception
2025-06-05 12:07:34 +02:00
Ricki Hirner
9666851d3d Support Fastmail OAuth (bitfireAT/davx5-ose#1509)
* Add Fastmail OAuth login implementation

* [CI] Run tests on API level 36, too

* Add Fastmail OAuth login support

* Remove logging and move companion object to bottom

* Remove FastmailLogin and GoogleLogin to OAuthLogin and OAuthGoogle

- Remove FastmailLogin class
- Refactor GoogleLogin class to OAuthGoogle object
- Update AndroidManifest.xml to use ${applicationId} for OAuth redirect URI
- Add OAuthFastmail object for Fastmail OAuth integration
- Update GoogleLoginModel and FastmailLoginModel to use OAuthGoogle and OAuthFastmail respectively
- Add OAuthIntegration object for shared OAuth functionality

* Update Fastmail authentication error message and add redirect URI documentation

* Add error handling for refresh token exception
2025-06-05 12:07:34 +02:00
Ricki Hirner
404324f42a Fetch translations from Transifex 2025-05-30 16:27:06 +02:00
Ricki Hirner
37362b3a10 Move Hilt modules to .di package (#665)
Move Hilt module definitions to di package
2025-04-26 23:15:11 +02:00
Ricki Hirner
ce4e581456 Move Hilt modules to .di package (#665)
Move Hilt module definitions to di package
2025-04-26 23:15:11 +02:00
Ricki Hirner
b53468b911 Move Hilt modules to .di package (#665)
Move Hilt module definitions to di package
2025-04-26 23:15:11 +02:00
Ricki Hirner
db6846e97e Fetch translations from Transifex 2025-04-17 12:56:52 +02:00
Sunik Kupfer
a2e66c672f Skip login type selection when logging in via intent (bitfireAT/davx5-ose#1267)
* Move companion object to the end of class

* Skip login type selection when logging in via intent

* Skip login type page if not default login type

* Add test for implicit email intent

* Fix test

* Update KDoc

* Refactor URI handling in LoginActivity and StandardLoginTypesProvider

* Skip login type page if intent is clear, but don't skip when using defaultLoginType

* Log unclear intents

* Use data class instead of pair

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-02-12 18:02:38 +01:00
Ricki Hirner
d63eb1ade8 Fetch translations from Transifex 2025-02-02 14:58:52 +01:00
Ricki Hirner
008ddf37f2 Further unmockk after tests (+ build variants) 2025-01-27 16:54:17 +01:00
Ricki Hirner
a908a0e407 Fetch translations from Transifex 2025-01-25 12:55:52 +01:00
Ricki Hirner
947e92a6a5 Update AUTHORS and copyright notices (bitfireAT/davx5-ose#1232)
* Update AUTHORS

* Add Android Studio copyright profiles

* Update copyright notices
2025-01-16 15:15:37 +01:00
Ricki Hirner
cd2fef7cbc Update AUTHORS and copyright notices (bitfireAT/davx5-ose#1232)
* Update AUTHORS

* Add Android Studio copyright profiles

* Update copyright notices
2025-01-16 15:15:37 +01:00
Ricki Hirner
38571962ee Update AUTHORS and copyright notices (bitfireAT/davx5-ose#1232)
* Update AUTHORS

* Add Android Studio copyright profiles

* Update copyright notices
2025-01-16 15:15:37 +01:00
Ricki Hirner
45f938e22b Fetch translations from Transifex 2024-12-23 14:16:59 +01:00
Arnau Mora
a2e5141f78 Link to Managed DAVx5 in navigation drawer (#633)
Added managed drawer entry
2024-12-16 14:13:30 +01:00
Ricki Hirner
5b097ed910 Move icons to gplay 2024-11-29 16:20:47 +01:00
Ricki Hirner
b5e8bdcf92 Move icons to gplay 2024-11-29 16:20:47 +01:00
Arnau Mora
eeb582e7a9 Migrated XML drawables to ImageVector (#628)
* Migrated badges from XML to ImageVector

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

* Got rid of unused resources

* Migrated Mastodon icon to ImageVector

* Moved file to managed

* Rollback

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
2024-11-29 11:40:31 +01:00
Arnau Mora
5601451e0e Migrated XML drawables to ImageVector (#628)
* Migrated badges from XML to ImageVector

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

* Got rid of unused resources

* Migrated Mastodon icon to ImageVector

* Moved file to managed

* Rollback

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
2024-11-29 11:40:31 +01:00
Ricki Hirner
b4625bd95a Fetch translations from Transifex 2024-11-22 17:18:24 +01:00
Sunik Kupfer
96ea1df52e Refactor and adapt earn badges activity for dark mode (#618)
* Handle deprecation

* Handle deprecation

* Use flows instead of live data

* Remove hardcoded colors

* Fix bought badges display

* Use lazy vertical grid
2024-10-30 12:04:35 +01:00
Sunik Kupfer
96c5502d19 Add badges of 2024 (#616)
* Add badges

* Add remaining colors of two badges

* Fix color assignment
2024-10-28 13:30:45 +01:00
Ricki Hirner
d57829b2eb Fetch translations from Transifex 2024-10-17 16:14:58 +02:00
Arnau Mora
92b5245409 Fixed surface container color in dark theme (bitfireAT/davx5-ose#1069)
* Fixed surface color in dark theme

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

* Account screen: higher contrast for collection cards

* Account screen: use normal instead of elevated cards

* Adapt colors of card lists

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-10-14 17:49:06 +02:00
Ricki Hirner
bfac9c627f Fetch translations from Transifex 2024-10-10 18:26:48 +02:00
Ricki Hirner
93d77d61cb Fix AppLinkUrl lint error in gplay, too (#610)
* Fix lint in gplay, too

- Add duplication notice (unfortunately we can't put it to davdroid/AndroidManifest.xml because multiple manifests per build variant are not supported by AGP)

* Add tools NS
2024-10-07 13:51:17 +02:00
Ricki Hirner
52e8edc769 Fix AppLinkUrl lint error in gplay, too (#610)
* Fix lint in gplay, too

- Add duplication notice (unfortunately we can't put it to davdroid/AndroidManifest.xml because multiple manifests per build variant are not supported by AGP)

* Add tools NS
2024-10-07 13:51:17 +02:00
Sunik Kupfer
375e1e9264 Ignore lint AppLinkUrlError (bitfireAT/davx5-ose#1053) 2024-10-03 18:04:05 +02:00
Arnau Mora
bfcea46458 ClickableText for URLs has been deprecated (bitfireAT/davx5-ose#1024)
* Got rid of `UrlAnnotation`

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

* Deprecated and suggested a ReplaceWith

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

* Optimized imports

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

* Replaced usages of `ClickableTextWithLink`

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

* Removed `ClickableTextWithLink`

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

* Migrated `ClickableTextWithLink`

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

* Remove experimental text api annotations

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Sunik Kupfer <kupfer@bitfire.at>
2024-09-16 15:06:57 +02:00
Ricki Hirner
d06511e7dc Reword login text (bitfireAT/davx5-ose#1026)
- Change empty accounts text
- Add privacy note to Login screen
- Changed last button text
2024-09-16 14:58:25 +02:00
Ricki Hirner
bbcaeed791 Fetch translations from Transifex 2024-08-05 17:43:09 +02:00
Ricki Hirner
07f79e2428 Remove obsolete Logger object (bitfireAT/davx5-ose#930) 2024-07-22 00:01:11 +02:00
Ricki Hirner
f7ca788045 Refactor SettingsManager/SettingsProvider to use DI properly (#599)
* Refactor SettingsManager/SettingsProvider to use DI

* Fix tests

* Remove BaseDefaultsProvider

* Fix gplay tests
2024-07-20 21:23:48 +02:00
Ricki Hirner
dac18ac0e6 Install uncaught exception handler in a separate startup plugin (#597) 2024-07-18 09:03:13 +02:00
Ricki Hirner
032711cfb3 Fetch translations from Transifex 2024-07-02 17:08:48 +02:00
Sunik Kupfer
eaff085abb Mock settings directly in EarnBadgesActivityTest (#582)
Mock settings for EarnBadgesActivityTest
2024-05-29 16:59:39 +02:00
Ricki Hirner
b5f8f50fc5 Update dark themes 2024-05-28 23:37:40 +02:00
Ricki Hirner
f6c42db2e8 Last M3 Tweaks (standard/gplay) (bitfireAT/davx5-ose#817)
* [WIP] Colors

* Update navigation drawer

* Update colors

* [WIP] PermissionSwitchRow night mode

* Fix PermissionSwitchRow icon in night mode

* Use more intense colors for FABs
2024-05-28 21:08:37 +02:00
Ricki Hirner
4e9888cb7f Update theme and style again to make it look like it used to look 2024-05-17 18:06:14 +02:00
Ricki Hirner
c33aa8a505 Fix translations from Transifex (again) 2024-05-17 15:22:09 +02:00
Ricki Hirner
1322654a42 Fetch translations from Transifex 2024-05-17 14:38:17 +02:00
Sunik Kupfer
fea09011f3 Fix setup through nextcloud app (intent) not working (bitfireAT/davx5-ose#782)
* Select nextcloud login type on nextcloud setup intent

* Fix linting error

* Add documentation

* Move model creation to compose LoginScreen

* Minor changes
- Use boolean to decide on skipping startPage
- Move login type selection logic to login types provider

* Minor changes

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-05-17 14:29:47 +02:00
Ricki Hirner
97791811bb [gplay] Fix tests 2024-05-17 13:38:22 +02:00
Ricki Hirner
c1cc52e1b8 Try different M3 theme 2024-05-17 13:11:14 +02:00
Ricki Hirner
8de85b74b6 Remove unnecessary resources 2024-05-16 18:27:40 +02:00
Ricki Hirner
ac09074811 Drop M2 (everything is M3 now) (bitfireAT/davx5-ose#797)
Drop M2
2024-05-16 17:56:54 +02:00
Ricki Hirner
3f16b606b4 Rewrite EarnBadgesActivity to M3 (without refactoring) 2024-05-16 16:56:01 +02:00
Ricki Hirner
827dbb641d Intro pages: use Hilt for dependency injection (bitfireAT/davx5-ose#779)
* Accounts: move syncAll to model; better Hilt usage

* Move calculation of whether IntroActivity is shown into AccountsModel

* Intro pages: inject dependencies with Hilt

* Fix preview
2024-05-04 20:18:16 +02:00
Ricki Hirner
aa6fea5914 Rewrite AccountsActivity to M3 (bitfireAT/davx5-ose#749 + flavor adaptions)
* [WIP] Rewrite AccountsActivity to M3

* [WIP] AccountsScreen: FAB, previews

* [WIP] Warning cards

* Adapt FABs
2024-04-26 13:55:34 +02:00
Ricki Hirner
ba5ddd6335 Rewrite AccountsActivity to M3 (bitfireAT/davx5-ose#749 + flavor adaptions)
* [WIP] Rewrite AccountsActivity to M3

* [WIP] AccountsScreen: FAB, previews

* [WIP] Warning cards

* Adapt FABs
2024-04-26 13:55:34 +02:00
Ricki Hirner
678eb6d8bb Rewrite Managed configuration screen to M3 (#576) 2024-04-24 10:30:56 +02:00
Ricki Hirner
f6c8c7242b Rewrite login to M3 and with better UI states (#567)
* [WIP] Rewrite login to M3 and better UI states

* Use AccountRepository to create account

* LoginModel is SSOT for page navigation

* Support forced group method

* Show progress bar when account is being created

* Make account name suggestions work again

* Use M3 text field supportText for errors

* Refactor: login by URL, login by email, advanced login

* Refactor Nextcloud login, move login flow logic to separate class

* Refactor Google login, move OAuth logic to separate class

* Fix errors when navigating back after successful resource detection

* Make PasswordTextField M3

* ManagedLogin: M3, UiState

* Updated theme; managed login functionality

* Improve back navigation
2024-04-21 16:20:08 +02:00
Ricki Hirner
4086103456 Add M3 theme and apply to AboutActivity (bitfireAT/davx5-ose#731)
* Rename AppTheme to M2Theme, add M3 theme

* Rewrite AboutActivity to M3

* Apply M3 theme; minor optimizations

* Use M3 version of AboutLibraries

* Use material3 instead of material3-android dependency

* Use reversed theme
2024-04-20 00:37:32 +02:00
Ricki Hirner
3b53c64f1a Add M3 theme and apply to AboutActivity (bitfireAT/davx5-ose#731)
* Rename AppTheme to M2Theme, add M3 theme

* Rewrite AboutActivity to M3

* Apply M3 theme; minor optimizations

* Use M3 version of AboutLibraries

* Use material3 instead of material3-android dependency

* Use reversed theme
2024-04-20 00:37:32 +02:00
Ricki Hirner
0733715ac3 Fetch translations from Transifex 2024-04-13 21:25:05 +02:00
Ricki Hirner
c3ba6d0826 Update copyright; upgrade dependencies (bitfireAT/davx5-ose#687) 2024-03-27 17:44:32 +01:00
Ricki Hirner
43f4ef5179 Login screen: increase padding of login options minimally 2024-03-27 17:43:51 +01:00
Arnau Mora
d9fe3b8632 Create a native Material theme and get rid of XML styles (bitfireAT/davx5-ose#675)
* Added custom Compose theme

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

* Got rid of accompanist theme adapter

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

* Removed unused import

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

* Added actionbar to the activities that didn't have one

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

* Theme now always hides actionbar

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

* Added back string

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

* Got rid of all color definitions

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

* Moved color definition to drawable

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

* Using Compose colors

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

* Using AppCompat theme

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

* Removed XML theme

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

* Moved color definition

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

* Added bars coloring in Compose

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

* Added dark theme

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

* Added custom Compose theme

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

* Got rid of accompanist theme adapter

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

* Removed unused import

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

* Added actionbar to the activities that didn't have one

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

* Theme now always hides actionbar

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

* Added back string

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

* Got rid of all color definitions

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

* Moved color definition to drawable

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

* Using Compose colors

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

* Using AppCompat theme

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

* Removed XML theme

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

* Moved color definition

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

* Added bars coloring in Compose

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

* Added dark theme

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

* Changed content description

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

* Using `onSupportNavigateUp`

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

* Changed color on top of primary green

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

* Added up navigation

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

* Made `onSupportNavigateUp` optional

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

* Typo

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

* Got rid of edge-to-edge

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

* Added back some XML styles

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

* Simplify TasksCard calling

* Move theme colors to flavor-specific ThemeColors file

* Remove global AppTheme paddings for now

* Optimize imports

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-03-27 17:43:39 +01:00
Arnau Mora
c3078e85cc Create a native Material theme and get rid of XML styles (bitfireAT/davx5-ose#675)
* Added custom Compose theme

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

* Got rid of accompanist theme adapter

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

* Removed unused import

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

* Added actionbar to the activities that didn't have one

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

* Theme now always hides actionbar

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

* Added back string

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

* Got rid of all color definitions

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

* Moved color definition to drawable

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

* Using Compose colors

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

* Using AppCompat theme

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

* Removed XML theme

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

* Moved color definition

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

* Added bars coloring in Compose

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

* Added dark theme

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

* Added custom Compose theme

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

* Got rid of accompanist theme adapter

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

* Removed unused import

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

* Added actionbar to the activities that didn't have one

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

* Theme now always hides actionbar

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

* Added back string

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

* Got rid of all color definitions

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

* Moved color definition to drawable

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

* Using Compose colors

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

* Using AppCompat theme

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

* Removed XML theme

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

* Moved color definition

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

* Added bars coloring in Compose

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

* Added dark theme

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

* Changed content description

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

* Using `onSupportNavigateUp`

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

* Changed color on top of primary green

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

* Added up navigation

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

* Made `onSupportNavigateUp` optional

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

* Typo

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

* Got rid of edge-to-edge

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

* Added back some XML styles

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

* Simplify TasksCard calling

* Move theme colors to flavor-specific ThemeColors file

* Remove global AppTheme paddings for now

* Optimize imports

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-03-27 17:43:39 +01:00
Ricki Hirner
d2ea4c1a84 Rewrite login activity to Compose (bitfireAT/davx5-ose#672)
* Remove unnecessary layout files

* [WIP] Rewrite LoginActivity to Compose

* [WIP] Login type

* [WIP] Login by URL, Google, Nextcloud

* Remove unnecessary files and kapt

* More renaming and removing of unnecessary files

* Login with email, URL

* Login type: Advanced

* Drop "known base URLs"

* "Detect resources" and "Create account" page

* Introduce LoginTypesProvider interface

* Managed adaptions
2024-03-24 21:10:37 +01:00
Arnau Mora
95ef141c55 Replaced icons with auto-mirrored version when possible (bitfireAT/davx5-ose#666)
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
2024-03-21 11:42:53 +01:00
Sunik Kupfer
5980d7e92a [gplay] Rewrite EarnBadgesActivity to Compose (#547)
* Remove adapters

* Remove xml

* Add top bar

* Add text

* Add available badges list

* Add bought badges list

* Add bought badges list

* Drop animations

* Show snackbar on errors

* Optimize imports

* Update uri handler to compose

* Suppress unused warning

* Update indenting
2024-03-21 11:42:13 +01:00
Ricki Hirner
e0d5f8de17 [gplay/managed] Fix strings and imports (#549) 2024-03-20 17:06:19 +01:00
Ricki Hirner
f831a4fbb6 Add stats parameters for Web site calls, rename AccountActivity2 back to AccountActivity 2024-03-12 13:03:54 +01:00
Ricki Hirner
b2637de513 Move "Community" to "Support the project", remove obsolete code 2024-03-12 12:58:09 +01:00
Ricki Hirner
b3359f69c4 Move "Community" to "Support the project", remove obsolete code 2024-03-12 12:58:09 +01:00
Ricki Hirner
10a4578c8b Rewrite navigation drawer to Compose (all build flavors) (#545)
* [WIP] Rewrite navigation drawer to Compose (only standard build variant)

* Rewrite other navigation drawer of all build flavors to Compose

* Shorten StandardAccountsDrawerHandler

* Add previews
2024-03-12 12:43:19 +01:00
Ricki Hirner
03b70e6ec7 Rewrite navigation drawer to Compose (all build flavors) (#545)
* [WIP] Rewrite navigation drawer to Compose (only standard build variant)

* Rewrite other navigation drawer of all build flavors to Compose

* Shorten StandardAccountsDrawerHandler

* Add previews
2024-03-12 12:43:19 +01:00
Ricki Hirner
78ba2f31e8 Improve homepage URL launching (bitfireAT/davx5-ose#632)
* Move homepage and other Web URLs to Constants; minor refactoring

* Use AppTheme with built-in safe LocalUriHandler instead of MdcTheme; minor refactoring

* Account settings: add TODO for Compose rewrite

* Use UriHandler instead of UiUtils.launch when possible
2024-03-10 21:05:42 +01:00
Ricki Hirner
e67f617f52 Improve homepage URL launching (bitfireAT/davx5-ose#632)
* Move homepage and other Web URLs to Constants; minor refactoring

* Use AppTheme with built-in safe LocalUriHandler instead of MdcTheme; minor refactoring

* Account settings: add TODO for Compose rewrite

* Use UriHandler instead of UiUtils.launch when possible
2024-03-10 21:05:42 +01:00
Ricki Hirner
3c77facab5 Rewrite intro pages to Compose (dropping all Fragments) (bitfireAT/davx5-ose#626) 2024-03-09 14:47:25 +01:00
Ricki Hirner
fae5afbf89 Fetch translations from Transifex 2024-02-22 12:16:28 +01:00
Ricki Hirner
1ee23af370 [CI] Run lint and tests for standard, gplay and managed flavor (#529)
* [CI] Run lint and tests for standard, gplay and managed flavor

* Fix requireViewById minimum SDK problem

* Specify exported for .ui.setup.LoginActivity in Manifest
2024-02-13 09:49:35 +01:00
Arnau Mora
00c855a9cc Rename LoginCredentialsFragmentFactory to LoginFragmentFactory (#507)
* Renamed LoginCredentialsFragmentFactory to LoginFragmentFactory

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

* Fixed fragment name

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

* Rename LoginInitFragment (managed) to ManagedLoginInitFragment

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-01-05 10:55:47 +01:00
Sunik Kupfer
02809da6ba Nextcloud login flow for managed (#501)
* Add login_type choice restriction

* Move NextcloudLoginFlowFragment from davdroid to main

* Start NextcloudLoginFlowFragment when set as login_type

* Remove unused default login type

* Prefill NextcloudLoginFlowFragment with base URL from config

* Update imports

* Make String comparison case-insensitive

* Create fragment with extra instead of constructor argument

* Minor reordering

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-01-03 11:23:42 +01:00
Ricki Hirner
d71d4c2c1e Fetch translations from Transifex 2023-12-23 12:38:55 +01:00
Ricki Hirner
bdec7e8931 Nextcloud Login flow: provide default DAV_PATH (/remote.php/dav) (#497) 2023-12-21 17:04:54 +01:00
Ricki Hirner
028517874e Fetch translations from Transifex 2023-11-13 16:30:17 +01:00
Sunik Kupfer
91bbe803f3 Move all common locales to main and adjust transifex config (#458)
* Move all common locales to main and adjust transifex config

* Rename gplay and managed string resource files

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-11-13 16:16:18 +01:00
Sunik Kupfer
fa14117d87 Move all common locales to main and adjust transifex config (#458)
* Move all common locales to main and adjust transifex config

* Rename gplay and managed string resource files

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-11-13 16:16:18 +01:00
Ricki Hirner
2d6bc27482 Clean up hilt modules 2023-11-07 15:05:34 +01:00
Ricki Hirner
66efc905c9 Clean up hilt modules 2023-11-07 15:05:34 +01:00
Ricki Hirner
c78986e3e7 Clean up hilt modules 2023-11-07 15:05:34 +01:00
Ricki Hirner
870cdc8f6a Rewrite About activity to Compose (#432)
* Rewrite AboutActivity to Compose (still keeping the tab fragments)

* Replace Translations and Libraries fragments

* [WIP] Rewrite AboutApp

* [WIP] App license info as Composable

* [WIP] Hilt viewModel/Compose

* Fill ManagedAppLicenseInfoProvider with real data

* Simplify setContent, use LazyColumn for translations

* Minor changes
2023-11-07 14:12:49 +01:00
Ricki Hirner
3e51051edd Rewrite About activity to Compose (#432)
* Rewrite AboutActivity to Compose (still keeping the tab fragments)

* Replace Translations and Libraries fragments

* [WIP] Rewrite AboutApp

* [WIP] App license info as Composable

* [WIP] Hilt viewModel/Compose

* Fill ManagedAppLicenseInfoProvider with real data

* Simplify setContent, use LazyColumn for translations

* Minor changes
2023-11-07 14:12:49 +01:00
Ricki Hirner
d7ecc0dd8a Rewrite About activity to Compose (#432)
* Rewrite AboutActivity to Compose (still keeping the tab fragments)

* Replace Translations and Libraries fragments

* [WIP] Rewrite AboutApp

* [WIP] App license info as Composable

* [WIP] Hilt viewModel/Compose

* Fill ManagedAppLicenseInfoProvider with real data

* Simplify setContent, use LazyColumn for translations

* Minor changes
2023-11-07 14:12:49 +01:00
Ricki Hirner
edd4def8f6 Fetch translations from Transifex 2023-10-26 12:25:29 +02:00
Ricki Hirner
3204cb7c0f Fetch translations from Transifex 2023-10-26 12:25:29 +02:00
Ricki Hirner
96da9a02d9 Nextcloud: pre-select contact group method (CATEGORIES) (#410)
* LoginActivity: refactor menu to MenuProvider; LoginModel: add contact group type

* Take LoginModel group method into account when creating the account; Nextcloud login: set preferred contact group type
2023-10-18 15:08:43 +02:00
Ricki Hirner
96cc70a472 LoginActivity: add Nextcloud Login Flow (#403)
* Replace onActivityResult by contract

* Add Nextcloud option to default login screen

* Decouple NextcloudLoginFlowComposable from model

* UI and model changes

* Single-line URL field

* Add progress indicator and other secondary UI
2023-10-18 14:44:16 +02:00
Ricki Hirner
1714ec0d8c Fetch translations from Transifex 2023-10-09 12:37:46 +02:00
Ricki Hirner
ba5976840f Fetch translations from Transifex 2023-10-09 12:37:46 +02:00
Ricki Hirner
1e59b834e8 Beta feedback (gplay flavor): provide alternative to in-app reviews (#401) 2023-10-07 11:53:23 +02:00
Ricki Hirner
f77a5a6314 Rename java sources directories to kotlin 2023-10-07 10:08:32 +02:00
Ricki Hirner
8177eedb71 Rename java sources directories to kotlin 2023-10-07 10:08:32 +02:00
Ricki Hirner
291d9c680b Rename java sources directories to kotlin 2023-10-07 10:08:32 +02:00
Ricki Hirner
3b233e7abf Rename java sources directories to kotlin 2023-10-07 10:08:32 +02:00
Ricki Hirner
e3117442de Repair beta feedback (#394)
- use Google Play In-App Review API for private feedback on -gplay (fall back to email)
- start email intent again when "beta feedback" is selected in navigation drawer
2023-09-27 10:09:30 +02:00
Ricki Hirner
d564dc4d10 Fetch translations from Transifex 2023-09-22 15:52:15 +02:00
Ricki Hirner
8f6b569882 Fetch translations from Transifex 2023-09-22 15:52:15 +02:00
Arnau Mora
e651b4f84f App name strings are the same for DAVx5 and Managed DAVx5 (#376)
* Moved translations to davdroid module

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

* Removed `trans.de`

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

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2023-09-10 13:43:21 +02:00
Ricki Hirner
dfa368b412 Fetch translations from Transifex 2023-09-05 10:44:00 +02:00
Ricki Hirner
d27fa97be6 Fetch translations from Transifex 2023-08-07 20:35:30 +02:00
Arnau Mora
8b901703ca Upgrade AGP to 8.1 and configure per app language (#338)
* Upgraded AGP

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

* Enabled automatic locale config generation

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

* Added fallback language

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

* Added legacy service

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

* Added `Accept-Language` header to custom tabs

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

* Nextcloud Login Flow/Google OAuth: also send language tag for default locale

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-07-27 19:38:09 +02:00
Ricki Hirner
6032d549fb Google OAuth: fix manifest merging so that intent-filter for OAuth callback is included again (bitfireAT/davx5-ose#367) 2023-07-27 18:19:53 +02:00
Sunik Kupfer
936e3bff7a Add "10 years DAVx5" badges (#336)
Add some badges
2023-07-27 14:27:32 +02:00
Ricki Hirner
711008d20b Fetch translations from Transifex 2023-07-21 22:04:31 +02:00
Ricki Hirner
abdad6fe1d Fetch translations from Transifex 2023-07-21 22:04:31 +02:00
Ricki Hirner
11592abb9e Google Login: add Limited Use policy (#327) 2023-07-10 15:05:15 +02:00
Ricki Hirner
6a6617bfc7 Fetch translations from Transifex 2023-07-10 13:38:07 +02:00
Ricki Hirner
1ddda87efc Fetch translations from Transifex 2023-07-10 13:38:07 +02:00
Ricki Hirner
ed06fad6aa Version bump to 4.3.5-alpha.1; update Compose; move GoogleLoginFragment to fix build 2023-06-30 13:16:31 +02:00
Sunik Kupfer
e2b898f1a3 291 clean up oauth mess (#307)
* Move GoogleOAuth members into GoogleLoginFragment

* Require login flow capable browser and notify user if missing

* Receive AppAuth redirects only in standard and gplay flavor

* Set davx5 as user-agent for AppAuth connection builder

* Re-authentication in Account settings

* Catch unauthorized exceptions at collection refresh and notify user to re-authenticate

* Suggest email address on account creation

* Set contact groups default setting as per-contact categories for oauth logins

* Add authentication to debug info, minor other changes

* Better error handling; don't pre-set group type

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-06-30 12:42:37 +02:00
Sunik Kupfer
4e5a937240 291 clean up oauth mess (#307)
* Move GoogleOAuth members into GoogleLoginFragment

* Require login flow capable browser and notify user if missing

* Receive AppAuth redirects only in standard and gplay flavor

* Set davx5 as user-agent for AppAuth connection builder

* Re-authentication in Account settings

* Catch unauthorized exceptions at collection refresh and notify user to re-authenticate

* Suggest email address on account creation

* Set contact groups default setting as per-contact categories for oauth logins

* Add authentication to debug info, minor other changes

* Better error handling; don't pre-set group type

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-06-30 12:42:37 +02:00
Sunik Kupfer
4c9e75d17e 291 clean up oauth mess (#307)
* Move GoogleOAuth members into GoogleLoginFragment

* Require login flow capable browser and notify user if missing

* Receive AppAuth redirects only in standard and gplay flavor

* Set davx5 as user-agent for AppAuth connection builder

* Re-authentication in Account settings

* Catch unauthorized exceptions at collection refresh and notify user to re-authenticate

* Suggest email address on account creation

* Set contact groups default setting as per-contact categories for oauth logins

* Add authentication to debug info, minor other changes

* Better error handling; don't pre-set group type

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-06-30 12:42:37 +02:00
Arnau Mora
c3dec982bd Removed Twitter from navigation drawer (#312)
Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2023-06-25 10:58:19 +02:00
Arnau Mora
a1ca245792 Removed Twitter from navigation drawer (#312)
Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2023-06-25 10:58:19 +02:00
Ricki Hirner
bee6342550 Fetch translations from Transifex 2023-06-13 18:42:39 +02:00
Ricki Hirner
19e2181fff Google OAuth: fix endless loop in Fragment (#295)
Fix endless loop
2023-06-13 18:32:46 +02:00
Sunik Kupfer
e4a4726a3a [Google OAuth] Support custom Client IDs (#294)
* Support custom Client IDs

* Refactoring

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-06-13 18:01:35 +02:00
Ricki Hirner
75f4e5b67b Fetch translations from Transifex 2023-06-11 13:21:13 +02:00
Ricki Hirner
1dcbe584c8 Support OAuth for Google (#289)
* Proof of concept (without auth state storage)
* Implement Bearer authentication, create network package
* Properly create/dispose AuthService
* Use proper ActivityResultContract
* Integrate into default login activity
* Change client ID to davx5integration@gmail.com
* Google Login: adapt login view
* Fix tests
* Don't allow empty Google account
* Move strings to resources
2023-06-11 13:18:06 +02:00
Sunik Kupfer
304a572a2a 280 fix problematic toasts (#283)
* When renaming show a toast when account with new name exists already

* Replace toasts with snackbars

* Apply code styling hints

* Move lambdas out of parentheses

* Inject application instead of context

* Replace Toast with Snackbar

* Shorten messages shown in snackbars

* Show Toast in UI instead of model; use AndroidViewModel

* Move account name check out of lock; don't close activity if not successful

* Duplicate account name check: only check for our account type

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-06-09 13:41:15 +02:00
Ricki Hirner
b476902cfd Fetch translations from Transifex 2023-06-01 12:29:42 +02:00
Ricki Hirner
8366965c2b Fetch translations from Transifex 2023-06-01 12:29:42 +02:00
Ricki Hirner
93799ef875 Fetch translations from Transifex 2023-05-22 08:25:52 +02:00
Ricki Hirner
1016915ca0 Fetch translations from Transifex 2023-05-12 10:44:05 +02:00
Ricki Hirner
dc075e8128 Fix translations 2023-05-02 13:09:31 +02:00
Ricki Hirner
8a20b3f071 Fetch translations from Transifex 2023-05-02 11:40:41 +02:00
Ricki Hirner
555c72618f Fetch translations from Transifex 2023-05-02 11:40:41 +02:00
Ricki Hirner
c7db2a3d0a Upgrade gradle, AGP, dependencies 2023-04-29 22:41:08 +02:00
Arnau Mora
9c466b8f64 Added missing monochrome icon (#233)
Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>
2023-03-22 11:23:20 +01:00
Ricki Hirner
0c2d9a7a6f Fetch translations from Transifex 2023-03-20 12:37:05 +01:00
Ricki Hirner
3fd13021bf Fetch translations from Transifex 2023-03-20 12:37:05 +01:00
Ricki Hirner
4b4a73e523 Fetch translations from Transifex, tx config changes 2023-02-08 11:42:16 +01:00
Ricki Hirner
b85801efe2 Fetch translations from Transifex 2023-02-01 13:32:02 +01:00
Ricki Hirner
a50fef6e1f Fetch translations from Transifex 2023-02-01 13:32:02 +01:00
Arnau Mora
6cc4ed72de Added Mastodon link to navigation drawer (#179)
* Added mastodon icon

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Added mastodon link to drawer

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Added mastodon link action

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Mastodon icon tint

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Added Mastodon icon to main

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>
2023-01-01 23:25:30 +01:00
Arnau Mora
738c5105a0 Added Mastodon link to navigation drawer (#179)
* Added mastodon icon

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Added mastodon link to drawer

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Added mastodon link action

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Mastodon icon tint

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Added Mastodon icon to main

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>
2023-01-01 23:25:30 +01:00
Ricki Hirner
5eb1662686 Fetch translations from Transifex 2022-12-10 13:19:31 +01:00
Ricki Hirner
e97b3ddbab Fetch translations from Transifex 2022-12-10 13:19:31 +01:00
Sunik Kupfer
929bf0c16d Refactor DavService to WorkManager (#164)
* SyncManager: remove retry intent
* refactor DavService to RefreshCollectionsWorker now using WorkManager
* move DavResourceFinder to new service detection package
* Optimize imports

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2022-12-03 14:00:38 +01:00
Sunik Kupfer
90a0892e33 Fetch translations from Transifex 2022-11-29 14:39:13 +01:00
Sunik Kupfer
019574d84a Fetch translations from Transifex 2022-11-29 14:39:13 +01:00
Sunik Kupfer
b9b570bf51 Fetch translations from Transifex 2022-10-25 11:02:34 +02:00
Sunik Kupfer
9b14f79570 add new anniversary badge (closes bitfireAT/davx5#147) (#152)
* add new anniversary badge
* change back to cake icon in lachsrosa
2022-10-24 20:34:36 +02:00
Ricki Hirner
3b2d4078c3 Fetch translations from Transifex 2022-10-21 13:09:52 +02:00
Ricki Hirner
dc274ef689 Fetch translations from Transifex 2022-10-21 13:09:52 +02:00
Ricki Hirner
d84e71750f Fetch translations from Transifex (including new translations) 2022-10-14 22:59:53 +02:00
Ricki Hirner
07fd7d9377 Update Manual URL 2022-10-08 18:57:44 +02:00
Ricki Hirner
ea3dfa9cbe Fetch translations from Transifex 2022-09-19 16:33:25 +02:00
Ricki Hirner
bed6feede1 Fetch translations from Transifex 2022-09-08 21:59:42 +02:00
Sunik Kupfer
448b6ae4c5 Migrate to gpblv5 (closes bitfireAT/davx5#116) (#123)
* migrate to gplay billing library v5
* alter handling of purchase state
* complete migration to gpblv5
* fix demo notification showing by using kotlin version of billing lib to block thread
* fix
2022-09-07 14:40:03 +02:00
Ricki Hirner
7dcfb10d15 Fetch translations from Transifex 2022-08-28 21:58:48 +02:00
Sunik Kupfer
53077f30d9 move badge colors to gplay res (#114) 2022-08-10 17:10:28 +02:00
Sunik Kupfer
0a42a34fc7 Fetch translations from Transifex 2022-06-21 11:44:12 +02:00
Ricki Hirner
61e86b32d3 Fix standard/gplay flavor intro fragments 2022-05-28 10:47:21 +02:00
Ricki Hirner
02811aa71a Fix standard/gplay flavor intro fragments 2022-05-28 10:47:21 +02:00
Ricki Hirner
0c3395102c Use Hilt instead of Koin (#93)
* [WIP] Use Hilt for DI
* Use Hilt instead of ServiceLoader for intro fragments
* Rewrite from Koin to Hilt
* Tests
* Flavors
* Use more Hilt modules for service loading (account drawer, intro fragments)
* Lint
* WebDAV provider: don't listen to changes in onCreate (causes problems with Hilt and tests)
* Clean before building
* Modify test CI
* Update hilt to fix test error
* OpenSourceFragment may stay in main repo (it's not referenced in the Hilt flavor module definition)
* Test only standard flavor again (seems that testing multiple flavors at the same time causes tests to fail)
* Hilt for tests
* Use Hilt for settings providers
* Use Hilt for login credentials fragment
* Use Hilt for sync validator (was: sync plugin)
* Fix tests
2022-05-27 14:41:57 +02:00
Ricki Hirner
84f6103404 Use Hilt instead of Koin (#93)
* [WIP] Use Hilt for DI
* Use Hilt instead of ServiceLoader for intro fragments
* Rewrite from Koin to Hilt
* Tests
* Flavors
* Use more Hilt modules for service loading (account drawer, intro fragments)
* Lint
* WebDAV provider: don't listen to changes in onCreate (causes problems with Hilt and tests)
* Clean before building
* Modify test CI
* Update hilt to fix test error
* OpenSourceFragment may stay in main repo (it's not referenced in the Hilt flavor module definition)
* Test only standard flavor again (seems that testing multiple flavors at the same time causes tests to fail)
* Hilt for tests
* Use Hilt for settings providers
* Use Hilt for login credentials fragment
* Use Hilt for sync validator (was: sync plugin)
* Fix tests
2022-05-27 14:41:57 +02:00
Ricki Hirner
aac543b759 Use Hilt instead of Koin (#93)
* [WIP] Use Hilt for DI
* Use Hilt instead of ServiceLoader for intro fragments
* Rewrite from Koin to Hilt
* Tests
* Flavors
* Use more Hilt modules for service loading (account drawer, intro fragments)
* Lint
* WebDAV provider: don't listen to changes in onCreate (causes problems with Hilt and tests)
* Clean before building
* Modify test CI
* Update hilt to fix test error
* OpenSourceFragment may stay in main repo (it's not referenced in the Hilt flavor module definition)
* Test only standard flavor again (seems that testing multiple flavors at the same time causes tests to fail)
* Hilt for tests
* Use Hilt for settings providers
* Use Hilt for login credentials fragment
* Use Hilt for sync validator (was: sync plugin)
* Fix tests
2022-05-27 14:41:57 +02:00
Ricki Hirner
13179912b1 Use Hilt instead of Koin (#93)
* [WIP] Use Hilt for DI
* Use Hilt instead of ServiceLoader for intro fragments
* Rewrite from Koin to Hilt
* Tests
* Flavors
* Use more Hilt modules for service loading (account drawer, intro fragments)
* Lint
* WebDAV provider: don't listen to changes in onCreate (causes problems with Hilt and tests)
* Clean before building
* Modify test CI
* Update hilt to fix test error
* OpenSourceFragment may stay in main repo (it's not referenced in the Hilt flavor module definition)
* Test only standard flavor again (seems that testing multiple flavors at the same time causes tests to fail)
* Hilt for tests
* Use Hilt for settings providers
* Use Hilt for login credentials fragment
* Use Hilt for sync validator (was: sync plugin)
* Fix tests
2022-05-27 14:41:57 +02:00
Ricki Hirner
bc8d4cd25d Fetch translations from Transifex 2022-05-22 23:49:26 +02:00
Sunik Kupfer
168e9b56aa In app review api (closes bitfireAT/davx5#82) (#85)
* Notify user if billing unavailable or network issues arise

* offer to rate the app in earn badges activity, instead of navigation drawer and linting

* linting

* use in-app review API to trigger the review request after two weeks every two weeks

* remove obsolete testing bools

* [WIP] add mockk tests

* Adapt tests

* finish EarnBadgesActivity tests

* refactor for gplay flavor testing

* use junit assert statements, add comments and ensure gplay android tests are run on test server

* Use org.junit for Assert

* Tests

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2022-05-22 23:45:25 +02:00
Sunik Kupfer
4919bf0d8d In app review api (closes bitfireAT/davx5#82) (#85)
* Notify user if billing unavailable or network issues arise

* offer to rate the app in earn badges activity, instead of navigation drawer and linting

* linting

* use in-app review API to trigger the review request after two weeks every two weeks

* remove obsolete testing bools

* [WIP] add mockk tests

* Adapt tests

* finish EarnBadgesActivity tests

* refactor for gplay flavor testing

* use junit assert statements, add comments and ensure gplay android tests are run on test server

* Use org.junit for Assert

* Tests

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2022-05-22 23:45:25 +02:00
Ricki Hirner
dddbce93f7 Use Koin for all tests; CI: run tests for all flavors (#91)
Closes bitfireAT/davx5#89
2022-05-22 18:54:01 +02:00
Sunik Kupfer
235e24839f Dark theme for EearnBadgesActivity (closes bitfireAT/davx5#88) (#90)
* [WIP] dark mode support for earnbadgesactivity

* dark mode for earn badges activity
2022-05-19 13:22:33 +02:00
Sunik Kupfer
677ad8c37a correctly acknowledge purchases (closes bitfireAT/davx5#86) (#87)
* linting

* fix acknowledging purchases correctly
2022-05-16 13:35:32 +02:00
Ricki Hirner
6887e44910 Fetch translations from Transifex 2022-04-29 15:49:25 +02:00
Ricki Hirner
493644c24b Fix manifests (problem when merging) 2022-04-29 15:47:13 +02:00
Ricki Hirner
284094c105 Move EarnBadgesActivity strings to gplay 2022-04-25 15:07:06 +02:00
Ricki Hirner
e48ad8b796 Fetch translations from Transifex 2022-04-25 15:07:06 +02:00
Ricki Hirner
593ee0ae24 Move EarnBadgesActivity strings to gplay 2022-04-25 15:07:06 +02:00
Ricki Hirner
89dcad5abc Setup intent filter (#80)
* Add intent filter for caldav(s)://, carddav(s):// and davx5:// schemes
* Define intent-filter only for standard and gplay flavors
* Merge manifest; remove Espresso tests (further tests should be added)
* Lint

Closes bitfireAT/davx5#77
2022-04-25 15:07:06 +02:00
Ricki Hirner
84f73918e2 Rename at.bitfire.davdroid.model to at.bitfire.davdroid.db to make clear it contains database models (and not view models) 2022-04-25 15:07:06 +02:00
Ricki Hirner
53ca7f769b Move file, update gradle and plugin 2022-04-15 12:02:32 +02:00
Ricki Hirner
b80aaa41a2 Move file, update gradle and plugin 2022-04-15 12:02:32 +02:00
Sunik Kupfer
37a7685e7a In app purchases and rating (bitfireAT/davx5#15) (#61)
* [WIP] add earning badges activity layout

* [WIP] ratings test

* [WIP] add billingClient

* correct gplay file placements

* cleanup and start on loading badges from play console

* [WIP]

* [WIP]

* use constraint layout instead of linear layouts

* [WIP] implement caching

* implement caching

* [WIP] load, cache and display bought badges

* [WIP] load, cache and display bought badges

* refactor and fix adapter not updating

* [WIP] load, cache and display bought badges

* proper use of RecyclerView

* refactor

* hide recycler views if no badges available or not yet bought

* make badges buyable and display earned badges

* reflect correct amount of bought badges

* [WIP] refactor, extract billing operations

* move files to gplay flavor and add sailing badge

* Load skus from cache if possible

* Load and save bought skus from and to cache

* linting

* alter log messages

* fixes and stop caching bought badges

* acknowledge purchases and show bought skus also when cache is cleared

* consume coffees

* add notice

* dont clear bought badges on new buy

* calculate bought badges only when purchases and skudetails are loaded

* retry if BillingService gets disconnected

* [WIP] disable buy button for bought badges

* [WIP] disable buy button for bought badges

* disable buy button for bought badges

* fix missing theme exception

* display correct amount of bought badges

* add back button and activity title

* add back button and activity title, via manifest instead

* add color tinting and center year of badge

* reflect bought state correctly

* easter eggs

* fix concurrent modification exception

* refactor for different flavors

* refactor AccountsDrawerHandler for different flavors

* Remove FakeReviewManager dependency in non gplay flavor test

* add community changes gone missing with improper rebase

* move files add add drop in animation for badges

* start using BaseAccountsDrawerHandler and variations for flavors

* remove dead code

* AccountsDrawer: minor changes

* Minor changes; remove caching for SKUs (done by billing library according to docs)

* Minor changes

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2022-04-15 11:27:21 +02:00
Sunik Kupfer
5396353cb5 In app purchases and rating (bitfireAT/davx5#15) (#61)
* [WIP] add earning badges activity layout

* [WIP] ratings test

* [WIP] add billingClient

* correct gplay file placements

* cleanup and start on loading badges from play console

* [WIP]

* [WIP]

* use constraint layout instead of linear layouts

* [WIP] implement caching

* implement caching

* [WIP] load, cache and display bought badges

* [WIP] load, cache and display bought badges

* refactor and fix adapter not updating

* [WIP] load, cache and display bought badges

* proper use of RecyclerView

* refactor

* hide recycler views if no badges available or not yet bought

* make badges buyable and display earned badges

* reflect correct amount of bought badges

* [WIP] refactor, extract billing operations

* move files to gplay flavor and add sailing badge

* Load skus from cache if possible

* Load and save bought skus from and to cache

* linting

* alter log messages

* fixes and stop caching bought badges

* acknowledge purchases and show bought skus also when cache is cleared

* consume coffees

* add notice

* dont clear bought badges on new buy

* calculate bought badges only when purchases and skudetails are loaded

* retry if BillingService gets disconnected

* [WIP] disable buy button for bought badges

* [WIP] disable buy button for bought badges

* disable buy button for bought badges

* fix missing theme exception

* display correct amount of bought badges

* add back button and activity title

* add back button and activity title, via manifest instead

* add color tinting and center year of badge

* reflect bought state correctly

* easter eggs

* fix concurrent modification exception

* refactor for different flavors

* refactor AccountsDrawerHandler for different flavors

* Remove FakeReviewManager dependency in non gplay flavor test

* add community changes gone missing with improper rebase

* move files add add drop in animation for badges

* start using BaseAccountsDrawerHandler and variations for flavors

* remove dead code

* AccountsDrawer: minor changes

* Minor changes; remove caching for SKUs (done by billing library according to docs)

* Minor changes

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2022-04-15 11:27:21 +02:00
Ricki Hirner
593be72ed8 Fetch translations from Transifex 2022-03-23 18:02:05 +01:00
Ricki Hirner
b904394793 Fetch translations from Transifex 2022-03-22 12:33:30 +01:00
Ricki Hirner
036911f117 Navigation drawer: update Community 2022-03-20 20:14:51 +01:00
Ricki Hirner
04b695fa6f Navigation drawer: update Community 2022-03-20 20:14:51 +01:00
Ricki Hirner
92be889ce0 Fetch translations from Transifex 2022-03-20 20:11:12 +01:00
Ricki Hirner
779fa43e9e Fetch translations from Transifex 2022-03-18 10:34:58 +01:00
Ricki Hirner
20d9d76a9d Fetch translations from Transifex 2022-03-16 14:44:25 +01:00
Ricki Hirner
4d0d463819 DebugInfo: provide IntentBuilder that truncates large logs/local resource dumps (should fix bitfireAT/davx5#69) 2022-03-16 14:44:24 +01:00
Ricki Hirner
41efc41fd9 Fetch translations from Transifex 2022-01-28 17:06:52 +01:00
Ricki Hirner
5826d89919 Fetch translations from Transifex 2022-01-24 11:13:54 +01:00
Ricki Hirner
305fada5f9 Fetch translations from Transifex 2022-01-05 16:16:46 +01:00
Ricki Hirner
c2e0ed6ac6 Fetch translations from Transifex 2021-12-20 17:47:32 +01:00
Ricki Hirner
0830dab59b Fetch translations from Transifex 2021-12-16 13:35:58 +01:00
Ricki Hirner
cc62e5f014 Update copyright (resolves bitfireAT/davx5#26) 2021-12-15 17:47:12 +01:00
Ricki Hirner
f8989e75cd Version bump to 4.1-alpha.1 2021-10-21 15:27:57 +02:00
Ricki Hirner
61b50ad2cc Fetch translations from Transifex 2021-10-04 17:42:54 +02:00
Ricki Hirner
b602ace309 Improve WebDAV UI 2021-10-02 18:54:21 +02:00
Ricki Hirner
88771a16c2 Squashed commit of the following:
commit 56f5f5a6cbad595534148a160678972734ca6cf2
Author: Ricki Hirner <hirner@bitfire.at>
Date:   Wed Sep 29 10:59:23 2021 +0200

    Version bump to 4.0-beta1

commit 2a4d7501d9d6cd572269de21e82c204c5a2a57e8
Author: Ricki Hirner <hirner@bitfire.at>
Date:   Tue Sep 28 21:45:49 2021 +0200

    Fix UiUtils.launchUri once again

commit 293accaf8f26842d198e9d5c4fc9029cb6ac71cc
Author: Ricki Hirner <hirner@bitfire.at>
Date:   Tue Sep 28 13:47:06 2021 +0200

    Adjust gradle daemon heap size

commit 7763a51c9bbc7baee36ef71648889f7fb4fe965d
Author: Ricki Hirner <hirner@bitfire.at>
Date:   Tue Sep 28 12:21:37 2021 +0200

    Query and show quota

commit 95f4dbfd8ffadd930aab9fc36dc3865bfee54fb7
Author: Ricki Hirner <hirner@bitfire.at>
Date:   Mon Sep 27 22:16:48 2021 +0200

    Persist cookies over WebDAV sessions

commit 17c9327eb3ef3d9384b28b70aa9637718d34e8a2
Author: Ricki Hirner <hirner@bitfire.at>
Date:   Sun Sep 26 19:54:57 2021 +0200

    Add WebDAV caches

    * add random access page cache
    * add thumbnail cache
    * add HEAD response cache

commit 0e5237f8b178ab66099999f72e657c59c9302d71
Author: Ricki Hirner <hirner@bitfire.at>
Date:   Tue Aug 31 13:25:36 2021 +0200

    Add WebDAV Access over Storage Access Framework
2021-09-29 11:03:22 +02:00
Ricki Hirner
11a5e68093 Squashed commit of the following:
commit 56f5f5a6cbad595534148a160678972734ca6cf2
Author: Ricki Hirner <hirner@bitfire.at>
Date:   Wed Sep 29 10:59:23 2021 +0200

    Version bump to 4.0-beta1

commit 2a4d7501d9d6cd572269de21e82c204c5a2a57e8
Author: Ricki Hirner <hirner@bitfire.at>
Date:   Tue Sep 28 21:45:49 2021 +0200

    Fix UiUtils.launchUri once again

commit 293accaf8f26842d198e9d5c4fc9029cb6ac71cc
Author: Ricki Hirner <hirner@bitfire.at>
Date:   Tue Sep 28 13:47:06 2021 +0200

    Adjust gradle daemon heap size

commit 7763a51c9bbc7baee36ef71648889f7fb4fe965d
Author: Ricki Hirner <hirner@bitfire.at>
Date:   Tue Sep 28 12:21:37 2021 +0200

    Query and show quota

commit 95f4dbfd8ffadd930aab9fc36dc3865bfee54fb7
Author: Ricki Hirner <hirner@bitfire.at>
Date:   Mon Sep 27 22:16:48 2021 +0200

    Persist cookies over WebDAV sessions

commit 17c9327eb3ef3d9384b28b70aa9637718d34e8a2
Author: Ricki Hirner <hirner@bitfire.at>
Date:   Sun Sep 26 19:54:57 2021 +0200

    Add WebDAV caches

    * add random access page cache
    * add thumbnail cache
    * add HEAD response cache

commit 0e5237f8b178ab66099999f72e657c59c9302d71
Author: Ricki Hirner <hirner@bitfire.at>
Date:   Tue Aug 31 13:25:36 2021 +0200

    Add WebDAV Access over Storage Access Framework
2021-09-29 11:03:22 +02:00
Ricki Hirner
5238f0491f Fetch translations from Transifex 2021-09-09 19:30:37 +02:00
Ricki Hirner
8e555f7909 lint 2021-09-09 16:34:35 +02:00
Ricki Hirner
762ecc38b8 Fetch translations from Transifex 2021-08-31 13:46:25 +02:00
Ricki Hirner
0e5ad5cbd8 Fetch translations from Transifex 2021-08-22 20:43:43 +02:00
Ricki Hirner
4d99ceae0f Fetch translations from Transifex 2021-07-31 13:05:00 +02:00
Ricki Hirner
f033c20754 Transifex: use "tx pull" instead of scripts/fetch-translations.sh from now on 2021-07-20 12:57:52 +02:00
Ricki Hirner
3d70055c7d Improve AutoComplete fields, especially in AppCompatDelegate-set dark mode 2021-07-16 01:50:10 +02:00
Ricki Hirner
8e9d66593a Fetch translations from Transifex 2021-07-14 16:24:50 +02:00
Ricki Hirner
b5dcb97b92 Fetch translations from Transifex 2021-07-08 14:38:15 +02:00
Ricki Hirner
506f7a7958 Provide auto-completion for some common base URLs 2021-07-06 16:15:14 +02:00
Ricki Hirner
3d7121d564 Fetch translations from Transifex 2021-06-29 19:11:19 +02:00
Ricki Hirner
556cb9a7f9 Fetch translations from Transifex 2021-05-23 14:00:56 +02:00
Ricki Hirner
9873c7947d [WIP] Dark mode for flavors 2021-05-07 23:10:20 +02:00
Ricki Hirner
6080e7845f Merge branch 'dev-3.x_EspressoExperimental-ose' into 'dev-3.x-ose'
Dev 3.x espresso experimental ose

See merge request bitfireAT/davx5-ose!59
2021-05-04 19:22:21 +02:00
Ricki Hirner
b1e120e810 Merge branch 'dev-3.x_RTLSupportEnabled-ose' into dev-3.x-ose 2021-05-04 15:33:23 +02:00
Ricki Hirner
39613b418f Add Vietnamese translations (thanks!); fetch translations from Transifex 2021-04-19 18:51:09 +02:00
Ricki Hirner
5ea2bc7bd1 Fetch translations from Transifex 2021-04-12 22:56:32 +02:00
Ricki Hirner
a6d6168eb0 Fetch translations from Transifex 2021-02-09 10:59:45 +01:00
Ricki Hirner
8eebcaff7a Fix translation 2021-02-07 12:00:48 +01:00
Ricki Hirner
98e873ccf1 Fetch translations from Transifex 2021-02-07 12:00:48 +01:00
Ricki Hirner
029e97f019 Fetch translations from Transifex 2021-01-11 20:07:32 +01:00
Ricki Hirner
3d07608efc Fetch translations from Transifex 2021-01-02 15:25:59 +01:00
Ricki Hirner
ce1724b69f Fetch translations from Transifex 2020-11-27 21:44:11 +01:00
Ricki Hirner
c6a3625510 Fetch translations from Transifex 2020-11-23 11:47:00 +01:00
Ricki Hirner
fa80d1fc91 Nextcloud Login Flow: follow redirects 2020-11-20 15:59:48 +01:00
Patrick
6a1cb74ec5 Shows a toast when no certificate was found
Currently the certificate selection would popup und disappear immedeately. This quick fix would show a Toast for User Feedback
2020-11-16 09:29:00 +01:00
Ricki Hirner
dae5c01aac Fetch translations from Transifex 2020-11-05 12:05:16 +01:00
Ricki Hirner
f91a275768 Add login layout ID for pre-launch tests; version bump to 3.3.6-beta1 2020-10-30 18:24:15 +01:00
Ricki Hirner
05d1e95ed0 Fetch translations from Transifex 2020-10-24 14:01:03 +02:00
Ricki Hirner
1d518eaac8 Fetch translations from Transifex 2020-10-20 12:08:08 +02:00
Ricki Hirner
32ebcaf8bc Fetch translations from Transifex 2020-10-19 23:15:33 +02:00
Ricki Hirner
6789e1d41d Advanced login: small fixes 2020-10-18 12:32:46 +02:00
Patrick Lang
c6b5d5cba0 Login with advanced options
Adds a new radiobutton for login with advanced options
2020-10-17 20:58:51 +02:00
Ricki Hirner
7a278f5e48 Nextcloud Login Flow v2: better error handling 2020-10-16 13:01:25 +02:00
Ricki Hirner
6da21bf9e5 Fetch translations from Transifex 2020-10-14 13:24:54 +02:00
Ricki Hirner
2b39d9025f Nextcloud: use Login Flow v2 for better browsing experience and to support things like Webauthn 2020-10-05 15:45:20 +02:00
Ricki Hirner
5fec57acdf Fetch translations from Transifex 2020-10-03 14:19:53 +02:00
Ricki Hirner
af2c3cade7 Fetch translations from Transifex 2020-10-02 13:06:55 +02:00
Ricki Hirner
5fdb893bfe Fetch translations from Transifex 2020-09-27 16:46:53 +02:00
Ricki Hirner
64c18dfa30 Fetch translations from Transifex 2020-09-20 14:20:07 +02:00
Ricki Hirner
9b65837436 Fetch translations from Transifex 2020-09-18 16:51:37 +02:00
Ricki Hirner
8d710909e1 Move translation pt-rBR to pt 2020-09-13 18:08:18 +02:00
Ricki Hirner
c86d2a42ad Fetch translations from Transifex 2020-09-09 12:04:55 +02:00
Ricki Hirner
e1dcd3e9f5 Fetch translations from Transifex 2020-08-31 12:40:22 +02:00
Ricki Hirner
60c18493cd Fetch translations from Transifex 2020-08-22 13:20:07 +02:00
Ricki Hirner
02095a6520 Fetch translations from Transifex 2020-08-12 12:10:25 +02:00
Ricki Hirner
ea7c5ed336 Some lint 2020-08-07 12:23:33 +02:00
Ricki Hirner
bb959e63c8 MultiSync: fix Sync all drawable/shortcut 2020-08-07 11:43:07 +02:00
Ricki Hirner
f01aa70342 Provide "Sync all accounts" option
* show sync status in accounts overview
* add "sync all accounts" in accounts overview
* add "sync all accounts" shortcut
2020-08-02 17:19:00 +02:00
Ricki Hirner
3ffee462ef Fetch translations from Transifex 2020-07-25 11:54:28 +02:00
Ricki Hirner
060b18ed6d Nextcloud login flow: decode parameters in nc://login URL 2020-07-24 18:38:54 +02:00
Ricki Hirner
12319c1a40 Fetch translations from Transifex 2020-07-22 11:11:21 +02:00
Ricki Hirner
2c3472328b Fetch translations from Transifex 2020-07-09 14:56:53 +02:00
Ricki Hirner
94ea1c0fbf Fetch translations from Transifex 2020-07-07 15:56:53 +02:00
Ricki Hirner
f83670a92d Merge branch 'clear-more-error-fields' into 'dev-3.x-ose'
Clear url, username, and name errors

See merge request bitfireAT/davx5-ose!34
2020-07-05 15:23:45 +02:00
Ricki Hirner
d50709407c Fetch translations from Transifex 2020-06-29 23:02:13 +02:00
Ricki Hirner
50d32fe5ed ical4android update 2020-06-12 13:37:41 +02:00
Ricki Hirner
30fa7b157a Fetch translations from Transifex 2020-06-11 18:31:41 +02:00
Ricki Hirner
405b7e09cd Use fragment-ktx viewModels(); begin removing I- prefix from interface names 2020-06-11 18:20:58 +02:00
Ricki Hirner
3a050c38d9 Fetch translations from Transifex 2020-05-31 20:46:27 +02:00
Ricki Hirner
8318c3f8a4 Fetch translations from Transifex 2020-05-29 00:27:21 +02:00
Ricki Hirner
9225455b84 Fix some TextInputLayouts 2020-05-27 15:50:37 +02:00
Ricki Hirner
045a399cfd Nextcloud login fragment: always use fragment view for Snackbar 2020-05-27 15:50:37 +02:00
Ricki Hirner
74732c8dc0 Fetch translations from Transifex 2020-05-27 15:50:36 +02:00
Ricki Hirner
5b59830db9 Update copyright 2020-05-27 15:50:36 +02:00
Ricki Hirner
86b0cb14c8 Merge branch 'clear-errors' into 'dev-3.x-ose'
Clear all errors when a different login method is chosen

See merge request bitfireAT/davx5-ose!31
2020-05-27 15:50:36 +02:00
Ricki Hirner
0e765f8789 Fetch translations from Transifex 2020-05-27 15:50:36 +02:00
Ricki Hirner
fc5a33737d Fetch translations from Transifex 2020-05-27 15:50:36 +02:00
Ricki Hirner
8962a25731 Show Donate in Navigation drawer only in ose 2020-05-27 15:50:36 +02:00
Ricki Hirner
be01910deb Fetch translations from Transifex 2020-04-21 11:34:04 +02:00
Ricki Hirner
32d4ae066a Adapt WebView progress bar; don't use okhttp BOM 2020-04-21 11:14:54 +02:00
Ricki Hirner
67ffe3b2e4 Fix build; bump version to 3.0-beta2 2020-04-20 16:57:53 +02:00
Ricki Hirner
841939dd70 Nextcloud Login flow: show progress and errors (including TLS errors) 2020-04-20 15:38:14 +02:00
Ricki Hirner
703f54a5aa Adapt login styles 2020-04-17 14:23:52 +02:00
Ricki Hirner
7a46e1a1db Don't cancel notifications of other sync threads (lets important notifications disappear sometimes) 2020-04-14 12:55:14 +02:00
Ricki Hirner
5ea4d48785 Update okhttp to 4.5.0 and dav4jvm to 2.0 2020-04-13 12:31:58 +02:00
Ricki Hirner
560593f531 Minor code cleanup 2020-04-09 15:05:59 +02:00
Ricki Hirner
24e4b2fd0d MultiSync 2020-04-09 14:32:10 +02:00
Ricki Hirner
172fed2eeb Managed intro 2020-04-08 17:54:42 +02:00
Ricki Hirner
565414d7d9 Introduce IntroActivity instead of startup fragments 2020-04-05 20:17:22 +02:00
Ricki Hirner
6fb8ab3acf Introduce IntroActivity instead of startup fragments 2020-04-05 20:17:22 +02:00
Ricki Hirner
74e6a93c1a Merge branch 'abaker/davx5-ose-clear_password_error' into master-ose 2020-03-06 12:01:06 +01:00
Ricki Hirner
cccc95a5f9 Fetch translations from Transifex 2020-03-06 00:21:51 +01:00
Ricki Hirner
b7c631e5d9 Merge branch 'textlayout_errors' into 'master-ose'
Move errors from EditTexts to TextInputLayouts

See merge request bitfireAT/davx5-ose!28
2020-03-03 23:50:14 +01:00
Ricki Hirner
b188a29596 Update gradle version and Android plugin; dependencies 2020-02-26 16:51:56 +01:00
Ricki Hirner
6455ff51e2 Fetch translations from Transifex 2020-02-20 18:13:03 +01:00
Ricki Hirner
cb88e39891 Fetch translations from Transifex 2020-01-23 18:36:49 +01:00
Ricki Hirner
900a5e58a3 Fetch translations from Transifex 2020-01-06 13:42:59 +01:00
Ricki Hirner
ca4422f374 Flavors: add privacy policy link to navigation drawer 2019-12-30 18:58:09 +01:00
Ricki Hirner
a134ffc7d7 Fetch translations from Transifex 2019-12-30 17:17:51 +01:00
Ricki Hirner
17a65e907c UiUtils.launchUri: show toast if no browser is installed 2019-12-28 15:33:03 +01:00
Ricki Hirner
d82c285f18 Add link to Privacy policy to Accounts drawer 2019-12-28 15:15:38 +01:00
Ricki Hirner
68d225e7d6 Fetch translations from Transifex 2019-12-22 11:00:41 +01:00
Ricki Hirner
76ad2bd94d Improve usage of Material theme 2019-12-03 18:46:41 +01:00
Ricki Hirner
ff02d8908a use Material theme; update AboutLibraries dependency 2019-12-03 17:13:56 +01:00
Ricki Hirner
d4a6f82110 Fetch translations from Transifex 2019-11-22 18:02:01 +01:00
Ricki Hirner
1e4fa8d104 Fetch translations from Transifex 2019-11-15 23:47:05 +01:00
Ricki Hirner
6eb8886c96 Fetch translations from Transifex 2019-11-13 00:02:29 +01:00
Ricki Hirner
a9fedf2c17 Login with URL: assume https:// URI scheme if none given 2019-11-10 10:33:17 +01:00
Ricki Hirner
2529ccd42c Fetch translations from Transifex 2019-10-25 11:08:06 +02:00
Ricki Hirner
2cc089a1fc Login Flow: optimization 2019-10-24 11:21:10 +02:00
tobiasKaminsky
8c6d689997 correct concatenate url with davPath.
Previously a subfolder was omitted:
serverUrl: http://localhost/nc
davPath: /remote.php/dav
-->
wrong: http://localhost/remote.php/dav
correct: http://localhost/nc/remote.php/dav
2019-10-24 11:17:25 +02:00
tobiasKaminsky
15f66a970a username was retrieved from intent, but not used 2019-10-24 11:17:17 +02:00
Ricki Hirner
0f928877aa Fetch translations from Transifex 2019-10-07 11:42:48 +02:00
Ricki Hirner
4b74005025 Implement Nextcloud Login Flow 2019-10-06 20:47:41 +02:00
Ricki Hirner
2448ea7a18 Fetch translations from Transifex 2019-09-21 11:45:23 +02:00
Ricki Hirner
31fecb0dc5 Fetch translations from Transifex (thanks for Bulgarian and Silesian!) 2019-09-20 00:06:50 +02:00
Ricki Hirner
215347b69d lint 2019-09-19 14:27:16 +02:00
Ricki Hirner
b6a0455b4f Enable Autofill for login screen 2019-08-25 16:35:19 +02:00
Ricki Hirner
11fa98cdaf Fetch translations from Transifex 2019-08-25 15:27:42 +02:00
Ricki Hirner
9e6b1037ae Fetch translations from Transifex 2019-07-20 22:10:10 +02:00
Ricki Hirner
f5ade54605 Fetch translations from Transifex 2019-07-08 21:40:34 +02:00
Ricki Hirner
d79744ada8 Fetch translations from Transifex 2019-06-09 11:05:46 +02:00
Ricki Hirner
722323f287 Fetch translations from Transifex 2019-05-29 18:09:58 +02:00
Ricki Hirner
a1f7a8a061 Use android:hint in TextInputLayout instead of TextInputEditText (should fix Meizu crashes) 2019-05-29 18:03:36 +02:00
Ricki Hirner
cd2f4baea2 Fetch translations from Transifex; add Finnish 2019-05-15 11:42:31 +02:00
Ricki Hirner
ae29de21cf Fetch translations from Transifex 2019-05-13 20:19:29 +02:00
Ricki Hirner
723e2bac12 Fetch translations from Transifex; new translation: Slovak (thanks brango67!) 2019-05-10 16:45:15 +02:00
Ricki Hirner
1c55c78e71 Fetch translations from Transifex 2019-05-09 12:21:34 +02:00
Ricki Hirner
9d3cf74f86 Fetch translations from Transifex 2019-05-06 19:35:11 +02:00
Ricki Hirner
666c069290 Fix crash in account setup 2019-04-18 13:35:41 +02:00
Ricki Hirner
37dffe420f Fetch translations from Transifex 2019-04-02 23:56:42 +02:00
Ricki Hirner
f11ed56032 Fetch translations from Transifex 2019-03-29 15:07:02 +01:00
Ricki Hirner
0d9402ebee Fetch translations from Transifex 2019-03-26 22:01:59 +01:00
Ricki Hirner
d6dcf7e1e1 Unify sync_prefs.xml by using string resource for package ID 2019-03-23 21:18:03 +01:00
Ricki Hirner
94326886e3 Fetch translations from Transifex 2019-03-23 21:07:42 +01:00
Ricki Hirner
524e3dffb2 About: use vector icon 2019-03-20 22:19:01 +01:00
Ricki Hirner
721244f340 Update beta feedback email address, libraries 2019-03-17 16:30:51 +01:00
Ricki Hirner
87382e229f Login: couple user name and email address 2019-03-17 16:09:51 +01:00
Ricki Hirner
f1444d1d1a Use ViewModel and data binding for login process 2019-03-15 22:39:00 +01:00
Ricki Hirner
9b0c0a40b3 Fetch translations from Transifex 2019-02-13 23:16:32 +01:00
Ricki Hirner
1dffa1bc69 Fetch translations from Transifex 2019-02-08 18:08:43 +01:00
Ricki Hirner
88566ba4bf Translations: fix positional argument (thanks @mbiebl) 2019-02-06 17:30:21 +01:00
Ricki Hirner
5a8d431ae1 Fetch translations from Transifex 2019-02-06 16:46:14 +01:00
Ricki Hirner
00a32ae8b4 Network security config per flavor 2019-01-18 12:03:35 +01:00
Ricki Hirner
5a651ed446 update homepage links; version bump to 2.2.3 2019-01-18 11:30:04 +01:00
Ricki Hirner
67683505d6 Fetch translations from Transifex 2019-01-13 21:47:06 +01:00
Ricki Hirner
ea134d7698 Fetch translations from Transifex 2019-01-08 19:17:49 +01:00
Ricki Hirner
1e0beb4e81 Rename dav4android to dav4jvm; update gradle 2019-01-06 18:15:59 +01:00
Ricki Hirner
a6a150a2e5 Fix Twitter link in gplay flavor 2019-01-04 22:57:14 +01:00
Ricki Hirner
5b555b4b2a Fetch translations from Transifex 2019-01-04 22:31:11 +01:00
Ricki Hirner
df39717be8 Fetch translations from Transifex 2019-01-03 18:55:32 +01:00
Ricki Hirner
03dec3bd72 Further DAVdroid -> DAVx5 replacements 2018-12-30 16:52:29 +01:00
Ricki Hirner
716b256bd5 New launcher icons 2018-12-30 15:30:11 +01:00
Ricki Hirner
295bd3fe9e Fetch translations from Transifex 2018-12-30 12:07:50 +01:00
Ricki Hirner
e9e59576a5 Rename DAVdroid to DAVx⁵ 2018-12-30 12:05:12 +01:00
Ricki Hirner
c039aea200 Refactor Settings provider
* don't use a separate :sync process anymore, so that settings management doesn't need IPC
* remove Settings service and IPC, use singleton with application Context instead
* adapt default number of sync worker threads
* library updates
2018-12-26 13:01:59 +01:00
Ricki Hirner
8d3f8ebdd4 Fetch translations from Transifex 2018-12-22 11:44:51 +01:00
Ricki Hirner
58c86c2174 Minor changes (lint/remove warnings) 2018-11-30 13:41:03 +01:00
Ricki Hirner
dbfc800eff Switch to AndroidX 2018-11-29 23:00:43 +01:00
Ricki Hirner
68e05874d7 Fetch translations from Transifex 2018-11-04 18:17:38 +01:00
Ricki Hirner
cf7cedb313 Handle unresolvable ACTION_VIEW intents; update gradle, Kotlin 2018-11-04 18:11:43 +01:00
Ricki Hirner
299ea45faa Fetch translations from Transifex 2018-08-28 13:40:35 +02:00
Ricki Hirner
36ee3398a1 Fetch translations from Transifex 2018-08-10 21:26:00 +02:00
Ricki Hirner
5cc1f353bc Fetch translations from Transifex; always replace "..." by "…" 2018-08-05 10:59:01 +02:00
Ricki Hirner
2bc542e120 Fetch translations from Transifex 2018-07-28 14:34:09 +02:00
Ricki Hirner
942082b3e7 Fetch translations from Transifex 2018-07-27 16:52:57 +02:00
Ricki Hirner
42269ff83f Fetch translations from Transifex 2018-07-22 11:18:50 +02:00
Ricki Hirner
d6a14fadd4 Fetch translations from Transifex 2018-07-13 15:06:59 +02:00
Ricki Hirner
2e92b38b8f New About activity
* new About activity using AboutLibraries library
* include DAVdroid version and other non-personal information in URL when DAVdroid homepage is opened
  (so that we know what DAVdroid versions are used out there and maybe can provide version-specific help)
2018-07-13 14:57:14 +02:00
Ricki Hirner
b48d6dfe0e move lambda expressions out of parentheses; use CREATOR-named companion objects for Parcelable 2018-06-17 16:34:45 +02:00
Ricki Hirner
7b15da2cd5 Fetch translations from Transifex 2018-06-08 11:50:39 +02:00
Ricki Hirner
24e2092f1b Update copyright 2018-05-28 12:04:43 +02:00
Ricki Hirner
902305cc5e Fetch translations from Transifex 2018-05-28 10:47:49 +02:00
Ricki Hirner
5524785c6b Fetch translations from Transifex 2018-05-15 14:21:37 +02:00
Ricki Hirner
b082cd9880 Code cleanup (lint) 2018-04-28 21:21:46 +02:00
Ricki Hirner
9fd4676d8b Fetch translations from Transifex 2018-04-26 10:54:20 +02:00
Ricki Hirner
c730a7814d Fetch translations from Transifex 2018-04-13 10:24:52 +02:00
Ricki Hirner
c6a06c3453 Fetch translations from Transifex 2018-03-26 09:37:41 +02:00
Ricki Hirner
9eee586c0a Minor fixes 2018-03-15 18:13:53 +01:00
Ricki Hirner
1c603ffc84 Themeing, minor refactoring 2018-03-15 12:09:27 +01:00
Ricki Hirner
b1e85a5295 Themeing, minor refactoring 2018-03-15 12:09:27 +01:00
Ricki Hirner
ae672648f4 Sync logic fix, theming, ProGuard 2018-03-07 23:24:49 +01:00
Ricki Hirner
ddcff6ca66 Update to support library 27.1.0 and use it wherever possible
* Fragment transactions can now be done in onLoadFinished().
2018-03-06 13:58:43 +01:00
Ricki Hirner
ff73b49560 Fetch translations from Transifex 2018-01-20 15:23:58 +01:00
Ricki Hirner
18166926fa Fetch translations from Transifex 2018-01-18 16:13:52 +01:00
Ricki Hirner
ab10a27575 Login activity: add padding; sync: re-throw Interrupted(IO)Exception 2018-01-15 21:32:21 +01:00
Ricki Hirner
5bfb875019 Fetch translations from Transifex 2018-01-15 20:52:33 +01:00
Ricki Hirner
2e72fb4c7b Login activity
* use TextInputLayout for input fields
* use support library instead of custom EditPassword widget
* improve client certificate UI
2018-01-15 13:58:21 +01:00
Ricki Hirner
38c6486610 Login with client certificates
* setup UI: login with URL and client certificate
* account settings UI: show either username/password or client certificate alias
* AccountSettings: serve credentials in generalized Credentials objects
* HttpClient: use Credentials (instead of username/password) for authentication
* HttpClient: always use CustomTlsSocketFactory
* CustomTlsSocketFactory: support client certificates
2018-01-13 22:56:33 +01:00
Ricki Hirner
ec9ac3a8cb Fetch translations from Transifex 2018-01-02 19:42:27 +01:00
Ricki Hirner
4d9b5c1ef7 Fetch translations from Transifex 2017-12-26 13:07:17 +01:00
Ricki Hirner
ca2344ea2c Fetch translations from Transifex 2017-12-16 22:02:05 +01:00
Ricki Hirner
d1644ab92b Fetch translations from Transifex 2017-12-16 19:08:49 +01:00
Ricki Hirner
4c97d501ce Navigation drawer: add link to manual 2017-12-16 19:08:45 +01:00
Ricki Hirner
599ed4d9a3 Navigation drawer: add link to manual 2017-12-16 19:08:45 +01:00
Ricki Hirner
633c3f6ae3 Use email instead of forum for beta feedback 2017-12-03 15:47:27 +01:00
Ricki Hirner
6822a0e5bc Fetch translations from Transifex 2017-12-03 15:23:31 +01:00
Ricki Hirner
91776fe0ae Fetch translations from Transifex 2017-11-25 18:49:52 +01:00
Ricki Hirner
1dfc34ace4 Fetch translations from Transifex 2017-11-11 21:26:02 +01:00
Ricki Hirner
59306a4c28 Fetch translations from Transifex 2017-10-31 09:20:12 +01:00
Ricki Hirner
3671a80ecb Fetch translations from Transifex 2017-10-25 20:38:05 +02:00
Ricki Hirner
5c8da1a4ec Fetch translations from Transifex 2017-10-15 17:44:48 +02:00
Ricki Hirner
e3349af73b Accounts drawer: beta feedback 2017-10-13 20:53:59 +02:00
Ricki Hirner
574576fbf8 Accounts drawer: beta feedback 2017-10-13 20:53:59 +02:00
Ricki Hirner
d2b6cba230 Fetch translations from Transifex 2017-10-13 20:45:13 +02:00
Ricki Hirner
19d81033ba Minor bug fixes, move beta feedback to navigation drawer 2017-10-13 13:30:01 +02:00
Ricki Hirner
996aa87b8e Managed DAVdroid, settings framework, theming, launcher icons, lib update 2017-10-13 12:24:35 +02:00
Ricki Hirner
47cc2c5515 Fetch translations from Transifex 2017-09-25 12:07:34 +02:00
Ricki Hirner
6e96c745a5 Fetch translations from Transifex 2017-09-23 21:23:37 +02:00
Ricki Hirner
3055e67310 Fetch translations from Transifex 2017-09-20 22:15:41 +02:00
Ricki Hirner
4f9892ee1b Fetch translations from Transifex 2017-09-15 13:29:22 +02:00
Ricki Hirner
e8a46171fd Fetch translations from Transifex 2017-09-10 20:17:31 +02:00
Ricki Hirner
92486d93e5 Fetch translations from Transifex 2017-09-01 15:12:35 +02:00
Ricki Hirner
4604afe63d Fetch translations from Transifex 2017-08-30 21:09:42 +02:00
Ricki Hirner
a41155cacf Fetch translations from Transifex 2017-08-22 15:17:20 +02:00
Ricki Hirner
362cbd7f12 Fetch translations from Transifex 2017-08-15 15:52:46 +02:00
Ricki Hirner
02a8ee2864 Fetch translations from Transifex 2017-08-05 13:13:49 +02:00
Ricki Hirner
e11c3c3e35 Fix device rotate crash bug, cert4android race condition 2017-08-03 20:46:32 +02:00
Ricki Hirner
70cfefb2bc Rewrite last UI classes to Kotlin; allow cancellation of resource detection 2017-08-02 16:23:51 +02:00
Ricki Hirner
99ed6aa159 Rewrite to Kotlin
* rewrite some UI classes
* move logic from App to CustomCertificates and Logger singletons
2017-07-31 15:30:11 +02:00
Ricki Hirner
db141fa826 Fetch translations from Transifex 2017-07-27 16:49:48 +02:00
Ricki Hirner
b69b72de19 Update copyright 2017-07-19 19:30:57 +02:00
Ricki Hirner
7f59874da7 Remove gplay license check 2017-07-19 19:29:42 +02:00
Ricki Hirner
9f53b62b42 Fetch translations from Transifex 2017-07-06 12:10:30 +02:00
Ricki Hirner
c164f4cf9c GPlay: fix NPE in LicenseCheckSyncPlugin 2017-07-03 19:41:30 +02:00
Ricki Hirner
0623ebcec8 Fetch translations from Transifex 2017-07-03 18:54:35 +02:00
Ricki Hirner
19734f16b7 License check for GPlay version 2017-06-20 11:50:39 +02:00
Ricki Hirner
c81dc3c0ad Fetch translations from Transifex 2017-06-09 16:34:03 +02:00
Ricki Hirner
d0cd8b17da Fetch translations from Transifex 2017-06-03 19:28:11 +02:00
Ricki Hirner
816d5e4e29 Fetch translations from Transifex 2017-05-14 12:19:11 +02:00
Ricki Hirner
e339ac71ef Fetch translations from Transifex 2017-04-25 14:12:11 +02:00
Ricki Hirner
6f2929ca18 Use untranslated User-Agent string
* refactoring: don't use global string variables
2017-04-24 22:52:52 +02:00
Ricki Hirner
dfacb65e15 Fetch translations from Transifex 2017-04-16 15:49:22 +02:00
Ricki Hirner
5e3f7e93b2 Branding strings
* HTTP client: use app name as User-Agent
* use string resources for homepage URLs
* MultiSync: add FAQ
2017-04-11 16:23:38 +02:00
Ricki Hirner
89a6d173e6 Fetch translations from Transifex 2017-03-26 19:31:00 +02:00
Ricki Hirner
2f35b32850 Unify action bar icon colors 2017-03-12 13:24:33 +01:00
Ricki Hirner
55dbefded5 Update translations from Transifex 2017-03-03 12:53:30 +01:00
Ricki Hirner
608439081e Fetch translations from Transifex 2017-03-02 23:43:46 +01:00
Ricki Hirner
cbe1360973 Soldupe/MultiSync branding 2017-02-28 14:21:04 +01:00
Ricki Hirner
eddac368be Fetch translations from Transifex 2017-02-17 13:55:36 +01:00
Ricki Hirner
994577242c Fetch translations from Transifex (fixes crash in Spanish version) 2017-01-29 19:22:47 +01:00
Ricki Hirner
a00a92e01a Fetch translations from Transifex 2017-01-01 12:26:43 +01:00
Ricki Hirner
ac008ae379 Fetch translations from Transifex 2016-12-23 15:55:50 +01:00
Ricki Hirner
6779155812 Fetch translations from Transifex 2016-11-14 18:41:24 +01:00
Ricki Hirner
8acf18ef7a Fetch translations from Transifex 2016-11-13 20:34:28 +01:00
Ricki Hirner
3f5de489b1 Fetch translations from Transifex
ical4android: fix for events without dtend/duration
2016-11-06 17:36:39 +01:00
Ricki Hirner
9c98a4abd8 Fetch translations from Transifex 2016-10-21 20:10:18 +02:00
Ricki Hirner
c71d29bac3 Fetch translations from Transifex 2016-10-14 21:19:56 +02:00
Ricki Hirner
c97bd2b3e6 Android 4.0/4.1 fixes
* require API level 15 for TransactionTooLargeException
* use SQLite WAL only on API level 16+
* various database access, provider access and UI fixes
2016-10-04 16:12:44 +02:00
Ricki Hirner
ce4395d0fe Fetch translations from Transifex 2016-10-03 20:43:33 +02:00
Ricki Hirner
13268bf79f Import strings from Transifex 2016-09-18 16:51:05 +02:00
Ricki Hirner
08b0ca3ce5 Fetch translations from Transifex 2016-09-02 12:15:17 +02:00
Ricki Hirner
442ec8ceb6 gplay version: remove donation link
Fix icons again
2016-09-02 00:56:00 +02:00
Ricki Hirner
b58b603f92 New launcher logo
* new launcher logo (contributed by Christoph Scheidl)
2016-09-01 22:48:12 +02:00
Ricki Hirner
fddacd4687 iCloud: UI and strings 2016-08-14 20:57:31 +02:00
Ricki Hirner
9c113bbcff White icons for Apple® iCloud®, hallelujah™ 2016-08-14 13:18:45 +02:00
Ricki Hirner
708e85b782 Accept intent extras for LoginActivity 2016-08-13 23:06:28 +02:00
Ricki Hirner
3cf0a04597 Fetch translations from Transifex 2016-08-06 00:11:40 +02:00
Ricki Hirner
d2b29e9986 Improve HTTP authentication
* use preemptive Basic auth automatically for HTTPS connections
* cache auth parameters (Basic/Digest)
2016-08-05 23:17:32 +02:00
Ricki Hirner
c238769b23 Fetch translations from Transifex 2016-08-02 19:27:14 +02:00
Ricki Hirner
ffc25ab3e8 Clean up launcher icon
* clean up launcher icon
* update dependencies
2016-08-01 21:03:21 +02:00
Ricki Hirner
79c9a8aa51 Basic subscription management
* SDK version 24
* Subscription management and GUI
2016-08-01 20:24:32 +02:00
Ricki Hirner
d40fe519be Initial iCloud version
* new gradle configField: useMTM
* new gradle source dir: davdroid (for DAVdroid OSE + DAVdroid variants)
* move strings and default login fragment to davdroid source dir
* iCloud AndroidManifest: add billing permission and SubscriptionActivity
* add sync plugins
* iCloud flavor + sync plugin: in-app billing
* iCloud flavor: login credentials fragment + font
* iCloud flavor: new strings
* use account type from string assets instead of hardcoded constant
2016-07-29 14:27:05 +02:00
Ricki Hirner
9d88fceb8b Add standard and gplay product flavor 2016-06-24 00:06:43 +02:00
83 changed files with 3413 additions and 0 deletions

View File

@@ -56,16 +56,28 @@ android {
flavorDimensions += "distribution"
productFlavors {
create("standard")
create("ose") {
dimension = "distribution"
versionNameSuffix = "-ose"
}
create("gplay") {
versionNameSuffix = "-gplay"
}
}
sourceSets {
getByName("standard") {
kotlin.srcDirs("src/standard/kotlin", "src/davdroid/kotlin")
res.srcDirs("src/standard/res", "src/davdroid/res")
}
getByName("androidTest") {
assets.srcDir("$projectDir/schemas")
}
getByName("gplay") {
kotlin.srcDirs("src/gplay/kotlin", "src/davdroid/kotlin")
res.srcDirs("src/gplay/res", "src/davdroid/res")
}
}
signingConfigs {
@@ -229,3 +241,14 @@ dependencies {
testImplementation(libs.okhttp.mockwebserver)
testImplementation(libs.robolectric)
}
// build variants (flavors)
val gplayImplementation by configurations {
dependencies {
implementation(libs.android.billing)
implementation(libs.android.review)
implementation(libs.confettikit)
}
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.ui
import android.content.Context
import androidx.test.core.app.launchActivity
import at.bitfire.davdroid.settings.SettingsManager
import com.google.android.play.core.review.testing.FakeReviewManager
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import io.mockk.every
import io.mockk.impl.annotations.RelaxedMockK
import io.mockk.junit4.MockKRule
import io.mockk.mockkObject
import io.mockk.spyk
import io.mockk.verify
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject
@HiltAndroidTest
class EarnBadgesActivityTest {
@Inject @ApplicationContext
lateinit var context: Context
@RelaxedMockK
lateinit var settings: SettingsManager
@get:Rule
val hiltRule = HiltAndroidRule(this)
@get:Rule
val mockkRule = MockKRule(this)
@Before
fun setup() {
hiltRule.inject()
}
@Test
fun testShowRatingRequest() {
launchActivity<EarnBadgesActivity>().use { scenario ->
scenario.onActivity { activity ->
val fakeReviewManager = spyk(FakeReviewManager(activity))
activity.showRatingRequest(fakeReviewManager)
verify {
fakeReviewManager.requestReviewFlow()
}
}
}
}
@Test
fun testShouldShowRatingRequest_firstInstallTimeIntervalPassed() {
// Interval is two weeks
mockkObject(EarnBadgesActivity) {
every { EarnBadgesActivity.currentTime() } returns 1652343892058 // "now" = 12.05.22
every { EarnBadgesActivity.installTime(context) } returns 1651087147000 // "15 days ago" = 27.04.2022
every { settings.getLongOrNull(EarnBadgesActivity.LAST_REVIEW_PROMPT) } returns 0 // "never" = 0
assertTrue(EarnBadgesActivity.shouldShowRatingRequest(context, settings)) // 15 > 14 => true
}
}
@Test
fun testShouldShowRatingRequest_firstInstallTimeIntervalNotPassed() {
// Interval is two weeks
mockkObject(EarnBadgesActivity) {
every { EarnBadgesActivity.currentTime() } returns 1652306400000 // "now" = 12.05.22
every { EarnBadgesActivity.installTime(context) } returns 1652133600000 // "2 days ago" = 10.05.2022
every { settings.getLongOrNull(EarnBadgesActivity.LAST_REVIEW_PROMPT) } returns 0 // "never" = 0
assertFalse(EarnBadgesActivity.shouldShowRatingRequest(context, settings)) // 2 > 14 => false
}
}
@Test
fun testShouldShowRatingRequest_firstInstallTimeAndLastReviewPromptIntervalPassed() {
// Interval is two weeks
mockkObject(EarnBadgesActivity) {
every { EarnBadgesActivity.currentTime() } returns 1652306400000 // "now" = 12.05.22
every { EarnBadgesActivity.installTime(context) } returns 1651087147000 // "15 days ago" = 27.04.2022
every { settings.getLongOrNull(EarnBadgesActivity.LAST_REVIEW_PROMPT) } returns 1651087147000 // "15 days ago" = 27.04.2022
assertTrue(EarnBadgesActivity.shouldShowRatingRequest(context, settings)) // 15 > 14 => true
}
}
@Test
fun testShouldShowRatingRequest_lastReviewPromptIntervalNotPassed() {
// Interval is two weeks
mockkObject(EarnBadgesActivity) {
every { EarnBadgesActivity.currentTime() } returns 1652306400000 // "now" = 12.05.22
every { EarnBadgesActivity.installTime(context) } returns 1652343892058 // "15 days ago" = 27.04.2022
every { settings.getLongOrNull(EarnBadgesActivity.LAST_REVIEW_PROMPT) } returns 1652306400000 // "now" = 12.05.22
assertFalse(EarnBadgesActivity.shouldShowRatingRequest(context, settings)) // 0 > 14 => false
}
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.di
import at.bitfire.davdroid.ui.intro.StandardAndGplayIntroPageFactory
import at.bitfire.davdroid.ui.intro.IntroPageFactory
import at.bitfire.davdroid.ui.setup.LoginTypesProvider
import at.bitfire.davdroid.ui.setup.StandardLoginTypesProvider
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent
import dagger.hilt.android.components.ViewModelComponent
import dagger.hilt.components.SingletonComponent
interface StandardAndGplayModules {
@Module
@InstallIn(ActivityComponent::class)
interface ForActivities {
@Binds
fun loginTypesProvider(impl: StandardLoginTypesProvider): LoginTypesProvider
}
@Module
@InstallIn(ViewModelComponent::class)
interface ForViewModels {
@Binds
fun loginTypesProvider(impl: StandardLoginTypesProvider): LoginTypesProvider
}
@Module
@InstallIn(SingletonComponent::class)
interface Global {
@Binds
fun introPageFactory(impl: StandardAndGplayIntroPageFactory): IntroPageFactory
}
}

View File

@@ -0,0 +1,148 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.ui
import androidx.compose.foundation.layout.Column
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.HelpCenter
import androidx.compose.material.icons.filled.CloudOff
import androidx.compose.material.icons.filled.CorporateFare
import androidx.compose.material.icons.filled.Forum
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.VolunteerActivism
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import at.bitfire.davdroid.R
import at.bitfire.davdroid.ui.ExternalUris.Homepage
import at.bitfire.davdroid.ui.ExternalUris.Social
import at.bitfire.davdroid.ui.ExternalUris.withStatParams
import javax.inject.Inject
open class StandardAccountsDrawerHandler @Inject constructor(): AccountsDrawerHandler() {
@Composable
override fun MenuEntries(
snackbarHostState: SnackbarHostState
) {
val uriHandler = LocalUriHandler.current
// Most important entries
ImportantEntries(snackbarHostState)
// News
MenuHeading(R.string.navigation_drawer_news_updates)
MenuEntry(
icon = painterResource(R.drawable.mastodon),
title = Social.fediverseHandle,
onClick = {
uriHandler.openUri(Social.fediverseUrl.toString())
}
)
// Tools
Tools()
// Support the project
MenuHeading(R.string.navigation_drawer_support_project)
Contribute(onContribute = {
uriHandler.openUri(
Homepage.baseUrl.buildUpon()
.appendPath(Homepage.PATH_OPEN_SOURCE)
.withStatParams(javaClass.simpleName)
.build().toString()
)
})
MenuEntry(
icon = Icons.Default.Forum,
title = stringResource(R.string.navigation_drawer_community),
onClick = {
uriHandler.openUri(Social.discussionsUrl.toString())
}
)
// External links
MenuHeading(R.string.navigation_drawer_external_links)
MenuEntry(
icon = Icons.Default.Home,
title = stringResource(R.string.navigation_drawer_website),
onClick = {
uriHandler.openUri(
Homepage.baseUrl
.buildUpon()
.withStatParams(javaClass.simpleName)
.build().toString())
}
)
MenuEntry(
icon = Icons.Default.Info,
title = stringResource(R.string.navigation_drawer_manual),
onClick = {
uriHandler.openUri(ExternalUris.Manual.baseUrl.toString())
}
)
MenuEntry(
icon = Icons.AutoMirrored.Default.HelpCenter,
title = stringResource(R.string.navigation_drawer_faq),
onClick = {
uriHandler.openUri(
Homepage.baseUrl.buildUpon()
.appendPath(Homepage.PATH_FAQ)
.withStatParams(javaClass.simpleName)
.build().toString()
)
}
)
MenuEntry(
icon = Icons.Default.CorporateFare,
title = stringResource(R.string.navigation_drawer_managed),
onClick = {
uriHandler.openUri(
Homepage.baseUrl.buildUpon()
.appendPath(Homepage.PATH_ORGANIZATIONS)
.appendPath(Homepage.PATH_ORGANIZATIONS_MANAGED)
.withStatParams(javaClass.simpleName)
.build().toString()
)
}
)
MenuEntry(
icon = Icons.Default.CloudOff,
title = stringResource(R.string.navigation_drawer_privacy_policy),
onClick = {
uriHandler.openUri(
Homepage.baseUrl.buildUpon()
.appendPath(Homepage.PATH_PRIVACY)
.withStatParams(javaClass.simpleName)
.build().toString()
)
}
)
}
@Composable
@Preview
fun MenuEntries_Standard_Preview() {
Column {
MenuEntries(SnackbarHostState())
}
}
@Composable
open fun Contribute(onContribute: () -> Unit) {
MenuEntry(
icon = Icons.Default.VolunteerActivism,
title = stringResource(R.string.navigation_drawer_contribute),
onClick = onContribute
)
}
}

View File

@@ -0,0 +1,167 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.ui
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.ui.graphics.Color
@Suppress("MemberVisibilityCanBePrivate")
object M3ColorScheme {
// All colors hand-crafted because Material Theme Builder generates unbelievably ugly colors
val primaryLight = Color(0xFF7cb342)
val onPrimaryLight = Color(0xFFffffff)
val primaryContainerLight = Color(0xFFb4e47d)
val onPrimaryContainerLight = Color(0xFF232d18)
val secondaryLight = Color(0xFFff7f2a)
val onSecondaryLight = Color(0xFFFFFFFF)
val secondaryContainerLight = Color(0xFFffa565)
val onSecondaryContainerLight = Color(0xFF3a271b)
val tertiaryLight = Color(0xFF658a24)
val onTertiaryLight = Color(0xFFFFFFFF)
val tertiaryContainerLight = Color(0xFFb0d08e)
val onTertiaryContainerLight = Color(0xFF263015)
val errorLight = Color(0xFFd71717)
val onErrorLight = Color(0xFFFFFFFF)
val errorContainerLight = Color(0xFFefb6b6)
val onErrorContainerLight = Color(0xFF3a0b0b)
val backgroundLight = Color(0xFFfcfcfc)
val onBackgroundLight = Color(0xFF2a2a2a)
val surfaceLight = Color(0xFFf5f5f5)
val onSurfaceLight = Color(0xFF4d4d4d)
val surfaceVariantLight = Color(0xFFe4e4e4)
val onSurfaceVariantLight = Color(0xFF2a2a2a)
val outlineLight = Color(0xFF838383)
val outlineVariantLight = Color(0xFFd4d4d4)
val scrimLight = Color(0xFF000000)
val inverseSurfaceLight = Color(0xFF2e322b)
val inverseOnSurfaceLight = Color(0xFFfafaf8)
val inversePrimaryLight = Color(0xFFb4e47d)
val surfaceDimLight = Color(0xFFe3e3e3)
val surfaceBrightLight = Color(0xFFf9f9f9)
val surfaceContainerLowestLight = Color(0xFFFFFFFF)
val surfaceContainerLowLight = Color(0xFFfafafa)
val surfaceContainerLight = Color(0xFFf5f5f5)
val surfaceContainerHighLight = Color(0xFFf0f0ef)
val surfaceContainerHighestLight = Color(0xFFebebea)
val primaryDark = Color(0xFFc4e3a4)
val onPrimaryDark = Color(0xFF2b4310)
val primaryContainerDark = Color(0xFF7cb342)
val onPrimaryContainerDark = Color(0xFFedf5e4)
val secondaryDark = Color(0xFFe5c3ac)
val onSecondaryDark = Color(0xFF3e332e)
val secondaryContainerDark = Color(0xFFff7f2a)
val onSecondaryContainerDark = Color(0xFFffeadb)
val tertiaryDark = Color(0xFFc6e597)
val onTertiaryDark = Color(0xFF4b661b)
val tertiaryContainerDark = Color(0xFF658a24)
val onTertiaryContainerDark = Color(0xFFf0f8e2)
val errorDark = Color(0xFFf6d0d0)
val onErrorDark = Color(0xFF4f1212)
val errorContainerDark = Color(0xFFe93434)
val onErrorContainerDark = Color(0xFFfcdede)
val backgroundDark = Color(0xFF1a1a1a)
val onBackgroundDark = Color(0xFFf0f0f0)
val surfaceDark = Color(0xFF292929)
val onSurfaceDark = Color(0xFFdedede)
val surfaceVariantDark = Color(0xFF363636)
val onSurfaceVariantDark = Color(0xFFededed)
val outlineDark = Color(0xFFa3a3a3)
val outlineVariantDark = Color(0xFF7cb342)
val scrimDark = Color(0xFF000000)
val inverseSurfaceDark = Color(0xFFdbdbdb)
val inverseOnSurfaceDark = Color(0xFF292929)
val inversePrimaryDark = Color(0xFF7cb342)
val surfaceDimDark = Color(0xFF333333)
val surfaceBrightDark = Color(0xFF4d4d4d)
val surfaceContainerLowestDark = Color(0xFF141414)
val surfaceContainerLowDark = Color(0xFF1f1f1f)
val surfaceContainerDark = Color(0xff3a3a3a)
val surfaceContainerHighDark = Color(0xFF383838)
val surfaceContainerHighestDark = Color(0xFF434343)
// Copied from Material Theme Builder: Theme.kt
val lightScheme = lightColorScheme(
primary = primaryLight,
onPrimary = onPrimaryLight,
primaryContainer = primaryContainerLight,
onPrimaryContainer = onPrimaryContainerLight,
secondary = secondaryLight,
onSecondary = onSecondaryLight,
secondaryContainer = secondaryContainerLight,
onSecondaryContainer = onSecondaryContainerLight,
tertiary = tertiaryLight,
onTertiary = onTertiaryLight,
tertiaryContainer = tertiaryContainerLight,
onTertiaryContainer = onTertiaryContainerLight,
error = errorLight,
onError = onErrorLight,
errorContainer = errorContainerLight,
onErrorContainer = onErrorContainerLight,
background = backgroundLight,
onBackground = onBackgroundLight,
surface = surfaceLight,
onSurface = onSurfaceLight,
surfaceVariant = surfaceVariantLight,
onSurfaceVariant = onSurfaceVariantLight,
outline = outlineLight,
outlineVariant = outlineVariantLight,
scrim = scrimLight,
inverseSurface = inverseSurfaceLight,
inverseOnSurface = inverseOnSurfaceLight,
inversePrimary = inversePrimaryLight,
surfaceDim = surfaceDimLight,
surfaceBright = surfaceBrightLight,
surfaceContainerLowest = surfaceContainerLowestLight,
surfaceContainerLow = surfaceContainerLowLight,
surfaceContainer = surfaceContainerLight,
surfaceContainerHigh = surfaceContainerHighLight,
surfaceContainerHighest = surfaceContainerHighestLight,
)
val darkScheme = darkColorScheme(
primary = primaryDark,
onPrimary = onPrimaryDark,
primaryContainer = primaryContainerDark,
onPrimaryContainer = onPrimaryContainerDark,
secondary = secondaryDark,
onSecondary = onSecondaryDark,
secondaryContainer = secondaryContainerDark,
onSecondaryContainer = onSecondaryContainerDark,
tertiary = tertiaryDark,
onTertiary = onTertiaryDark,
tertiaryContainer = tertiaryContainerDark,
onTertiaryContainer = onTertiaryContainerDark,
error = errorDark,
onError = onErrorDark,
errorContainer = errorContainerDark,
onErrorContainer = onErrorContainerDark,
background = backgroundDark,
onBackground = onBackgroundDark,
surface = surfaceDark,
onSurface = onSurfaceDark,
surfaceVariant = surfaceVariantDark,
onSurfaceVariant = onSurfaceVariantDark,
outline = outlineDark,
outlineVariant = outlineVariantDark,
scrim = scrimDark,
inverseSurface = inverseSurfaceDark,
inverseOnSurface = inverseOnSurfaceDark,
inversePrimary = inversePrimaryDark,
surfaceDim = surfaceDimDark,
surfaceBright = surfaceBrightDark,
surfaceContainerLowest = surfaceContainerLowestDark,
surfaceContainerLow = surfaceContainerLowDark,
surfaceContainer = surfaceContainerDark,
surfaceContainerHigh = surfaceContainerHighDark,
surfaceContainerHighest = surfaceContainerHighestDark,
)
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.ui.intro
import javax.inject.Inject
class StandardAndGplayIntroPageFactory @Inject constructor(
batteryOptimizationsPage: BatteryOptimizationsPage,
permissionsIntroPage: PermissionsIntroPage,
tasksIntroPage: TasksIntroPage
): IntroPageFactory {
override val introPages = arrayOf(
WelcomePage(),
tasksIntroPage,
permissionsIntroPage,
batteryOptimizationsPage
)
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.ui.setup
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.text.HtmlCompat
import at.bitfire.davdroid.R
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
@Composable
fun StandardLoginTypePage(
selectedLoginType: LoginType,
onSelectLoginType: (LoginType) -> Unit,
@Suppress("unused") // for build variants
setInitialLoginInfo: (LoginInfo) -> Unit,
onContinue: () -> Unit = {}
) {
Assistant(
nextLabel = stringResource(R.string.login_continue),
nextEnabled = true,
onNext = onContinue
) {
Column(Modifier.padding(8.dp)) {
Text(
stringResource(R.string.login_generic_login),
style = MaterialTheme.typography.headlineSmall,
modifier = Modifier.padding(vertical = 8.dp)
)
for (type in StandardLoginTypesProvider.genericLoginTypes)
LoginTypeSelector(
title = stringResource(type.title),
selected = type == selectedLoginType,
onSelect = { onSelectLoginType(type) }
)
Text(
stringResource(R.string.login_provider_login),
style = MaterialTheme.typography.headlineSmall,
modifier = Modifier.padding(top = 16.dp, bottom = 8.dp)
)
for (type in StandardLoginTypesProvider.specificLoginTypes)
LoginTypeSelector(
title = stringResource(type.title),
selected = type == selectedLoginType,
onSelect = { onSelectLoginType(type) }
)
HorizontalDivider(Modifier.padding(vertical = 12.dp))
val privacyPolicy = ExternalUris.Homepage.baseUrl.buildUpon()
.appendPath(ExternalUris.Homepage.PATH_PRIVACY)
.withStatParams("StandardLoginTypePage")
.build().toString()
val privacy = HtmlCompat.fromHtml(
stringResource(R.string.login_privacy_hint, stringResource(R.string.app_name), privacyPolicy),
HtmlCompat.FROM_HTML_MODE_COMPACT)
Text(
text = privacy.toAnnotatedString(),
style = MaterialTheme.typography.bodyMedium
)
}
}
}
@Composable
fun LoginTypeSelector(
title: String,
selected: Boolean,
onSelect: () -> Unit = {}
) {
Column {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.clickable(onClick = onSelect)
.padding(bottom = 4.dp)
) {
RadioButton(
selected = selected,
onClick = onSelect
)
Text(
title,
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.weight(1f)
)
}
}
}
@Composable
@Preview
fun StandardLoginTypePage_Preview() {
StandardLoginTypePage(
selectedLoginType = StandardLoginTypesProvider.genericLoginTypes.first(),
onSelectLoginType = {},
setInitialLoginInfo = {},
onContinue = {}
)
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.ui.setup
import android.content.Intent
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import at.bitfire.davdroid.ui.setup.LoginTypesProvider.LoginAction
import java.util.logging.Logger
import javax.inject.Inject
class StandardLoginTypesProvider @Inject constructor(
private val logger: Logger
) : LoginTypesProvider {
companion object {
val genericLoginTypes = listOf(
UrlLogin,
EmailLogin,
AdvancedLogin
)
val specificLoginTypes = listOf(
FastmailLogin,
GoogleLogin,
NextcloudLogin
)
}
override val defaultLoginType = UrlLogin
override fun intentToInitialLoginType(intent: Intent): LoginAction =
intent.data?.normalizeScheme().let { uri ->
when {
intent.hasExtra(LoginActivity.EXTRA_LOGIN_FLOW) ->
LoginAction(NextcloudLogin, true)
uri?.scheme == "mailto" ->
LoginAction(EmailLogin, true)
listOf("caldavs", "carddavs", "davx5", "http", "https").any { uri?.scheme == it } ->
LoginAction(UrlLogin, true)
else -> {
logger.warning("Did not understand login intent: $intent")
LoginAction(defaultLoginType, false) // Don't skip login type page if intent is unclear
}
}
}
@Composable
override fun LoginTypePage(
snackbarHostState: SnackbarHostState,
selectedLoginType: LoginType,
onSelectLoginType: (LoginType) -> Unit,
setInitialLoginInfo: (LoginInfo) -> Unit,
onContinue: () -> Unit
) {
StandardLoginTypePage(
selectedLoginType = selectedLoginType,
onSelectLoginType = onSelectLoginType,
setInitialLoginInfo = setInitialLoginInfo,
onContinue = onContinue
)
}
}

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!--suppress AndroidUnknownAttribute -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportWidth="108"
android:viewportHeight="108"
android:width="108dp"
android:height="108dp">
<group
android:translateX="-213.3939"
android:translateY="-709.5244">
<group
android:scaleX="0.0662553"
android:scaleY="0.0662553"
android:translateX="233.5464"
android:translateY="729.8276">
<path
android:pathData="M282.78183 280.50711l-85.53115 -85.53116 -0.00001 228.08306 228.08306 0 -85.53115 -85.53114c94.36922 -94.36921 247.75506 -94.36951 342.12458 0.00001 28.79533 28.79533 49.03756 48.10677 59.87183 84.60006l83.25028 0c-12.82982 -57.30603 -41.34018 -96.85969 -86.10134 -141.62083 -126.01589 -126.0159 -330.15022 -126.0159 -456.1661 0zm399.14533 399.14532c-94.36952 94.36952 -247.75535 94.36921 -342.12458 0 -28.79562 -28.79564 -49.03816 -48.10677 -59.8718 -84.60006l-83.25029 0c12.82949 57.30571 41.33988 96.85938 86.10134 141.62083 126.01588 126.0159 330.15021 126.0159 456.1661 0l85.53115 85.53115 0.00001 -228.08305 -228.08307 0z"
android:fillColor="#ffffff" />
<path
android:pathData="M201.33878 550.34595l27.70423 0c25.78414 0 44.43649 -13.44067 44.43649 -44.98509 0 -31.54442 -18.65235 -44.16219 -45.80799 -44.16219l-26.33273 0zm23.58974 -18.92666l0 -51.29397 1.3715 0c12.89207 0 23.04114 4.38879 23.04114 25.23554 0 20.84675 -10.14907 26.05843 -23.04114 26.05843z"
android:fillColor="#ffffff" />
<path
android:pathData="M311.13854 507.00666c2.1944 -8.50328 4.38879 -19.20096 6.30889 -28.25283l0.5486 0c2.19439 8.91472 4.38879 19.74955 6.58318 28.25283l1.50865 6.17173 -16.45796 0zm-34.28741 43.33929l24.13834 0 4.38879 -18.92666 24.96124 0 4.38878 18.92666 24.96124 0 -27.15563 -89.14728 -28.52713 0z"
android:fillColor="#ffffff" />
<path
android:pathData="M380.56703 550.34595l28.52713 0 26.33273 -89.14728 -24.13834 0 -9.05188 38.9505c-2.33154 9.46333 -4.11449 18.65236 -6.58318 28.25283l-0.5486 0c-2.46869 -9.60047 -4.11449 -18.7895 -6.58318 -28.25283l-9.32618 -38.9505 -24.96124 0z"
android:fillColor="#ffffff" />
<path
android:pathData="M449.1301 596.08783l39.35586 0 6.19081 -15.47702c2.4321 -6.41191 5.0853 -12.82382 7.51741 -19.01463l0.8844 0c3.3165 6.19081 6.41191 12.82382 9.72841 19.01463l8.84401 15.47702 40.68246 0 -33.16504 -53.06408 31.39624 -57.48608 -39.35586 0 -5.3064 15.47703c-1.98991 6.1908 -4.64311 12.82381 -6.63301 19.01462l-0.8844 0c-2.87431 -6.19081 -5.96971 -12.82382 -8.84402 -19.01462l-7.95961 -15.47703 -40.68245 0 31.39624 53.06408z"
android:fillColor="#ffffff" />
<path
android:pathData="M601.43782 509.37229c18.53926 0 34.49164 -11.78465 34.49164 -32.19221 0 -19.25783 -13.50922 -28.16817 -29.3179 -28.16817 -3.01801 0 -5.46117 0.28743 -8.62291 1.43715l1.14972 -13.2218 32.76707 0 0 -20.69499 -54.03691 0 -2.29945 46.85116 10.63493 6.89832c5.46117 -3.44916 7.76062 -4.31145 12.64693 -4.31145 7.18576 0 12.35951 4.02402 12.35951 11.78464 0 8.04806 -4.88632 11.78465 -13.50923 11.78465 -6.6109 0 -12.93437 -3.73659 -18.39554 -8.62291l-10.92236 15.52124c7.47319 7.47319 18.10812 12.93437 33.0545 12.93437z"
android:fillColor="#ffffff" />
</group>
</group>
</vector>

View File

@@ -0,0 +1,8 @@
<!-- drawable/mastodon.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#6364FF" android:pathData="M20.94,14C20.66,15.41 18.5,16.96 15.97,17.26C14.66,17.41 13.37,17.56 12,17.5C9.75,17.39 8,16.96 8,16.96V17.58C8.32,19.8 10.22,19.93 12.03,20C13.85,20.05 15.47,19.54 15.47,19.54L15.55,21.19C15.55,21.19 14.27,21.87 12,22C10.75,22.07 9.19,21.97 7.38,21.5C3.46,20.45 2.78,16.26 2.68,12L2.67,8.57C2.67,4.23 5.5,2.96 5.5,2.96C6.95,2.3 9.41,2 11.97,2H12.03C14.59,2 17.05,2.3 18.5,2.96C18.5,2.96 21.33,4.23 21.33,8.57C21.33,8.57 21.37,11.78 20.94,14M18,8.91C18,7.83 17.7,7 17.15,6.35C16.59,5.72 15.85,5.39 14.92,5.39C13.86,5.39 13.05,5.8 12.5,6.62L12,7.5L11.5,6.62C10.94,5.8 10.14,5.39 9.07,5.39C8.15,5.39 7.41,5.72 6.84,6.35C6.29,7 6,7.83 6,8.91V14.17H8.1V9.06C8.1,8 8.55,7.44 9.46,7.44C10.46,7.44 10.96,8.09 10.96,9.37V12.16H13.03V9.37C13.03,8.09 13.53,7.44 14.54,7.44C15.44,7.44 15.89,8 15.89,9.06V14.17H18V8.91Z" />
</vector>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/primaryColor" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@@ -0,0 +1,9 @@
<?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

@@ -0,0 +1,31 @@
<!--
~ Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application android:icon="@mipmap/ic_launcher">
<activity
android:name="at.bitfire.davdroid.ui.EarnBadgesActivity"
android:exported="false"
android:parentActivityName=".ui.account.AccountActivity"/>
<!-- AppAuth login flow redirect (duplicated in standard/AndroidManifest.xml) -->
<activity
android:name="net.openid.appauth.RedirectUriReceiverActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
tools:ignore="AppLinkUrlError"
android:scheme="at.bitfire.davdroid"
android:path="/oauth2/redirect"/>
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,433 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid
import android.app.Activity
import androidx.compose.ui.graphics.Color
import androidx.lifecycle.LifecycleCoroutineScope
import at.bitfire.davdroid.di.IoDispatcher
import at.bitfire.davdroid.ui.icons.Badge1UpExtralife
import at.bitfire.davdroid.ui.icons.BadgeCoffee
import at.bitfire.davdroid.ui.icons.BadgeCupcake
import at.bitfire.davdroid.ui.icons.BadgeDavx5Decade
import at.bitfire.davdroid.ui.icons.BadgeEnergyBooster
import at.bitfire.davdroid.ui.icons.BadgeLifeBuoy
import at.bitfire.davdroid.ui.icons.BadgeLocalBar
import at.bitfire.davdroid.ui.icons.BadgeMedal
import at.bitfire.davdroid.ui.icons.BadgeNinthAnniversary
import at.bitfire.davdroid.ui.icons.BadgeOfflineBolt
import at.bitfire.davdroid.ui.icons.BadgeSailboat
import at.bitfire.davdroid.ui.icons.BadgesIcons
import com.android.billingclient.api.AcknowledgePurchaseParams
import com.android.billingclient.api.BillingClient
import com.android.billingclient.api.BillingClient.BillingResponseCode
import com.android.billingclient.api.BillingClientStateListener
import com.android.billingclient.api.BillingFlowParams
import com.android.billingclient.api.BillingResult
import com.android.billingclient.api.PendingPurchasesParams
import com.android.billingclient.api.ProductDetails
import com.android.billingclient.api.Purchase
import com.android.billingclient.api.PurchasesResponseListener
import com.android.billingclient.api.PurchasesUpdatedListener
import com.android.billingclient.api.QueryProductDetailsParams
import com.android.billingclient.api.QueryPurchasesParams
import com.android.billingclient.api.acknowledgePurchase
import com.android.billingclient.api.queryProductDetails
import com.android.billingclient.api.queryPurchasesAsync
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import java.io.Closeable
import java.util.logging.Logger
class PlayClient @AssistedInject constructor(
@Assisted val activity: Activity,
@IoDispatcher val ioDispatcher: CoroutineDispatcher,
@Assisted val lifecycleScope: LifecycleCoroutineScope,
val logger: Logger
) : Closeable,
PurchasesUpdatedListener,
BillingClientStateListener,
PurchasesResponseListener
{
@AssistedFactory
interface Factory {
fun create(activity: Activity, lifecycleScope: LifecycleCoroutineScope): PlayClient
}
/**
* The product details; IE title, description, price, etc.
*/
val productDetailsList = MutableStateFlow<List<ProductDetails>>(emptyList())
/**
* The purchases that have been made.
*/
val purchases = MutableStateFlow<List<Purchase>>(emptyList())
/**
* Short message to display to the user
*/
val message = MutableStateFlow<String?>(null)
val purchaseSuccessful = MutableStateFlow(false)
private val billingClient: BillingClient = BillingClient.newBuilder(activity)
.setListener(this)
.enablePendingPurchases(PendingPurchasesParams.newBuilder().enableOneTimeProducts().build())
.enableAutoServiceReconnection() // Less SERVICE_DISCONNECTED responses (still need to handle them)
.build()
private var connectionTriesCount: Int = 0
/**
* Set up the billing client and connect when the activity is created.
* Product details and purchases are loaded from play store app cache,
* but this will give a more responsive user experience, when buying a product.
*/
init {
if (!billingClient.isReady) {
logger.fine("Start connection...")
billingClient.startConnection(this)
}
}
fun resetMessage() { message.value = null }
fun resetPurchaseSuccessful() { purchaseSuccessful.value = false }
/**
* Query the product details and purchases
*/
fun queryProductsAndPurchases() {
// Make sure billing client is available
if (!billingClient.isReady) {
logger.warning("BillingClient is not ready")
message.value = activity.getString(R.string.billing_unavailable)
return
}
// Only request product details if not found already
if (productDetailsList.value.isEmpty()) {
logger.fine("No products loaded yet, requesting")
// Query product details
queryProductDetails()
}
// Query purchases
// Purchases are stored locally by gplay app
// Result is received in [onQueryPurchasesResponse]
billingClient.queryPurchasesAsync(QueryPurchasesParams.newBuilder()
.setProductType(BillingClient.ProductType.INAPP)
.build(), this)
}
/**
* Start the purchase flow for a product
*/
fun purchaseProduct(badge: Badge) {
// Make sure billing client is available
if (!billingClient.isReady) {
logger.warning("BillingClient is not ready")
message.value = activity.getString(R.string.billing_unavailable)
return
}
// Build and send purchase request
val params = BillingFlowParams.newBuilder().setProductDetailsParamsList(listOf(
BillingFlowParams.ProductDetailsParams.newBuilder()
.setProductDetails(badge.productDetails)
.build()
)).build()
val billingResult = billingClient.launchBillingFlow(activity, params)
// Check purchase was successful
if (!billingResultOk(billingResult)) {
logBillingResult("launchBillingFlow", billingResult)
message.value = activity.getString(R.string.purchase_failed)
}
}
/**
* Stop the billing client connection (ie. when the activity is destroyed)
*/
override fun close() {
logger.fine("Closing connection...")
billingClient.endConnection()
}
/**
* Continue if connected
*/
override fun onBillingSetupFinished(billingResult: BillingResult) {
logBillingResult("onBillingSetupFinished", billingResult)
if (billingResultOk(billingResult)) {
logger.fine("Play client ready")
queryProductsAndPurchases()
}
}
/**
* Retry starting the billingClient a few times
*/
override fun onBillingServiceDisconnected() {
connectionTriesCount++
val maxTries = BILLINGCLIENT_CONNECTION_MAX_RETRIES
logger.warning("Connecting to BillingService failed. Retrying $connectionTriesCount/$maxTries times")
if (connectionTriesCount > maxTries) {
logger.warning("Failed to connect to BillingService. Given up on re-trying")
return
}
// Try to restart the connection on the next request
billingClient.startConnection(this)
}
/**
* Ask google servers for product details to display (ie. id, price, description, etc)
*/
private fun queryProductDetails() = lifecycleScope.launch(ioDispatcher) {
// Build request and query product details
val productList = productIds.map {
QueryProductDetailsParams.Product.newBuilder()
.setProductId(it)
.setProductType(BillingClient.ProductType.INAPP)
.build()
}
val params = QueryProductDetailsParams.newBuilder().setProductList(productList).build()
val productDetailsResult = billingClient.queryProductDetails(params)
// Handle billing result for request
val billingResult = productDetailsResult.billingResult
logBillingResult("onProductDetailsResponse", billingResult)
if (!billingResultOk(billingResult)) {
logger.warning("Failed to retrieve product details")
return@launch
}
// Check amount of products received is correct
val productDetails = productDetailsResult.productDetailsList
if (productDetails?.size != productIds.size) {
logger.warning("Missing products. Expected ${productIds.size}, but got ${productDetails?.size} product details from server.")
return@launch
}
// Save product details to be shown on screen
logger.fine("Got product details!\n$productDetails")
productDetailsList.emit(productDetails)
}
/**
* Callback from the billing library when [queryPurchasesAsync] is called.
*/
override fun onQueryPurchasesResponse(billingResult: BillingResult, purchasesList: MutableList<Purchase>) {
logBillingResult("onQueryPurchasesResponse", billingResult)
if (billingResultOk(billingResult)) {
logger.fine("Received purchases list")
processPurchases(purchasesList)
}
}
/**
* Called by the Billing Library when new purchases are detected.
*/
override fun onPurchasesUpdated(billingResult: BillingResult, purchases: MutableList<Purchase>?) {
logBillingResult("onPurchasesUpdated", billingResult)
if (billingResultOk(billingResult) && !purchases.isNullOrEmpty()){
logger.fine("Received updated purchases list")
processPurchases(purchases)
}
}
/**
* Process purchases
*/
private fun processPurchases(purchasesList: MutableList<Purchase>) {
// Return early if purchases list has not changed
if (purchasesList == purchases.value)
return
// Handle purchases
logPurchaseStatus(purchasesList)
for (purchase in purchasesList) {
logger.info("Handling purchase with state: ${purchase.purchaseState}")
// Verify purchase state
if (purchase.purchaseState != Purchase.PurchaseState.PURCHASED) {
// purchase pending or in undefined state (ie. refunded or consumed)
purchasesList.remove(purchase)
continue
}
// Check acknowledgement
if (!purchase.isAcknowledged) {
// Don't entitle user to purchase yet remove from purchases list for now
purchasesList.remove(purchase)
// Try to acknowledge purchase
acknowledgePurchase(purchase)
}
}
logAcknowledgementStatus(purchasesList)
// Update list
val mergedPurchases = (purchases.value + purchasesList).distinctBy {
it.purchaseToken
}
logger.info("Purchases: $mergedPurchases")
purchases.value = mergedPurchases
}
/**
* Requests acknowledgement of a purchase
*/
private fun acknowledgePurchase(purchase: Purchase) = lifecycleScope.launch(ioDispatcher) {
logger.info("Acknowledging purchase")
val params = AcknowledgePurchaseParams.newBuilder().setPurchaseToken(purchase.purchaseToken).build()
val billingResult = billingClient.acknowledgePurchase(params)
logBillingResult("acknowledgePurchase", billingResult)
// Check billing result
if (!billingResultOk(billingResult)) {
logger.warning("Acknowledging Purchase failed!")
// Notify user about failure
message.value = activity.getString(R.string.purchase_acknowledgement_failed)
return@launch
}
// Billing result OK! Acknowledgement successful
// Now entitle user to purchase (Add to purchases list)
val purchasesList = purchases.value.toMutableList()
purchasesList.add(purchase)
purchases.emit(purchasesList)
// Notify user about success
message.value = activity.getString(R.string.purchase_acknowledgement_successful)
purchaseSuccessful.value = true
}
/**
* Checks if the billing result response code is ok. Logs and may set error message if not.
*/
private fun billingResultOk(result: BillingResult): Boolean {
when (result.responseCode) {
BillingResponseCode.SERVICE_DISCONNECTED,
BillingResponseCode.SERVICE_UNAVAILABLE ->
message.value = activity.getString(R.string.network_problems)
BillingResponseCode.BILLING_UNAVAILABLE ->
message.value = activity.getString(R.string.billing_unavailable)
BillingResponseCode.USER_CANCELED ->
logger.info("User canceled the purchase")
BillingResponseCode.ITEM_ALREADY_OWNED ->
logger.info("The user already owns this item")
BillingResponseCode.DEVELOPER_ERROR ->
logger.warning("Google Play does not recognize the application configuration." +
"Do the product IDs match and is the APK in use signed with release keys?")
}
return result.responseCode == BillingResponseCode.OK
}
// /**
// * DANGER: Use only for testing!
// * Consumes a purchased item, so it will be available for purchasing again.
// * Used only for revoking a test purchase.
// */
// @Suppress("unused")
// private fun consumePurchase(purchase: Purchase) {
// if (BuildConfig.BUILD_TYPE != "debug")
// return
//
// logger.info("Trying to consume purchase with token: ${purchase.purchaseToken}")
// val consumeParams = ConsumeParams.newBuilder()
// .setPurchaseToken(purchase.purchaseToken)
// .build()
// lifecycleScope.launch(ioDispatcher) {
// val consumeResult = billingClient.consumePurchase(consumeParams)
// when (consumeResult.billingResult.responseCode) {
// BillingResponseCode.OK ->
// logger.info("Successfully consumed item with purchase token: '${consumeResult.purchaseToken}'")
// BillingResponseCode.ITEM_NOT_OWNED ->
// logger.info("Failed to consume item with purchase token: '${consumeResult.purchaseToken}'. Item not owned")
// else ->
// logger.info("Failed to consume item with purchase token: '${consumeResult.purchaseToken}'. BillingResult: ${consumeResult.billingResult}")
// }
// }
// }
// logging helpers
/**
* Log billing result the same way each time
*/
private fun logBillingResult(source: String, result: BillingResult) {
logger.fine("$source: responseCode=${result.responseCode}, message=${result.debugMessage}")
}
/**
* Log the number of purchases that are acknowledge and not acknowledged.
*/
private fun logAcknowledgementStatus(purchasesList: List<Purchase>) {
var ackYes = 0
var ackNo = 0
for (purchase in purchasesList)
if (purchase.isAcknowledged) ackYes++ else ackNo++
logger.info("logAcknowledgementStatus: acknowledged=$ackYes unacknowledged=$ackNo")
}
/**
* Log the number of purchases that are acknowledge and not acknowledged.
*/
private fun logPurchaseStatus(purchasesList: List<Purchase>) {
var undefined = 0
var purchased = 0
var pending = 0
for (purchase in purchasesList)
when (purchase.purchaseState) {
Purchase.PurchaseState.UNSPECIFIED_STATE -> undefined++
Purchase.PurchaseState.PURCHASED -> purchased++
Purchase.PurchaseState.PENDING -> pending++
}
logger.info("Purchases status: purchased=$purchased pending=$pending undefined=$undefined")
}
/**
* Support badge product
* @param productDetails
* @param yearBought
* @param count - amount of badge items of this badge type
*/
data class Badge(val productDetails: ProductDetails, var yearBought: String?, val count: Int) {
val name = productDetails.name
val description = productDetails.description
val price = productDetails.oneTimePurchaseOfferDetails?.formattedPrice
val purchased = yearBought != null
}
companion object {
const val BILLINGCLIENT_CONNECTION_MAX_RETRIES = 4
val BADGE_ICONS = mapOf(
"helping_hands.2022" to (BadgesIcons.BadgeLifeBuoy to Color(0xFFFF6A00)),
"a_coffee_for_you.2022" to (BadgesIcons.BadgeCoffee to Color(0xFF352B1B) ),
"loyal_foss_backer.2022" to (BadgesIcons.BadgeMedal to Color(0xFFFFC200)),
"part_of_the_journey.2022" to (BadgesIcons.BadgeSailboat to Color(0xFF083D77)),
"9th_anniversary.2022" to (BadgesIcons.BadgeNinthAnniversary to Color(0xFFFA8072)),
"1up_extralife.2023" to (BadgesIcons.Badge1UpExtralife to Color(0xFFD32F2F)),
"energy_booster.2023" to (BadgesIcons.BadgeEnergyBooster to Color(0xFFFDD835)),
"davx5_decade" to (BadgesIcons.BadgeDavx5Decade to Color(0xFF43A047)),
"push_development" to (BadgesIcons.BadgeOfflineBolt to Color(0xFFFDD835)),
"davx5_cocktail" to (BadgesIcons.BadgeLocalBar to Color(0xFF29CC00)),
"11th_anniversary" to (BadgesIcons.BadgeCupcake to Color(0xFFF679E5)),
)
val productIds = BADGE_ICONS.keys.toList()
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.di
import at.bitfire.davdroid.ui.AboutActivity
import at.bitfire.davdroid.ui.AccountsDrawerHandler
import at.bitfire.davdroid.ui.GplayAccountsDrawerHandler
import at.bitfire.davdroid.ui.GplayLicenseInfoProvider
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent
interface GplayModules {
@Module
@InstallIn(ActivityComponent::class)
interface ForActivities {
@Binds
fun accountsDrawerHandler(impl: GplayAccountsDrawerHandler): AccountsDrawerHandler
@Binds
fun appLicenseInfoProvider(impl: GplayLicenseInfoProvider): AboutActivity.AppLicenseInfoProvider
}
}

View File

@@ -0,0 +1,96 @@
/*
* 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.appcompat.app.AppCompatActivity
import androidx.compose.ui.platform.LocalUriHandler
import androidx.core.net.toUri
import androidx.lifecycle.lifecycleScope
import at.bitfire.davdroid.PlayClient
import at.bitfire.davdroid.settings.SettingsManager
import at.bitfire.davdroid.ui.ExternalUris.withStatParams
import com.google.android.play.core.review.ReviewManager
import com.google.android.play.core.review.ReviewManagerFactory
import dagger.hilt.android.AndroidEntryPoint
import jakarta.inject.Inject
import java.util.logging.Level
import java.util.logging.Logger
@AndroidEntryPoint
class EarnBadgesActivity() : AppCompatActivity() {
@Inject lateinit var logger: Logger
@Inject lateinit var playClientFactory: PlayClient.Factory
@Inject lateinit var settingsManager: SettingsManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Show rating API dialog one week after the app has been installed
if (shouldShowRatingRequest(this, settingsManager))
showRatingRequest(ReviewManagerFactory.create(this))
setContent {
AppTheme {
val uriHandler = LocalUriHandler.current
EarnBadgesScreen(
playClient = playClientFactory.create(this, lifecycleScope),
onStartRating = { uriHandler.openUri(
"market://details?id=$packageName".toUri()
.buildUpon()
.withStatParams(javaClass.simpleName)
.build().toString()
) },
onNavUp = ::onNavigateUp
)
}
}
}
/**
* Starts the in-app review API to trigger the review request
* Once the user has rated the app, it will still trigger, but won't show up anymore.
*/
fun showRatingRequest(manager: ReviewManager) {
// Try prompting for review/rating
manager.requestReviewFlow().addOnSuccessListener { reviewInfo ->
logger.log(Level.INFO, "Launching app rating flow")
manager.launchReviewFlow(this, reviewInfo)
}
}
companion object {
internal const val LAST_REVIEW_PROMPT = "lastReviewPrompt"
/** Time between rating interval prompts in milliseconds */
private const val RATING_INTERVAL = 2*7*24*60*60*1000 // Two weeks
/**
* Determines whether we should show a rating prompt to the user depending on whether
* - the RATING_INTERVAL has passed once after first installation, or
* - the last rating prompt is older than RATING_INTERVAL
*
* If the return value is `true`, also updates the `LAST_REVIEW_PROMPT` setting to the current time
* so that the next call won't be `true` again for the time specified in `RATING_INTERVAL`.
*/
fun shouldShowRatingRequest(context: Context, settings: SettingsManager): Boolean {
val now = currentTime()
val firstInstall = installTime(context)
val lastPrompt = settings.getLongOrNull(LAST_REVIEW_PROMPT) ?: now
val shouldShowRatingRequest = (now > firstInstall + RATING_INTERVAL) && (now > lastPrompt + RATING_INTERVAL)
Logger.getGlobal().info("now=$now, firstInstall=$firstInstall, lastPrompt=$lastPrompt, shouldShowRatingRequest=$shouldShowRatingRequest")
if (shouldShowRatingRequest)
settings.putLong(LAST_REVIEW_PROMPT, now)
return shouldShowRatingRequest
}
fun currentTime() = System.currentTimeMillis()
fun installTime(context: Context) = context.packageManager.getPackageInfo(context.packageName, 0).firstInstallTime
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.ui
import android.content.Context
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import at.bitfire.davdroid.PlayClient
import at.bitfire.davdroid.PlayClient.Badge
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.logging.Logger
@HiltViewModel(assistedFactory = EarnBadgesModel.Factory::class)
class EarnBadgesModel @AssistedInject constructor(
@ApplicationContext val context: Context,
private val logger: Logger,
@Assisted val playClient: PlayClient
) : ViewModel() {
@AssistedFactory
interface Factory {
fun create(playClient: PlayClient): EarnBadgesModel
}
init {
// Load the current state of bought badges
playClient.queryProductsAndPurchases()
}
val message = playClient.message
val purchaseSuccessful = playClient.purchaseSuccessful
/**
* List of badges available to buy
*/
val availableBadges = combine(
playClient.productDetailsList,
playClient.purchases
) { productDetails, purchases ->
logger.info("Creating new list of badges from product details and purchases")
logger.info("Product IDs: ${productDetails.map {"\n" + it.productId}}")
logger.info("Purchases: ${purchases.map { "\nPurchase: ${it.products}"}}")
// Create badges
productDetails.map { productDetails ->
// If the product/badge has been bought in one of the purchases, find the year and amount
var yearBought: String? = null
var count = 0
for (purchase in purchases)
if (purchase.products.contains(productDetails.productId)) {
yearBought = SimpleDateFormat("yyyy", Locale.getDefault()).format(Date(purchase.purchaseTime))
count = purchase.quantity
}
Badge(productDetails, yearBought, count)
}
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = emptyList()
)
/**
* Bought badges
*/
val boughtBadges = availableBadges.map { allBadges ->
logger.info("Finding bought badges")
// Filter for the bought badges
val boughtBadges = allBadges.filter { badge ->
badge.purchased
}
// Create duplicates for the ones that have been bought multiple times
boughtBadges.toMutableList().apply {
addAll(flatMap { badge -> List(badge.count - 1) { badge } })
}
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = emptyList()
)
fun buyBadge(badge: Badge) = playClient.purchaseProduct(badge)
fun onResetMessage() = playClient.resetMessage()
fun onResetPurchaseSuccessful() = playClient.resetPurchaseSuccessful()
override fun onCleared() = playClient.close()
}

View File

@@ -0,0 +1,308 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.ui
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
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.Favorite
import androidx.compose.material.icons.filled.FavoriteBorder
import androidx.compose.material.icons.filled.Star
import androidx.compose.material.icons.filled.StarRate
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
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.SnackbarDuration
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.zIndex
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import at.bitfire.davdroid.PlayClient
import at.bitfire.davdroid.PlayClient.Badge
import at.bitfire.davdroid.PlayClient.Companion.BADGE_ICONS
import at.bitfire.davdroid.R
import io.github.vinceglb.confettikit.compose.ConfettiKit
import io.github.vinceglb.confettikit.core.Angle
import io.github.vinceglb.confettikit.core.Party
import io.github.vinceglb.confettikit.core.Position
import io.github.vinceglb.confettikit.core.emitter.Emitter
import io.github.vinceglb.confettikit.core.models.Shape
import io.github.vinceglb.confettikit.core.models.Size
import kotlin.time.Duration.Companion.seconds
@Composable
fun EarnBadgesScreen(
playClient: PlayClient,
onStartRating: () -> Unit = {},
onNavUp: () -> Unit = {},
model: EarnBadgesModel = hiltViewModel(
creationCallback = { factory: EarnBadgesModel.Factory ->
factory.create(playClient)
}
)
) {
val availableBadges by model.availableBadges.collectAsStateWithLifecycle()
val boughtBadges by model.boughtBadges.collectAsStateWithLifecycle()
val errorMessage by model.message.collectAsStateWithLifecycle()
val purchaseSuccessful by model.purchaseSuccessful.collectAsStateWithLifecycle()
EarnBadges(
availableBadges = availableBadges,
boughtBadges = boughtBadges,
message = errorMessage,
purchaseSuccessful = purchaseSuccessful,
onBuyBadge = model::buyBadge,
onResetMessage = model::onResetMessage,
onResetPurchaseSuccessful = model::onResetPurchaseSuccessful,
onStartRating = onStartRating,
onNavUp = onNavUp
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun EarnBadges(
availableBadges: List<Badge>,
boughtBadges: List<Badge>,
message: String?,
purchaseSuccessful: Boolean,
onBuyBadge: (badge: Badge) -> Unit = {},
onResetMessage: () -> Unit = {},
onResetPurchaseSuccessful: () -> Unit = {},
onStartRating: () -> Unit = {},
onNavUp: () -> Unit = {}
) {
// Show snackbar when some message needs to be displayed
val snackbarHostState = remember { SnackbarHostState() }
LaunchedEffect(message != null) {
message?.let {
snackbarHostState.showSnackbar(message, duration = SnackbarDuration.Long)
onResetMessage()
}
}
Scaffold(
topBar = {
TopAppBar(
navigationIcon = {
IconButton(onClick = onNavUp) {
Icon(
Icons.AutoMirrored.Default.ArrowBack,
stringResource(R.string.navigate_up)
)
}
},
title = {
Text(
stringResource(R.string.earn_badges),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
},
actions = {
IconButton(onClick = onStartRating) {
Icon(Icons.Default.StarRate, stringResource(R.string.nav_rate_us))
}
}
)
},
snackbarHost = {
SnackbarHost(snackbarHostState)
},
) { padding ->
if (purchaseSuccessful) {
ConfettiKit(
modifier = Modifier.fillMaxSize().navigationBarsPadding().zIndex(1f),
parties = listOf(
Party(
angle = Angle.TOP,
spread = 60,
speed = 70f,
size = listOf(Size(10), Size(15), Size(20)),
shapes = listOf(
Shape.Vector(rememberVectorPainter(Icons.Default.Favorite)),
Shape.Vector(rememberVectorPainter(Icons.Default.FavoriteBorder)),
Shape.Circle,
Shape.Square,
),
emitter = Emitter(duration = 3.seconds).perSecond(50),
position = Position.Relative(x = 0.5, y = 1.0)
)
),
onParticleSystemEnded = { _, _ -> onResetPurchaseSuccessful() }
)
}
Column(
modifier = Modifier
.padding(padding)
.padding(horizontal = 16.dp)
.verticalScroll(rememberScrollState())
) {
if (boughtBadges.isNotEmpty()) {
TextHeading(
pluralStringResource(
R.plurals.you_earned_badges,
boughtBadges.size,
boughtBadges.size
)
)
LazyVerticalGrid(
modifier = Modifier.heightIn(max = 1000.dp),
columns = GridCells.Adaptive(minSize = 60.dp)
) {
items(boughtBadges.size) { index ->
BoughtBadgeListItem(boughtBadges[index])
}
}
}
TextHeading(stringResource(R.string.available_badges))
if (availableBadges.isEmpty())
TextBody(stringResource(R.string.available_badges_empty))
availableBadges.forEach { badge ->
BuyBadgeListItem(badge, onBuyBadge)
}
TextHeading(stringResource(R.string.what_are_badges_title))
TextBody(stringResource(R.string.what_are_badges_body))
TextHeading(stringResource(R.string.why_badges_title))
TextBody(stringResource(R.string.why_badges_body))
}
}
}
@Composable
fun BoughtBadgeListItem(badge: Badge) {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
IconButton(
modifier = Modifier
.fillMaxSize(),
onClick = { /* could start an animation */ }
) {
Card(
modifier = Modifier
.aspectRatio(1f),
colors = CardDefaults.cardColors(
containerColor = Color.White,
),
) {
val (icon, tint) = BADGE_ICONS[badge.productDetails.productId]!!
Icon(
imageVector = icon,
contentDescription = badge.productDetails.productId,
tint = tint,
modifier = Modifier
.size(65.dp)
.padding(3.dp)
)
}
}
Text(badge.yearBought ?: "?", fontSize = 14.sp, maxLines = 1)
}
}
@Composable
fun BuyBadgeListItem(
badge: Badge,
onBuyBadge: (badge: Badge) -> Unit,
) {
Card(
Modifier
.fillMaxWidth()
.padding(bottom = 16.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(vertical = 8.dp, horizontal = 16.dp)
) {
val (icon, tint) = BADGE_ICONS[badge.productDetails.productId]!!
Icon(
imageVector = icon,
contentDescription = badge.productDetails.productId,
tint = tint,
modifier = Modifier.size(30.dp)
)
Column(
modifier = Modifier
.weight(3f, true)
.padding(horizontal = 16.dp)
) {
Text(badge.name, fontSize = 14.sp, fontWeight = FontWeight.Bold)
Text(badge.description.replace("\n", ""), fontSize = 12.sp, lineHeight = 14.sp)
}
Button(
onClick = { onBuyBadge(badge) },
enabled = !badge.purchased
) {
Icon(
imageVector = if (!badge.purchased) Icons.Default.Star else Icons.Default.Favorite,
contentDescription = null,
modifier = Modifier.size(20.dp),
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
if (!badge.purchased)
Text(badge.price ?: stringResource(R.string.button_buy_badge_free))
else
Text(stringResource(R.string.button_buy_badge_bought))
}
}
}
}
@Composable
fun TextHeading(text: String) = Text(
text,
style = MaterialTheme.typography.headlineSmall,
modifier = Modifier.padding(top = 20.dp, bottom = 16.dp)
)
@Composable
fun TextBody(text: String) = Text(
text,
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.padding(bottom = 16.dp)
)

View File

@@ -0,0 +1,102 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.ui
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.widget.Toast
import androidx.compose.foundation.layout.Column
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.VolunteerActivism
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import at.bitfire.davdroid.BuildConfig
import at.bitfire.davdroid.R
import com.google.android.play.core.review.ReviewManagerFactory
import java.util.logging.Level
import java.util.logging.Logger
import javax.inject.Inject
/**
* Overrides some navigationd drawer actions for Google Play
*/
class GplayAccountsDrawerHandler @Inject constructor(
private val logger: Logger
) : StandardAccountsDrawerHandler() {
@Composable
override fun Contribute(onContribute: () -> Unit) {
val context = LocalContext.current
MenuEntry(
icon = Icons.Default.VolunteerActivism,
title = stringResource(R.string.earn_badges),
onClick = {
context.startActivity(Intent(context, EarnBadgesActivity::class.java))
}
)
}
@Composable
@Preview
fun MenuEntries_Gplay_Preview() {
Column {
MenuEntries(SnackbarHostState())
}
}
override fun onBetaFeedback(
context: Context,
onShowSnackbar: (message: String, actionLabel: String, action: () -> Unit) -> Unit
) {
// use In-App Review API to submit private feedback
val manager = ReviewManagerFactory.create(context)
val request = manager.requestReviewFlow()
request.addOnCompleteListener { task ->
if (task.isSuccessful) {
logger.info("Launching in-app review flow")
if (context is Activity)
manager.launchReviewFlow(context, task.result)
// provide alternative for the case that the in-app review flow didn't show up
onShowSnackbar(
context.getString(R.string.nav_feedback_inapp_didnt_appear),
context.getString(R.string.nav_feedback_google_play),
{
if (!openInStore(context))
// couldn't open in store, fall back to email
super.onBetaFeedback(context, onShowSnackbar)
}
)
} else {
logger.log(Level.WARNING, "Couldn't start in-app review flow", task.exception)
openInStore(context)
}
}
}
private fun openInStore(context: Context): Boolean {
val intent = Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse("https://play.google.com/store/apps/details?id=${BuildConfig.APPLICATION_ID}")
setPackage("com.android.vending") // Google Play only (this is only for the gplay flavor)
}
return try {
context.startActivity(intent)
Toast.makeText(context, R.string.nav_feedback_scroll_to_reviews, Toast.LENGTH_LONG).show()
true
} catch (e: ActivityNotFoundException) {
// fall back to email
false
}
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.ui
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import at.bitfire.davdroid.R
import javax.inject.Inject
class GplayLicenseInfoProvider @Inject constructor() : AboutActivity.AppLicenseInfoProvider {
@Composable
override fun LicenseInfo() {
Text(stringResource(R.string.about_flavor_info))
}
@Composable
@Preview
fun LicenseInfo_Preview() {
LicenseInfo()
}
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.ui.icons
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
val BadgesIcons.Badge1UpExtralife: ImageVector
get() {
if (_Badge1UpExtralife != null) {
return _Badge1UpExtralife!!
}
_Badge1UpExtralife = ImageVector.Builder(
name = "Badge1UpExtralife",
defaultWidth = 24.dp,
defaultHeight = 24.dp,
viewportWidth = 24f,
viewportHeight = 24f,
autoMirror = true
).apply {
path(fill = SolidColor(Color(0xFF000000))) {
moveTo(10f, 19f)
verticalLineTo(19f)
curveTo(9.4f, 19f, 9f, 18.6f, 9f, 18f)
verticalLineTo(17f)
curveTo(9f, 16.5f, 9.4f, 16f, 10f, 16f)
verticalLineTo(16f)
curveTo(10.5f, 16f, 11f, 16.4f, 11f, 17f)
verticalLineTo(18f)
curveTo(11f, 18.6f, 10.6f, 19f, 10f, 19f)
moveTo(15f, 18f)
verticalLineTo(17f)
curveTo(15f, 16.5f, 14.6f, 16f, 14f, 16f)
verticalLineTo(16f)
curveTo(13.5f, 16f, 13f, 16.4f, 13f, 17f)
verticalLineTo(18f)
curveTo(13f, 18.5f, 13.4f, 19f, 14f, 19f)
verticalLineTo(19f)
curveTo(14.6f, 19f, 15f, 18.6f, 15f, 18f)
moveTo(22f, 12f)
curveTo(22f, 14.6f, 20.4f, 16.9f, 18f, 18.4f)
verticalLineTo(20f)
arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = true, 16f, 22f)
horizontalLineTo(8f)
arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = true, 6f, 20f)
verticalLineTo(18.4f)
curveTo(3.6f, 16.9f, 2f, 14.6f, 2f, 12f)
arcTo(10f, 10f, 0f, isMoreThanHalf = false, isPositiveArc = true, 12f, 2f)
arcTo(10f, 10f, 0f, isMoreThanHalf = false, isPositiveArc = true, 22f, 12f)
moveTo(7f, 10f)
curveTo(7f, 8.9f, 6.4f, 7.9f, 5.5f, 7.4f)
curveTo(4.5f, 8.7f, 4f, 10.3f, 4f, 12f)
curveTo(4f, 12.3f, 4f, 12.7f, 4.1f, 13f)
curveTo(5.7f, 12.9f, 7f, 11.6f, 7f, 10f)
moveTo(9f, 9f)
curveTo(9f, 10.7f, 10.3f, 12f, 12f, 12f)
curveTo(13.7f, 12f, 15f, 10.7f, 15f, 9f)
curveTo(15f, 7.3f, 13.7f, 6f, 12f, 6f)
curveTo(10.3f, 6f, 9f, 7.3f, 9f, 9f)
moveTo(16f, 20f)
verticalLineTo(15.5f)
curveTo(14.8f, 15.2f, 13.4f, 15f, 12f, 15f)
curveTo(10.6f, 15f, 9.2f, 15.2f, 8f, 15.5f)
verticalLineTo(20f)
horizontalLineTo(16f)
moveTo(19.9f, 13f)
curveTo(20f, 12.7f, 20f, 12.3f, 20f, 12f)
curveTo(20f, 10.3f, 19.5f, 8.7f, 18.5f, 7.4f)
curveTo(17.6f, 7.9f, 17f, 8.9f, 17f, 10f)
curveTo(17f, 11.6f, 18.3f, 12.9f, 19.9f, 13f)
close()
}
}.build()
return _Badge1UpExtralife!!
}
@Suppress("ObjectPropertyName")
private var _Badge1UpExtralife: ImageVector? = null

View File

@@ -0,0 +1,57 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.ui.icons
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
val BadgesIcons.BadgeCoffee: ImageVector
get() {
if (_BadgeCoffee != null) {
return _BadgeCoffee!!
}
_BadgeCoffee = ImageVector.Builder(
name = "BadgeCoffee",
defaultWidth = 24.dp,
defaultHeight = 24.dp,
viewportWidth = 24f,
viewportHeight = 24f,
autoMirror = true
).apply {
path(fill = SolidColor(Color(0xFF000000))) {
moveTo(20f, 3f)
lineTo(4f, 3f)
verticalLineToRelative(10f)
curveToRelative(0f, 2.21f, 1.79f, 4f, 4f, 4f)
horizontalLineToRelative(6f)
curveToRelative(2.21f, 0f, 4f, -1.79f, 4f, -4f)
verticalLineToRelative(-3f)
horizontalLineToRelative(2f)
curveToRelative(1.11f, 0f, 2f, -0.9f, 2f, -2f)
lineTo(22f, 5f)
curveToRelative(0f, -1.11f, -0.89f, -2f, -2f, -2f)
close()
moveTo(20f, 8f)
horizontalLineToRelative(-2f)
lineTo(18f, 5f)
horizontalLineToRelative(2f)
verticalLineToRelative(3f)
close()
moveTo(4f, 19f)
horizontalLineToRelative(16f)
verticalLineToRelative(2f)
lineTo(4f, 21f)
close()
}
}.build()
return _BadgeCoffee!!
}
@Suppress("ObjectPropertyName")
private var _BadgeCoffee: ImageVector? = null

View File

@@ -0,0 +1,62 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.ui.icons
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
val BadgesIcons.BadgeCupcake: ImageVector
get() {
if (_BadgeCupcake != null) {
return _BadgeCupcake!!
}
_BadgeCupcake = ImageVector.Builder(
name = "BadgeCupcake",
defaultWidth = 24.dp,
defaultHeight = 24.dp,
viewportWidth = 24f,
viewportHeight = 24f
).apply {
path(fill = SolidColor(Color(0xFF000000))) {
moveTo(12f, 1.5f)
arcTo(2.5f, 2.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 14.5f, 4f)
arcTo(2.5f, 2.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 12f, 6.5f)
arcTo(2.5f, 2.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 9.5f, 4f)
arcTo(2.5f, 2.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 12f, 1.5f)
moveTo(15.87f, 5f)
curveTo(18f, 5f, 20f, 7f, 20f, 9f)
curveTo(22.7f, 9f, 22.7f, 13f, 20f, 13f)
horizontalLineTo(4f)
curveTo(1.3f, 13f, 1.3f, 9f, 4f, 9f)
curveTo(4f, 7f, 6f, 5f, 8.13f, 5f)
curveTo(8.57f, 6.73f, 10.14f, 8f, 12f, 8f)
curveTo(13.86f, 8f, 15.43f, 6.73f, 15.87f, 5f)
moveTo(5f, 15f)
horizontalLineTo(8f)
lineTo(9f, 22f)
horizontalLineTo(7f)
lineTo(5f, 15f)
moveTo(10f, 15f)
horizontalLineTo(14f)
lineTo(13f, 22f)
horizontalLineTo(11f)
lineTo(10f, 15f)
moveTo(16f, 15f)
horizontalLineTo(19f)
lineTo(17f, 22f)
horizontalLineTo(15f)
lineTo(16f, 15f)
close()
}
}.build()
return _BadgeCupcake!!
}
@Suppress("ObjectPropertyName")
private var _BadgeCupcake: ImageVector? = null

View File

@@ -0,0 +1,65 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.ui.icons
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
val BadgesIcons.BadgeDavx5Decade: ImageVector
get() {
if (_BadgeDavx5Decade != null) {
return _BadgeDavx5Decade!!
}
_BadgeDavx5Decade = ImageVector.Builder(
name = "BadgeDavx5Decade",
defaultWidth = 24.dp,
defaultHeight = 24.dp,
viewportWidth = 24f,
viewportHeight = 24f,
autoMirror = true
).apply {
path(fill = SolidColor(Color(0xFF000000))) {
moveTo(14f, 9f)
horizontalLineTo(16f)
verticalLineTo(15f)
horizontalLineTo(14f)
verticalLineTo(9f)
moveTo(21f, 5f)
verticalLineTo(19f)
curveTo(21f, 20.11f, 20.11f, 21f, 19f, 21f)
horizontalLineTo(5f)
arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = true, 3f, 19f)
verticalLineTo(5f)
arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = true, 5f, 3f)
horizontalLineTo(19f)
curveTo(20.11f, 3f, 21f, 3.9f, 21f, 5f)
moveTo(10f, 7f)
horizontalLineTo(6f)
verticalLineTo(9f)
horizontalLineTo(8f)
verticalLineTo(17f)
horizontalLineTo(10f)
verticalLineTo(7f)
moveTo(18f, 9f)
arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = false, 16f, 7f)
horizontalLineTo(14f)
arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = false, 12f, 9f)
verticalLineTo(15f)
curveTo(12f, 16.11f, 12.9f, 17f, 14f, 17f)
horizontalLineTo(16f)
curveTo(17.11f, 17f, 18f, 16.11f, 18f, 15f)
verticalLineTo(9f)
close()
}
}.build()
return _BadgeDavx5Decade!!
}
@Suppress("ObjectPropertyName")
private var _BadgeDavx5Decade: ImageVector? = null

View File

@@ -0,0 +1,42 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.ui.icons
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
val BadgesIcons.BadgeEnergyBooster: ImageVector
get() {
if (_BadgeEnergyBooster != null) {
return _BadgeEnergyBooster!!
}
_BadgeEnergyBooster = ImageVector.Builder(
name = "BadgeEnergyBooster",
defaultWidth = 24.dp,
defaultHeight = 24.dp,
viewportWidth = 24f,
viewportHeight = 24f,
autoMirror = true
).apply {
path(fill = SolidColor(Color(0xFF000000))) {
moveTo(11f, 15f)
horizontalLineTo(6f)
lineTo(13f, 1f)
verticalLineTo(9f)
horizontalLineTo(18f)
lineTo(11f, 23f)
verticalLineTo(15f)
close()
}
}.build()
return _BadgeEnergyBooster!!
}
@Suppress("ObjectPropertyName")
private var _BadgeEnergyBooster: ImageVector? = null

View File

@@ -0,0 +1,70 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.ui.icons
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
val BadgesIcons.BadgeLifeBuoy: ImageVector
get() {
if (_BadgeLifeBuoy != null) {
return _BadgeLifeBuoy!!
}
_BadgeLifeBuoy = ImageVector.Builder(
name = "BadgeLifeBuoy",
defaultWidth = 24.dp,
defaultHeight = 24.dp,
viewportWidth = 24f,
viewportHeight = 24f,
autoMirror = true
).apply {
path(fill = SolidColor(Color(0xFF000000))) {
moveTo(12f, 2f)
curveTo(6.48f, 2f, 2f, 6.48f, 2f, 12f)
curveToRelative(0f, 5.52f, 4.48f, 10f, 10f, 10f)
reflectiveCurveToRelative(10f, -4.48f, 10f, -10f)
curveTo(22f, 6.48f, 17.52f, 2f, 12f, 2f)
close()
moveTo(19.46f, 9.12f)
lineToRelative(-2.78f, 1.15f)
curveToRelative(-0.51f, -1.36f, -1.58f, -2.44f, -2.95f, -2.94f)
lineToRelative(1.15f, -2.78f)
curveTo(16.98f, 5.35f, 18.65f, 7.02f, 19.46f, 9.12f)
close()
moveTo(12f, 15f)
curveToRelative(-1.66f, 0f, -3f, -1.34f, -3f, -3f)
reflectiveCurveToRelative(1.34f, -3f, 3f, -3f)
reflectiveCurveToRelative(3f, 1.34f, 3f, 3f)
reflectiveCurveTo(13.66f, 15f, 12f, 15f)
close()
moveTo(9.13f, 4.54f)
lineToRelative(1.17f, 2.78f)
curveToRelative(-1.38f, 0.5f, -2.47f, 1.59f, -2.98f, 2.97f)
lineTo(4.54f, 9.13f)
curveTo(5.35f, 7.02f, 7.02f, 5.35f, 9.13f, 4.54f)
close()
moveTo(4.54f, 14.87f)
lineToRelative(2.78f, -1.15f)
curveToRelative(0.51f, 1.38f, 1.59f, 2.46f, 2.97f, 2.96f)
lineToRelative(-1.17f, 2.78f)
curveTo(7.02f, 18.65f, 5.35f, 16.98f, 4.54f, 14.87f)
close()
moveTo(14.88f, 19.46f)
lineToRelative(-1.15f, -2.78f)
curveToRelative(1.37f, -0.51f, 2.45f, -1.59f, 2.95f, -2.97f)
lineToRelative(2.78f, 1.17f)
curveTo(18.65f, 16.98f, 16.98f, 18.65f, 14.88f, 19.46f)
close()
}
}.build()
return _BadgeLifeBuoy!!
}
@Suppress("ObjectPropertyName")
private var _BadgeLifeBuoy: ImageVector? = null

View File

@@ -0,0 +1,53 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.ui.icons
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
val BadgesIcons.BadgeLocalBar: ImageVector
get() {
if (_BadgeLocalBar != null) {
return _BadgeLocalBar!!
}
_BadgeLocalBar = ImageVector.Builder(
name = "BadgeLocalBar",
defaultWidth = 24.dp,
defaultHeight = 24.dp,
viewportWidth = 24f,
viewportHeight = 24f
).apply {
path(fill = SolidColor(Color(0xFF000000))) {
moveTo(21f, 5f)
verticalLineTo(3f)
horizontalLineTo(3f)
verticalLineToRelative(2f)
lineToRelative(8f, 9f)
verticalLineToRelative(5f)
horizontalLineTo(6f)
verticalLineToRelative(2f)
horizontalLineToRelative(12f)
verticalLineToRelative(-2f)
horizontalLineToRelative(-5f)
verticalLineToRelative(-5f)
lineToRelative(8f, -9f)
close()
moveTo(7.43f, 7f)
lineTo(5.66f, 5f)
horizontalLineToRelative(12.69f)
lineToRelative(-1.78f, 2f)
horizontalLineTo(7.43f)
close()
}
}.build()
return _BadgeLocalBar!!
}
@Suppress("ObjectPropertyName")
private var _BadgeLocalBar: ImageVector? = null

View File

@@ -0,0 +1,60 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.ui.icons
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
val BadgesIcons.BadgeMedal: ImageVector
get() {
if (_BadgeMedal != null) {
return _BadgeMedal!!
}
_BadgeMedal = ImageVector.Builder(
name = "BadgeMedal",
defaultWidth = 24.dp,
defaultHeight = 24.dp,
viewportWidth = 24f,
viewportHeight = 24f,
autoMirror = true
).apply {
path(fill = SolidColor(Color(0xFF000000))) {
moveTo(17f, 10.43f)
verticalLineTo(2f)
horizontalLineTo(7f)
verticalLineToRelative(8.43f)
curveToRelative(0f, 0.35f, 0.18f, 0.68f, 0.49f, 0.86f)
lineToRelative(4.18f, 2.51f)
lineToRelative(-0.99f, 2.34f)
lineToRelative(-3.41f, 0.29f)
lineToRelative(2.59f, 2.24f)
lineTo(9.07f, 22f)
lineTo(12f, 20.23f)
lineTo(14.93f, 22f)
lineToRelative(-0.78f, -3.33f)
lineToRelative(2.59f, -2.24f)
lineToRelative(-3.41f, -0.29f)
lineToRelative(-0.99f, -2.34f)
lineToRelative(4.18f, -2.51f)
curveTo(16.82f, 11.11f, 17f, 10.79f, 17f, 10.43f)
close()
moveTo(13f, 12.23f)
lineToRelative(-1f, 0.6f)
lineToRelative(-1f, -0.6f)
verticalLineTo(3f)
horizontalLineToRelative(2f)
verticalLineTo(12.23f)
close()
}
}.build()
return _BadgeMedal!!
}
@Suppress("ObjectPropertyName")
private var _BadgeMedal: ImageVector? = null

View File

@@ -0,0 +1,78 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.ui.icons
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
val BadgesIcons.BadgeNinthAnniversary: ImageVector
get() {
if (_BadgeNinthAnniversary != null) {
return _BadgeNinthAnniversary!!
}
_BadgeNinthAnniversary = ImageVector.Builder(
name = "BadgeNinthAnniversary",
defaultWidth = 24.dp,
defaultHeight = 24.dp,
viewportWidth = 24f,
viewportHeight = 24f,
autoMirror = true
).apply {
path(fill = SolidColor(Color(0xFF000000))) {
moveTo(12f, 6f)
curveToRelative(1.11f, 0f, 2f, -0.9f, 2f, -2f)
curveToRelative(0f, -0.38f, -0.1f, -0.73f, -0.29f, -1.03f)
lineTo(12f, 0f)
lineToRelative(-1.71f, 2.97f)
curveToRelative(-0.19f, 0.3f, -0.29f, 0.65f, -0.29f, 1.03f)
curveToRelative(0f, 1.1f, 0.9f, 2f, 2f, 2f)
close()
moveTo(16.6f, 15.99f)
lineToRelative(-1.07f, -1.07f)
lineToRelative(-1.08f, 1.07f)
curveToRelative(-1.3f, 1.3f, -3.58f, 1.31f, -4.89f, 0f)
lineToRelative(-1.07f, -1.07f)
lineToRelative(-1.09f, 1.07f)
curveTo(6.75f, 16.64f, 5.88f, 17f, 4.96f, 17f)
curveToRelative(-0.73f, 0f, -1.4f, -0.23f, -1.96f, -0.61f)
lineTo(3f, 21f)
curveToRelative(0f, 0.55f, 0.45f, 1f, 1f, 1f)
horizontalLineToRelative(16f)
curveToRelative(0.55f, 0f, 1f, -0.45f, 1f, -1f)
verticalLineToRelative(-4.61f)
curveToRelative(-0.56f, 0.38f, -1.23f, 0.61f, -1.96f, 0.61f)
curveToRelative(-0.92f, 0f, -1.79f, -0.36f, -2.44f, -1.01f)
close()
moveTo(18f, 9f)
horizontalLineToRelative(-5f)
lineTo(13f, 7f)
horizontalLineToRelative(-2f)
verticalLineToRelative(2f)
lineTo(6f, 9f)
curveToRelative(-1.66f, 0f, -3f, 1.34f, -3f, 3f)
verticalLineToRelative(1.54f)
curveToRelative(0f, 1.08f, 0.88f, 1.96f, 1.96f, 1.96f)
curveToRelative(0.52f, 0f, 1.02f, -0.2f, 1.38f, -0.57f)
lineToRelative(2.14f, -2.13f)
lineToRelative(2.13f, 2.13f)
curveToRelative(0.74f, 0.74f, 2.03f, 0.74f, 2.77f, 0f)
lineToRelative(2.14f, -2.13f)
lineToRelative(2.13f, 2.13f)
curveToRelative(0.37f, 0.37f, 0.86f, 0.57f, 1.38f, 0.57f)
curveToRelative(1.08f, 0f, 1.96f, -0.88f, 1.96f, -1.96f)
lineTo(20.99f, 12f)
curveTo(21f, 10.34f, 19.66f, 9f, 18f, 9f)
close()
}
}.build()
return _BadgeNinthAnniversary!!
}
@Suppress("ObjectPropertyName")
private var _BadgeNinthAnniversary: ImageVector? = null

View File

@@ -0,0 +1,47 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.ui.icons
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
val BadgesIcons.BadgeOfflineBolt: ImageVector
get() {
if (_BadgeOfflineBolt != null) {
return _BadgeOfflineBolt!!
}
_BadgeOfflineBolt = ImageVector.Builder(
name = "BadgeOfflineBolt",
defaultWidth = 24.dp,
defaultHeight = 24.dp,
viewportWidth = 24f,
viewportHeight = 24f
).apply {
path(fill = SolidColor(Color(0xFF000000))) {
moveTo(12f, 2.02f)
curveToRelative(-5.51f, 0f, -9.98f, 4.47f, -9.98f, 9.98f)
reflectiveCurveToRelative(4.47f, 9.98f, 9.98f, 9.98f)
reflectiveCurveToRelative(9.98f, -4.47f, 9.98f, -9.98f)
reflectiveCurveTo(17.51f, 2.02f, 12f, 2.02f)
close()
moveTo(11.48f, 20f)
verticalLineToRelative(-6.26f)
horizontalLineTo(8f)
lineTo(13f, 4f)
verticalLineToRelative(6.26f)
horizontalLineToRelative(3.35f)
lineTo(11.48f, 20f)
close()
}
}.build()
return _BadgeOfflineBolt!!
}
@Suppress("ObjectPropertyName")
private var _BadgeOfflineBolt: ImageVector? = null

View File

@@ -0,0 +1,72 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.ui.icons
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
val BadgesIcons.BadgeSailboat: ImageVector
get() {
if (_BadgeSailboat != null) {
return _BadgeSailboat!!
}
_BadgeSailboat = ImageVector.Builder(
name = "BadgeSailboat",
defaultWidth = 24.dp,
defaultHeight = 24.dp,
viewportWidth = 24f,
viewportHeight = 24f
).apply {
path(fill = SolidColor(Color(0xFF000000))) {
moveTo(11f, 13.5f)
verticalLineTo(2f)
lineTo(3f, 13.5f)
horizontalLineTo(11f)
close()
moveTo(21f, 13.5f)
curveTo(21f, 6.5f, 14.5f, 1f, 12.5f, 1f)
curveToRelative(0f, 0f, 1f, 3f, 1f, 6.5f)
reflectiveCurveToRelative(-1f, 6f, -1f, 6f)
horizontalLineTo(21f)
close()
moveTo(22f, 15f)
horizontalLineTo(2f)
curveToRelative(0.31f, 1.53f, 1.16f, 2.84f, 2.33f, 3.73f)
curveTo(4.98f, 18.46f, 5.55f, 18.01f, 6f, 17.5f)
curveTo(6.73f, 18.34f, 7.8f, 19f, 9f, 19f)
reflectiveCurveToRelative(2.27f, -0.66f, 3f, -1.5f)
curveToRelative(0.73f, 0.84f, 1.8f, 1.5f, 3f, 1.5f)
reflectiveCurveToRelative(2.26f, -0.66f, 3f, -1.5f)
curveToRelative(0.45f, 0.51f, 1.02f, 0.96f, 1.67f, 1.23f)
curveTo(20.84f, 17.84f, 21.69f, 16.53f, 22f, 15f)
close()
moveTo(22f, 23f)
verticalLineToRelative(-2f)
horizontalLineToRelative(-1f)
curveToRelative(-1.04f, 0f, -2.08f, -0.35f, -3f, -1f)
curveToRelative(-1.83f, 1.3f, -4.17f, 1.3f, -6f, 0f)
curveToRelative(-1.83f, 1.3f, -4.17f, 1.3f, -6f, 0f)
curveToRelative(-0.91f, 0.65f, -1.96f, 1f, -3f, 1f)
horizontalLineTo(2f)
lineToRelative(0f, 2f)
horizontalLineToRelative(1f)
curveToRelative(1.03f, 0f, 2.05f, -0.25f, 3f, -0.75f)
curveToRelative(1.89f, 1f, 4.11f, 1f, 6f, 0f)
curveToRelative(1.89f, 1f, 4.11f, 1f, 6f, 0f)
horizontalLineToRelative(0f)
curveToRelative(0.95f, 0.5f, 1.97f, 0.75f, 3f, 0.75f)
horizontalLineTo(22f)
close()
}
}.build()
return _BadgeSailboat!!
}
@Suppress("ObjectPropertyName")
private var _BadgeSailboat: ImageVector? = null

View File

@@ -0,0 +1,7 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.ui.icons
object BadgesIcons

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Navigation drawer -->
<string name="nav_earn_badges">Подкрепете ни със значки</string>
<string name="nav_rate_us">Оценки в Google Play</string>
<string name="nav_feedback_inapp_didnt_appear">Оценката не се вижда в приложението?</string>
<string name="nav_feedback_google_play">Изпращане към Google Play</string>
<string name="nav_feedback_scroll_to_reviews">Прелистете до оценките, за да изпратите своя</string>
<!-- AboutActivity -->
<string name="about_flavor_info">Тази версия може да се разпространява само чрез Google Play.</string>
<!-- EarnBadgesActivity -->
<plurals name="you_earned_badges">
<item quantity="one">Получили сте значка, благодарим ви!</item>
<item quantity="other">Получили сте %d значки, благодарим ви!</item>
</plurals>
<string name="available_badges">Налични значки</string>
<string name="available_badges_empty">Значките не могат да бъдат заредени, съжаляваме. Влезли ли сте в Play Store?</string>
<string name="what_are_badges">Какво представляват значките?</string>
<string name="what_are_badges_title">Какво представляват значките?</string>
<string name="what_are_badges_body">Значките са еднократни плащания в приложението. Ще спечелите хубава малка значка и с нея ще можете да ни подкрепите във времето.</string>
<string name="why_badges_title">Защо DAVx5 предлага значки без функционалност?</string>
<string name="why_badges_body">DAVx5 наистина се разрасна през годините! Все още активно разработваме нови възможности, осигуряваме поддръжка и винаги обновяваме приложението за предстоящите издания на Андроид. Тези значки са еднократни плащания, при които можете просто да покажете подкрепата си, като ни почерпите кафе или две&#8230; или 10 :-) Желанието ни е да бъдем възможно най-отворени, така че никога и по никакъв начин няма да има нови неща, заключени зад плащане в приложението.</string>
<string name="button_buy_badge_free">БЕЗПЛАТНО</string>
<string name="button_buy_badge_bought">Благодаря!</string>
<string name="earn_badges">Печелете значки, за да ни подкрепите!</string>
<string name="billing_unavailable">Billing API е недостъпно</string>
<string name="network_problems">Проблеми с мрежата, опитайте по-късно.</string>
</resources>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Navigation drawer -->
<string name="nav_earn_badges">Ajudeu-nos amb insígnies</string>
<string name="nav_rate_us">Ressenya al Google Play</string>
<string name="nav_feedback_inapp_didnt_appear">La revisió a l\'aplicació no ha aparegut?</string>
<string name="nav_feedback_google_play">Envia en Google Play</string>
<string name="nav_feedback_scroll_to_reviews">Desplaceu-vos a la secció de revisió per a enviar comentaris</string>
<!-- AboutActivity -->
<string name="about_flavor_info">Aquesta versió només pot ser distribuïda a través de Google Play.</string>
<!-- EarnBadgesActivity -->
<plurals name="you_earned_badges">
<item quantity="one">Heu guanyat una insígnia, gràcies!</item>
<item quantity="other">Heu guanyat %d insígnies, gràcies!</item>
</plurals>
<string name="available_badges">Insígnies disponibles</string>
<string name="available_badges_empty">No s\'han pogut carregar les insígnies. Heu iniciat la sessió a la Play Store?</string>
<string name="what_are_badges">Què són les insígnies?</string>
<string name="what_are_badges_title">Què són les insígnies?</string>
<string name="what_are_badges_body">Les insígnies són pagaments d\'un sol ús en l\'aplicació. Guanyareu una petita insígnia i amb ella podreu ajudar-nos al llarg del temps.</string>
<string name="why_badges_title">Perquè DAVx5 ofereix insígnies sense característiques?</string>
<string name="why_badges_body">DAVx5 ha crescut realment amb els anys! Encara estem desenvolupant activament característiques noves, proporcionant suport i sempre actualitzem l\'aplicació per a les properes versions d\'Android. Aquestes insígnies són pagaments puntuals en els quals simplement podeu mostrar el vostre suport comprant-nos un cafè, o dos&#8230; o 10 :-) Volem ser el més oberts possible, així que mai hi haurà noves coses bloquejades per a un pagament en l\'aplicació.</string>
<string name="button_buy_badge_free">Lliure</string>
<string name="button_buy_badge_bought">Gràcies!</string>
<string name="earn_badges">Guanyeu insígnies per ajudar-nos!</string>
<string name="billing_unavailable">L\'API de facturació no està disponible</string>
<string name="network_problems">Hi ha problemes de xarxa. Torneu-ho a provar més tard.</string>
</resources>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Navigation drawer -->
<string name="nav_earn_badges">Podpořte nás odznáčky</string>
<string name="nav_rate_us">Napsat recenzi v Google Play</string>
<!-- EarnBadgesActivity -->
<plurals name="you_earned_badges">
<item quantity="one">Získali jste odznáček, děkujeme!</item>
<item quantity="few">Získali jste %d odznáčky, děkujeme!</item>
<item quantity="many">Získali jste %d odznáčků, děkujeme!</item>
<item quantity="other">Získali jste %d odznáčků, děkujeme!</item>
</plurals>
<string name="available_badges">Odznáčky k dispozici</string>
<string name="available_badges_empty">Omlouváme se, nelze načíst odznáčky. Jste přihlášeni do obchodu Google Play?</string>
<string name="what_are_badges">Co jsou odznáčky?</string>
<string name="what_are_badges_title">Co jsou odznáčky?</string>
<string name="what_are_badges_body">Odznáčky jsou jednoduché jednorázové platby v aplikaci. Získáte hezký malý odznáček a pomocí něho nás v čase můžete podporovat.</string>
<string name="why_badges_title">Proč DAVx5 nabízí odznáčky bez funkcí?</string>
<string name="why_badges_body">DAVx5 v průběhu let značně vyrostl! Stále aktivně vyvíjíme nové funkce, poskytujeme podporu a vždy aktualizujeme aplikaci pro nejnovější verze Androidu. Tyto odznáčky jsou jednorázové platby, kterými nás můžete podpořit koupením kávy, nebo dvou&#8230; nebo 10 :-) Chceme mít co nejotevřenější kód, takže nové funkce nikdy nebudou zamčeny za další platby.</string>
<string name="button_buy_badge_free">ZDARMA</string>
<string name="button_buy_badge_bought">Děkujeme!</string>
<string name="earn_badges">Získejte odznáčky pro naši podporu!</string>
</resources>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Navigation drawer -->
<string name="nav_earn_badges">Støt os med emblemer</string>
<string name="nav_rate_us">Bedøm i Google Play</string>
<!-- EarnBadgesActivity -->
<plurals name="you_earned_badges">
<item quantity="one">Du har fortjent et emblem, tak!</item>
<item quantity="other">Du har optjent %d emblemer, tak!</item>
</plurals>
<string name="available_badges">Mulige emblemer</string>
<string name="available_badges_empty">Beklager, kunne ikke indlæse emblemer. Er du logget ind i Play Store?</string>
<string name="what_are_badges">Hvad er emblemer?</string>
<string name="what_are_badges_title">Hvad er emblemer?</string>
<string name="what_are_badges_body">Badges er enkle engangsbetalinger i appen. Du optjener et fint lille emblem, og støtter os dermed.</string>
<string name="why_badges_title">Hvorfor tilbyder DAVx5 emblemer uden funktioner?</string>
<string name="why_badges_body">DAVx5 er virkelig vokset gennem årene! Vi udvikler stadig aktivt nye funktioner, yder support, og vi opdaterer altid appen til kommende Android-versioner. Disse badges er engangsbetalinger, hvor du blot kan vise din støtte ved at købe os en kaffe eller to&#8230; eller 10 :-) Vi prøver at være så åbne som muligt, så der vil aldrig være nye funktioner, der er gemt bag betaling i appen.</string>
<string name="button_buy_badge_free">GRATIS</string>
<string name="button_buy_badge_bought">Tak!</string>
<string name="earn_badges">Optjen emblemer for at støtte os!</string>
</resources>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Navigation drawer -->
<string name="nav_earn_badges">Unterstütze uns mit Badges</string>
<string name="nav_rate_us">In Google Play bewerten</string>
<string name="nav_feedback_inapp_didnt_appear">Rezension innerhalb der App erschien nicht?</string>
<string name="nav_feedback_google_play">In Google Play senden</string>
<string name="nav_feedback_scroll_to_reviews">Zum Rezensionsabschnitt blättern, um Feedback zu übermitteln</string>
<!-- AboutActivity -->
<string name="about_flavor_info">Diese Version ist ausschließlich zur Verteilung über Google Play bestimmt.</string>
<!-- EarnBadgesActivity -->
<plurals name="you_earned_badges">
<item quantity="one">Du hast dir Abzeichen verdient, danke!</item>
<item quantity="other">Sie haben sich %d Abzeichen verdient, vielen Dank!</item>
</plurals>
<string name="available_badges">Verfügbare Abzeichen</string>
<string name="available_badges_empty">Tut mir leid, ich konnte keine Abzeichen laden. Sind Sie im Play Store eingeloggt?</string>
<string name="what_are_badges">Was sind Abzeichen?</string>
<string name="what_are_badges_title">Was sind Abzeichen?</string>
<string name="what_are_badges_body">Abzeichen sind einfache In-App-Zahlungen, die nur einmalig fällig werden. Sie verdienen sich ein nettes kleines Abzeichen und können uns damit über einen längeren Zeitraum unterstützen.</string>
<string name="why_badges_title">Warum bietet DAVx5 funktionslose Abzeichen an?</string>
<string name="why_badges_body">DAVx5 ist im Laufe der Jahre wirklich gewachsen! Wir entwickeln immer noch aktiv neue Funktionen, bieten Support und aktualisieren die App immer für kommende Android-Versionen. Diese Badges sind einmalige Zahlungen, mit denen Sie uns einfach Ihre Unterstützung zeigen können, indem Sie uns einen Kaffee kaufen, oder zwei&#8230; oder 10 :-) Wir wollen so offen wie möglich sein, daher wird es niemals neue Dinge geben, die an die In-App-Zahlung gebunden sind.</string>
<string name="button_buy_badge_free">GRATIS</string>
<string name="button_buy_badge_bought">Danke!</string>
<string name="earn_badges">Sammle Abzeichen, um uns zu unterstützen!</string>
<string name="billing_unavailable">Keine API zur Abrechnung vorhanden</string>
<string name="network_problems">Netzwerkprobleme, bitte später nochmal probieren.</string>
</resources>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Navigation drawer -->
<string name="nav_earn_badges">Υποστηρίξτε μας με τα εμβλήματα</string>
<string name="nav_rate_us">Κριτική στο Google Play</string>
<!-- EarnBadgesActivity -->
<plurals name="you_earned_badges">
<item quantity="one">Κερδίσατε ένα σήμα, σας ευχαριστούμε!</item>
<item quantity="other">Κερδίσατε %d εμβλήματα, σας ευχαριστούμε!</item>
</plurals>
<string name="available_badges">Διαθέσιμα εμβλήματα</string>
<string name="available_badges_empty">Συγγνώμη, αδυναμία φόρτωσης εμβλημάτων. Έχετε συνδεθεί στο Play Store;</string>
<string name="what_are_badges">Τι είναι τα εμβλήματα;</string>
<string name="what_are_badges_title">Τι είναι τα εμβλήματα;</string>
<string name="what_are_badges_body">Τα εμβλήματα είναι απλές πληρωμές μίας χρήσης εντός της εφαρμογής. Θα κερδίσετε ένα ωραίο μικρό έμβλημα και με αυτό μπορείτε να μας υποστηρίξετε με την πάροδο του χρόνου.</string>
<string name="why_badges_title">Γιατί το DAVx5 προσφέρει εμβλήματα χωρίς χαρακτηριστικά;</string>
<string name="why_badges_body">Το DAVx5 έχει πραγματικά μεγαλώσει με τα χρόνια! Εξακολουθούμε να αναπτύσσουμε ενεργά νέα χαρακτηριστικά, να παρέχουμε υποστήριξη και να ενημερώνουμε πάντα την εφαρμογή για τις επερχόμενες εκδόσεις Android. Αυτά τα εμβλήματα είναι εφάπαξ πληρωμές όπου μπορείτε απλά να δείξετε την υποστήριξή σας αγοράζοντας μας έναν καφέ, ή δύο&#8230; ή 10 :-) Θέλουμε να είμαστε όσο το δυνατόν πιο ανοιχτοί, οπότε δεν θα υπάρξουν ποτέ-ποτέ νέα πράγματα κλειδωμένα με πληρωμή εντός της εφαρμογής.</string>
<string name="button_buy_badge_free">ΔΩΡΕΑΝ</string>
<string name="button_buy_badge_bought">Σας ευχαριστούμε!</string>
<string name="earn_badges">Κερδίστε εμβλήματα για να μας υποστηρίξετε!</string>
</resources>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Navigation drawer -->
<string name="nav_earn_badges">Support us with badges</string>
<string name="nav_rate_us">Review in Google Play</string>
<!-- EarnBadgesActivity -->
<plurals name="you_earned_badges">
<item quantity="one">You\'ve earned a badge, thank you!</item>
<item quantity="other">You have earned %d badges, thank you!</item>
</plurals>
<string name="available_badges">Available badges</string>
<string name="available_badges_empty">Sorry, could not load badges. Are you logged in to the Play Store?</string>
<string name="what_are_badges">What are badges?</string>
<string name="what_are_badges_title">What are badges?</string>
<string name="what_are_badges_body">Badges are simple in-app one-time-payments. You will earn a nice little badge and with it you can support us over time.</string>
<string name="why_badges_title">Why does DAVx5 offer feature-free badges?</string>
<string name="why_badges_body">DAVx5 has really grown over the years! We are still actively developing new features, providing support and we always update the app for upcoming Android versions. These badges are one-time payments where you can simply show your support by buying us a coffee, or two&#8230; or 10 :-) We want to be as open as possible, so there will never-ever be any new stuff locked to in-app payment.</string>
<string name="button_buy_badge_free">FREE</string>
<string name="button_buy_badge_bought">Thank you!</string>
<string name="earn_badges">Earn badges to support us!</string>
</resources>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Navigation drawer -->
<string name="nav_earn_badges">Danos soporte con medallas</string>
<string name="nav_rate_us">Valora en Google Play</string>
<!-- EarnBadgesActivity -->
<plurals name="you_earned_badges">
<item quantity="one">Has ganado una medalla, gracias!</item>
<item quantity="many">Has ganado %d medallas, gracias!</item>
<item quantity="other">Has ganado %d medallas, gracias!</item>
</plurals>
<string name="available_badges">Medallas disponibles</string>
<string name="available_badges_empty">Perdona, no se han podido cargar las medallas. Tienes la sesión iniciada en el Play Store?</string>
<string name="what_are_badges">Qué son las medallas?</string>
<string name="what_are_badges_title">Qué son las medallas?</string>
<string name="what_are_badges_body">Las medallas son transacciones en la aplicación de un solo uso. Tendrás una pequeña medalla, y nos podrás dar soporte a lo largo del tiempo.</string>
<string name="why_badges_title">Por qué DAVx5 ofrece medallas sin funciones?</string>
<string name="why_badges_body">DAVx5 ha crecido mucho durante los años! Aún estamos desarrollando nuevas funciones continuamente, dando soporte, y actualizando la app para nuevas versiones de Android. Estas medallas son pagos únicos, con los que puedes mostrar soporte comprándonos un café, o dos&#8230; o 10 :-) Queremos ser lo más abiertos posible, así que nunca habrán nuevas funciones bloqueadas con microtransacciones.</string>
<string name="button_buy_badge_free">GRATIS</string>
<string name="button_buy_badge_bought">Gracias!</string>
<string name="earn_badges">Obtén medallas para darnos soporte!</string>
</resources>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Navigation drawer -->
<string name="nav_earn_badges">Toeta meid reklaamsiltidega</string>
<string name="nav_rate_us">Koosta arvustus Google Plays</string>
<string name="nav_feedback_inapp_didnt_appear">Rakenduse-sisest arvustust pole näha?</string>
<string name="nav_feedback_google_play">Saada Google Plays</string>
<string name="nav_feedback_scroll_to_reviews">Tagasiside koostamiseks keri arvustuste lõiguni</string>
<!-- AboutActivity -->
<string name="about_flavor_info">Seda versiooni on võimalik levitada Google Play kaudu.</string>
<!-- EarnBadgesActivity -->
<plurals name="you_earned_badges">
<item quantity="one">Suur tänu, sa oled teeninud ühe reklaamsildi!</item>
<item quantity="other">Suur tänu, sa oled teeninud %d reklaamsilti!</item>
</plurals>
<string name="available_badges">Saadavalolevad reklaamsildid</string>
<string name="available_badges_empty">Vabandust, reklaamsiltide laadimine ei õnnestunud. Kas sa ikka oled Play Store\'i sisse loginud?</string>
<string name="what_are_badges">Mis on reklaamsildid?</string>
<string name="what_are_badges_title">Mis on reklaamsildid?</string>
<string name="what_are_badges_body">Reklaamsildid on lihtsad ühekordsed rakendusesisesed maksed. Sa teenid lihtsa ja toreda reklaamsildi ja sellega saad meid rahaliselt toetada.</string>
<string name="why_badges_title">Miks pakub DAVx⁵ funktsionaalsuseta reklaamsilte?</string>
<string name="why_badges_body">DAVx⁵ on aastate jooksul tõesti kasvanud! Me oleme seda jätkuvalt arendamas, tagame kasutajatoe ning alati kohandate teda järgmise uue Androidi versiooni jaoks. Need reklaamsildid on ühekordsed maksed, millega saad meid toetada, ostes ühe kohvi või kaks või kasvõi 10 :-) Me soovime olla võimalikult avatud ja rakendusesisestes maksetes ei saa kunagi olema midagi muud.</string>
<string name="button_buy_badge_free">TASUTA</string>
<string name="button_buy_badge_bought">Suur tänu!</string>
<string name="earn_badges">Meie toetamiseks teeni reklaamsilte!</string>
<string name="billing_unavailable">Billing API pole saadaval</string>
<string name="network_problems">Probleem võrguühendusega, palun proovi hiljem uuesti.</string>
</resources>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Navigation drawer -->
<string name="nav_earn_badges">Lagundu bereizgarriekin</string>
<string name="nav_rate_us">Eman iritzia Google Play-en</string>
<string name="nav_feedback_inapp_didnt_appear">Ez da aplikazioko berrikuspenik agertu?</string>
<string name="nav_feedback_google_play">Bidali Google Play-n</string>
<string name="nav_feedback_scroll_to_reviews">Joan berrikuspen atalera iritzia bidaltzeko</string>
<!-- AboutActivity -->
<string name="about_flavor_info">Bertsio hau Google Play bidez soilik banatzeko aukera dago.</string>
<!-- EarnBadgesActivity -->
<plurals name="you_earned_badges">
<item quantity="one"> Bereizgarri bat irabazi dituzu, eskerrik asko!</item>
<item quantity="other">%d bereizgarri irabazi dituzu, eskerrik asko!</item>
</plurals>
<string name="available_badges">Bereizgarri eskuragarriak</string>
<string name="available_badges_empty">Sentitzen dugu, ezin izan dira bereizgarriak kargatu. Saioa hasi duzu Play Store-n?</string>
<string name="what_are_badges">Zer dira bereizgarriak</string>
<string name="what_are_badges_title">Zer dira bereizgarriak</string>
<string name="what_are_badges_body">Bereizgarriak aplikazioan egindako ordainketa bakun sinpleak dira. Bereizgarri bat irabaziko duzu eta honekin lagundu ahal diguzu denboran zehar.</string>
<string name="why_badges_title">Zergatik DAVx5-ek ezaugarririk gabeko bereizgarriak ematen ditu?</string>
<string name="why_badges_body">DAVx5 asko hazi egin da urteekin! Funtzionalitate berriak garatzen jarraitzen ditugu, laguntza ematen, eta aplikazioa eguneratzen dugu hurrengo Android bertsioetarako. Bereizgarri horiek ordainketa puntualak dira, eta kafe bat, edo bi&#8230; edo 10 :-) erostearekin proiektua babesten duzula erakusten diguzu. Ahalik eta irekien izan nahi gara eta, beraz, inoiz ez da gauza berririk egongo aplikazioan ordainketarik behar duenik.</string>
<string name="button_buy_badge_free">DOAN</string>
<string name="button_buy_badge_bought">Eskerrik asko!</string>
<string name="earn_badges">Irabazi bereizgarriak laguntzeko!</string>
<string name="billing_unavailable">Fakturazio APIa ez dago erabilgarri</string>
<string name="network_problems">Sareko arazoak, saiatu berriro geroago.</string>
</resources>

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Navigation drawer -->
<string name="nav_earn_badges">Soutenez-nous avec des badges</string>
<string name="nav_rate_us">Avis sur Google Play</string>
<string name="available_badges">Badges disponibles</string>
<string name="available_badges_empty">Désolé, impossible de charger les badges. Êtes-vous connecté au Play Store ?</string>
<string name="what_are_badges">Qu\'est-ce que sont les badges ?</string>
<string name="what_are_badges_title">Qu\'est-ce que sont les badges ?</string>
<string name="what_are_badges_body">Les badges sont des paiements unitaires dans l\'application. Vous gagnerez un petit badge sympathique, qui permet de nous soutenir dans le temps.</string>
<string name="why_badges_title">Pourquoi DAVx5 propose-t-il des badges sans fonctionnalité ?</string>
<string name="why_badges_body">DAVx5 a considérablement grandi au fil des années ! Nous continions à développer activement de nouvelles fonctionnalités, faire du support et nous mettrons toujours à jour l\'application pour les prochaines versions d\'Android. Ces badges sont un paiement unitaire, mais vous pouvez aussi nous montrer votre soutien en nous payant un café ou 2 ; ou 10 :-) Nous voulons rester open-source autant que possible, il n\'y aura jamais de fonctionnalités verrouillée, nécessitant de payer pour les débloquer.</string>
<string name="button_buy_badge_free">GRATUIT</string>
<string name="button_buy_badge_bought">Merci !</string>
<string name="earn_badges">Acheter des badges pour nous soutenir !</string>
</resources>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Navigation drawer -->
<string name="nav_earn_badges">Axúdanos con insignias</string>
<string name="nav_rate_us">Recensión en Google Play</string>
<string name="nav_feedback_inapp_didnt_appear">Non aparece a recensión na propia app?</string>
<string name="nav_feedback_google_play">Enviar en Google Play</string>
<string name="nav_feedback_scroll_to_reviews">Ir á sección de recensións para enviar a túa experiencia</string>
<!-- AboutActivity -->
<string name="about_flavor_info">Esta versión só se pode distribuír en Google Play.</string>
<!-- EarnBadgesActivity -->
<plurals name="you_earned_badges">
<item quantity="one">Gañaches unha insignia, grazas!</item>
<item quantity="other">Gañaches %d insignias, grazas!</item>
</plurals>
<string name="available_badges">Insignias dispoñibles</string>
<string name="available_badges_empty">Lamentámolo, pero non cargaron as insignias. Iniciaches sesión na Play Store?</string>
<string name="what_are_badges">Que son as insignias?</string>
<string name="what_are_badges_title">Que son as insignias?</string>
<string name="what_are_badges_body">As insignias son simples pagamentos únicos dentro da app. Gañarás unha pequena insignia coa que nos axudarás.</string>
<string name="why_badges_title">Por que DAVx5 ofrece insignias sen recompensa?</string>
<string name="why_badges_body">DAVx5 medrou moito nos últimos anos! Seguimos co seu desenvolvemento de xeito activo, proporcionando axuda e actualizando a app para as vindeiras versións de Android. Estas insignias son pagamentos únicos cos que mostras o teu apoio convidándonos a un café, ou two&#8230; ou 10 :-) Queremos ser o máis transparentes posible, polo que nunca engadiremos funcións que estén detrás dun valado de pagamento.</string>
<string name="button_buy_badge_free">DE BALDE</string>
<string name="button_buy_badge_bought">Grazas!</string>
<string name="earn_badges">Consigue insignias para axudarnos!</string>
<string name="billing_unavailable">API de pagos non dispoñible</string>
<string name="network_problems">Problemas coa rede, inténtao máis tarede.</string>
</resources>

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Navigation drawer -->
<string name="nav_earn_badges">Támogatás jelvényekkel</string>
<string name="nav_rate_us">Értékelés a Google Playen</string>
<string name="nav_feedback_inapp_didnt_appear">Az alkalmazáson belüli értékelés nem jelent meg?</string>
<string name="nav_feedback_google_play">Küldés a Google Playen</string>
<string name="nav_feedback_scroll_to_reviews">Görgessen vissza a visszajelzési részhez, hogy visszajelzést küldjön</string>
<!-- AboutActivity -->
<string name="about_flavor_info">Ez a verzió csak a Google Playen keresztül terjeszthető</string>
<!-- EarnBadgesActivity -->
<plurals name="you_earned_badges">
<item quantity="one">Egy jelvényt szerzett, köszönjük!</item>
<item quantity="other">%d jelvényt szerzett, köszönjük!</item>
</plurals>
<string name="available_badges">Választható jelvények</string>
<string name="available_badges_empty">A jelvények betöltése nem sikerült. Be van jelentkezve a Play Áruházba?</string>
<string name="what_are_badges">Mik azok a jelvények?</string>
<string name="what_are_badges_title">Mik azok a jelvények?</string>
<string name="what_are_badges_body">A jelvények egyszerű alkalmazáson belüli egyszeri fizetések. Egy szép kis jelvényt fog szerezni, miközben támogat minket.</string>
<string name="why_badges_title">Miért kínál a DAVx5 funkció nélküli jelvényeket?</string>
<string name="why_badges_body">A DAVx5 az évek során nagyon sokat fejlődött! Még mindig aktívan fejlesztünk új funkciókat, támogatást nyújtunk, és mindig frissítjük az alkalmazást az újabb Android-verziókhoz. Ezek a jelvények egyszeri befizetések, ahol egyszerűen megmutathatja a támogatását azzal, hogy vesz nekünk egy kávét, vagy kettőt&#8230; vagy 10-et :-) A lehető legnyitottabbak szeretnénk lenni, így soha-soha nem lesz semmilyen új funkcionalitás, ami alkalmazáson belüli fizetéshez kötött.</string>
<string name="button_buy_badge_free">INGYENES</string>
<string name="button_buy_badge_bought">Köszönjük!</string>
<string name="earn_badges">Szerezzen jelvényeket és támogasson minket!</string>
<string name="billing_unavailable">A számlázási API nem érhető el</string>
<string name="network_problems">Hálózati problémák, próbálja újra később.</string>
</resources>

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="nav_rate_us">Recensisci su Google Play</string>
<string name="what_are_badges">Cosa sono i badge?</string>
<string name="what_are_badges_title">Cosa sono i badge?</string>
<string name="what_are_badges_body">I badge sono semplici pagamenti una tantum in-app. Guadagnerai un piccolo badge e puoi usarlo per supportarci nel tempo.</string>
<string name="button_buy_badge_free">GRATIS</string>
<string name="button_buy_badge_bought">Grazie!</string>
<string name="earn_badges">Guadagna badge per supportarci!</string>
</resources>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Navigation drawer -->
<string name="nav_earn_badges">バッジで私たちを支援</string>
<string name="nav_rate_us">Google Play でレビュー</string>
<string name="nav_feedback_inapp_didnt_appear">アプリ内レビューが表示されませんか?</string>
<string name="nav_feedback_google_play">Google Play で送信</string>
<string name="nav_feedback_scroll_to_reviews">レビューまでスクロールして、フィードバックを送信してください</string>
<!-- AboutActivity -->
<string name="about_flavor_info">このバージョンは Google Play 経由でのみ配布されます。</string>
<!-- EarnBadgesActivity -->
<plurals name="you_earned_badges">
<item quantity="other">%d 個のバッジをお持ちです。ありがとう!</item>
</plurals>
<string name="available_badges">利用できるバッジ</string>
<string name="available_badges_empty">バッジを読み込めませんでした。Play ストアにログインしていますか?</string>
<string name="what_are_badges">バッジとは何ですか?</string>
<string name="what_are_badges_title">バッジとは何ですか?</string>
<string name="what_are_badges_body">アプリ内で買い切りのバッジを購入できます。小さなバッジですが、私たちへの大きな支援になります。</string>
<string name="why_badges_title">DAVx5 のバッジを購入しても追加機能がないのはなぜですか?</string>
<string name="why_badges_body">DAVx5 は何年もの時を経て大きく成長してきました! 今も新たな機能の開発やサポートを継続しており、Android のバージョンアップにも常に対応してきました。これらのバッジはあなたが私たちを 1 杯、2 杯&#8230; 10 杯のコーヒーの分支援してくださったことを表します:-) 私たちはできるだけオープンでありたいと願っており、アプリ内購入が必要な新機能を導入することは - 今までもこれからも - ありません。</string>
<string name="button_buy_badge_free">無料</string>
<string name="button_buy_badge_bought">ありがとう!</string>
<string name="earn_badges">支援のためにバッジを入手!</string>
<string name="billing_unavailable">支払い API が利用できません</string>
<string name="network_problems">ネットワークに問題があります。後でもう一度お試しください。</string>
</resources>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Navigation drawer -->
<string name="nav_earn_badges">დაგვიჭირეთ მხარი ბეიჯებით</string>
<string name="nav_rate_us">Google Play-ში შეფასება</string>
<!-- EarnBadgesActivity -->
<plurals name="you_earned_badges">
<item quantity="one">თქვენ მიიღეთ ბეიჯი, გმადლობთ!</item>
<item quantity="other">თქვენ მიიღეთ %d ბეიჯი, გმადლობთ!</item>
</plurals>
<string name="available_badges">ხელმისაწვდომი ბეიჯები</string>
<string name="available_badges_empty">უკაცრავად, ბეიჯები ვერ ჩაიტვირთა. შესული ხართ Play მაღაზიაში?</string>
<string name="what_are_badges">რა არის ბეიჯები?</string>
<string name="what_are_badges_title">რა არის ბეიჯები?</string>
<string name="what_are_badges_body">ბეიჯები არის მარტივი აპის შიდა ერთჯერადი გადახდები. შეგიძლიათ მიიღოთ ლამაზი ბეიჯი და მის საშუალებით, დროის განმავლობაში, დაგვიჭიროთ მხარი.</string>
<string name="why_badges_title">რატომ სთავაზობს DAVx⁵ ფუნქციების გარეშე ბეიჯებს?</string>
<string name="why_badges_body">DAVx⁵ საკმაოდ გაიზარდა წლების განმავლობაში! ჩვენ კვლავ აქტიურად ვამატებთ ახალ ფუნქციების, ვუწევთ მხარდაჭერას და ყოველთვის ვანახლებთ აპს მომავალი Android-ის ვერსიებისთვის. ეს ბეიჯები წარმოადგენს ერთჯერად გადახდებს, რომლითაც თქვენ მარტივად გვიჩვენებთ თქვენს მხარდაჭერას ჩვენთვის ერთი-ორი&#8230; ან 10 ყავის შეძენით :-) ჩვენ გვინდა ვიყოთ ღია, რამდენადაც ეს არის შესაძლებელი, ამიტომ არასდროს არ იქნება ახალი რამ, რაც იქნება დაბლოკილი აპის შიდა გადახდაზე.</string>
<string name="button_buy_badge_free">უფასო</string>
<string name="button_buy_badge_bought">გმადლობთ!</string>
<string name="earn_badges">მიიღეთ ბეიჯები ჩვენს მხარდასაჭერად!</string>
</resources>

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="available_badges">उपलब्ध बिल्ले </string>
<string name="what_are_badges">बिलयांचा अर्थ ?</string>
<string name="what_are_badges_title">बिलयांचा अर्थ ?</string>
<string name="button_buy_badge_free">मुफ्ट </string>
<string name="button_buy_badge_bought">धन्यवाद !</string>
</resources>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Navigation drawer -->
<string name="nav_earn_badges">Bakke oss opp med emblemer</string>
<string name="available_badges_empty">Unnskyld, emblemer kan ikke lader. Er du anmeldet i Play Store?</string>
<string name="what_are_badges">Hva er emblemer?</string>
<string name="what_are_badges_title">Hva er emblemer?</string>
<string name="what_are_badges_body">Emblemer er simpel in-app engangsbetalinger. Di vil tjene en sött liten emblem og kan bakke oss opp med dem med tiden.</string>
<string name="why_badges_title">Hvorfor byr DAVx5 funksjon-fri emblemer?</string>
<string name="button_buy_badge_free">FRI</string>
<string name="button_buy_badge_bought">Tussen Takk!</string>
<string name="earn_badges">Tjene emblemer for å bakke oss opp!</string>
</resources>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Navigation drawer -->
<string name="nav_earn_badges">Steun ons met badges</string>
<string name="nav_rate_us">Beoordeel in Google Play</string>
<string name="nav_feedback_inapp_didnt_appear">Is de in-app-recensie niet verschenen?</string>
<string name="nav_feedback_google_play">Verzenden in Google Play</string>
<string name="nav_feedback_scroll_to_reviews">Scroll naar het beoordelingsgedeelte om feedback te geven</string>
<!-- AboutActivity -->
<string name="about_flavor_info">Deze versie is alleen geschikt voor distributie via Google Play.</string>
<!-- EarnBadgesActivity -->
<plurals name="you_earned_badges">
<item quantity="one">Je hebt badges verdiend, bedankt!</item>
<item quantity="other">Je hebt %d badges verdiend, bedankt!</item>
</plurals>
<string name="available_badges">Beschikbare badges</string>
<string name="available_badges_empty">Sorry, kon geen badges laden. Ben je ingelogd in de Play Store?</string>
<string name="what_are_badges">Wat zijn badges?</string>
<string name="what_are_badges_title">Wat zijn badges?</string>
<string name="what_are_badges_body">Badges zijn eenvoudige eenmalige in-app betalingen. Je verdient een leuke kleine badge en daarmee kun je ons in de loop der tijd steunen.</string>
<string name="why_badges_title">Waarom biedt DAVx5 functievrije badges aan?</string>
<string name="why_badges_body">DAVx5 is in de loop der jaren echt gegroeid! We ontwikkelen nog steeds actief nieuwe functies, bieden ondersteuning en we werken de app altijd bij voor komende Android versies. Deze badges zijn eenmalige betalingen waarbij je gewoon je steun kunt tonen door ons een kopje koffie te kopen, of twee&#8230; of 10 :-) We willen zo open mogelijk zijn, dus zullen er nooit nieuwe dingen worden vergrendeld voor in-app betaling.</string>
<string name="button_buy_badge_free">Gratis</string>
<string name="button_buy_badge_bought">Dank U!</string>
<string name="earn_badges">Verdien badges om ons te steunen!</string>
<string name="billing_unavailable">Facturerings-API niet beschikbaar</string>
<string name="network_problems">Er zijn netwerkproblemen. Probeer het later opnieuw.</string>
</resources>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Navigation drawer -->
<string name="nav_earn_badges">Wspieraj nas z odznakami</string>
<string name="nav_rate_us">Oceń w Google Play</string>
<!-- EarnBadgesActivity -->
<plurals name="you_earned_badges">
<item quantity="one">Zdobyłeś odznakę, dziękujemy!</item>
<item quantity="few">Zdobyłeś %d odznaki, dziękujemy</item>
<item quantity="many">Zdobyłeś %d odznaki, dziękujemy!</item>
<item quantity="other">Zdobyłeś %d odznak, dziękujemy!</item>
</plurals>
<string name="available_badges">Dostępne odznaki</string>
<string name="available_badges_empty">Przepraszamy, nie można załadować odznak. Czy jesteś zalogowany do Play Store?</string>
<string name="what_are_badges">Czym są odznaki?</string>
<string name="what_are_badges_title">Czym są odznaki?</string>
<string name="what_are_badges_body">Odznaki są po prostu jednorazowymi płatnościami w aplikacji. Zdobędziesz ładna, małą odznakę a przez to możesz nas wspierać.</string>
<string name="why_badges_title">Dlaczego DAVx5 offeruje odznaki bez funkcjonalności?</string>
<string name="why_badges_body">DAVx5 naprawdę rozrósł się przez lata! Wciąż aktywnie rozwijamy nowe funkcjonalności, dostarczamy wsparcie i zawsze aktualizujemy aplikację dla nadchodzących wersji systemu Android. Te odznaki są jednorazowymi płatnościami, przez które możesz okazac swoje wsparcie kupując nam kawę, albo dwie&#8230; albo 10 :-) Chcemy byc tak otwarci jak to możliwe więc nigdy ale to nigdy nie będzie nowych elementów ograniczonych poprzez płatności w aplikacji.</string>
<string name="button_buy_badge_free">DARMOWY</string>
<string name="button_buy_badge_bought">Dziękujemy!</string>
<string name="earn_badges">Zdobądź odznaki aby nas wspierać!</string>
</resources>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Navigation drawer -->
<string name="nav_earn_badges">Nos apoie com emblemas</string>
<string name="nav_rate_us">Avalie na Google Play</string>
<string name="nav_feedback_inapp_didnt_appear">A avaliação dentro do app não apareceu?</string>
<string name="nav_feedback_google_play">Enviar no Google Play</string>
<string name="nav_feedback_scroll_to_reviews">Deslize para a seção de avaliações para enviar um retorno</string>
<!-- AboutActivity -->
<string name="about_flavor_info">Essa versão só é válida para distribuição pelo Google Play.</string>
<!-- EarnBadgesActivity -->
<plurals name="you_earned_badges">
<item quantity="one">Você ganhou um emblema, obrigado!</item>
<item quantity="many">Você ganhou %d emblemas, obrigado!</item>
<item quantity="other">Você ganhou %d emblemas, obrigado!</item>
</plurals>
<string name="available_badges">Emblemas disponíveis</string>
<string name="available_badges_empty">Desculpe, não foi possível carregar os emblemas. Você está logado na Play Store?</string>
<string name="what_are_badges">O que são emblemas?</string>
<string name="what_are_badges_title">O que são emblemas?</string>
<string name="what_are_badges_body">Emblemas são simples pagamentos feitos uma vez só no app. Você ganha um pequeno emblema legal e com ele você nos apoia ao correr do tempo.</string>
<string name="why_badges_title">Por que que o DAVx5 oferece emblemas sem função?</string>
<string name="why_badges_body">O DAVx5 cresceu muito ao longo dos anos! Nós ainda estamos desenvolvendo ativamente novas funções, dando suporte, e sempre atualizamos o app para novas versões do Android. Esses emblemas são pagamentos feito-só-uma-vez onde você pode mostrar seu apoio comprando a nos um café, ou dois&#8230; ou 10 :-) Nós queremos ser o mais abertos possível, por causa disso nunca teremos novas coisas bloqueadas por pagamentos dentro do app.</string>
<string name="button_buy_badge_free">GRÁTIS</string>
<string name="button_buy_badge_bought">Obrigado!</string>
<string name="earn_badges">Ganhe emblemas para nos apoiar!</string>
<string name="billing_unavailable">API de cobrança indisponível</string>
<string name="network_problems">Problemas de rede, tente novamente mais tarde.</string>
</resources>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Navigation drawer -->
<string name="nav_earn_badges">Sprijină-ne cu insigne</string>
<string name="nav_rate_us">Recenzie în Google Play</string>
<string name="nav_feedback_inapp_didnt_appear">Examinarea în aplicație nu a apărut?</string>
<string name="nav_feedback_google_play">Trimite în Google Play</string>
<string name="nav_feedback_scroll_to_reviews">Derulează la secțiunea de revizuire pentru a trimite feedback</string>
<!-- AboutActivity -->
<string name="about_flavor_info">Această versiune este eligibilă pentru distribuție numai prin Google Play.</string>
<!-- EarnBadgesActivity -->
<plurals name="you_earned_badges">
<item quantity="one">Ai câștigat o insignă, îți mulțumim!</item>
<item quantity="few">Ai câștigat %d insigne, îți mulțumim!</item>
<item quantity="other">Ai câștigat %d insigne, îți mulțumim!</item>
</plurals>
<string name="available_badges">Ecusoane disponibile</string>
<string name="available_badges_empty">Ne pare rău, nu s-au putut încărca insignele. Ești autentificat în Magazinul Play?</string>
<string name="what_are_badges">Ce sunt insignele?</string>
<string name="what_are_badges_title">Ce sunt insignele?</string>
<string name="what_are_badges_body">Insignele sunt plăți simple în aplicație, o singură dată. Vei câștiga o insignă drăguță și cu ea ne poți susține în timp.</string>
<string name="why_badges_title">De ce DAVx5 oferă insigne fără funcții?</string>
<string name="why_badges_body">DAVx5 a crescut cu adevărat de-a lungul anilor! Încă dezvoltăm în mod activ noi funcții, oferim asistență și actualizăm întotdeauna aplicația pentru versiunile viitoare de Android. Aceste insigne sunt plăți unice în care poți pur și simplu să-ți arăți sprijinul cumpărându-ne o cafea, două&#8230; sau 10 :-) Vrem să fim cât mai deschiși posibil, așa că nu vor fi niciodată lucruri noi blocate prin plată prin aplicație.</string>
<string name="button_buy_badge_free">GRATUIT</string>
<string name="button_buy_badge_bought">Mulțumesc!</string>
<string name="earn_badges">Câștigă insigne pentru a ne susține!</string>
<string name="billing_unavailable">API-ul de facturare nu este disponibil</string>
<string name="network_problems">Probleme de rețea, încearcă din nou mai târziu.</string>
</resources>

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Navigation drawer -->
<string name="nav_earn_badges">Поддержать нас значками</string>
<string name="nav_rate_us">Отзыв в Google Play</string>
<string name="nav_feedback_inapp_didnt_appear">Не появился отзыв о приложении?</string>
<string name="nav_feedback_google_play">Отправить в Google Play</string>
<string name="nav_feedback_scroll_to_reviews">Перейдите к разделу отзывов, чтобы оставить свой</string>
<!-- AboutActivity -->
<string name="about_flavor_info">Эта версия может распространяться только через Google Play.</string>
<!-- EarnBadgesActivity -->
<plurals name="you_earned_badges">
<item quantity="one">Вы заработали значок, спасибо!</item>
<item quantity="few">Вы заработали %d значка, спасибо!</item>
<item quantity="many">Вы заработали %d значков, спасибо!</item>
<item quantity="other">Вы заработали %d значков, спасибо!</item>
</plurals>
<string name="available_badges">Доступные значки</string>
<string name="available_badges_empty">К сожалению, не удалось загрузить значки. Вы авторизовались в Play Store?</string>
<string name="what_are_badges">Что такое значки?</string>
<string name="what_are_badges_title">Что такое значки?</string>
<string name="what_are_badges_body">Значки — это обычные одноразовые платежи в приложении. Вы заработаете миленький значок, и с его помощью вы сможете поддерживать нас в течение долгого времени.</string>
<string name="why_badges_title">Почему DAVx5 предлагает значки без каких-либо возможностей?</string>
<string name="why_badges_body">DAVx5 действительно вырос за эти годы! Мы по-прежнему активно разрабатываем новые функции, оказываем поддержку и всегда обновляем приложение для новых версий Android. Эти значки - одноразовые платежи, которыми вы можете просто показать свою поддержку, купив нам кофе, или два&#8230; или 10 :-) Мы хотим быть максимально открытыми, поэтому в приложении никогда не будет никаких новых функций, которые можно было бы оплатить через приложение.</string>
<string name="button_buy_badge_free">БЕСПЛАТНО</string>
<string name="button_buy_badge_bought">Спасибо!</string>
<string name="earn_badges">Зарабатывайте значки, поддерживая нас!</string>
<string name="billing_unavailable">API биллинга недоступен</string>
<string name="network_problems">Проблемы с сетью, попробуйте позже.</string>
</resources>

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Navigation drawer -->
<string name="nav_earn_badges">Stöd oss med märken</string>
<string name="nav_rate_us">Betygsätt i Google Play</string>
<string name="nav_feedback_inapp_didnt_appear">Betygsättning i appen visades inte?</string>
<string name="nav_feedback_google_play">Skicka i Google Play</string>
<string name="nav_feedback_scroll_to_reviews">Bläddra till granskningssektionen för att skicka feedback</string>
<!-- AboutActivity -->
<string name="about_flavor_info">Den här versionen är endast kvalificerad för distribution över Google Play.</string>
<!-- EarnBadgesActivity -->
<plurals name="you_earned_badges">
<item quantity="one">Du har förtjänat ett märke. Stort tack!</item>
<item quantity="other">Du har förtjänat %d märken. Stort tack!</item>
</plurals>
<string name="available_badges">Tillgängliga märken</string>
<string name="available_badges_empty">Ledsen, men märken kunde inte hämtas. Är du inloggad i Play Store?</string>
<string name="what_are_badges">Vad är märken?</string>
<string name="what_are_badges_title">Vad är märken?</string>
<string name="what_are_badges_body">Märken är enkla engångsköp i appen. Du erhåller ett märke och stöder oss över tid.</string>
<string name="why_badges_title">Varför erbjuder DAVx5 märken utan funktioner?</string>
<string name="why_badges_body">DAVx5 har verkligen vuxit under årens lopp! Vi utvecklar aktivt nya funktioner, ger support och uppdaterar alltid appen för kommande Android-versioner. Dessa märken är engångsbetalningar där du helt enkelt kan visa ditt stöd genom att bjuda oss på en kaffe, eller två&#8230; eller 10 :-) Vi vill vara så öppna som möjligt och det kommer aldrig finnas funktioner som kräver köp i appen.</string>
<string name="button_buy_badge_free">GRATIS</string>
<string name="button_buy_badge_bought">Tack!</string>
<string name="earn_badges">Tjäna märken genom att stödja oss!</string>
<string name="billing_unavailable">Fakturerings-API inte tillgängligt</string>
<string name="network_problems">Nätverksproblem, försök igen senare</string>
</resources>

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Navigation drawer -->
<string name="nav_earn_badges">用徽章支持我们</string>
<string name="nav_rate_us">在 Google Play 中打分</string>
<string name="nav_feedback_inapp_didnt_appear">应用内预览没有出现?</string>
<string name="nav_feedback_google_play">在 Google Play 中发送</string>
<string name="nav_feedback_scroll_to_reviews">滚动到评价部分来提交反馈</string>
<!-- AboutActivity -->
<string name="about_flavor_info">此版本只允许在 Google Play 上发行。</string>
<!-- EarnBadgesActivity -->
<plurals name="you_earned_badges">
<item quantity="other">你已赚得 %d 枚徽章,谢谢!</item>
</plurals>
<string name="available_badges">可用的徽章</string>
<string name="available_badges_empty">抱歉,无法加载徽章。你登录 Play Store 了吗?</string>
<string name="what_are_badges">什么是徽章?</string>
<string name="what_are_badges_title">什么是徽章?</string>
<string name="what_are_badges_body">徽章是一种简单的应用内一次性付费。付款后,你将获得一个漂亮的小徽章,你可以用它支持我们。</string>
<string name="why_badges_title">为何 DAVx5 提供无功能徽章?</string>
<string name="why_badges_body">DAVx5 这些年真的成长了很多!我们仍在积极开发新功能,提供支持,我们总是为即将到来的 Android 版本更新应用。这些徽章是一次性付款,你可以支付一两杯或 &#8230; 10 杯咖啡钱来表示你的支持 :-) 我们希望尽可能地开放,确保不会有任何新功能需要付费才能使用。</string>
<string name="button_buy_badge_free">免费</string>
<string name="button_buy_badge_bought">谢谢!</string>
<string name="earn_badges">用徽章表示支持!</string>
<string name="billing_unavailable">账单 API 不可用</string>
<string name="network_problems">网络问题,请稍后再试</string>
</resources>

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Navigation drawer -->
<string name="nav_earn_badges">Support us with badges</string>
<string name="nav_rate_us">Review in Google Play</string>
<string name="nav_feedback_inapp_didnt_appear">In-app review didn\'t appear?</string>
<string name="nav_feedback_google_play">Send in Google Play</string>
<string name="nav_feedback_scroll_to_reviews">Scroll to review section to submit feedback</string>
<!-- AboutActivity -->
<string name="about_flavor_info">This version is only eligible for distribution over Google Play.</string>
<!-- EarnBadgesActivity -->
<plurals name="you_earned_badges">
<item quantity="one">You\'ve earned a badge, thank you!</item>
<item quantity="other">You have earned %d badges, thank you!</item>
</plurals>
<string name="available_badges">Available badges</string>
<string name="available_badges_empty">Sorry, could not load badges. Are you logged in to the Play Store?</string>
<string name="what_are_badges">What are badges?</string>
<string name="what_are_badges_title">What are badges?</string>
<string name="what_are_badges_body">Badges are simple in-app one-time-payments. You will earn a nice little badge and with it you can support us over time.</string>
<string name="why_badges_title">Why does DAVx5 offer feature-free badges?</string>
<string name="why_badges_body">DAVx5 has really grown over the years! We are still actively developing new features, providing support and we always update the app for upcoming Android versions. These badges are one-time payments where you can simply show your support by buying us a coffee, or two&#8230; or 10 :-) We want to be as open as possible, so there will never-ever be any new stuff locked to in-app payment.</string>
<string name="button_buy_badge_free">FREE</string>
<string name="button_buy_badge_bought">Thank you!</string>
<string name="earn_badges">Earn badges to support us!</string>
<string name="billing_unavailable">Billing API not available</string>
<string name="purchase_acknowledgement_failed">Failed to acknowledge purchase</string>
<string name="purchase_acknowledgement_successful">Thank you for the support!</string>
<string name="purchase_failed">Purchase failed</string>
<string name="network_problems">Network problems, please try again later.</string>
</resources>

View File

@@ -0,0 +1,26 @@
<!--
~ Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application>
<!-- AppAuth login flow redirect (duplicated in gplay/AndroidManifest.xml) -->
<activity
android:name="net.openid.appauth.RedirectUriReceiverActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
tools:ignore="AppLinkUrlError"
android:scheme="${applicationId}"
android:path="/oauth2/redirect"/>
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,50 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid
import android.content.Context
import at.bitfire.davdroid.ui.DebugInfoActivity
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import java.util.logging.Level
import java.util.logging.Logger
import javax.inject.Inject
class DebugInfoCrashHandler @Inject constructor(
@ApplicationContext private val context: Context,
private val logger: Logger
): Thread.UncaughtExceptionHandler {
@Module
@InstallIn(SingletonComponent::class)
interface DebugInfoCrashHandlerModule {
@Binds
fun debugInfoCrashHandler(
debugInfoCrashHandler: DebugInfoCrashHandler
): Thread.UncaughtExceptionHandler
}
// See https://developer.android.com/about/versions/oreo/android-8.0-changes#loue
val originalCrashHandler = Thread.getDefaultUncaughtExceptionHandler()
override fun uncaughtException(t: Thread, e: Throwable) {
logger.log(Level.SEVERE, "Unhandled exception in thread ${t.id}!", e)
// start debug info activity with exception (will be started in a new process)
val intent = DebugInfoActivity.IntentBuilder(context)
.withCause(e)
.newTask()
.build()
context.startActivity(intent)
// pass through to default handler to kill the process
originalCrashHandler?.uncaughtException(t, e)
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.di
import at.bitfire.davdroid.ui.AccountsDrawerHandler
import at.bitfire.davdroid.ui.StandardAccountsDrawerHandler
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent
interface StandardModules {
@Module
@InstallIn(ActivityComponent::class)
interface ForActivities {
@Binds
fun accountsDrawerHandler(handler: StandardAccountsDrawerHandler): AccountsDrawerHandler
}
}

View File

@@ -23,6 +23,7 @@ bitfire-dav4jvm = "acd9bca096"
bitfire-synctools = "ad0c68d820"
compose-accompanist = "0.37.3"
compose-bom = "2025.11.01"
confettikit = "0.6.0"
conscrypt = "2.5.3"
dnsjava = "3.6.3"
glance = "1.1.1"
@@ -42,6 +43,10 @@ room = "2.8.4"
unifiedpush = "3.1.2"
unifiedpush-fcm = "3.0.0"
# gplay build variants
android-billing = "8.0.0"
android-review = "2.0.2"
# Other libraries, especially ical4j, require Apache Commons. Some recent versions of Apache
# Commons require a newer Java version than our minSdk provides. So we require these strict versions here:
#noinspection NewerVersionAvailable
@@ -83,6 +88,7 @@ compose-material3 = { group = "androidx.compose.material3", name = "material3" }
compose-materialIconsExtended = { module = "androidx.compose.material:material-icons-extended" }
compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
compose-ui-toolingPreview = { module = "androidx.compose.ui:ui-tooling-preview" }
confettikit = { module = "io.github.vinceglb:confettikit", version.ref = "confettikit" }
conscrypt = { module = "org.conscrypt:conscrypt-android", version.ref = "conscrypt" }
dnsjava = { module = "dnsjava:dnsjava", version.ref = "dnsjava" }
glance-base = { module = "androidx.glance:glance-appwidget", version.ref = "glance" }
@@ -114,6 +120,11 @@ room-testing = { module = "androidx.room:room-testing", version.ref = "room" }
unifiedpush = { module = "org.unifiedpush.android:connector", version.ref = "unifiedpush" }
unifiedpush-fcm = { module = "org.unifiedpush.android:embedded-fcm-distributor", version.ref = "unifiedpush-fcm" }
# gplay build variant
# Keep in sync with the allow list of the non-ose-dependencies in .github/dependabot.yml!
android-billing = { module = "com.android.billingclient:billing-ktx", version.ref = "android-billing" }
android-review = { module = "com.google.android.play:review-ktx", version.ref = "android-review" }
[plugins]
android-application = { id = "com.android.application", version.ref = "android-agp" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }