Skip to content

Commit

Permalink
Privacy UI implementation (planetdecred#529)
Browse files Browse the repository at this point in the history
* privacy: add introduction card and popup message

* Implement privacy page design

- add privacy introduction card to overview page
- add 'new' badger on privacy list items under wallet options
- add SetupPrivacy page
- update list cards design to include divider lines and higher radius

* complete account mixer page design and functionality

* Add mixer status card to overview page

* add 'go to mixer' button in overview page

* Multi-wallet mixing status

* coin format: add foreground color span

* privacy: check account name for conflict before creating privacy accounts

* remove unnecessary comments

* Fix faint transaction list divider

* privacy: add auto and manual setup screens

* Remove excess parameters from account spinner and make them class memebers

- add account filter to filter out accounts from account picker

* privacy: implement manual mixer setup

* account picker: add support for showing just one wallet
  • Loading branch information
beansgum committed Jan 29, 2021
1 parent e2d1a43 commit 80bbc94
Show file tree
Hide file tree
Showing 78 changed files with 2,595 additions and 867 deletions.
12 changes: 12 additions & 0 deletions app/src/main/AndroidManifest.xml
Expand Up @@ -181,6 +181,18 @@
android:name=".activities.AccountMixerActivity"
android:launchMode="singleTask" />

<activity
android:name=".activities.SetupPrivacy"
android:launchMode="singleTask" />

<activity
android:name=".activities.SetupMixerAccounts"
android:launchMode="singleTask" />

<activity
android:name=".activities.ManualMixerSetup"
android:launchMode="singleTask" />

<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
Expand Down
301 changes: 84 additions & 217 deletions app/src/main/java/com/dcrandroid/activities/AccountMixerActivity.kt

Large diffs are not rendered by default.

86 changes: 86 additions & 0 deletions app/src/main/java/com/dcrandroid/activities/ManualMixerSetup.kt
@@ -0,0 +1,86 @@
/*
* Copyright (c) 2018-2019 The Decred developers
* Use of this source code is governed by an ISC
* license that can be found in the LICENSE file.
*/

package com.dcrandroid.activities

import android.app.Activity
import android.content.Intent
import android.os.Bundle
import com.dcrandroid.R
import com.dcrandroid.data.Constants
import com.dcrandroid.util.PassPromptTitle
import com.dcrandroid.util.PassPromptUtil
import com.dcrandroid.util.SnackBar
import com.dcrandroid.view.util.AccountCustomSpinner
import dcrlibwallet.Wallet
import kotlinx.android.synthetic.main.activity_manual_mixer_setup.*
import kotlinx.android.synthetic.main.activity_setup_mixer_accounts.go_back
import kotlinx.android.synthetic.main.activity_setup_mixer_accounts.wallet_name
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch

const val MANUAL_MIXER_REQUEST_CODE = 500
class ManualMixerSetup : BaseActivity() {

private lateinit var wallet: Wallet
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_manual_mixer_setup)

wallet = multiWallet!!.walletWithID(intent.extras!!.getLong(Constants.WALLET_ID))
wallet_name.text = wallet.name

val mixed = AccountCustomSpinner(supportFragmentManager, mixed_account_spinner)
mixed.pickerTitle = R.string.mixed_account
mixed.singleWalletID = wallet.id

val unmixed = AccountCustomSpinner(supportFragmentManager, unmixed_account_spinner)
unmixed.pickerTitle = R.string.unmixed_account
unmixed.singleWalletID = wallet.id

mixed.init {
true
}
unmixed.init {
true
}

go_back.setOnClickListener { finish() }

btn_setup.setOnClickListener {
if (mixed.selectedAccount!!.accountNumber == unmixed.selectedAccount!!.accountNumber) {
SnackBar.showError(this, R.string.same_mixed_unmixed)
return@setOnClickListener
}

val title = PassPromptTitle(R.string.confirm_setup_mixer, R.string.confirm_setup_mixer, R.string.confirm_setup_mixer)
PassPromptUtil(this, wallet.id, title, allowFingerprint = true) { dialog, passphrase ->
if (passphrase == null) {
return@PassPromptUtil true
}

GlobalScope.launch(Dispatchers.Default) {
try {
wallet.setAccountMixerConfig(mixed.selectedAccount!!.accountNumber, unmixed.selectedAccount!!.accountNumber, passphrase)
multiWallet!!.setBoolConfigValueForKey(Constants.HAS_SETUP_PRIVACY, true)
val intent = Intent(this@ManualMixerSetup, AccountMixerActivity::class.java)
intent.putExtra(Constants.WALLET_ID, wallet.id)
dialog?.dismissAllowingStateLoss()
setResult(Activity.RESULT_OK)
startActivity(intent)
finish()
SnackBar.showText(this@ManualMixerSetup, R.string.mixer_setup_completed)
} catch (e: Exception) {
e.printStackTrace()
PassPromptUtil.handleError(this@ManualMixerSetup, e, dialog!!)
}
}
false
}.show()
}
}
}
112 changes: 112 additions & 0 deletions app/src/main/java/com/dcrandroid/activities/SetupMixerAccounts.kt
@@ -0,0 +1,112 @@
/*
* Copyright (c) 2018-2019 The Decred developers
* Use of this source code is governed by an ISC
* license that can be found in the LICENSE file.
*/

