Migrate app to ViewBinding. (#6)

* Enable ViewBinding

- Migrate MainActivity to ViewBinding.
- Migrate HomeFragment to ViewBinding.

* Migrate a batch of Fragments to ViewBinding.

- Migrate BalancesFragment to ViewBinding.
- Migrate CreateAccountFragment to ViewBinding.
- Migrate EReceiptFragment to ViewBinding.
- Migrate FilterOptionsDialog to ViewBinding.
- Migrate HomeFragment to ViewBinding.

* Migrate another batch of Fragments to ViewBinding.

- Migrate ImportBrainkeyFragment to ViewBinding.
- Migrate LicenseFragment to ViewBinding.
- Migrate MerchantsFragment to ViewBinding.
- Migrate NetWorthFragment to ViewBinding.
- Migrate PatternSecurityLockDialog to ViewBinding.

* Migrate final batch of Fragments to ViewBinding.

- Migrate PINSecurityLockScreen to ViewBinding.
- Migrate ReceiveTransactionFragment to ViewBinding.
- Migrate SendTransactionFragment to ViewBinding.
- Migrate SettingsFragment to ViewBinding.
- Migrate TransactionsFragment to ViewBinding.

* Migrate to the kotlin-parcelize plugin.

- Get rid of the deprecated kotlin-android-extensions plugin.
This commit is contained in:
Severiano Jaramillo 2021-03-04 22:46:36 -08:00 committed by GitHub
parent cc53526038
commit 0ec0457eb7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 1049 additions and 597 deletions

View file

@ -1,16 +1,11 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-parcelize'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
apply plugin: "androidx.navigation.safeargs.kotlin" apply plugin: "androidx.navigation.safeargs.kotlin"
apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics' apply plugin: 'com.google.firebase.crashlytics'
// Needed for Kotlin's @Parcelize annotation
androidExtensions {
experimental = true
}
android { android {
compileSdkVersion 29 compileSdkVersion 29
defaultConfig { defaultConfig {
@ -31,6 +26,11 @@ android {
androidTest.assets.srcDirs += files("$projectDir/schemas".toString()) androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
} }
} }
buildFeatures {
viewBinding true
}
buildTypes { buildTypes {
debug { debug {
minifyEnabled false minifyEnabled false

View file

@ -12,8 +12,8 @@ import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.onNavDestinationSelected import androidx.navigation.ui.onNavDestinationSelected
import androidx.navigation.ui.setupActionBarWithNavController import androidx.navigation.ui.setupActionBarWithNavController
import cy.agorise.bitsybitshareswallet.R import cy.agorise.bitsybitshareswallet.R
import cy.agorise.bitsybitshareswallet.databinding.ActivityMainBinding
import cy.agorise.bitsybitshareswallet.utils.Constants import cy.agorise.bitsybitshareswallet.utils.Constants
import kotlinx.android.synthetic.main.activity_main.*
/** /**
* Uses the AAC Navigation Component with a NavHostFragment which is the place where all Fragments are shown, * Uses the AAC Navigation Component with a NavHostFragment which is the place where all Fragments are shown,
@ -21,7 +21,8 @@ import kotlinx.android.synthetic.main.activity_main.*
*/ */
class MainActivity : ConnectedActivity() { class MainActivity : ConnectedActivity() {
private lateinit var appBarConfiguration : AppBarConfiguration private lateinit var binding: ActivityMainBinding
private lateinit var appBarConfiguration: AppBarConfiguration
// Handler and Runnable used to add a timer for user inaction and close the app if enough time has passed // Handler and Runnable used to add a timer for user inaction and close the app if enough time has passed
private lateinit var mHandler: Handler private lateinit var mHandler: Handler
@ -31,12 +32,14 @@ class MainActivity : ConnectedActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
// Sets the theme to night mode if it has been selected by the user // Sets the theme to night mode if it has been selected by the user
if (PreferenceManager.getDefaultSharedPreferences(this) if (PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean(Constants.KEY_NIGHT_MODE_ACTIVATED, false)) { .getBoolean(Constants.KEY_NIGHT_MODE_ACTIVATED, false)
) {
setTheme(R.style.Theme_Bitsy_Dark) setTheme(R.style.Theme_Bitsy_Dark)
} }
setContentView(R.layout.activity_main) binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(toolbar) setSupportActionBar(binding.toolbar)
val host: NavHostFragment = supportFragmentManager val host: NavHostFragment = supportFragmentManager
.findFragmentById(R.id.navHostFragment) as NavHostFragment? ?: return .findFragmentById(R.id.navHostFragment) as NavHostFragment? ?: return
@ -56,7 +59,8 @@ class MainActivity : ConnectedActivity() {
// closes the app, if not then it just restarts the Handler (timer) // closes the app, if not then it just restarts the Handler (timer)
mRunnable = Runnable { mRunnable = Runnable {
if (PreferenceManager.getDefaultSharedPreferences(this) if (PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean(Constants.KEY_AUTO_CLOSE_ACTIVATED, true)) { .getBoolean(Constants.KEY_AUTO_CLOSE_ACTIVATED, true)
) {
finish() finish()
android.os.Process.killProcess(android.os.Process.myPid()) android.os.Process.killProcess(android.os.Process.myPid())
} else } else
@ -107,8 +111,8 @@ class MainActivity : ConnectedActivity() {
override fun onBackPressed() { override fun onBackPressed() {
// Trick used to avoid crashes when the user is in the License or ImportBrainkey and presses the back button // Trick used to avoid crashes when the user is in the License or ImportBrainkey and presses the back button
val currentDestination=NavHostFragment.findNavController(navHostFragment).currentDestination val currentDestination = binding.navHostFragment.findNavController().currentDestination
when(currentDestination?.id) { when (currentDestination?.id) {
R.id.license_dest, R.id.import_brainkey_dest -> finish() R.id.license_dest, R.id.import_brainkey_dest -> finish()
else -> super.onBackPressed() else -> super.onBackPressed()
} }

View file

@ -9,37 +9,49 @@ import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import cy.agorise.bitsybitshareswallet.R
import cy.agorise.bitsybitshareswallet.adapters.BalancesAdapter import cy.agorise.bitsybitshareswallet.adapters.BalancesAdapter
import cy.agorise.bitsybitshareswallet.database.joins.BalanceDetail import cy.agorise.bitsybitshareswallet.database.joins.BalanceDetail
import cy.agorise.bitsybitshareswallet.databinding.FragmentBalancesBinding
import cy.agorise.bitsybitshareswallet.viewmodels.BalanceDetailViewModel import cy.agorise.bitsybitshareswallet.viewmodels.BalanceDetailViewModel
import kotlinx.android.synthetic.main.fragment_balances.*
class BalancesFragment: Fragment() { class BalancesFragment : Fragment() {
private var _binding: FragmentBalancesBinding? = null
private val binding get() = _binding!!
private lateinit var mBalanceDetailViewModel: BalanceDetailViewModel private lateinit var mBalanceDetailViewModel: BalanceDetailViewModel
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View {
setHasOptionsMenu(true) setHasOptionsMenu(true)
return inflater.inflate(R.layout.fragment_balances, container, false) _binding = FragmentBalancesBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
// Configure BalanceDetailViewModel to show the current balances // Configure BalanceDetailViewModel to show the current balances
mBalanceDetailViewModel = ViewModelProviders.of(this).get(BalanceDetailViewModel::class.java) mBalanceDetailViewModel =
ViewModelProviders.of(this).get(BalanceDetailViewModel::class.java)
val balancesAdapter = BalancesAdapter(context!!) val balancesAdapter = BalancesAdapter(context!!)
rvBalances.adapter = balancesAdapter binding.rvBalances.adapter = balancesAdapter
rvBalances.layoutManager = LinearLayoutManager(context!!) binding.rvBalances.layoutManager = LinearLayoutManager(context!!)
rvBalances.addItemDecoration(DividerItemDecoration(context!!, DividerItemDecoration.VERTICAL)) binding.rvBalances.addItemDecoration(
DividerItemDecoration(context!!, DividerItemDecoration.VERTICAL)
)
mBalanceDetailViewModel.getAll().observe(this, Observer<List<BalanceDetail>> { balancesDetails -> mBalanceDetailViewModel.getAll()
.observe(this, Observer<List<BalanceDetail>> { balancesDetails ->
balancesAdapter.replaceAll(balancesDetails) balancesAdapter.replaceAll(balancesDetails)
}) })
} }

View file

@ -8,9 +8,15 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.collection.LongSparseArray import androidx.collection.LongSparseArray
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.afollestad.materialdialogs.MaterialDialog
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.jakewharton.rxbinding3.widget.textChanges import com.jakewharton.rxbinding3.widget.textChanges
import cy.agorise.bitsybitshareswallet.R import cy.agorise.bitsybitshareswallet.R
import cy.agorise.bitsybitshareswallet.databinding.FragmentCreateAccountBinding
import cy.agorise.bitsybitshareswallet.models.FaucetRequest
import cy.agorise.bitsybitshareswallet.models.FaucetResponse
import cy.agorise.bitsybitshareswallet.network.FaucetService import cy.agorise.bitsybitshareswallet.network.FaucetService
import cy.agorise.bitsybitshareswallet.network.ServiceGenerator
import cy.agorise.bitsybitshareswallet.utils.Constants import cy.agorise.bitsybitshareswallet.utils.Constants
import cy.agorise.bitsybitshareswallet.utils.containsDigits import cy.agorise.bitsybitshareswallet.utils.containsDigits
import cy.agorise.bitsybitshareswallet.utils.containsVowels import cy.agorise.bitsybitshareswallet.utils.containsVowels
@ -22,20 +28,14 @@ import cy.agorise.graphenej.api.calls.GetAccountByName
import cy.agorise.graphenej.models.AccountProperties import cy.agorise.graphenej.models.AccountProperties
import cy.agorise.graphenej.models.JsonRpcResponse import cy.agorise.graphenej.models.JsonRpcResponse
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.synthetic.main.fragment_create_account.*
import org.bitcoinj.core.ECKey import org.bitcoinj.core.ECKey
import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.Response
import java.io.BufferedReader import java.io.BufferedReader
import java.io.IOException import java.io.IOException
import java.io.InputStreamReader import java.io.InputStreamReader
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import com.afollestad.materialdialogs.MaterialDialog
import com.google.firebase.crashlytics.FirebaseCrashlytics
import cy.agorise.bitsybitshareswallet.models.FaucetRequest
import cy.agorise.bitsybitshareswallet.models.FaucetResponse
import cy.agorise.bitsybitshareswallet.network.ServiceGenerator
import retrofit2.Call
import retrofit2.Response
class CreateAccountFragment : BaseAccountFragment() { class CreateAccountFragment : BaseAccountFragment() {
@ -49,10 +49,14 @@ class CreateAccountFragment : BaseAccountFragment() {
// Used when trying to validate that the account name is available // Used when trying to validate that the account name is available
private const val RESPONSE_GET_ACCOUNT_BY_NAME_VALIDATION = 1 private const val RESPONSE_GET_ACCOUNT_BY_NAME_VALIDATION = 1
// Used when trying to obtain the info of the newly created account // Used when trying to obtain the info of the newly created account
private const val RESPONSE_GET_ACCOUNT_BY_NAME_CREATED = 2 private const val RESPONSE_GET_ACCOUNT_BY_NAME_CREATED = 2
} }
private var _binding: FragmentCreateAccountBinding? = null
private val binding get() = _binding!!
private lateinit var mAddress: String private lateinit var mAddress: String
/** Variables used to store the validation status of the form fields */ /** Variables used to store the validation status of the form fields */
@ -63,10 +67,20 @@ class CreateAccountFragment : BaseAccountFragment() {
// Map used to keep track of request and response id pairs // Map used to keep track of request and response id pairs
private val responseMap = LongSparseArray<Int>() private val responseMap = LongSparseArray<Int>()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
setHasOptionsMenu(true) setHasOptionsMenu(true)
return inflater.inflate(R.layout.fragment_create_account, container, false) _binding = FragmentCreateAccountBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -77,7 +91,7 @@ class CreateAccountFragment : BaseAccountFragment() {
// Use RxJava Debounce to check the validity and availability of the user's proposed account name // Use RxJava Debounce to check the validity and availability of the user's proposed account name
mDisposables.add( mDisposables.add(
tietAccountName.textChanges() binding.tietAccountName.textChanges()
.skipInitialValue() .skipInitialValue()
.debounce(800, TimeUnit.MILLISECONDS) .debounce(800, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
@ -89,7 +103,7 @@ class CreateAccountFragment : BaseAccountFragment() {
// Use RxJava Debounce to update the PIN error only after the user stops writing for > 500 ms // Use RxJava Debounce to update the PIN error only after the user stops writing for > 500 ms
mDisposables.add( mDisposables.add(
tietPin.textChanges() binding.tietPin.textChanges()
.skipInitialValue() .skipInitialValue()
.debounce(500, TimeUnit.MILLISECONDS) .debounce(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
@ -101,7 +115,7 @@ class CreateAccountFragment : BaseAccountFragment() {
// Use RxJava Debounce to update the PIN Confirmation error only after the user stops writing for > 500 ms // Use RxJava Debounce to update the PIN Confirmation error only after the user stops writing for > 500 ms
mDisposables.add( mDisposables.add(
tietPinConfirmation.textChanges() binding.tietPinConfirmation.textChanges()
.skipInitialValue() .skipInitialValue()
.debounce(500, TimeUnit.MILLISECONDS) .debounce(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
@ -111,10 +125,10 @@ class CreateAccountFragment : BaseAccountFragment() {
) )
) )
btnCancel.setOnClickListener { findNavController().navigateUp() } binding.btnCancel.setOnClickListener { findNavController().navigateUp() }
btnCreate.isEnabled = false binding.btnCreate.isEnabled = false
btnCreate.setOnClickListener { createAccount() } binding.btnCreate.setOnClickListener { createAccount() }
// Generating BrainKey // Generating BrainKey
generateKeys() generateKeys()
@ -123,19 +137,23 @@ class CreateAccountFragment : BaseAccountFragment() {
private fun validateAccountName(accountName: String) { private fun validateAccountName(accountName: String) {
isAccountValidAndAvailable = false isAccountValidAndAvailable = false
if ( !isAccountLengthValid(accountName) ) { if (!isAccountLengthValid(accountName)) {
tilAccountName.helperText = "" binding.tilAccountName.helperText = ""
tilAccountName.error = getString(R.string.error__invalid_account_length) binding.tilAccountName.error = getString(R.string.error__invalid_account_length)
} else if ( !isAccountStartValid(accountName) ) { } else if (!isAccountStartValid(accountName)) {
tilAccountName.helperText = "" binding.tilAccountName.helperText = ""
tilAccountName.error = getString(R.string.error__invalid_account_start) binding.tilAccountName.error = getString(R.string.error__invalid_account_start)
} else if ( !isAccountNameValid(accountName) ) { } else if (!isAccountNameValid(accountName)) {
tilAccountName.helperText = "" binding.tilAccountName.helperText = ""
tilAccountName.error = getString(R.string.error__invalid_account_name) binding.tilAccountName.error = getString(R.string.error__invalid_account_name)
} else { } else {
tilAccountName.isErrorEnabled = false binding.tilAccountName.isErrorEnabled = false
tilAccountName.helperText = getString(R.string.text__verifying_account_availability) binding.tilAccountName.helperText =
val id = mNetworkService?.sendMessage(GetAccountByName(accountName), GetAccountByName.REQUIRED_API) getString(R.string.text__verifying_account_availability)
val id = mNetworkService?.sendMessage(
GetAccountByName(accountName),
GetAccountByName.REQUIRED_API
)
if (id != null) if (id != null)
responseMap.append(id, RESPONSE_GET_ACCOUNT_BY_NAME_VALIDATION) responseMap.append(id, RESPONSE_GET_ACCOUNT_BY_NAME_VALIDATION)
@ -170,13 +188,13 @@ class CreateAccountFragment : BaseAccountFragment() {
} }
private fun validatePIN() { private fun validatePIN() {
val pin = tietPin.text.toString() val pin = binding.tietPin.text.toString()
if (pin.length < Constants.MIN_PIN_LENGTH) { if (pin.length < Constants.MIN_PIN_LENGTH) {
tilPin.error = getString(R.string.error__pin_too_short) binding.tilPin.error = getString(R.string.error__pin_too_short)
isPINValid = false isPINValid = false
} else { } else {
tilPin.isErrorEnabled = false binding.tilPin.isErrorEnabled = false
isPINValid = true isPINValid = true
} }
@ -184,13 +202,13 @@ class CreateAccountFragment : BaseAccountFragment() {
} }
private fun validatePINConfirmation() { private fun validatePINConfirmation() {
val pinConfirmation = tietPinConfirmation.text.toString() val pinConfirmation = binding.tietPinConfirmation.text.toString()
if (pinConfirmation != tietPin.text.toString()) { if (pinConfirmation != binding.tietPin.text.toString()) {
tilPinConfirmation.error = getString(R.string.error__pin_mismatch) binding.tilPinConfirmation.error = getString(R.string.error__pin_mismatch)
isPINConfirmationValid = false isPINConfirmationValid = false
} else { } else {
tilPinConfirmation.isErrorEnabled = false binding.tilPinConfirmation.isErrorEnabled = false
isPINConfirmationValid = true isPINConfirmationValid = true
} }
@ -198,7 +216,8 @@ class CreateAccountFragment : BaseAccountFragment() {
} }
private fun enableDisableCreateButton() { private fun enableDisableCreateButton() {
btnCreate.isEnabled = (isPINValid && isPINConfirmationValid && isAccountValidAndAvailable) binding.btnCreate.isEnabled =
(isPINValid && isPINConfirmationValid && isAccountValidAndAvailable)
} }
override fun handleJsonRpcResponse(response: JsonRpcResponse<*>) { override fun handleJsonRpcResponse(response: JsonRpcResponse<*>) {
@ -211,7 +230,7 @@ class CreateAccountFragment : BaseAccountFragment() {
} }
} }
override fun handleConnectionStatusUpdate(connectionStatusUpdate: ConnectionStatusUpdate) { } override fun handleConnectionStatusUpdate(connectionStatusUpdate: ConnectionStatusUpdate) {}
/** /**
* Handles the response from the NetworkService's GetAccountByName call to decide if the user's suggested * Handles the response from the NetworkService's GetAccountByName call to decide if the user's suggested
@ -219,12 +238,12 @@ class CreateAccountFragment : BaseAccountFragment() {
*/ */
private fun handleAccountNameValidation(result: Any?) { private fun handleAccountNameValidation(result: Any?) {
if (result is AccountProperties) { if (result is AccountProperties) {
tilAccountName.helperText = "" binding.tilAccountName.helperText = ""
tilAccountName.error = getString(R.string.error__account_not_available) binding.tilAccountName.error = getString(R.string.error__account_not_available)
isAccountValidAndAvailable = false isAccountValidAndAvailable = false
} else { } else {
tilAccountName.isErrorEnabled = false binding.tilAccountName.isErrorEnabled = false
tilAccountName.helperText = getString(R.string.text__account_is_available) binding.tilAccountName.helperText = getString(R.string.text__account_is_available)
isAccountValidAndAvailable = true isAccountValidAndAvailable = true
} }
@ -237,7 +256,7 @@ class CreateAccountFragment : BaseAccountFragment() {
*/ */
private fun handleAccountNameCreated(result: Any?) { private fun handleAccountNameCreated(result: Any?) {
if (result is AccountProperties) { if (result is AccountProperties) {
onAccountSelected(result, tietPin.text.toString()) onAccountSelected(result, binding.tietPin.text.toString())
} else { } else {
context?.toast(getString(R.string.error__created_account_not_found)) context?.toast(getString(R.string.error__created_account_not_found))
setStateError() setStateError()
@ -248,9 +267,9 @@ class CreateAccountFragment : BaseAccountFragment() {
* Sets the state to Loading, when the app is trying to create an account and waiting for the response. * Sets the state to Loading, when the app is trying to create an account and waiting for the response.
*/ */
private fun setStateLoading() { private fun setStateLoading() {
btnCancel.isEnabled = false binding.btnCancel.isEnabled = false
btnCreate.isEnabled = false binding.btnCreate.isEnabled = false
progressBar.visibility = View.VISIBLE binding.progressBar.visibility = View.VISIBLE
} }
/** /**
@ -258,9 +277,9 @@ class CreateAccountFragment : BaseAccountFragment() {
* the information from the newly created account. * the information from the newly created account.
*/ */
private fun setStateError() { private fun setStateError() {
btnCancel.isEnabled = true binding.btnCancel.isEnabled = true
btnCreate.isEnabled = false binding.btnCreate.isEnabled = false
progressBar.visibility = View.GONE binding.progressBar.visibility = View.GONE
} }
/** /**
@ -270,7 +289,7 @@ class CreateAccountFragment : BaseAccountFragment() {
private fun createAccount() { private fun createAccount() {
setStateLoading() setStateLoading()
val accountName = tietAccountName.text.toString() val accountName = binding.tietAccountName.text.toString()
val faucetRequest = FaucetRequest(accountName, mAddress, Constants.FAUCET_REFERRER) val faucetRequest = FaucetRequest(accountName, mAddress, Constants.FAUCET_REFERRER)
val sg = ServiceGenerator(Constants.FAUCET_URL) val sg = ServiceGenerator(Constants.FAUCET_URL)
@ -280,7 +299,10 @@ class CreateAccountFragment : BaseAccountFragment() {
// Execute the call asynchronously. Get a positive or negative callback. // Execute the call asynchronously. Get a positive or negative callback.
call?.enqueue(object : Callback<FaucetResponse> { call?.enqueue(object : Callback<FaucetResponse> {
override fun onResponse(call: Call<FaucetResponse>, response: Response<FaucetResponse>) { override fun onResponse(
call: Call<FaucetResponse>,
response: Response<FaucetResponse>
) {
// The network call was a success and we got a response, obtain the info of the newly created account // The network call was a success and we got a response, obtain the info of the newly created account
// with a delay to let the nodes update their information // with a delay to let the nodes update their information
val handler = Handler() val handler = Handler()
@ -307,8 +329,10 @@ class CreateAccountFragment : BaseAccountFragment() {
private fun getCreatedAccountInfo(faucetResponse: FaucetResponse?) { private fun getCreatedAccountInfo(faucetResponse: FaucetResponse?) {
if (faucetResponse?.account != null) { if (faucetResponse?.account != null) {
val id = mNetworkService?.sendMessage(GetAccountByName(faucetResponse.account?.name), val id = mNetworkService?.sendMessage(
GetAccountByName.REQUIRED_API) GetAccountByName(faucetResponse.account?.name),
GetAccountByName.REQUIRED_API
)
if (id != null) if (id != null)
responseMap.append(id, RESPONSE_GET_ACCOUNT_BY_NAME_CREATED) responseMap.append(id, RESPONSE_GET_ACCOUNT_BY_NAME_CREATED)
@ -320,7 +344,7 @@ class CreateAccountFragment : BaseAccountFragment() {
getString(R.string.error__faucet_template, "None") getString(R.string.error__faucet_template, "None")
} }
context?.let {context -> context?.let { context ->
MaterialDialog(context) MaterialDialog(context)
.title(R.string.title_error) .title(R.string.title_error)
.message(text = content) .message(text = content)
@ -338,7 +362,8 @@ class CreateAccountFragment : BaseAccountFragment() {
var reader: BufferedReader? = null var reader: BufferedReader? = null
val dictionary: String val dictionary: String
try { try {
reader = BufferedReader(InputStreamReader(context!!.assets.open(BRAINKEY_FILE), "UTF-8")) reader =
BufferedReader(InputStreamReader(context!!.assets.open(BRAINKEY_FILE), "UTF-8"))
dictionary = reader.readLine() dictionary = reader.readLine()
val brainKeySuggestion = BrainKey.suggest(dictionary) val brainKeySuggestion = BrainKey.suggest(dictionary)
@ -347,7 +372,7 @@ class CreateAccountFragment : BaseAccountFragment() {
Log.d(TAG, "brain key: $brainKeySuggestion") Log.d(TAG, "brain key: $brainKeySuggestion")
Log.d(TAG, "address would be: $address") Log.d(TAG, "address would be: $address")
mAddress = address.toString() mAddress = address.toString()
tvBrainKey.text = mBrainKey?.brainKey binding.tvBrainKey.text = mBrainKey?.brainKey
} catch (e: IOException) { } catch (e: IOException) {
Log.e(TAG, "IOException while trying to generate key. Msg: " + e.message) Log.e(TAG, "IOException while trying to generate key. Msg: " + e.message)
@ -364,7 +389,10 @@ class CreateAccountFragment : BaseAccountFragment() {
try { try {
reader.close() reader.close()
} catch (e: IOException) { } catch (e: IOException) {
Log.e(TAG, "IOException while trying to close BufferedReader. Msg: " + e.message) Log.e(
TAG,
"IOException while trying to close BufferedReader. Msg: " + e.message
)
} }
} }

View file

@ -16,14 +16,13 @@ import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProviders
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import com.google.firebase.crashlytics.FirebaseCrashlytics import com.google.firebase.crashlytics.FirebaseCrashlytics
import cy.agorise.bitsybitshareswallet.R import cy.agorise.bitsybitshareswallet.R
import cy.agorise.bitsybitshareswallet.database.joins.TransferDetail import cy.agorise.bitsybitshareswallet.database.joins.TransferDetail
import cy.agorise.bitsybitshareswallet.databinding.FragmentEReceiptBinding
import cy.agorise.bitsybitshareswallet.utils.Constants import cy.agorise.bitsybitshareswallet.utils.Constants
import cy.agorise.bitsybitshareswallet.utils.Helper import cy.agorise.bitsybitshareswallet.utils.Helper
import cy.agorise.bitsybitshareswallet.utils.toast import cy.agorise.bitsybitshareswallet.utils.toast
import cy.agorise.bitsybitshareswallet.viewmodels.EReceiptViewModel import cy.agorise.bitsybitshareswallet.viewmodels.EReceiptViewModel
import kotlinx.android.synthetic.main.fragment_e_receipt.*
import java.math.RoundingMode import java.math.RoundingMode
import java.text.DecimalFormat import java.text.DecimalFormat
import java.text.DecimalFormatSymbols import java.text.DecimalFormatSymbols
@ -39,15 +38,28 @@ class EReceiptFragment : Fragment() {
private const val REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION = 100 private const val REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION = 100
} }
private var _binding: FragmentEReceiptBinding? = null
private val binding get() = _binding!!
private val args: EReceiptFragmentArgs by navArgs() private val args: EReceiptFragmentArgs by navArgs()
private lateinit var mEReceiptViewModel: EReceiptViewModel private lateinit var mEReceiptViewModel: EReceiptViewModel
private lateinit var mLocale: Locale private lateinit var mLocale: Locale
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
setHasOptionsMenu(true) setHasOptionsMenu(true)
return inflater.inflate(R.layout.fragment_e_receipt, container, false) _binding = FragmentEReceiptBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -65,28 +77,30 @@ class EReceiptFragment : Fragment() {
mEReceiptViewModel = ViewModelProviders.of(this).get(EReceiptViewModel::class.java) mEReceiptViewModel = ViewModelProviders.of(this).get(EReceiptViewModel::class.java)
mEReceiptViewModel.get(userId, transferId).observe(this, Observer<TransferDetail> { transferDetail -> mEReceiptViewModel.get(userId, transferId)
.observe(this, Observer<TransferDetail> { transferDetail ->
bindTransferDetail(transferDetail) bindTransferDetail(transferDetail)
}) })
} }
private fun bindTransferDetail(transferDetail: TransferDetail) { private fun bindTransferDetail(transferDetail: TransferDetail) {
context?.let { vPaymentDirection.setBackgroundColor(ContextCompat.getColor(it, context?.let { context ->
if(transferDetail.direction) R.color.colorReceive else R.color.colorSend val colorRes = if (transferDetail.direction) R.color.colorReceive else R.color.colorSend
))} binding.vPaymentDirection.setBackgroundColor(ContextCompat.getColor(context, colorRes))
}
tvFrom.text = transferDetail.from ?: "" binding.tvFrom.text = transferDetail.from ?: ""
tvTo.text = transferDetail.to ?: "" binding.tvTo.text = transferDetail.to ?: ""
// Show the crypto amount correctly formatted // Show the crypto amount correctly formatted
val df = DecimalFormat("####."+("#".repeat(transferDetail.assetPrecision))) val df = DecimalFormat("####." + ("#".repeat(transferDetail.assetPrecision)))
df.roundingMode = RoundingMode.CEILING df.roundingMode = RoundingMode.CEILING
df.decimalFormatSymbols = DecimalFormatSymbols(Locale.getDefault()) df.decimalFormatSymbols = DecimalFormatSymbols(Locale.getDefault())
val amount = transferDetail.assetAmount.toDouble() / val amount = transferDetail.assetAmount.toDouble() /
Math.pow(10.toDouble(), transferDetail.assetPrecision.toDouble()) Math.pow(10.toDouble(), transferDetail.assetPrecision.toDouble())
val assetAmount = "${df.format(amount)} ${transferDetail.getUIAssetSymbol()}" val assetAmount = "${df.format(amount)} ${transferDetail.getUIAssetSymbol()}"
tvAmount.text = assetAmount binding.tvAmount.text = assetAmount
// Fiat equivalent // Fiat equivalent
if (transferDetail.fiatAmount != null && transferDetail.fiatSymbol != null) { if (transferDetail.fiatAmount != null && transferDetail.fiatSymbol != null) {
@ -96,20 +110,21 @@ class EReceiptFragment : Fragment() {
Math.pow(10.0, currency.defaultFractionDigits.toDouble()) Math.pow(10.0, currency.defaultFractionDigits.toDouble())
val equivalentValue = "${numberFormat.format(fiatEquivalent)} ${currency.currencyCode}" val equivalentValue = "${numberFormat.format(fiatEquivalent)} ${currency.currencyCode}"
tvEquivalentValue.text = equivalentValue binding.tvEquivalentValue.text = equivalentValue
} else { } else {
tvEquivalentValue.text = "-" binding.tvEquivalentValue.text = "-"
} }
// Memo // Memo
if (transferDetail.memo != "") if (transferDetail.memo != "")
tvMemo.text = getString(R.string.template__memo, transferDetail.memo) binding.tvMemo.text = getString(R.string.template__memo, transferDetail.memo)
else else
tvMemo.visibility = View.GONE binding.tvMemo.visibility = View.GONE
// Date // Date
val dateFormat = SimpleDateFormat("dd MMM HH:mm:ss z", mLocale) val dateFormat = SimpleDateFormat("dd MMM HH:mm:ss z", mLocale)
tvDate.text = getString(R.string.template__date, dateFormat.format(transferDetail.date * 1000)) binding.tvDate.text =
getString(R.string.template__date, dateFormat.format(transferDetail.date * 1000))
// Transaction # // Transaction #
formatTransferTextView(transferDetail.id) formatTransferTextView(transferDetail.id)
@ -118,11 +133,14 @@ class EReceiptFragment : Fragment() {
/** Formats the transfer TextView to show a link to explore the given transfer /** Formats the transfer TextView to show a link to explore the given transfer
* in a BitShares explorer */ * in a BitShares explorer */
private fun formatTransferTextView(transferId: String) { private fun formatTransferTextView(transferId: String) {
val tx = Html.fromHtml(getString(R.string.template__tx, val tx = Html.fromHtml(
getString(
R.string.template__tx,
"<a href=\"http://bitshares-explorer.io/#/operations/$transferId\">$transferId</a>" "<a href=\"http://bitshares-explorer.io/#/operations/$transferId\">$transferId</a>"
)) )
tvTransferID.text = tx )
tvTransferID.movementMethod = LinkMovementMethod.getInstance() binding.tvTransferID.text = tx
binding.tvTransferID.movementMethod = LinkMovementMethod.getInstance()
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
@ -146,18 +164,26 @@ class EReceiptFragment : Fragment() {
/** Verifies if the storage permission is already granted, if that is the case then it takes the screenshot and /** Verifies if the storage permission is already granted, if that is the case then it takes the screenshot and
* shares it but if it is not then it asks the user for that permission */ * shares it but if it is not then it asks the user for that permission */
private fun verifyStoragePermission() { private fun verifyStoragePermission() {
if (ContextCompat.checkSelfPermission(activity!!, Manifest.permission.WRITE_EXTERNAL_STORAGE) if (ContextCompat
!= PackageManager.PERMISSION_GRANTED) { .checkSelfPermission(activity!!, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED
) {
// Permission is not already granted // Permission is not already granted
requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), requestPermissions(
REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION) arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION
)
} else { } else {
// Permission is already granted // Permission is already granted
shareEReceiptScreenshot() shareEReceiptScreenshot()
} }
} }
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults) super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION) { if (requestCode == REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION) {
@ -174,8 +200,8 @@ class EReceiptFragment : Fragment() {
* sends an intent so the user can select the desired method to share the image. */ * sends an intent so the user can select the desired method to share the image. */
private fun shareEReceiptScreenshot() { private fun shareEReceiptScreenshot() {
// Get Screenshot // Get Screenshot
tvTransferID.text = getString(R.string.template__tx, args.transferId) binding.tvTransferID.text = getString(R.string.template__tx, args.transferId)
val screenshot = Helper.loadBitmapFromView(container) val screenshot = Helper.loadBitmapFromView(binding.container)
formatTransferTextView(args.transferId) formatTransferTextView(args.transferId)
val imageUri = context?.let { Helper.saveTemporalBitmap(it, screenshot) } val imageUri = context?.let { Helper.saveTemporalBitmap(it, screenshot) }

View file

@ -6,25 +6,24 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.DialogFragment import android.widget.TextView
import android.widget.*
import androidx.core.os.ConfigurationCompat import androidx.core.os.ConfigurationCompat
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProviders
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import com.google.firebase.crashlytics.FirebaseCrashlytics import com.google.firebase.crashlytics.FirebaseCrashlytics
import cy.agorise.bitsybitshareswallet.R import cy.agorise.bitsybitshareswallet.R
import cy.agorise.bitsybitshareswallet.adapters.BalancesDetailsAdapter import cy.agorise.bitsybitshareswallet.adapters.BalancesDetailsAdapter
import cy.agorise.bitsybitshareswallet.database.joins.BalanceDetail import cy.agorise.bitsybitshareswallet.database.joins.BalanceDetail
import cy.agorise.bitsybitshareswallet.databinding.DialogFilterOptionsBinding
import cy.agorise.bitsybitshareswallet.models.FilterOptions import cy.agorise.bitsybitshareswallet.models.FilterOptions
import cy.agorise.bitsybitshareswallet.utils.Constants import cy.agorise.bitsybitshareswallet.utils.Constants
import cy.agorise.bitsybitshareswallet.utils.Helper import cy.agorise.bitsybitshareswallet.utils.Helper
import cy.agorise.bitsybitshareswallet.viewmodels.BalanceDetailViewModel import cy.agorise.bitsybitshareswallet.viewmodels.BalanceDetailViewModel
import cy.agorise.bitsybitshareswallet.views.DatePickerFragment import cy.agorise.bitsybitshareswallet.views.DatePickerFragment
import kotlinx.android.synthetic.main.dialog_filter_options.*
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
import kotlin.ClassCastException
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
@ -43,12 +42,17 @@ class FilterOptionsDialog : DialogFragment(), DatePickerFragment.OnDateSetListen
const val END_DATE_PICKER = 1 const val END_DATE_PICKER = 1
} }
private var _binding: DialogFilterOptionsBinding? = null
private val binding get() = _binding!!
private lateinit var mFilterOptions: FilterOptions private lateinit var mFilterOptions: FilterOptions
private var mCallback: OnFilterOptionsSelectedListener? = null private var mCallback: OnFilterOptionsSelectedListener? = null
private var dateFormat: SimpleDateFormat = SimpleDateFormat("d/MMM/yyyy", private var dateFormat: SimpleDateFormat = SimpleDateFormat(
ConfigurationCompat.getLocales(Resources.getSystem().configuration)[0]) "d/MMM/yyyy",
ConfigurationCompat.getLocales(Resources.getSystem().configuration)[0]
)
private var mBalanceDetails = ArrayList<BalanceDetail>() private var mBalanceDetails = ArrayList<BalanceDetail>()
@ -59,7 +63,7 @@ class FilterOptionsDialog : DialogFragment(), DatePickerFragment.OnDateSetListen
private lateinit var mCurrency: Currency private lateinit var mCurrency: Currency
override fun onDateSet(which: Int, timestamp: Long) { override fun onDateSet(which: Int, timestamp: Long) {
when(which) { when (which) {
START_DATE_PICKER -> { START_DATE_PICKER -> {
mFilterOptions.startDate = timestamp mFilterOptions.startDate = timestamp
@ -85,10 +89,10 @@ class FilterOptionsDialog : DialogFragment(), DatePickerFragment.OnDateSetListen
private fun updateDateTextViews() { private fun updateDateTextViews() {
var date = Date(mFilterOptions.startDate) var date = Date(mFilterOptions.startDate)
tvStartDate.text = dateFormat.format(date) binding.tvStartDate.text = dateFormat.format(date)
date = Date(mFilterOptions.endDate) date = Date(mFilterOptions.endDate)
tvEndDate.text = dateFormat.format(date) binding.tvEndDate.text = dateFormat.format(date)
} }
// Container Fragment must implement this interface // Container Fragment must implement this interface
@ -96,8 +100,18 @@ class FilterOptionsDialog : DialogFragment(), DatePickerFragment.OnDateSetListen
fun onFilterOptionsSelected(filterOptions: FilterOptions) fun onFilterOptionsSelected(filterOptions: FilterOptions)
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(
return inflater.inflate(R.layout.dialog_filter_options, container, false) inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = DialogFilterOptionsBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -112,73 +126,81 @@ class FilterOptionsDialog : DialogFragment(), DatePickerFragment.OnDateSetListen
// Initialize Transactions direction // Initialize Transactions direction
when (mFilterOptions.transactionsDirection) { when (mFilterOptions.transactionsDirection) {
0 -> rbTransactionAll.isChecked = true 0 -> binding.rbTransactionAll.isChecked = true
1 -> rbTransactionSent.isChecked = true 1 -> binding.rbTransactionSent.isChecked = true
2 -> rbTransactionReceived.isChecked = true 2 -> binding.rbTransactionReceived.isChecked = true
} }
// Initialize Date range // Initialize Date range
cbDateRange.setOnCheckedChangeListener { _, isChecked -> binding.cbDateRange.setOnCheckedChangeListener { _, isChecked ->
llDateRange.visibility = if(isChecked) View.GONE else View.VISIBLE } binding.llDateRange.visibility = if (isChecked) View.GONE else View.VISIBLE
cbDateRange.isChecked = mFilterOptions.dateRangeAll }
binding.cbDateRange.isChecked = mFilterOptions.dateRangeAll
tvStartDate.setOnClickListener(mDateClickListener) binding.tvStartDate.setOnClickListener(mDateClickListener)
tvEndDate.setOnClickListener(mDateClickListener) binding.tvEndDate.setOnClickListener(mDateClickListener)
updateDateTextViews() updateDateTextViews()
// Initialize Asset // Initialize Asset
cbAsset.setOnCheckedChangeListener { _, isChecked -> binding.cbAsset.setOnCheckedChangeListener { _, isChecked ->
sAsset.visibility = if(isChecked) View.GONE else View.VISIBLE binding.sAsset.visibility = if (isChecked) View.GONE else View.VISIBLE
} }
cbAsset.isChecked = mFilterOptions.assetAll binding.cbAsset.isChecked = mFilterOptions.assetAll
// Configure BalanceDetailViewModel to obtain the user's Balances // Configure BalanceDetailViewModel to obtain the user's Balances
mBalanceDetailViewModel = ViewModelProviders.of(this).get(BalanceDetailViewModel::class.java) mBalanceDetailViewModel =
ViewModelProviders.of(this).get(BalanceDetailViewModel::class.java)
mBalanceDetailViewModel.getAll().observe(this, Observer<List<BalanceDetail>> { balancesDetails -> mBalanceDetailViewModel.getAll()
.observe(this, Observer<List<BalanceDetail>> { balancesDetails ->
mBalanceDetails.clear() mBalanceDetails.clear()
mBalanceDetails.addAll(balancesDetails) mBalanceDetails.addAll(balancesDetails)
mBalanceDetails.sortWith( mBalanceDetails.sortWith(
Comparator { a, b -> a.toString().compareTo(b.toString(), true) } Comparator { a, b -> a.toString().compareTo(b.toString(), true) }
) )
mBalancesDetailsAdapter = BalancesDetailsAdapter(context!!, android.R.layout.simple_spinner_item, mBalanceDetails) mBalancesDetailsAdapter = BalancesDetailsAdapter(
sAsset.adapter = mBalancesDetailsAdapter context!!,
android.R.layout.simple_spinner_item,
mBalanceDetails
)
binding.sAsset.adapter = mBalancesDetailsAdapter
// Try to select the selectedAssetSymbol // Try to select the selectedAssetSymbol
for (i in 0 until mBalancesDetailsAdapter!!.count) { for (i in 0 until mBalancesDetailsAdapter!!.count) {
if (mBalancesDetailsAdapter!!.getItem(i)!!.symbol == mFilterOptions.asset) { if (mBalancesDetailsAdapter!!.getItem(i)!!.symbol == mFilterOptions.asset) {
sAsset.setSelection(i) binding.sAsset.setSelection(i)
break break
} }
} }
}) })
// Initialize Equivalent Value // Initialize Equivalent Value
cbEquivalentValue.setOnCheckedChangeListener { _, isChecked -> binding.cbEquivalentValue.setOnCheckedChangeListener { _, isChecked ->
llEquivalentValue.visibility = if(isChecked) View.GONE else View.VISIBLE } binding.llEquivalentValue.visibility = if (isChecked) View.GONE else View.VISIBLE
cbEquivalentValue.isChecked = mFilterOptions.equivalentValueAll }
binding.cbEquivalentValue.isChecked = mFilterOptions.equivalentValueAll
val currencyCode = Helper.getCoingeckoSupportedCurrency(Locale.getDefault()) val currencyCode = Helper.getCoingeckoSupportedCurrency(Locale.getDefault())
mCurrency = Currency.getInstance(currencyCode) mCurrency = Currency.getInstance(currencyCode)
val fromEquivalentValue = mFilterOptions.fromEquivalentValue / val fromEquivalentValue = mFilterOptions.fromEquivalentValue /
Math.pow(10.0, mCurrency.defaultFractionDigits.toDouble()).toLong() Math.pow(10.0, mCurrency.defaultFractionDigits.toDouble()).toLong()
etFromEquivalentValue.setText("$fromEquivalentValue", TextView.BufferType.EDITABLE) binding.etFromEquivalentValue.setText("$fromEquivalentValue", TextView.BufferType.EDITABLE)
val toEquivalentValue = mFilterOptions.toEquivalentValue / val toEquivalentValue = mFilterOptions.toEquivalentValue /
Math.pow(10.0, mCurrency.defaultFractionDigits.toDouble()).toLong() Math.pow(10.0, mCurrency.defaultFractionDigits.toDouble()).toLong()
etToEquivalentValue.setText("$toEquivalentValue", TextView.BufferType.EDITABLE) binding.etToEquivalentValue.setText("$toEquivalentValue", TextView.BufferType.EDITABLE)
tvEquivalentValueSymbol.text = currencyCode.toUpperCase(Locale.getDefault()) binding.tvEquivalentValueSymbol.text = currencyCode.toUpperCase(Locale.getDefault())
// Initialize transaction network fees // Initialize transaction network fees
switchAgoriseFees.isChecked = mFilterOptions.agoriseFees binding.switchAgoriseFees.isChecked = mFilterOptions.agoriseFees
// Setup cancel and filter buttons // Setup cancel and filter buttons
btnCancel.setOnClickListener { dismiss() } binding.btnCancel.setOnClickListener { dismiss() }
btnFilter.setOnClickListener { validateFields() } binding.btnFilter.setOnClickListener { validateFields() }
} }
override fun onResume() { override fun onResume() {
@ -187,7 +209,10 @@ class FilterOptionsDialog : DialogFragment(), DatePickerFragment.OnDateSetListen
// Force dialog fragment to use the full width of the screen // Force dialog fragment to use the full width of the screen
// TODO use the same width as standard fragments // TODO use the same width as standard fragments
val dialogWindow = dialog?.window val dialogWindow = dialog?.window
dialogWindow?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) dialogWindow?.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
} }
/** /**
@ -227,17 +252,19 @@ class FilterOptionsDialog : DialogFragment(), DatePickerFragment.OnDateSetListen
private fun validateFields() { private fun validateFields() {
mFilterOptions.transactionsDirection = when { mFilterOptions.transactionsDirection = when {
rbTransactionAll.isChecked -> 0 binding.rbTransactionAll.isChecked -> 0
rbTransactionSent.isChecked -> 1 binding.rbTransactionSent.isChecked -> 1
rbTransactionReceived.isChecked -> 2 binding.rbTransactionReceived.isChecked -> 2
else -> { 0 } else -> {
0
}
} }
mFilterOptions.dateRangeAll = cbDateRange.isChecked mFilterOptions.dateRangeAll = binding.cbDateRange.isChecked
mFilterOptions.assetAll = cbAsset.isChecked mFilterOptions.assetAll = binding.cbAsset.isChecked
val symbol = (sAsset.selectedItem as BalanceDetail?)?.symbol val symbol = (binding.sAsset.selectedItem as BalanceDetail?)?.symbol
// If there are no assets in the spinner (the account has 0 balances or the app has not yet // If there are no assets in the spinner (the account has 0 balances or the app has not yet
// fetched the account balances) symbol will be null, make sure that does not create a crash. // fetched the account balances) symbol will be null, make sure that does not create a crash.
if (symbol != null) if (symbol != null)
@ -245,19 +272,20 @@ class FilterOptionsDialog : DialogFragment(), DatePickerFragment.OnDateSetListen
else else
mFilterOptions.assetAll = true mFilterOptions.assetAll = true
mFilterOptions.equivalentValueAll = cbEquivalentValue.isChecked mFilterOptions.equivalentValueAll = binding.cbEquivalentValue.isChecked
mFilterOptions.fromEquivalentValue = etFromEquivalentValue.text.toString().toLong() * mFilterOptions.fromEquivalentValue =
binding.etFromEquivalentValue.text.toString().toLong() *
Math.pow(10.0, mCurrency.defaultFractionDigits.toDouble()).toLong() Math.pow(10.0, mCurrency.defaultFractionDigits.toDouble()).toLong()
mFilterOptions.toEquivalentValue = etToEquivalentValue.text.toString().toLong() * mFilterOptions.toEquivalentValue = binding.etToEquivalentValue.text.toString().toLong() *
Math.pow(10.0, mCurrency.defaultFractionDigits.toDouble()).toLong() Math.pow(10.0, mCurrency.defaultFractionDigits.toDouble()).toLong()
// Make sure ToEquivalentValue is at least 50 units bigger than FromEquivalentValue // Make sure ToEquivalentValue is at least 50 units bigger than FromEquivalentValue
mFilterOptions.toEquivalentValue = mFilterOptions.toEquivalentValue =
Math.max(mFilterOptions.toEquivalentValue, mFilterOptions.fromEquivalentValue + 50) Math.max(mFilterOptions.toEquivalentValue, mFilterOptions.fromEquivalentValue + 50)
mFilterOptions.agoriseFees = switchAgoriseFees.isChecked mFilterOptions.agoriseFees = binding.switchAgoriseFees.isChecked
mCallback!!.onFilterOptionsSelected(mFilterOptions) mCallback!!.onFilterOptionsSelected(mFilterOptions)
dismiss() dismiss()

View file

@ -1,36 +1,42 @@
package cy.agorise.bitsybitshareswallet.fragments package cy.agorise.bitsybitshareswallet.fragments
import androidx.lifecycle.ViewModelProviders
import android.os.Bundle import android.os.Bundle
import android.preference.PreferenceManager import android.preference.PreferenceManager
import android.view.* import android.view.*
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentPagerAdapter import androidx.fragment.app.FragmentPagerAdapter
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.navigation.Navigation import androidx.navigation.Navigation
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.google.firebase.crashlytics.FirebaseCrashlytics
import cy.agorise.bitsybitshareswallet.R import cy.agorise.bitsybitshareswallet.R
import cy.agorise.bitsybitshareswallet.database.entities.UserAccount import cy.agorise.bitsybitshareswallet.database.entities.UserAccount
import cy.agorise.bitsybitshareswallet.databinding.FragmentHomeBinding
import cy.agorise.bitsybitshareswallet.utils.Constants import cy.agorise.bitsybitshareswallet.utils.Constants
import cy.agorise.bitsybitshareswallet.viewmodels.UserAccountViewModel import cy.agorise.bitsybitshareswallet.viewmodels.UserAccountViewModel
import kotlinx.android.synthetic.main.fragment_home.*
import androidx.appcompat.app.AppCompatActivity
import com.google.firebase.crashlytics.FirebaseCrashlytics
class HomeFragment : Fragment() { class HomeFragment : Fragment() {
companion object { companion object {
private const val TAG ="HomeFragment" private const val TAG = "HomeFragment"
} }
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
private lateinit var mUserAccountViewModel: UserAccountViewModel private lateinit var mUserAccountViewModel: UserAccountViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
setHasOptionsMenu(true) setHasOptionsMenu(true)
val nightMode = PreferenceManager.getDefaultSharedPreferences(context) val nightMode = PreferenceManager.getDefaultSharedPreferences(context)
@ -52,13 +58,21 @@ class HomeFragment : Fragment() {
window?.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) window?.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
// Sets the status and navigation bars background color to a dark blue or just dark // Sets the status and navigation bars background color to a dark blue or just dark
context?.let { context -> context?.let { context ->
val statusBarColor = ContextCompat.getColor(context, val statusBarColor = ContextCompat.getColor(
if (!nightMode) R.color.colorPrimaryVariant else R.color.colorStatusBarDark) context,
if (!nightMode) R.color.colorPrimaryVariant else R.color.colorStatusBarDark
)
window?.statusBarColor = statusBarColor window?.statusBarColor = statusBarColor
window?.navigationBarColor = statusBarColor window?.navigationBarColor = statusBarColor
} }
return inflater.inflate(R.layout.fragment_home, container, false) _binding = FragmentHomeBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -82,48 +96,49 @@ class HomeFragment : Fragment() {
// Configure UserAccountViewModel to show the current account // Configure UserAccountViewModel to show the current account
mUserAccountViewModel = ViewModelProviders.of(this).get(UserAccountViewModel::class.java) mUserAccountViewModel = ViewModelProviders.of(this).get(UserAccountViewModel::class.java)
mUserAccountViewModel.getUserAccount(userId).observe(this, Observer<UserAccount>{ userAccount -> mUserAccountViewModel.getUserAccount(userId)
.observe(this, Observer<UserAccount> { userAccount ->
if (userAccount != null) { if (userAccount != null) {
tvAccountName.text = userAccount.name binding.tvAccountName.text = userAccount.name
if (userAccount.isLtm) { if (userAccount.isLtm) {
// Add the lightning bolt to the start of the account name if it is LTM // Add the lightning bolt to the start of the account name if it is LTM
tvAccountName.setCompoundDrawablesWithIntrinsicBounds( binding.tvAccountName.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.ic_ltm_account, 0, 0, 0 R.drawable.ic_ltm_account, 0, 0, 0
) )
// Add some padding so that the lightning bolt icon is not too close to the account name text // Add some padding so that the lightning bolt icon is not too close to the account name text
tvAccountName.compoundDrawablePadding = 12 binding.tvAccountName.compoundDrawablePadding = 12
} }
} }
}) })
// Navigate to the Receive Transaction Fragment // Navigate to the Receive Transaction Fragment
fabReceiveTransaction.setOnClickListener ( binding.fabReceiveTransaction.setOnClickListener(
Navigation.createNavigateOnClickListener(R.id.receive_action) Navigation.createNavigateOnClickListener(R.id.receive_action)
) )
// Navigate to the Send Transaction Fragment without activating the camera // Navigate to the Send Transaction Fragment without activating the camera
fabSendTransaction.setOnClickListener( binding.fabSendTransaction.setOnClickListener(
Navigation.createNavigateOnClickListener(R.id.send_action) Navigation.createNavigateOnClickListener(R.id.send_action)
) )
// Navigate to the Send Transaction Fragment using Navigation's SafeArgs to activate the camera // Navigate to the Send Transaction Fragment using Navigation's SafeArgs to activate the camera
fabSendTransactionCamera.setOnClickListener { binding.fabSendTransactionCamera.setOnClickListener {
val action = HomeFragmentDirections.sendAction(true) val action = HomeFragmentDirections.sendAction(true)
findNavController().navigate(action) findNavController().navigate(action)
} }
// Configure ViewPager with PagerAdapter and TabLayout to display the Balances/NetWorth section // Configure ViewPager with PagerAdapter and TabLayout to display the Balances/NetWorth section
val pagerAdapter = PagerAdapter(childFragmentManager) val pagerAdapter = PagerAdapter(childFragmentManager)
viewPager.adapter = pagerAdapter binding.viewPager.adapter = pagerAdapter
tabLayout.setupWithViewPager(viewPager) binding.tabLayout.setupWithViewPager(binding.viewPager)
// Set the pie chart icon for the third tab // Set the pie chart icon for the third tab
tabLayout.getTabAt(2)?.setIcon(R.drawable.ic_pie_chart) binding.tabLayout.getTabAt(2)?.setIcon(R.drawable.ic_pie_chart)
} }
/** /**
* Pager adapter to create the placeholder fragments * Pager adapter to create the placeholder fragments
*/ */
private inner class PagerAdapter internal constructor(fm: FragmentManager) : FragmentPagerAdapter(fm) { private inner class PagerAdapter(fm: FragmentManager) : FragmentPagerAdapter(fm) {
override fun getItem(position: Int): Fragment { override fun getItem(position: Int): Fragment {
// getItem is called to instantiate the fragment for the given page. // getItem is called to instantiate the fragment for the given page.
@ -133,8 +148,12 @@ class HomeFragment : Fragment() {
NetWorthFragment() NetWorthFragment()
} }
override fun getPageTitle(position: Int): CharSequence? { override fun getPageTitle(position: Int): CharSequence {
return listOf(getString(R.string.title_balances), getString(R.string.title_net_worth), "")[position] return listOf(
getString(R.string.title_balances),
getString(R.string.title_net_worth),
""
)[position]
} }
override fun getCount(): Int { override fun getCount(): Int {

View file

@ -7,6 +7,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.core.content.res.ResourcesCompat
import androidx.navigation.Navigation import androidx.navigation.Navigation
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
@ -19,9 +20,12 @@ import com.jakewharton.rxbinding3.widget.textChanges
import cy.agorise.bitsybitshareswallet.BuildConfig import cy.agorise.bitsybitshareswallet.BuildConfig
import cy.agorise.bitsybitshareswallet.R import cy.agorise.bitsybitshareswallet.R
import cy.agorise.bitsybitshareswallet.adapters.FullNodesAdapter import cy.agorise.bitsybitshareswallet.adapters.FullNodesAdapter
import cy.agorise.bitsybitshareswallet.databinding.FragmentImportBrainkeyBinding
import cy.agorise.bitsybitshareswallet.utils.Constants import cy.agorise.bitsybitshareswallet.utils.Constants
import cy.agorise.bitsybitshareswallet.utils.toast import cy.agorise.bitsybitshareswallet.utils.toast
import cy.agorise.graphenej.* import cy.agorise.graphenej.Address
import cy.agorise.graphenej.BrainKey
import cy.agorise.graphenej.UserAccount
import cy.agorise.graphenej.api.ConnectionStatusUpdate import cy.agorise.graphenej.api.ConnectionStatusUpdate
import cy.agorise.graphenej.api.calls.GetAccounts import cy.agorise.graphenej.api.calls.GetAccounts
import cy.agorise.graphenej.api.calls.GetDynamicGlobalProperties import cy.agorise.graphenej.api.calls.GetDynamicGlobalProperties
@ -30,7 +34,6 @@ import cy.agorise.graphenej.models.AccountProperties
import cy.agorise.graphenej.models.DynamicGlobalProperties import cy.agorise.graphenej.models.DynamicGlobalProperties
import cy.agorise.graphenej.models.JsonRpcResponse import cy.agorise.graphenej.models.JsonRpcResponse
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.synthetic.main.fragment_import_brainkey.*
import org.bitcoinj.core.ECKey import org.bitcoinj.core.ECKey
import java.text.NumberFormat import java.text.NumberFormat
import java.util.* import java.util.*
@ -42,6 +45,9 @@ class ImportBrainkeyFragment : BaseAccountFragment() {
private const val TAG = "ImportBrainkeyFragment" private const val TAG = "ImportBrainkeyFragment"
} }
private var _binding: FragmentImportBrainkeyBinding? = null
private val binding get() = _binding!!
/** User account associated with the key derived from the brainkey that the user just typed in */ /** User account associated with the key derived from the brainkey that the user just typed in */
private var mUserAccount: UserAccount? = null private var mUserAccount: UserAccount? = null
@ -72,12 +78,22 @@ class ImportBrainkeyFragment : BaseAccountFragment() {
/** Handler that will be used to make recurrent calls to get the latest BitShares block number*/ /** Handler that will be used to make recurrent calls to get the latest BitShares block number*/
private val mHandler = Handler() private val mHandler = Handler()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Remove up navigation icon from the toolbar // Remove up navigation icon from the toolbar
val toolbar: Toolbar? = activity?.findViewById(R.id.toolbar) val toolbar: Toolbar? = activity?.findViewById(R.id.toolbar)
toolbar?.navigationIcon = null toolbar?.navigationIcon = null
return inflater.inflate(R.layout.fragment_import_brainkey, container, false) _binding = FragmentImportBrainkeyBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -88,7 +104,7 @@ class ImportBrainkeyFragment : BaseAccountFragment() {
// Use RxJava Debounce to update the PIN error only after the user stops writing for > 500 ms // Use RxJava Debounce to update the PIN error only after the user stops writing for > 500 ms
mDisposables.add( mDisposables.add(
tietPin.textChanges() binding.tietPin.textChanges()
.skipInitialValue() .skipInitialValue()
.debounce(500, TimeUnit.MILLISECONDS) .debounce(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
@ -100,7 +116,7 @@ class ImportBrainkeyFragment : BaseAccountFragment() {
// Use RxJava Debounce to update the PIN Confirmation error only after the user stops writing for > 500 ms // Use RxJava Debounce to update the PIN Confirmation error only after the user stops writing for > 500 ms
mDisposables.add( mDisposables.add(
tietPinConfirmation.textChanges() binding.tietPinConfirmation.textChanges()
.skipInitialValue() .skipInitialValue()
.debounce(500, TimeUnit.MILLISECONDS) .debounce(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
@ -112,7 +128,7 @@ class ImportBrainkeyFragment : BaseAccountFragment() {
// Use RxJava Debounce to update the BrainKey error only after the user stops writing for > 500 ms // Use RxJava Debounce to update the BrainKey error only after the user stops writing for > 500 ms
mDisposables.add( mDisposables.add(
tietBrainKey.textChanges() binding.tietBrainKey.textChanges()
.skipInitialValue() .skipInitialValue()
.debounce(500, TimeUnit.MILLISECONDS) .debounce(500, TimeUnit.MILLISECONDS)
.map { it.toString().trim() } .map { it.toString().trim() }
@ -123,14 +139,14 @@ class ImportBrainkeyFragment : BaseAccountFragment() {
) )
) )
btnImport.isEnabled = false binding.btnImport.isEnabled = false
btnImport.setOnClickListener { verifyBrainKey(false) } binding.btnImport.setOnClickListener { verifyBrainKey(false) }
btnCreate.setOnClickListener ( binding.btnCreate.setOnClickListener(
Navigation.createNavigateOnClickListener(R.id.create_account_action) Navigation.createNavigateOnClickListener(R.id.create_account_action)
) )
tvNetworkStatus.setOnClickListener { v -> showNodesDialog(v) } binding.tvNetworkStatus.setOnClickListener { v -> showNodesDialog(v) }
} }
private fun showNodesDialog(v: View) { private fun showNodesDialog(v: View) {
@ -160,7 +176,13 @@ class ImportBrainkeyFragment : BaseAccountFragment() {
) )
mNodesDialog = MaterialDialog(v.context).show { mNodesDialog = MaterialDialog(v.context).show {
title(text = String.format("%s v%s", getString(R.string.app_name), BuildConfig.VERSION_NAME)) title(
text = String.format(
"%s v%s",
getString(R.string.app_name),
BuildConfig.VERSION_NAME
)
)
message(text = getString(R.string.title__bitshares_nodes_dialog, "-------")) message(text = getString(R.string.title__bitshares_nodes_dialog, "-------"))
customListAdapter(nodesAdapter as FullNodesAdapter) customListAdapter(nodesAdapter as FullNodesAdapter)
negativeButton(android.R.string.ok) negativeButton(android.R.string.ok)
@ -170,7 +192,8 @@ class ImportBrainkeyFragment : BaseAccountFragment() {
} }
} }
mNodesDialogLinearLayoutManager = (mNodesDialog?.getRecyclerView()?.layoutManager as LinearLayoutManager) mNodesDialogLinearLayoutManager =
(mNodesDialog?.getRecyclerView()?.layoutManager as LinearLayoutManager)
// Registering a recurrent task used to poll for dynamic global properties requests // Registering a recurrent task used to poll for dynamic global properties requests
mHandler.post(mRequestDynamicGlobalPropertiesTask) mHandler.post(mRequestDynamicGlobalPropertiesTask)
@ -178,13 +201,13 @@ class ImportBrainkeyFragment : BaseAccountFragment() {
} }
private fun validatePIN() { private fun validatePIN() {
val pin = tietPin.text.toString() val pin = binding.tietPin.text.toString()
if (pin.length < Constants.MIN_PIN_LENGTH) { if (pin.length < Constants.MIN_PIN_LENGTH) {
tilPin.error = getString(R.string.error__pin_too_short) binding.tilPin.error = getString(R.string.error__pin_too_short)
isPINValid = false isPINValid = false
} else { } else {
tilPin.isErrorEnabled = false binding.tilPin.isErrorEnabled = false
isPINValid = true isPINValid = true
} }
@ -192,13 +215,13 @@ class ImportBrainkeyFragment : BaseAccountFragment() {
} }
private fun validatePINConfirmation() { private fun validatePINConfirmation() {
val pinConfirmation = tietPinConfirmation.text.toString() val pinConfirmation = binding.tietPinConfirmation.text.toString()
if (pinConfirmation != tietPin.text.toString()) { if (pinConfirmation != binding.tietPin.text.toString()) {
tilPinConfirmation.error = getString(R.string.error__pin_mismatch) binding.tilPinConfirmation.error = getString(R.string.error__pin_mismatch)
isPINConfirmationValid = false isPINConfirmationValid = false
} else { } else {
tilPinConfirmation.isErrorEnabled = false binding.tilPinConfirmation.isErrorEnabled = false
isPINConfirmationValid = true isPINConfirmationValid = true
} }
@ -207,10 +230,10 @@ class ImportBrainkeyFragment : BaseAccountFragment() {
private fun validateBrainKey(brainKey: String) { private fun validateBrainKey(brainKey: String) {
if (brainKey.isEmpty() || !brainKey.contains(" ") || brainKey.split(" ").size !in 12..16) { if (brainKey.isEmpty() || !brainKey.contains(" ") || brainKey.split(" ").size !in 12..16) {
tilBrainKey.error = getString(R.string.error__enter_correct_brainkey) binding.tilBrainKey.error = getString(R.string.error__enter_correct_brainkey)
isBrainKeyValid = false isBrainKeyValid = false
} else { } else {
tilBrainKey.isErrorEnabled = false binding.tilBrainKey.isErrorEnabled = false
isBrainKeyValid = true isBrainKeyValid = true
} }
@ -218,7 +241,7 @@ class ImportBrainkeyFragment : BaseAccountFragment() {
} }
private fun enableDisableImportButton() { private fun enableDisableImportButton() {
btnImport.isEnabled = (isPINValid && isPINConfirmationValid && isBrainKeyValid) binding.btnImport.isEnabled = (isPINValid && isPINConfirmationValid && isBrainKeyValid)
} }
/** /**
@ -242,7 +265,7 @@ class ImportBrainkeyFragment : BaseAccountFragment() {
*/ */
private fun verifyBrainKey(switchCase: Boolean) { private fun verifyBrainKey(switchCase: Boolean) {
//showDialog("", getString(R.string.importing_your_wallet)) //showDialog("", getString(R.string.importing_your_wallet))
val brainKey = tietBrainKey.text.toString().trim() val brainKey = binding.tietBrainKey.text.toString().trim()
// Should we switch the brainkey case? // Should we switch the brainkey case?
if (switchCase) { if (switchCase) {
if (Character.isUpperCase(brainKey.toCharArray()[brainKey.length - 1])) { if (Character.isUpperCase(brainKey.toCharArray()[brainKey.length - 1])) {
@ -270,7 +293,8 @@ class ImportBrainkeyFragment : BaseAccountFragment() {
mBrainKey = BrainKey(brainKey, 0) mBrainKey = BrainKey(brainKey, 0)
val address = Address(ECKey.fromPublicOnly(mBrainKey!!.privateKey.pubKey)) val address = Address(ECKey.fromPublicOnly(mBrainKey!!.privateKey.pubKey))
Log.d(TAG, String.format("Brainkey would generate address: %s", address.toString())) Log.d(TAG, String.format("Brainkey would generate address: %s", address.toString()))
keyReferencesRequestId = mNetworkService?.sendMessage(GetKeyReferences(address), GetKeyReferences.REQUIRED_API) keyReferencesRequestId =
mNetworkService?.sendMessage(GetKeyReferences(address), GetKeyReferences.REQUIRED_API)
} }
override fun onStart() { override fun onStart() {
@ -290,8 +314,14 @@ class ImportBrainkeyFragment : BaseAccountFragment() {
} else if (response.result is DynamicGlobalProperties) { } else if (response.result is DynamicGlobalProperties) {
val dynamicGlobalProperties = response.result as DynamicGlobalProperties val dynamicGlobalProperties = response.result as DynamicGlobalProperties
if (mNodesDialog != null && mNodesDialog?.isShowing == true) { if (mNodesDialog != null && mNodesDialog?.isShowing == true) {
val blockNumber = NumberFormat.getInstance().format(dynamicGlobalProperties.head_block_number) val blockNumber =
mNodesDialog?.message(text = getString(R.string.title__bitshares_nodes_dialog, blockNumber)) NumberFormat.getInstance().format(dynamicGlobalProperties.head_block_number)
mNodesDialog?.message(
text = getString(
R.string.title__bitshares_nodes_dialog,
blockNumber
)
)
} }
} }
} }
@ -308,13 +338,17 @@ class ImportBrainkeyFragment : BaseAccountFragment() {
} }
private fun showConnectedState() { private fun showConnectedState() {
tvNetworkStatus.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, binding.tvNetworkStatus.setCompoundDrawablesRelativeWithIntrinsicBounds(
resources.getDrawable(R.drawable.ic_connected, null), null) null, null,
ResourcesCompat.getDrawable(resources, R.drawable.ic_connected, null), null
)
} }
private fun showDisconnectedState() { private fun showDisconnectedState() {
tvNetworkStatus.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, binding.tvNetworkStatus.setCompoundDrawablesRelativeWithIntrinsicBounds(
resources.getDrawable(R.drawable.ic_disconnected, null), null) null, null,
ResourcesCompat.getDrawable(resources, R.drawable.ic_disconnected, null), null
)
} }
/** /**
@ -371,12 +405,18 @@ class ImportBrainkeyFragment : BaseAccountFragment() {
MaterialDialog(context!!) MaterialDialog(context!!)
.title(R.string.dialog__account_candidates_title) .title(R.string.dialog__account_candidates_title)
.message(R.string.dialog__account_candidates_content) .message(R.string.dialog__account_candidates_content)
.listItemsSingleChoice (items = candidates, initialSelection = -1) { _, index, _ -> .listItemsSingleChoice(
items = candidates,
initialSelection = -1
) { _, index, _ ->
if (index >= 0) { if (index >= 0) {
// If one account was selected, we keep a reference to it and // If one account was selected, we keep a reference to it and
// store the account properties // store the account properties
mUserAccount = mUserAccountCandidates!![index] mUserAccount = mUserAccountCandidates!![index]
onAccountSelected(accountPropertiesList[index], tietPin.text.toString()) onAccountSelected(
accountPropertiesList[index],
binding.tietPin.text.toString()
)
} }
} }
.positiveButton(android.R.string.ok) .positiveButton(android.R.string.ok)
@ -386,7 +426,7 @@ class ImportBrainkeyFragment : BaseAccountFragment() {
.cancelable(false) .cancelable(false)
.show() .show()
} else if (accountPropertiesList.size == 1) { } else if (accountPropertiesList.size == 1) {
onAccountSelected(accountPropertiesList[0], tietPin.text.toString()) onAccountSelected(accountPropertiesList[0], binding.tietPin.text.toString())
} else { } else {
context?.toast(getString(R.string.error__try_again)) context?.toast(getString(R.string.error__try_again))
} }
@ -400,7 +440,10 @@ class ImportBrainkeyFragment : BaseAccountFragment() {
override fun run() { override fun run() {
if (mNetworkService != null) { if (mNetworkService != null) {
if (mNetworkService?.isConnected == true) { if (mNetworkService?.isConnected == true) {
mNetworkService?.sendMessage(GetDynamicGlobalProperties(), GetDynamicGlobalProperties.REQUIRED_API) mNetworkService?.sendMessage(
GetDynamicGlobalProperties(),
GetDynamicGlobalProperties.REQUIRED_API
)
} else { } else {
Log.d(TAG, "NetworkService exists but is not connected") Log.d(TAG, "NetworkService exists but is not connected")
} }

View file

@ -10,8 +10,8 @@ import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.google.firebase.crashlytics.FirebaseCrashlytics import com.google.firebase.crashlytics.FirebaseCrashlytics
import cy.agorise.bitsybitshareswallet.R import cy.agorise.bitsybitshareswallet.R
import cy.agorise.bitsybitshareswallet.databinding.FragmentLicenseBinding
import cy.agorise.bitsybitshareswallet.utils.Constants import cy.agorise.bitsybitshareswallet.utils.Constants
import kotlinx.android.synthetic.main.fragment_license.*
class LicenseFragment : Fragment() { class LicenseFragment : Fragment() {
@ -19,12 +19,25 @@ class LicenseFragment : Fragment() {
private const val TAG = "LicenseFragment" private const val TAG = "LicenseFragment"
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { private var _binding: FragmentLicenseBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Remove up navigation icon from the toolbar // Remove up navigation icon from the toolbar
val toolbar: Toolbar? = activity?.findViewById(R.id.toolbar) val toolbar: Toolbar? = activity?.findViewById(R.id.toolbar)
toolbar?.navigationIcon = null toolbar?.navigationIcon = null
return inflater.inflate(R.layout.fragment_license, container, false) _binding = FragmentLicenseBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -41,11 +54,11 @@ class LicenseFragment : Fragment() {
if (agreedLicenseVersion == Constants.CURRENT_LICENSE_VERSION) { if (agreedLicenseVersion == Constants.CURRENT_LICENSE_VERSION) {
agree() agree()
} else { } else {
wbLA.loadUrl("file:///android_asset/eula.html") binding.wbLA.loadUrl("file:///android_asset/eula.html")
btnDisagree.setOnClickListener { activity?.finish() } binding.btnDisagree.setOnClickListener { activity?.finish() }
btnAgree.setOnClickListener { agree() } binding.btnAgree.setOnClickListener { agree() }
} }
} }
@ -55,7 +68,8 @@ class LicenseFragment : Fragment() {
*/ */
private fun agree() { private fun agree() {
PreferenceManager.getDefaultSharedPreferences(context).edit() PreferenceManager.getDefaultSharedPreferences(context).edit()
.putInt(Constants.KEY_LAST_AGREED_LICENSE_VERSION, Constants.CURRENT_LICENSE_VERSION).apply() .putInt(Constants.KEY_LAST_AGREED_LICENSE_VERSION, Constants.CURRENT_LICENSE_VERSION)
.apply()
findNavController().navigate(R.id.import_brainkey_action) findNavController().navigate(R.id.import_brainkey_action)
} }

View file

@ -39,6 +39,7 @@ import com.jakewharton.rxbinding3.appcompat.queryTextChangeEvents
import cy.agorise.bitsybitshareswallet.R import cy.agorise.bitsybitshareswallet.R
import cy.agorise.bitsybitshareswallet.database.entities.Merchant import cy.agorise.bitsybitshareswallet.database.entities.Merchant
import cy.agorise.bitsybitshareswallet.database.entities.Teller import cy.agorise.bitsybitshareswallet.database.entities.Teller
import cy.agorise.bitsybitshareswallet.databinding.FragmentMerchantsBinding
import cy.agorise.bitsybitshareswallet.models.MapObject import cy.agorise.bitsybitshareswallet.models.MapObject
import cy.agorise.bitsybitshareswallet.utils.Constants import cy.agorise.bitsybitshareswallet.utils.Constants
import cy.agorise.bitsybitshareswallet.utils.MerchantClusterRenderer import cy.agorise.bitsybitshareswallet.utils.MerchantClusterRenderer
@ -50,10 +51,8 @@ import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import io.reactivex.functions.BiFunction import io.reactivex.functions.BiFunction
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import kotlinx.android.synthetic.main.fragment_merchants.*
import java.math.BigInteger import java.math.BigInteger
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.collections.ArrayList
class MerchantsFragment : Fragment(), OnMapReadyCallback, SearchView.OnSuggestionListener { class MerchantsFragment : Fragment(), OnMapReadyCallback, SearchView.OnSuggestionListener {
@ -74,6 +73,9 @@ class MerchantsFragment : Fragment(), OnMapReadyCallback, SearchView.OnSuggestio
private const val SUGGEST_COLUMN_IMAGE_RESOURCE = "suggest_image_resource" private const val SUGGEST_COLUMN_IMAGE_RESOURCE = "suggest_image_resource"
} }
private var _binding: FragmentMerchantsBinding? = null
private val binding get() = _binding!!
private var mMap: GoogleMap? = null private var mMap: GoogleMap? = null
private lateinit var mMerchantViewModel: MerchantViewModel private lateinit var mMerchantViewModel: MerchantViewModel
@ -108,17 +110,33 @@ class MerchantsFragment : Fragment(), OnMapReadyCallback, SearchView.OnSuggestio
private var statusBarSize = 0 private var statusBarSize = 0
private var navigationBarSize = 0 private var navigationBarSize = 0
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Hide the activity's Toolbar so that we can make the trick of the translucent navigation and status bars // Hide the activity's Toolbar so that we can make the trick of the translucent navigation and status bars
val activityToolbar: Toolbar? = activity?.findViewById(R.id.toolbar) val activityToolbar: Toolbar? = activity?.findViewById(R.id.toolbar)
activityToolbar?.visibility = View.GONE activityToolbar?.visibility = View.GONE
// Sets the Navigation and Status bars translucent so that the map can be viewed through them // Sets the Navigation and Status bars translucent so that the map can be viewed through them
val window = activity?.window val window = activity?.window
window?.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) window?.setFlags(
window?.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION,
WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION
)
window?.setFlags(
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
)
return inflater.inflate(R.layout.fragment_merchants, container, false) _binding = FragmentMerchantsBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -132,17 +150,17 @@ class MerchantsFragment : Fragment(), OnMapReadyCallback, SearchView.OnSuggestio
view.setOnApplyWindowInsetsListener { v, insets -> view.setOnApplyWindowInsetsListener { v, insets ->
statusBarSize = insets.systemWindowInsetTop statusBarSize = insets.systemWindowInsetTop
navigationBarSize = insets.systemWindowInsetBottom navigationBarSize = insets.systemWindowInsetBottom
val layoutParams = toolbar.layoutParams as ViewGroup.MarginLayoutParams val layoutParams = binding.toolbar.layoutParams as ViewGroup.MarginLayoutParams
layoutParams.topMargin = statusBarSize layoutParams.topMargin = statusBarSize
insets insets
} }
// Set the fragment's toolbar as the activity toolbar just for this fragment // Set the fragment's toolbar as the activity toolbar just for this fragment
(activity as AppCompatActivity).setSupportActionBar(toolbar) (activity as AppCompatActivity).setSupportActionBar(binding.toolbar)
(activity as AppCompatActivity).supportActionBar?.setDisplayHomeAsUpEnabled(true) (activity as AppCompatActivity).supportActionBar?.setDisplayHomeAsUpEnabled(true)
setHasOptionsMenu(true) setHasOptionsMenu(true)
toolbar?.setOnClickListener { dismissPopupWindow() } binding.toolbar.setOnClickListener { dismissPopupWindow() }
// Obtain the SupportMapFragment and get notified when the map is ready to be used. // Obtain the SupportMapFragment and get notified when the map is ready to be used.
val mapFragment = childFragmentManager.findFragmentById(R.id.map) as SupportMapFragment val mapFragment = childFragmentManager.findFragmentById(R.id.map) as SupportMapFragment
@ -183,7 +201,11 @@ class MerchantsFragment : Fragment(), OnMapReadyCallback, SearchView.OnSuggestio
} }
} }
mPopupWindow = PopupWindow(popupView, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) mPopupWindow = PopupWindow(
popupView,
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
@ -192,7 +214,8 @@ class MerchantsFragment : Fragment(), OnMapReadyCallback, SearchView.OnSuggestio
// Adds listener for the SearchView // Adds listener for the SearchView
val searchItem = menu.findItem(R.id.menu_search) val searchItem = menu.findItem(R.id.menu_search)
mSearchView = searchItem.actionView as SearchView mSearchView = searchItem.actionView as SearchView
mSearchView?.suggestionsAdapter = SimpleCursorAdapter(context, R.layout.item_merchant_suggestion, null, mSearchView?.suggestionsAdapter = SimpleCursorAdapter(
context, R.layout.item_merchant_suggestion, null,
arrayOf(SUGGEST_COLUMN_NAME, SUGGEST_COLUMN_ADDRESS, SUGGEST_COLUMN_IMAGE_RESOURCE), arrayOf(SUGGEST_COLUMN_NAME, SUGGEST_COLUMN_ADDRESS, SUGGEST_COLUMN_IMAGE_RESOURCE),
intArrayOf(R.id.tvName, R.id.tvAddress, R.id.ivMarkerPin) intArrayOf(R.id.tvName, R.id.tvAddress, R.id.ivMarkerPin)
) )
@ -268,24 +291,40 @@ class MerchantsFragment : Fragment(), OnMapReadyCallback, SearchView.OnSuggestio
mapObjects mapObjects
} }
).subscribe({mapObjects -> ).subscribe({ mapObjects ->
run { run {
Log.d(TAG, "list with ${mapObjects.size} elements") Log.d(TAG, "list with ${mapObjects.size} elements")
val cursor = MatrixCursor( val cursor = MatrixCursor(
arrayOf( arrayOf(
SUGGEST_COLUMN_ID, SUGGEST_COLUMN_LAT, SUGGEST_COLUMN_LON, SUGGEST_COLUMN_NAME, SUGGEST_COLUMN_ID,
SUGGEST_COLUMN_ADDRESS, SUGGEST_COLUMN_IS_MERCHANT, SUGGEST_COLUMN_IMAGE_RESOURCE SUGGEST_COLUMN_LAT,
SUGGEST_COLUMN_LON,
SUGGEST_COLUMN_NAME,
SUGGEST_COLUMN_ADDRESS,
SUGGEST_COLUMN_IS_MERCHANT,
SUGGEST_COLUMN_IMAGE_RESOURCE
) )
) )
for (mapObject in mapObjects) { for (mapObject in mapObjects) {
cursor.addRow(arrayOf(BigInteger(mapObject._id, 16).toLong(), mapObject.lat, mapObject.lon, cursor.addRow(
mapObject.name, mapObject.address, mapObject.isMerchant, arrayOf(
if (mapObject.isMerchant == 1) R.drawable.ic_merchant_pin else R.drawable.ic_teller_pin)) BigInteger(mapObject._id, 16).toLong(),
mapObject.lat,
mapObject.lon,
mapObject.name,
mapObject.address,
mapObject.isMerchant,
if (mapObject.isMerchant == 1) R.drawable.ic_merchant_pin else R.drawable.ic_teller_pin
)
)
} }
mSearchView?.suggestionsAdapter?.changeCursor(cursor) mSearchView?.suggestionsAdapter?.changeCursor(cursor)
} }
}, },
{error -> Log.e(TAG, "Error while retrieving autocomplete suggestions. Msg: $error")}) { error ->
val message = "Error while retrieving autocomplete suggestions. Msg: $error"
Log.e(TAG, message)
})
) )
} }
@ -319,7 +358,7 @@ class MerchantsFragment : Fragment(), OnMapReadyCallback, SearchView.OnSuggestio
if (item.itemId == R.id.menu_filter) { if (item.itemId == R.id.menu_filter) {
// Try to show or dismiss the custom popup window with the merchants and tellers switches // Try to show or dismiss the custom popup window with the merchants and tellers switches
if (mPopupWindow?.isShowing == false) { if (mPopupWindow?.isShowing == false) {
mPopupWindow?.showAsDropDown(toolbar, screenWidth, 8) mPopupWindow?.showAsDropDown(binding.toolbar, screenWidth, 8)
if (mMap?.isMyLocationEnabled == true) if (mMap?.isMyLocationEnabled == true)
mMap?.uiSettings?.isMyLocationButtonEnabled = false mMap?.uiSettings?.isMyLocationButtonEnabled = false
} else } else
@ -331,7 +370,11 @@ class MerchantsFragment : Fragment(), OnMapReadyCallback, SearchView.OnSuggestio
/** Handles the result from the location permission request */ /** Handles the result from the location permission request */
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults) super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_LOCATION_PERMISSION) { if (requestCode == REQUEST_LOCATION_PERMISSION) {
@ -357,7 +400,7 @@ class MerchantsFragment : Fragment(), OnMapReadyCallback, SearchView.OnSuggestio
mMap = googleMap mMap = googleMap
// Add padding to move the controls out of the toolbar/status bar and navigation bar. // Add padding to move the controls out of the toolbar/status bar and navigation bar.
mMap?.setPadding(0, toolbar.height + statusBarSize, 0, navigationBarSize) mMap?.setPadding(0, binding.toolbar.height + statusBarSize, 0, navigationBarSize)
applyMapTheme() applyMapTheme()
@ -407,9 +450,13 @@ class MerchantsFragment : Fragment(), OnMapReadyCallback, SearchView.OnSuggestio
private fun verifyLocationPermission() { private fun verifyLocationPermission() {
if (ContextCompat.checkSelfPermission(activity!!, Manifest.permission.ACCESS_FINE_LOCATION) if (ContextCompat.checkSelfPermission(activity!!, Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) { != PackageManager.PERMISSION_GRANTED
) {
// Permission is not already granted // Permission is not already granted
requestPermissions(arrayOf(android.Manifest.permission.ACCESS_FINE_LOCATION), REQUEST_LOCATION_PERMISSION) requestPermissions(
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
REQUEST_LOCATION_PERMISSION
)
} else { } else {
// Permission is already granted // Permission is already granted
mMap?.isMyLocationEnabled = true mMap?.isMyLocationEnabled = true
@ -435,7 +482,7 @@ class MerchantsFragment : Fragment(), OnMapReadyCallback, SearchView.OnSuggestio
// Force marker to use a custom info window // Force marker to use a custom info window
mMerchantClusterManager?.markerCollection?.setOnInfoWindowAdapter(MerchantInfoWindowAdapter()) mMerchantClusterManager?.markerCollection?.setOnInfoWindowAdapter(MerchantInfoWindowAdapter())
mMerchantViewModel.getAllMerchants().observe(this, Observer<List<Merchant>> {merchants -> mMerchantViewModel.getAllMerchants().observe(this, Observer<List<Merchant>> { merchants ->
this.merchants.clear() this.merchants.clear()
this.merchants.addAll(merchants) this.merchants.addAll(merchants)
showHideMerchantsMarkers() showHideMerchantsMarkers()
@ -461,7 +508,7 @@ class MerchantsFragment : Fragment(), OnMapReadyCallback, SearchView.OnSuggestio
// Force marker to use a custom info window // Force marker to use a custom info window
mTellerClusterManager?.markerCollection?.setOnInfoWindowAdapter(TellerInfoWindowAdapter()) mTellerClusterManager?.markerCollection?.setOnInfoWindowAdapter(TellerInfoWindowAdapter())
mMerchantViewModel.getAllTellers().observe(this, Observer<List<Teller>> {tellers -> mMerchantViewModel.getAllTellers().observe(this, Observer<List<Teller>> { tellers ->
this.tellers.clear() this.tellers.clear()
this.tellers.addAll(tellers) this.tellers.addAll(tellers)
showHideTellersMarkers() showHideTellersMarkers()
@ -512,8 +559,9 @@ class MerchantsFragment : Fragment(), OnMapReadyCallback, SearchView.OnSuggestio
inner class MerchantInfoWindowAdapter : GoogleMap.InfoWindowAdapter { inner class MerchantInfoWindowAdapter : GoogleMap.InfoWindowAdapter {
override fun getInfoWindow(marker: Marker?): View { override fun getInfoWindow(marker: Marker?): View {
val infoWindowLayout: View = LayoutInflater.from(context).inflate( val infoWindowLayout: View = LayoutInflater.from(context)
R.layout.marker_merch_info_window, null) .inflate(R.layout.marker_merch_info_window, null)
val tvName = infoWindowLayout.findViewById<TextView>(R.id.tvName) val tvName = infoWindowLayout.findViewById<TextView>(R.id.tvName)
val tvAddress = infoWindowLayout.findViewById<TextView>(R.id.tvAddress) val tvAddress = infoWindowLayout.findViewById<TextView>(R.id.tvAddress)
val tvPhone = infoWindowLayout.findViewById<TextView>(R.id.tvPhone) val tvPhone = infoWindowLayout.findViewById<TextView>(R.id.tvPhone)
@ -557,8 +605,9 @@ class MerchantsFragment : Fragment(), OnMapReadyCallback, SearchView.OnSuggestio
inner class TellerInfoWindowAdapter : GoogleMap.InfoWindowAdapter { inner class TellerInfoWindowAdapter : GoogleMap.InfoWindowAdapter {
override fun getInfoWindow(marker: Marker?): View { override fun getInfoWindow(marker: Marker?): View {
val infoWindowLayout: View = LayoutInflater.from(context).inflate( val infoWindowLayout: View = LayoutInflater.from(context)
R.layout.marker_teller_info_window, null) .inflate(R.layout.marker_teller_info_window, null)
val tvName = infoWindowLayout.findViewById<TextView>(R.id.tvName) val tvName = infoWindowLayout.findViewById<TextView>(R.id.tvName)
val tvAddress = infoWindowLayout.findViewById<TextView>(R.id.tvAddress) val tvAddress = infoWindowLayout.findViewById<TextView>(R.id.tvAddress)
val tvPhone = infoWindowLayout.findViewById<TextView>(R.id.tvPhone) val tvPhone = infoWindowLayout.findViewById<TextView>(R.id.tvPhone)

View file

@ -5,16 +5,25 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import cy.agorise.bitsybitshareswallet.R import cy.agorise.bitsybitshareswallet.databinding.FragmentNetWorthBinding
class NetWorthFragment: Fragment() { class NetWorthFragment : Fragment() {
private var _binding: FragmentNetWorthBinding? = null
private val binding get() = _binding!!
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View {
setHasOptionsMenu(true) setHasOptionsMenu(true)
return inflater.inflate(R.layout.fragment_net_worth, container, false) _binding = FragmentNetWorthBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
} }
} }

View file

@ -10,11 +10,11 @@ import android.view.inputmethod.EditorInfo
import com.google.firebase.crashlytics.FirebaseCrashlytics import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.jakewharton.rxbinding3.widget.textChanges import com.jakewharton.rxbinding3.widget.textChanges
import cy.agorise.bitsybitshareswallet.R import cy.agorise.bitsybitshareswallet.R
import cy.agorise.bitsybitshareswallet.databinding.DialogPinSecurityLockBinding
import cy.agorise.bitsybitshareswallet.utils.Constants import cy.agorise.bitsybitshareswallet.utils.Constants
import cy.agorise.bitsybitshareswallet.utils.CryptoUtils import cy.agorise.bitsybitshareswallet.utils.CryptoUtils
import cy.agorise.bitsybitshareswallet.utils.hideKeyboard import cy.agorise.bitsybitshareswallet.utils.hideKeyboard
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.synthetic.main.dialog_pin_security_lock.*
/** /**
* Contains all the specific logic to create and confirm a new PIN or verifying the validity of the current one. * Contains all the specific logic to create and confirm a new PIN or verifying the validity of the current one.
@ -25,9 +25,22 @@ class PINSecurityLockDialog : BaseSecurityLockDialog() {
const val TAG = "PINSecurityLockDialog" const val TAG = "PINSecurityLockDialog"
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { private var _binding: DialogPinSecurityLockBinding? = null
private val binding get() = _binding!!
return inflater.inflate(R.layout.dialog_pin_security_lock, container, false) override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = DialogPinSecurityLockBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
} }
private var newPIN = "" private var newPIN = ""
@ -39,13 +52,13 @@ class PINSecurityLockDialog : BaseSecurityLockDialog() {
crashlytics.setCustomKey(Constants.CRASHLYTICS_KEY_LAST_SCREEN, TAG) crashlytics.setCustomKey(Constants.CRASHLYTICS_KEY_LAST_SCREEN, TAG)
// Request focus to the PIN EditText and automatically show the keyboard when the dialog appears. // Request focus to the PIN EditText and automatically show the keyboard when the dialog appears.
tietPIN.requestFocus() binding.tietPIN.requestFocus()
dialog?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) dialog?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
setupScreen() setupScreen()
// Listens to the event when the user clicks the 'Enter' button in the keyboard and acts accordingly // Listens to the event when the user clicks the 'Enter' button in the keyboard and acts accordingly
tietPIN.setOnEditorActionListener { v, actionId, _ -> binding.tietPIN.setOnEditorActionListener { v, actionId, _ ->
var handled = false var handled = false
if (actionId == EditorInfo.IME_ACTION_GO) { if (actionId == EditorInfo.IME_ACTION_GO) {
if (currentStep == STEP_SECURITY_LOCK_VERIFY) { if (currentStep == STEP_SECURITY_LOCK_VERIFY) {
@ -56,8 +69,8 @@ class PINSecurityLockDialog : BaseSecurityLockDialog() {
if (hashedPIN == currentHashedPINPattern) { if (hashedPIN == currentHashedPINPattern) {
// PIN is correct, proceed // PIN is correct, proceed
resetIncorrectSecurityLockAttemptsAndTime() resetIncorrectSecurityLockAttemptsAndTime()
tietPIN.hideKeyboard() binding.tietPIN.hideKeyboard()
rootView.requestFocus() binding.rootView.requestFocus()
dismiss() dismiss()
mCallback?.onPINPatternEntered(actionIdentifier) mCallback?.onPINPatternEntered(actionIdentifier)
} else { } else {
@ -65,7 +78,7 @@ class PINSecurityLockDialog : BaseSecurityLockDialog() {
if (incorrectSecurityLockAttempts < Constants.MAX_INCORRECT_SECURITY_LOCK_ATTEMPTS) { if (incorrectSecurityLockAttempts < Constants.MAX_INCORRECT_SECURITY_LOCK_ATTEMPTS) {
// Show the error only when the user has not reached the max attempts limit, because if that // Show the error only when the user has not reached the max attempts limit, because if that
// is the case another error is gonna be shown in the setupScreen() method // is the case another error is gonna be shown in the setupScreen() method
tilPIN.error = getString(R.string.error__wrong_pin) binding.tilPIN.error = getString(R.string.error__wrong_pin)
} }
setupScreen() setupScreen()
} }
@ -81,7 +94,7 @@ class PINSecurityLockDialog : BaseSecurityLockDialog() {
} else if (currentStep == STEP_SECURITY_LOCK_CONFIRM) { } else if (currentStep == STEP_SECURITY_LOCK_CONFIRM) {
val pinConfirm = v.text.toString().trim() val pinConfirm = v.text.toString().trim()
if (pinConfirm != newPIN) { if (pinConfirm != newPIN) {
tvTitle.text = getString(R.string.title__pins_dont_match) binding.tvTitle.text = getString(R.string.title__pins_dont_match)
} else { } else {
val salt = CryptoUtils.generateSalt() val salt = CryptoUtils.generateSalt()
val hashedPIN = CryptoUtils.createSHA256Hash(salt + pinConfirm) val hashedPIN = CryptoUtils.createSHA256Hash(salt + pinConfirm)
@ -103,20 +116,21 @@ class PINSecurityLockDialog : BaseSecurityLockDialog() {
} }
mDisposables.add( mDisposables.add(
tietPIN.textChanges() binding.tietPIN.textChanges()
.skipInitialValue() .skipInitialValue()
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { .subscribe {
if (currentStep == STEP_SECURITY_LOCK_VERIFY && if (currentStep == STEP_SECURITY_LOCK_VERIFY &&
incorrectSecurityLockAttempts < Constants.MAX_INCORRECT_SECURITY_LOCK_ATTEMPTS) { incorrectSecurityLockAttempts < Constants.MAX_INCORRECT_SECURITY_LOCK_ATTEMPTS
) {
// Make sure the error is removed when the user types again // Make sure the error is removed when the user types again
tilPIN.isErrorEnabled = false binding.tilPIN.isErrorEnabled = false
} else if (currentStep == STEP_SECURITY_LOCK_CREATE) { } else if (currentStep == STEP_SECURITY_LOCK_CREATE) {
// Show the min length requirement for the PIN only when it has not been fulfilled // Show the min length requirement for the PIN only when it has not been fulfilled
if (it.trim().length >= Constants.MIN_PIN_LENGTH) { if (it.trim().length >= Constants.MIN_PIN_LENGTH) {
tilPIN.helperText = "" binding.tilPIN.helperText = ""
} else { } else {
tilPIN.helperText = getString(R.string.msg__min_pin_length) binding.tilPIN.helperText = getString(R.string.msg__min_pin_length)
} }
} }
} }
@ -126,15 +140,15 @@ class PINSecurityLockDialog : BaseSecurityLockDialog() {
private fun setupScreen() { private fun setupScreen() {
when (currentStep) { when (currentStep) {
STEP_SECURITY_LOCK_VERIFY -> { STEP_SECURITY_LOCK_VERIFY -> {
tvTitle.text = getString(R.string.title__re_enter_your_pin) binding.tvTitle.text = getString(R.string.title__re_enter_your_pin)
tvSubTitle.text = getString(R.string.msg__enter_your_pin) binding.tvSubTitle.text = getString(R.string.msg__enter_your_pin)
tietPIN.isEnabled = true binding.tietPIN.isEnabled = true
if (incorrectSecurityLockAttempts >= Constants.MAX_INCORRECT_SECURITY_LOCK_ATTEMPTS) { if (incorrectSecurityLockAttempts >= Constants.MAX_INCORRECT_SECURITY_LOCK_ATTEMPTS) {
// User has entered the PIN incorrectly too many times // User has entered the PIN incorrectly too many times
val now = System.currentTimeMillis() val now = System.currentTimeMillis()
if (now <= incorrectSecurityLockTime + Constants.INCORRECT_SECURITY_LOCK_COOLDOWN) { if (now <= incorrectSecurityLockTime + Constants.INCORRECT_SECURITY_LOCK_COOLDOWN) {
tietPIN.setText("") binding.tietPIN.setText("")
tietPIN.isEnabled = false binding.tietPIN.isEnabled = false
startContDownTimer() startContDownTimer()
} else { } else {
resetIncorrectSecurityLockAttemptsAndTime() resetIncorrectSecurityLockAttemptsAndTime()
@ -142,28 +156,28 @@ class PINSecurityLockDialog : BaseSecurityLockDialog() {
} }
} }
STEP_SECURITY_LOCK_CREATE -> { STEP_SECURITY_LOCK_CREATE -> {
tvTitle.text = getString(R.string.title__set_bitsy_security_lock) binding.tvTitle.text = getString(R.string.title__set_bitsy_security_lock)
tvSubTitle.text = getString(R.string.msg__set_a_pin) binding.tvSubTitle.text = getString(R.string.msg__set_a_pin)
tilPIN.helperText = getString(R.string.msg__min_pin_length) binding.tilPIN.helperText = getString(R.string.msg__min_pin_length)
tilPIN.isErrorEnabled = false binding.tilPIN.isErrorEnabled = false
} }
STEP_SECURITY_LOCK_CONFIRM -> { STEP_SECURITY_LOCK_CONFIRM -> {
tvTitle.text = getString(R.string.title__re_enter_your_pin) binding.tvTitle.text = getString(R.string.title__re_enter_your_pin)
tvSubTitle.text = "" binding.tvSubTitle.text = ""
tvSubTitle.visibility = View.GONE binding.tvSubTitle.visibility = View.GONE
tietPIN.setText("") binding.tietPIN.setText("")
tilPIN.helperText = "" binding.tilPIN.helperText = ""
tilPIN.isErrorEnabled = false binding.tilPIN.isErrorEnabled = false
} }
} }
} }
override fun onTimerSecondPassed(errorMessage: String) { override fun onTimerSecondPassed(errorMessage: String) {
tilPIN.error = errorMessage binding.tilPIN.error = errorMessage
} }
override fun onTimerFinished() { override fun onTimerFinished() {
setupScreen() setupScreen()
tilPIN.isErrorEnabled = false binding.tilPIN.isErrorEnabled = false
} }
} }

View file

@ -5,11 +5,11 @@ import android.preference.PreferenceManager
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import cy.agorise.bitsybitshareswallet.R
import kotlinx.android.synthetic.main.dialog_pattern_security_lock.*
import com.andrognito.patternlockview.PatternLockView import com.andrognito.patternlockview.PatternLockView
import com.andrognito.patternlockview.listener.PatternLockViewListener import com.andrognito.patternlockview.listener.PatternLockViewListener
import com.google.firebase.crashlytics.FirebaseCrashlytics import com.google.firebase.crashlytics.FirebaseCrashlytics
import cy.agorise.bitsybitshareswallet.R
import cy.agorise.bitsybitshareswallet.databinding.DialogPatternSecurityLockBinding
import cy.agorise.bitsybitshareswallet.utils.Constants import cy.agorise.bitsybitshareswallet.utils.Constants
import cy.agorise.bitsybitshareswallet.utils.CryptoUtils import cy.agorise.bitsybitshareswallet.utils.CryptoUtils
@ -23,9 +23,22 @@ class PatternSecurityLockDialog : BaseSecurityLockDialog() {
const val TAG = "PatternSecurityLockDialog" const val TAG = "PatternSecurityLockDialog"
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { private var _binding: DialogPatternSecurityLockBinding? = null
private val binding get() = _binding!!
return inflater.inflate(R.layout.dialog_pattern_security_lock, container, false) override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = DialogPatternSecurityLockBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
} }
private var newPattern = "" private var newPattern = ""
@ -38,9 +51,9 @@ class PatternSecurityLockDialog : BaseSecurityLockDialog() {
setupScreen() setupScreen()
patternLockView.addPatternLockListener(mPatternLockViewListener) binding.patternLockView.addPatternLockListener(mPatternLockViewListener)
btnClear.setOnClickListener { setupScreen() } binding.btnClear.setOnClickListener { setupScreen() }
} }
private val mPatternLockViewListener = object : PatternLockViewListener { private val mPatternLockViewListener = object : PatternLockViewListener {
@ -51,11 +64,11 @@ class PatternSecurityLockDialog : BaseSecurityLockDialog() {
setMessage("") setMessage("")
} }
STEP_SECURITY_LOCK_CREATE -> { STEP_SECURITY_LOCK_CREATE -> {
btnClear.visibility = View.INVISIBLE binding.btnClear.visibility = View.INVISIBLE
setMessage(getString(R.string.msg__release_finger)) setMessage(getString(R.string.msg__release_finger))
} }
STEP_SECURITY_LOCK_CONFIRM -> { STEP_SECURITY_LOCK_CONFIRM -> {
btnClear.visibility = View.INVISIBLE binding.btnClear.visibility = View.INVISIBLE
setMessage(getString(R.string.msg__release_finger)) setMessage(getString(R.string.msg__release_finger))
} }
} }
@ -67,8 +80,10 @@ class PatternSecurityLockDialog : BaseSecurityLockDialog() {
override fun onComplete(pattern: List<PatternLockView.Dot>) { override fun onComplete(pattern: List<PatternLockView.Dot>) {
if (currentStep == STEP_SECURITY_LOCK_VERIFY) { if (currentStep == STEP_SECURITY_LOCK_VERIFY) {
val hashedPattern = CryptoUtils.createSHA256Hash(currentPINPatternSalt + val hashedPattern = CryptoUtils.createSHA256Hash(
getStringPattern(pattern)) currentPINPatternSalt +
getStringPattern(pattern)
)
if (hashedPattern == currentHashedPINPattern) { if (hashedPattern == currentHashedPINPattern) {
// Pattern is correct, proceed // Pattern is correct, proceed
resetIncorrectSecurityLockAttemptsAndTime() resetIncorrectSecurityLockAttemptsAndTime()
@ -84,17 +99,17 @@ class PatternSecurityLockDialog : BaseSecurityLockDialog() {
setupScreen() setupScreen()
} }
} else if (currentStep == STEP_SECURITY_LOCK_CREATE) { } else if (currentStep == STEP_SECURITY_LOCK_CREATE) {
btnClear.visibility = View.VISIBLE binding.btnClear.visibility = View.VISIBLE
if (pattern.size < 4) { if (pattern.size < 4) {
setError(getString(R.string.error__connect_at_least_4_dots)) setError(getString(R.string.error__connect_at_least_4_dots))
patternLockView.setViewMode(PatternLockView.PatternViewMode.WRONG) binding.patternLockView.setViewMode(PatternLockView.PatternViewMode.WRONG)
} else { } else {
setMessage(getString(R.string.text__pattern_recorded)) setMessage(getString(R.string.text__pattern_recorded))
patternLockView.setViewMode(PatternLockView.PatternViewMode.CORRECT) binding.patternLockView.setViewMode(PatternLockView.PatternViewMode.CORRECT)
patternLockView.isInputEnabled = false binding.patternLockView.isInputEnabled = false
btnNext.isEnabled = true binding.btnNext.isEnabled = true
newPattern = getStringPattern(pattern) newPattern = getStringPattern(pattern)
btnNext.setOnClickListener { binding.btnNext.setOnClickListener {
currentStep = STEP_SECURITY_LOCK_CONFIRM currentStep = STEP_SECURITY_LOCK_CONFIRM
setupScreen() setupScreen()
} }
@ -103,14 +118,14 @@ class PatternSecurityLockDialog : BaseSecurityLockDialog() {
val patternConfirm = getStringPattern(pattern) val patternConfirm = getStringPattern(pattern)
if (patternConfirm != newPattern) { if (patternConfirm != newPattern) {
setError(getString(R.string.error__wront_pattern)) setError(getString(R.string.error__wront_pattern))
btnNext.isEnabled = false binding.btnNext.isEnabled = false
patternLockView.setViewMode(PatternLockView.PatternViewMode.WRONG) binding.patternLockView.setViewMode(PatternLockView.PatternViewMode.WRONG)
} else { } else {
setMessage(getString(R.string.msg__your_new_unlock_pattern)) setMessage(getString(R.string.msg__your_new_unlock_pattern))
patternLockView.isEnabled = false binding.patternLockView.isEnabled = false
patternLockView.setViewMode(PatternLockView.PatternViewMode.CORRECT) binding.patternLockView.setViewMode(PatternLockView.PatternViewMode.CORRECT)
btnNext.isEnabled = true binding.btnNext.isEnabled = true
btnNext.setOnClickListener { binding.btnNext.setOnClickListener {
context?.let { context?.let {
val salt = CryptoUtils.generateSalt() val salt = CryptoUtils.generateSalt()
val hashedPattern = CryptoUtils.createSHA256Hash(salt + patternConfirm) val hashedPattern = CryptoUtils.createSHA256Hash(salt + patternConfirm)
@ -119,7 +134,8 @@ class PatternSecurityLockDialog : BaseSecurityLockDialog() {
PreferenceManager.getDefaultSharedPreferences(it).edit() PreferenceManager.getDefaultSharedPreferences(it).edit()
.putString(Constants.KEY_HASHED_PIN_PATTERN, hashedPattern) .putString(Constants.KEY_HASHED_PIN_PATTERN, hashedPattern)
.putString(Constants.KEY_PIN_PATTERN_SALT, salt) .putString(Constants.KEY_PIN_PATTERN_SALT, salt)
.putInt(Constants.KEY_SECURITY_LOCK_SELECTED, 2).apply() // 2 -> Pattern .putInt(Constants.KEY_SECURITY_LOCK_SELECTED, 2)
.apply() // 2 -> Pattern
dismiss() dismiss()
mCallback?.onPINPatternChanged() mCallback?.onPINPatternChanged()
@ -148,17 +164,17 @@ class PatternSecurityLockDialog : BaseSecurityLockDialog() {
private fun setupScreen() { private fun setupScreen() {
when (currentStep) { when (currentStep) {
STEP_SECURITY_LOCK_VERIFY -> { STEP_SECURITY_LOCK_VERIFY -> {
tvTitle.text = getString(R.string.title__re_enter_your_pattern) binding.tvTitle.text = getString(R.string.title__re_enter_your_pattern)
tvSubTitle.text = getString(R.string.msg__enter_your_pattern) binding.tvSubTitle.text = getString(R.string.msg__enter_your_pattern)
btnClear.visibility = View.GONE binding.btnClear.visibility = View.GONE
btnNext.visibility = View.GONE binding.btnNext.visibility = View.GONE
patternLockView.isInputEnabled = true binding.patternLockView.isInputEnabled = true
patternLockView.isInStealthMode = true binding.patternLockView.isInStealthMode = true
if (incorrectSecurityLockAttempts >= Constants.MAX_INCORRECT_SECURITY_LOCK_ATTEMPTS) { if (incorrectSecurityLockAttempts >= Constants.MAX_INCORRECT_SECURITY_LOCK_ATTEMPTS) {
// User has entered the Pattern incorrectly too many times // User has entered the Pattern incorrectly too many times
val now = System.currentTimeMillis() val now = System.currentTimeMillis()
if (now <= incorrectSecurityLockTime + Constants.INCORRECT_SECURITY_LOCK_COOLDOWN) { if (now <= incorrectSecurityLockTime + Constants.INCORRECT_SECURITY_LOCK_COOLDOWN) {
patternLockView.isInputEnabled = false binding.patternLockView.isInputEnabled = false
startContDownTimer() startContDownTimer()
} else { } else {
resetIncorrectSecurityLockAttemptsAndTime() resetIncorrectSecurityLockAttemptsAndTime()
@ -166,24 +182,24 @@ class PatternSecurityLockDialog : BaseSecurityLockDialog() {
} }
} }
STEP_SECURITY_LOCK_CREATE -> { STEP_SECURITY_LOCK_CREATE -> {
tvTitle.text = getString(R.string.title__set_bitsy_security_lock) binding.tvTitle.text = getString(R.string.title__set_bitsy_security_lock)
tvSubTitle.text = getString(R.string.msg__set_a_pattern) binding.tvSubTitle.text = getString(R.string.msg__set_a_pattern)
setMessage(getString(R.string.text__draw_an_unlock_pattern)) setMessage(getString(R.string.text__draw_an_unlock_pattern))
patternLockView.clearPattern() binding.patternLockView.clearPattern()
patternLockView.isInputEnabled = true binding.patternLockView.isInputEnabled = true
btnClear.visibility = View.INVISIBLE binding.btnClear.visibility = View.INVISIBLE
btnNext.isEnabled = false binding.btnNext.isEnabled = false
} }
STEP_SECURITY_LOCK_CONFIRM -> { STEP_SECURITY_LOCK_CONFIRM -> {
tvTitle.text = getString(R.string.title__re_enter_your_pattern) binding.tvTitle.text = getString(R.string.title__re_enter_your_pattern)
tvSubTitle.text = "" binding.tvSubTitle.text = ""
setMessage(getString(R.string.msg__draw_pattern_confirm)) setMessage(getString(R.string.msg__draw_pattern_confirm))
tvSubTitle.visibility = View.GONE binding.tvSubTitle.visibility = View.GONE
patternLockView.clearPattern() binding.patternLockView.clearPattern()
patternLockView.isInputEnabled = true binding.patternLockView.isInputEnabled = true
btnClear.visibility = View.INVISIBLE binding.btnClear.visibility = View.INVISIBLE
btnNext.isEnabled = false binding.btnNext.isEnabled = false
btnNext.text = getString(R.string.button__confirm) binding.btnNext.text = getString(R.string.button__confirm)
} }
} }
} }
@ -191,21 +207,21 @@ class PatternSecurityLockDialog : BaseSecurityLockDialog() {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
private fun setMessage(message: String) { private fun setMessage(message: String) {
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M) { if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M) {
tvMessage.setTextAppearance(context, R.style.TextAppearance_Bitsy_Body2) binding.tvMessage.setTextAppearance(context, R.style.TextAppearance_Bitsy_Body2)
} else { } else {
tvMessage.setTextAppearance(R.style.TextAppearance_Bitsy_Body2) binding.tvMessage.setTextAppearance(R.style.TextAppearance_Bitsy_Body2)
} }
tvMessage.text = message binding.tvMessage.text = message
} }
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
private fun setError(error: String) { private fun setError(error: String) {
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M) { if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M) {
tvMessage.setTextAppearance(context, R.style.TextAppearance_Bitsy_Body2_Error) binding.tvMessage.setTextAppearance(context, R.style.TextAppearance_Bitsy_Body2_Error)
} else { } else {
tvMessage.setTextAppearance(R.style.TextAppearance_Bitsy_Body2_Error) binding.tvMessage.setTextAppearance(R.style.TextAppearance_Bitsy_Body2_Error)
} }
tvMessage.text = error binding.tvMessage.text = error
} }
override fun onTimerSecondPassed(errorMessage: String) { override fun onTimerSecondPassed(errorMessage: String) {

View file

@ -19,6 +19,7 @@ import com.jakewharton.rxbinding3.widget.textChanges
import cy.agorise.bitsybitshareswallet.R import cy.agorise.bitsybitshareswallet.R
import cy.agorise.bitsybitshareswallet.adapters.AssetsAdapter import cy.agorise.bitsybitshareswallet.adapters.AssetsAdapter
import cy.agorise.bitsybitshareswallet.adapters.AutoSuggestAssetAdapter import cy.agorise.bitsybitshareswallet.adapters.AutoSuggestAssetAdapter
import cy.agorise.bitsybitshareswallet.databinding.FragmentReceiveTransactionBinding
import cy.agorise.bitsybitshareswallet.utils.Constants import cy.agorise.bitsybitshareswallet.utils.Constants
import cy.agorise.bitsybitshareswallet.utils.Helper import cy.agorise.bitsybitshareswallet.utils.Helper
import cy.agorise.bitsybitshareswallet.utils.showKeyboard import cy.agorise.bitsybitshareswallet.utils.showKeyboard
@ -29,8 +30,6 @@ import cy.agorise.graphenej.api.ConnectionStatusUpdate
import cy.agorise.graphenej.api.calls.ListAssets import cy.agorise.graphenej.api.calls.ListAssets
import cy.agorise.graphenej.models.JsonRpcResponse import cy.agorise.graphenej.models.JsonRpcResponse
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.synthetic.main.fragment_receive_transaction.*
import java.lang.Exception
import java.math.RoundingMode import java.math.RoundingMode
import java.text.DecimalFormat import java.text.DecimalFormat
import java.text.DecimalFormatSymbols import java.text.DecimalFormatSymbols
@ -53,6 +52,9 @@ class ReceiveTransactionFragment : ConnectedFragment() {
private const val OTHER_ASSET = "other_asset" private const val OTHER_ASSET = "other_asset"
} }
private var _binding: FragmentReceiveTransactionBinding? = null
private val binding get() = _binding!!
private lateinit var mViewModel: ReceiveTransactionViewModel private lateinit var mViewModel: ReceiveTransactionViewModel
/** Current user account */ /** Current user account */
@ -75,7 +77,11 @@ class ReceiveTransactionFragment : ConnectedFragment() {
// Map used to keep track of request and response id pairs // Map used to keep track of request and response id pairs
private val responseMap = LongSparseArray<Int>() private val responseMap = LongSparseArray<Int>()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
setHasOptionsMenu(true) setHasOptionsMenu(true)
val nightMode = PreferenceManager.getDefaultSharedPreferences(context) val nightMode = PreferenceManager.getDefaultSharedPreferences(context)
@ -88,13 +94,21 @@ class ReceiveTransactionFragment : ConnectedFragment() {
// Sets the status and navigation bars background color to a dark green or just dark // Sets the status and navigation bars background color to a dark green or just dark
val window = activity?.window val window = activity?.window
context?.let { context -> context?.let { context ->
val statusBarColor = ContextCompat.getColor(context, val statusBarColor = ContextCompat.getColor(
if (!nightMode) R.color.colorReceiveDark else R.color.colorStatusBarDark) context,
if (!nightMode) R.color.colorReceiveDark else R.color.colorStatusBarDark
)
window?.statusBarColor = statusBarColor window?.statusBarColor = statusBarColor
window?.navigationBarColor = statusBarColor window?.navigationBarColor = statusBarColor
} }
return inflater.inflate(R.layout.fragment_receive_transaction, container, false) _binding = FragmentReceiveTransactionBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -110,7 +124,7 @@ class ReceiveTransactionFragment : ConnectedFragment() {
.getString(Constants.KEY_CURRENT_ACCOUNT_ID, "") .getString(Constants.KEY_CURRENT_ACCOUNT_ID, "")
mViewModel.getUserAccount(userId!!).observe(this, mViewModel.getUserAccount(userId!!).observe(this,
Observer<cy.agorise.bitsybitshareswallet.database.entities.UserAccount>{ user -> Observer<cy.agorise.bitsybitshareswallet.database.entities.UserAccount> { user ->
mUserAccount = UserAccount(user.id, user.name) mUserAccount = UserAccount(user.id, user.name)
}) })
@ -121,8 +135,10 @@ class ReceiveTransactionFragment : ConnectedFragment() {
// Add BTS to always show a QR // Add BTS to always show a QR
if (mAssets.isEmpty()) if (mAssets.isEmpty())
mAssets.add(cy.agorise.bitsybitshareswallet.database.entities.Asset( mAssets.add(
"1.3.0", "BTS", 5, "", "") cy.agorise.bitsybitshareswallet.database.entities.Asset(
"1.3.0", "BTS", 5, "", ""
)
) )
mAssets.sortWith( mAssets.sortWith(
@ -135,33 +151,39 @@ class ReceiveTransactionFragment : ConnectedFragment() {
) )
mAssets.add(asset) mAssets.add(asset)
mAssetsAdapter = AssetsAdapter(context!!, android.R.layout.simple_spinner_item, mAssets) mAssetsAdapter =
spAsset.adapter = mAssetsAdapter AssetsAdapter(context!!, android.R.layout.simple_spinner_item, mAssets)
binding.spAsset.adapter = mAssetsAdapter
// Try to select the selectedAssetSymbol // Try to select the selectedAssetSymbol
for (i in 0 until mAssetsAdapter!!.count) { for (i in 0 until mAssetsAdapter!!.count) {
if (mAssetsAdapter!!.getItem(i)!!.symbol == selectedAssetSymbol) { if (mAssetsAdapter!!.getItem(i)!!.symbol == selectedAssetSymbol) {
spAsset.setSelection(i) binding.spAsset.setSelection(i)
break break
} }
} }
}) })
mViewModel.qrCodeBitmap.observe(this, Observer { bitmap -> mViewModel.qrCodeBitmap.observe(this, Observer { bitmap ->
ivQR.setImageBitmap(bitmap) binding.ivQR.setImageBitmap(bitmap)
}) })
spAsset.onItemSelectedListener = object : AdapterView.OnItemSelectedListener{ binding.spAsset.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) { } override fun onNothingSelected(parent: AdapterView<*>?) {}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
mAssetsAdapter?.getItem(position)?.let { asset -> mAssetsAdapter?.getItem(position)?.let { asset ->
if (asset.id == OTHER_ASSET) { if (asset.id == OTHER_ASSET) {
tilAsset.visibility = View.VISIBLE binding.tilAsset.visibility = View.VISIBLE
actvAsset.showKeyboard() binding.actvAsset.showKeyboard()
mAsset = null mAsset = null
} else { } else {
tilAsset.visibility = View.GONE binding.tilAsset.visibility = View.GONE
selectedAssetSymbol = asset.symbol selectedAssetSymbol = asset.symbol
mAsset = Asset(asset.id, asset.toString(), asset.precision) mAsset = Asset(asset.id, asset.toString(), asset.precision)
@ -174,25 +196,26 @@ class ReceiveTransactionFragment : ConnectedFragment() {
// Use RxJava Debounce to create QR code only after the user stopped typing an amount // Use RxJava Debounce to create QR code only after the user stopped typing an amount
mDisposables.add( mDisposables.add(
tietAmount.textChanges() binding.tietAmount.textChanges()
.debounce(1000, TimeUnit.MILLISECONDS) .debounce(1000, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe( { updateQR() }, { mAsset = null} ) .subscribe({ updateQR() }, { mAsset = null })
) )
// Add adapter to the Assets AutoCompleteTextView // Add adapter to the Assets AutoCompleteTextView
mAutoSuggestAssetAdapter = AutoSuggestAssetAdapter(context!!, android.R.layout.simple_dropdown_item_1line) mAutoSuggestAssetAdapter =
actvAsset.setAdapter(mAutoSuggestAssetAdapter) AutoSuggestAssetAdapter(context!!, android.R.layout.simple_dropdown_item_1line)
binding.actvAsset.setAdapter(mAutoSuggestAssetAdapter)
// Use RxJava Debounce to avoid making calls to the NetworkService on every text change event and also avoid // Use RxJava Debounce to avoid making calls to the NetworkService on every text change event and also avoid
// the first call when the View is created // the first call when the View is created
mDisposables.add( mDisposables.add(
actvAsset.textChanges() binding.actvAsset.textChanges()
.skipInitialValue() .skipInitialValue()
.debounce(500, TimeUnit.MILLISECONDS) .debounce(500, TimeUnit.MILLISECONDS)
.map { it.toString().trim().toUpperCase() } .map { it.toString().trim().toUpperCase() }
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe( { .subscribe({
if (!selectedInAutoCompleteTextView) { if (!selectedInAutoCompleteTextView) {
mAsset = null mAsset = null
updateQR() updateQR()
@ -201,15 +224,18 @@ class ReceiveTransactionFragment : ConnectedFragment() {
// Get a list of assets that match the already typed string by the user // Get a list of assets that match the already typed string by the user
if (it.length > 1 && mNetworkService != null) { if (it.length > 1 && mNetworkService != null) {
val id = mNetworkService?.sendMessage(ListAssets(it, AUTO_SUGGEST_ASSET_LIMIT), val id = mNetworkService?.sendMessage(
ListAssets.REQUIRED_API) ListAssets(it, AUTO_SUGGEST_ASSET_LIMIT),
ListAssets.REQUIRED_API
)
if (id != null) responseMap.append(id, RESPONSE_LIST_ASSETS) if (id != null) responseMap.append(id, RESPONSE_LIST_ASSETS)
} }
}, { mAsset = null } ) }, { mAsset = null })
) )
actvAsset.setOnItemClickListener { parent, _, position, _ -> binding.actvAsset.setOnItemClickListener { parent, _, position, _ ->
val asset = parent.adapter.getItem(position) as cy.agorise.bitsybitshareswallet.database.entities.Asset val asset =
parent.adapter.getItem(position) as cy.agorise.bitsybitshareswallet.database.entities.Asset
mAsset = Asset(asset.id, asset.toString(), asset.precision) mAsset = Asset(asset.id, asset.toString(), asset.precision)
selectedInAutoCompleteTextView = true selectedInAutoCompleteTextView = true
updateQR() updateQR()
@ -261,7 +287,7 @@ class ReceiveTransactionFragment : ConnectedFragment() {
private fun updateQR() { private fun updateQR() {
if (mAsset == null) { if (mAsset == null) {
ivQR.setImageDrawable(null) binding.ivQR.setImageDrawable(null)
// TODO clean the please pay and to text at the bottom too // TODO clean the please pay and to text at the bottom too
return return
} }
@ -270,20 +296,22 @@ class ReceiveTransactionFragment : ConnectedFragment() {
// Try to obtain the amount from the Amount Text Field or make it zero otherwise // Try to obtain the amount from the Amount Text Field or make it zero otherwise
val amount: Long = try { val amount: Long = try {
val tmpAmount = tietAmount.text.toString().toDouble() val tmpAmount = binding.tietAmount.text.toString().toDouble()
(tmpAmount * Math.pow(10.0, asset.precision.toDouble())).toLong() (tmpAmount * Math.pow(10.0, asset.precision.toDouble())).toLong()
}catch (e: Exception) { } catch (e: Exception) {
0 0
} }
val total = AssetAmount(UnsignedLong.valueOf(amount), asset) val total = AssetAmount(UnsignedLong.valueOf(amount), asset)
val totalInDouble = Util.fromBase(total) val totalInDouble = Util.fromBase(total)
val items = arrayOf(LineItem("transfer", 1, totalInDouble)) val items = arrayOf(LineItem("transfer", 1, totalInDouble))
val invoice = Invoice(mUserAccount?.name, "", "", val invoice = Invoice(
asset.symbol.replaceFirst("bit", ""), items, "", "") mUserAccount?.name, "", "",
asset.symbol.replaceFirst("bit", ""), items, "", ""
)
Log.d(TAG, "invoice: " + invoice.toJsonString()) Log.d(TAG, "invoice: " + invoice.toJsonString())
try { try {
mViewModel.updateInvoice(invoice, min(ivQR.width, ivQR.height)) mViewModel.updateInvoice(invoice, min(binding.ivQR.width, binding.ivQR.height))
updateAmountAddressUI(amount, asset.symbol, asset.precision, mUserAccount!!.name) updateAmountAddressUI(amount, asset.symbol, asset.precision, mUserAccount!!.name)
} catch (e: NullPointerException) { } catch (e: NullPointerException) {
Log.e(TAG, "NullPointerException. Msg: " + e.message) Log.e(TAG, "NullPointerException. Msg: " + e.message)
@ -294,11 +322,16 @@ class ReceiveTransactionFragment : ConnectedFragment() {
/** /**
* Updates the UI to show the amount and account to send the payment * Updates the UI to show the amount and account to send the payment
*/ */
private fun updateAmountAddressUI(assetAmount: Long, assetSymbol: String, assetPrecision: Int, account: String) { private fun updateAmountAddressUI(
assetAmount: Long,
assetSymbol: String,
assetPrecision: Int,
account: String
) {
val txtAmount: String = if (assetAmount == 0L) { val txtAmount: String = if (assetAmount == 0L) {
getString(R.string.template__please_send, getString(R.string.text__any_amount), " ") getString(R.string.template__please_send, getString(R.string.text__any_amount), " ")
} else { } else {
val df = DecimalFormat("####."+("#".repeat(assetPrecision))) val df = DecimalFormat("####." + ("#".repeat(assetPrecision)))
df.roundingMode = RoundingMode.CEILING df.roundingMode = RoundingMode.CEILING
df.decimalFormatSymbols = DecimalFormatSymbols(Locale.getDefault()) df.decimalFormatSymbols = DecimalFormatSymbols(Locale.getDefault())
@ -309,8 +342,8 @@ class ReceiveTransactionFragment : ConnectedFragment() {
val txtAccount = getString(R.string.template__to, account) val txtAccount = getString(R.string.template__to, account)
tvPleasePay.text = txtAmount binding.tvPleasePay.text = txtAmount
tvTo.text = txtAccount binding.tvTo.text = txtAccount
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
@ -332,18 +365,28 @@ class ReceiveTransactionFragment : ConnectedFragment() {
} }
private fun verifyStoragePermission() { private fun verifyStoragePermission() {
if (ContextCompat.checkSelfPermission(activity!!, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) if (ContextCompat.checkSelfPermission(
!= PackageManager.PERMISSION_GRANTED) { activity!!,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE
)
!= PackageManager.PERMISSION_GRANTED
) {
// Permission is not already granted // Permission is not already granted
requestPermissions(arrayOf(android.Manifest.permission.WRITE_EXTERNAL_STORAGE), requestPermissions(
REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION) arrayOf(android.Manifest.permission.WRITE_EXTERNAL_STORAGE),
REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION
)
} else { } else {
// Permission is already granted // Permission is already granted
shareQRScreenshot() shareQRScreenshot()
} }
} }
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults) super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION) { if (requestCode == REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION) {
@ -367,13 +410,13 @@ class ReceiveTransactionFragment : ConnectedFragment() {
return return
// Get Screenshot // Get Screenshot
val screenshot = Helper.loadBitmapFromView(container) val screenshot = Helper.loadBitmapFromView(binding.container)
val imageUri = Helper.saveTemporalBitmap(context!!, screenshot) val imageUri = Helper.saveTemporalBitmap(context!!, screenshot)
// Prepare information for share intent // Prepare information for share intent
val subject = getString(R.string.msg__invoice_subject, mUserAccount?.name) val subject = getString(R.string.msg__invoice_subject, mUserAccount?.name)
val content = tvPleasePay.text.toString() + "\n" + val content = binding.tvPleasePay.text.toString() + "\n" +
tvTo.text.toString() binding.tvTo.text.toString()
// Create share intent and call it // Create share intent and call it
val shareIntent = Intent() val shareIntent = Intent()

View file

@ -27,6 +27,7 @@ import com.jakewharton.rxbinding3.widget.textChanges
import cy.agorise.bitsybitshareswallet.R import cy.agorise.bitsybitshareswallet.R
import cy.agorise.bitsybitshareswallet.adapters.BalancesDetailsAdapter import cy.agorise.bitsybitshareswallet.adapters.BalancesDetailsAdapter
import cy.agorise.bitsybitshareswallet.database.joins.BalanceDetail import cy.agorise.bitsybitshareswallet.database.joins.BalanceDetail
import cy.agorise.bitsybitshareswallet.databinding.FragmentSendTransactionBinding
import cy.agorise.bitsybitshareswallet.utils.* import cy.agorise.bitsybitshareswallet.utils.*
import cy.agorise.bitsybitshareswallet.viewmodels.BalanceDetailViewModel import cy.agorise.bitsybitshareswallet.viewmodels.BalanceDetailViewModel
import cy.agorise.bitsybitshareswallet.viewmodels.SendTransactionViewModel import cy.agorise.bitsybitshareswallet.viewmodels.SendTransactionViewModel
@ -43,7 +44,6 @@ import cy.agorise.graphenej.models.JsonRpcResponse
import cy.agorise.graphenej.operations.TransferOperation import cy.agorise.graphenej.operations.TransferOperation
import cy.agorise.graphenej.operations.TransferOperationBuilder import cy.agorise.graphenej.operations.TransferOperationBuilder
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.synthetic.main.fragment_send_transaction.*
import me.dm7.barcodescanner.zxing.ZXingScannerView import me.dm7.barcodescanner.zxing.ZXingScannerView
import org.bitcoinj.core.DumpedPrivateKey import org.bitcoinj.core.DumpedPrivateKey
import org.bitcoinj.core.ECKey import org.bitcoinj.core.ECKey
@ -74,6 +74,9 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand
private const val ACTION_SEND_TRANSFER = 1 private const val ACTION_SEND_TRANSFER = 1
} }
private var _binding: FragmentSendTransactionBinding? = null
private val binding get() = _binding!!
// Navigation AAC Safe Args // Navigation AAC Safe Args
private val args: SendTransactionFragmentArgs by navArgs() private val args: SendTransactionFragmentArgs by navArgs()
@ -112,7 +115,11 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand
/** This is one of the recipient account's public key, it will be used for memo encoding */ /** This is one of the recipient account's public key, it will be used for memo encoding */
private var destinationPublicKey: PublicKey? = null private var destinationPublicKey: PublicKey? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
setHasOptionsMenu(true) setHasOptionsMenu(true)
val nightMode = PreferenceManager.getDefaultSharedPreferences(context) val nightMode = PreferenceManager.getDefaultSharedPreferences(context)
@ -125,13 +132,21 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand
// Sets the status and navigation bars background color to a dark red or just dark // Sets the status and navigation bars background color to a dark red or just dark
val window = activity?.window val window = activity?.window
context?.let { context -> context?.let { context ->
val statusBarColor = ContextCompat.getColor(context, val statusBarColor = ContextCompat.getColor(
if (!nightMode) R.color.colorSendDark else R.color.colorStatusBarDark) context,
if (!nightMode) R.color.colorSendDark else R.color.colorStatusBarDark
)
window?.statusBarColor = statusBarColor window?.statusBarColor = statusBarColor
window?.navigationBarColor = statusBarColor window?.navigationBarColor = statusBarColor
} }
return inflater.inflate(R.layout.fragment_send_transaction, container, false) _binding = FragmentSendTransactionBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -147,7 +162,7 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand
mUserAccount = UserAccount(userId) mUserAccount = UserAccount(userId)
// Configure ViewModel // Configure ViewModel
mViewModel= ViewModelProviders.of(this).get(SendTransactionViewModel::class.java) mViewModel = ViewModelProviders.of(this).get(SendTransactionViewModel::class.java)
mViewModel.getWIF(userId, AuthorityType.ACTIVE.ordinal).observe(this, mViewModel.getWIF(userId, AuthorityType.ACTIVE.ordinal).observe(this,
Observer<String> { encryptedWIF -> Observer<String> { encryptedWIF ->
@ -168,37 +183,43 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand
}, 500) }, 500)
} }
fabOpenCamera.setOnClickListener { if (isCameraPreviewVisible) stopCameraPreview() else verifyCameraPermission() } binding.fabOpenCamera.setOnClickListener { if (isCameraPreviewVisible) stopCameraPreview() else verifyCameraPermission() }
// Configure BalanceDetailViewModel to show the current balances // Configure BalanceDetailViewModel to show the current balances
mBalanceDetailViewModel = ViewModelProviders.of(this).get(BalanceDetailViewModel::class.java) mBalanceDetailViewModel =
ViewModelProviders.of(this).get(BalanceDetailViewModel::class.java)
mBalanceDetailViewModel.getAll().observe(this, Observer<List<BalanceDetail>> { balancesDetails -> mBalanceDetailViewModel.getAll()
.observe(this, Observer<List<BalanceDetail>> { balancesDetails ->
mBalancesDetails.clear() mBalancesDetails.clear()
mBalancesDetails.addAll(balancesDetails) mBalancesDetails.addAll(balancesDetails)
mBalancesDetails.sortWith( mBalancesDetails.sortWith(
Comparator { a, b -> a.toString().compareTo(b.toString(), true) } Comparator { a, b -> a.toString().compareTo(b.toString(), true) }
) )
mBalancesDetailsAdapter = BalancesDetailsAdapter(context!!, android.R.layout.simple_spinner_item, mBalancesDetails) mBalancesDetailsAdapter = BalancesDetailsAdapter(
spAsset.adapter = mBalancesDetailsAdapter context!!,
android.R.layout.simple_spinner_item,
mBalancesDetails
)
binding.spAsset.adapter = mBalancesDetailsAdapter
// Try to select the selectedAssetSymbol // Try to select the selectedAssetSymbol
for (i in 0 until mBalancesDetailsAdapter!!.count) { for (i in 0 until mBalancesDetailsAdapter!!.count) {
if (mBalancesDetailsAdapter!!.getItem(i)!!.symbol == selectedAssetSymbol) { if (mBalancesDetailsAdapter!!.getItem(i)!!.symbol == selectedAssetSymbol) {
spAsset.setSelection(i) binding.spAsset.setSelection(i)
break break
} }
} }
}) })
spAsset.onItemSelectedListener = assetItemSelectedListener binding.spAsset.onItemSelectedListener = assetItemSelectedListener
fabSendTransaction.setOnClickListener { verifySecurityLockSendTransfer() } binding.fabSendTransaction.setOnClickListener { verifySecurityLockSendTransfer() }
fabSendTransaction.disable(R.color.lightGray) binding.fabSendTransaction.disable(R.color.lightGray)
// Use RxJava Debounce to avoid making calls to the NetworkService on every text change event // Use RxJava Debounce to avoid making calls to the NetworkService on every text change event
mDisposables.add( mDisposables.add(
tietTo.textChanges() binding.tietTo.textChanges()
.skipInitialValue() .skipInitialValue()
.debounce(500, TimeUnit.MILLISECONDS) .debounce(500, TimeUnit.MILLISECONDS)
.map { it.toString().trim() } .map { it.toString().trim() }
@ -209,7 +230,7 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand
// Use RxJava Debounce to update the Amount error only after the user stops writing for > 500 ms // Use RxJava Debounce to update the Amount error only after the user stops writing for > 500 ms
mDisposables.add( mDisposables.add(
tietAmount.textChanges() binding.tietAmount.textChanges()
.skipInitialValue() .skipInitialValue()
.debounce(500, TimeUnit.MILLISECONDS) .debounce(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
@ -218,13 +239,13 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand
// Use RxJava Debounce to update the Memo error, to make sure it has the correct length // Use RxJava Debounce to update the Memo error, to make sure it has the correct length
mDisposables.add( mDisposables.add(
tietMemo.textChanges() binding.tietMemo.textChanges()
.skipInitialValue() .skipInitialValue()
.debounce(500, TimeUnit.MILLISECONDS) .debounce(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.map { it.toString().trim() } .map { it.toString().trim() }
.subscribe { .subscribe {
isMemoCorrect = it.length <= tilMemo.counterMaxLength isMemoCorrect = it.length <= binding.tilMemo.counterMaxLength
enableDisableSendFAB() enableDisableSendFAB()
} }
) )
@ -243,8 +264,8 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand
/** Handles the selection of items in the Asset spinner, to keep track of the selectedAssetSymbol and show the /** Handles the selection of items in the Asset spinner, to keep track of the selectedAssetSymbol and show the
* current user's balance of the selected asset. */ * current user's balance of the selected asset. */
private val assetItemSelectedListener = object : AdapterView.OnItemSelectedListener{ private val assetItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) { } override fun onNothingSelected(parent: AdapterView<*>?) {}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
val balance = mBalancesDetailsAdapter!!.getItem(position)!! val balance = mBalancesDetailsAdapter!!.getItem(position)!!
@ -252,8 +273,12 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand
val amount = balance.amount.toDouble() / Math.pow(10.0, balance.precision.toDouble()) val amount = balance.amount.toDouble() / Math.pow(10.0, balance.precision.toDouble())
tvAvailableAssetAmount.text = binding.tvAvailableAssetAmount.text =
String.format("%." + Math.min(balance.precision, 8) + "f %s", amount, balance.toString()) String.format(
"%." + Math.min(balance.precision, 8) + "f %s",
amount,
balance.toString()
)
validateAmount() validateAmount()
} }
@ -285,12 +310,12 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand
if (result is AccountProperties) { if (result is AccountProperties) {
mSelectedUserAccount = UserAccount(result.id, result.name) mSelectedUserAccount = UserAccount(result.id, result.name)
destinationPublicKey = result.active.keyAuths.keys.iterator().next() destinationPublicKey = result.active.keyAuths.keys.iterator().next()
tilTo.isErrorEnabled = false binding.tilTo.isErrorEnabled = false
isToAccountCorrect = true isToAccountCorrect = true
} else { } else {
mSelectedUserAccount = null mSelectedUserAccount = null
destinationPublicKey = null destinationPublicKey = null
tilTo.error = getString(R.string.error__invalid_account) binding.tilTo.error = getString(R.string.error__invalid_account)
isToAccountCorrect = false isToAccountCorrect = false
} }
@ -320,7 +345,10 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand
val feeAsset = Asset(Constants.CORE_ASSET) val feeAsset = Asset(Constants.CORE_ASSET)
val id = mNetworkService?.sendMessage(GetRequiredFees(transaction!!, feeAsset), GetRequiredFees.REQUIRED_API) val id = mNetworkService?.sendMessage(
GetRequiredFees(transaction!!, feeAsset),
GetRequiredFees.REQUIRED_API
)
if (id != null) responseMap.append(id, RESPONSE_GET_REQUIRED_FEES) if (id != null) responseMap.append(id, RESPONSE_GET_REQUIRED_FEES)
} else { } else {
context?.toast(getString(R.string.msg__transaction_not_sent)) context?.toast(getString(R.string.msg__transaction_not_sent))
@ -334,7 +362,10 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand
Log.d(TAG, "GetRequiredFees: " + transaction.toString()) Log.d(TAG, "GetRequiredFees: " + transaction.toString())
transaction!!.setFees(result as List<AssetAmount>) // TODO find how to remove this warning transaction!!.setFees(result as List<AssetAmount>) // TODO find how to remove this warning
val id = mNetworkService?.sendMessage(BroadcastTransaction(transaction), BroadcastTransaction.REQUIRED_API) val id = mNetworkService?.sendMessage(
BroadcastTransaction(transaction),
BroadcastTransaction.REQUIRED_API
)
if (id != null) responseMap.append(id, RESPONSE_BROADCAST_TRANSACTION) if (id != null) responseMap.append(id, RESPONSE_BROADCAST_TRANSACTION)
} else { } else {
context?.toast(getString(R.string.msg__transaction_not_sent)) context?.toast(getString(R.string.msg__transaction_not_sent))
@ -357,9 +388,13 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand
/** Verifies if the user has already granted the Camera permission, if not the asks for it */ /** Verifies if the user has already granted the Camera permission, if not the asks for it */
private fun verifyCameraPermission() { private fun verifyCameraPermission() {
if (ContextCompat.checkSelfPermission(activity!!, android.Manifest.permission.CAMERA) if (ContextCompat.checkSelfPermission(activity!!, android.Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) { != PackageManager.PERMISSION_GRANTED
) {
// Permission is not already granted // Permission is not already granted
requestPermissions(arrayOf(android.Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION) requestPermissions(
arrayOf(android.Manifest.permission.CAMERA),
REQUEST_CAMERA_PERMISSION
)
} else { } else {
// Permission is already granted // Permission is already granted
startCameraPreview() startCameraPreview()
@ -367,7 +402,11 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand
} }
/** Handles the result from the camera permission request */ /** Handles the result from the camera permission request */
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults) super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_CAMERA_PERMISSION) { if (requestCode == REQUEST_CAMERA_PERMISSION) {
@ -381,27 +420,27 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand
} }
private fun startCameraPreview() { private fun startCameraPreview() {
cameraPreview.visibility = View.VISIBLE binding.cameraPreview.visibility = View.VISIBLE
fabOpenCamera.setImageResource(R.drawable.ic_close) binding.fabOpenCamera.setImageResource(R.drawable.ic_close)
isCameraPreviewVisible = true isCameraPreviewVisible = true
// Configure QR scanner // Configure QR scanner
cameraPreview.setFormats(listOf(BarcodeFormat.QR_CODE)) binding.cameraPreview.setFormats(listOf(BarcodeFormat.QR_CODE))
cameraPreview.setAspectTolerance(0.5f) binding.cameraPreview.setAspectTolerance(0.5f)
cameraPreview.setAutoFocus(true) binding.cameraPreview.setAutoFocus(true)
cameraPreview.setLaserColor(R.color.colorSecondary) binding.cameraPreview.setLaserColor(R.color.colorSecondary)
cameraPreview.setMaskColor(R.color.colorSecondary) binding.cameraPreview.setMaskColor(R.color.colorSecondary)
cameraPreview.setResultHandler(this) binding.cameraPreview.setResultHandler(this)
cameraPreview.startCamera() binding.cameraPreview.startCamera()
cameraPreview.scrollY = holderCamera.width / 6 binding.cameraPreview.scrollY = binding.holderCamera.width / 6
} }
private fun stopCameraPreview() { private fun stopCameraPreview() {
cameraPreview.visibility = View.INVISIBLE binding.cameraPreview.visibility = View.INVISIBLE
fabOpenCamera.setImageResource(R.drawable.ic_camera) binding.fabOpenCamera.setImageResource(R.drawable.ic_camera)
isCameraPreviewVisible = false isCameraPreviewVisible = false
cameraPreview.stopCamera() binding.cameraPreview.stopCamera()
} }
/** Handles the result of the QR code read from the camera **/ /** Handles the result of the QR code read from the camera **/
@ -417,12 +456,12 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand
Log.d(TAG, "QR Code read: " + invoice.toJsonString()) Log.d(TAG, "QR Code read: " + invoice.toJsonString())
tietTo.setText(invoice.to) binding.tietTo.setText(invoice.to)
if (invoice.memo != null) { if (invoice.memo != null) {
tietMemo.setText(invoice.memo) binding.tietMemo.setText(invoice.memo)
if (invoice.memo.startsWith("PP")) if (invoice.memo.startsWith("PP"))
tietMemo.isEnabled = false binding.tietMemo.isEnabled = false
} }
var balanceDetail: BalanceDetail? = null var balanceDetail: BalanceDetail? = null
@ -432,8 +471,9 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand
if (mBalancesDetailsAdapter?.getItem(i)?.symbol == invoice.currency.toUpperCase() || if (mBalancesDetailsAdapter?.getItem(i)?.symbol == invoice.currency.toUpperCase() ||
(invoice.currency.startsWith("bit", true) && (invoice.currency.startsWith("bit", true) &&
invoice.currency.replaceFirst("bit", "").toUpperCase() == invoice.currency.replaceFirst("bit", "").toUpperCase() ==
mBalancesDetailsAdapter?.getItem(i)?.symbol)) { mBalancesDetailsAdapter?.getItem(i)?.symbol)
spAsset.setSelection(i) ) {
binding.spAsset.setSelection(i)
balanceDetail = mBalancesDetailsAdapter?.getItem(i) balanceDetail = mBalancesDetailsAdapter?.getItem(i)
break break
} }
@ -442,8 +482,11 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand
// If the user does not own any of the requested asset then show a SnackBar to explain the issue and // If the user does not own any of the requested asset then show a SnackBar to explain the issue and
// return early to avoid filling the asset field // return early to avoid filling the asset field
if (balanceDetail == null) { if (balanceDetail == null) {
Snackbar.make(rootView, getString(R.string.error__you_dont_own_asset, invoice.currency.toUpperCase()), Snackbar.make(
Snackbar.LENGTH_INDEFINITE).setAction(android.R.string.ok) { }.show() binding.rootView,
getString(R.string.error__you_dont_own_asset, invoice.currency.toUpperCase()),
Snackbar.LENGTH_INDEFINITE
).setAction(android.R.string.ok) { }.show()
return return
} }
@ -457,12 +500,12 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand
val df = DecimalFormat("####." + "#".repeat(balanceDetail.precision)) val df = DecimalFormat("####." + "#".repeat(balanceDetail.precision))
df.roundingMode = RoundingMode.CEILING df.roundingMode = RoundingMode.CEILING
df.decimalFormatSymbols = DecimalFormatSymbols(Locale.getDefault()) df.decimalFormatSymbols = DecimalFormatSymbols(Locale.getDefault())
tietAmount.setText(df.format(amount)) binding.tietAmount.setText(df.format(amount))
} else { } else {
tietAmount.setText("") binding.tietAmount.setText("")
} }
}catch (e: Exception) { } catch (e: Exception) {
Log.d(TAG, "Invoice error: " + e.message) Log.d(TAG, "Invoice error: " + e.message)
} }
} }
@ -473,15 +516,19 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand
*/ */
private fun validateAccount(accountName: String) { private fun validateAccount(accountName: String) {
isToAccountCorrect = false isToAccountCorrect = false
val id = mNetworkService?.sendMessage(GetAccountByName(accountName), GetAccountByName.REQUIRED_API) val id = mNetworkService?.sendMessage(
GetAccountByName(accountName),
GetAccountByName.REQUIRED_API
)
if (id != null) responseMap.append(id, RESPONSE_GET_ACCOUNT_BY_NAME) if (id != null) responseMap.append(id, RESPONSE_GET_ACCOUNT_BY_NAME)
} }
private fun validateAmount() { private fun validateAmount() {
val txtAmount = tietAmount.text.toString().replace(",", ".") val txtAmount = binding.tietAmount.text.toString().replace(",", ".")
if (mBalancesDetailsAdapter?.isEmpty != false) return if (mBalancesDetailsAdapter?.isEmpty != false) return
val balance = mBalancesDetailsAdapter?.getItem(spAsset.selectedItemPosition) ?: return val balance =
mBalancesDetailsAdapter?.getItem(binding.spAsset.selectedItemPosition) ?: return
val currentAmount = balance.amount.toDouble() / Math.pow(10.0, balance.precision.toDouble()) val currentAmount = balance.amount.toDouble() / Math.pow(10.0, balance.precision.toDouble())
val amount: Double = try { val amount: Double = try {
@ -492,15 +539,15 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand
when { when {
currentAmount < amount -> { currentAmount < amount -> {
tilAmount.error = getString(R.string.error__not_enough_funds) binding.tilAmount.error = getString(R.string.error__not_enough_funds)
isAmountCorrect = false isAmountCorrect = false
} }
amount == 0.0 -> { amount == 0.0 -> {
tilAmount.isErrorEnabled = false binding.tilAmount.isErrorEnabled = false
isAmountCorrect = false isAmountCorrect = false
} }
else -> { else -> {
tilAmount.isErrorEnabled = false binding.tilAmount.isErrorEnabled = false
isAmountCorrect = true isAmountCorrect = true
} }
} }
@ -510,11 +557,11 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand
private fun enableDisableSendFAB() { private fun enableDisableSendFAB() {
if (isToAccountCorrect && isAmountCorrect && isMemoCorrect) { if (isToAccountCorrect && isAmountCorrect && isMemoCorrect) {
fabSendTransaction.enable(R.color.colorSend) binding.fabSendTransaction.enable(R.color.colorSend)
vSend.setBackgroundResource(R.drawable.send_fab_background) binding.vSend.setBackgroundResource(R.drawable.send_fab_background)
} else { } else {
fabSendTransaction.disable(R.color.lightGray) binding.fabSendTransaction.disable(R.color.lightGray)
vSend.setBackgroundResource(R.drawable.send_fab_background_disabled) binding.vSend.setBackgroundResource(R.drawable.send_fab_background_disabled)
} }
} }
@ -528,8 +575,10 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand
// Args used for both PIN and Pattern options // Args used for both PIN and Pattern options
val args = Bundle() val args = Bundle()
args.putInt(BaseSecurityLockDialog.KEY_STEP_SECURITY_LOCK, args.putInt(
BaseSecurityLockDialog.STEP_SECURITY_LOCK_VERIFY) BaseSecurityLockDialog.KEY_STEP_SECURITY_LOCK,
BaseSecurityLockDialog.STEP_SECURITY_LOCK_VERIFY
)
args.putInt(BaseSecurityLockDialog.KEY_ACTION_IDENTIFIER, ACTION_SEND_TRANSFER) args.putInt(BaseSecurityLockDialog.KEY_ACTION_IDENTIFIER, ACTION_SEND_TRANSFER)
when (securityLockSelected) { when (securityLockSelected) {
@ -556,15 +605,16 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand
} }
} }
override fun onPINPatternChanged() { /* Do nothing */ } override fun onPINPatternChanged() { /* Do nothing */
}
/** Starts the Send Transfer operation procedure, creating a [TransferOperation] and sending a call to the /** Starts the Send Transfer operation procedure, creating a [TransferOperation] and sending a call to the
* NetworkService to obtain the [DynamicGlobalProperties] object needed to successfully send a Transfer */ * NetworkService to obtain the [DynamicGlobalProperties] object needed to successfully send a Transfer */
private fun startSendTransferOperation() { private fun startSendTransferOperation() {
// Create TransferOperation // Create TransferOperation
if (mNetworkService?.isConnected == true) { if (mNetworkService?.isConnected == true) {
val balance = mBalancesDetailsAdapter!!.getItem(spAsset.selectedItemPosition)!! val balance = mBalancesDetailsAdapter!!.getItem(binding.spAsset.selectedItemPosition)!!
val amount = (tietAmount.text.toString().replace(",", ".").toDouble() val amount = (binding.tietAmount.text.toString().replace(",", ".").toDouble()
* Math.pow(10.0, balance.precision.toDouble())).toLong() * Math.pow(10.0, balance.precision.toDouble())).toLong()
val transferAmount = AssetAmount(UnsignedLong.valueOf(amount), Asset(balance.id)) val transferAmount = AssetAmount(UnsignedLong.valueOf(amount), Asset(balance.id))
@ -574,13 +624,16 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand
.setDestination(mSelectedUserAccount) .setDestination(mSelectedUserAccount)
.setTransferAmount(transferAmount) .setTransferAmount(transferAmount)
val privateKey = ECKey.fromPrivate(DumpedPrivateKey.fromBase58(null, wifKey).key.privKeyBytes) val privateKey =
ECKey.fromPrivate(DumpedPrivateKey.fromBase58(null, wifKey).key.privKeyBytes)
// Add memo if it is not empty // Add memo if it is not empty
val memoMsg = tietMemo.text.toString() val memoMsg = binding.tietMemo.text.toString()
if (memoMsg.isNotEmpty()) { if (memoMsg.isNotEmpty()) {
val nonce = Math.abs(SecureRandomGenerator.getSecureRandom().nextLong()).toBigInteger() val nonce =
val encryptedMemo = Memo.encryptMessage(privateKey, destinationPublicKey!!, nonce, memoMsg) Math.abs(SecureRandomGenerator.getSecureRandom().nextLong()).toBigInteger()
val encryptedMemo =
Memo.encryptMessage(privateKey, destinationPublicKey!!, nonce, memoMsg)
val from = Address(ECKey.fromPublicOnly(privateKey.pubKey)) val from = Address(ECKey.fromPublicOnly(privateKey.pubKey))
val to = Address(destinationPublicKey!!.key) val to = Address(destinationPublicKey!!.key)
val memo = Memo(from, to, nonce, encryptedMemo) val memo = Memo(from, to, nonce, encryptedMemo)
@ -601,9 +654,11 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand
transaction = Transaction(privateKey, null, operations) transaction = Transaction(privateKey, null, operations)
// Start the send transaction procedure which includes a series of calls // Start the send transaction procedure which includes a series of calls
val id = mNetworkService?.sendMessage(GetDynamicGlobalProperties(), val id = mNetworkService?.sendMessage(
GetDynamicGlobalProperties.REQUIRED_API) GetDynamicGlobalProperties(),
if (id != null ) responseMap.append(id, RESPONSE_GET_DYNAMIC_GLOBAL_PROPERTIES) GetDynamicGlobalProperties.REQUIRED_API
)
if (id != null) responseMap.append(id, RESPONSE_GET_DYNAMIC_GLOBAL_PROPERTIES)
} else } else
Log.d(TAG, "Network Service is not connected") Log.d(TAG, "Network Service is not connected")
} }
@ -634,8 +689,12 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand
return null return null
// Verify that the current Asset is either BTS or a SmartCoin // Verify that the current Asset is either BTS or a SmartCoin
if (Constants.assetsWhichSendFeeToAgorise.contains(transferOperation.assetAmount?.asset?.objectId ?: "")) { if (Constants.assetsWhichSendFeeToAgorise.contains(
val fee = transferOperation.assetAmount?.multiplyBy(Constants.FEE_PERCENTAGE) ?: return null transferOperation.assetAmount?.asset?.objectId ?: ""
)
) {
val fee =
transferOperation.assetAmount?.multiplyBy(Constants.FEE_PERCENTAGE) ?: return null
return TransferOperationBuilder() return TransferOperationBuilder()
.setSource(mUserAccount) .setSource(mUserAccount)

View file

@ -1,6 +1,6 @@
package cy.agorise.bitsybitshareswallet.fragments package cy.agorise.bitsybitshareswallet.fragments
import android.content.* import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.preference.PreferenceManager import android.preference.PreferenceManager
@ -10,6 +10,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.collection.LongSparseArray import androidx.collection.LongSparseArray
import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
@ -22,6 +23,7 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics
import cy.agorise.bitsybitshareswallet.BuildConfig import cy.agorise.bitsybitshareswallet.BuildConfig
import cy.agorise.bitsybitshareswallet.R import cy.agorise.bitsybitshareswallet.R
import cy.agorise.bitsybitshareswallet.adapters.FullNodesAdapter import cy.agorise.bitsybitshareswallet.adapters.FullNodesAdapter
import cy.agorise.bitsybitshareswallet.databinding.FragmentSettingsBinding
import cy.agorise.bitsybitshareswallet.repositories.AuthorityRepository import cy.agorise.bitsybitshareswallet.repositories.AuthorityRepository
import cy.agorise.bitsybitshareswallet.utils.Constants import cy.agorise.bitsybitshareswallet.utils.Constants
import cy.agorise.bitsybitshareswallet.utils.CryptoUtils import cy.agorise.bitsybitshareswallet.utils.CryptoUtils
@ -36,7 +38,6 @@ import cy.agorise.graphenej.models.JsonRpcResponse
import cy.agorise.graphenej.operations.AccountUpgradeOperationBuilder import cy.agorise.graphenej.operations.AccountUpgradeOperationBuilder
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import kotlinx.android.synthetic.main.fragment_settings.*
import org.bitcoinj.core.DumpedPrivateKey import org.bitcoinj.core.DumpedPrivateKey
import org.bitcoinj.core.ECKey import org.bitcoinj.core.ECKey
import java.text.NumberFormat import java.text.NumberFormat
@ -59,6 +60,9 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter
private const val RESPONSE_BROADCAST_TRANSACTION = 3 private const val RESPONSE_BROADCAST_TRANSACTION = 3
} }
private var _binding: FragmentSettingsBinding? = null
private val binding get() = _binding!!
private lateinit var mViewModel: SettingsFragmentViewModel private lateinit var mViewModel: SettingsFragmentViewModel
private var mUserAccount: UserAccount? = null private var mUserAccount: UserAccount? = null
@ -82,7 +86,11 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter
private val mHandler = Handler() private val mHandler = Handler()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
setHasOptionsMenu(true) setHasOptionsMenu(true)
val nightMode = PreferenceManager.getDefaultSharedPreferences(context) val nightMode = PreferenceManager.getDefaultSharedPreferences(context)
@ -92,7 +100,13 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter
val toolbar: Toolbar? = activity?.findViewById(R.id.toolbar) val toolbar: Toolbar? = activity?.findViewById(R.id.toolbar)
toolbar?.setBackgroundResource(if (!nightMode) R.color.colorPrimary else R.color.colorToolbarDark) toolbar?.setBackgroundResource(if (!nightMode) R.color.colorPrimary else R.color.colorToolbarDark)
return inflater.inflate(R.layout.fragment_settings, container, false) _binding = FragmentSettingsBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -105,13 +119,14 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter
.getString(Constants.KEY_CURRENT_ACCOUNT_ID, "") ?: "" .getString(Constants.KEY_CURRENT_ACCOUNT_ID, "") ?: ""
// Configure ViewModel // Configure ViewModel
mViewModel= ViewModelProviders.of(this).get(SettingsFragmentViewModel::class.java) mViewModel = ViewModelProviders.of(this).get(SettingsFragmentViewModel::class.java)
mViewModel.getUserAccount(userId).observe(this, mViewModel.getUserAccount(userId).observe(this,
androidx.lifecycle.Observer<cy.agorise.bitsybitshareswallet.database.entities.UserAccount>{ userAccount -> androidx.lifecycle.Observer<cy.agorise.bitsybitshareswallet.database.entities.UserAccount> { userAccount ->
if (userAccount != null) { if (userAccount != null) {
mUserAccount = UserAccount(userAccount.id, userAccount.name) mUserAccount = UserAccount(userAccount.id, userAccount.name)
btnUpgradeToLTM.isEnabled = !userAccount.isLtm // Disable button if already LTM binding.btnUpgradeToLTM.isEnabled =
!userAccount.isLtm // Disable button if already LTM
} }
}) })
@ -121,7 +136,10 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter
try { try {
privateKey = CryptoUtils.decrypt(it, encryptedWIF) privateKey = CryptoUtils.decrypt(it, encryptedWIF)
} catch (e: AEADBadTagException) { } catch (e: AEADBadTagException) {
Log.e(TAG, "AEADBadTagException. Class: " + e.javaClass + ", Msg: " + e.message) Log.e(
TAG,
"AEADBadTagException. Class: " + e.javaClass + ", Msg: " + e.message
)
} catch (e: IllegalStateException) { } catch (e: IllegalStateException) {
crashlytics.recordException(e) crashlytics.recordException(e)
} }
@ -132,7 +150,7 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter
initNightModeSwitch() initNightModeSwitch()
tvNetworkStatus.setOnClickListener { v -> showNodesDialog(v) } binding.tvNetworkStatus.setOnClickListener { v -> showNodesDialog(v) }
// Obtain the current Security Lock Option selected and display it in the screen // Obtain the current Security Lock Option selected and display it in the screen
val securityLockSelected = PreferenceManager.getDefaultSharedPreferences(context) val securityLockSelected = PreferenceManager.getDefaultSharedPreferences(context)
@ -142,12 +160,13 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter
// 1 -> PIN // 1 -> PIN
// 2 -> Pattern // 2 -> Pattern
tvSecurityLockSelected.text = resources.getStringArray(R.array.security_lock_options)[securityLockSelected] binding.tvSecurityLockSelected.text =
resources.getStringArray(R.array.security_lock_options)[securityLockSelected]
tvSecurityLock.setOnClickListener { onSecurityLockTextSelected() } binding.tvSecurityLock.setOnClickListener { onSecurityLockTextSelected() }
tvSecurityLockSelected.setOnClickListener { onSecurityLockTextSelected() } binding.tvSecurityLockSelected.setOnClickListener { onSecurityLockTextSelected() }
btnViewBrainKey.setOnClickListener { onShowBrainKeyButtonSelected() } binding.btnViewBrainKey.setOnClickListener { onShowBrainKeyButtonSelected() }
val lastAccountBackup = PreferenceManager.getDefaultSharedPreferences(context) val lastAccountBackup = PreferenceManager.getDefaultSharedPreferences(context)
.getLong(Constants.KEY_LAST_ACCOUNT_BACKUP, 0L) .getLong(Constants.KEY_LAST_ACCOUNT_BACKUP, 0L)
@ -155,11 +174,11 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter
val now = System.currentTimeMillis() val now = System.currentTimeMillis()
if (lastAccountBackup + Constants.ACCOUNT_BACKUP_PERIOD < now) if (lastAccountBackup + Constants.ACCOUNT_BACKUP_PERIOD < now)
tvBackupWarning.visibility = View.VISIBLE binding.tvBackupWarning.visibility = View.VISIBLE
btnUpgradeToLTM.setOnClickListener { onUpgradeToLTMButtonSelected() } binding.btnUpgradeToLTM.setOnClickListener { onUpgradeToLTMButtonSelected() }
btnRemoveAccount.setOnClickListener { onRemoveAccountButtonSelected() } binding.btnRemoveAccount.setOnClickListener { onRemoveAccountButtonSelected() }
} }
private fun showNodesDialog(v: View) { private fun showNodesDialog(v: View) {
@ -191,7 +210,13 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter
mNodesDialogLinearLayoutManager = LinearLayoutManager(v.context) mNodesDialogLinearLayoutManager = LinearLayoutManager(v.context)
mNodesDialog = MaterialDialog(v.context).show { mNodesDialog = MaterialDialog(v.context).show {
title(text = String.format("%s v%s", getString(R.string.app_name), BuildConfig.VERSION_NAME)) title(
text = String.format(
"%s v%s",
getString(R.string.app_name),
BuildConfig.VERSION_NAME
)
)
message(text = getString(R.string.title__bitshares_nodes_dialog, "-------")) message(text = getString(R.string.title__bitshares_nodes_dialog, "-------"))
customListAdapter(nodesAdapter as FullNodesAdapter, mNodesDialogLinearLayoutManager) customListAdapter(nodesAdapter as FullNodesAdapter, mNodesDialogLinearLayoutManager)
negativeButton(android.R.string.ok) negativeButton(android.R.string.ok)
@ -218,8 +243,12 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter
override fun handleJsonRpcResponse(response: JsonRpcResponse<*>) { override fun handleJsonRpcResponse(response: JsonRpcResponse<*>) {
if (responseMap.containsKey(response.id)) { if (responseMap.containsKey(response.id)) {
when (responseMap[response.id]) { when (responseMap[response.id]) {
RESPONSE_GET_DYNAMIC_GLOBAL_PROPERTIES_NODES -> handleDynamicGlobalPropertiesNodes(response.result) RESPONSE_GET_DYNAMIC_GLOBAL_PROPERTIES_NODES -> handleDynamicGlobalPropertiesNodes(
RESPONSE_GET_DYNAMIC_GLOBAL_PROPERTIES_LTM -> handleDynamicGlobalPropertiesLTM(response.result) response.result
)
RESPONSE_GET_DYNAMIC_GLOBAL_PROPERTIES_LTM -> handleDynamicGlobalPropertiesLTM(
response.result
)
RESPONSE_BROADCAST_TRANSACTION -> handleBroadcastTransaction(response) RESPONSE_BROADCAST_TRANSACTION -> handleBroadcastTransaction(response)
} }
responseMap.remove(response.id) responseMap.remove(response.id)
@ -238,13 +267,17 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter
} }
private fun showConnectedState() { private fun showConnectedState() {
tvNetworkStatus.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, binding.tvNetworkStatus.setCompoundDrawablesRelativeWithIntrinsicBounds(
resources.getDrawable(R.drawable.ic_connected, null), null) null, null,
ResourcesCompat.getDrawable(resources, R.drawable.ic_connected, null), null
)
} }
private fun showDisconnectedState() { private fun showDisconnectedState() {
tvNetworkStatus.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, binding.tvNetworkStatus.setCompoundDrawablesRelativeWithIntrinsicBounds(
resources.getDrawable(R.drawable.ic_disconnected, null), null) null, null,
ResourcesCompat.getDrawable(resources, R.drawable.ic_disconnected, null), null
)
} }
/** Handles the result of the [GetDynamicGlobalProperties] api call to obtain the current block number and update /** Handles the result of the [GetDynamicGlobalProperties] api call to obtain the current block number and update
@ -253,7 +286,9 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter
if (result is DynamicGlobalProperties) { if (result is DynamicGlobalProperties) {
if (mNodesDialog != null && mNodesDialog?.isShowing == true) { if (mNodesDialog != null && mNodesDialog?.isShowing == true) {
val blockNumber = NumberFormat.getInstance().format(result.head_block_number) val blockNumber = NumberFormat.getInstance().format(result.head_block_number)
mNodesDialog?.message(text = getString(R.string.title__bitshares_nodes_dialog, blockNumber)) mNodesDialog?.message(
text = getString(R.string.title__bitshares_nodes_dialog, blockNumber)
)
} }
} }
} }
@ -266,7 +301,10 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter
ltmTransaction?.blockData = BlockData(headBlockNumber, headBlockId, expirationTime) ltmTransaction?.blockData = BlockData(headBlockNumber, headBlockId, expirationTime)
val id = mNetworkService?.sendMessage(BroadcastTransaction(ltmTransaction), BroadcastTransaction.REQUIRED_API) val id = mNetworkService?.sendMessage(
BroadcastTransaction(ltmTransaction),
BroadcastTransaction.REQUIRED_API
)
if (id != null) responseMap.append(id, RESPONSE_BROADCAST_TRANSACTION) if (id != null) responseMap.append(id, RESPONSE_BROADCAST_TRANSACTION)
// TODO use an indicator to show that a transaction is in progress // TODO use an indicator to show that a transaction is in progress
@ -305,7 +343,10 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter
*/ */
private val mRequestDynamicGlobalPropertiesTask = object : Runnable { private val mRequestDynamicGlobalPropertiesTask = object : Runnable {
override fun run() { override fun run() {
val id = mNetworkService?.sendMessage(GetDynamicGlobalProperties(), GetDynamicGlobalProperties.REQUIRED_API) val id = mNetworkService?.sendMessage(
GetDynamicGlobalProperties(),
GetDynamicGlobalProperties.REQUIRED_API
)
if (id != null) responseMap.append(id, RESPONSE_GET_DYNAMIC_GLOBAL_PROPERTIES_NODES) if (id != null) responseMap.append(id, RESPONSE_GET_DYNAMIC_GLOBAL_PROPERTIES_NODES)
mHandler.postDelayed(this, Constants.BLOCK_PERIOD) mHandler.postDelayed(this, Constants.BLOCK_PERIOD)
@ -320,9 +361,9 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter
val autoCloseOn = PreferenceManager.getDefaultSharedPreferences(context) val autoCloseOn = PreferenceManager.getDefaultSharedPreferences(context)
.getBoolean(Constants.KEY_AUTO_CLOSE_ACTIVATED, true) .getBoolean(Constants.KEY_AUTO_CLOSE_ACTIVATED, true)
switchAutoClose.isChecked = autoCloseOn binding.switchAutoClose.isChecked = autoCloseOn
switchAutoClose.setOnCheckedChangeListener { buttonView, isChecked -> binding.switchAutoClose.setOnCheckedChangeListener { buttonView, isChecked ->
PreferenceManager.getDefaultSharedPreferences(buttonView.context).edit() PreferenceManager.getDefaultSharedPreferences(buttonView.context).edit()
.putBoolean(Constants.KEY_AUTO_CLOSE_ACTIVATED, isChecked).apply() .putBoolean(Constants.KEY_AUTO_CLOSE_ACTIVATED, isChecked).apply()
} }
@ -337,9 +378,9 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter
val nightModeOn = PreferenceManager.getDefaultSharedPreferences(context) val nightModeOn = PreferenceManager.getDefaultSharedPreferences(context)
.getBoolean(Constants.KEY_NIGHT_MODE_ACTIVATED, false) .getBoolean(Constants.KEY_NIGHT_MODE_ACTIVATED, false)
switchNightMode.isChecked = nightModeOn binding.switchNightMode.isChecked = nightModeOn
switchNightMode.setOnCheckedChangeListener { buttonView, isChecked -> binding.switchNightMode.setOnCheckedChangeListener { buttonView, isChecked ->
PreferenceManager.getDefaultSharedPreferences(buttonView.context).edit() PreferenceManager.getDefaultSharedPreferences(buttonView.context).edit()
.putBoolean(Constants.KEY_NIGHT_MODE_ACTIVATED, isChecked).apply() .putBoolean(Constants.KEY_NIGHT_MODE_ACTIVATED, isChecked).apply()
@ -371,8 +412,10 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter
// Args used for both PIN and Pattern options // Args used for both PIN and Pattern options
val args = Bundle() val args = Bundle()
args.putInt(BaseSecurityLockDialog.KEY_STEP_SECURITY_LOCK, args.putInt(
BaseSecurityLockDialog.STEP_SECURITY_LOCK_VERIFY) BaseSecurityLockDialog.KEY_STEP_SECURITY_LOCK,
BaseSecurityLockDialog.STEP_SECURITY_LOCK_VERIFY
)
args.putInt(BaseSecurityLockDialog.KEY_ACTION_IDENTIFIER, actionIdentifier) args.putInt(BaseSecurityLockDialog.KEY_ACTION_IDENTIFIER, actionIdentifier)
return when (securityLockSelected) { return when (securityLockSelected) {
@ -412,7 +455,8 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter
// 1 -> PIN // 1 -> PIN
// 2 -> Pattern // 2 -> Pattern
tvSecurityLockSelected.text = resources.getStringArray(R.array.security_lock_options)[securityLockSelected] binding.tvSecurityLockSelected.text =
resources.getStringArray(R.array.security_lock_options)[securityLockSelected]
} }
/** /**
@ -430,17 +474,23 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter
context?.let { context?.let {
MaterialDialog(it).show { MaterialDialog(it).show {
title(R.string.title__security_dialog) title(R.string.title__security_dialog)
listItemsSingleChoice(R.array.security_lock_options, initialSelection = securityLockSelected) {_, index, _ -> listItemsSingleChoice(
R.array.security_lock_options,
initialSelection = securityLockSelected
) { _, index, _ ->
// Args used for both PIN and Pattern options // Args used for both PIN and Pattern options
val args = Bundle() val args = Bundle()
args.putInt(BaseSecurityLockDialog.KEY_STEP_SECURITY_LOCK, args.putInt(
BaseSecurityLockDialog.STEP_SECURITY_LOCK_CREATE) BaseSecurityLockDialog.KEY_STEP_SECURITY_LOCK,
BaseSecurityLockDialog.STEP_SECURITY_LOCK_CREATE
)
args.putInt(BaseSecurityLockDialog.KEY_ACTION_IDENTIFIER, -1) args.putInt(BaseSecurityLockDialog.KEY_ACTION_IDENTIFIER, -1)
when (index) { when (index) {
0 -> { /* None */ 0 -> { /* None */
PreferenceManager.getDefaultSharedPreferences(context).edit() PreferenceManager.getDefaultSharedPreferences(context).edit()
.putInt(Constants.KEY_SECURITY_LOCK_SELECTED, 0).apply() // 0 -> None .putInt(Constants.KEY_SECURITY_LOCK_SELECTED, 0)
.apply() // 0 -> None
// Call this function to update the UI // Call this function to update the UI
onPINPatternChanged() onPINPatternChanged()
@ -491,7 +541,8 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.map { authority -> .map { authority ->
val plainBrainKey = CryptoUtils.decrypt(it, authority.encryptedBrainKey) val plainBrainKey = CryptoUtils.decrypt(it, authority.encryptedBrainKey)
val plainSequenceNumber = CryptoUtils.decrypt(it, authority.encryptedSequenceNumber) val plainSequenceNumber =
CryptoUtils.decrypt(it, authority.encryptedSequenceNumber)
val sequenceNumber = Integer.parseInt(plainSequenceNumber) val sequenceNumber = Integer.parseInt(plainSequenceNumber)
BrainKey(plainBrainKey, sequenceNumber) BrainKey(plainBrainKey, sequenceNumber)
} }
@ -518,7 +569,7 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter
val now = System.currentTimeMillis() val now = System.currentTimeMillis()
PreferenceManager.getDefaultSharedPreferences(it.context).edit() PreferenceManager.getDefaultSharedPreferences(it.context).edit()
.putLong(Constants.KEY_LAST_ACCOUNT_BACKUP, now).apply() .putLong(Constants.KEY_LAST_ACCOUNT_BACKUP, now).apply()
tvBackupWarning.visibility = View.GONE binding.tvBackupWarning.visibility = View.GONE
} }
dialog.show() dialog.show()
@ -541,11 +592,18 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter
operations.add(operation) operations.add(operation)
val currentPrivateKey = ECKey.fromPrivate( val currentPrivateKey = ECKey.fromPrivate(
DumpedPrivateKey.fromBase58(null, privateKey).key.privKeyBytes) DumpedPrivateKey.fromBase58(null, privateKey).key.privKeyBytes
)
ltmTransaction = Transaction(currentPrivateKey, null, operations) ltmTransaction = Transaction(currentPrivateKey, null, operations)
val id = mNetworkService?.sendMessage(GetDynamicGlobalProperties(), GetDynamicGlobalProperties.REQUIRED_API) val id = mNetworkService?.sendMessage(
if (id != null) responseMap.append(id, RESPONSE_GET_DYNAMIC_GLOBAL_PROPERTIES_LTM) GetDynamicGlobalProperties(),
GetDynamicGlobalProperties.REQUIRED_API
)
if (id != null) responseMap.append(
id,
RESPONSE_GET_DYNAMIC_GLOBAL_PROPERTIES_LTM
)
} }
} }
} }
@ -574,7 +632,8 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter
// Marks the license as agreed, so that it is not shown to the user again. // Marks the license as agreed, so that it is not shown to the user again.
pref.edit().putInt( pref.edit().putInt(
Constants.KEY_LAST_AGREED_LICENSE_VERSION, Constants.CURRENT_LICENSE_VERSION).apply() Constants.KEY_LAST_AGREED_LICENSE_VERSION, Constants.CURRENT_LICENSE_VERSION
).apply()
// Restarts the activity, which will restart the whole application since it uses a // Restarts the activity, which will restart the whole application since it uses a
// single activity architecture. // single activity architecture.

View file

@ -21,12 +21,12 @@ import com.jakewharton.rxbinding3.appcompat.queryTextChangeEvents
import cy.agorise.bitsybitshareswallet.R import cy.agorise.bitsybitshareswallet.R
import cy.agorise.bitsybitshareswallet.adapters.TransfersDetailsAdapter import cy.agorise.bitsybitshareswallet.adapters.TransfersDetailsAdapter
import cy.agorise.bitsybitshareswallet.database.joins.TransferDetail import cy.agorise.bitsybitshareswallet.database.joins.TransferDetail
import cy.agorise.bitsybitshareswallet.databinding.FragmentTransactionsBinding
import cy.agorise.bitsybitshareswallet.models.FilterOptions import cy.agorise.bitsybitshareswallet.models.FilterOptions
import cy.agorise.bitsybitshareswallet.utils.* import cy.agorise.bitsybitshareswallet.utils.*
import cy.agorise.bitsybitshareswallet.viewmodels.TransactionsViewModel import cy.agorise.bitsybitshareswallet.viewmodels.TransactionsViewModel
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import kotlinx.android.synthetic.main.fragment_transactions.*
import java.io.File import java.io.File
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -42,14 +42,27 @@ class TransactionsFragment : Fragment(), FilterOptionsDialog.OnFilterOptionsSele
private const val REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION = 100 private const val REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION = 100
} }
private var _binding: FragmentTransactionsBinding? = null
private val binding get() = _binding!!
private lateinit var mViewModel: TransactionsViewModel private lateinit var mViewModel: TransactionsViewModel
private var mDisposables = CompositeDisposable() private var mDisposables = CompositeDisposable()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
setHasOptionsMenu(true) setHasOptionsMenu(true)
return inflater.inflate(R.layout.fragment_transactions, container, false) _binding = FragmentTransactionsBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -62,8 +75,8 @@ class TransactionsFragment : Fragment(), FilterOptionsDialog.OnFilterOptionsSele
.getString(Constants.KEY_CURRENT_ACCOUNT_ID, "") ?: "" .getString(Constants.KEY_CURRENT_ACCOUNT_ID, "") ?: ""
val transfersDetailsAdapter = TransfersDetailsAdapter(context!!) val transfersDetailsAdapter = TransfersDetailsAdapter(context!!)
rvTransactions.adapter = transfersDetailsAdapter binding.rvTransactions.adapter = transfersDetailsAdapter
rvTransactions.layoutManager = LinearLayoutManager(context) binding.rvTransactions.layoutManager = LinearLayoutManager(context)
// Configure TransactionsViewModel to fetch the transaction history // Configure TransactionsViewModel to fetch the transaction history
mViewModel = ViewModelProviders.of(this).get(TransactionsViewModel::class.java) mViewModel = ViewModelProviders.of(this).get(TransactionsViewModel::class.java)
@ -71,11 +84,11 @@ class TransactionsFragment : Fragment(), FilterOptionsDialog.OnFilterOptionsSele
mViewModel.getFilteredTransactions(userId).observe(this, mViewModel.getFilteredTransactions(userId).observe(this,
Observer<List<TransferDetail>> { transactions -> Observer<List<TransferDetail>> { transactions ->
if (transactions.isEmpty()) { if (transactions.isEmpty()) {
rvTransactions.visibility = View.GONE binding.rvTransactions.visibility = View.GONE
tvEmpty.visibility = View.VISIBLE binding.tvEmpty.visibility = View.VISIBLE
} else { } else {
rvTransactions.visibility = View.VISIBLE binding.rvTransactions.visibility = View.VISIBLE
tvEmpty.visibility = View.GONE binding.tvEmpty.visibility = View.GONE
val shouldScrollUp = transactions.size - transfersDetailsAdapter.itemCount == 1 val shouldScrollUp = transactions.size - transfersDetailsAdapter.itemCount == 1
transfersDetailsAdapter.replaceAll(transactions) transfersDetailsAdapter.replaceAll(transactions)
@ -83,13 +96,13 @@ class TransactionsFragment : Fragment(), FilterOptionsDialog.OnFilterOptionsSele
// Scroll to the top only if the difference between old and new items is 1 // Scroll to the top only if the difference between old and new items is 1
// which most likely means a new transaction was received/sent. // which most likely means a new transaction was received/sent.
if (shouldScrollUp) if (shouldScrollUp)
rvTransactions.scrollToPosition(0) binding.rvTransactions.scrollToPosition(0)
} }
}) })
// Set custom touch listener to handle bounce/stretch effect // Set custom touch listener to handle bounce/stretch effect
val bounceTouchListener = BounceTouchListener(rvTransactions) val bounceTouchListener = BounceTouchListener(binding.rvTransactions)
rvTransactions.setOnTouchListener(bounceTouchListener) binding.rvTransactions.setOnTouchListener(bounceTouchListener)
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
@ -118,7 +131,10 @@ class TransactionsFragment : Fragment(), FilterOptionsDialog.OnFilterOptionsSele
R.id.menu_filter -> { R.id.menu_filter -> {
val filterOptionsDialog = FilterOptionsDialog() val filterOptionsDialog = FilterOptionsDialog()
val args = Bundle() val args = Bundle()
args.putParcelable(FilterOptionsDialog.KEY_FILTER_OPTIONS, mViewModel.getFilterOptions()) args.putParcelable(
FilterOptionsDialog.KEY_FILTER_OPTIONS,
mViewModel.getFilterOptions()
)
filterOptionsDialog.arguments = args filterOptionsDialog.arguments = args
filterOptionsDialog.show(childFragmentManager, "filter-options-tag") filterOptionsDialog.show(childFragmentManager, "filter-options-tag")
true true
@ -152,11 +168,17 @@ class TransactionsFragment : Fragment(), FilterOptionsDialog.OnFilterOptionsSele
/** Verifies that the storage permission has been granted before attempting to generate the export options */ /** Verifies that the storage permission has been granted before attempting to generate the export options */
private fun verifyStoragePermission() { private fun verifyStoragePermission() {
if (ContextCompat.checkSelfPermission(activity!!, Manifest.permission.WRITE_EXTERNAL_STORAGE) if (ContextCompat.checkSelfPermission(
!= PackageManager.PERMISSION_GRANTED) { activity!!,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
!= PackageManager.PERMISSION_GRANTED
) {
// Permission is not already granted // Permission is not already granted
requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), requestPermissions(
REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION) arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION
)
} else { } else {
// Permission is already granted // Permission is already granted
showExportOptionsDialog() showExportOptionsDialog()
@ -166,7 +188,11 @@ class TransactionsFragment : Fragment(), FilterOptionsDialog.OnFilterOptionsSele
/** Received the result of the storage permission request and if it was accepted then shows the export options /** Received the result of the storage permission request and if it was accepted then shows the export options
* dialog, but if it was not accepted then shows a toast explaining that the permission is necessary to generate * dialog, but if it was not accepted then shows a toast explaining that the permission is necessary to generate
* the export options */ * the export options */
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults) super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION) { if (requestCode == REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION) {
if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) { if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
@ -181,7 +207,10 @@ class TransactionsFragment : Fragment(), FilterOptionsDialog.OnFilterOptionsSele
private fun showExportOptionsDialog() { private fun showExportOptionsDialog() {
MaterialDialog(context!!).show { MaterialDialog(context!!).show {
title(R.string.title_export_transactions) title(R.string.title_export_transactions)
listItemsMultiChoice(R.array.export_options, initialSelection = intArrayOf(0,1)) { _, indices, _ -> listItemsMultiChoice(
R.array.export_options,
initialSelection = intArrayOf(0, 1)
) { _, indices, _ ->
val exportPDF = indices.contains(0) val exportPDF = indices.contains(0)
val exportCSV = indices.contains(1) val exportCSV = indices.contains(1)
exportFilteredTransactions(exportPDF, exportCSV) exportFilteredTransactions(exportPDF, exportCSV)

View file

@ -1,14 +1,14 @@
package cy.agorise.bitsybitshareswallet.models package cy.agorise.bitsybitshareswallet.models
import android.os.Parcelable import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
import cy.agorise.bitsybitshareswallet.fragments.TransactionsFragment import cy.agorise.bitsybitshareswallet.fragments.TransactionsFragment
import kotlinx.parcelize.Parcelize
/** /**
* Model that includes all the options to filter the transactions in the [TransactionsFragment] * Model that includes all the options to filter the transactions in the [TransactionsFragment]
*/ */
@Parcelize @Parcelize
data class FilterOptions ( data class FilterOptions(
var query: String = "", var query: String = "",
var transactionsDirection: Int = 0, var transactionsDirection: Int = 0,
var dateRangeAll: Boolean = true, var dateRangeAll: Boolean = true,

View file

@ -16,7 +16,7 @@
android:elevation="4dp" android:elevation="4dp"
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar" /> android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar" />
<fragment <androidx.fragment.app.FragmentContainerView
android:id="@+id/navHostFragment" android:id="@+id/navHostFragment"
android:name="androidx.navigation.fragment.NavHostFragment" android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent" android:layout_width="match_parent"