Move gplay variant to ose

This commit is contained in:
Arnau Mora
2025-10-25 13:42:27 +02:00
62 changed files with 2548 additions and 0 deletions

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>