package com.dcrandroid.activities

import android.app.Activity
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.text.Html
import com.dcrandroid.R
import com.dcrandroid.data.Constants
import com.dcrandroid.dialog.InfoDialog
import com.dcrandroid.util.PassPromptTitle
import com.dcrandroid.util.PassPromptUtil
import com.dcrandroid.util.SnackBar
import dcrlibwallet.Wallet
import kotlinx.android.synthetic.main.activity_setup_mixer_accounts.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch

class SetupMixerAccounts : BaseActivity() {

private lateinit var wallet: Wallet
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_setup_mixer_accounts)

wallet = multiWallet!!.walletWithID(intent.extras!!.getLong(Constants.WALLET_ID))
wallet_name.text = wallet.name

btn_auto_setup.setOnClickListener {
InfoDialog(this)
.setDialogTitle(getString(R.string.privacy_intro_dialog_title))
.setMessage(Html.fromHtml(getString(R.string.privacy_intro_dialog_desc)))
.setNegativeButton(getString(R.string.cancel))
.setPositiveButton(getString(R.string.begin_setup), DialogInterface.OnClickListener { _, _ ->
checkAccountNameConflict()
})
.show()
}

btn_manual_setup.setOnClickListener {
val intent = Intent(this, ManualMixerSetup::class.java)
intent.putExtra(Constants.WALLET_ID, wallet.id)
startActivityForResult(intent, MANUAL_MIXER_REQUEST_CODE)
}

go_back.setOnClickListener { finish() }


}

private fun checkAccountNameConflict() {

if (wallet.hasAccount(Constants.MIXED) || wallet.hasAccount(Constants.UNMIXED)) {
InfoDialog(this)
.setDialogTitle(R.string.account_name_taken)
.setMessage(R.string.account_name_conflict_dialog_desc)
.setIcon(R.drawable.ic_alert2, R.drawable.grey_dialog_bg)
.cancelable(false)
.setPositiveButton(R.string.go_back_rename, DialogInterface.OnClickListener { dialog, _ ->
dialog.dismiss()
finish()
})
.show()
return
}

beginAutoSetup()
}

