diff --git a/app/build.gradle b/app/build.gradle index a2f5a3b..595af35 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,16 +1,11 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-parcelize' apply plugin: 'kotlin-kapt' apply plugin: "androidx.navigation.safeargs.kotlin" apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.firebase.crashlytics' -// Needed for Kotlin's @Parcelize annotation -androidExtensions { - experimental = true -} - android { compileSdkVersion 29 defaultConfig { @@ -31,6 +26,11 @@ android { androidTest.assets.srcDirs += files("$projectDir/schemas".toString()) } } + + buildFeatures { + viewBinding true + } + buildTypes { debug { minifyEnabled false diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/activities/MainActivity.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/activities/MainActivity.kt index 26e2bc6..9e43b8c 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/activities/MainActivity.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/activities/MainActivity.kt @@ -12,8 +12,8 @@ import androidx.navigation.ui.navigateUp import androidx.navigation.ui.onNavDestinationSelected import androidx.navigation.ui.setupActionBarWithNavController import cy.agorise.bitsybitshareswallet.R +import cy.agorise.bitsybitshareswallet.databinding.ActivityMainBinding 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, @@ -21,7 +21,8 @@ import kotlinx.android.synthetic.main.activity_main.* */ 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 private lateinit var mHandler: Handler @@ -31,12 +32,14 @@ class MainActivity : ConnectedActivity() { super.onCreate(savedInstanceState) // Sets the theme to night mode if it has been selected by the user if (PreferenceManager.getDefaultSharedPreferences(this) - .getBoolean(Constants.KEY_NIGHT_MODE_ACTIVATED, false)) { + .getBoolean(Constants.KEY_NIGHT_MODE_ACTIVATED, false) + ) { 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 .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) mRunnable = Runnable { if (PreferenceManager.getDefaultSharedPreferences(this) - .getBoolean(Constants.KEY_AUTO_CLOSE_ACTIVATED, true)) { + .getBoolean(Constants.KEY_AUTO_CLOSE_ACTIVATED, true) + ) { finish() android.os.Process.killProcess(android.os.Process.myPid()) } else @@ -107,8 +111,8 @@ class MainActivity : ConnectedActivity() { override fun onBackPressed() { // 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 - when(currentDestination?.id) { + val currentDestination = binding.navHostFragment.findNavController().currentDestination + when (currentDestination?.id) { R.id.license_dest, R.id.import_brainkey_dest -> finish() else -> super.onBackPressed() } diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/BalancesFragment.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/BalancesFragment.kt index 17c4250..b51882d 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/BalancesFragment.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/BalancesFragment.kt @@ -9,38 +9,50 @@ import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProviders import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager -import cy.agorise.bitsybitshareswallet.R import cy.agorise.bitsybitshareswallet.adapters.BalancesAdapter import cy.agorise.bitsybitshareswallet.database.joins.BalanceDetail +import cy.agorise.bitsybitshareswallet.databinding.FragmentBalancesBinding 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 override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { + ): View { 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?) { super.onViewCreated(view, savedInstanceState) // 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!!) - rvBalances.adapter = balancesAdapter - rvBalances.layoutManager = LinearLayoutManager(context!!) - rvBalances.addItemDecoration(DividerItemDecoration(context!!, DividerItemDecoration.VERTICAL)) + binding.rvBalances.adapter = balancesAdapter + binding.rvBalances.layoutManager = LinearLayoutManager(context!!) + binding.rvBalances.addItemDecoration( + DividerItemDecoration(context!!, DividerItemDecoration.VERTICAL) + ) - mBalanceDetailViewModel.getAll().observe(this, Observer> { balancesDetails -> - balancesAdapter.replaceAll(balancesDetails) - }) + mBalanceDetailViewModel.getAll() + .observe(this, Observer> { balancesDetails -> + balancesAdapter.replaceAll(balancesDetails) + }) } -} \ No newline at end of file +} diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/CreateAccountFragment.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/CreateAccountFragment.kt index 6c03180..a83b18a 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/CreateAccountFragment.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/CreateAccountFragment.kt @@ -8,9 +8,15 @@ import android.view.View import android.view.ViewGroup import androidx.collection.LongSparseArray import androidx.navigation.fragment.findNavController +import com.afollestad.materialdialogs.MaterialDialog +import com.google.firebase.crashlytics.FirebaseCrashlytics import com.jakewharton.rxbinding3.widget.textChanges 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.ServiceGenerator import cy.agorise.bitsybitshareswallet.utils.Constants import cy.agorise.bitsybitshareswallet.utils.containsDigits 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.JsonRpcResponse import io.reactivex.android.schedulers.AndroidSchedulers -import kotlinx.android.synthetic.main.fragment_create_account.* import org.bitcoinj.core.ECKey +import retrofit2.Call import retrofit2.Callback +import retrofit2.Response import java.io.BufferedReader import java.io.IOException import java.io.InputStreamReader 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() { @@ -49,10 +49,14 @@ class CreateAccountFragment : BaseAccountFragment() { // Used when trying to validate that the account name is available private const val RESPONSE_GET_ACCOUNT_BY_NAME_VALIDATION = 1 + // Used when trying to obtain the info of the newly created account 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 /** 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 private val responseMap = LongSparseArray() - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { 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?) { @@ -77,7 +91,7 @@ class CreateAccountFragment : BaseAccountFragment() { // Use RxJava Debounce to check the validity and availability of the user's proposed account name mDisposables.add( - tietAccountName.textChanges() + binding.tietAccountName.textChanges() .skipInitialValue() .debounce(800, TimeUnit.MILLISECONDS) .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 mDisposables.add( - tietPin.textChanges() + binding.tietPin.textChanges() .skipInitialValue() .debounce(500, TimeUnit.MILLISECONDS) .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 mDisposables.add( - tietPinConfirmation.textChanges() + binding.tietPinConfirmation.textChanges() .skipInitialValue() .debounce(500, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) @@ -111,10 +125,10 @@ class CreateAccountFragment : BaseAccountFragment() { ) ) - btnCancel.setOnClickListener { findNavController().navigateUp() } + binding.btnCancel.setOnClickListener { findNavController().navigateUp() } - btnCreate.isEnabled = false - btnCreate.setOnClickListener { createAccount() } + binding.btnCreate.isEnabled = false + binding.btnCreate.setOnClickListener { createAccount() } // Generating BrainKey generateKeys() @@ -123,19 +137,23 @@ class CreateAccountFragment : BaseAccountFragment() { private fun validateAccountName(accountName: String) { isAccountValidAndAvailable = false - if ( !isAccountLengthValid(accountName) ) { - tilAccountName.helperText = "" - tilAccountName.error = getString(R.string.error__invalid_account_length) - } else if ( !isAccountStartValid(accountName) ) { - tilAccountName.helperText = "" - tilAccountName.error = getString(R.string.error__invalid_account_start) - } else if ( !isAccountNameValid(accountName) ) { - tilAccountName.helperText = "" - tilAccountName.error = getString(R.string.error__invalid_account_name) + if (!isAccountLengthValid(accountName)) { + binding.tilAccountName.helperText = "" + binding.tilAccountName.error = getString(R.string.error__invalid_account_length) + } else if (!isAccountStartValid(accountName)) { + binding.tilAccountName.helperText = "" + binding.tilAccountName.error = getString(R.string.error__invalid_account_start) + } else if (!isAccountNameValid(accountName)) { + binding.tilAccountName.helperText = "" + binding.tilAccountName.error = getString(R.string.error__invalid_account_name) } else { - tilAccountName.isErrorEnabled = false - tilAccountName.helperText = getString(R.string.text__verifying_account_availability) - val id = mNetworkService?.sendMessage(GetAccountByName(accountName), GetAccountByName.REQUIRED_API) + binding.tilAccountName.isErrorEnabled = false + binding.tilAccountName.helperText = + getString(R.string.text__verifying_account_availability) + val id = mNetworkService?.sendMessage( + GetAccountByName(accountName), + GetAccountByName.REQUIRED_API + ) if (id != null) responseMap.append(id, RESPONSE_GET_ACCOUNT_BY_NAME_VALIDATION) @@ -170,13 +188,13 @@ class CreateAccountFragment : BaseAccountFragment() { } private fun validatePIN() { - val pin = tietPin.text.toString() + val pin = binding.tietPin.text.toString() 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 } else { - tilPin.isErrorEnabled = false + binding.tilPin.isErrorEnabled = false isPINValid = true } @@ -184,13 +202,13 @@ class CreateAccountFragment : BaseAccountFragment() { } private fun validatePINConfirmation() { - val pinConfirmation = tietPinConfirmation.text.toString() + val pinConfirmation = binding.tietPinConfirmation.text.toString() - if (pinConfirmation != tietPin.text.toString()) { - tilPinConfirmation.error = getString(R.string.error__pin_mismatch) + if (pinConfirmation != binding.tietPin.text.toString()) { + binding.tilPinConfirmation.error = getString(R.string.error__pin_mismatch) isPINConfirmationValid = false } else { - tilPinConfirmation.isErrorEnabled = false + binding.tilPinConfirmation.isErrorEnabled = false isPINConfirmationValid = true } @@ -198,20 +216,21 @@ class CreateAccountFragment : BaseAccountFragment() { } private fun enableDisableCreateButton() { - btnCreate.isEnabled = (isPINValid && isPINConfirmationValid && isAccountValidAndAvailable) + binding.btnCreate.isEnabled = + (isPINValid && isPINConfirmationValid && isAccountValidAndAvailable) } override fun handleJsonRpcResponse(response: JsonRpcResponse<*>) { if (responseMap.containsKey(response.id)) { when (responseMap[response.id]) { RESPONSE_GET_ACCOUNT_BY_NAME_VALIDATION -> handleAccountNameValidation(response.result) - RESPONSE_GET_ACCOUNT_BY_NAME_CREATED -> handleAccountNameCreated(response.result) + RESPONSE_GET_ACCOUNT_BY_NAME_CREATED -> handleAccountNameCreated(response.result) } responseMap.remove(response.id) } } - 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 @@ -219,12 +238,12 @@ class CreateAccountFragment : BaseAccountFragment() { */ private fun handleAccountNameValidation(result: Any?) { if (result is AccountProperties) { - tilAccountName.helperText = "" - tilAccountName.error = getString(R.string.error__account_not_available) + binding.tilAccountName.helperText = "" + binding.tilAccountName.error = getString(R.string.error__account_not_available) isAccountValidAndAvailable = false } else { - tilAccountName.isErrorEnabled = false - tilAccountName.helperText = getString(R.string.text__account_is_available) + binding.tilAccountName.isErrorEnabled = false + binding.tilAccountName.helperText = getString(R.string.text__account_is_available) isAccountValidAndAvailable = true } @@ -237,7 +256,7 @@ class CreateAccountFragment : BaseAccountFragment() { */ private fun handleAccountNameCreated(result: Any?) { if (result is AccountProperties) { - onAccountSelected(result, tietPin.text.toString()) + onAccountSelected(result, binding.tietPin.text.toString()) } else { context?.toast(getString(R.string.error__created_account_not_found)) 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. */ private fun setStateLoading() { - btnCancel.isEnabled = false - btnCreate.isEnabled = false - progressBar.visibility = View.VISIBLE + binding.btnCancel.isEnabled = false + binding.btnCreate.isEnabled = false + binding.progressBar.visibility = View.VISIBLE } /** @@ -258,9 +277,9 @@ class CreateAccountFragment : BaseAccountFragment() { * the information from the newly created account. */ private fun setStateError() { - btnCancel.isEnabled = true - btnCreate.isEnabled = false - progressBar.visibility = View.GONE + binding.btnCancel.isEnabled = true + binding.btnCreate.isEnabled = false + binding.progressBar.visibility = View.GONE } /** @@ -270,7 +289,7 @@ class CreateAccountFragment : BaseAccountFragment() { private fun createAccount() { setStateLoading() - val accountName = tietAccountName.text.toString() + val accountName = binding.tietAccountName.text.toString() val faucetRequest = FaucetRequest(accountName, mAddress, Constants.FAUCET_REFERRER) val sg = ServiceGenerator(Constants.FAUCET_URL) @@ -280,7 +299,10 @@ class CreateAccountFragment : BaseAccountFragment() { // Execute the call asynchronously. Get a positive or negative callback. call?.enqueue(object : Callback { - override fun onResponse(call: Call, response: Response) { + override fun onResponse( + call: Call, + response: Response + ) { // 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 val handler = Handler() @@ -307,8 +329,10 @@ class CreateAccountFragment : BaseAccountFragment() { private fun getCreatedAccountInfo(faucetResponse: FaucetResponse?) { if (faucetResponse?.account != null) { - val id = mNetworkService?.sendMessage(GetAccountByName(faucetResponse.account?.name), - GetAccountByName.REQUIRED_API) + val id = mNetworkService?.sendMessage( + GetAccountByName(faucetResponse.account?.name), + GetAccountByName.REQUIRED_API + ) if (id != null) responseMap.append(id, RESPONSE_GET_ACCOUNT_BY_NAME_CREATED) @@ -320,7 +344,7 @@ class CreateAccountFragment : BaseAccountFragment() { getString(R.string.error__faucet_template, "None") } - context?.let {context -> + context?.let { context -> MaterialDialog(context) .title(R.string.title_error) .message(text = content) @@ -338,7 +362,8 @@ class CreateAccountFragment : BaseAccountFragment() { var reader: BufferedReader? = null val dictionary: String try { - reader = BufferedReader(InputStreamReader(context!!.assets.open(BRAINKEY_FILE), "UTF-8")) + reader = + BufferedReader(InputStreamReader(context!!.assets.open(BRAINKEY_FILE), "UTF-8")) dictionary = reader.readLine() val brainKeySuggestion = BrainKey.suggest(dictionary) @@ -347,7 +372,7 @@ class CreateAccountFragment : BaseAccountFragment() { Log.d(TAG, "brain key: $brainKeySuggestion") Log.d(TAG, "address would be: $address") mAddress = address.toString() - tvBrainKey.text = mBrainKey?.brainKey + binding.tvBrainKey.text = mBrainKey?.brainKey } catch (e: IOException) { Log.e(TAG, "IOException while trying to generate key. Msg: " + e.message) @@ -364,7 +389,10 @@ class CreateAccountFragment : BaseAccountFragment() { try { reader.close() } 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 + ) } } diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/EReceiptFragment.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/EReceiptFragment.kt index 7a5f6f9..d203142 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/EReceiptFragment.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/EReceiptFragment.kt @@ -16,14 +16,13 @@ import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProviders import androidx.navigation.fragment.navArgs import com.google.firebase.crashlytics.FirebaseCrashlytics - import cy.agorise.bitsybitshareswallet.R import cy.agorise.bitsybitshareswallet.database.joins.TransferDetail +import cy.agorise.bitsybitshareswallet.databinding.FragmentEReceiptBinding import cy.agorise.bitsybitshareswallet.utils.Constants import cy.agorise.bitsybitshareswallet.utils.Helper import cy.agorise.bitsybitshareswallet.utils.toast import cy.agorise.bitsybitshareswallet.viewmodels.EReceiptViewModel -import kotlinx.android.synthetic.main.fragment_e_receipt.* import java.math.RoundingMode import java.text.DecimalFormat import java.text.DecimalFormatSymbols @@ -39,15 +38,28 @@ class EReceiptFragment : Fragment() { 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 lateinit var mEReceiptViewModel: EReceiptViewModel 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) - 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?) { @@ -65,28 +77,30 @@ class EReceiptFragment : Fragment() { mEReceiptViewModel = ViewModelProviders.of(this).get(EReceiptViewModel::class.java) - mEReceiptViewModel.get(userId, transferId).observe(this, Observer { transferDetail -> - bindTransferDetail(transferDetail) - }) + mEReceiptViewModel.get(userId, transferId) + .observe(this, Observer { transferDetail -> + bindTransferDetail(transferDetail) + }) } private fun bindTransferDetail(transferDetail: TransferDetail) { - context?.let { vPaymentDirection.setBackgroundColor(ContextCompat.getColor(it, - if(transferDetail.direction) R.color.colorReceive else R.color.colorSend - ))} + context?.let { context -> + val colorRes = if (transferDetail.direction) R.color.colorReceive else R.color.colorSend + binding.vPaymentDirection.setBackgroundColor(ContextCompat.getColor(context, colorRes)) + } - tvFrom.text = transferDetail.from ?: "" - tvTo.text = transferDetail.to ?: "" + binding.tvFrom.text = transferDetail.from ?: "" + binding.tvTo.text = transferDetail.to ?: "" // Show the crypto amount correctly formatted - val df = DecimalFormat("####."+("#".repeat(transferDetail.assetPrecision))) + val df = DecimalFormat("####." + ("#".repeat(transferDetail.assetPrecision))) df.roundingMode = RoundingMode.CEILING df.decimalFormatSymbols = DecimalFormatSymbols(Locale.getDefault()) val amount = transferDetail.assetAmount.toDouble() / Math.pow(10.toDouble(), transferDetail.assetPrecision.toDouble()) val assetAmount = "${df.format(amount)} ${transferDetail.getUIAssetSymbol()}" - tvAmount.text = assetAmount + binding.tvAmount.text = assetAmount // Fiat equivalent if (transferDetail.fiatAmount != null && transferDetail.fiatSymbol != null) { @@ -96,20 +110,21 @@ class EReceiptFragment : Fragment() { Math.pow(10.0, currency.defaultFractionDigits.toDouble()) val equivalentValue = "${numberFormat.format(fiatEquivalent)} ${currency.currencyCode}" - tvEquivalentValue.text = equivalentValue + binding.tvEquivalentValue.text = equivalentValue } else { - tvEquivalentValue.text = "-" + binding.tvEquivalentValue.text = "-" } // Memo if (transferDetail.memo != "") - tvMemo.text = getString(R.string.template__memo, transferDetail.memo) + binding.tvMemo.text = getString(R.string.template__memo, transferDetail.memo) else - tvMemo.visibility = View.GONE + binding.tvMemo.visibility = View.GONE // Date 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 # formatTransferTextView(transferDetail.id) @@ -118,11 +133,14 @@ class EReceiptFragment : Fragment() { /** Formats the transfer TextView to show a link to explore the given transfer * in a BitShares explorer */ private fun formatTransferTextView(transferId: String) { - val tx = Html.fromHtml(getString(R.string.template__tx, - "$transferId" - )) - tvTransferID.text = tx - tvTransferID.movementMethod = LinkMovementMethod.getInstance() + val tx = Html.fromHtml( + getString( + R.string.template__tx, + "$transferId" + ) + ) + binding.tvTransferID.text = tx + binding.tvTransferID.movementMethod = LinkMovementMethod.getInstance() } 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 * shares it but if it is not then it asks the user for that permission */ private fun verifyStoragePermission() { - if (ContextCompat.checkSelfPermission(activity!!, Manifest.permission.WRITE_EXTERNAL_STORAGE) - != PackageManager.PERMISSION_GRANTED) { + if (ContextCompat + .checkSelfPermission(activity!!, Manifest.permission.WRITE_EXTERNAL_STORAGE) + != PackageManager.PERMISSION_GRANTED + ) { // Permission is not already granted - requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), - REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION) + requestPermissions( + arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), + REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION + ) } else { // Permission is already granted shareEReceiptScreenshot() } } - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) 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. */ private fun shareEReceiptScreenshot() { // Get Screenshot - tvTransferID.text = getString(R.string.template__tx, args.transferId) - val screenshot = Helper.loadBitmapFromView(container) + binding.tvTransferID.text = getString(R.string.template__tx, args.transferId) + val screenshot = Helper.loadBitmapFromView(binding.container) formatTransferTextView(args.transferId) val imageUri = context?.let { Helper.saveTemporalBitmap(it, screenshot) } diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/FilterOptionsDialog.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/FilterOptionsDialog.kt index 49a3f96..e3545da 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/FilterOptionsDialog.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/FilterOptionsDialog.kt @@ -6,25 +6,24 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.fragment.app.DialogFragment -import android.widget.* +import android.widget.TextView import androidx.core.os.ConfigurationCompat +import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment -import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProviders import com.google.firebase.crashlytics.FirebaseCrashlytics import cy.agorise.bitsybitshareswallet.R import cy.agorise.bitsybitshareswallet.adapters.BalancesDetailsAdapter import cy.agorise.bitsybitshareswallet.database.joins.BalanceDetail +import cy.agorise.bitsybitshareswallet.databinding.DialogFilterOptionsBinding import cy.agorise.bitsybitshareswallet.models.FilterOptions import cy.agorise.bitsybitshareswallet.utils.Constants import cy.agorise.bitsybitshareswallet.utils.Helper import cy.agorise.bitsybitshareswallet.viewmodels.BalanceDetailViewModel import cy.agorise.bitsybitshareswallet.views.DatePickerFragment -import kotlinx.android.synthetic.main.dialog_filter_options.* import java.text.SimpleDateFormat import java.util.* -import kotlin.ClassCastException import kotlin.collections.ArrayList @@ -43,12 +42,17 @@ class FilterOptionsDialog : DialogFragment(), DatePickerFragment.OnDateSetListen const val END_DATE_PICKER = 1 } + private var _binding: DialogFilterOptionsBinding? = null + private val binding get() = _binding!! + private lateinit var mFilterOptions: FilterOptions private var mCallback: OnFilterOptionsSelectedListener? = null - private var dateFormat: SimpleDateFormat = SimpleDateFormat("d/MMM/yyyy", - ConfigurationCompat.getLocales(Resources.getSystem().configuration)[0]) + private var dateFormat: SimpleDateFormat = SimpleDateFormat( + "d/MMM/yyyy", + ConfigurationCompat.getLocales(Resources.getSystem().configuration)[0] + ) private var mBalanceDetails = ArrayList() @@ -59,7 +63,7 @@ class FilterOptionsDialog : DialogFragment(), DatePickerFragment.OnDateSetListen private lateinit var mCurrency: Currency override fun onDateSet(which: Int, timestamp: Long) { - when(which) { + when (which) { START_DATE_PICKER -> { mFilterOptions.startDate = timestamp @@ -85,10 +89,10 @@ class FilterOptionsDialog : DialogFragment(), DatePickerFragment.OnDateSetListen private fun updateDateTextViews() { var date = Date(mFilterOptions.startDate) - tvStartDate.text = dateFormat.format(date) + binding.tvStartDate.text = dateFormat.format(date) date = Date(mFilterOptions.endDate) - tvEndDate.text = dateFormat.format(date) + binding.tvEndDate.text = dateFormat.format(date) } // Container Fragment must implement this interface @@ -96,8 +100,18 @@ class FilterOptionsDialog : DialogFragment(), DatePickerFragment.OnDateSetListen fun onFilterOptionsSelected(filterOptions: FilterOptions) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.dialog_filter_options, container, false) + override fun onCreateView( + 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?) { @@ -112,73 +126,81 @@ class FilterOptionsDialog : DialogFragment(), DatePickerFragment.OnDateSetListen // Initialize Transactions direction when (mFilterOptions.transactionsDirection) { - 0 -> rbTransactionAll.isChecked = true - 1 -> rbTransactionSent.isChecked = true - 2 -> rbTransactionReceived.isChecked = true + 0 -> binding.rbTransactionAll.isChecked = true + 1 -> binding.rbTransactionSent.isChecked = true + 2 -> binding.rbTransactionReceived.isChecked = true } // Initialize Date range - cbDateRange.setOnCheckedChangeListener { _, isChecked -> - llDateRange.visibility = if(isChecked) View.GONE else View.VISIBLE } - cbDateRange.isChecked = mFilterOptions.dateRangeAll + binding.cbDateRange.setOnCheckedChangeListener { _, isChecked -> + binding.llDateRange.visibility = if (isChecked) View.GONE else View.VISIBLE + } + binding.cbDateRange.isChecked = mFilterOptions.dateRangeAll - tvStartDate.setOnClickListener(mDateClickListener) + binding.tvStartDate.setOnClickListener(mDateClickListener) - tvEndDate.setOnClickListener(mDateClickListener) + binding.tvEndDate.setOnClickListener(mDateClickListener) updateDateTextViews() // Initialize Asset - cbAsset.setOnCheckedChangeListener { _, isChecked -> - sAsset.visibility = if(isChecked) View.GONE else View.VISIBLE + binding.cbAsset.setOnCheckedChangeListener { _, isChecked -> + 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 - mBalanceDetailViewModel = ViewModelProviders.of(this).get(BalanceDetailViewModel::class.java) + mBalanceDetailViewModel = + ViewModelProviders.of(this).get(BalanceDetailViewModel::class.java) - mBalanceDetailViewModel.getAll().observe(this, Observer> { balancesDetails -> - mBalanceDetails.clear() - mBalanceDetails.addAll(balancesDetails) - mBalanceDetails.sortWith( - Comparator { a, b -> a.toString().compareTo(b.toString(), true) } - ) - mBalancesDetailsAdapter = BalancesDetailsAdapter(context!!, android.R.layout.simple_spinner_item, mBalanceDetails) - sAsset.adapter = mBalancesDetailsAdapter + mBalanceDetailViewModel.getAll() + .observe(this, Observer> { balancesDetails -> + mBalanceDetails.clear() + mBalanceDetails.addAll(balancesDetails) + mBalanceDetails.sortWith( + Comparator { a, b -> a.toString().compareTo(b.toString(), true) } + ) + mBalancesDetailsAdapter = BalancesDetailsAdapter( + context!!, + android.R.layout.simple_spinner_item, + mBalanceDetails + ) + binding.sAsset.adapter = mBalancesDetailsAdapter - // Try to select the selectedAssetSymbol - for (i in 0 until mBalancesDetailsAdapter!!.count) { - if (mBalancesDetailsAdapter!!.getItem(i)!!.symbol == mFilterOptions.asset) { - sAsset.setSelection(i) - break + // Try to select the selectedAssetSymbol + for (i in 0 until mBalancesDetailsAdapter!!.count) { + if (mBalancesDetailsAdapter!!.getItem(i)!!.symbol == mFilterOptions.asset) { + binding.sAsset.setSelection(i) + break + } } - } - }) + }) // Initialize Equivalent Value - cbEquivalentValue.setOnCheckedChangeListener { _, isChecked -> - llEquivalentValue.visibility = if(isChecked) View.GONE else View.VISIBLE } - cbEquivalentValue.isChecked = mFilterOptions.equivalentValueAll + binding.cbEquivalentValue.setOnCheckedChangeListener { _, isChecked -> + binding.llEquivalentValue.visibility = if (isChecked) View.GONE else View.VISIBLE + } + binding.cbEquivalentValue.isChecked = mFilterOptions.equivalentValueAll val currencyCode = Helper.getCoingeckoSupportedCurrency(Locale.getDefault()) mCurrency = Currency.getInstance(currencyCode) val fromEquivalentValue = mFilterOptions.fromEquivalentValue / 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 / 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 - switchAgoriseFees.isChecked = mFilterOptions.agoriseFees + binding.switchAgoriseFees.isChecked = mFilterOptions.agoriseFees // Setup cancel and filter buttons - btnCancel.setOnClickListener { dismiss() } - btnFilter.setOnClickListener { validateFields() } + binding.btnCancel.setOnClickListener { dismiss() } + binding.btnFilter.setOnClickListener { validateFields() } } override fun onResume() { @@ -187,7 +209,10 @@ class FilterOptionsDialog : DialogFragment(), DatePickerFragment.OnDateSetListen // Force dialog fragment to use the full width of the screen // TODO use the same width as standard fragments val dialogWindow = dialog?.window - dialogWindow?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + dialogWindow?.setLayout( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) } /** @@ -226,18 +251,20 @@ class FilterOptionsDialog : DialogFragment(), DatePickerFragment.OnDateSetListen } private fun validateFields() { - mFilterOptions.transactionsDirection = when { - rbTransactionAll.isChecked -> 0 - rbTransactionSent.isChecked -> 1 - rbTransactionReceived.isChecked -> 2 - else -> { 0 } + mFilterOptions.transactionsDirection = when { + binding.rbTransactionAll.isChecked -> 0 + binding.rbTransactionSent.isChecked -> 1 + binding.rbTransactionReceived.isChecked -> 2 + 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 // fetched the account balances) symbol will be null, make sure that does not create a crash. if (symbol != null) @@ -245,19 +272,20 @@ class FilterOptionsDialog : DialogFragment(), DatePickerFragment.OnDateSetListen else mFilterOptions.assetAll = true - mFilterOptions.equivalentValueAll = cbEquivalentValue.isChecked + mFilterOptions.equivalentValueAll = binding.cbEquivalentValue.isChecked - mFilterOptions.fromEquivalentValue = etFromEquivalentValue.text.toString().toLong() * - Math.pow(10.0, mCurrency.defaultFractionDigits.toDouble()).toLong() + mFilterOptions.fromEquivalentValue = + binding.etFromEquivalentValue.text.toString().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() // Make sure ToEquivalentValue is at least 50 units bigger than FromEquivalentValue mFilterOptions.toEquivalentValue = Math.max(mFilterOptions.toEquivalentValue, mFilterOptions.fromEquivalentValue + 50) - mFilterOptions.agoriseFees = switchAgoriseFees.isChecked + mFilterOptions.agoriseFees = binding.switchAgoriseFees.isChecked mCallback!!.onFilterOptionsSelected(mFilterOptions) dismiss() diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/HomeFragment.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/HomeFragment.kt index ea29e13..2d0860b 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/HomeFragment.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/HomeFragment.kt @@ -1,36 +1,42 @@ package cy.agorise.bitsybitshareswallet.fragments -import androidx.lifecycle.ViewModelProviders import android.os.Bundle import android.preference.PreferenceManager import android.view.* +import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.Toolbar import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentPagerAdapter import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProviders import androidx.navigation.Navigation import androidx.navigation.fragment.findNavController - +import com.google.firebase.crashlytics.FirebaseCrashlytics import cy.agorise.bitsybitshareswallet.R import cy.agorise.bitsybitshareswallet.database.entities.UserAccount +import cy.agorise.bitsybitshareswallet.databinding.FragmentHomeBinding import cy.agorise.bitsybitshareswallet.utils.Constants 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() { 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 - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { setHasOptionsMenu(true) val nightMode = PreferenceManager.getDefaultSharedPreferences(context) @@ -52,13 +58,21 @@ class HomeFragment : Fragment() { window?.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) // Sets the status and navigation bars background color to a dark blue or just dark context?.let { context -> - val statusBarColor = ContextCompat.getColor(context, - if (!nightMode) R.color.colorPrimaryVariant else R.color.colorStatusBarDark) + val statusBarColor = ContextCompat.getColor( + context, + if (!nightMode) R.color.colorPrimaryVariant else R.color.colorStatusBarDark + ) window?.statusBarColor = 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?) { @@ -82,48 +96,49 @@ class HomeFragment : Fragment() { // Configure UserAccountViewModel to show the current account mUserAccountViewModel = ViewModelProviders.of(this).get(UserAccountViewModel::class.java) - mUserAccountViewModel.getUserAccount(userId).observe(this, Observer{ userAccount -> - if (userAccount != null) { - tvAccountName.text = userAccount.name - if (userAccount.isLtm) { - // Add the lightning bolt to the start of the account name if it is LTM - tvAccountName.setCompoundDrawablesWithIntrinsicBounds( - 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 - tvAccountName.compoundDrawablePadding = 12 + mUserAccountViewModel.getUserAccount(userId) + .observe(this, Observer { userAccount -> + if (userAccount != null) { + binding.tvAccountName.text = userAccount.name + if (userAccount.isLtm) { + // Add the lightning bolt to the start of the account name if it is LTM + binding.tvAccountName.setCompoundDrawablesWithIntrinsicBounds( + 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 + binding.tvAccountName.compoundDrawablePadding = 12 + } } - } - }) + }) // Navigate to the Receive Transaction Fragment - fabReceiveTransaction.setOnClickListener ( + binding.fabReceiveTransaction.setOnClickListener( Navigation.createNavigateOnClickListener(R.id.receive_action) ) // Navigate to the Send Transaction Fragment without activating the camera - fabSendTransaction.setOnClickListener( + binding.fabSendTransaction.setOnClickListener( Navigation.createNavigateOnClickListener(R.id.send_action) ) // Navigate to the Send Transaction Fragment using Navigation's SafeArgs to activate the camera - fabSendTransactionCamera.setOnClickListener { + binding.fabSendTransactionCamera.setOnClickListener { val action = HomeFragmentDirections.sendAction(true) findNavController().navigate(action) } // Configure ViewPager with PagerAdapter and TabLayout to display the Balances/NetWorth section val pagerAdapter = PagerAdapter(childFragmentManager) - viewPager.adapter = pagerAdapter - tabLayout.setupWithViewPager(viewPager) + binding.viewPager.adapter = pagerAdapter + binding.tabLayout.setupWithViewPager(binding.viewPager) // 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 */ - private inner class PagerAdapter internal constructor(fm: FragmentManager) : FragmentPagerAdapter(fm) { + private inner class PagerAdapter(fm: FragmentManager) : FragmentPagerAdapter(fm) { override fun getItem(position: Int): Fragment { // getItem is called to instantiate the fragment for the given page. @@ -133,8 +148,12 @@ class HomeFragment : Fragment() { NetWorthFragment() } - override fun getPageTitle(position: Int): CharSequence? { - return listOf(getString(R.string.title_balances), getString(R.string.title_net_worth), "")[position] + override fun getPageTitle(position: Int): CharSequence { + return listOf( + getString(R.string.title_balances), + getString(R.string.title_net_worth), + "" + )[position] } override fun getCount(): Int { diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/ImportBrainkeyFragment.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/ImportBrainkeyFragment.kt index aebd84e..3580e70 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/ImportBrainkeyFragment.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/ImportBrainkeyFragment.kt @@ -7,6 +7,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.appcompat.widget.Toolbar +import androidx.core.content.res.ResourcesCompat import androidx.navigation.Navigation import androidx.recyclerview.widget.LinearLayoutManager import com.afollestad.materialdialogs.MaterialDialog @@ -19,9 +20,12 @@ import com.jakewharton.rxbinding3.widget.textChanges import cy.agorise.bitsybitshareswallet.BuildConfig import cy.agorise.bitsybitshareswallet.R import cy.agorise.bitsybitshareswallet.adapters.FullNodesAdapter +import cy.agorise.bitsybitshareswallet.databinding.FragmentImportBrainkeyBinding import cy.agorise.bitsybitshareswallet.utils.Constants 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.calls.GetAccounts 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.JsonRpcResponse import io.reactivex.android.schedulers.AndroidSchedulers -import kotlinx.android.synthetic.main.fragment_import_brainkey.* import org.bitcoinj.core.ECKey import java.text.NumberFormat import java.util.* @@ -42,6 +45,9 @@ class ImportBrainkeyFragment : BaseAccountFragment() { 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 */ 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*/ 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 val toolbar: Toolbar? = activity?.findViewById(R.id.toolbar) 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?) { @@ -88,7 +104,7 @@ class ImportBrainkeyFragment : BaseAccountFragment() { // Use RxJava Debounce to update the PIN error only after the user stops writing for > 500 ms mDisposables.add( - tietPin.textChanges() + binding.tietPin.textChanges() .skipInitialValue() .debounce(500, TimeUnit.MILLISECONDS) .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 mDisposables.add( - tietPinConfirmation.textChanges() + binding.tietPinConfirmation.textChanges() .skipInitialValue() .debounce(500, TimeUnit.MILLISECONDS) .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 mDisposables.add( - tietBrainKey.textChanges() + binding.tietBrainKey.textChanges() .skipInitialValue() .debounce(500, TimeUnit.MILLISECONDS) .map { it.toString().trim() } @@ -123,14 +139,14 @@ class ImportBrainkeyFragment : BaseAccountFragment() { ) ) - btnImport.isEnabled = false - btnImport.setOnClickListener { verifyBrainKey(false) } + binding.btnImport.isEnabled = false + binding.btnImport.setOnClickListener { verifyBrainKey(false) } - btnCreate.setOnClickListener ( + binding.btnCreate.setOnClickListener( Navigation.createNavigateOnClickListener(R.id.create_account_action) ) - tvNetworkStatus.setOnClickListener { v -> showNodesDialog(v) } + binding.tvNetworkStatus.setOnClickListener { v -> showNodesDialog(v) } } private fun showNodesDialog(v: View) { @@ -160,7 +176,13 @@ class ImportBrainkeyFragment : BaseAccountFragment() { ) 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, "-------")) customListAdapter(nodesAdapter as FullNodesAdapter) 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 mHandler.post(mRequestDynamicGlobalPropertiesTask) @@ -178,13 +201,13 @@ class ImportBrainkeyFragment : BaseAccountFragment() { } private fun validatePIN() { - val pin = tietPin.text.toString() + val pin = binding.tietPin.text.toString() 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 } else { - tilPin.isErrorEnabled = false + binding.tilPin.isErrorEnabled = false isPINValid = true } @@ -192,13 +215,13 @@ class ImportBrainkeyFragment : BaseAccountFragment() { } private fun validatePINConfirmation() { - val pinConfirmation = tietPinConfirmation.text.toString() + val pinConfirmation = binding.tietPinConfirmation.text.toString() - if (pinConfirmation != tietPin.text.toString()) { - tilPinConfirmation.error = getString(R.string.error__pin_mismatch) + if (pinConfirmation != binding.tietPin.text.toString()) { + binding.tilPinConfirmation.error = getString(R.string.error__pin_mismatch) isPINConfirmationValid = false } else { - tilPinConfirmation.isErrorEnabled = false + binding.tilPinConfirmation.isErrorEnabled = false isPINConfirmationValid = true } @@ -207,10 +230,10 @@ class ImportBrainkeyFragment : BaseAccountFragment() { private fun validateBrainKey(brainKey: String) { 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 } else { - tilBrainKey.isErrorEnabled = false + binding.tilBrainKey.isErrorEnabled = false isBrainKeyValid = true } @@ -218,7 +241,7 @@ class ImportBrainkeyFragment : BaseAccountFragment() { } 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) { //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? if (switchCase) { if (Character.isUpperCase(brainKey.toCharArray()[brainKey.length - 1])) { @@ -270,7 +293,8 @@ class ImportBrainkeyFragment : BaseAccountFragment() { mBrainKey = BrainKey(brainKey, 0) val address = Address(ECKey.fromPublicOnly(mBrainKey!!.privateKey.pubKey)) 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() { @@ -290,8 +314,14 @@ class ImportBrainkeyFragment : BaseAccountFragment() { } else if (response.result is DynamicGlobalProperties) { val dynamicGlobalProperties = response.result as DynamicGlobalProperties if (mNodesDialog != null && mNodesDialog?.isShowing == true) { - val blockNumber = NumberFormat.getInstance().format(dynamicGlobalProperties.head_block_number) - mNodesDialog?.message(text = getString(R.string.title__bitshares_nodes_dialog, blockNumber)) + val 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() { - tvNetworkStatus.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, - resources.getDrawable(R.drawable.ic_connected, null), null) + binding.tvNetworkStatus.setCompoundDrawablesRelativeWithIntrinsicBounds( + null, null, + ResourcesCompat.getDrawable(resources, R.drawable.ic_connected, null), null + ) } private fun showDisconnectedState() { - tvNetworkStatus.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, - resources.getDrawable(R.drawable.ic_disconnected, null), null) + binding.tvNetworkStatus.setCompoundDrawablesRelativeWithIntrinsicBounds( + null, null, + ResourcesCompat.getDrawable(resources, R.drawable.ic_disconnected, null), null + ) } /** @@ -342,7 +376,7 @@ class ImportBrainkeyFragment : BaseAccountFragment() { // trying to find out the account name mUserAccount = accountList[0] getAccountsRequestId = - mNetworkService?.sendMessage(GetAccounts(mUserAccount), GetAccounts.REQUIRED_API) + mNetworkService?.sendMessage(GetAccounts(mUserAccount), GetAccounts.REQUIRED_API) } else { // If we found more than one account linked to this key, we must also // find out the account names, but the procedure is a bit different in @@ -371,12 +405,18 @@ class ImportBrainkeyFragment : BaseAccountFragment() { MaterialDialog(context!!) .title(R.string.dialog__account_candidates_title) .message(R.string.dialog__account_candidates_content) - .listItemsSingleChoice (items = candidates, initialSelection = -1) { _, index, _ -> + .listItemsSingleChoice( + items = candidates, + initialSelection = -1 + ) { _, index, _ -> if (index >= 0) { // If one account was selected, we keep a reference to it and // store the account properties mUserAccount = mUserAccountCandidates!![index] - onAccountSelected(accountPropertiesList[index], tietPin.text.toString()) + onAccountSelected( + accountPropertiesList[index], + binding.tietPin.text.toString() + ) } } .positiveButton(android.R.string.ok) @@ -386,7 +426,7 @@ class ImportBrainkeyFragment : BaseAccountFragment() { .cancelable(false) .show() } else if (accountPropertiesList.size == 1) { - onAccountSelected(accountPropertiesList[0], tietPin.text.toString()) + onAccountSelected(accountPropertiesList[0], binding.tietPin.text.toString()) } else { context?.toast(getString(R.string.error__try_again)) } @@ -400,7 +440,10 @@ class ImportBrainkeyFragment : BaseAccountFragment() { override fun run() { if (mNetworkService != null) { if (mNetworkService?.isConnected == true) { - mNetworkService?.sendMessage(GetDynamicGlobalProperties(), GetDynamicGlobalProperties.REQUIRED_API) + mNetworkService?.sendMessage( + GetDynamicGlobalProperties(), + GetDynamicGlobalProperties.REQUIRED_API + ) } else { Log.d(TAG, "NetworkService exists but is not connected") } diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/LicenseFragment.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/LicenseFragment.kt index 6fa29e4..91236e7 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/LicenseFragment.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/LicenseFragment.kt @@ -10,8 +10,8 @@ import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController import com.google.firebase.crashlytics.FirebaseCrashlytics import cy.agorise.bitsybitshareswallet.R +import cy.agorise.bitsybitshareswallet.databinding.FragmentLicenseBinding import cy.agorise.bitsybitshareswallet.utils.Constants -import kotlinx.android.synthetic.main.fragment_license.* class LicenseFragment : Fragment() { @@ -19,12 +19,25 @@ class LicenseFragment : Fragment() { 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 val toolbar: Toolbar? = activity?.findViewById(R.id.toolbar) 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?) { @@ -41,11 +54,11 @@ class LicenseFragment : Fragment() { if (agreedLicenseVersion == Constants.CURRENT_LICENSE_VERSION) { agree() } 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() { 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) } diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/MerchantsFragment.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/MerchantsFragment.kt index b27ea01..8a37863 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/MerchantsFragment.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/MerchantsFragment.kt @@ -39,6 +39,7 @@ import com.jakewharton.rxbinding3.appcompat.queryTextChangeEvents import cy.agorise.bitsybitshareswallet.R import cy.agorise.bitsybitshareswallet.database.entities.Merchant import cy.agorise.bitsybitshareswallet.database.entities.Teller +import cy.agorise.bitsybitshareswallet.databinding.FragmentMerchantsBinding import cy.agorise.bitsybitshareswallet.models.MapObject import cy.agorise.bitsybitshareswallet.utils.Constants import cy.agorise.bitsybitshareswallet.utils.MerchantClusterRenderer @@ -50,10 +51,8 @@ import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable import io.reactivex.functions.BiFunction import io.reactivex.schedulers.Schedulers -import kotlinx.android.synthetic.main.fragment_merchants.* import java.math.BigInteger import java.util.concurrent.TimeUnit -import kotlin.collections.ArrayList 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 var _binding: FragmentMerchantsBinding? = null + private val binding get() = _binding!! + private var mMap: GoogleMap? = null private lateinit var mMerchantViewModel: MerchantViewModel @@ -108,17 +110,33 @@ class MerchantsFragment : Fragment(), OnMapReadyCallback, SearchView.OnSuggestio private var statusBarSize = 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 val activityToolbar: Toolbar? = activity?.findViewById(R.id.toolbar) activityToolbar?.visibility = View.GONE // Sets the Navigation and Status bars translucent so that the map can be viewed through them val window = activity?.window - window?.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) - window?.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + window?.setFlags( + 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?) { @@ -132,17 +150,17 @@ class MerchantsFragment : Fragment(), OnMapReadyCallback, SearchView.OnSuggestio view.setOnApplyWindowInsetsListener { v, insets -> statusBarSize = insets.systemWindowInsetTop navigationBarSize = insets.systemWindowInsetBottom - val layoutParams = toolbar.layoutParams as ViewGroup.MarginLayoutParams + val layoutParams = binding.toolbar.layoutParams as ViewGroup.MarginLayoutParams layoutParams.topMargin = statusBarSize insets } // 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) setHasOptionsMenu(true) - toolbar?.setOnClickListener { dismissPopupWindow() } + binding.toolbar.setOnClickListener { dismissPopupWindow() } // Obtain the SupportMapFragment and get notified when the map is ready to be used. 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) { @@ -192,7 +214,8 @@ class MerchantsFragment : Fragment(), OnMapReadyCallback, SearchView.OnSuggestio // Adds listener for the SearchView val searchItem = menu.findItem(R.id.menu_search) 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), intArrayOf(R.id.tvName, R.id.tvAddress, R.id.ivMarkerPin) ) @@ -268,24 +291,40 @@ class MerchantsFragment : Fragment(), OnMapReadyCallback, SearchView.OnSuggestio mapObjects } - ).subscribe({mapObjects -> + ).subscribe({ mapObjects -> run { Log.d(TAG, "list with ${mapObjects.size} elements") val cursor = MatrixCursor( arrayOf( - SUGGEST_COLUMN_ID, SUGGEST_COLUMN_LAT, SUGGEST_COLUMN_LON, SUGGEST_COLUMN_NAME, - SUGGEST_COLUMN_ADDRESS, SUGGEST_COLUMN_IS_MERCHANT, SUGGEST_COLUMN_IMAGE_RESOURCE + SUGGEST_COLUMN_ID, + SUGGEST_COLUMN_LAT, + SUGGEST_COLUMN_LON, + SUGGEST_COLUMN_NAME, + SUGGEST_COLUMN_ADDRESS, + SUGGEST_COLUMN_IS_MERCHANT, + SUGGEST_COLUMN_IMAGE_RESOURCE ) ) for (mapObject in mapObjects) { - cursor.addRow(arrayOf(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)) + cursor.addRow( + arrayOf( + 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) } }, - {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) { // Try to show or dismiss the custom popup window with the merchants and tellers switches if (mPopupWindow?.isShowing == false) { - mPopupWindow?.showAsDropDown(toolbar, screenWidth, 8) + mPopupWindow?.showAsDropDown(binding.toolbar, screenWidth, 8) if (mMap?.isMyLocationEnabled == true) mMap?.uiSettings?.isMyLocationButtonEnabled = false } else @@ -331,7 +370,11 @@ class MerchantsFragment : Fragment(), OnMapReadyCallback, SearchView.OnSuggestio /** Handles the result from the location permission request */ @SuppressLint("MissingPermission") - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == REQUEST_LOCATION_PERMISSION) { @@ -357,7 +400,7 @@ class MerchantsFragment : Fragment(), OnMapReadyCallback, SearchView.OnSuggestio mMap = googleMap // 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() @@ -407,9 +450,13 @@ class MerchantsFragment : Fragment(), OnMapReadyCallback, SearchView.OnSuggestio private fun verifyLocationPermission() { if (ContextCompat.checkSelfPermission(activity!!, Manifest.permission.ACCESS_FINE_LOCATION) - != PackageManager.PERMISSION_GRANTED) { + != PackageManager.PERMISSION_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 { // Permission is already granted mMap?.isMyLocationEnabled = true @@ -435,7 +482,7 @@ class MerchantsFragment : Fragment(), OnMapReadyCallback, SearchView.OnSuggestio // Force marker to use a custom info window mMerchantClusterManager?.markerCollection?.setOnInfoWindowAdapter(MerchantInfoWindowAdapter()) - mMerchantViewModel.getAllMerchants().observe(this, Observer> {merchants -> + mMerchantViewModel.getAllMerchants().observe(this, Observer> { merchants -> this.merchants.clear() this.merchants.addAll(merchants) showHideMerchantsMarkers() @@ -461,7 +508,7 @@ class MerchantsFragment : Fragment(), OnMapReadyCallback, SearchView.OnSuggestio // Force marker to use a custom info window mTellerClusterManager?.markerCollection?.setOnInfoWindowAdapter(TellerInfoWindowAdapter()) - mMerchantViewModel.getAllTellers().observe(this, Observer> {tellers -> + mMerchantViewModel.getAllTellers().observe(this, Observer> { tellers -> this.tellers.clear() this.tellers.addAll(tellers) showHideTellersMarkers() @@ -512,13 +559,14 @@ class MerchantsFragment : Fragment(), OnMapReadyCallback, SearchView.OnSuggestio inner class MerchantInfoWindowAdapter : GoogleMap.InfoWindowAdapter { override fun getInfoWindow(marker: Marker?): View { - val infoWindowLayout: View = LayoutInflater.from(context).inflate( - R.layout.marker_merch_info_window, null) - val tvName = infoWindowLayout.findViewById(R.id.tvName) - val tvAddress = infoWindowLayout.findViewById(R.id.tvAddress) - val tvPhone = infoWindowLayout.findViewById(R.id.tvPhone) - val tvTelegram = infoWindowLayout.findViewById(R.id.tvTelegram) - val tvWebsite = infoWindowLayout.findViewById(R.id.tvWebsite) + val infoWindowLayout: View = LayoutInflater.from(context) + .inflate(R.layout.marker_merch_info_window, null) + + val tvName = infoWindowLayout.findViewById(R.id.tvName) + val tvAddress = infoWindowLayout.findViewById(R.id.tvAddress) + val tvPhone = infoWindowLayout.findViewById(R.id.tvPhone) + val tvTelegram = infoWindowLayout.findViewById(R.id.tvTelegram) + val tvWebsite = infoWindowLayout.findViewById(R.id.tvWebsite) if (selectedMerchant != null) { tvName.text = selectedMerchant?.name @@ -557,17 +605,18 @@ class MerchantsFragment : Fragment(), OnMapReadyCallback, SearchView.OnSuggestio inner class TellerInfoWindowAdapter : GoogleMap.InfoWindowAdapter { override fun getInfoWindow(marker: Marker?): View { - val infoWindowLayout: View = LayoutInflater.from(context).inflate( - R.layout.marker_teller_info_window, null) - val tvName = infoWindowLayout.findViewById(R.id.tvName) - val tvAddress = infoWindowLayout.findViewById(R.id.tvAddress) - val tvPhone = infoWindowLayout.findViewById(R.id.tvPhone) - val tvTelegram = infoWindowLayout.findViewById(R.id.tvTelegram) - val tvKeybase = infoWindowLayout.findViewById(R.id.tvKeybase) - val tvWhatsapp = infoWindowLayout.findViewById(R.id.tvWhatsapp) - val tvViber = infoWindowLayout.findViewById(R.id.tvViber) - val tvEmail = infoWindowLayout.findViewById(R.id.tvEmail) - val tvWebsite = infoWindowLayout.findViewById(R.id.tvWebsite) + val infoWindowLayout: View = LayoutInflater.from(context) + .inflate(R.layout.marker_teller_info_window, null) + + val tvName = infoWindowLayout.findViewById(R.id.tvName) + val tvAddress = infoWindowLayout.findViewById(R.id.tvAddress) + val tvPhone = infoWindowLayout.findViewById(R.id.tvPhone) + val tvTelegram = infoWindowLayout.findViewById(R.id.tvTelegram) + val tvKeybase = infoWindowLayout.findViewById(R.id.tvKeybase) + val tvWhatsapp = infoWindowLayout.findViewById(R.id.tvWhatsapp) + val tvViber = infoWindowLayout.findViewById(R.id.tvViber) + val tvEmail = infoWindowLayout.findViewById(R.id.tvEmail) + val tvWebsite = infoWindowLayout.findViewById(R.id.tvWebsite) if (selectedTeller != null) { tvName.text = selectedTeller?.gt_name diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/NetWorthFragment.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/NetWorthFragment.kt index 5996cfe..321f237 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/NetWorthFragment.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/NetWorthFragment.kt @@ -5,16 +5,25 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup 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( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { + ): View { 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 } } \ No newline at end of file diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/PINSecurityLockDialog.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/PINSecurityLockDialog.kt index ec2eeb2..2131380 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/PINSecurityLockDialog.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/PINSecurityLockDialog.kt @@ -10,11 +10,11 @@ import android.view.inputmethod.EditorInfo import com.google.firebase.crashlytics.FirebaseCrashlytics import com.jakewharton.rxbinding3.widget.textChanges import cy.agorise.bitsybitshareswallet.R +import cy.agorise.bitsybitshareswallet.databinding.DialogPinSecurityLockBinding import cy.agorise.bitsybitshareswallet.utils.Constants import cy.agorise.bitsybitshareswallet.utils.CryptoUtils import cy.agorise.bitsybitshareswallet.utils.hideKeyboard 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. @@ -25,9 +25,22 @@ class PINSecurityLockDialog : BaseSecurityLockDialog() { 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 = "" @@ -39,13 +52,13 @@ class PINSecurityLockDialog : BaseSecurityLockDialog() { crashlytics.setCustomKey(Constants.CRASHLYTICS_KEY_LAST_SCREEN, TAG) // 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) setupScreen() // 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 if (actionId == EditorInfo.IME_ACTION_GO) { if (currentStep == STEP_SECURITY_LOCK_VERIFY) { @@ -56,8 +69,8 @@ class PINSecurityLockDialog : BaseSecurityLockDialog() { if (hashedPIN == currentHashedPINPattern) { // PIN is correct, proceed resetIncorrectSecurityLockAttemptsAndTime() - tietPIN.hideKeyboard() - rootView.requestFocus() + binding.tietPIN.hideKeyboard() + binding.rootView.requestFocus() dismiss() mCallback?.onPINPatternEntered(actionIdentifier) } else { @@ -65,7 +78,7 @@ class PINSecurityLockDialog : BaseSecurityLockDialog() { 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 // 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() } @@ -81,7 +94,7 @@ class PINSecurityLockDialog : BaseSecurityLockDialog() { } else if (currentStep == STEP_SECURITY_LOCK_CONFIRM) { val pinConfirm = v.text.toString().trim() if (pinConfirm != newPIN) { - tvTitle.text = getString(R.string.title__pins_dont_match) + binding.tvTitle.text = getString(R.string.title__pins_dont_match) } else { val salt = CryptoUtils.generateSalt() val hashedPIN = CryptoUtils.createSHA256Hash(salt + pinConfirm) @@ -103,20 +116,21 @@ class PINSecurityLockDialog : BaseSecurityLockDialog() { } mDisposables.add( - tietPIN.textChanges() + binding.tietPIN.textChanges() .skipInitialValue() .observeOn(AndroidSchedulers.mainThread()) .subscribe { 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 - tilPIN.isErrorEnabled = false + binding.tilPIN.isErrorEnabled = false } else if (currentStep == STEP_SECURITY_LOCK_CREATE) { // Show the min length requirement for the PIN only when it has not been fulfilled if (it.trim().length >= Constants.MIN_PIN_LENGTH) { - tilPIN.helperText = "" + binding.tilPIN.helperText = "" } 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() { when (currentStep) { STEP_SECURITY_LOCK_VERIFY -> { - tvTitle.text = getString(R.string.title__re_enter_your_pin) - tvSubTitle.text = getString(R.string.msg__enter_your_pin) - tietPIN.isEnabled = true + binding.tvTitle.text = getString(R.string.title__re_enter_your_pin) + binding.tvSubTitle.text = getString(R.string.msg__enter_your_pin) + binding.tietPIN.isEnabled = true if (incorrectSecurityLockAttempts >= Constants.MAX_INCORRECT_SECURITY_LOCK_ATTEMPTS) { // User has entered the PIN incorrectly too many times val now = System.currentTimeMillis() if (now <= incorrectSecurityLockTime + Constants.INCORRECT_SECURITY_LOCK_COOLDOWN) { - tietPIN.setText("") - tietPIN.isEnabled = false + binding.tietPIN.setText("") + binding.tietPIN.isEnabled = false startContDownTimer() } else { resetIncorrectSecurityLockAttemptsAndTime() @@ -142,28 +156,28 @@ class PINSecurityLockDialog : BaseSecurityLockDialog() { } } STEP_SECURITY_LOCK_CREATE -> { - tvTitle.text = getString(R.string.title__set_bitsy_security_lock) - tvSubTitle.text = getString(R.string.msg__set_a_pin) - tilPIN.helperText = getString(R.string.msg__min_pin_length) - tilPIN.isErrorEnabled = false + binding.tvTitle.text = getString(R.string.title__set_bitsy_security_lock) + binding.tvSubTitle.text = getString(R.string.msg__set_a_pin) + binding.tilPIN.helperText = getString(R.string.msg__min_pin_length) + binding.tilPIN.isErrorEnabled = false } STEP_SECURITY_LOCK_CONFIRM -> { - tvTitle.text = getString(R.string.title__re_enter_your_pin) - tvSubTitle.text = "" - tvSubTitle.visibility = View.GONE - tietPIN.setText("") - tilPIN.helperText = "" - tilPIN.isErrorEnabled = false + binding.tvTitle.text = getString(R.string.title__re_enter_your_pin) + binding.tvSubTitle.text = "" + binding.tvSubTitle.visibility = View.GONE + binding.tietPIN.setText("") + binding.tilPIN.helperText = "" + binding.tilPIN.isErrorEnabled = false } } } override fun onTimerSecondPassed(errorMessage: String) { - tilPIN.error = errorMessage + binding.tilPIN.error = errorMessage } override fun onTimerFinished() { setupScreen() - tilPIN.isErrorEnabled = false + binding.tilPIN.isErrorEnabled = false } } \ No newline at end of file diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/PatternSecurityLockDialog.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/PatternSecurityLockDialog.kt index 26d193c..9fdb4e2 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/PatternSecurityLockDialog.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/PatternSecurityLockDialog.kt @@ -5,11 +5,11 @@ import android.preference.PreferenceManager import android.view.LayoutInflater import android.view.View 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.listener.PatternLockViewListener 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.CryptoUtils @@ -23,9 +23,22 @@ class PatternSecurityLockDialog : BaseSecurityLockDialog() { 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 = "" @@ -38,9 +51,9 @@ class PatternSecurityLockDialog : BaseSecurityLockDialog() { setupScreen() - patternLockView.addPatternLockListener(mPatternLockViewListener) + binding.patternLockView.addPatternLockListener(mPatternLockViewListener) - btnClear.setOnClickListener { setupScreen() } + binding.btnClear.setOnClickListener { setupScreen() } } private val mPatternLockViewListener = object : PatternLockViewListener { @@ -51,11 +64,11 @@ class PatternSecurityLockDialog : BaseSecurityLockDialog() { setMessage("") } STEP_SECURITY_LOCK_CREATE -> { - btnClear.visibility = View.INVISIBLE + binding.btnClear.visibility = View.INVISIBLE setMessage(getString(R.string.msg__release_finger)) } STEP_SECURITY_LOCK_CONFIRM -> { - btnClear.visibility = View.INVISIBLE + binding.btnClear.visibility = View.INVISIBLE setMessage(getString(R.string.msg__release_finger)) } } @@ -67,8 +80,10 @@ class PatternSecurityLockDialog : BaseSecurityLockDialog() { override fun onComplete(pattern: List) { if (currentStep == STEP_SECURITY_LOCK_VERIFY) { - val hashedPattern = CryptoUtils.createSHA256Hash(currentPINPatternSalt + - getStringPattern(pattern)) + val hashedPattern = CryptoUtils.createSHA256Hash( + currentPINPatternSalt + + getStringPattern(pattern) + ) if (hashedPattern == currentHashedPINPattern) { // Pattern is correct, proceed resetIncorrectSecurityLockAttemptsAndTime() @@ -84,17 +99,17 @@ class PatternSecurityLockDialog : BaseSecurityLockDialog() { setupScreen() } } else if (currentStep == STEP_SECURITY_LOCK_CREATE) { - btnClear.visibility = View.VISIBLE + binding.btnClear.visibility = View.VISIBLE if (pattern.size < 4) { setError(getString(R.string.error__connect_at_least_4_dots)) - patternLockView.setViewMode(PatternLockView.PatternViewMode.WRONG) + binding.patternLockView.setViewMode(PatternLockView.PatternViewMode.WRONG) } else { setMessage(getString(R.string.text__pattern_recorded)) - patternLockView.setViewMode(PatternLockView.PatternViewMode.CORRECT) - patternLockView.isInputEnabled = false - btnNext.isEnabled = true + binding.patternLockView.setViewMode(PatternLockView.PatternViewMode.CORRECT) + binding.patternLockView.isInputEnabled = false + binding.btnNext.isEnabled = true newPattern = getStringPattern(pattern) - btnNext.setOnClickListener { + binding.btnNext.setOnClickListener { currentStep = STEP_SECURITY_LOCK_CONFIRM setupScreen() } @@ -103,14 +118,14 @@ class PatternSecurityLockDialog : BaseSecurityLockDialog() { val patternConfirm = getStringPattern(pattern) if (patternConfirm != newPattern) { setError(getString(R.string.error__wront_pattern)) - btnNext.isEnabled = false - patternLockView.setViewMode(PatternLockView.PatternViewMode.WRONG) + binding.btnNext.isEnabled = false + binding.patternLockView.setViewMode(PatternLockView.PatternViewMode.WRONG) } else { setMessage(getString(R.string.msg__your_new_unlock_pattern)) - patternLockView.isEnabled = false - patternLockView.setViewMode(PatternLockView.PatternViewMode.CORRECT) - btnNext.isEnabled = true - btnNext.setOnClickListener { + binding.patternLockView.isEnabled = false + binding.patternLockView.setViewMode(PatternLockView.PatternViewMode.CORRECT) + binding.btnNext.isEnabled = true + binding.btnNext.setOnClickListener { context?.let { val salt = CryptoUtils.generateSalt() val hashedPattern = CryptoUtils.createSHA256Hash(salt + patternConfirm) @@ -119,7 +134,8 @@ class PatternSecurityLockDialog : BaseSecurityLockDialog() { PreferenceManager.getDefaultSharedPreferences(it).edit() .putString(Constants.KEY_HASHED_PIN_PATTERN, hashedPattern) .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() mCallback?.onPINPatternChanged() @@ -148,17 +164,17 @@ class PatternSecurityLockDialog : BaseSecurityLockDialog() { private fun setupScreen() { when (currentStep) { STEP_SECURITY_LOCK_VERIFY -> { - tvTitle.text = getString(R.string.title__re_enter_your_pattern) - tvSubTitle.text = getString(R.string.msg__enter_your_pattern) - btnClear.visibility = View.GONE - btnNext.visibility = View.GONE - patternLockView.isInputEnabled = true - patternLockView.isInStealthMode = true + binding.tvTitle.text = getString(R.string.title__re_enter_your_pattern) + binding.tvSubTitle.text = getString(R.string.msg__enter_your_pattern) + binding.btnClear.visibility = View.GONE + binding.btnNext.visibility = View.GONE + binding.patternLockView.isInputEnabled = true + binding.patternLockView.isInStealthMode = true if (incorrectSecurityLockAttempts >= Constants.MAX_INCORRECT_SECURITY_LOCK_ATTEMPTS) { // User has entered the Pattern incorrectly too many times val now = System.currentTimeMillis() if (now <= incorrectSecurityLockTime + Constants.INCORRECT_SECURITY_LOCK_COOLDOWN) { - patternLockView.isInputEnabled = false + binding.patternLockView.isInputEnabled = false startContDownTimer() } else { resetIncorrectSecurityLockAttemptsAndTime() @@ -166,24 +182,24 @@ class PatternSecurityLockDialog : BaseSecurityLockDialog() { } } STEP_SECURITY_LOCK_CREATE -> { - tvTitle.text = getString(R.string.title__set_bitsy_security_lock) - tvSubTitle.text = getString(R.string.msg__set_a_pattern) + binding.tvTitle.text = getString(R.string.title__set_bitsy_security_lock) + binding.tvSubTitle.text = getString(R.string.msg__set_a_pattern) setMessage(getString(R.string.text__draw_an_unlock_pattern)) - patternLockView.clearPattern() - patternLockView.isInputEnabled = true - btnClear.visibility = View.INVISIBLE - btnNext.isEnabled = false + binding.patternLockView.clearPattern() + binding.patternLockView.isInputEnabled = true + binding.btnClear.visibility = View.INVISIBLE + binding.btnNext.isEnabled = false } STEP_SECURITY_LOCK_CONFIRM -> { - tvTitle.text = getString(R.string.title__re_enter_your_pattern) - tvSubTitle.text = "" + binding.tvTitle.text = getString(R.string.title__re_enter_your_pattern) + binding.tvSubTitle.text = "" setMessage(getString(R.string.msg__draw_pattern_confirm)) - tvSubTitle.visibility = View.GONE - patternLockView.clearPattern() - patternLockView.isInputEnabled = true - btnClear.visibility = View.INVISIBLE - btnNext.isEnabled = false - btnNext.text = getString(R.string.button__confirm) + binding.tvSubTitle.visibility = View.GONE + binding.patternLockView.clearPattern() + binding.patternLockView.isInputEnabled = true + binding.btnClear.visibility = View.INVISIBLE + binding.btnNext.isEnabled = false + binding.btnNext.text = getString(R.string.button__confirm) } } } @@ -191,21 +207,21 @@ class PatternSecurityLockDialog : BaseSecurityLockDialog() { @Suppress("DEPRECATION") private fun setMessage(message: String) { 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 { - tvMessage.setTextAppearance(R.style.TextAppearance_Bitsy_Body2) + binding.tvMessage.setTextAppearance(R.style.TextAppearance_Bitsy_Body2) } - tvMessage.text = message + binding.tvMessage.text = message } @Suppress("DEPRECATION") private fun setError(error: String) { 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 { - 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) { diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/ReceiveTransactionFragment.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/ReceiveTransactionFragment.kt index 6dc425a..b54883b 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/ReceiveTransactionFragment.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/ReceiveTransactionFragment.kt @@ -19,6 +19,7 @@ import com.jakewharton.rxbinding3.widget.textChanges import cy.agorise.bitsybitshareswallet.R import cy.agorise.bitsybitshareswallet.adapters.AssetsAdapter import cy.agorise.bitsybitshareswallet.adapters.AutoSuggestAssetAdapter +import cy.agorise.bitsybitshareswallet.databinding.FragmentReceiveTransactionBinding import cy.agorise.bitsybitshareswallet.utils.Constants import cy.agorise.bitsybitshareswallet.utils.Helper 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.models.JsonRpcResponse import io.reactivex.android.schedulers.AndroidSchedulers -import kotlinx.android.synthetic.main.fragment_receive_transaction.* -import java.lang.Exception import java.math.RoundingMode import java.text.DecimalFormat import java.text.DecimalFormatSymbols @@ -53,6 +52,9 @@ class ReceiveTransactionFragment : ConnectedFragment() { private const val OTHER_ASSET = "other_asset" } + private var _binding: FragmentReceiveTransactionBinding? = null + private val binding get() = _binding!! + private lateinit var mViewModel: ReceiveTransactionViewModel /** Current user account */ @@ -75,7 +77,11 @@ class ReceiveTransactionFragment : ConnectedFragment() { // Map used to keep track of request and response id pairs private val responseMap = LongSparseArray() - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { setHasOptionsMenu(true) 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 val window = activity?.window context?.let { context -> - val statusBarColor = ContextCompat.getColor(context, - if (!nightMode) R.color.colorReceiveDark else R.color.colorStatusBarDark) + val statusBarColor = ContextCompat.getColor( + context, + if (!nightMode) R.color.colorReceiveDark else R.color.colorStatusBarDark + ) window?.statusBarColor = 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?) { @@ -110,9 +124,9 @@ class ReceiveTransactionFragment : ConnectedFragment() { .getString(Constants.KEY_CURRENT_ACCOUNT_ID, "") mViewModel.getUserAccount(userId!!).observe(this, - Observer{ user -> + Observer { user -> mUserAccount = UserAccount(user.id, user.name) - }) + }) mViewModel.getAllNonZero().observe(this, Observer> { assets -> @@ -121,8 +135,10 @@ class ReceiveTransactionFragment : ConnectedFragment() { // Add BTS to always show a QR if (mAssets.isEmpty()) - mAssets.add(cy.agorise.bitsybitshareswallet.database.entities.Asset( - "1.3.0", "BTS", 5, "", "") + mAssets.add( + cy.agorise.bitsybitshareswallet.database.entities.Asset( + "1.3.0", "BTS", 5, "", "" + ) ) mAssets.sortWith( @@ -135,33 +151,39 @@ class ReceiveTransactionFragment : ConnectedFragment() { ) mAssets.add(asset) - mAssetsAdapter = AssetsAdapter(context!!, android.R.layout.simple_spinner_item, mAssets) - spAsset.adapter = mAssetsAdapter + mAssetsAdapter = + AssetsAdapter(context!!, android.R.layout.simple_spinner_item, mAssets) + binding.spAsset.adapter = mAssetsAdapter // Try to select the selectedAssetSymbol for (i in 0 until mAssetsAdapter!!.count) { if (mAssetsAdapter!!.getItem(i)!!.symbol == selectedAssetSymbol) { - spAsset.setSelection(i) + binding.spAsset.setSelection(i) break } } - }) + }) mViewModel.qrCodeBitmap.observe(this, Observer { bitmap -> - ivQR.setImageBitmap(bitmap) + binding.ivQR.setImageBitmap(bitmap) }) - spAsset.onItemSelectedListener = object : AdapterView.OnItemSelectedListener{ - override fun onNothingSelected(parent: AdapterView<*>?) { } + binding.spAsset.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + 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 -> if (asset.id == OTHER_ASSET) { - tilAsset.visibility = View.VISIBLE - actvAsset.showKeyboard() + binding.tilAsset.visibility = View.VISIBLE + binding.actvAsset.showKeyboard() mAsset = null } else { - tilAsset.visibility = View.GONE + binding.tilAsset.visibility = View.GONE selectedAssetSymbol = asset.symbol 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 mDisposables.add( - tietAmount.textChanges() + binding.tietAmount.textChanges() .debounce(1000, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) - .subscribe( { updateQR() }, { mAsset = null} ) + .subscribe({ updateQR() }, { mAsset = null }) ) // Add adapter to the Assets AutoCompleteTextView - mAutoSuggestAssetAdapter = AutoSuggestAssetAdapter(context!!, android.R.layout.simple_dropdown_item_1line) - actvAsset.setAdapter(mAutoSuggestAssetAdapter) + 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 // the first call when the View is created mDisposables.add( - actvAsset.textChanges() + binding.actvAsset.textChanges() .skipInitialValue() .debounce(500, TimeUnit.MILLISECONDS) .map { it.toString().trim().toUpperCase() } .observeOn(AndroidSchedulers.mainThread()) - .subscribe( { + .subscribe({ if (!selectedInAutoCompleteTextView) { mAsset = null updateQR() @@ -201,15 +224,18 @@ class ReceiveTransactionFragment : ConnectedFragment() { // Get a list of assets that match the already typed string by the user if (it.length > 1 && mNetworkService != null) { - val id = mNetworkService?.sendMessage(ListAssets(it, AUTO_SUGGEST_ASSET_LIMIT), - ListAssets.REQUIRED_API) + val id = mNetworkService?.sendMessage( + ListAssets(it, AUTO_SUGGEST_ASSET_LIMIT), + ListAssets.REQUIRED_API + ) if (id != null) responseMap.append(id, RESPONSE_LIST_ASSETS) } - }, { mAsset = null } ) + }, { mAsset = null }) ) - actvAsset.setOnItemClickListener { parent, _, position, _ -> - val asset = parent.adapter.getItem(position) as cy.agorise.bitsybitshareswallet.database.entities.Asset + binding.actvAsset.setOnItemClickListener { parent, _, position, _ -> + val asset = + parent.adapter.getItem(position) as cy.agorise.bitsybitshareswallet.database.entities.Asset mAsset = Asset(asset.id, asset.toString(), asset.precision) selectedInAutoCompleteTextView = true updateQR() @@ -220,7 +246,7 @@ class ReceiveTransactionFragment : ConnectedFragment() { if (responseMap.containsKey(response.id)) { val responseType = responseMap[response.id] when (responseType) { - RESPONSE_LIST_ASSETS -> handleListAssets(response.result) + RESPONSE_LIST_ASSETS -> handleListAssets(response.result) } responseMap.remove(response.id) } @@ -261,7 +287,7 @@ class ReceiveTransactionFragment : ConnectedFragment() { private fun updateQR() { if (mAsset == null) { - ivQR.setImageDrawable(null) + binding.ivQR.setImageDrawable(null) // TODO clean the please pay and to text at the bottom too return } @@ -270,20 +296,22 @@ class ReceiveTransactionFragment : ConnectedFragment() { // Try to obtain the amount from the Amount Text Field or make it zero otherwise 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() - }catch (e: Exception) { + } catch (e: Exception) { 0 } val total = AssetAmount(UnsignedLong.valueOf(amount), asset) val totalInDouble = Util.fromBase(total) val items = arrayOf(LineItem("transfer", 1, totalInDouble)) - val invoice = Invoice(mUserAccount?.name, "", "", - asset.symbol.replaceFirst("bit", ""), items, "", "") + val invoice = Invoice( + mUserAccount?.name, "", "", + asset.symbol.replaceFirst("bit", ""), items, "", "" + ) Log.d(TAG, "invoice: " + invoice.toJsonString()) 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) } catch (e: NullPointerException) { 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 */ - 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) { getString(R.string.template__please_send, getString(R.string.text__any_amount), " ") } else { - val df = DecimalFormat("####."+("#".repeat(assetPrecision))) + val df = DecimalFormat("####." + ("#".repeat(assetPrecision))) df.roundingMode = RoundingMode.CEILING df.decimalFormatSymbols = DecimalFormatSymbols(Locale.getDefault()) @@ -309,8 +342,8 @@ class ReceiveTransactionFragment : ConnectedFragment() { val txtAccount = getString(R.string.template__to, account) - tvPleasePay.text = txtAmount - tvTo.text = txtAccount + binding.tvPleasePay.text = txtAmount + binding.tvTo.text = txtAccount } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { @@ -332,18 +365,28 @@ class ReceiveTransactionFragment : ConnectedFragment() { } private fun verifyStoragePermission() { - if (ContextCompat.checkSelfPermission(activity!!, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) - != PackageManager.PERMISSION_GRANTED) { + if (ContextCompat.checkSelfPermission( + activity!!, + android.Manifest.permission.WRITE_EXTERNAL_STORAGE + ) + != PackageManager.PERMISSION_GRANTED + ) { // Permission is not already granted - requestPermissions(arrayOf(android.Manifest.permission.WRITE_EXTERNAL_STORAGE), - REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION) + requestPermissions( + arrayOf(android.Manifest.permission.WRITE_EXTERNAL_STORAGE), + REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION + ) } else { // Permission is already granted shareQRScreenshot() } } - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION) { @@ -367,13 +410,13 @@ class ReceiveTransactionFragment : ConnectedFragment() { return // Get Screenshot - val screenshot = Helper.loadBitmapFromView(container) + val screenshot = Helper.loadBitmapFromView(binding.container) val imageUri = Helper.saveTemporalBitmap(context!!, screenshot) // Prepare information for share intent val subject = getString(R.string.msg__invoice_subject, mUserAccount?.name) - val content = tvPleasePay.text.toString() + "\n" + - tvTo.text.toString() + val content = binding.tvPleasePay.text.toString() + "\n" + + binding.tvTo.text.toString() // Create share intent and call it val shareIntent = Intent() diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/SendTransactionFragment.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/SendTransactionFragment.kt index cc4ec26..5734858 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/SendTransactionFragment.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/SendTransactionFragment.kt @@ -27,6 +27,7 @@ import com.jakewharton.rxbinding3.widget.textChanges import cy.agorise.bitsybitshareswallet.R import cy.agorise.bitsybitshareswallet.adapters.BalancesDetailsAdapter import cy.agorise.bitsybitshareswallet.database.joins.BalanceDetail +import cy.agorise.bitsybitshareswallet.databinding.FragmentSendTransactionBinding import cy.agorise.bitsybitshareswallet.utils.* import cy.agorise.bitsybitshareswallet.viewmodels.BalanceDetailViewModel 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.TransferOperationBuilder import io.reactivex.android.schedulers.AndroidSchedulers -import kotlinx.android.synthetic.main.fragment_send_transaction.* import me.dm7.barcodescanner.zxing.ZXingScannerView import org.bitcoinj.core.DumpedPrivateKey import org.bitcoinj.core.ECKey @@ -74,6 +74,9 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand private const val ACTION_SEND_TRANSFER = 1 } + private var _binding: FragmentSendTransactionBinding? = null + private val binding get() = _binding!! + // Navigation AAC Safe Args 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 */ 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) 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 val window = activity?.window context?.let { context -> - val statusBarColor = ContextCompat.getColor(context, - if (!nightMode) R.color.colorSendDark else R.color.colorStatusBarDark) + val statusBarColor = ContextCompat.getColor( + context, + if (!nightMode) R.color.colorSendDark else R.color.colorStatusBarDark + ) window?.statusBarColor = 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?) { @@ -147,7 +162,7 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand mUserAccount = UserAccount(userId) // 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, Observer { encryptedWIF -> @@ -168,37 +183,43 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand }, 500) } - fabOpenCamera.setOnClickListener { if (isCameraPreviewVisible) stopCameraPreview() else verifyCameraPermission() } + binding.fabOpenCamera.setOnClickListener { if (isCameraPreviewVisible) stopCameraPreview() else verifyCameraPermission() } // 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> { balancesDetails -> - mBalancesDetails.clear() - mBalancesDetails.addAll(balancesDetails) - mBalancesDetails.sortWith( - Comparator { a, b -> a.toString().compareTo(b.toString(), true) } - ) - mBalancesDetailsAdapter = BalancesDetailsAdapter(context!!, android.R.layout.simple_spinner_item, mBalancesDetails) - spAsset.adapter = mBalancesDetailsAdapter + mBalanceDetailViewModel.getAll() + .observe(this, Observer> { balancesDetails -> + mBalancesDetails.clear() + mBalancesDetails.addAll(balancesDetails) + mBalancesDetails.sortWith( + Comparator { a, b -> a.toString().compareTo(b.toString(), true) } + ) + mBalancesDetailsAdapter = BalancesDetailsAdapter( + context!!, + android.R.layout.simple_spinner_item, + mBalancesDetails + ) + binding.spAsset.adapter = mBalancesDetailsAdapter - // Try to select the selectedAssetSymbol - for (i in 0 until mBalancesDetailsAdapter!!.count) { - if (mBalancesDetailsAdapter!!.getItem(i)!!.symbol == selectedAssetSymbol) { - spAsset.setSelection(i) - break + // Try to select the selectedAssetSymbol + for (i in 0 until mBalancesDetailsAdapter!!.count) { + if (mBalancesDetailsAdapter!!.getItem(i)!!.symbol == selectedAssetSymbol) { + binding.spAsset.setSelection(i) + break + } } - } - }) + }) - spAsset.onItemSelectedListener = assetItemSelectedListener + binding.spAsset.onItemSelectedListener = assetItemSelectedListener - fabSendTransaction.setOnClickListener { verifySecurityLockSendTransfer() } - fabSendTransaction.disable(R.color.lightGray) + binding.fabSendTransaction.setOnClickListener { verifySecurityLockSendTransfer() } + binding.fabSendTransaction.disable(R.color.lightGray) // Use RxJava Debounce to avoid making calls to the NetworkService on every text change event mDisposables.add( - tietTo.textChanges() + binding.tietTo.textChanges() .skipInitialValue() .debounce(500, TimeUnit.MILLISECONDS) .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 mDisposables.add( - tietAmount.textChanges() + binding.tietAmount.textChanges() .skipInitialValue() .debounce(500, TimeUnit.MILLISECONDS) .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 mDisposables.add( - tietMemo.textChanges() + binding.tietMemo.textChanges() .skipInitialValue() .debounce(500, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) .map { it.toString().trim() } .subscribe { - isMemoCorrect = it.length <= tilMemo.counterMaxLength + isMemoCorrect = it.length <= binding.tilMemo.counterMaxLength 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 * current user's balance of the selected asset. */ - private val assetItemSelectedListener = object : AdapterView.OnItemSelectedListener{ - override fun onNothingSelected(parent: AdapterView<*>?) { } + private val assetItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onNothingSelected(parent: AdapterView<*>?) {} override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { 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()) - tvAvailableAssetAmount.text = - String.format("%." + Math.min(balance.precision, 8) + "f %s", amount, balance.toString()) + binding.tvAvailableAssetAmount.text = + String.format( + "%." + Math.min(balance.precision, 8) + "f %s", + amount, + balance.toString() + ) validateAmount() } @@ -262,10 +287,10 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand override fun handleJsonRpcResponse(response: JsonRpcResponse<*>) { if (responseMap.containsKey(response.id)) { when (responseMap[response.id]) { - RESPONSE_GET_ACCOUNT_BY_NAME -> handleAccountProperties(response.result) - RESPONSE_GET_DYNAMIC_GLOBAL_PROPERTIES -> handleDynamicGlobalProperties(response.result) - RESPONSE_GET_REQUIRED_FEES -> handleRequiredFees(response.result) - RESPONSE_BROADCAST_TRANSACTION -> handleBroadcastTransaction(response) + RESPONSE_GET_ACCOUNT_BY_NAME -> handleAccountProperties(response.result) + RESPONSE_GET_DYNAMIC_GLOBAL_PROPERTIES -> handleDynamicGlobalProperties(response.result) + RESPONSE_GET_REQUIRED_FEES -> handleRequiredFees(response.result) + RESPONSE_BROADCAST_TRANSACTION -> handleBroadcastTransaction(response) } responseMap.remove(response.id) } @@ -285,12 +310,12 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand if (result is AccountProperties) { mSelectedUserAccount = UserAccount(result.id, result.name) destinationPublicKey = result.active.keyAuths.keys.iterator().next() - tilTo.isErrorEnabled = false + binding.tilTo.isErrorEnabled = false isToAccountCorrect = true } else { mSelectedUserAccount = null destinationPublicKey = null - tilTo.error = getString(R.string.error__invalid_account) + binding.tilTo.error = getString(R.string.error__invalid_account) isToAccountCorrect = false } @@ -320,7 +345,10 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand 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) } else { context?.toast(getString(R.string.msg__transaction_not_sent)) @@ -334,7 +362,10 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand Log.d(TAG, "GetRequiredFees: " + transaction.toString()) transaction!!.setFees(result as List) // 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) } else { 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 */ private fun verifyCameraPermission() { if (ContextCompat.checkSelfPermission(activity!!, android.Manifest.permission.CAMERA) - != PackageManager.PERMISSION_GRANTED) { + != PackageManager.PERMISSION_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 { // Permission is already granted startCameraPreview() @@ -367,7 +402,11 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand } /** Handles the result from the camera permission request */ - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == REQUEST_CAMERA_PERMISSION) { @@ -381,27 +420,27 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand } private fun startCameraPreview() { - cameraPreview.visibility = View.VISIBLE - fabOpenCamera.setImageResource(R.drawable.ic_close) + binding.cameraPreview.visibility = View.VISIBLE + binding.fabOpenCamera.setImageResource(R.drawable.ic_close) isCameraPreviewVisible = true // Configure QR scanner - cameraPreview.setFormats(listOf(BarcodeFormat.QR_CODE)) - cameraPreview.setAspectTolerance(0.5f) - cameraPreview.setAutoFocus(true) - cameraPreview.setLaserColor(R.color.colorSecondary) - cameraPreview.setMaskColor(R.color.colorSecondary) - cameraPreview.setResultHandler(this) - cameraPreview.startCamera() + binding.cameraPreview.setFormats(listOf(BarcodeFormat.QR_CODE)) + binding.cameraPreview.setAspectTolerance(0.5f) + binding.cameraPreview.setAutoFocus(true) + binding.cameraPreview.setLaserColor(R.color.colorSecondary) + binding.cameraPreview.setMaskColor(R.color.colorSecondary) + binding.cameraPreview.setResultHandler(this) + binding.cameraPreview.startCamera() - cameraPreview.scrollY = holderCamera.width / 6 + binding.cameraPreview.scrollY = binding.holderCamera.width / 6 } private fun stopCameraPreview() { - cameraPreview.visibility = View.INVISIBLE - fabOpenCamera.setImageResource(R.drawable.ic_camera) + binding.cameraPreview.visibility = View.INVISIBLE + binding.fabOpenCamera.setImageResource(R.drawable.ic_camera) isCameraPreviewVisible = false - cameraPreview.stopCamera() + binding.cameraPreview.stopCamera() } /** Handles the result of the QR code read from the camera **/ @@ -410,19 +449,19 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand } /** Tries to populate the Account, Amount and Memo fields - * and the Asset spinner with the obtained information */ + * and the Asset spinner with the obtained information */ private fun populatePropertiesFromQRCodeString(qrString: String) { try { val invoice = Invoice.fromQrCode(qrString) Log.d(TAG, "QR Code read: " + invoice.toJsonString()) - tietTo.setText(invoice.to) + binding.tietTo.setText(invoice.to) if (invoice.memo != null) { - tietMemo.setText(invoice.memo) + binding.tietMemo.setText(invoice.memo) if (invoice.memo.startsWith("PP")) - tietMemo.isEnabled = false + binding.tietMemo.isEnabled = false } var balanceDetail: BalanceDetail? = null @@ -432,8 +471,9 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand if (mBalancesDetailsAdapter?.getItem(i)?.symbol == invoice.currency.toUpperCase() || (invoice.currency.startsWith("bit", true) && invoice.currency.replaceFirst("bit", "").toUpperCase() == - mBalancesDetailsAdapter?.getItem(i)?.symbol)) { - spAsset.setSelection(i) + mBalancesDetailsAdapter?.getItem(i)?.symbol) + ) { + binding.spAsset.setSelection(i) balanceDetail = mBalancesDetailsAdapter?.getItem(i) 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 // return early to avoid filling the asset field if (balanceDetail == null) { - Snackbar.make(rootView, getString(R.string.error__you_dont_own_asset, invoice.currency.toUpperCase()), - Snackbar.LENGTH_INDEFINITE).setAction(android.R.string.ok) { }.show() + Snackbar.make( + binding.rootView, + getString(R.string.error__you_dont_own_asset, invoice.currency.toUpperCase()), + Snackbar.LENGTH_INDEFINITE + ).setAction(android.R.string.ok) { }.show() return } @@ -457,12 +500,12 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand val df = DecimalFormat("####." + "#".repeat(balanceDetail.precision)) df.roundingMode = RoundingMode.CEILING df.decimalFormatSymbols = DecimalFormatSymbols(Locale.getDefault()) - tietAmount.setText(df.format(amount)) + binding.tietAmount.setText(df.format(amount)) } else { - tietAmount.setText("") + binding.tietAmount.setText("") } - }catch (e: Exception) { + } catch (e: Exception) { Log.d(TAG, "Invoice error: " + e.message) } } @@ -473,15 +516,19 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand */ private fun validateAccount(accountName: String) { 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) } private fun validateAmount() { - val txtAmount = tietAmount.text.toString().replace(",", ".") + val txtAmount = binding.tietAmount.text.toString().replace(",", ".") 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 amount: Double = try { @@ -492,15 +539,15 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand when { currentAmount < amount -> { - tilAmount.error = getString(R.string.error__not_enough_funds) + binding.tilAmount.error = getString(R.string.error__not_enough_funds) isAmountCorrect = false } amount == 0.0 -> { - tilAmount.isErrorEnabled = false + binding.tilAmount.isErrorEnabled = false isAmountCorrect = false } else -> { - tilAmount.isErrorEnabled = false + binding.tilAmount.isErrorEnabled = false isAmountCorrect = true } } @@ -510,11 +557,11 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand private fun enableDisableSendFAB() { if (isToAccountCorrect && isAmountCorrect && isMemoCorrect) { - fabSendTransaction.enable(R.color.colorSend) - vSend.setBackgroundResource(R.drawable.send_fab_background) + binding.fabSendTransaction.enable(R.color.colorSend) + binding.vSend.setBackgroundResource(R.drawable.send_fab_background) } else { - fabSendTransaction.disable(R.color.lightGray) - vSend.setBackgroundResource(R.drawable.send_fab_background_disabled) + binding.fabSendTransaction.disable(R.color.lightGray) + 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 val args = Bundle() - args.putInt(BaseSecurityLockDialog.KEY_STEP_SECURITY_LOCK, - BaseSecurityLockDialog.STEP_SECURITY_LOCK_VERIFY) + args.putInt( + BaseSecurityLockDialog.KEY_STEP_SECURITY_LOCK, + BaseSecurityLockDialog.STEP_SECURITY_LOCK_VERIFY + ) args.putInt(BaseSecurityLockDialog.KEY_ACTION_IDENTIFIER, ACTION_SEND_TRANSFER) 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 * NetworkService to obtain the [DynamicGlobalProperties] object needed to successfully send a Transfer */ private fun startSendTransferOperation() { // Create TransferOperation if (mNetworkService?.isConnected == true) { - val balance = mBalancesDetailsAdapter!!.getItem(spAsset.selectedItemPosition)!! - val amount = (tietAmount.text.toString().replace(",", ".").toDouble() + val balance = mBalancesDetailsAdapter!!.getItem(binding.spAsset.selectedItemPosition)!! + val amount = (binding.tietAmount.text.toString().replace(",", ".").toDouble() * Math.pow(10.0, balance.precision.toDouble())).toLong() val transferAmount = AssetAmount(UnsignedLong.valueOf(amount), Asset(balance.id)) @@ -574,13 +624,16 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand .setDestination(mSelectedUserAccount) .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 - val memoMsg = tietMemo.text.toString() + val memoMsg = binding.tietMemo.text.toString() if (memoMsg.isNotEmpty()) { - val nonce = Math.abs(SecureRandomGenerator.getSecureRandom().nextLong()).toBigInteger() - val encryptedMemo = Memo.encryptMessage(privateKey, destinationPublicKey!!, nonce, memoMsg) + val nonce = + Math.abs(SecureRandomGenerator.getSecureRandom().nextLong()).toBigInteger() + val encryptedMemo = + Memo.encryptMessage(privateKey, destinationPublicKey!!, nonce, memoMsg) val from = Address(ECKey.fromPublicOnly(privateKey.pubKey)) val to = Address(destinationPublicKey!!.key) val memo = Memo(from, to, nonce, encryptedMemo) @@ -601,9 +654,11 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand transaction = Transaction(privateKey, null, operations) // Start the send transaction procedure which includes a series of calls - val id = mNetworkService?.sendMessage(GetDynamicGlobalProperties(), - GetDynamicGlobalProperties.REQUIRED_API) - if (id != null ) responseMap.append(id, RESPONSE_GET_DYNAMIC_GLOBAL_PROPERTIES) + val id = mNetworkService?.sendMessage( + GetDynamicGlobalProperties(), + GetDynamicGlobalProperties.REQUIRED_API + ) + if (id != null) responseMap.append(id, RESPONSE_GET_DYNAMIC_GLOBAL_PROPERTIES) } else Log.d(TAG, "Network Service is not connected") } @@ -634,8 +689,12 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand return null // Verify that the current Asset is either BTS or a SmartCoin - if (Constants.assetsWhichSendFeeToAgorise.contains(transferOperation.assetAmount?.asset?.objectId ?: "")) { - val fee = transferOperation.assetAmount?.multiplyBy(Constants.FEE_PERCENTAGE) ?: return null + if (Constants.assetsWhichSendFeeToAgorise.contains( + transferOperation.assetAmount?.asset?.objectId ?: "" + ) + ) { + val fee = + transferOperation.assetAmount?.multiplyBy(Constants.FEE_PERCENTAGE) ?: return null return TransferOperationBuilder() .setSource(mUserAccount) diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/SettingsFragment.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/SettingsFragment.kt index 1d74838..b94e780 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/SettingsFragment.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/SettingsFragment.kt @@ -1,6 +1,6 @@ package cy.agorise.bitsybitshareswallet.fragments -import android.content.* +import android.content.Context import android.os.Bundle import android.os.Handler import android.preference.PreferenceManager @@ -10,6 +10,7 @@ import android.view.View import android.view.ViewGroup import androidx.appcompat.widget.Toolbar import androidx.collection.LongSparseArray +import androidx.core.content.res.ResourcesCompat import androidx.lifecycle.ViewModelProviders import androidx.recyclerview.widget.LinearLayoutManager import com.afollestad.materialdialogs.MaterialDialog @@ -22,6 +23,7 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics import cy.agorise.bitsybitshareswallet.BuildConfig import cy.agorise.bitsybitshareswallet.R import cy.agorise.bitsybitshareswallet.adapters.FullNodesAdapter +import cy.agorise.bitsybitshareswallet.databinding.FragmentSettingsBinding import cy.agorise.bitsybitshareswallet.repositories.AuthorityRepository import cy.agorise.bitsybitshareswallet.utils.Constants import cy.agorise.bitsybitshareswallet.utils.CryptoUtils @@ -36,7 +38,6 @@ import cy.agorise.graphenej.models.JsonRpcResponse import cy.agorise.graphenej.operations.AccountUpgradeOperationBuilder import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers -import kotlinx.android.synthetic.main.fragment_settings.* import org.bitcoinj.core.DumpedPrivateKey import org.bitcoinj.core.ECKey import java.text.NumberFormat @@ -59,6 +60,9 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter private const val RESPONSE_BROADCAST_TRANSACTION = 3 } + private var _binding: FragmentSettingsBinding? = null + private val binding get() = _binding!! + private lateinit var mViewModel: SettingsFragmentViewModel private var mUserAccount: UserAccount? = null @@ -82,17 +86,27 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter 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) val nightMode = PreferenceManager.getDefaultSharedPreferences(context) - .getBoolean(Constants.KEY_NIGHT_MODE_ACTIVATED, false) + .getBoolean(Constants.KEY_NIGHT_MODE_ACTIVATED, false) // Make sure the toolbar show the correct colors in both day and night modes val toolbar: Toolbar? = activity?.findViewById(R.id.toolbar) 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?) { @@ -105,15 +119,16 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter .getString(Constants.KEY_CURRENT_ACCOUNT_ID, "") ?: "" // Configure ViewModel - mViewModel= ViewModelProviders.of(this).get(SettingsFragmentViewModel::class.java) + mViewModel = ViewModelProviders.of(this).get(SettingsFragmentViewModel::class.java) mViewModel.getUserAccount(userId).observe(this, - androidx.lifecycle.Observer{ userAccount -> + androidx.lifecycle.Observer { userAccount -> if (userAccount != null) { 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 } - }) + }) mViewModel.getWIF(userId, AuthorityType.ACTIVE.ordinal).observe(this, androidx.lifecycle.Observer { encryptedWIF -> @@ -121,7 +136,10 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter try { privateKey = CryptoUtils.decrypt(it, encryptedWIF) } 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) { crashlytics.recordException(e) } @@ -132,22 +150,23 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter 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 val securityLockSelected = PreferenceManager.getDefaultSharedPreferences(context) - .getInt(Constants.KEY_SECURITY_LOCK_SELECTED, 0) + .getInt(Constants.KEY_SECURITY_LOCK_SELECTED, 0) // Security Lock Options // 0 -> None // 1 -> PIN // 2 -> Pattern - tvSecurityLockSelected.text = resources.getStringArray(R.array.security_lock_options)[securityLockSelected] + binding.tvSecurityLockSelected.text = + resources.getStringArray(R.array.security_lock_options)[securityLockSelected] - tvSecurityLock.setOnClickListener { onSecurityLockTextSelected() } - tvSecurityLockSelected.setOnClickListener { onSecurityLockTextSelected() } + binding.tvSecurityLock.setOnClickListener { onSecurityLockTextSelected() } + binding.tvSecurityLockSelected.setOnClickListener { onSecurityLockTextSelected() } - btnViewBrainKey.setOnClickListener { onShowBrainKeyButtonSelected() } + binding.btnViewBrainKey.setOnClickListener { onShowBrainKeyButtonSelected() } val lastAccountBackup = PreferenceManager.getDefaultSharedPreferences(context) .getLong(Constants.KEY_LAST_ACCOUNT_BACKUP, 0L) @@ -155,11 +174,11 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter val now = System.currentTimeMillis() 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) { @@ -191,7 +210,13 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter mNodesDialogLinearLayoutManager = LinearLayoutManager(v.context) 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, "-------")) customListAdapter(nodesAdapter as FullNodesAdapter, mNodesDialogLinearLayoutManager) negativeButton(android.R.string.ok) @@ -218,9 +243,13 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter override fun handleJsonRpcResponse(response: JsonRpcResponse<*>) { if (responseMap.containsKey(response.id)) { when (responseMap[response.id]) { - RESPONSE_GET_DYNAMIC_GLOBAL_PROPERTIES_NODES -> handleDynamicGlobalPropertiesNodes(response.result) - RESPONSE_GET_DYNAMIC_GLOBAL_PROPERTIES_LTM -> handleDynamicGlobalPropertiesLTM(response.result) - RESPONSE_BROADCAST_TRANSACTION -> handleBroadcastTransaction(response) + RESPONSE_GET_DYNAMIC_GLOBAL_PROPERTIES_NODES -> handleDynamicGlobalPropertiesNodes( + response.result + ) + RESPONSE_GET_DYNAMIC_GLOBAL_PROPERTIES_LTM -> handleDynamicGlobalPropertiesLTM( + response.result + ) + RESPONSE_BROADCAST_TRANSACTION -> handleBroadcastTransaction(response) } responseMap.remove(response.id) } @@ -238,13 +267,17 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter } private fun showConnectedState() { - tvNetworkStatus.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, - resources.getDrawable(R.drawable.ic_connected, null), null) + binding.tvNetworkStatus.setCompoundDrawablesRelativeWithIntrinsicBounds( + null, null, + ResourcesCompat.getDrawable(resources, R.drawable.ic_connected, null), null + ) } private fun showDisconnectedState() { - tvNetworkStatus.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, - resources.getDrawable(R.drawable.ic_disconnected, null), null) + binding.tvNetworkStatus.setCompoundDrawablesRelativeWithIntrinsicBounds( + 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 @@ -253,7 +286,9 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter if (result is DynamicGlobalProperties) { if (mNodesDialog != null && mNodesDialog?.isShowing == true) { 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) - 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) // 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 { 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) mHandler.postDelayed(this, Constants.BLOCK_PERIOD) @@ -320,9 +361,9 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter val autoCloseOn = PreferenceManager.getDefaultSharedPreferences(context) .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() .putBoolean(Constants.KEY_AUTO_CLOSE_ACTIVATED, isChecked).apply() } @@ -337,9 +378,9 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter val nightModeOn = PreferenceManager.getDefaultSharedPreferences(context) .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() .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 val args = Bundle() - args.putInt(BaseSecurityLockDialog.KEY_STEP_SECURITY_LOCK, - BaseSecurityLockDialog.STEP_SECURITY_LOCK_VERIFY) + args.putInt( + BaseSecurityLockDialog.KEY_STEP_SECURITY_LOCK, + BaseSecurityLockDialog.STEP_SECURITY_LOCK_VERIFY + ) args.putInt(BaseSecurityLockDialog.KEY_ACTION_IDENTIFIER, actionIdentifier) return when (securityLockSelected) { @@ -397,9 +440,9 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter override fun onPINPatternEntered(actionIdentifier: Int) { when (actionIdentifier) { ACTION_CHANGE_SECURITY_LOCK -> showChooseSecurityLockDialog() - ACTION_SHOW_BRAINKEY -> getBrainkey() - ACTION_UPGRADE_TO_LTM -> showUpgradeToLTMDialog() - ACTION_REMOVE_ACCOUNT -> showRemoveAccountDialog() + ACTION_SHOW_BRAINKEY -> getBrainkey() + ACTION_UPGRADE_TO_LTM -> showUpgradeToLTMDialog() + ACTION_REMOVE_ACCOUNT -> showRemoveAccountDialog() } } @@ -412,7 +455,8 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter // 1 -> PIN // 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 { MaterialDialog(it).show { 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 val args = Bundle() - args.putInt(BaseSecurityLockDialog.KEY_STEP_SECURITY_LOCK, - BaseSecurityLockDialog.STEP_SECURITY_LOCK_CREATE) + args.putInt( + BaseSecurityLockDialog.KEY_STEP_SECURITY_LOCK, + BaseSecurityLockDialog.STEP_SECURITY_LOCK_CREATE + ) args.putInt(BaseSecurityLockDialog.KEY_ACTION_IDENTIFIER, -1) when (index) { 0 -> { /* None */ PreferenceManager.getDefaultSharedPreferences(context).edit() - .putInt(Constants.KEY_SECURITY_LOCK_SELECTED, 0).apply() // 0 -> None + .putInt(Constants.KEY_SECURITY_LOCK_SELECTED, 0) + .apply() // 0 -> None // Call this function to update the UI onPINPatternChanged() @@ -491,7 +541,8 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter .subscribeOn(Schedulers.io()) .map { authority -> 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) BrainKey(plainBrainKey, sequenceNumber) } @@ -518,7 +569,7 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter val now = System.currentTimeMillis() PreferenceManager.getDefaultSharedPreferences(it.context).edit() .putLong(Constants.KEY_LAST_ACCOUNT_BACKUP, now).apply() - tvBackupWarning.visibility = View.GONE + binding.tvBackupWarning.visibility = View.GONE } dialog.show() @@ -541,11 +592,18 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter operations.add(operation) val currentPrivateKey = ECKey.fromPrivate( - DumpedPrivateKey.fromBase58(null, privateKey).key.privKeyBytes) + DumpedPrivateKey.fromBase58(null, privateKey).key.privKeyBytes + ) ltmTransaction = Transaction(currentPrivateKey, null, operations) - val id = mNetworkService?.sendMessage(GetDynamicGlobalProperties(), GetDynamicGlobalProperties.REQUIRED_API) - if (id != null) responseMap.append(id, RESPONSE_GET_DYNAMIC_GLOBAL_PROPERTIES_LTM) + val id = mNetworkService?.sendMessage( + 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. 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 // single activity architecture. diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/TransactionsFragment.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/TransactionsFragment.kt index 65782e6..f19752b 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/TransactionsFragment.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/TransactionsFragment.kt @@ -21,12 +21,12 @@ import com.jakewharton.rxbinding3.appcompat.queryTextChangeEvents import cy.agorise.bitsybitshareswallet.R import cy.agorise.bitsybitshareswallet.adapters.TransfersDetailsAdapter import cy.agorise.bitsybitshareswallet.database.joins.TransferDetail +import cy.agorise.bitsybitshareswallet.databinding.FragmentTransactionsBinding import cy.agorise.bitsybitshareswallet.models.FilterOptions import cy.agorise.bitsybitshareswallet.utils.* import cy.agorise.bitsybitshareswallet.viewmodels.TransactionsViewModel import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable -import kotlinx.android.synthetic.main.fragment_transactions.* import java.io.File import java.util.concurrent.TimeUnit @@ -42,14 +42,27 @@ class TransactionsFragment : Fragment(), FilterOptionsDialog.OnFilterOptionsSele 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 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) - 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?) { @@ -62,8 +75,8 @@ class TransactionsFragment : Fragment(), FilterOptionsDialog.OnFilterOptionsSele .getString(Constants.KEY_CURRENT_ACCOUNT_ID, "") ?: "" val transfersDetailsAdapter = TransfersDetailsAdapter(context!!) - rvTransactions.adapter = transfersDetailsAdapter - rvTransactions.layoutManager = LinearLayoutManager(context) + binding.rvTransactions.adapter = transfersDetailsAdapter + binding.rvTransactions.layoutManager = LinearLayoutManager(context) // Configure TransactionsViewModel to fetch the transaction history mViewModel = ViewModelProviders.of(this).get(TransactionsViewModel::class.java) @@ -71,11 +84,11 @@ class TransactionsFragment : Fragment(), FilterOptionsDialog.OnFilterOptionsSele mViewModel.getFilteredTransactions(userId).observe(this, Observer> { transactions -> if (transactions.isEmpty()) { - rvTransactions.visibility = View.GONE - tvEmpty.visibility = View.VISIBLE + binding.rvTransactions.visibility = View.GONE + binding.tvEmpty.visibility = View.VISIBLE } else { - rvTransactions.visibility = View.VISIBLE - tvEmpty.visibility = View.GONE + binding.rvTransactions.visibility = View.VISIBLE + binding.tvEmpty.visibility = View.GONE val shouldScrollUp = transactions.size - transfersDetailsAdapter.itemCount == 1 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 // which most likely means a new transaction was received/sent. if (shouldScrollUp) - rvTransactions.scrollToPosition(0) + binding.rvTransactions.scrollToPosition(0) } - }) + }) // Set custom touch listener to handle bounce/stretch effect - val bounceTouchListener = BounceTouchListener(rvTransactions) - rvTransactions.setOnTouchListener(bounceTouchListener) + val bounceTouchListener = BounceTouchListener(binding.rvTransactions) + binding.rvTransactions.setOnTouchListener(bounceTouchListener) } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { @@ -118,7 +131,10 @@ class TransactionsFragment : Fragment(), FilterOptionsDialog.OnFilterOptionsSele R.id.menu_filter -> { val filterOptionsDialog = FilterOptionsDialog() val args = Bundle() - args.putParcelable(FilterOptionsDialog.KEY_FILTER_OPTIONS, mViewModel.getFilterOptions()) + args.putParcelable( + FilterOptionsDialog.KEY_FILTER_OPTIONS, + mViewModel.getFilterOptions() + ) filterOptionsDialog.arguments = args filterOptionsDialog.show(childFragmentManager, "filter-options-tag") 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 */ private fun verifyStoragePermission() { - if (ContextCompat.checkSelfPermission(activity!!, Manifest.permission.WRITE_EXTERNAL_STORAGE) - != PackageManager.PERMISSION_GRANTED) { + if (ContextCompat.checkSelfPermission( + activity!!, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ) + != PackageManager.PERMISSION_GRANTED + ) { // Permission is not already granted - requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), - REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION) + requestPermissions( + arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), + REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION + ) } else { // Permission is already granted 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 * dialog, but if it was not accepted then shows a toast explaining that the permission is necessary to generate * the export options */ - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION) { if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) { @@ -181,7 +207,10 @@ class TransactionsFragment : Fragment(), FilterOptionsDialog.OnFilterOptionsSele private fun showExportOptionsDialog() { MaterialDialog(context!!).show { 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 exportCSV = indices.contains(1) exportFilteredTransactions(exportPDF, exportCSV) diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/models/FilterOptions.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/models/FilterOptions.kt index 0e77359..1f03878 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/models/FilterOptions.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/models/FilterOptions.kt @@ -1,14 +1,14 @@ package cy.agorise.bitsybitshareswallet.models import android.os.Parcelable -import kotlinx.android.parcel.Parcelize import cy.agorise.bitsybitshareswallet.fragments.TransactionsFragment +import kotlinx.parcelize.Parcelize /** * Model that includes all the options to filter the transactions in the [TransactionsFragment] */ @Parcelize -data class FilterOptions ( +data class FilterOptions( var query: String = "", var transactionsDirection: Int = 0, var dateRangeAll: Boolean = true, @@ -20,4 +20,4 @@ data class FilterOptions ( var fromEquivalentValue: Long = 0L, var toEquivalentValue: Long = 5000L, var agoriseFees: Boolean = true -) : Parcelable \ No newline at end of file +) : Parcelable diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 937f012..3d25e2e 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -16,7 +16,7 @@ android:elevation="4dp" android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar" /> -