diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9bcbef9ab..557dcda88 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -187,7 +187,6 @@ dependencies { implementation("io.reactivex.rxjava2:rxandroid:2.1.1") implementation("androidx.paging:paging-runtime:2.1.2") implementation("io.noties.markwon:core:${Versions.markwon}") - implementation("io.noties.markwon:ext-strikethrough:${Versions.markwon}") kapt("com.jakewharton:butterknife-compiler:${Versions.butterknife}") implementation("com.jakewharton:butterknife:${Versions.butterknife}") diff --git a/app/licenses.yml b/app/licenses.yml index 13d07ae3b..a32de5590 100644 --- a/app/licenses.yml +++ b/app/licenses.yml @@ -707,12 +707,6 @@ license: The Apache Software License, Version 2.0 licenseUrl: https://www.apache.org/licenses/LICENSE-2.0.txt url: https://github.com/google/dagger -- artifact: io.noties.markwon:ext-strikethrough:+ - name: ext-strikethrough - copyrightHolder: Dimitry Ivanov - license: The Apache Software License, Version 2.0 - licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt - url: https://github.com/noties/Markwon - artifact: io.noties.markwon:core:+ name: core copyrightHolder: Dimitry Ivanov diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9a6127d95..7b390998a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -302,6 +302,10 @@ + + diff --git a/app/src/main/assets/licenses.json b/app/src/main/assets/licenses.json index 34ff0e37e..826907414 100644 --- a/app/src/main/assets/licenses.json +++ b/app/src/main/assets/licenses.json @@ -1691,20 +1691,6 @@ "url": "https://github.com/google/dagger", "libraryName": "hilt-core" }, - { - "artifactId": { - "name": "ext-strikethrough", - "group": "io.noties.markwon", - "version": "+" - }, - "copyrightHolder": "Dimitry Ivanov", - "copyrightStatement": "Copyright © Dimitry Ivanov. All rights reserved.", - "license": "The Apache Software License, Version 2.0", - "licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt", - "normalizedLicense": "apache2", - "url": "https://github.com/noties/Markwon", - "libraryName": "ext-strikethrough" - }, { "artifactId": { "name": "core", diff --git a/app/src/main/java/org/tasks/auth/SignInActivity.kt b/app/src/main/java/org/tasks/auth/SignInActivity.kt index fee112fda..389864421 100644 --- a/app/src/main/java/org/tasks/auth/SignInActivity.kt +++ b/app/src/main/java/org/tasks/auth/SignInActivity.kt @@ -29,13 +29,19 @@ import androidx.lifecycle.lifecycleScope import at.bitfire.dav4jvm.exception.HttpException import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch -import net.openid.appauth.* +import net.openid.appauth.AuthState +import net.openid.appauth.AuthorizationException +import net.openid.appauth.AuthorizationRequest +import net.openid.appauth.AuthorizationServiceConfiguration +import net.openid.appauth.ClientSecretBasic +import net.openid.appauth.RegistrationRequest +import net.openid.appauth.RegistrationResponse +import net.openid.appauth.ResponseTypeValues import org.tasks.R import org.tasks.analytics.Firebase import org.tasks.billing.Inventory -import org.tasks.billing.PurchaseDialog -import org.tasks.billing.PurchaseDialog.Companion.FRAG_TAG_PURCHASE_DIALOG -import org.tasks.billing.PurchaseDialog.Companion.newPurchaseDialog +import org.tasks.billing.PurchaseActivity +import org.tasks.billing.PurchaseActivity.Companion.EXTRA_GITHUB import org.tasks.dialogs.DialogBuilder import org.tasks.injection.InjectingAppCompatActivity import org.tasks.themes.ThemeColor @@ -56,7 +62,7 @@ import javax.inject.Inject * - Initiate the authorization request using the built-in heuristics or a user-selected browser. */ @AndroidEntryPoint -class SignInActivity : InjectingAppCompatActivity(), PurchaseDialog.PurchaseHandler { +class SignInActivity : InjectingAppCompatActivity() { @Inject lateinit var themeColor: ThemeColor @Inject lateinit var inventory: Inventory @Inject lateinit var dialogBuilder: DialogBuilder @@ -166,8 +172,11 @@ class SignInActivity : InjectingAppCompatActivity(), PurchaseDialog.PurchaseHand private fun handleError(e: Throwable) { if (e is HttpException && e.code == 402) { - newPurchaseDialog(tasksPayment = true, github = authService.isGitHub) - .show(supportFragmentManager, FRAG_TAG_PURCHASE_DIALOG) + startActivityForResult( + Intent(this, PurchaseActivity::class.java) + .putExtra(EXTRA_GITHUB, authService.isGitHub), + RC_PURCHASE + ) } else { returnError(e) } @@ -180,24 +189,39 @@ class SignInActivity : InjectingAppCompatActivity(), PurchaseDialog.PurchaseHand } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (requestCode == RC_AUTH) { - if (resultCode == RESULT_OK) { - lifecycleScope.launch { - val account = try { - viewModel.handleResult(authService, data!!) - } catch (e: Exception) { - returnError(e) + when (requestCode) { + RC_PURCHASE -> + if (inventory.subscription.value?.isTasksSubscription == true) { + lifecycleScope.launch { + val account = viewModel.setupAccount(authService) + if (account != null) { + setResult(RESULT_OK) + finish() + } } - if (account != null) { - setResult(RESULT_OK) - finish() + } else { + finish() + } + RC_AUTH -> + if (resultCode == RESULT_OK) { + lifecycleScope.launch { + val account = try { + viewModel.handleResult(authService, data!!) + } catch (e: Exception) { + returnError(e) + } + if (account != null) { + setResult(RESULT_OK) + finish() + } } + } else { + returnError( + Exception(getString(R.string.authorization_cancelled)), + report = false + ) } - } else { - returnError(Exception(getString(R.string.authorization_cancelled)), report = false) - } - } else { - super.onActivityResult(requestCode, resultCode, data) + else -> super.onActivityResult(requestCode, resultCode, data) } } @@ -364,19 +388,6 @@ class SignInActivity : InjectingAppCompatActivity(), PurchaseDialog.PurchaseHand const val EXTRA_ERROR = "extra_error" const val EXTRA_SELECT_SERVICE = "extra_select_service" private const val RC_AUTH = 100 - } - - override fun onPurchaseDialogDismissed() { - if (inventory.subscription.value?.isTasksSubscription == true) { - lifecycleScope.launch { - val account = viewModel.setupAccount(authService) - if (account != null) { - setResult(RESULT_OK) - finish() - } - } - } else { - finish() - } + private const val RC_PURCHASE = 101 } } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/billing/PurchaseActivity.kt b/app/src/main/java/org/tasks/billing/PurchaseActivity.kt new file mode 100644 index 000000000..b759ff669 --- /dev/null +++ b/app/src/main/java/org/tasks/billing/PurchaseActivity.kt @@ -0,0 +1,112 @@ +package org.tasks.billing + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.os.Bundle +import androidx.activity.compose.setContent +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.window.Dialog +import com.google.android.material.composethemeadapter.MdcTheme +import dagger.hilt.android.AndroidEntryPoint +import org.tasks.LocalBroadcastManager +import org.tasks.Tasks.Companion.IS_GENERIC +import org.tasks.compose.PurchaseText.PurchaseText +import org.tasks.injection.InjectingAppCompatActivity +import org.tasks.themes.Theme +import javax.inject.Inject + +@AndroidEntryPoint +class PurchaseActivity : InjectingAppCompatActivity(), OnPurchasesUpdated { + @Inject lateinit var theme: Theme + @Inject lateinit var billingClient: BillingClient + @Inject lateinit var localBroadcastManager: LocalBroadcastManager + @Inject lateinit var inventory: Inventory + + private var currentSubscription: Purchase? = null + private val purchaseReceiver: BroadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + setup() + } + } + private val nameYourPrice = mutableStateOf(false) + private val sliderPosition = mutableStateOf(-1f) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val github = intent?.extras?.getBoolean(EXTRA_GITHUB) ?: false + + theme.applyToContext(this) + + savedInstanceState?.let { + nameYourPrice.value = it.getBoolean(EXTRA_NAME_YOUR_PRICE) + sliderPosition.value = it.getFloat(EXTRA_PRICE) + } + + setContent { + MdcTheme { + Dialog(onDismissRequest = { finish() }) { + PurchaseText( + nameYourPrice, + sliderPosition, + github, + IS_GENERIC, + this::purchase) + } + } + } + } + + override fun onStart() { + super.onStart() + + localBroadcastManager.registerPurchaseReceiver(purchaseReceiver) + billingClient.queryPurchases() + } + + override fun onStop() { + super.onStop() + localBroadcastManager.unregisterReceiver(purchaseReceiver) + } + + private fun setup() { + currentSubscription = inventory.subscription.value + if (sliderPosition.value < 0) { + sliderPosition.value = + currentSubscription + ?.subscriptionPrice + ?.coerceAtMost(25) + ?.toFloat() ?: 10f + } + } + + private fun purchase(price: Int, monthly: Boolean) { + val newSku = String.format("%s_%02d", if (monthly) "monthly" else "annual", price) + billingClient.initiatePurchaseFlow( + this, + newSku, + BillingClientImpl.TYPE_SUBS, + currentSubscription?.sku?.takeIf { it != newSku }) + billingClient.addPurchaseCallback(this) + } + + override fun onPurchasesUpdated(success: Boolean) { + if (success) { + setResult(RESULT_OK) + finish() + } + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putFloat(EXTRA_PRICE, sliderPosition.value) + outState.putBoolean(EXTRA_NAME_YOUR_PRICE, nameYourPrice.value) + } + + companion object { + const val EXTRA_GITHUB = "extra_github" + private const val EXTRA_PRICE = "extra_price" + private const val EXTRA_NAME_YOUR_PRICE = "extra_name_your_price" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/billing/PurchaseDialog.kt b/app/src/main/java/org/tasks/billing/PurchaseDialog.kt deleted file mode 100644 index d72eaf6da..000000000 --- a/app/src/main/java/org/tasks/billing/PurchaseDialog.kt +++ /dev/null @@ -1,375 +0,0 @@ -package org.tasks.billing - -import android.app.Activity.RESULT_OK -import android.app.Dialog -import android.content.BroadcastReceiver -import android.content.Context -import android.content.DialogInterface -import android.content.Intent -import android.net.Uri -import android.os.Bundle -import android.text.method.LinkMovementMethod -import android.text.style.TextAppearanceSpan -import androidx.appcompat.app.AlertDialog -import androidx.core.content.ContextCompat -import androidx.core.view.isVisible -import androidx.fragment.app.DialogFragment -import androidx.fragment.app.Fragment -import butterknife.ButterKnife -import butterknife.OnClick -import com.google.android.material.slider.Slider -import dagger.hilt.android.AndroidEntryPoint -import io.noties.markwon.AbstractMarkwonPlugin -import io.noties.markwon.Markwon -import io.noties.markwon.MarkwonSpansFactory -import io.noties.markwon.ext.strikethrough.StrikethroughPlugin -import org.commonmark.ext.gfm.strikethrough.Strikethrough -import org.tasks.LocalBroadcastManager -import org.tasks.R -import org.tasks.Tasks.Companion.IS_GENERIC -import org.tasks.Tasks.Companion.IS_GOOGLE_PLAY -import org.tasks.analytics.Firebase -import org.tasks.databinding.ActivityPurchaseBinding -import org.tasks.dialogs.DialogBuilder -import org.tasks.locale.Locale -import timber.log.Timber -import javax.inject.Inject - - -@AndroidEntryPoint -class PurchaseDialog : DialogFragment(), OnPurchasesUpdated { - - interface PurchaseHandler { - fun onPurchaseDialogDismissed() - } - - private val purchaseReceiver: BroadcastReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - setup() - } - } - - @Inject lateinit var inventory: Inventory - @Inject lateinit var dialogBuilder: DialogBuilder - @Inject lateinit var billingClient: BillingClient - @Inject lateinit var localBroadcastManager: LocalBroadcastManager - @Inject lateinit var locale: Locale - @Inject lateinit var firebase: Firebase - - private lateinit var binding: ActivityPurchaseBinding - private lateinit var markwon: Markwon - - private var currentSubscription: Purchase? = null - private var priceChanged = false - private var nameYourPrice = false - - private val hasTasksSubscription - get() = inventory.subscription.value?.isTasksSubscription == true - - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - binding = ActivityPurchaseBinding.inflate(layoutInflater) - ButterKnife.bind(this, binding.root) - - if (savedInstanceState == null) { - nameYourPrice = !isTasksPayment && !hasTasksSubscription - } else { - binding.slider.value = savedInstanceState.getFloat(EXTRA_PRICE) - priceChanged = savedInstanceState.getBoolean(EXTRA_PRICE_CHANGED) - nameYourPrice = savedInstanceState.getBoolean(EXTRA_NAME_YOUR_PRICE) - } - - binding.slider.addOnChangeListener(this::onPriceChanged) - binding.slider.setLabelFormatter { "$${it - .01}" } - binding.text.movementMethod = LinkMovementMethod.getInstance() - - markwon = Markwon.builder(requireContext()) - .usePlugin(StrikethroughPlugin.create()) - .usePlugin(object : AbstractMarkwonPlugin() { - override fun configureSpansFactory(builder: MarkwonSpansFactory.Builder) { - builder.appendFactory(Strikethrough::class.java) { _, _ -> - TextAppearanceSpan(requireContext(), R.style.RedText) - } - } - }) - .build() - - setWaitScreen(!IS_GENERIC) - - return if (IS_GENERIC) { - if (isGitHub) { - getPurchaseDialog() - } else { - getMessageDialog(R.string.no_google_play_subscription) - } - } else { - if (isGitHub) { - getMessageDialog(R.string.insufficient_sponsorship) - } else { - getPurchaseDialog() - } - } - } - - private fun getPurchaseDialog(): AlertDialog = - dialogBuilder.newDialog().setView(binding.root).show() - - private fun getMessageDialog(res: Int): AlertDialog = - dialogBuilder.newDialog() - .setMessage(res) - .setPositiveButton(R.string.ok, null) - .setNeutralButton(R.string.help) { _, _ -> - val url = Uri.parse(getString(R.string.subscription_help_url)) - startActivity(Intent(Intent.ACTION_VIEW, url)) - } - .show() - - private fun updateText() { - var benefits = "### ${getString(when { - nameYourPrice -> R.string.name_your_price - !inventory.hasPro -> R.string.upgrade_to_pro - !hasTasksSubscription -> R.string.button_upgrade - else -> R.string.manage_subscription - })}" - benefits += if (nameYourPrice) { - """ ---- -#### ~~${getString(R.string.tasks_org_account)}~~ - -_${getString(R.string.account_not_included)}_ -""" - } else { - """ ---- -#### ${getString(R.string.tasks_org_account)} -* ${getString(R.string.tasks_org_description)} -* [${getString(R.string.tasks_org_share)}](${getString(R.string.url_sharing)}) -* [${getString(R.string.upgrade_third_party_apps)}](${getString(R.string.url_app_passwords)}) -* ${getString(R.string.upgrade_google_places)} -* [${getString(R.string.upgrade_coming_soon)}](${getString(R.string.help_url_sync)}) -""" - } - if (IS_GOOGLE_PLAY) { - benefits += """ ---- -#### ${getString(R.string.upgrade_sync_self_hosted)} -* [${getString(R.string.davx5)}](${getString(R.string.url_davx5)}) -* [${getString(R.string.caldav)}](${getString(R.string.url_caldav)}) -* [${getString(R.string.etesync)}](${getString(R.string.url_etesync)}) -* [${getString(R.string.decsync)}](${getString(R.string.url_decsync)}) -* ${getString(R.string.upgrade_google_tasks)} ---- -#### ${getString(R.string.upgrade_additional_features)} -* ${getString(R.string.upgrade_themes)} -* [${getString(R.string.upgrade_tasker)}](${getString(R.string.url_tasker)}) ---- -* ${getString(R.string.upgrade_free_trial)} -* ${getString(R.string.upgrade_downgrade)} -* ${getString(R.string.upgrade_support_development)} -""" - } - binding.text.text = markwon.toMarkdown(benefits) - } - - @OnClick(R.id.pay_annually) - fun subscribeAnnually() { - initiatePurchase(false, 30) - } - - @OnClick(R.id.pay_monthly) - fun subscribeMonthly() { - initiatePurchase(true, 3) - } - - @OnClick(R.id.sponsor) - fun sponsor() { - startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.url_sponsor)))) - } - - private fun initiatePurchase(isMonthly: Boolean, price: Int) { - val newSku = String.format("%s_%02d", if (isMonthly) "monthly" else "annual", price) - billingClient.initiatePurchaseFlow( - requireActivity(), - newSku, - BillingClientImpl.TYPE_SUBS, - currentSubscription?.sku?.takeIf { it != newSku }) - billingClient.addPurchaseCallback(this) - } - - @OnClick(R.id.pay_other) - fun nameYourPrice() { - if (isTasksPayment) { - dismiss() - } else { - nameYourPrice = !nameYourPrice - setWaitScreen(false) - binding.scroll.scrollTo(0, 0) - updateSubscribeButton() - } - } - - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - outState.putFloat(EXTRA_PRICE, binding.slider.value) - outState.putBoolean(EXTRA_PRICE_CHANGED, priceChanged) - outState.putBoolean(EXTRA_NAME_YOUR_PRICE, nameYourPrice) - } - - private fun setWaitScreen(isWaitScreen: Boolean) { - Timber.d("setWaitScreen(%s)", isWaitScreen) - binding.sliderContainer.isVisible = !isWaitScreen && nameYourPrice - binding.payOther.isVisible = !isWaitScreen - binding.payOther.setText(when { - isTasksPayment -> R.string.cancel - nameYourPrice -> R.string.get_tasks_org_account - else -> R.string.name_your_price - }) - binding.tasksOrgButtonPanel.isVisible = !isWaitScreen && !IS_GENERIC - binding.screenWait.isVisible = isWaitScreen && !IS_GENERIC - binding.sponsor.isVisible = IS_GENERIC - updateText() - } - - override fun onStart() { - super.onStart() - localBroadcastManager.registerPurchaseReceiver(purchaseReceiver) - billingClient.queryPurchases() - } - - override fun onStop() { - super.onStop() - localBroadcastManager.unregisterReceiver(purchaseReceiver) - } - - private fun setup() { - currentSubscription = inventory.subscription.value - if (!priceChanged) { - binding.slider.value = - currentSubscription - ?.subscriptionPrice - ?.coerceAtMost(25) - ?.toFloat() ?: 10f - } - updateSubscribeButton() - setWaitScreen(false) - } - - private fun updateSubscribeButton() { - val sliderValue = binding.slider.value.toInt() - val annualPrice = if (nameYourPrice) sliderValue else 30 - val monthlyPrice = if (nameYourPrice) sliderValue else 3 - val constrained = resources.getBoolean(R.bool.width_constrained) - val aboveAverage = "${getString(R.string.above_average)} $POPPER" - binding.avgAnnual.text = when { - !nameYourPrice -> "${getString( - R.string.save_percent, - ((1 - (annualPrice / (12.0 * monthlyPrice))) * 100).toInt() - )} $POPPER" - sliderValue < firebase.averageSubscription() -> "" //getString(R.string.below_average) - else -> aboveAverage - } - binding.avgAnnual.setTextColor( - if (nameYourPrice && sliderValue < firebase.averageSubscription()) { - ContextCompat.getColor(requireContext(), R.color.text_secondary) - } else { - ContextCompat.getColor(requireContext(), R.color.purchase_highlight) - } - ) - binding.avgMonthly.setTextColor( - ContextCompat.getColor(requireContext(), R.color.purchase_highlight) - ) - binding.avgMonthly.text = aboveAverage - with(binding.payAnnually) { - isEnabled = true - text = getString( - if (constrained) R.string.price_per_year_abbreviated else R.string.price_per_year, - annualPrice - .01 - ) - setOnClickListener { - initiatePurchase(false, if (nameYourPrice) sliderValue else 30) - } - } - with(binding.payMonthly) { - isEnabled = true - text = getString( - if (constrained) R.string.price_per_month_abbreviated else R.string.price_per_month, - monthlyPrice - .01 - ) - setOnClickListener { - initiatePurchase(true, if (nameYourPrice) sliderValue else 3) - } - isVisible = !nameYourPrice || sliderValue < 3 - } - - binding.avgMonthly.isVisible = nameYourPrice && binding.payMonthly.isVisible - currentSubscription?.let { - binding.payMonthly.isEnabled = - it.isCanceled || !it.isMonthly || monthlyPrice != it.subscriptionPrice - binding.payAnnually.isEnabled = - it.isCanceled || it.isMonthly || annualPrice != it.subscriptionPrice - } - } - - private fun onPriceChanged(slider: Slider, value: Float, fromUser: Boolean) { - if (fromUser) { - priceChanged = true - } - updateSubscribeButton() - } - - override fun onPurchasesUpdated(success: Boolean) { - if (success) { - dismiss() - targetFragment?.onActivityResult(targetRequestCode, RESULT_OK, null) - } - } - - override fun onDismiss(dialog: DialogInterface) { - super.onDismiss(dialog) - - activity.takeIf { it is PurchaseHandler }?.let { - (it as PurchaseHandler).onPurchaseDialogDismissed() - } - } - - private val isTasksPayment: Boolean - get() = arguments?.getBoolean(EXTRA_TASKS_PAYMENT, false) ?: false - - private val isGitHub: Boolean - get() = arguments?.getBoolean(EXTRA_GITHUB, false) ?: false - - companion object { - private const val POPPER = "\uD83C\uDF89" - private const val EXTRA_PRICE = "extra_price" - private const val EXTRA_PRICE_CHANGED = "extra_price_changed" - private const val EXTRA_NAME_YOUR_PRICE = "extra_name_your_price" - private const val EXTRA_TASKS_PAYMENT = "extra_tasks_payment" - private const val EXTRA_GITHUB = "extra_github" - - @JvmStatic - val FRAG_TAG_PURCHASE_DIALOG = "frag_tag_purchase_dialog" - - @JvmStatic - @JvmOverloads - fun newPurchaseDialog( - tasksPayment: Boolean = false, - github: Boolean = IS_GENERIC - ): PurchaseDialog { - val dialog = PurchaseDialog() - val args = Bundle() - args.putBoolean(EXTRA_TASKS_PAYMENT, tasksPayment) - args.putBoolean(EXTRA_GITHUB, github) - dialog.arguments = args - return dialog - } - - fun newPurchaseDialog( - target: Fragment, - rc: Int, - tasksPayment: Boolean = false - ): PurchaseDialog { - val dialog = newPurchaseDialog(tasksPayment) - dialog.setTargetFragment(target, rc) - return dialog - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/caldav/BaseCaldavAccountSettingsActivity.kt b/app/src/main/java/org/tasks/caldav/BaseCaldavAccountSettingsActivity.kt index de869b281..dcf17f219 100644 --- a/app/src/main/java/org/tasks/caldav/BaseCaldavAccountSettingsActivity.kt +++ b/app/src/main/java/org/tasks/caldav/BaseCaldavAccountSettingsActivity.kt @@ -26,8 +26,7 @@ import org.tasks.R import org.tasks.Strings.isNullOrEmpty import org.tasks.analytics.Firebase import org.tasks.billing.Inventory -import org.tasks.billing.PurchaseDialog.Companion.FRAG_TAG_PURCHASE_DIALOG -import org.tasks.billing.PurchaseDialog.Companion.newPurchaseDialog +import org.tasks.billing.PurchaseActivity import org.tasks.data.CaldavAccount import org.tasks.data.CaldavDao import org.tasks.databinding.ActivityCaldavAccountSettingsBinding @@ -104,7 +103,7 @@ abstract class BaseCaldavAccountSettingsActivity : ThemedInjectingAppCompatActiv newSnackbar(getString(R.string.this_feature_requires_a_subscription)) .setDuration(BaseTransientBottomBar.LENGTH_INDEFINITE) .setAction(R.string.button_subscribe) { - newPurchaseDialog().show(supportFragmentManager, FRAG_TAG_PURCHASE_DIALOG) + startActivity(Intent(this, PurchaseActivity::class.java)) } .show() } diff --git a/app/src/main/java/org/tasks/compose/Pager.kt b/app/src/main/java/org/tasks/compose/Pager.kt new file mode 100644 index 000000000..4d485e1e3 --- /dev/null +++ b/app/src/main/java/org/tasks/compose/Pager.kt @@ -0,0 +1,227 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.tasks.compose + +import androidx.compose.animation.core.Animatable +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.draggable +import androidx.compose.foundation.gestures.rememberDraggableState +import androidx.compose.foundation.layout.Box +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.key +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.runtime.structuralEqualityPolicy +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.Layout +import androidx.compose.ui.layout.Measurable +import androidx.compose.ui.layout.ParentDataModifier +import androidx.compose.ui.unit.Density +import kotlinx.coroutines.launch +import kotlin.math.roundToInt + +/** + * This is a modified version of: + * https://gist.github.com/adamp/07d468f4bcfe632670f305ce3734f511 + */ + +class PagerState( + currentPage: Int = 0, + minPage: Int = 0, + maxPage: Int = 0 +) { + private var _minPage by mutableStateOf(minPage) + var minPage: Int + get() = _minPage + set(value) { + _minPage = value.coerceAtMost(_maxPage) + _currentPage = _currentPage.coerceIn(_minPage, _maxPage) + } + + private var _maxPage by mutableStateOf(maxPage, structuralEqualityPolicy()) + var maxPage: Int + get() = _maxPage + set(value) { + _maxPage = value.coerceAtLeast(_minPage) + _currentPage = _currentPage.coerceIn(_minPage, maxPage) + } + + private var _currentPage by mutableStateOf(currentPage.coerceIn(minPage, maxPage)) + var currentPage: Int + get() = _currentPage + set(value) { + _currentPage = value.coerceIn(minPage, maxPage) + } + + enum class SelectionState { Selected, Undecided } + + var selectionState by mutableStateOf(SelectionState.Selected) + + suspend inline fun selectPage(block: PagerState.() -> R): R = try { + selectionState = SelectionState.Undecided + block() + } finally { + selectPage() + } + + suspend fun selectPage() { + currentPage -= currentPageOffset.roundToInt() + snapToOffset(0f) + selectionState = SelectionState.Selected + } + + private var _currentPageOffset = Animatable(0f).apply { + updateBounds(-1f, 1f) + } + val currentPageOffset: Float + get() = _currentPageOffset.value + + suspend fun snapToOffset(offset: Float) { + val max = if (currentPage == minPage) 0f else 1f + val min = if (currentPage == maxPage) 0f else -1f + _currentPageOffset.snapTo(offset.coerceIn(min, max)) + } + + suspend fun fling(velocity: Float) { + if (velocity < 0 && currentPage == maxPage) return + if (velocity > 0 && currentPage == minPage) return + + _currentPageOffset.animateTo(currentPageOffset.roundToInt().toFloat()) + selectPage() + } + + override fun toString(): String = "PagerState{minPage=$minPage, maxPage=$maxPage, " + + "currentPage=$currentPage, currentPageOffset=$currentPageOffset}" +} + +@Immutable +private data class PageData(val page: Int) : ParentDataModifier { + override fun Density.modifyParentData(parentData: Any?): Any = this@PageData +} + +private val Measurable.page: Int + get() = (parentData as? PageData)?.page ?: error("no PageData for measurable $this") + +@Composable +fun Pager( + state: PagerState, + modifier: Modifier = Modifier, + offscreenLimit: Int = 2, + pageContent: @Composable PagerScope.() -> Unit +) { + var pageSize by remember { mutableStateOf(0) } + val coroutineScope = rememberCoroutineScope() + Layout( + content = { + val minPage = (state.currentPage - offscreenLimit).coerceAtLeast(state.minPage) + val maxPage = (state.currentPage + offscreenLimit).coerceAtMost(state.maxPage) + + for (page in minPage..maxPage) { + val pageData = PageData(page) + val scope = PagerScope(state, page) + key(pageData) { + Box(contentAlignment = Alignment.Center, modifier = pageData) { + scope.pageContent() + } + } + } + }, + modifier = modifier.draggable( + orientation = Orientation.Horizontal, + onDragStarted = { + state.selectionState = PagerState.SelectionState.Undecided + }, + onDragStopped = { velocity -> + coroutineScope.launch { + // Velocity is in pixels per second, but we deal in percentage offsets, so we + // need to scale the velocity to match + state.fling(velocity / pageSize) + } + }, + state = rememberDraggableState { dy -> + coroutineScope.launch { + with(state) { + val pos = pageSize * currentPageOffset + val max = if (currentPage == minPage) 0 else pageSize * offscreenLimit + val min = if (currentPage == maxPage) 0 else -pageSize * offscreenLimit + val newPos = (pos + dy).coerceIn(min.toFloat(), max.toFloat()) + snapToOffset(newPos / pageSize) + } + } + }, + ) + ) { measurables, constraints -> + layout(constraints.maxWidth, constraints.maxHeight) { + val currentPage = state.currentPage + val offset = state.currentPageOffset + val childConstraints = constraints.copy(minWidth = 0, minHeight = 0) + + measurables + .map { + it.measure(childConstraints) to it.page + } + .forEach { (placeable, page) -> + // TODO: current this centers each page. We should investigate reading + // gravity modifiers on the child, or maybe as a param to Pager. + val xCenterOffset = (constraints.maxWidth - placeable.width) / 2 + val yCenterOffset = (constraints.maxHeight - placeable.height) / 2 + + if (currentPage == page) { + pageSize = placeable.width + } + + val xItemOffset = ((page + offset - currentPage) * placeable.width).roundToInt() + + placeable.place( + x = xCenterOffset + xItemOffset, + y = yCenterOffset + ) + } + } + } +} + +/** + * Scope for [Pager] content. + */ +class PagerScope( + private val state: PagerState, + val page: Int +) { + /** + * Returns the current selected page + */ + val currentPage: Int + get() = state.currentPage + + /** + * Returns the current selected page offset + */ + val currentPageOffset: Float + get() = state.currentPageOffset + + /** + * Returns the current selection state + */ + val selectionState: PagerState.SelectionState + get() = state.selectionState +} diff --git a/app/src/main/java/org/tasks/compose/Subscription.kt b/app/src/main/java/org/tasks/compose/Subscription.kt new file mode 100644 index 000000000..284076d33 --- /dev/null +++ b/app/src/main/java/org/tasks/compose/Subscription.kt @@ -0,0 +1,419 @@ +package org.tasks.compose + +import android.content.Intent +import android.net.Uri +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredSize +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Divider +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.OutlinedButton +import androidx.compose.material.Slider +import androidx.compose.material.SliderDefaults +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +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.ColorFilter +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import org.tasks.R +import org.tasks.Tasks.Companion.IS_GENERIC +import org.tasks.compose.Constants.HALF_KEYLINE +import org.tasks.compose.Constants.KEYLINE_FIRST +import org.tasks.compose.PurchaseText.PurchaseText + +@Preview(showBackground = true, backgroundColor = 0xFFFFFF) +@Composable +private fun LightPreview() { + PurchaseText { _, _ -> } +} + +@Preview(showBackground = true, backgroundColor = 0x202124) +@Composable +private fun DarkPreview() { + PurchaseText { _, _ -> } +} + +object PurchaseText { + private const val POPPER = "\uD83C\uDF89" + + data class CarouselItem( + val title: Int, + val icon: Int, + val description: Int, + val tint: Boolean = true + ) + + private val featureList = listOf( + CarouselItem( + R.string.tasks_org_account, + R.drawable.ic_round_icon, + R.string.upgrade_tasks_org_account_description, + tint = false + ), + CarouselItem( + R.string.upgrade_more_customization, + R.drawable.ic_outline_palette_24px, + R.string.upgrade_more_customization_description + ), + CarouselItem( + R.string.open_source, + R.drawable.ic_octocat, + R.string.upgrade_open_source_description + ), + CarouselItem( + R.string.upgrade_desktop_access, + R.drawable.ic_outline_computer_24px, + R.string.upgrade_desktop_access_description + ), + CarouselItem( + R.string.gtasks_GPr_header, + R.drawable.ic_google, + R.string.upgrade_google_tasks, + false + ), + CarouselItem( + R.string.davx5, + R.drawable.ic_davx5_icon_green_bg, + R.string.davx5_selection_description, + false + ), + CarouselItem( + R.string.caldav, + R.drawable.ic_webdav_logo, + R.string.caldav_selection_description + ), + CarouselItem( + R.string.etesync, + R.drawable.ic_etesync, + R.string.etesync_selection_description, + false + ), + CarouselItem( + R.string.decsync, + R.drawable.ic_decsync, + R.string.decsync_selection_description, + false + ), + CarouselItem( + R.string.upgrade_automation, + R.drawable.ic_tasker, + R.string.upgrade_automation_description, + false, + ) + ) + + @Composable + fun PurchaseText( + nameYourPrice: MutableState = mutableStateOf(false), + sliderPosition: MutableState = mutableStateOf(0f), + github: Boolean = false, + fdroid: Boolean = IS_GENERIC, + subscribe: (Int, Boolean) -> Unit + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .verticalScroll(rememberScrollState()) + .background(color = colorResource(R.color.content_background)), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + GreetingText(R.string.upgrade_blurb_1) + GreetingText(R.string.upgrade_blurb_2) + Spacer(Modifier.height(KEYLINE_FIRST)) + val pagerState = remember { + PagerState(maxPage = (featureList.size - 1).coerceAtLeast(0)) + } + Pager(state = pagerState, modifier = Modifier.fillMaxWidth().height(200.dp)) { + PagerItem(featureList[page], nameYourPrice.value && page == 0) + } + if (github) { + SponsorButton() + } else { + GooglePlayButtons(nameYourPrice, sliderPosition, pagerState, subscribe) + } + } + } + + @Composable + fun SponsorButton() { + val context = LocalContext.current + OutlinedButton( + onClick = { + context.startActivity( + Intent( + Intent.ACTION_VIEW, + Uri.parse(context.getString(R.string.url_sponsor)) + ) + ) + }, + colors = ButtonDefaults.textButtonColors( + backgroundColor = MaterialTheme.colors.secondary, + contentColor = MaterialTheme.colors.onSecondary + ), + modifier = Modifier.padding(KEYLINE_FIRST, 0.dp, KEYLINE_FIRST, KEYLINE_FIRST) + ) { + Row { + Icon( + painter = painterResource(R.drawable.ic_outline_favorite_border_24px), + contentDescription = null + ) + Text( + text = stringResource(R.string.github_sponsor), + color = MaterialTheme.colors.onSecondary, + style = MaterialTheme.typography.body1 + ) + } + } + } + + @Composable + fun GreetingText(resId: Int) { + Text( + modifier = Modifier.padding(KEYLINE_FIRST, KEYLINE_FIRST, KEYLINE_FIRST, 0.dp), + text = stringResource(resId), + color = MaterialTheme.colors.onBackground, + style = MaterialTheme.typography.body1, + textAlign = TextAlign.Center, + ) + } + + @Composable + fun GooglePlayButtons( + nameYourPrice: MutableState, + sliderPosition: MutableState, + pagerState: PagerState, + subscribe: (Int, Boolean) -> Unit, + ) { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Divider(color = MaterialTheme.colors.onSurface, thickness = 0.25.dp) + Spacer(Modifier.height(KEYLINE_FIRST)) + if (nameYourPrice.value) { + NameYourPrice(sliderPosition, subscribe) + } else { + TasksAccount(subscribe) + } + Spacer(Modifier.height(KEYLINE_FIRST)) + OutlinedButton( + onClick = { + nameYourPrice.value = !nameYourPrice.value + pagerState.currentPage = 0 + }, + colors = ButtonDefaults.textButtonColors( + backgroundColor = Color.Transparent + ) + ) { + Text( + text = stringResource( + if (nameYourPrice.value) + R.string.back + else + R.string.more_options + ), + color = MaterialTheme.colors.secondary, + style = MaterialTheme.typography.body1 + ) + } + Text( + text = stringResource(R.string.pro_free_trial), + style = MaterialTheme.typography.caption, + modifier = Modifier + .fillMaxWidth(.75f) + .padding(KEYLINE_FIRST), + color = MaterialTheme.colors.onBackground, + textAlign = TextAlign.Center, + ) + } + } + + @Composable + fun PagerItem( + feature: CarouselItem, + disabled: Boolean = false + ) { + Column { + Box( + modifier = Modifier + .weight(1f) + .fillMaxWidth(.5f), + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(HALF_KEYLINE), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Image( + painter = painterResource(feature.icon), + contentDescription = null, + modifier = Modifier.requiredSize(72.dp), + alignment = Alignment.Center, + colorFilter = if (feature.tint) { + ColorFilter.tint(colorResource(R.color.icon_tint_with_alpha)) + } else { + null + } + ) + Text( + text = stringResource(feature.title), + modifier = Modifier + .fillMaxWidth() + .padding(0.dp, 4.dp), + color = MaterialTheme.colors.onBackground, + style = TextStyle( + fontWeight = FontWeight.Bold, + fontSize = 14.sp, + letterSpacing = 0.25.sp + ), + textAlign = TextAlign.Center + ) + Text( + text = stringResource(if (disabled) R.string.account_not_included else feature.description), + modifier = Modifier.fillMaxWidth(), + color = if (disabled) Color.Red else MaterialTheme.colors.onBackground, + style = TextStyle( + fontWeight = if (disabled) FontWeight.Bold else FontWeight.Normal, + fontSize = 12.sp, + letterSpacing = 0.4.sp + ), + textAlign = TextAlign.Center, + ) + } + } + } + } + + @Composable + fun TasksAccount(subscribe: (Int, Boolean) -> Unit) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(KEYLINE_FIRST, 0.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + PurchaseButton( + price = 30, + popperText = "${stringResource(R.string.save_percent, 16)} $POPPER", + onClick = subscribe + ) + Spacer(Modifier.width(KEYLINE_FIRST)) + PurchaseButton( + price = 3, + monthly = true, + onClick = subscribe + ) + } + } + } + + @Composable + fun PurchaseButton( + price: Int, + monthly: Boolean = false, + popperText: String = "", + onClick: (Int, Boolean) -> Unit + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + OutlinedButton( + onClick = { onClick(price, monthly) }, + colors = ButtonDefaults.textButtonColors( + backgroundColor = MaterialTheme.colors.secondary + ) + ) { + Text( + text = stringResource( + if (monthly) R.string.price_per_month else R.string.price_per_year, + price + ), + color = MaterialTheme.colors.onSecondary, + style = MaterialTheme.typography.body1 + ) + } + Text( + text = popperText, + color = MaterialTheme.colors.onSurface, + style = MaterialTheme.typography.caption, + ) + } + } + + @Composable + fun NameYourPrice(sliderPosition: MutableState, subscribe: (Int, Boolean) -> Unit) { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Row(Modifier.fillMaxWidth()) { + Slider( + modifier = Modifier.padding(KEYLINE_FIRST, 0.dp, KEYLINE_FIRST, HALF_KEYLINE), + value = sliderPosition.value, + onValueChange = { sliderPosition.value = it }, + valueRange = 1f..25f, + steps = 25, + colors = SliderDefaults.colors( + thumbColor = MaterialTheme.colors.secondary, + activeTrackColor = MaterialTheme.colors.secondary, + inactiveTrackColor = colorResource(R.color.text_tertiary), + activeTickColor = Color.Transparent, + inactiveTickColor = Color.Transparent + ) + ) + } + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + PurchaseButton( + price = sliderPosition.value.toInt(), + popperText = if (sliderPosition.value.toInt() >= 5) + "${stringResource(R.string.above_average, 16)} $POPPER" + else + "", + onClick = subscribe + ) + if (sliderPosition.value.toInt() < 3) { + Spacer(Modifier.width(KEYLINE_FIRST)) + PurchaseButton( + price = sliderPosition.value.toInt(), + monthly = true, + popperText = "${stringResource(R.string.above_average)} $POPPER", + onClick = subscribe + ) + } + } + } + } +} diff --git a/app/src/main/java/org/tasks/dialogs/ColorPalettePicker.kt b/app/src/main/java/org/tasks/dialogs/ColorPalettePicker.kt index 36af2a1fc..045b9e694 100644 --- a/app/src/main/java/org/tasks/dialogs/ColorPalettePicker.kt +++ b/app/src/main/java/org/tasks/dialogs/ColorPalettePicker.kt @@ -16,8 +16,7 @@ import butterknife.ButterKnife import dagger.hilt.android.AndroidEntryPoint import org.tasks.R import org.tasks.billing.Inventory -import org.tasks.billing.PurchaseDialog.Companion.FRAG_TAG_PURCHASE_DIALOG -import org.tasks.billing.PurchaseDialog.Companion.newPurchaseDialog +import org.tasks.billing.PurchaseActivity import org.tasks.dialogs.ColorPickerAdapter.Palette import org.tasks.dialogs.ColorWheelPicker.Companion.newColorWheel import org.tasks.themes.ColorProvider @@ -108,7 +107,7 @@ class ColorPalettePicker : DialogFragment() { builder.setNegativeButton(R.string.cancel, null) } else { builder.setPositiveButton(R.string.upgrade_to_pro) { _: DialogInterface?, _: Int -> - newPurchaseDialog().show(parentFragmentManager, FRAG_TAG_PURCHASE_DIALOG) + startActivity(Intent(context, PurchaseActivity::class.java)) } } return builder.show() diff --git a/app/src/main/java/org/tasks/dialogs/ColorWheelPicker.kt b/app/src/main/java/org/tasks/dialogs/ColorWheelPicker.kt index 2e1b933b4..5db05c422 100644 --- a/app/src/main/java/org/tasks/dialogs/ColorWheelPicker.kt +++ b/app/src/main/java/org/tasks/dialogs/ColorWheelPicker.kt @@ -15,8 +15,7 @@ import com.flask.colorpicker.builder.ColorPickerDialogBuilder import dagger.hilt.android.AndroidEntryPoint import org.tasks.R import org.tasks.billing.Inventory -import org.tasks.billing.PurchaseDialog.Companion.FRAG_TAG_PURCHASE_DIALOG -import org.tasks.billing.PurchaseDialog.Companion.newPurchaseDialog +import org.tasks.billing.PurchaseActivity import javax.inject.Inject @AndroidEntryPoint @@ -65,8 +64,10 @@ class ColorWheelPicker : DialogFragment() { if (inventory.purchasedThemes()) { deliverSelection() } else { - newPurchaseDialog(this, REQUEST_PURCHASE) - .show(parentFragmentManager, FRAG_TAG_PURCHASE_DIALOG) + startActivityForResult( + Intent(context, PurchaseActivity::class.java), + REQUEST_PURCHASE + ) } } .setNegativeButton(R.string.cancel, null) diff --git a/app/src/main/java/org/tasks/dialogs/IconPickerDialog.java b/app/src/main/java/org/tasks/dialogs/IconPickerDialog.java index 1869649eb..cccfeca2e 100644 --- a/app/src/main/java/org/tasks/dialogs/IconPickerDialog.java +++ b/app/src/main/java/org/tasks/dialogs/IconPickerDialog.java @@ -1,26 +1,29 @@ package org.tasks.dialogs; -import static org.tasks.billing.PurchaseDialog.newPurchaseDialog; - import android.app.Activity; import android.app.Dialog; import android.content.DialogInterface; +import android.content.Intent; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.DialogFragment; import androidx.recyclerview.widget.RecyclerView; -import butterknife.BindView; -import butterknife.ButterKnife; -import dagger.hilt.android.AndroidEntryPoint; -import javax.inject.Inject; + import org.tasks.R; import org.tasks.billing.Inventory; -import org.tasks.billing.PurchaseDialog; +import org.tasks.billing.PurchaseActivity; import org.tasks.themes.CustomIcons; +import javax.inject.Inject; + +import butterknife.BindView; +import butterknife.ButterKnife; +import dagger.hilt.android.AndroidEntryPoint; + @AndroidEntryPoint public class IconPickerDialog extends DialogFragment { @@ -65,8 +68,8 @@ public class IconPickerDialog extends DialogFragment { if (!inventory.getHasPro()) { builder.setPositiveButton( R.string.upgrade_to_pro, - (dialog, which) -> newPurchaseDialog() - .show(getParentFragmentManager(), PurchaseDialog.getFRAG_TAG_PURCHASE_DIALOG())); + (dialog, which) -> startActivity(new Intent(getContext(), PurchaseActivity.class)) + ); } return builder.show(); } diff --git a/app/src/main/java/org/tasks/dialogs/WhatsNewDialog.kt b/app/src/main/java/org/tasks/dialogs/WhatsNewDialog.kt index fc201b3a0..8a8ac1ff7 100644 --- a/app/src/main/java/org/tasks/dialogs/WhatsNewDialog.kt +++ b/app/src/main/java/org/tasks/dialogs/WhatsNewDialog.kt @@ -6,23 +6,16 @@ import android.content.Intent import android.net.Uri import android.os.Bundle import android.text.method.LinkMovementMethod -import android.view.LayoutInflater import android.view.View -import android.widget.TextView import androidx.fragment.app.DialogFragment -import butterknife.BindView -import butterknife.ButterKnife -import butterknife.OnClick -import com.google.android.material.button.MaterialButton import dagger.hilt.android.AndroidEntryPoint import io.noties.markwon.Markwon -import io.noties.markwon.ext.strikethrough.StrikethroughPlugin -import org.tasks.BuildConfig import org.tasks.R +import org.tasks.Tasks.Companion.IS_GENERIC import org.tasks.analytics.Firebase import org.tasks.billing.Inventory -import org.tasks.billing.PurchaseDialog.Companion.FRAG_TAG_PURCHASE_DIALOG -import org.tasks.billing.PurchaseDialog.Companion.newPurchaseDialog +import org.tasks.billing.PurchaseActivity +import org.tasks.databinding.DialogWhatsNewBinding import org.tasks.preferences.Preferences import java.io.BufferedReader import javax.inject.Inject @@ -36,26 +29,17 @@ class WhatsNewDialog : DialogFragment() { @Inject lateinit var preferences: Preferences @Inject lateinit var inventory: Inventory - @BindView(R.id.changelog) lateinit var changelog: TextView - @BindView(R.id.action_question) lateinit var actionQuestion: TextView - @BindView(R.id.action_text) lateinit var actionText: TextView - @BindView(R.id.action_button) lateinit var actionButton: MaterialButton - @BindView(R.id.dismiss_button) lateinit var dismissButton: MaterialButton - private var displayedRate = false private var displayedSubscribe = false override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val view: View = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_whats_new, null) - ButterKnife.bind(this, view) + val binding = DialogWhatsNewBinding.inflate(layoutInflater) val textStream = requireContext().assets.open("CHANGELOG.md") val text = BufferedReader(textStream.reader()).readText() - val markwon = Markwon.builder(requireContext()) - .usePlugin(StrikethroughPlugin.create()) - .build() - changelog.movementMethod = LinkMovementMethod.getInstance() - changelog.text = markwon.toMarkdown(text) + val markwon = Markwon.builder(requireContext()).build() + binding.changelog.movementMethod = LinkMovementMethod.getInstance() + binding.changelog.text = markwon.toMarkdown(text) val begForSubscription = !inventory.hasPro val begForRating = !preferences.getBoolean(R.string.p_clicked_rate, false) @@ -63,43 +47,51 @@ class WhatsNewDialog : DialogFragment() { && (!begForSubscription || Random.nextBoolean()) when { - BuildConfig.FLAVOR == "generic" -> { - actionText.text = getString(R.string.upgrade_blurb_4) - actionButton.text = getString(R.string.TLA_menu_donate) - actionButton.setOnClickListener { onDonateClick() } + IS_GENERIC -> { + binding.actionQuestion.setText(R.string.enjoying_tasks) + binding.actionText.setText(R.string.upgrade_blurb_4) + binding.actionButton.text = getString(R.string.TLA_menu_donate) + binding.actionButton.setOnClickListener { onDonateClick() } } begForRating -> { displayedRate = true - actionButton.text = getString(R.string.rate_tasks) - actionButton.setOnClickListener { onRateClick() } + binding.actionQuestion.setText(R.string.enjoying_tasks) + binding.actionButton.setText(R.string.rate_tasks) + binding.actionButton.setOnClickListener { onRateClick() } } begForSubscription -> { displayedSubscribe = true - actionText.text = getString(R.string.support_development_subscribe) - actionButton.text = getString(R.string.name_your_price) - actionButton.setOnClickListener { onSubscribeClick() } + binding.actionQuestion.setText(R.string.tasks_needs_your_support) + binding.actionText.setText(R.string.support_development_subscribe) + binding.actionButton.setText(R.string.name_your_price) + binding.actionButton.setOnClickListener { onSubscribeClick() } } else -> { - actionQuestion.visibility = View.GONE - actionText.visibility = View.GONE - actionButton.visibility = View.GONE - dismissButton.text = getString(R.string.got_it) + binding.actionQuestion.visibility = View.GONE + binding.actionText.visibility = View.GONE + binding.actionButton.visibility = View.GONE + binding.dismissButton.text = getString(R.string.got_it) } } if (!resources.getBoolean(R.bool.whats_new_action)) { - actionText.visibility = View.GONE + binding.actionText.visibility = View.GONE + } + + binding.dismissButton.setOnClickListener { + logClick(false) + dismiss() } return dialogBuilder.newDialog() - .setView(view) + .setView(binding.root) .show() } private fun onSubscribeClick() { logClick(true) dismiss() - newPurchaseDialog().show(parentFragmentManager, FRAG_TAG_PURCHASE_DIALOG) + startActivity(Intent(context, PurchaseActivity::class.java)) } private fun onRateClick() { @@ -119,12 +111,6 @@ class WhatsNewDialog : DialogFragment() { super.onCancel(dialog) } - @OnClick(R.id.dismiss_button) - fun onDismissClick() { - logClick(false) - dismiss() - } - private fun logClick(click: Boolean) { firebase.logEvent( R.string.event_whats_new, diff --git a/app/src/main/java/org/tasks/locale/ui/activity/TaskerCreateTaskActivity.java b/app/src/main/java/org/tasks/locale/ui/activity/TaskerCreateTaskActivity.java index 3672d19b2..7787cc494 100755 --- a/app/src/main/java/org/tasks/locale/ui/activity/TaskerCreateTaskActivity.java +++ b/app/src/main/java/org/tasks/locale/ui/activity/TaskerCreateTaskActivity.java @@ -1,22 +1,25 @@ package org.tasks.locale.ui.activity; -import static org.tasks.billing.PurchaseDialog.newPurchaseDialog; - import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.view.MenuItem; + import androidx.appcompat.widget.Toolbar; -import dagger.hilt.android.AndroidEntryPoint; -import javax.inject.Inject; + import net.dinglisch.android.tasker.TaskerPlugin; + import org.tasks.LocalBroadcastManager; import org.tasks.R; import org.tasks.billing.Inventory; -import org.tasks.billing.PurchaseDialog; +import org.tasks.billing.PurchaseActivity; import org.tasks.databinding.ActivityTaskerCreateBinding; import org.tasks.locale.bundle.TaskCreationBundle; +import javax.inject.Inject; + +import dagger.hilt.android.AndroidEntryPoint; + @AndroidEntryPoint public final class TaskerCreateTaskActivity extends AbstractFragmentPluginAppCompatActivity implements Toolbar.OnMenuItemClickListener { @@ -55,8 +58,7 @@ public final class TaskerCreateTaskActivity extends AbstractFragmentPluginAppCom } private void showPurchaseDialog() { - newPurchaseDialog() - .show(getSupportFragmentManager(), PurchaseDialog.getFRAG_TAG_PURCHASE_DIALOG()); + startActivity(new Intent(this, PurchaseActivity.class)); } @Override diff --git a/app/src/main/java/org/tasks/preferences/fragments/BaseAccountPreference.kt b/app/src/main/java/org/tasks/preferences/fragments/BaseAccountPreference.kt index 073915cdd..b5cd182cf 100644 --- a/app/src/main/java/org/tasks/preferences/fragments/BaseAccountPreference.kt +++ b/app/src/main/java/org/tasks/preferences/fragments/BaseAccountPreference.kt @@ -9,7 +9,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.tasks.R import org.tasks.billing.BillingClient -import org.tasks.billing.PurchaseDialog +import org.tasks.billing.PurchaseActivity import org.tasks.injection.InjectingPreferenceFragment import javax.inject.Inject @@ -38,10 +38,11 @@ abstract class BaseAccountPreference : InjectingPreferenceFragment() { protected abstract suspend fun removeAccount() - protected fun showPurchaseDialog(tasksPayment: Boolean = false): Boolean { - PurchaseDialog - .newPurchaseDialog(this, REQUEST_PURCHASE, tasksPayment) - .show(parentFragmentManager, PurchaseDialog.FRAG_TAG_PURCHASE_DIALOG) + protected fun showPurchaseDialog(): Boolean { + startActivityForResult( + Intent(context, PurchaseActivity::class.java), + REQUEST_PURCHASE + ) return false } diff --git a/app/src/main/java/org/tasks/preferences/fragments/LookAndFeel.kt b/app/src/main/java/org/tasks/preferences/fragments/LookAndFeel.kt index 156aea46b..727a2bf3b 100644 --- a/app/src/main/java/org/tasks/preferences/fragments/LookAndFeel.kt +++ b/app/src/main/java/org/tasks/preferences/fragments/LookAndFeel.kt @@ -19,8 +19,7 @@ import org.tasks.R import org.tasks.Strings.isNullOrEmpty import org.tasks.activities.FilterSelectionActivity import org.tasks.billing.Inventory -import org.tasks.billing.PurchaseDialog.Companion.FRAG_TAG_PURCHASE_DIALOG -import org.tasks.billing.PurchaseDialog.Companion.newPurchaseDialog +import org.tasks.billing.PurchaseActivity import org.tasks.dialogs.ColorPalettePicker import org.tasks.dialogs.ColorPalettePicker.Companion.newColorPalette import org.tasks.dialogs.ColorPickerAdapter @@ -182,8 +181,10 @@ class LookAndFeel : InjectingPreferenceFragment() { if (inventory.purchasedThemes() || ThemeBase(index).isFree) { setBaseTheme(index) } else { - newPurchaseDialog(this, REQUEST_PURCHASE) - .show(parentFragmentManager, FRAG_TAG_PURCHASE_DIALOG) + startActivityForResult( + Intent(context, PurchaseActivity::class.java), + REQUEST_PURCHASE + ) } } else { setBaseTheme(index) diff --git a/app/src/main/java/org/tasks/preferences/fragments/MainSettingsFragment.kt b/app/src/main/java/org/tasks/preferences/fragments/MainSettingsFragment.kt index 7b3caae3a..9d44d7ee1 100644 --- a/app/src/main/java/org/tasks/preferences/fragments/MainSettingsFragment.kt +++ b/app/src/main/java/org/tasks/preferences/fragments/MainSettingsFragment.kt @@ -13,7 +13,7 @@ import org.tasks.R import org.tasks.billing.BillingClient import org.tasks.billing.Inventory import org.tasks.billing.Purchase -import org.tasks.billing.PurchaseDialog +import org.tasks.billing.PurchaseActivity import org.tasks.caldav.BaseCaldavAccountSettingsActivity import org.tasks.data.CaldavAccount import org.tasks.data.GoogleTaskAccount @@ -47,9 +47,7 @@ class MainSettingsFragment : InjectingPreferenceFragment() { findPreference(R.string.add_account).setOnPreferenceClickListener { addAccount() } findPreference(R.string.name_your_price).setOnPreferenceClickListener { - PurchaseDialog - .newPurchaseDialog() - .show(parentFragmentManager, PurchaseDialog.FRAG_TAG_PURCHASE_DIALOG) + startActivity(Intent(context, PurchaseActivity::class.java)) false } diff --git a/app/src/main/java/org/tasks/preferences/fragments/TaskerListNotification.kt b/app/src/main/java/org/tasks/preferences/fragments/TaskerListNotification.kt index 53b56437f..0052c4948 100644 --- a/app/src/main/java/org/tasks/preferences/fragments/TaskerListNotification.kt +++ b/app/src/main/java/org/tasks/preferences/fragments/TaskerListNotification.kt @@ -8,8 +8,7 @@ import dagger.hilt.android.AndroidEntryPoint import org.tasks.R import org.tasks.activities.FilterSelectionActivity import org.tasks.billing.Inventory -import org.tasks.billing.PurchaseDialog.Companion.FRAG_TAG_PURCHASE_DIALOG -import org.tasks.billing.PurchaseDialog.Companion.newPurchaseDialog +import org.tasks.billing.PurchaseActivity import org.tasks.injection.InjectingPreferenceFragment import org.tasks.locale.bundle.ListNotificationBundle import org.tasks.preferences.DefaultFilterProvider @@ -58,8 +57,10 @@ class TaskerListNotification : InjectingPreferenceFragment() { } if (!inventory.purchasedTasker()) { - newPurchaseDialog(this, REQUEST_SUBSCRIPTION) - .show(parentFragmentManager, FRAG_TAG_PURCHASE_DIALOG) + startActivityForResult( + Intent(context, PurchaseActivity::class.java), + REQUEST_SUBSCRIPTION + ) } } diff --git a/app/src/main/java/org/tasks/preferences/fragments/TasksAccount.kt b/app/src/main/java/org/tasks/preferences/fragments/TasksAccount.kt index cfa262714..57176c5f2 100644 --- a/app/src/main/java/org/tasks/preferences/fragments/TasksAccount.kt +++ b/app/src/main/java/org/tasks/preferences/fragments/TasksAccount.kt @@ -175,9 +175,7 @@ class TasksAccount : BaseAccountPreference() { } } } else { - setOnPreferenceClickListener { - showPurchaseDialog(tasksPayment = true) - } + setOnPreferenceClickListener { showPurchaseDialog() } if (subscription == null || subscription.isTasksSubscription) { setTitle(R.string.button_subscribe) setSummary(R.string.your_subscription_expired) diff --git a/app/src/main/java/org/tasks/ui/NavigationDrawerFragment.kt b/app/src/main/java/org/tasks/ui/NavigationDrawerFragment.kt index e892d2d18..41f16a023 100644 --- a/app/src/main/java/org/tasks/ui/NavigationDrawerFragment.kt +++ b/app/src/main/java/org/tasks/ui/NavigationDrawerFragment.kt @@ -24,8 +24,7 @@ import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch import org.tasks.LocalBroadcastManager import org.tasks.R -import org.tasks.billing.PurchaseDialog.Companion.FRAG_TAG_PURCHASE_DIALOG -import org.tasks.billing.PurchaseDialog.Companion.newPurchaseDialog +import org.tasks.billing.PurchaseActivity import org.tasks.data.TaskDao import org.tasks.dialogs.NewFilterDialog.Companion.newFilterDialog import org.tasks.filters.FilterProvider @@ -85,7 +84,7 @@ class NavigationDrawerFragment : Fragment() { } else if (item is NavigationDrawerAction) { when (item.requestCode) { REQUEST_PURCHASE -> - newPurchaseDialog().show(parentFragmentManager, FRAG_TAG_PURCHASE_DIALOG) + startActivity(Intent(context, PurchaseActivity::class.java)) REQUEST_DONATE -> startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.url_donate)))) REQUEST_NEW_FILTER -> newFilterDialog().show(parentFragmentManager, FRAG_TAG_NEW_FILTER) else -> activity?.startActivityForResult(item.intent, item.requestCode) diff --git a/app/src/main/res/drawable/ic_tasker.png b/app/src/main/res/drawable/ic_tasker.png new file mode 100644 index 000000000..a368d0921 Binary files /dev/null and b/app/src/main/res/drawable/ic_tasker.png differ diff --git a/app/src/main/res/layout/activity_purchase.xml b/app/src/main/res/layout/activity_purchase.xml deleted file mode 100644 index cf90d37a1..000000000 --- a/app/src/main/res/layout/activity_purchase.xml +++ /dev/null @@ -1,175 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/dialog_whats_new.xml b/app/src/main/res/layout/dialog_whats_new.xml index bd28643ca..1d4d28dc9 100644 --- a/app/src/main/res/layout/dialog_whats_new.xml +++ b/app/src/main/res/layout/dialog_whats_new.xml @@ -45,6 +45,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" + android:paddingTop="@dimen/half_keyline_first" android:paddingStart="@dimen/keyline_first" android:paddingEnd="@dimen/keyline_first" android:text="@string/tell_me_how_im_doing" diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 42717b368..1e52a02d6 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -553,6 +553,8 @@ Při spuštění Seznamy Přidat filtr + Strávil jsem tisíce hodin prací na Tasks a bezplatně zveřejňuji veškerý zdrojový kód online. Za účelem podpory mé práce některé funkce vyžadují předplatné + Ahoj! Jmenuji se Alex. Jsem nezávislý vývojář Tasks Více barev Neplatné uživatelské jméno nebo heslo Synchronizujte své úkoly pomocí aplikace DAVx⁵ diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index cb79882ad..4cd92f0ba 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -163,6 +163,8 @@ Boble-stil Tilbage Din støtte betydet meget for mig, tak! + Jeg har brugt tusindvis af arbejdstimer på Tasks, og jeg udgiver hele kildekoden gratis online. For at støtte mit arbejde kræver nogle funktioner et abonnement + Hej! Jeg hedder Alex. Jeg er den selvstændige udvikler bag Tasks Farvehjul Ugyldigt brugernavn eller kodeord Ringetone, vibrér og mere @@ -641,8 +643,6 @@ Dit abonnement er udløbet. Abonner nu for at genoptage tjenesten. Er underopgaver Har underopgaver - $%s/m - $%s/år $%s/år Ingen kvalificeret Google Play abonnement fundet diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index c31ec89d7..186960688 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -493,6 +493,8 @@ Klingelton, Vibration und mehr Ungültiger Benutzername oder Passwort Farbkreis + Hallo! Mein Name ist Alex. Ich bin der unabhängige Entwickler hinter Tasks + Ich habe Tausende von Stunden mit der Arbeit an Tasks verbracht und veröffentliche den gesamten Quellcode kostenlos online. Um meine Arbeit zu unterstützen, erfordern einige Funktionen ein Abonnement Ihre Unterstützung bedeutet mir sehr viel, danke! Zurück Stil der Marken @@ -621,9 +623,7 @@ Ihr Abonnement ist abgelaufen. Abonnieren Sie jetzt, um den Dienst fortzusetzen. Tasks sammelt Standortdaten, um standortbezogene Erinnerungen zu ermöglichen, auch wenn die App geschlossen oder nicht in Gebrauch ist. fünften - %s $/Monat %s $/Monat - %s $/Jahr %s $/Jahr %d %% sparen Kein qualifiziertes Googl Play -Abo gefunden diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index d2509856f..caa67008b 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -492,6 +492,8 @@ Nombre de usuario o contraseña inválidos El sistema por defecto Rueda de colores + ¡Hola! Me llamo Alex. Soy el desarrollador independiente detrás de Tasks + He pasado miles de horas trabajando en Tareas, y publico todo el código fuente en línea de forma gratuita. Para apoyar mi trabajo, algunas características requieren una suscripción Su apoyo significa mucho para mí, ¡gracias! Volver Estilo de chip @@ -616,9 +618,7 @@ Es una subtarea Tiene subtareas Suscripción actual: %s - $%s/mes $%s/mes - $%s/año $%s/año Nivel de suscripción insuficiente. Actualice su suscripción para reanudar el servicio. Vuestra suscripción ha expirado. Suscribe ahora a resume servicio. diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index a6edd7bf5..2cb3143fe 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -492,6 +492,8 @@ Erabiltzaile-izen edo pasahitz okerra Sisteman lehenetsia Kolore-gurpila + Kaixo! Nire izana Alex da. Ni naiz Tasks aplikazioaren garatzaile independentea + Milaka ordu eman ditut Tsks aplikazioan lanean, eta kode guztia argitaratzen dut doan. Nire lana babesteko ezaugarri batzuk harpidetza eskatzen dute Zure babesa asko da niretzat, eskerrik asko! Atzera Txip estiloa @@ -592,9 +594,7 @@ Autorizazioa ezeztatuta Egin bat r/task-ekin Uneko harpidetza: %s - $%s hilean $%s hilean - $%s urtean $%s urtean Harpidetza maila ez da nahikoa. Hobetu zure harpidetza zerbitzua berrekiteko. Zure harpidetza agortu da. Harpidetu orain zerbitzua berrekiteko. diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index d22b010fc..165d679fe 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -545,6 +545,8 @@ Sulje automaattisesti kun valitaan tehtävä listalta Paikka asetukset Paikat + Olen käyttänyt tuhansia tunteja työskennellen Task ohjelman parissa, ja julkaisen koko lähdekoodin netissä ilmaiseksi. Työni tueksi joidenkin ohjelman ominaisuuksien käyttö vaatii tilauksen + Moi! Nimeni on Alex. Olen itsenäinen ohjelmistontekijä Task ohjelman takana Ongelmien ratkaisu Näytä ilmoitukset puettavassa laitteessasi Puettavan laitteen ilmoitukset diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 8a25a547b..0576cbe37 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -487,6 +487,8 @@ Nom d\'utilisateur ou mot de passe invalide Défaut du système Palette de couleurs + Salut ! Je m\'appelle Alex. Je suis le développeur indépendant à l\'origine de Tasks + J\'ai passé des milliers d\'heures à travailler sur Tasks, et je publie gratuitement tout le code source en ligne. Afin de soutenir mon travail, certaines fonctionnalités nécessitent un abonnement Votre soutien est très important pour moi, merci ! Retour Style d\'étiquette @@ -611,9 +613,7 @@ Est une sous-tâche A des sous-tâches Abonnement actuel : %s - %s $/mois %s $/mois - %s $/an %s $/an Niveau d\'abonnement insuffisant. Veuillez mettre votre abonnement à niveau pour reprendre le service. Votre abonnement a expiré. Abonnez-vous dès maintenant pour reprendre le service. diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index be9f354f1..f29a00aa2 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -490,6 +490,8 @@ Hibás felhasználónév vagy jelszó Alapértelmezett Színkör + Szia! Alexnek hívnak. Én vagyok a Tasks mögött álló független fejlesztő + Több ezer órát dolgoztam a Tasks appon, és a teljes forráskódot ingyenesen elérhetővé tettem. A munkám támogatása érdekében néhány funkció eléréséhez előfizetés szükséges A támogatásod sokat jelent nekem, köszönöm! Vissza Jelölő stílusa @@ -611,8 +613,6 @@ Teljes dátum mutatása Jelenlegi előfizetés: %s %s $/hónap - %s $/hó - %s $/év %s $/év Elégtelen előfizetési szint. Kérem, upgrade-elje az előfizetést a szolgáltatás folytatásához. Az előfizetés lejárt. Fizessen elő most a szolgáltatás folytatásához. diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index 933ba79fa..4de71319d 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -523,9 +523,7 @@ Otorisasi dibatalkan Gabung r/tasks Langganan saat ini: %s - $%s/Bln $%s/Bulan - $%s/Thn $%s/Tahun Tidak ditemukan langganan Google Play yang memenuhi syarat Tidak ditemukan sponsor GitHub yang memenuhi syarat diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index c213be2e3..be5696f81 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -467,6 +467,8 @@ Contornato Indietro Il tuo supporto significa molto per me, grazie! + Ho dedicato a Tasks migliaia di ore di lavoro, pubblicando tutto il codice sorgente online, gratuitamente. Per supportare il mio lavoro alcune funzioni richiedono un abbonamento + Ciao! Mi chiamo Alex. Sono lo sviluppatore, indipendente, di Tasks Cerchio cromatico Nome utente o password non validi Suoneria, vibrazione ed altro @@ -614,9 +616,7 @@ Contiene attività secondarie È un\'attività secondaria Abbonamento attuale: %s - %s $/mese %s $/mese - %s $/anno %s $/anno Livello di abbonamento insufficiente. Per favore aggiornalo per riattivare il servizio. Il tuo abbonamento è scaduto. Abbonati ora per riattivare il servizio. diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index ada03aa32..659271fc2 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -575,6 +575,8 @@ גישה מלאה למסד הנתונים של Tasks איפוס אופן הסידור התמיכה שלך יקרה ללבי, תודה רבה לך! + השקעתי אלפי שעות בעבודה על Tasks ואני מפרסם את כל קוד המקור באינטרנט בחינם. כדי לתמוך בעבודה שלי חלק מהתכונות דורשות הרשמה + היי! אני אלכס, המתכנת העצמאי שמאחורי Tasks שם המשתמש או הססמה שגויים צלצול, סוגי רטט ועוד השבתת שיפורי סוללה @@ -648,9 +650,7 @@ האפליקצייה הזו אוספת נתוני מיקום כדי לאפשר תזכורות על בסיס מיקום אפילו כשהיישומון סגור ולא בשימוש. מעקב אחר r/tasks מינוי נוכחי: %s - $%s/חודש $%s לחודש - $%s/שנה $%s לשנה תוקף המינוי שלך פג. ניתן להירשם כעת להמשיך את השירות. היא תת־משימה diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 177588be7..4034dfdb2 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -485,6 +485,7 @@ 네비게이션 서랍 위치 설정 위치 + 안녕하세요! Tasks의 1인 개발자 알렉스입니다 다크 테마 사용 시 채도를 낮춥니다 다크 테마 사용 시 채도를 낮추지 않습니다 저채도 색상 @@ -493,6 +494,7 @@ 배경색 채움 칩 스타일 + 저는 Tasks 개발에 엄청나게 많은 시간을 쏟고 있으며, 모든 소스코드를 웹에 무상으로 공개하고 있습니다. 저의 작업을 후원하기 위해 일부 기능은 구독이 필요합니다 당신의 후원은 저에게 큰 힘이 됩니다. 감사합니다! 색상환 유효하지 않은 사용자명과 비밀번호 @@ -603,8 +605,6 @@ 인증 취소됨 현재 구독: %s $%s/년 - $%s/년 - $%s/월 $%s/월 구독이 만료되었습니다. 서비스를 재개하려면 지금 구독하세요. 백그라운드 위치정보 diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml index 4e3ee496e..70af9020d 100644 --- a/app/src/main/res/values-nb/strings.xml +++ b/app/src/main/res/values-nb/strings.xml @@ -539,6 +539,8 @@ Flis Omrisset Flisstil + Jeg har brukt tusenvis av timer på å jobbe med Tasks, og jeg offentliggjør all kildekoden på nettet gratis. For å støtte mitt arbeid krever noen funksjoner et abonnement + Hei, jeg heter Alex. Jeg er den uavhengige utvikleren bak Tasks Krever en konto med en CalDAV-tjenestetilbyder, eller en selvdrevet tjener. Finn en tjenestetilbyder ved å besøke tasks.org/caldav Fylt Vis merknader på din ikledbare @@ -617,9 +619,7 @@ OK Utilstrekkelig abonnementsnivå. Oppgrader ditt abonnement for å fortsette tjenesten. Nåværende abonnement: %s - $%s/md. $%s/måned - $%s/år $%s/år Ditt abonnement har utløpt. Abonner nå for å fortsette tjenesten. Ta del i r/tasks diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index a0a99b80f..43c28c5c6 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -487,6 +487,8 @@ Ongeldige gebruikersnaam of wachtwoord Systeeminstelling Kleurenwiel + Hoi! Mijn naam is Alex. Ik ben de onafhankelijke ontwikkelaar achter Tasks. + Ik heb duizenden uren aan Tasks gewerkt, en ik publiceer de volledige broncode gratis online. Om mijn werk te steunen vereisen sommige functies een abonnement. Je steun betekent veel voor me, bedankt! Terug Fiche-stijl @@ -611,9 +613,7 @@ Is deeltaak Heeft deeltaken Huidige abonnement: %s - $%s/ma $%s/maand - $%s/jr $%s/jaar Abonnementsniveau onvoldoende. Verhoog a.u.b. je abonnement om deze dienst te hervatten. Je abonnement is verlopen. Abonneer je nu om deze dienst te hervatten. diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 88a57df86..1bac746ac 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -503,6 +503,8 @@ Nieprawidłowa nazwa użytkownika lub hasło Domyślny systemowy Paleta + Cześć! Mam na imię Alex. Jestem niezależnym deweloperem stojącym za Tasks + Spędziłem tysiące godzin pracując nad Tasks i publikuję cały kod źródłowy online za darmo. Aby wesprzeć moją pracę, niektóre funkcję wymagają subskrypcji Twoje wsparcie wiele dla mnie znaczy, dziękuję! Wstecz Styl chipa @@ -660,9 +662,7 @@ Autoryzacja anulowana Dołącz do r/tasks Aktualna subskrypcja: %s - $%s/mies. $%s/miesiąc - $%s/rok $%s/rok Nie znaleziono kwalifikującej się subskrypcji Google Play Nie znaleziono kwalifikującego się sponsoringu GitHub diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index f671e7a5d..1872c64dd 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -462,6 +462,8 @@ Estilo de notificação Voltar Seu suporte significa muito para mim, obrigado! + Eu investi centenas de horas trabalhando no Tasks, e eu publico todo o código fonte online de graça. Para apoiar meu trabalho, algumas funcionalidades precisam de um plano de subscrição + Olá! Meu nome é Alex e eu sou o desenvolvedor independente por trás do Tasks Roda de cores Nome de usuário ou senha inválido Toque, vibrações e mais diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 40f666b57..a8617eab5 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -518,6 +518,8 @@ Estilo de notificação Voltar Seu suporte significa muito para mim, obrigado! + Eu investi centenas de horas a trabalhar no Tasks e publico todo o código-fonte online de graça. Para apoiar meu trabalho, algumas funcionalidades precisam de uma assinatura + Olá! O meu nome é Alex e sou o programador independente por trás do Tasks Roda de cores Predefinição do Sistema Nome de utilizador ou palavra-passe inválido @@ -596,9 +598,7 @@ Autorização Segue r/ Subscrição atual: %s - %s€/mês %s€/mês - %s€/ano %s€/ É Apagar este comentário\? diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 16e56f6e8..4ff26a58e 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -508,6 +508,8 @@ Неверное имя пользователя или пароль Системная по умолчанию Палитра + Привет! Меня зовут Алекс. Я - независимый разработчик, стоящий за программой Tasks + Я потратил тысячи часов, работая над Tasks, и я публикую весь исходный код онлайн, бесплатно. Для того, чтобы поддержать мою работу, некоторые функциональности требуют подписки Ваша поддержка много значит для меня, спасибо! Назад Стиль индикаторов списков @@ -633,9 +635,7 @@ Перенести Подписаться на r/tasks Текущая подписка: %s - $%s/мес. $%s/месяц - $%s/год $%s/год Недостаточный уровень подписки. Обновите подписку, чтобы возобновить обслуживание. Срок действия вашей подписки истек. Подпишитесь сейчас, чтобы возобновить обслуживание. diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml index 7825283df..2c09947db 100644 --- a/app/src/main/res/values-ta/strings.xml +++ b/app/src/main/res/values-ta/strings.xml @@ -222,6 +222,8 @@ கோடிட்டுக் காட்டப்பட்டுள்ளது சிப் பாணி மீண்டும் + நான் பணிகளில் ஆயிரக்கணக்கான மணிநேரங்களை செலவிட்டேன், மேலும் மூலக் குறியீடு அனைத்தையும் ஆன்லைனில் இலவசமாக வெளியிடுகிறேன். எனது பணியை ஆதரிக்க சில அம்சங்களுக்கு சந்தா தேவை + வணக்கம்! என் பெயர் அலெக்ஸ். பணிகளுக்குப் பின்னால் உள்ள சுயாதீன டெவலப்பர் நான் வண்ண சக்கரம் தவறான பயனர்பெயர் அல்லது கடவுச்சொல் ரிங்டோன், அதிர்வுகள் மற்றும் பல diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index db25d5b37..cb0fbc6ad 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -492,6 +492,8 @@ Geçersiz kullanıcı adı veya parola Sistem öntanımlısı Renk tekeri + Hey! Ben Alex. Tasks\'ın arkasındaki bağımsız geliştiriciyim + Binlerce saatimi Tasks\'ta çalışarak geçirdim, kaynak kodun tümünü çevrim içi olarak ücretsiz yayımladım. Çalışmamı desteklemek için bazı özellikler abonelik gerektirir Desteğiniz çok şey ifade ediyor, teşekkürler! Geri Yonga biçimi @@ -618,9 +620,7 @@ Tasks, kapalıyken veya kullanılmazken de konuma dayalı anımsatmaları etkinleştirmek için konum verisi toplar. r/tasks topluluğuna katıl Geçerli abonelik: %s - %s$/ay %s$/ay - %s$/yıl %s$/yıl Yetersiz abonelik düzeyi. Hizmeti sürdürmek için lütfen aboneliğinizi yükseltin. Aboneliğinizin süresi doldu. Hizmeti sürdürmek için şimdi abone olun. diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index f80d6d6f9..a6b1df6f5 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -531,6 +531,8 @@ Стиль індикаторів списків Назад Ваша підтримка багато означає для мене. Дякую! + Я витратив тисячі годин, працюючи над Tasks, і я публікую весь код онлайн безоплатно. Щоб підтримати мою роботу, деякі функції потребують підписки + Привіт! Моє ім\'я Алекс. Я - незалежний розробник Tasks Палітра Невірне ім\'я користувача або пароль Мелодія, вібрація та інше @@ -566,8 +568,6 @@ Авторизацію скасовано Долучитися до r/tasks Поточна підписка: %s - %s $/рік - %s$/міс. %s $ на місяць %s $ на рік Відповідної вимогам підписки Google Play не знайдено diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 1476396f8..d094554a6 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -483,6 +483,8 @@ 无效的用户名或密码 系统默认 给滚轮着色 + 你好!我叫Alex,是Tasks背后的独立开发者 + 我已经花了数千个小时用于开发Tasks,并且在网上免费发布了所有源代码。 为了支持我的工作,某些功能需要订阅 您的支持对我很重要,谢谢! 返回 流式布局样式 @@ -607,9 +609,7 @@ 是子任务 有子任务 当前订阅:%s - $%s/月 $%s/月 - $%s/年 $%s/年 订阅等级不够。请升级你的订阅来恢复服务。 你的订阅已到期。现在订阅恢复服务。 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index b2e7dd0f8..b7c61949e 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -345,9 +345,7 @@ 未登入 取消授權 目前訂閱: %s - $%s/每月 $%s/每月 - $%s/每年 為副工作 有副工作 忽略警告 diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index a0ab108c7..18d2d198c 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -149,7 +149,6 @@ @color/black_12 @color/grey_300 @color/black_87 - @color/basil #eb52ae diff --git a/app/src/main/res/values/keys.xml b/app/src/main/res/values/keys.xml index 88afae21e..cf8de9807 100644 --- a/app/src/main/res/values/keys.xml +++ b/app/src/main/res/values/keys.xml @@ -32,19 +32,6 @@ https://tasks.org/passwords https://tasks.org/sharing - - Sync with third-party apps and services - Compatible with Outlook, Thunderbird, Apple Reminders, and more - Many new features coming soon! - Multiple Google Task accounts - Unlock additional features - All themes, colors, and icons - Improved location search with Google Places - Tasker plugins - 7-day free trial for new subscribers - Upgrade, downgrade, or cancel your subscription at any time - Your subscription supports open source software! - date_shortcut_morning date_shortcut_afternoon date_shortcut_evening diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 67997a1f7..7742d9ef6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -536,8 +536,8 @@ File %1$s contained %2$s.\n\n Share lists with other users Basic service that synchronizes with your Google account Synchronization based on open internet standards - Open source, end-to-end encrypted synchronization - Synchronize your tasks with the DecSync CC app + End-to-end encrypted synchronization + File-based synchronization Synchronize your tasks with the DAVx⁵ app Show advanced settings Requires an account with a CalDAV service provider or a self-hosted server. Find a service provider by visiting tasks.org/caldav @@ -555,6 +555,9 @@ File %1$s contained %2$s.\n\n Ringtone, vibrations, and more Invalid username or password Color wheel + Hi! My name is Alex. I am the independent developer behind Tasks + I have spent thousands of hours working on Tasks, and I publish all of the source code online for free. In order to support my work some features require a subscription + Subscription benefits Your support means a lot to me, thank you! Back Chip style @@ -612,6 +615,7 @@ File %1$s contained %2$s.\n\n Not important and urgent Not important and not urgent Enjoying Tasks? + Tasks needs your support! Please tell me how I\'m doing Unlock additional features and support open source software No thanks @@ -648,9 +652,7 @@ File %1$s contained %2$s.\n\n No eligible GitHub sponsorship found No eligible Google Play subscription found $%s/year - $%s/yr $%s/month - $%s/mo Current subscription: %s Join r/tasks Follow @tasks_org @@ -703,4 +705,18 @@ File %1$s contained %2$s.\n\n Calendar Clock Text + New subscribers receive a 7-day free trial. Cancel at any time + More customization + Unlock all themes, colors, and icons + Wallpaper and day/night themes + Launcher icons, color palettes, and color wheel + List icons + Synchronize multiple accounts + Sync with Tasks.org and collaborate with other users + Desktop access + Sync with third-party clients like Outlook and Apple Reminders + Your subscription supports continued development + Automation + Plugins for Tasker, Automate, and Locale + More options diff --git a/deps_fdroid.txt b/deps_fdroid.txt index 69067735e..6bf5f3f6a 100644 --- a/deps_fdroid.txt +++ b/deps_fdroid.txt @@ -292,10 +292,6 @@ ++--- io.noties.markwon:core:4.6.2 +| +--- androidx.annotation:annotation:1.1.0 -> 1.2.0-beta01 +| \--- com.atlassian.commonmark:commonmark:0.13.0 -++--- io.noties.markwon:ext-strikethrough:4.6.2 -+| +--- io.noties.markwon:core:4.6.2 (*) -+| \--- com.atlassian.commonmark:commonmark-ext-gfm-strikethrough:0.13.0 -+| \--- com.atlassian.commonmark:commonmark:0.13.0 ++--- com.jakewharton:butterknife:10.2.3 +| \--- com.jakewharton:butterknife-runtime:10.2.3 +| +--- com.jakewharton:butterknife-annotations:10.2.3 diff --git a/deps_googleplay.txt b/deps_googleplay.txt index 96b83d079..e4b452531 100644 --- a/deps_googleplay.txt +++ b/deps_googleplay.txt @@ -406,10 +406,6 @@ ++--- io.noties.markwon:core:4.6.2 +| +--- androidx.annotation:annotation:1.1.0 -> 1.2.0-beta01 +| \--- com.atlassian.commonmark:commonmark:0.13.0 -++--- io.noties.markwon:ext-strikethrough:4.6.2 -+| +--- io.noties.markwon:core:4.6.2 (*) -+| \--- com.atlassian.commonmark:commonmark-ext-gfm-strikethrough:0.13.0 -+| \--- com.atlassian.commonmark:commonmark:0.13.0 ++--- com.jakewharton:butterknife:10.2.3 +| \--- com.jakewharton:butterknife-runtime:10.2.3 +| +--- com.jakewharton:butterknife-annotations:10.2.3