Add Tasks.org to synchronization options

pull/1208/head
Alex Baker 5 years ago
parent e10d78c712
commit a67d62f1ea

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"> <resources xmlns:tools="http://schemas.android.com/tools">
<string name="app_name" tools:ignore="PrivateResource">Tasks Debug</string> <string name="app_name" tools:ignore="PrivateResource">Tasks Debug</string>
<string name="tasks_caldav_url">https://caldav.tasks.org</string>
<string name="debug_strict_mode_thread">Strict mode - Thread</string> <string name="debug_strict_mode_thread">Strict mode - Thread</string>
<string name="debug_strict_mode_vm">Strict mode - VM</string> <string name="debug_strict_mode_vm">Strict mode - VM</string>
<string name="debug_leakcanary">LeakCanary</string> <string name="debug_leakcanary">LeakCanary</string>

@ -0,0 +1,3 @@
package org.tasks.auth
class SignInActivity

@ -14,6 +14,11 @@
android:value="@integer/google_play_services_version"/> android:value="@integer/google_play_services_version"/>
<service android:name=".location.GeofenceTransitionsIntentService"/> <service android:name=".location.GeofenceTransitionsIntentService"/>
<activity
android:name=".auth.SignInActivity"
android:theme="@style/TranslucentDialog" />
</application> </application>
</manifest> </manifest>

@ -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
}
}

@ -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<CaldavAccount?>() {
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)
}
}
}
}

@ -4,6 +4,7 @@ import android.app.Activity.RESULT_OK
import android.app.Dialog import android.app.Dialog
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.text.method.LinkMovementMethod 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 { companion object {
private const val EXTRA_PRICE = "extra_price" private const val EXTRA_PRICE = "extra_price"
private const val EXTRA_PRICE_CHANGED = "extra_price_changed" private const val EXTRA_PRICE_CHANGED = "extra_price_changed"
private const val EXTRA_NAME_YOUR_PRICE = "extra_name_your_price" private const val EXTRA_NAME_YOUR_PRICE = "extra_name_your_price"
const val EXTRA_FINISH_ACTIVITY = "extra_activity_rc"
@JvmStatic @JvmStatic
val FRAG_TAG_PURCHASE_DIALOG = "frag_tag_purchase_dialog" val FRAG_TAG_PURCHASE_DIALOG = "frag_tag_purchase_dialog"
@JvmStatic @JvmStatic
fun newPurchaseDialog(): PurchaseDialog { fun newPurchaseDialog() = newPurchaseDialog(false)
return PurchaseDialog()
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 { fun newPurchaseDialog(target: Fragment, rc: Int): PurchaseDialog {

@ -95,13 +95,29 @@ class CaldavSynchronizer @Inject constructor(
setError(account, e.message) setError(account, e.message)
} catch (e: IOException) { } catch (e: IOException) {
setError(account, e.message) 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) { } catch (e: DavException) {
setError(account, e.message) setError(account, e.message)
if (e !is HttpException || e.code < 500) {
firebase.reportException(e) firebase.reportException(e)
} }
} }
}
@Throws(IOException::class, DavException::class, KeyManagementException::class, NoSuchAlgorithmException::class) @Throws(IOException::class, DavException::class, KeyManagementException::class, NoSuchAlgorithmException::class)
private suspend fun synchronize(account: CaldavAccount) { private suspend fun synchronize(account: CaldavAccount) {

@ -215,6 +215,7 @@ class Synchronization : InjectingPreferenceFragment() {
const val REQUEST_CALDAV_SETTINGS = 10013 const val REQUEST_CALDAV_SETTINGS = 10013
const val REQUEST_GOOGLE_TASKS = 10014 const val REQUEST_GOOGLE_TASKS = 10014
private const val REQUEST_ADD_ACCOUNT = 10015 private const val REQUEST_ADD_ACCOUNT = 10015
const val REQUEST_TASKS_ORG = 10016
private const val FRAG_TAG_ADD_ACCOUNT = "frag_tag_add_account" private const val FRAG_TAG_ADD_ACCOUNT = "frag_tag_add_account"
} }
} }

@ -14,12 +14,15 @@ import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.tasks.BuildConfig
import org.tasks.R import org.tasks.R
import org.tasks.auth.SignInActivity
import org.tasks.caldav.CaldavAccountSettingsActivity import org.tasks.caldav.CaldavAccountSettingsActivity
import org.tasks.dialogs.DialogBuilder import org.tasks.dialogs.DialogBuilder
import org.tasks.etesync.EteSyncAccountSettingsActivity import org.tasks.etesync.EteSyncAccountSettingsActivity
import org.tasks.preferences.fragments.Synchronization.Companion.REQUEST_CALDAV_SETTINGS 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_GOOGLE_TASKS
import org.tasks.preferences.fragments.Synchronization.Companion.REQUEST_TASKS_ORG
import org.tasks.themes.DrawableUtil import org.tasks.themes.DrawableUtil
import javax.inject.Inject import javax.inject.Inject
@ -45,7 +48,7 @@ class AddAccountDialog : DialogFragment() {
view.findViewById<TextView>(R.id.text2).text = descriptions[position] view.findViewById<TextView>(R.id.text2).text = descriptions[position]
val icon = view.findViewById<ImageView>(R.id.image_view) val icon = view.findViewById<ImageView>(R.id.image_view)
icon.setImageDrawable(DrawableUtil.getWrapped(context, icons[position])) icon.setImageDrawable(DrawableUtil.getWrapped(context, icons[position]))
if (position == 1) { if (position == 2) {
icon.drawable.setTint(context.getColor(R.color.icon_tint)) icon.drawable.setTint(context.getColor(R.color.icon_tint))
} }
return view return view
@ -56,24 +59,37 @@ class AddAccountDialog : DialogFragment() {
.setTitle(R.string.choose_synchronization_service) .setTitle(R.string.choose_synchronization_service)
.setSingleChoiceItems(adapter, -1) { dialog, which -> .setSingleChoiceItems(adapter, -1) { dialog, which ->
when (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), Intent(activity, GtasksLoginActivity::class.java),
REQUEST_GOOGLE_TASKS) REQUEST_GOOGLE_TASKS)
1 -> activity?.startActivityForResult( 2 -> activity?.startActivityForResult(
Intent(activity, CaldavAccountSettingsActivity::class.java), Intent(activity, CaldavAccountSettingsActivity::class.java),
REQUEST_CALDAV_SETTINGS) REQUEST_CALDAV_SETTINGS)
2 -> activity?.startActivityForResult( 3 -> activity?.startActivityForResult(
Intent(activity, EteSyncAccountSettingsActivity::class.java), Intent(activity, EteSyncAccountSettingsActivity::class.java),
REQUEST_CALDAV_SETTINGS) REQUEST_CALDAV_SETTINGS)
3 -> activity?.startActivity( 4 -> activity?.startActivity(
Intent(ACTION_VIEW, Uri.parse("https://tasks.org/davx5"))) Intent(ACTION_VIEW, Uri.parse("https://tasks.org/davx5")))
} }
dialog.dismiss() dialog.dismiss()
} }
.setNeutralButton(R.string.help) { _, _ -> .setNeutralButton(R.string.help) { _, _ ->
activity?.startActivity(Intent( activity?.startActivity(
Intent.ACTION_VIEW, Intent(
Uri.parse(context?.getString(R.string.help_url_sync)))) ACTION_VIEW,
Uri.parse(context?.getString(R.string.help_url_sync))
)
)
} }
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.show() .show()

