From a67d62f1eab70a5d30344289a31a2f0fd7c416a7 Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Fri, 6 Nov 2020 13:18:38 -0600 Subject: [PATCH] Add Tasks.org to synchronization options --- app/src/debug/res/values/keys.xml | 1 + .../java/org/tasks/auth/SignInActivity.kt | 3 + app/src/googleplay/AndroidManifest.xml | 5 ++ .../java/org/tasks/auth/SignInActivity.kt | 77 +++++++++++++++++++ .../java/org/tasks/auth/SignInViewModel.kt | 45 +++++++++++ .../java/org/tasks/billing/PurchaseDialog.kt | 20 ++++- .../org/tasks/caldav/CaldavSynchronizer.kt | 22 +++++- .../preferences/fragments/Synchronization.kt | 1 + .../java/org/tasks/sync/AddAccountDialog.kt | 32 ++++++-- app/src/main/res/drawable/ic_round_icon.xml | 26 +++++++ app/src/main/res/values/arrays.xml | 3 + app/src/main/res/values/keys.xml | 1 + app/src/main/res/values/strings.xml | 4 + app/src/release/res/values/keys.xml | 1 + deps_googleplay.txt | 15 ++++ 15 files changed, 243 insertions(+), 13 deletions(-) create mode 100644 app/src/generic/java/org/tasks/auth/SignInActivity.kt create mode 100644 app/src/googleplay/java/org/tasks/auth/SignInActivity.kt create mode 100644 app/src/googleplay/java/org/tasks/auth/SignInViewModel.kt create mode 100644 app/src/main/res/drawable/ic_round_icon.xml diff --git a/app/src/debug/res/values/keys.xml b/app/src/debug/res/values/keys.xml index 759a5091f..b37c29b4d 100644 --- a/app/src/debug/res/values/keys.xml +++ b/app/src/debug/res/values/keys.xml @@ -1,6 +1,7 @@ Tasks Debug + https://caldav.tasks.org Strict mode - Thread Strict mode - VM LeakCanary diff --git a/app/src/generic/java/org/tasks/auth/SignInActivity.kt b/app/src/generic/java/org/tasks/auth/SignInActivity.kt new file mode 100644 index 000000000..5818a0855 --- /dev/null +++ b/app/src/generic/java/org/tasks/auth/SignInActivity.kt @@ -0,0 +1,3 @@ +package org.tasks.auth + +class SignInActivity \ No newline at end of file diff --git a/app/src/googleplay/AndroidManifest.xml b/app/src/googleplay/AndroidManifest.xml index f2586abd5..5b04ba4ff 100644 --- a/app/src/googleplay/AndroidManifest.xml +++ b/app/src/googleplay/AndroidManifest.xml @@ -14,6 +14,11 @@ android:value="@integer/google_play_services_version"/> + + + diff --git a/app/src/googleplay/java/org/tasks/auth/SignInActivity.kt b/app/src/googleplay/java/org/tasks/auth/SignInActivity.kt new file mode 100644 index 000000000..0f0718144 --- /dev/null +++ b/app/src/googleplay/java/org/tasks/auth/SignInActivity.kt @@ -0,0 +1,77 @@ +package org.tasks.auth + +import android.content.Intent +import android.os.Bundle +import androidx.activity.viewModels +import androidx.lifecycle.lifecycleScope +import at.bitfire.dav4jvm.exception.HttpException +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.tasks.R +import org.tasks.analytics.Firebase +import org.tasks.billing.PurchaseDialog.Companion.FRAG_TAG_PURCHASE_DIALOG +import org.tasks.billing.PurchaseDialog.Companion.newPurchaseDialog +import org.tasks.caldav.CaldavClientProvider +import org.tasks.data.CaldavAccount +import org.tasks.gtasks.PlayServices +import org.tasks.injection.InjectingAppCompatActivity +import org.tasks.ui.Toaster +import javax.inject.Inject + +@AndroidEntryPoint +class SignInActivity : InjectingAppCompatActivity() { + + @Inject lateinit var toaster: Toaster + @Inject lateinit var provider: CaldavClientProvider + @Inject lateinit var playServices: PlayServices + @Inject lateinit var firebase: Firebase + + val viewModel: SignInViewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + viewModel.observe(this, this::onSignIn, this::onError) + + lifecycleScope.launch { + playServices + .getSignedInAccount() + ?.let { validate(it) } + ?: startActivityForResult(playServices.signInIntent, RC_SIGN_IN) + } + } + + private fun onSignIn(account: CaldavAccount?) { + account?.let { toaster.longToast(getString(R.string.logged_in, it.name)) } + finish() + } + + private fun onError(t: Throwable) { + if (t is HttpException && t.code == 402) { + newPurchaseDialog(true).show(supportFragmentManager, FRAG_TAG_PURCHASE_DIALOG) + } else { + firebase.reportException(t) + toaster.longToast(t.message) + finish() + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (requestCode == RC_SIGN_IN) { + playServices + .signInFromIntent(data) + ?.let { validate(it) } + } else { + super.onActivityResult(requestCode, resultCode, data) + } + } + + private fun validate(account: OauthSignIn) = lifecycleScope.launch(Dispatchers.IO) { + viewModel.validate(account.id!!, account.email!!, account.idToken!!) + } + + companion object { + private const val RC_SIGN_IN = 10000 + } +} \ No newline at end of file diff --git a/app/src/googleplay/java/org/tasks/auth/SignInViewModel.kt b/app/src/googleplay/java/org/tasks/auth/SignInViewModel.kt new file mode 100644 index 000000000..00766e871 --- /dev/null +++ b/app/src/googleplay/java/org/tasks/auth/SignInViewModel.kt @@ -0,0 +1,45 @@ +package org.tasks.auth + +import android.content.Context +import androidx.hilt.lifecycle.ViewModelInject +import com.todoroo.astrid.helper.UUIDHelper +import dagger.hilt.android.qualifiers.ApplicationContext +import org.tasks.R +import org.tasks.caldav.CaldavClientProvider +import org.tasks.data.CaldavAccount +import org.tasks.data.CaldavAccount.Companion.TYPE_TASKS +import org.tasks.data.CaldavDao +import org.tasks.ui.CompletableViewModel + +class SignInViewModel @ViewModelInject constructor( + @ApplicationContext private val context: Context, + private val provider: CaldavClientProvider, + private val caldavDao: CaldavDao +) : CompletableViewModel() { + + suspend fun validate(id: String, email: String, idToken: String) { + run { + val homeSet = provider + .forUrl( + "${context.getString(R.string.tasks_caldav_url)}/google_login", + token = idToken + ) + .setForeground() + .homeSet(token = idToken) + val username = "google_$id" + caldavDao.getAccount(TYPE_TASKS, username) + ?.apply { + error = null + caldavDao.update(this) + } + ?: CaldavAccount().apply { + accountType = TYPE_TASKS + uuid = UUIDHelper.newUUID() + url = homeSet + this.username = username + name = email + caldavDao.insert(this) + } + } + } +} diff --git a/app/src/main/java/org/tasks/billing/PurchaseDialog.kt b/app/src/main/java/org/tasks/billing/PurchaseDialog.kt index a5f1e150f..2cf489680 100644 --- a/app/src/main/java/org/tasks/billing/PurchaseDialog.kt +++ b/app/src/main/java/org/tasks/billing/PurchaseDialog.kt @@ -4,6 +4,7 @@ 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.os.Bundle import android.text.method.LinkMovementMethod @@ -229,16 +230,31 @@ class PurchaseDialog : DialogFragment(), OnPurchasesUpdated { } } + override fun onDismiss(dialog: DialogInterface) { + super.onDismiss(dialog) + + if (arguments?.getBoolean(EXTRA_FINISH_ACTIVITY, false) == true) { + activity?.finish() + } + } + companion object { 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" + const val EXTRA_FINISH_ACTIVITY = "extra_activity_rc" @JvmStatic val FRAG_TAG_PURCHASE_DIALOG = "frag_tag_purchase_dialog" @JvmStatic - fun newPurchaseDialog(): PurchaseDialog { - return PurchaseDialog() + fun newPurchaseDialog() = newPurchaseDialog(false) + + fun newPurchaseDialog(finishActivity: Boolean): PurchaseDialog { + val dialog = PurchaseDialog() + val args = Bundle() + args.putBoolean(EXTRA_FINISH_ACTIVITY, finishActivity) + dialog.arguments = args + return dialog } fun newPurchaseDialog(target: Fragment, rc: Int): PurchaseDialog { diff --git a/app/src/main/java/org/tasks/caldav/CaldavSynchronizer.kt b/app/src/main/java/org/tasks/caldav/CaldavSynchronizer.kt index be5938257..c266335ea 100644 --- a/app/src/main/java/org/tasks/caldav/CaldavSynchronizer.kt +++ b/app/src/main/java/org/tasks/caldav/CaldavSynchronizer.kt @@ -95,11 +95,27 @@ class CaldavSynchronizer @Inject constructor( setError(account, e.message) } catch (e: IOException) { setError(account, e.message) + } catch (e: HttpException) { + val message = when(e.code) { + 402 -> if (account.isTasksOrg) { + context.getString(if (inventory.subscription == null) { + R.string.your_subscription_expired + } else { + R.string.insufficient_subscription + }) + } else { + e.message + } + in 500..599 -> e.message + else -> { + firebase.reportException(e) + e.message + } + } + setError(account, message) } catch (e: DavException) { setError(account, e.message) - if (e !is HttpException || e.code < 500) { - firebase.reportException(e) - } + firebase.reportException(e) } } diff --git a/app/src/main/java/org/tasks/preferences/fragments/Synchronization.kt b/app/src/main/java/org/tasks/preferences/fragments/Synchronization.kt index a09a3c7dd..6c045b918 100644 --- a/app/src/main/java/org/tasks/preferences/fragments/Synchronization.kt +++ b/app/src/main/java/org/tasks/preferences/fragments/Synchronization.kt @@ -215,6 +215,7 @@ class Synchronization : InjectingPreferenceFragment() { const val REQUEST_CALDAV_SETTINGS = 10013 const val REQUEST_GOOGLE_TASKS = 10014 private const val REQUEST_ADD_ACCOUNT = 10015 + const val REQUEST_TASKS_ORG = 10016 private const val FRAG_TAG_ADD_ACCOUNT = "frag_tag_add_account" } } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/sync/AddAccountDialog.kt b/app/src/main/java/org/tasks/sync/AddAccountDialog.kt index fb17a026a..efe651ff6 100644 --- a/app/src/main/java/org/tasks/sync/AddAccountDialog.kt +++ b/app/src/main/java/org/tasks/sync/AddAccountDialog.kt @@ -14,12 +14,15 @@ import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity import dagger.hilt.android.AndroidEntryPoint +import org.tasks.BuildConfig import org.tasks.R +import org.tasks.auth.SignInActivity import org.tasks.caldav.CaldavAccountSettingsActivity import org.tasks.dialogs.DialogBuilder import org.tasks.etesync.EteSyncAccountSettingsActivity import org.tasks.preferences.fragments.Synchronization.Companion.REQUEST_CALDAV_SETTINGS import org.tasks.preferences.fragments.Synchronization.Companion.REQUEST_GOOGLE_TASKS +import org.tasks.preferences.fragments.Synchronization.Companion.REQUEST_TASKS_ORG import org.tasks.themes.DrawableUtil import javax.inject.Inject @@ -45,7 +48,7 @@ class AddAccountDialog : DialogFragment() { view.findViewById(R.id.text2).text = descriptions[position] val icon = view.findViewById(R.id.image_view) icon.setImageDrawable(DrawableUtil.getWrapped(context, icons[position])) - if (position == 1) { + if (position == 2) { icon.drawable.setTint(context.getColor(R.color.icon_tint)) } return view @@ -56,24 +59,37 @@ class AddAccountDialog : DialogFragment() { .setTitle(R.string.choose_synchronization_service) .setSingleChoiceItems(adapter, -1) { dialog, which -> when (which) { - 0 -> activity?.startActivityForResult( + 0 -> if (BuildConfig.FLAVOR == "generic") { + dialogBuilder + .newDialog(R.string.github_sponsor_login) + .setPositiveButton(R.string.ok, null) + .show() + } else { + activity?.startActivityForResult( + Intent(activity, SignInActivity::class.java), + REQUEST_TASKS_ORG) + } + 1 -> activity?.startActivityForResult( Intent(activity, GtasksLoginActivity::class.java), REQUEST_GOOGLE_TASKS) - 1 -> activity?.startActivityForResult( + 2 -> activity?.startActivityForResult( Intent(activity, CaldavAccountSettingsActivity::class.java), REQUEST_CALDAV_SETTINGS) - 2 -> activity?.startActivityForResult( + 3 -> activity?.startActivityForResult( Intent(activity, EteSyncAccountSettingsActivity::class.java), REQUEST_CALDAV_SETTINGS) - 3 -> activity?.startActivity( + 4 -> activity?.startActivity( Intent(ACTION_VIEW, Uri.parse("https://tasks.org/davx5"))) } dialog.dismiss() } .setNeutralButton(R.string.help) { _, _ -> - activity?.startActivity(Intent( - Intent.ACTION_VIEW, - Uri.parse(context?.getString(R.string.help_url_sync)))) + activity?.startActivity( + Intent( + ACTION_VIEW, + Uri.parse(context?.getString(R.string.help_url_sync)) + ) + ) } .setNegativeButton(R.string.cancel, null) .show() diff --git a/app/src/main/res/drawable/ic_round_icon.xml b/app/src/main/res/drawable/ic_round_icon.xml new file mode 100644 index 000000000..21e9a9807 --- /dev/null +++ b/app/src/main/res/drawable/ic_round_icon.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index e17b8b9fd..157fc0d87 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -186,6 +186,7 @@ + @string/tasks_org @string/gtasks_GPr_header @string/caldav @string/etesync @@ -193,6 +194,7 @@ + @string/tasks_org_sync_description @string/google_tasks_selection_description @string/caldav_selection_description @string/etesync_selection_description @@ -200,6 +202,7 @@ + @drawable/ic_round_icon @drawable/ic_google @drawable/ic_webdav_logo @drawable/ic_etesync diff --git a/app/src/main/res/values/keys.xml b/app/src/main/res/values/keys.xml index 9272ab9a8..0e36f78f9 100644 --- a/app/src/main/res/values/keys.xml +++ b/app/src/main/res/values/keys.xml @@ -38,6 +38,7 @@ Your remaining balance will apply to your new subscription Cancel at any time benefits are retained until the end of your billing period + Login for Github Sponsors coming soon! date_shortcut_morning date_shortcut_afternoon diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f57656be8..35a537be8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -547,6 +547,7 @@ File %1$s contained %2$s.\n\n Enter tag name Create \"%s\" Select a platform + Synchronize your data with Tasks.org Basic service that synchronizes with your Google account Synchronization based on open internet standards Open source, end-to-end encrypted synchronization @@ -654,6 +655,9 @@ File %1$s contained %2$s.\n\n Delete this comment? Has subtasks Is subtask + Logged in %s + Your subscription has expired. Subscribe now to resume service. + Insufficient subscription level. Please upgrade your subscription to resume service. $%s/year $%s/yr $%s/month diff --git a/app/src/release/res/values/keys.xml b/app/src/release/res/values/keys.xml index 0b39dd0fe..dfdae5954 100644 --- a/app/src/release/res/values/keys.xml +++ b/app/src/release/res/values/keys.xml @@ -1,4 +1,5 @@ Tasks + https://caldav.tasks.org \ No newline at end of file diff --git a/deps_googleplay.txt b/deps_googleplay.txt index 530a48f71..40a7044de 100644 --- a/deps_googleplay.txt +++ b/deps_googleplay.txt @@ -252,6 +252,21 @@ +| +--- com.google.auto.value:auto-value-annotations:1.6.2 -> 1.7.2 +| \--- com.google.code.gson:gson:2.8.5 -> 2.8.6 ++--- com.android.billingclient:billing:1.2.2 +++--- com.google.android.gms:play-services-auth:18.1.0 ++| +--- androidx.fragment:fragment:1.0.0 -> 1.2.5 (*) ++| +--- androidx.loader:loader:1.0.0 (*) ++| +--- com.google.android.gms:play-services-auth-api-phone:17.0.0 ++| | +--- com.google.android.gms:play-services-base:17.0.0 -> 17.3.0 (*) ++| | +--- com.google.android.gms:play-services-basement:17.0.0 -> 17.3.0 (*) ++| | \--- com.google.android.gms:play-services-tasks:17.0.0 -> 17.1.0 (*) ++| +--- com.google.android.gms:play-services-auth-base:17.0.0 ++| | +--- androidx.collection:collection:1.0.0 -> 1.1.0 (*) ++| | +--- com.google.android.gms:play-services-base:17.0.0 -> 17.3.0 (*) ++| | +--- com.google.android.gms:play-services-basement:17.0.0 -> 17.3.0 (*) ++| | \--- com.google.android.gms:play-services-tasks:17.0.0 -> 17.1.0 (*) ++| +--- com.google.android.gms:play-services-base:17.1.0 -> 17.3.0 (*) ++| +--- com.google.android.gms:play-services-basement:17.1.1 -> 17.3.0 (*) ++| \--- com.google.android.gms:play-services-tasks:17.0.0 -> 17.1.0 (*) ++--- com.gitlab.bitfireAT:dav4jvm:2.1.1 +| +--- org.jetbrains.kotlin:kotlin-stdlib:1.3.72 -> 1.4.10 (*) +| \--- org.apache.commons:commons-lang3:3.9