You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tasks/app/src/main/java/org/tasks/preferences/fragments/TasksAccount.kt

282 lines
11 KiB
Kotlin

package org.tasks.preferences.fragments
import android.content.BroadcastReceiver
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.widget.Toast.LENGTH_SHORT
import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.getSystemService
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.LiveData
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceCategory
import com.google.android.material.textfield.TextInputLayout
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.service.TaskDeleter
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import org.tasks.BuildConfig
import org.tasks.LocalBroadcastManager
import org.tasks.R
import org.tasks.auth.SignInActivity
import org.tasks.auth.SignInActivity.Platform
import org.tasks.billing.Inventory
import org.tasks.billing.Purchase
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavAccount.Companion.isPaymentRequired
import org.tasks.data.CaldavDao
import org.tasks.extensions.Context.openUri
import org.tasks.extensions.Context.toast
import org.tasks.jobs.WorkManager
import org.tasks.preferences.IconPreference
import org.tasks.preferences.fragments.MainSettingsFragment.Companion.REQUEST_TASKS_ORG
import java.time.format.FormatStyle
import java.util.Locale
import javax.inject.Inject
@AndroidEntryPoint
class TasksAccount : BaseAccountPreference() {
@Inject lateinit var taskDeleter: TaskDeleter
@Inject lateinit var inventory: Inventory
@Inject lateinit var localBroadcastManager: LocalBroadcastManager
@Inject lateinit var caldavDao: CaldavDao
@Inject lateinit var workManager: WorkManager
@Inject lateinit var locale: Locale
private val viewModel: TasksAccountViewModel by viewModels()
private lateinit var caldavAccountLiveData: LiveData<CaldavAccount>
val caldavAccount: CaldavAccount
get() = caldavAccountLiveData.value ?: requireArguments().getParcelable(EXTRA_ACCOUNT)!!
private val refreshReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
refreshUi(caldavAccount)
}
}
override fun getPreferenceXml() = R.xml.preferences_tasks
private fun clearPurchaseError(purchase: Purchase?) {
if (purchase?.isTasksSubscription == true && caldavAccount.error.isPaymentRequired()) {
caldavAccount.error = null
lifecycleScope.launch {
caldavDao.update(caldavAccount)
}
}
}
override suspend fun setupPreferences(savedInstanceState: Bundle?) {
super.setupPreferences(savedInstanceState)
inventory.subscription.observe(this) { clearPurchaseError(it) }
caldavAccountLiveData = caldavDao.watchAccount(
requireArguments().getParcelable<CaldavAccount>(EXTRA_ACCOUNT)!!.id
)
if (savedInstanceState == null) {
viewModel.refreshPasswords(caldavAccount)
}
findPreference(R.string.local_lists).setOnPreferenceClickListener {
workManager.migrateLocalTasks(caldavAccount)
context?.toast(R.string.migrating_tasks)
false
}
findPreference(R.string.generate_new_password).setOnPreferenceChangeListener { _, description ->
viewModel.requestNewPassword(caldavAccount, description as String)
false
}
openUrl(R.string.app_passwords_more_info, R.string.url_app_passwords)
}
override suspend fun removeAccount() {
// try to delete session from caldav.tasks.org
taskDeleter.delete(caldavAccount)
inventory.updateTasksAccount()
}
override fun onResume() {
super.onResume()
caldavAccountLiveData.observe(this) { account ->
account?.let { refreshUi(it) }
}
viewModel.appPasswords.observe(this) { passwords ->
passwords?.let { refreshPasswords(passwords) }
}
viewModel.newPassword.observe(this) {
it?.let {
val view = LayoutInflater.from(requireActivity()).inflate(R.layout.dialog_app_password, null)
setupTextField(view, R.id.url_layout, R.string.url, getString(R.string.tasks_caldav_url))
setupTextField(view, R.id.user_layout, R.string.user, it.username)
setupTextField(view, R.id.password_layout, R.string.password, it.password)
dialogBuilder.newDialog()
.setView(view)
.setPositiveButton(R.string.ok) { _, _ ->
viewModel.clearNewPassword()
viewModel.refreshPasswords(caldavAccount)
}
.setCancelable(false)
.setNeutralButton(R.string.help) { _, _ ->
context?.openUri(R.string.url_app_passwords)
}
.show()
}
}
localBroadcastManager.registerRefreshListReceiver(refreshReceiver)
}
private fun setupTextField(v: View, layout: Int, labelRes: Int, value: String?) {
with(v.findViewById<TextInputLayout>(layout)) {
editText?.setText(value)
setEndIconOnClickListener {
val label = getString(labelRes)
getSystemService(requireContext(), ClipboardManager::class.java)
?.setPrimaryClip(ClipData.newPlainText(label, value))
context?.toast(R.string.copied_to_clipboard, label, duration = LENGTH_SHORT)
}
}
}
override fun onPause() {
super.onPause()
localBroadcastManager.unregisterReceiver(refreshReceiver)
}
private val isGithub: Boolean
get() = caldavAccount.username?.startsWith("github") == true
private fun refreshUi(account: CaldavAccount) {
(findPreference(R.string.sign_in_with_google) as IconPreference).apply {
if (account.error.isNullOrBlank()) {
isVisible = false
return@apply
}
isVisible = true
when {
account.isPaymentRequired() -> {
val subscription = inventory.subscription.value
if (isGithub) {
title = null
setSummary(R.string.insufficient_sponsorship)
@Suppress("KotlinConstantConditions")
if (BuildConfig.FLAVOR == "googleplay") {
onPreferenceClickListener = null
} else {
setOnPreferenceClickListener {
context.openUri(R.string.url_sponsor)
false
}
}
} else {
setOnPreferenceClickListener { showPurchaseDialog() }
if (subscription == null || subscription.isTasksSubscription) {
setTitle(R.string.button_subscribe)
setSummary(R.string.your_subscription_expired)
} else {
setTitle(R.string.manage_subscription)
setSummary(R.string.insufficient_subscription)
}
}
}
account.isLoggedOut() -> {
setTitle(if (isGithub) {
R.string.sign_in_with_github
} else {
R.string.sign_in_with_google
})
setSummary(R.string.authentication_required)
setOnPreferenceClickListener {
activity?.startActivityForResult(
Intent(activity, SignInActivity::class.java)
.putExtra(
SignInActivity.EXTRA_SELECT_SERVICE,
if (isGithub) Platform.GITHUB else Platform.GOOGLE
),
REQUEST_TASKS_ORG)
false
}
}
else -> {
this.title = null
this.summary = account.error
this.onPreferenceClickListener = null
}
}
iconVisible = true
}
lifecycleScope.launch {
val listCount = caldavDao.listCount(CaldavDao.LOCAL)
val quantityString = resources.getQuantityString(R.plurals.list_count, listCount, listCount)
findPreference(R.string.migrate).isVisible = listCount > 0
findPreference(R.string.local_lists).summary =
getString(R.string.migrate_count, quantityString)
}
}
private fun refreshPasswords(passwords: List<TasksAccountViewModel.AppPassword>) {
findPreference(R.string.app_passwords_more_info).isVisible = passwords.isEmpty()
val category = findPreference(R.string.app_passwords) as PreferenceCategory
category.removeAll()
passwords.forEach {
val description = it.description ?: getString(R.string.app_password)
category.addPreference(IconPreference(requireContext()).apply {
layoutResource = R.layout.preference_icon
iconVisible = true
drawable = context.getDrawable(R.drawable.ic_outline_delete_24px)
tint = ContextCompat.getColor(requireContext(), R.color.icon_tint_with_alpha)
title = description
iconClickListener = View.OnClickListener { _ ->
dialogBuilder.newDialog()
.setTitle(R.string.delete_tag_confirmation, description)
.setMessage(R.string.app_password_delete_confirmation)
.setPositiveButton(R.string.ok) { _, _ ->
viewModel.deletePassword(caldavAccount, it.id)
}
.setNegativeButton(R.string.cancel, null)
.show()
}
summary = """
${getString(R.string.app_password_created_at, formatString(it.createdAt))}
${getString(R.string.app_password_last_access, formatString(it.lastAccess) ?: getString(R.string.last_backup_never))}
""".trimIndent()
})
}
}
private fun formatString(date: Long?): String? = date?.let {
DateUtilities.getRelativeDay(
requireContext(),
date,
locale,
FormatStyle.FULL,
false,
true
)
}
companion object {
private const val EXTRA_ACCOUNT = "extra_account"
fun newTasksAccountPreference(account: CaldavAccount): Fragment {
val fragment = TasksAccount()
fragment.arguments = Bundle().apply {
putParcelable(EXTRA_ACCOUNT, account)
}
return fragment
}
}
}