@ -0,0 +1,26 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="1146dp"
android:height="1146dp"
android:viewportWidth="1146"
android:viewportHeight="1146">
<group>
<clip-path
android:pathData="M0,0l1145.72,0l0,1145.72l-1145.72,0z"/>
<path
android:pathData="M572.65,571.88m-572.51,0a572.51,572.51 0,1 1,1145.02 0a572.51,572.51 45,1 1,-1145.02 0"
android:fillColor="#2196F3"/>
<group>
<clip-path
android:pathData="M572.65,571.88m-572.51,0a572.51,572.51 0,1 1,1145.02 0a572.51,572.51 45,1 1,-1145.02 0"/>
<path
android:fillColor="#FF000000"
android:pathData="M429.5,771.89L230.23,572.61L162.61,640.23L1146.31,1623.94L1719.76,1050.49L935.33,266.06L429.5,771.89Z"
android:fillType="nonZero"
android:fillAlpha="0.15"/>
</group>
</group>
<path
android:pathData="M429.5,771.88L230.22,572.61L162.61,640.23L429.5,907.12L1002.95,333.67L935.33,266.05L429.5,771.88Z"
android:fillColor="#ffffff"
android:fillType="nonZero"/>
</vector>

@ -186,6 +186,7 @@
</string-array> </string-array>
<string-array name="synchronization_services"> <string-array name="synchronization_services">
<item>@string/tasks_org</item>
<item>@string/gtasks_GPr_header</item> <item>@string/gtasks_GPr_header</item>
<item>@string/caldav</item> <item>@string/caldav</item>
<item>@string/etesync</item> <item>@string/etesync</item>
@ -193,6 +194,7 @@
</string-array> </string-array>
<string-array name="synchronization_services_description"> <string-array name="synchronization_services_description">
<item>@string/tasks_org_sync_description</item>
<item>@string/google_tasks_selection_description</item> <item>@string/google_tasks_selection_description</item>
<item>@string/caldav_selection_description</item> <item>@string/caldav_selection_description</item>
<item>@string/etesync_selection_description</item> <item>@string/etesync_selection_description</item>
@ -200,6 +202,7 @@
</string-array> </string-array>
<array name="synchronization_services_icons"> <array name="synchronization_services_icons">
<item>@drawable/ic_round_icon</item>
<item>@drawable/ic_google</item> <item>@drawable/ic_google</item>
<item>@drawable/ic_webdav_logo</item> <item>@drawable/ic_webdav_logo</item>
<item>@drawable/ic_etesync</item> <item>@drawable/ic_etesync</item>