private fun beginAutoSetup() {
val title = PassPromptTitle(R.string.confirm_create_needed_accounts, R.string.confirm_create_needed_accounts, R.string.confirm_create_needed_accounts)
PassPromptUtil(this, wallet.id, title, allowFingerprint = true) { dialog, passphrase ->

if (passphrase == null) {
return@PassPromptUtil true
}

GlobalScope.launch(Dispatchers.Default) {
try {
wallet.createMixerAccounts(Constants.MIXED, Constants.UNMIXED, passphrase)
multiWallet!!.setBoolConfigValueForKey(Constants.HAS_SETUP_PRIVACY, true)
val intent = Intent(this@SetupMixerAccounts, AccountMixerActivity::class.java)
intent.putExtra(Constants.WALLET_ID, wallet.id)
dialog?.dismissAllowingStateLoss()
startActivity(intent)
finish()
SnackBar.showText(this@SetupMixerAccounts, R.string.mixer_setup_completed)
} catch (e: Exception) {
e.printStackTrace()

PassPromptUtil.handleError(this@SetupMixerAccounts, e, dialog!!)
}
}
false
}.show()
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == MANUAL_MIXER_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
finish()
}
}

}
37 changes: 37 additions & 0 deletions app/src/main/java/com/dcrandroid/activities/SetupPrivacy.kt
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2018-2019 The Decred developers
* Use of this source code is governed by an ISC
* license that can be found in the LICENSE file.
*/

package com.dcrandroid.activities

import android.content.Intent
import android.os.Bundle
import com.dcrandroid.R
import com.dcrandroid.data.Constants
import dcrlibwallet.Wallet
import kotlinx.android.synthetic.main.activity_setup_privacy.*

class SetupPrivacy : BaseActivity() {

private lateinit var wallet: Wallet
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_setup_privacy)

wallet = multiWallet!!.walletWithID(intent.extras!!.getLong(Constants.WALLET_ID))
wallet_name.text = wallet.name

btn_setup_mixer.setOnClickListener {
val intent = Intent(this, SetupMixerAccounts::class.java)
intent.putExtra(Constants.WALLET_ID, wallet.id)
finish()
startActivity(intent)
}

go_back.setOnClickListener { finish() }

multiWallet!!.setBoolConfigValueForKey(Constants.CHECKED_PRIVACY_PAGE, true)
}
}
16 changes: 4 additions & 12 deletions app/src/main/java/com/dcrandroid/adapter/AccountPickerAdapter.kt
Expand Up @@ -17,16 +17,11 @@ import com.dcrandroid.util.CoinFormat
import dcrlibwallet.Wallet
import kotlinx.android.synthetic.main.account_picker_header.view.*
import kotlinx.android.synthetic.main.account_picker_row.view.*
import java.util.*

enum class DisabledAccounts(value: Int) {
MixerChangeAccount(0),
MixerMixedAccount(1),
WatchOnlyWalletAccount(2),
}
class AccountPickerAdapter(val context: Context, val items: Array<Any>, val currentAccount: Account) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

class AccountPickerAdapter(val context: Context, val items: Array<Any>, val currentAccount: Account, val disabledAccounts: EnumSet<DisabledAccounts>,
val accountSelected: (account: Account) -> Unit?) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
lateinit var filterAccount: (account: Account) -> Boolean
lateinit var accountSelected: (account: Account) -> Unit? // must be set

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(context)
Expand Down Expand Up @@ -84,10 +79,7 @@ class AccountPickerAdapter(val context: Context, val items: Array<Any>, val curr
accountSelected(item)
}

var disableAccount = false
disableAccount = item.isMixerMixedAccount && disabledAccounts.contains(DisabledAccounts.MixerMixedAccount)
disableAccount = disableAccount || (item.isMixerChangeAccount && disabledAccounts.contains(DisabledAccounts.MixerChangeAccount))
holder.itemView.isEnabled = !disableAccount
holder.itemView.isEnabled = filterAccount(item)
}
}

Expand Down
47 changes: 47 additions & 0 deletions app/src/main/java/com/dcrandroid/adapter/MixerStatusAdapter.kt
@@ -0,0 +1,47 @@
/*
* Copyright (c) 2018-2019 The Decred developers
* Use of this source code is governed by an ISC
* license that can be found in the LICENSE file.
*/

