408 lines
16 KiB
Kotlin
408 lines
16 KiB
Kotlin
package cy.agorise.bitsybitshareswallet.fragments
|
|
|
|
import android.content.*
|
|
import android.os.Bundle
|
|
import android.os.Handler
|
|
import android.os.IBinder
|
|
import android.preference.PreferenceManager
|
|
import android.util.Log
|
|
import android.view.LayoutInflater
|
|
import android.view.View
|
|
import android.view.ViewGroup
|
|
import androidx.fragment.app.Fragment
|
|
import com.afollestad.materialdialogs.MaterialDialog
|
|
import com.afollestad.materialdialogs.callbacks.onDismiss
|
|
import com.afollestad.materialdialogs.customview.customView
|
|
import com.afollestad.materialdialogs.list.customListAdapter
|
|
import com.afollestad.materialdialogs.list.listItems
|
|
import cy.agorise.bitsybitshareswallet.BuildConfig
|
|
import cy.agorise.bitsybitshareswallet.R
|
|
import cy.agorise.bitsybitshareswallet.adapters.FullNodesAdapter
|
|
import cy.agorise.bitsybitshareswallet.repositories.AuthorityRepository
|
|
import cy.agorise.bitsybitshareswallet.utils.Constants
|
|
import cy.agorise.bitsybitshareswallet.utils.CryptoUtils
|
|
import cy.agorise.graphenej.BrainKey
|
|
import cy.agorise.graphenej.api.android.NetworkService
|
|
import cy.agorise.graphenej.api.android.RxBus
|
|
import cy.agorise.graphenej.api.calls.GetDynamicGlobalProperties
|
|
import cy.agorise.graphenej.models.DynamicGlobalProperties
|
|
import cy.agorise.graphenej.models.JsonRpcResponse
|
|
import cy.agorise.graphenej.network.FullNode
|
|
import io.reactivex.Observer
|
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
|
import io.reactivex.disposables.CompositeDisposable
|
|
import io.reactivex.disposables.Disposable
|
|
import io.reactivex.schedulers.Schedulers
|
|
import kotlinx.android.synthetic.main.fragment_settings.*
|
|
import java.text.NumberFormat
|
|
|
|
class SettingsFragment : Fragment(), ServiceConnection, BaseSecurityLockDialog.OnPINPatternEnteredListener {
|
|
|
|
companion object {
|
|
private const val TAG = "SettingsFragment"
|
|
|
|
// Constants used to perform security locked requests
|
|
private const val ACTION_CHANGE_SECURITY_LOCK = 1
|
|
private const val ACTION_SHOW_BRAINKEY = 2
|
|
}
|
|
|
|
private var mDisposables = CompositeDisposable()
|
|
|
|
/* Network service connection */
|
|
private var mNetworkService: NetworkService? = null
|
|
|
|
/** Flag used to keep track of the NetworkService binding state */
|
|
private var mShouldUnbindNetwork: Boolean = false
|
|
|
|
// Dialog displaying the list of nodes and their latencies
|
|
private var mNodesDialog: MaterialDialog? = null
|
|
|
|
/** Adapter that holds the FullNode list used in the Bitshares nodes modal */
|
|
private var nodesAdapter: FullNodesAdapter? = null
|
|
|
|
private val mHandler = Handler()
|
|
|
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
|
setHasOptionsMenu(true)
|
|
|
|
return inflater.inflate(R.layout.fragment_settings, container, false)
|
|
}
|
|
|
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
super.onViewCreated(view, savedInstanceState)
|
|
|
|
initAutoCloseSwitch()
|
|
|
|
initNightModeSwitch()
|
|
|
|
tvNetworkStatus.setOnClickListener { v ->
|
|
if (mNetworkService != null) {
|
|
// PublishSubject used to announce full node latencies updates
|
|
val fullNodePublishSubject = mNetworkService!!.nodeLatencyObservable
|
|
fullNodePublishSubject?.observeOn(AndroidSchedulers.mainThread())?.subscribe(nodeLatencyObserver)
|
|
|
|
val fullNodes = mNetworkService!!.nodes
|
|
|
|
nodesAdapter = FullNodesAdapter(v.context)
|
|
nodesAdapter?.add(fullNodes)
|
|
|
|
mNodesDialog = MaterialDialog(v.context)
|
|
.title(text = String.format("%s v%s", getString(R.string.app_name), BuildConfig.VERSION_NAME))
|
|
.message(text = getString(R.string.title__bitshares_nodes_dialog, "-------"))
|
|
.customListAdapter(nodesAdapter as FullNodesAdapter)
|
|
.negativeButton(android.R.string.ok)
|
|
.onDismiss { mHandler.removeCallbacks(mRequestDynamicGlobalPropertiesTask) }
|
|
|
|
mNodesDialog?.show()
|
|
|
|
// Registering a recurrent task used to poll for dynamic global properties requests
|
|
mHandler.post(mRequestDynamicGlobalPropertiesTask)
|
|
}
|
|
}
|
|
|
|
// Obtain the current Security Lock Option selected and display it in the screen
|
|
val securityLockSelected = PreferenceManager.getDefaultSharedPreferences(context)
|
|
.getInt(Constants.KEY_SECURITY_LOCK_SELECTED, 0)
|
|
// Security Lock Options
|
|
// 0 -> None
|
|
// 1 -> PIN
|
|
// 2 -> Pattern
|
|
|
|
tvSecurityLockSelected.text = resources.getStringArray(R.array.security_lock_options)[securityLockSelected]
|
|
|
|
tvSecurityLock.setOnClickListener { onSecurityLockTextSelected() }
|
|
tvSecurityLockSelected.setOnClickListener { onSecurityLockTextSelected() }
|
|
|
|
btnViewBrainKey.setOnClickListener { onShowBrainKeyButtonSelected() }
|
|
|
|
// Connect to the RxBus, which receives events from the NetworkService
|
|
mDisposables.add(
|
|
RxBus.getBusInstance()
|
|
.asFlowable()
|
|
.observeOn(AndroidSchedulers.mainThread())
|
|
.subscribe { handleIncomingMessage(it) }
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Observer used to be notified about node latency measurement updates.
|
|
*/
|
|
private val nodeLatencyObserver = object : Observer<FullNode> {
|
|
override fun onSubscribe(d: Disposable) {
|
|
mDisposables.add(d)
|
|
}
|
|
|
|
override fun onNext(fullNode: FullNode) {
|
|
if (!fullNode.isRemoved)
|
|
nodesAdapter?.add(fullNode)
|
|
else
|
|
nodesAdapter?.remove(fullNode)
|
|
}
|
|
|
|
override fun onError(e: Throwable) {
|
|
Log.e(TAG, "nodeLatencyObserver.onError.Msg: " + e.message)
|
|
}
|
|
|
|
override fun onComplete() {}
|
|
}
|
|
|
|
private fun handleIncomingMessage(message: Any?) {
|
|
if (message is JsonRpcResponse<*>) {
|
|
if (message.result is DynamicGlobalProperties) {
|
|
val dynamicGlobalProperties = message.result as DynamicGlobalProperties
|
|
if (mNodesDialog != null && mNodesDialog?.isShowing == true) {
|
|
val blockNumber = NumberFormat.getInstance().format(dynamicGlobalProperties.head_block_number)
|
|
mNodesDialog?.message(text = getString(R.string.title__bitshares_nodes_dialog, blockNumber))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Task used to obtain frequent updates on the global dynamic properties object
|
|
*/
|
|
private val mRequestDynamicGlobalPropertiesTask = object : Runnable {
|
|
override fun run() {
|
|
if (mNetworkService != null) {
|
|
if (mNetworkService?.isConnected == true) {
|
|
mNetworkService?.sendMessage(GetDynamicGlobalProperties(), GetDynamicGlobalProperties.REQUIRED_API)
|
|
} else {
|
|
Log.d(TAG, "NetworkService exists but is not connected")
|
|
}
|
|
} else {
|
|
Log.d(TAG, "NetworkService reference is null")
|
|
}
|
|
mHandler.postDelayed(this, Constants.BLOCK_PERIOD)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetches the relevant preference from the SharedPreferences and configures the corresponding switch accordingly,
|
|
* and adds a listener to the said switch to store the preference in case the user changes it.
|
|
*/
|
|
private fun initAutoCloseSwitch() {
|
|
val autoCloseOn = PreferenceManager.getDefaultSharedPreferences(context)
|
|
.getBoolean(Constants.KEY_AUTO_CLOSE_ACTIVATED, true)
|
|
|
|
switchAutoClose.isChecked = autoCloseOn
|
|
|
|
switchAutoClose.setOnCheckedChangeListener { buttonView, isChecked ->
|
|
PreferenceManager.getDefaultSharedPreferences(buttonView.context).edit()
|
|
.putBoolean(Constants.KEY_AUTO_CLOSE_ACTIVATED, isChecked).apply()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetches the relevant preference from the SharedPreferences and configures the corresponding switch accordingly,
|
|
* and adds a listener to the said switch to store the preference in case the user changes it. Also makes a call to
|
|
* recreate the activity and apply the selected theme.
|
|
*/
|
|
private fun initNightModeSwitch() {
|
|
val nightModeOn = PreferenceManager.getDefaultSharedPreferences(context)
|
|
.getBoolean(Constants.KEY_NIGHT_MODE_ACTIVATED, false)
|
|
|
|
switchNightMode.isChecked = nightModeOn
|
|
|
|
switchNightMode.setOnCheckedChangeListener { buttonView, isChecked ->
|
|
|
|
PreferenceManager.getDefaultSharedPreferences(buttonView.context).edit()
|
|
.putBoolean(Constants.KEY_NIGHT_MODE_ACTIVATED, isChecked).apply()
|
|
|
|
// Recreates the activity to apply the selected theme
|
|
activity?.recreate()
|
|
}
|
|
}
|
|
|
|
private fun onSecurityLockTextSelected() {
|
|
if (!verifySecurityLock(ACTION_CHANGE_SECURITY_LOCK))
|
|
showChooseSecurityLockDialog()
|
|
}
|
|
|
|
/**
|
|
* Encapsulated the logic required to do actions possibly locked by the Security Lock. If PIN/Pattern is selected
|
|
* then it prompts for it.
|
|
*
|
|
* @param actionIdentifier Identifier used to know why a verify security lock was launched
|
|
* @return true if the action was handled, false otherwise
|
|
*/
|
|
private fun verifySecurityLock(actionIdentifier: Int): Boolean {
|
|
val securityLockSelected = PreferenceManager.getDefaultSharedPreferences(context)
|
|
.getInt(Constants.KEY_SECURITY_LOCK_SELECTED, 0)
|
|
// Security Lock Options
|
|
// 0 -> None
|
|
// 1 -> PIN
|
|
// 2 -> Pattern
|
|
|
|
// Args used for both PIN and Pattern options
|
|
val args = Bundle()
|
|
args.putInt(BaseSecurityLockDialog.KEY_STEP_SECURITY_LOCK,
|
|
BaseSecurityLockDialog.STEP_SECURITY_LOCK_VERIFY)
|
|
args.putInt(BaseSecurityLockDialog.KEY_ACTION_IDENTIFIER, actionIdentifier)
|
|
|
|
return when (securityLockSelected) {
|
|
0 -> { /* None */
|
|
false
|
|
}
|
|
1 -> { /* PIN */
|
|
val pinFrag = PINSecurityLockDialog()
|
|
pinFrag.arguments = args
|
|
pinFrag.show(childFragmentManager, "pin_security_lock_tag")
|
|
true
|
|
}
|
|
else -> { /* Pattern */
|
|
val patternFrag = PatternSecurityLockDialog()
|
|
patternFrag.arguments = args
|
|
patternFrag.show(childFragmentManager, "pattern_security_lock_tag")
|
|
true
|
|
}
|
|
}
|
|
}
|
|
|
|
override fun onPINPatternEntered(actionIdentifier: Int) {
|
|
if (actionIdentifier == ACTION_CHANGE_SECURITY_LOCK) {
|
|
showChooseSecurityLockDialog()
|
|
} else if (actionIdentifier == ACTION_SHOW_BRAINKEY) {
|
|
getBrainkey()
|
|
}
|
|
}
|
|
|
|
override fun onPINPatternChanged() {
|
|
// Obtain the new Security Lock Option selected and display it in the screen
|
|
val securityLockSelected = PreferenceManager.getDefaultSharedPreferences(context)
|
|
.getInt(Constants.KEY_SECURITY_LOCK_SELECTED, 0)
|
|
// Security Lock Options
|
|
// 0 -> None
|
|
// 1 -> PIN
|
|
// 2 -> Pattern
|
|
|
|
tvSecurityLockSelected.text = resources.getStringArray(R.array.security_lock_options)[securityLockSelected]
|
|
}
|
|
|
|
/**
|
|
* Shows a dialog so the user can select its desired Security Lock option.
|
|
*/
|
|
private fun showChooseSecurityLockDialog() {
|
|
context?.let {
|
|
MaterialDialog(it).show {
|
|
title(R.string.title__security_dialog)
|
|
listItems(R.array.security_lock_options) {_, index, _ ->
|
|
// Args used for both PIN and Pattern options
|
|
val args = Bundle()
|
|
args.putInt(BaseSecurityLockDialog.KEY_STEP_SECURITY_LOCK,
|
|
BaseSecurityLockDialog.STEP_SECURITY_LOCK_CREATE)
|
|
args.putInt(BaseSecurityLockDialog.KEY_ACTION_IDENTIFIER, -1)
|
|
|
|
when (index) {
|
|
0 -> { /* None */
|
|
PreferenceManager.getDefaultSharedPreferences(context).edit()
|
|
.putInt(Constants.KEY_SECURITY_LOCK_SELECTED, 0).apply() // 0 -> None
|
|
|
|
// Call this function to update the UI
|
|
onPINPatternChanged()
|
|
}
|
|
1 -> { /* PIN */
|
|
val pinFrag = PINSecurityLockDialog()
|
|
pinFrag.arguments = args
|
|
pinFrag.show(childFragmentManager, "pin_security_lock_tag")
|
|
}
|
|
else -> { /* Pattern */
|
|
val patternFrag = PatternSecurityLockDialog()
|
|
patternFrag.arguments = args
|
|
patternFrag.show(childFragmentManager, "pattern_security_lock_tag")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun onShowBrainKeyButtonSelected() {
|
|
if (!verifySecurityLock(ACTION_SHOW_BRAINKEY))
|
|
getBrainkey()
|
|
}
|
|
|
|
/**
|
|
* Obtains the brainKey from the authorities db table for the current user account and if it is not null it passes
|
|
* the brainKey to a method to show it in a nice MaterialDialog
|
|
*/
|
|
private fun getBrainkey() {
|
|
context?.let {
|
|
val userId = PreferenceManager.getDefaultSharedPreferences(it)
|
|
.getString(Constants.KEY_CURRENT_ACCOUNT_ID, "") ?: ""
|
|
|
|
val authorityRepository = AuthorityRepository(it)
|
|
|
|
mDisposables.add(authorityRepository.get(userId)
|
|
.subscribeOn(Schedulers.computation())
|
|
.observeOn(AndroidSchedulers.mainThread())
|
|
.subscribe { authority ->
|
|
if (authority != null) {
|
|
val plainBrainKey = CryptoUtils.decrypt(it, authority.encryptedBrainKey)
|
|
val plainSequenceNumber = CryptoUtils.decrypt(it, authority.encryptedSequenceNumber)
|
|
val sequenceNumber = Integer.parseInt(plainSequenceNumber)
|
|
val brainKey = BrainKey(plainBrainKey, sequenceNumber)
|
|
showBrainKeyDialog(brainKey)
|
|
}
|
|
}
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Shows the plain brainkey in a dialog so that the user can view and Copy it.
|
|
*/
|
|
private fun showBrainKeyDialog(brainKey: BrainKey) {
|
|
context?.let { context ->
|
|
MaterialDialog(context).show {
|
|
title(text = "BrainKey")
|
|
message(text = brainKey.brainKey)
|
|
customView(R.layout.dialog_copy_brainkey)
|
|
cancelable(false)
|
|
positiveButton(R.string.button__copied) { it.dismiss() }
|
|
}
|
|
}
|
|
}
|
|
|
|
override fun onResume() {
|
|
super.onResume()
|
|
|
|
val intent = Intent(context, NetworkService::class.java)
|
|
if (context?.bindService(intent, this, Context.BIND_AUTO_CREATE) == true) {
|
|
mShouldUnbindNetwork = true
|
|
} else {
|
|
Log.e(TAG, "Binding to the network service failed.")
|
|
}
|
|
}
|
|
|
|
override fun onPause() {
|
|
super.onPause()
|
|
|
|
// Unbinding from network service
|
|
if (mShouldUnbindNetwork) {
|
|
context?.unbindService(this)
|
|
mShouldUnbindNetwork = false
|
|
}
|
|
}
|
|
|
|
override fun onDestroy() {
|
|
super.onDestroy()
|
|
|
|
if (!mDisposables.isDisposed) mDisposables.dispose()
|
|
}
|
|
|
|
override fun onServiceDisconnected(name: ComponentName?) {
|
|
tvNetworkStatus.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null,
|
|
resources.getDrawable(R.drawable.ic_disconnected, null), null)
|
|
}
|
|
|
|
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
|
// We've bound to LocalService, cast the IBinder and get LocalService instance
|
|
val binder = service as NetworkService.LocalBinder
|
|
mNetworkService = binder.service
|
|
|
|
tvNetworkStatus.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null,
|
|
resources.getDrawable(R.drawable.ic_connected, null), null)
|
|
}
|
|
}
|
|
|