@ -38,6 +38,7 @@
<string name="upgrade_balance">Your remaining balance will apply to your new subscription</string> <string name="upgrade_balance">Your remaining balance will apply to your new subscription</string>
<string name="upgrade_cancel">Cancel at any time</string> <string name="upgrade_cancel">Cancel at any time</string>
<string name="upgrade_benefits_retained">benefits are retained until the end of your billing period</string> <string name="upgrade_benefits_retained">benefits are retained until the end of your billing period</string>
<string name="github_sponsor_login">Login for Github Sponsors coming soon!</string>
<string name="p_date_shortcut_morning">date_shortcut_morning</string> <string name="p_date_shortcut_morning">date_shortcut_morning</string>
<string name="p_date_shortcut_afternoon">date_shortcut_afternoon</string> <string name="p_date_shortcut_afternoon">date_shortcut_afternoon</string>

@ -547,6 +547,7 @@ File %1$s contained %2$s.\n\n
<string name="enter_tag_name">Enter tag name</string> <string name="enter_tag_name">Enter tag name</string>
<string name="create_new_tag">Create \"%s\"</string> <string name="create_new_tag">Create \"%s\"</string>
<string name="choose_synchronization_service">Select a platform</string> <string name="choose_synchronization_service">Select a platform</string>
<string name="tasks_org_sync_description">Synchronize your data with Tasks.org</string>
<string name="google_tasks_selection_description">Basic service that synchronizes with your Google account</string> <string name="google_tasks_selection_description">Basic service that synchronizes with your Google account</string>
<string name="caldav_selection_description">Synchronization based on open internet standards</string> <string name="caldav_selection_description">Synchronization based on open internet standards</string>
<string name="etesync_selection_description">Open source, end-to-end encrypted synchronization</string> <string name="etesync_selection_description">Open source, end-to-end encrypted synchronization</string>
@ -654,6 +655,9 @@ File %1$s contained %2$s.\n\n
<string name="delete_comment">Delete this comment?</string> <string name="delete_comment">Delete this comment?</string>
<string name="custom_filter_has_subtask">Has subtasks</string> <string name="custom_filter_has_subtask">Has subtasks</string>
<string name="custom_filter_is_subtask">Is subtask</string> <string name="custom_filter_is_subtask">Is subtask</string>
<string name="logged_in">Logged in %s</string>
<string name="your_subscription_expired">Your subscription has expired. Subscribe now to resume service.</string>
<string name="insufficient_subscription">Insufficient subscription level. Please upgrade your subscription to resume service.</string>
<string name="price_per_year">$%s/year</string> <string name="price_per_year">$%s/year</string>
<string name="price_per_year_abbreviated">$%s/yr</string> <string name="price_per_year_abbreviated">$%s/yr</string>
<string name="price_per_month">$%s/month</string> <string name="price_per_month">$%s/month</string>

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">Tasks</string> <string name="app_name">Tasks</string>
<string name="tasks_caldav_url">https://caldav.tasks.org</string>
</resources> </resources>

@ -252,6 +252,21 @@
+| +--- com.google.auto.value:auto-value-annotations:1.6.2 -> 1.7.2 +| +--- 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.google.code.gson:gson:2.8.5 -> 2.8.6
++--- com.android.billingclient:billing:1.2.2 ++--- 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 ++--- com.gitlab.bitfireAT:dav4jvm:2.1.1
+| +--- org.jetbrains.kotlin:kotlin-stdlib:1.3.72 -> 1.4.10 (*) +| +--- org.jetbrains.kotlin:kotlin-stdlib:1.3.72 -> 1.4.10 (*)
+| \--- org.apache.commons:commons-lang3:3.9 +| \--- org.apache.commons:commons-lang3:3.9

Loading…
Cancel
Save