package com.dcrandroid.adapter

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.dcrandroid.R
import com.dcrandroid.extensions.openedWalletsList
import com.dcrandroid.util.CoinFormat
import com.dcrandroid.util.WalletData
import dcrlibwallet.Dcrlibwallet
import dcrlibwallet.Wallet
import kotlinx.android.synthetic.main.mixer_status_row.view.*

class MixerStatusAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

private val multiWallet = WalletData.multiWallet!!
private val wallets = multiWallet.openedWalletsList()
private val mixingWallets: List<Wallet>
get() {
return wallets.filter { it.isAccountMixerActive }
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return ViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.mixer_status_row, parent, false))
}

override fun getItemCount() = mixingWallets.size

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {

holder.itemView.mixer_status_wallet_name.text = mixingWallets[position].name

val unmixedAccountNumber = mixingWallets[position].readInt32ConfigValueForKey(Dcrlibwallet.AccountMixerUnmixedAccount, -1)
holder.itemView.mixer_status_unmixed_balance.text = CoinFormat.formatAlpha(mixingWallets[position].getAccountBalance(unmixedAccountNumber).total)
}

class ViewHolder(v: View) : RecyclerView.ViewHolder(v)
}
Expand Up @@ -15,10 +15,8 @@ import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.recyclerview.widget.RecyclerView
import com.dcrandroid.R
import com.dcrandroid.extensions.hasWalletsRequiringPrivacySetup
import com.dcrandroid.extensions.hide
import com.dcrandroid.extensions.show
import com.dcrandroid.util.WalletData
import kotlinx.android.synthetic.main.tab_row.view.*

data class NavigationTab(@StringRes val title: Int, @DrawableRes val activeIcon: Int, @DrawableRes val inactiveIcon: Int)
Expand Down Expand Up @@ -55,7 +53,7 @@ class NavigationTabsAdapter(val context: Context, var activeTab: Int, var device
holder.icon.setImageResource(tabs[position].inactiveIcon)
}

if (position == 2 && (backupsNeeded > 0 || WalletData.multiWallet!!.hasWalletsRequiringPrivacySetup())) { // Wallets Page
if (position == 2 && (backupsNeeded > 0)) { // Wallets Page
holder.backupIcon.show()
} else {
holder.backupIcon.hide()
Expand Down
11 changes: 5 additions & 6 deletions app/src/main/java/com/dcrandroid/adapter/PopupMenuAdapter.kt
Expand Up @@ -11,6 +11,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.ListPopupWindow
import android.widget.PopupWindow
import androidx.annotation.ColorRes
import androidx.annotation.StringRes
Expand Down Expand Up @@ -62,18 +63,16 @@ class PopupMenuAdapter(private val context: Context, private val items: Array<An
holder.itemView.isEnabled = item.enabled

if (item.showNotificationDot) {
holder.itemView.notification_dot.show()
holder.itemView.new_badge.show()
} else {
holder.itemView.notification_dot.hide()
holder.itemView.new_badge.hide()
}

holder.itemView.setOnClickListener {
itemClicked(position)
}
} else if (item is PopupDivider) {
val layoutParams = holder.itemView.layoutParams
layoutParams.width = item.widthPixels
holder.itemView.layoutParams = layoutParams
holder.itemView.visibility = View.GONE
}
}

Expand All @@ -86,7 +85,7 @@ class PopupUtil {
val context = anchorView.context
val inflater = LayoutInflater.from(context)
val view = inflater.inflate(R.layout.popup_layout, null)
val window = PopupWindow(view, LinearLayout.LayoutParams.WRAP_CONTENT,
val window = PopupWindow(view, ListPopupWindow.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT, true)

val recyclerView = view.popup_rv
Expand Down

0 comments on commit 80bbc94

Please sign in to comment.