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