From be5c9bcea7fa90ce45cf2a22ce1d4a92a5585b30 Mon Sep 17 00:00:00 2001 From: Severiano Jaramillo Date: Mon, 22 Mar 2021 22:31:40 -0700 Subject: [PATCH 01/27] Update Navigation and Lifecycle libraries. - Updated the Navigation architecture component library to the latest stable version, with no big changes required. - Updated the Lifecycle architecture component library to the latest stable version, along with a bunch of changes related to the deprecation of some methods and the addition of others. --- app/build.gradle | 6 +- .../activities/ConnectedActivity.kt | 152 ++++++++++-------- .../fragments/BalancesFragment.kt | 24 ++- .../fragments/BaseAccountFragment.kt | 50 ++++-- .../fragments/CreateAccountFragment.kt | 5 +- .../fragments/EReceiptFragment.kt | 21 ++- .../fragments/FilterOptionsDialog.kt | 49 +++--- .../fragments/HomeFragment.kt | 49 +++--- .../fragments/ImportBrainkeyFragment.kt | 2 +- .../fragments/MerchantsFragment.kt | 22 +-- .../fragments/ReceiveTransactionFragment.kt | 83 +++++----- .../fragments/SendTransactionFragment.kt | 69 ++++---- .../fragments/SettingsFragment.kt | 29 ++-- .../fragments/TransactionsFragment.kt | 55 +++---- build.gradle | 4 +- 15 files changed, 312 insertions(+), 308 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index b36e918..0ef4c63 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -63,7 +63,7 @@ android { } dependencies { - def lifecycle_version = "2.2.0" + def lifecycle_version = "2.3.0" def arch_version = "2.1.0" def room_version = "2.2.6" def rx_bindings_version = '3.0.0' @@ -83,8 +83,8 @@ dependencies { implementation 'com.google.android.gms:play-services-maps:17.0.0' implementation 'com.google.maps.android:android-maps-utils:0.5' // AAC Lifecycle - implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" - implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" // viewModelScope + implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" // AAC Room implementation "androidx.room:room-runtime:$room_version" diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/activities/ConnectedActivity.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/activities/ConnectedActivity.kt index 0904194..371a5bb 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/activities/ConnectedActivity.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/activities/ConnectedActivity.kt @@ -7,10 +7,9 @@ import android.os.Handler import android.preference.PreferenceManager import android.util.Log import android.widget.Toast +import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.core.content.pm.PackageInfoCompat -import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModelProviders import com.google.firebase.crashlytics.FirebaseCrashlytics import cy.agorise.bitsybitshareswallet.database.entities.Balance import cy.agorise.bitsybitshareswallet.database.entities.Transfer @@ -69,10 +68,11 @@ abstract class ConnectedActivity : AppCompatActivity() { private const val RESPONSE_GET_MARKET_HISTORY = 6 } - private lateinit var mUserAccountViewModel: UserAccountViewModel - private lateinit var mBalanceViewModel: BalanceViewModel - private lateinit var mTransferViewModel: TransferViewModel - private lateinit var mConnectedActivityViewModel: ConnectedActivityViewModel + // TODO consolidate ViewModels + private val userAccountViewModel: UserAccountViewModel by viewModels() + private val balanceViewModel: BalanceViewModel by viewModels() + private val transferViewModel: TransferViewModel by viewModels() + private val connectedActivityViewModel: ConnectedActivityViewModel by viewModels() private lateinit var mAssetRepository: AssetRepository @@ -114,42 +114,38 @@ abstract class ConnectedActivity : AppCompatActivity() { mAssetRepository = AssetRepository(this) // Configure ConnectedActivityViewModel to obtain missing equivalent values - mConnectedActivityViewModel = ViewModelProviders.of(this).get(ConnectedActivityViewModel::class.java) - val currencyCode = Helper.getCoingeckoSupportedCurrency(Locale.getDefault()) Log.d(TAG, "Using currency: ${currencyCode.toUpperCase(Locale.ROOT)}") - mConnectedActivityViewModel.observeMissingEquivalentValuesIn(currencyCode) + connectedActivityViewModel.observeMissingEquivalentValuesIn(currencyCode) // Configure UserAccountViewModel to obtain the missing account ids - mUserAccountViewModel = ViewModelProviders.of(this).get(UserAccountViewModel::class.java) - - mUserAccountViewModel.getMissingUserAccountIds().observe(this, Observer>{ userAccountIds -> + userAccountViewModel.getMissingUserAccountIds().observe(this, { userAccountIds -> if (userAccountIds.isNotEmpty()) { missingUserAccounts.clear() for (userAccountId in userAccountIds) missingUserAccounts.add(UserAccount(userAccountId)) - mHandler.postDelayed(mRequestMissingUserAccountsTask, Constants.NETWORK_SERVICE_RETRY_PERIOD) + mHandler.postDelayed( + mRequestMissingUserAccountsTask, + Constants.NETWORK_SERVICE_RETRY_PERIOD + ) } }) // Configure UserAccountViewModel to obtain the missing account ids - mBalanceViewModel = ViewModelProviders.of(this).get(BalanceViewModel::class.java) - - mBalanceViewModel.getMissingAssetIds().observe(this, Observer>{ assetIds -> + balanceViewModel.getMissingAssetIds().observe(this, { assetIds -> if (assetIds.isNotEmpty()) { missingAssets.clear() for (assetId in assetIds) missingAssets.add(Asset(assetId)) - mHandler.postDelayed(mRequestMissingAssetsTask, Constants.NETWORK_SERVICE_RETRY_PERIOD) + mHandler + .postDelayed(mRequestMissingAssetsTask, Constants.NETWORK_SERVICE_RETRY_PERIOD) } }) //Configure TransferViewModel to obtain the Transfer's block numbers with missing time information, one by one - mTransferViewModel = ViewModelProviders.of(this).get(TransferViewModel::class.java) - - mTransferViewModel.getTransferBlockNumberWithMissingTime().observe(this, Observer{ blockNumber -> + transferViewModel.getTransferBlockNumberWithMissingTime().observe(this, { blockNumber -> if (blockNumber != null && blockNumber != blockNumberWithMissingTime) { blockNumberWithMissingTime = blockNumber mHandler.post(mRequestBlockMissingTimeTask) @@ -167,13 +163,14 @@ abstract class ConnectedActivity : AppCompatActivity() { mCompositeDisposable.add(disposable) - val info = this.packageManager.getPackageInfo(this.packageName, PackageManager.GET_ACTIVITIES) + val info = + this.packageManager.getPackageInfo(this.packageName, PackageManager.GET_ACTIVITIES) val versionCode = PackageInfoCompat.getLongVersionCode(info) val hasPurgedEquivalentValues = PreferenceManager.getDefaultSharedPreferences(this) .getBoolean(Constants.KEY_HAS_PURGED_EQUIVALENT_VALUES, false) - if(versionCode > 11 && !hasPurgedEquivalentValues) { + if (versionCode > 11 && !hasPurgedEquivalentValues) { thread { - mConnectedActivityViewModel.purgeEquivalentValues() + connectedActivityViewModel.purgeEquivalentValues() PreferenceManager.getDefaultSharedPreferences(this) .edit() .putBoolean(Constants.KEY_HAS_PURGED_EQUIVALENT_VALUES, true) @@ -201,7 +198,7 @@ abstract class ConnectedActivity : AppCompatActivity() { * Error consumer used to handle potential errors caused by the NetworkService while processing * incoming data. */ - private fun handleError(throwable: Throwable){ + private fun handleError(throwable: Throwable) { Log.e(TAG, "Error while processing received message. Msg: " + throwable.message) val stack = throwable.stackTrace for (e in stack) { @@ -217,24 +214,24 @@ abstract class ConnectedActivity : AppCompatActivity() { if (message.error == null) { if (responseMap.containsKey(message.id)) { when (responseMap[message.id]) { - RESPONSE_GET_FULL_ACCOUNTS -> + RESPONSE_GET_FULL_ACCOUNTS -> handleAccountDetails((message.result as List<*>)[0] as FullAccountDetails) - RESPONSE_GET_ACCOUNTS -> + RESPONSE_GET_ACCOUNTS -> handleAccountProperties(message.result as List) - RESPONSE_GET_ACCOUNT_BALANCES -> + RESPONSE_GET_ACCOUNT_BALANCES -> handleBalanceUpdate(message.result as List) - RESPONSE_GET_ASSETS -> + RESPONSE_GET_ASSETS -> handleAssets(message.result as List) - RESPONSE_GET_BLOCK_HEADER -> { + RESPONSE_GET_BLOCK_HEADER -> { val blockNumber = requestIdToBlockNumberMap[message.id] ?: 0L handleBlockHeader(message.result as BlockHeader, blockNumber) requestIdToBlockNumberMap.remove(message.id) } - RESPONSE_GET_MARKET_HISTORY -> handleMarketData(message.result as List) + RESPONSE_GET_MARKET_HISTORY -> handleMarketData(message.result as List) } responseMap.remove(message.id) } @@ -259,11 +256,11 @@ abstract class ConnectedActivity : AppCompatActivity() { responseMap.clear() } else if (message.updateCode == ConnectionStatusUpdate.API_UPDATE) { // If we got an API update - if(message.api == ApiAccess.API_HISTORY) { + if (message.api == ApiAccess.API_HISTORY) { // Starts the procedure that will obtain the missing equivalent values - mTransferViewModel - .getTransfersWithMissingBtsValue().observe(this, Observer { - if(it != null) handleTransfersWithMissingBtsValue(it) + transferViewModel + .getTransfersWithMissingBtsValue().observe(this, { + if (it != null) handleTransfersWithMissingBtsValue(it) }) } } @@ -274,13 +271,16 @@ abstract class ConnectedActivity : AppCompatActivity() { * Method called whenever we get a list of transfers with their bts value missing. */ private fun handleTransfersWithMissingBtsValue(transfer: Transfer) { - if(mNetworkService?.isConnected == true){ + if (mNetworkService?.isConnected == true) { val base = Asset(transfer.transferAssetId) val quote = Asset("1.3.0") val bucket: Long = TimeUnit.SECONDS.convert(1, TimeUnit.DAYS) val end: Long = transfer.timestamp * 1000L val start: Long = (transfer.timestamp - bucket) * 1000L - val id = mNetworkService!!.sendMessage(GetMarketHistory(base, quote, bucket, start, end), GetMarketHistory.REQUIRED_API) + val id = mNetworkService!!.sendMessage( + GetMarketHistory(base, quote, bucket, start, end), + GetMarketHistory.REQUIRED_API + ) responseMap[id] = RESPONSE_GET_MARKET_HISTORY this.transfer = transfer } @@ -292,11 +292,17 @@ abstract class ConnectedActivity : AppCompatActivity() { */ private fun handleAccountDetails(accountDetails: FullAccountDetails) { val latestOpCount = accountDetails.statistics.total_ops - Log.d(TAG, "handleAccountDetails. prev count: $storedOpCount, current count: $latestOpCount") + Log.d( + TAG, + "handleAccountDetails. prev count: $storedOpCount, current count: $latestOpCount" + ) if (latestOpCount == 0L) { - Log.d(TAG, "The node returned 0 total_ops for current account and may not have installed the history plugin. " + - "\nAsk the NetworkService to remove the node from the list and connect to another one.") + Log.d( + TAG, + "The node returned 0 total_ops for current account and may not have installed the history plugin. " + + "\nAsk the NetworkService to remove the node from the list and connect to another one." + ) mNetworkService?.reconnectNode() } else if (storedOpCount == -1L) { // Initial case when the app starts @@ -317,7 +323,8 @@ abstract class ConnectedActivity : AppCompatActivity() { * create a list of BiTSy's UserAccount objects and stores them into the database */ private fun handleAccountProperties(accountPropertiesList: List) { - val userAccounts = ArrayList() + val userAccounts = + ArrayList() for (accountProperties in accountPropertiesList) { val userAccount = cy.agorise.bitsybitshareswallet.database.entities.UserAccount( @@ -329,7 +336,7 @@ abstract class ConnectedActivity : AppCompatActivity() { userAccounts.add(userAccount) } - mUserAccountViewModel.insertAll(userAccounts) + userAccountViewModel.insertAll(userAccounts) missingUserAccounts.clear() } @@ -346,7 +353,7 @@ abstract class ConnectedActivity : AppCompatActivity() { balances.add(balance) } - mBalanceViewModel.insertAll(balances) + balanceViewModel.insertAll(balances) } /** @@ -382,35 +389,38 @@ abstract class ConnectedActivity : AppCompatActivity() { try { val date = dateFormat.parse(blockHeader.timestamp) - mTransferViewModel.setBlockTime(blockNumber, date.time / 1000) + transferViewModel.setBlockTime(blockNumber, date.time / 1000) } catch (e: ParseException) { Log.e(TAG, "ParseException. Msg: " + e.message) } } private fun handleMarketData(buckets: List) { - if(buckets.isNotEmpty()){ - Log.d(TAG,"handleMarketData. Bucket is not empty") + if (buckets.isNotEmpty()) { + Log.d(TAG, "handleMarketData. Bucket is not empty") val bucket = buckets[0] val pair = Pair(transfer, bucket) val disposable = Observable.just(pair) .subscribeOn(Schedulers.computation()) - .map { mTransferViewModel.updateBtsValue(it.first!!, it.second) } - .subscribe({},{ - Log.e(TAG,"Error at updateBtsValue. Msg: ${it.message}") - for(line in it.stackTrace) Log.e(TAG, "${line.className}#${line.methodName}:${line.lineNumber}") + .map { transferViewModel.updateBtsValue(it.first!!, it.second) } + .subscribe({}, { + Log.e(TAG, "Error at updateBtsValue. Msg: ${it.message}") + for (line in it.stackTrace) + Log.e(TAG, "${line.className}#${line.methodName}:${line.lineNumber}") }) mCompositeDisposable.add(disposable) - }else{ - Log.i(TAG,"handleMarketData. Bucket IS empty") - AsyncTask.execute { mTransferViewModel.updateBtsValue(transfer!!, Transfer.ERROR) } + } else { + Log.i(TAG, "handleMarketData. Bucket IS empty") + AsyncTask.execute { transferViewModel.updateBtsValue(transfer!!, Transfer.ERROR) } } } private fun updateBalances() { if (mNetworkService?.isConnected == true) { - val id = mNetworkService!!.sendMessage(GetAccountBalances(mCurrentAccount, ArrayList()), - GetAccountBalances.REQUIRED_API) + val id = mNetworkService!!.sendMessage( + GetAccountBalances(mCurrentAccount, ArrayList()), + GetAccountBalances.REQUIRED_API + ) responseMap[id] = RESPONSE_GET_ACCOUNT_BALANCES } @@ -447,10 +457,13 @@ abstract class ConnectedActivity : AppCompatActivity() { private val mRequestMissingUserAccountsTask = object : Runnable { override fun run() { if (mNetworkService?.isConnected == true) { - val id = mNetworkService!!.sendMessage(GetAccounts(missingUserAccounts), GetAccounts.REQUIRED_API) + val id = mNetworkService!!.sendMessage( + GetAccounts(missingUserAccounts), + GetAccounts.REQUIRED_API + ) responseMap[id] = RESPONSE_GET_ACCOUNTS - } else if (missingUserAccounts.isNotEmpty()){ + } else if (missingUserAccounts.isNotEmpty()) { mHandler.postDelayed(this, Constants.NETWORK_SERVICE_RETRY_PERIOD) } } @@ -462,10 +475,11 @@ abstract class ConnectedActivity : AppCompatActivity() { private val mRequestMissingAssetsTask = object : Runnable { override fun run() { if (mNetworkService?.isConnected == true) { - val id = mNetworkService!!.sendMessage(GetAssets(missingAssets), GetAssets.REQUIRED_API) + val id = + mNetworkService!!.sendMessage(GetAssets(missingAssets), GetAssets.REQUIRED_API) responseMap[id] = RESPONSE_GET_ASSETS - } else if (missingAssets.isNotEmpty()){ + } else if (missingAssets.isNotEmpty()) { mHandler.postDelayed(this, Constants.NETWORK_SERVICE_RETRY_PERIOD) } } @@ -480,13 +494,17 @@ abstract class ConnectedActivity : AppCompatActivity() { if (mCurrentAccount != null) { val userAccounts = ArrayList() userAccounts.add(mCurrentAccount!!.objectId) - val id = mNetworkService!!.sendMessage(GetFullAccounts(userAccounts, false), - GetFullAccounts.REQUIRED_API) + val id = mNetworkService!!.sendMessage( + GetFullAccounts(userAccounts, false), + GetFullAccounts.REQUIRED_API + ) responseMap[id] = RESPONSE_GET_FULL_ACCOUNTS } } else { - Log.w(TAG, "NetworkService is null or is not connected. mNetworkService: $mNetworkService") + val msg = "NetworkService is null or is not connected. " + + "mNetworkService: $mNetworkService" + Log.w(TAG, msg) } mHandler.postDelayed(this, Constants.MISSING_PAYMENT_CHECK_PERIOD) @@ -500,8 +518,10 @@ abstract class ConnectedActivity : AppCompatActivity() { override fun run() { if (mNetworkService?.isConnected == true) { - val id = mNetworkService!!.sendMessage(GetBlockHeader(blockNumberWithMissingTime), - GetBlockHeader.REQUIRED_API) + val id = mNetworkService!!.sendMessage( + GetBlockHeader(blockNumberWithMissingTime), + GetBlockHeader.REQUIRED_API + ) responseMap[id] = RESPONSE_GET_BLOCK_HEADER requestIdToBlockNumberMap[id] = blockNumberWithMissingTime @@ -521,7 +541,7 @@ abstract class ConnectedActivity : AppCompatActivity() { override fun onPause() { super.onPause() mNetworkService?.nodeLatencyVerifier?.nodeList?.let { nodes -> - mConnectedActivityViewModel.updateNodeLatencies(nodes as List) + connectedActivityViewModel.updateNodeLatencies(nodes as List) } mHandler.removeCallbacks(mCheckMissingPaymentsTask) @@ -532,6 +552,6 @@ abstract class ConnectedActivity : AppCompatActivity() { override fun onDestroy() { super.onDestroy() - if(!mCompositeDisposable.isDisposed) mCompositeDisposable.dispose() + if (!mCompositeDisposable.isDisposed) mCompositeDisposable.dispose() } -} \ No newline at end of file +} 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 b51882d..dccd3f2 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/BalancesFragment.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/BalancesFragment.kt @@ -5,22 +5,20 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment -import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModelProviders +import androidx.fragment.app.viewModels import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager 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 class BalancesFragment : Fragment() { + private val viewModel: BalanceDetailViewModel by viewModels() + private var _binding: FragmentBalancesBinding? = null private val binding get() = _binding!! - private lateinit var mBalanceDetailViewModel: BalanceDetailViewModel - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? @@ -40,19 +38,15 @@ class BalancesFragment : Fragment() { super.onViewCreated(view, savedInstanceState) // Configure BalanceDetailViewModel to show the current balances - mBalanceDetailViewModel = - ViewModelProviders.of(this).get(BalanceDetailViewModel::class.java) - - val balancesAdapter = BalancesAdapter(context!!) + val balancesAdapter = BalancesAdapter(requireContext()) binding.rvBalances.adapter = balancesAdapter - binding.rvBalances.layoutManager = LinearLayoutManager(context!!) + binding.rvBalances.layoutManager = LinearLayoutManager(requireContext()) binding.rvBalances.addItemDecoration( - DividerItemDecoration(context!!, DividerItemDecoration.VERTICAL) + DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL) ) - mBalanceDetailViewModel.getAll() - .observe(this, Observer> { balancesDetails -> - balancesAdapter.replaceAll(balancesDetails) - }) + viewModel.getAll().observe(viewLifecycleOwner, { balancesDetails -> + balancesAdapter.replaceAll(balancesDetails) + }) } } diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/BaseAccountFragment.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/BaseAccountFragment.kt index e1cf1bb..4658729 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/BaseAccountFragment.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/BaseAccountFragment.kt @@ -3,6 +3,7 @@ package cy.agorise.bitsybitshareswallet.fragments import android.preference.PreferenceManager import androidx.navigation.fragment.findNavController import cy.agorise.bitsybitshareswallet.R +import cy.agorise.bitsybitshareswallet.activities.ConnectedActivity import cy.agorise.bitsybitshareswallet.database.entities.Authority import cy.agorise.bitsybitshareswallet.repositories.AuthorityRepository import cy.agorise.bitsybitshareswallet.repositories.UserAccountRepository @@ -13,7 +14,6 @@ import cy.agorise.graphenej.BrainKey import cy.agorise.graphenej.PublicKey import cy.agorise.graphenej.models.AccountProperties import org.bitcoinj.core.ECKey -import cy.agorise.bitsybitshareswallet.activities.ConnectedActivity abstract class BaseAccountFragment : ConnectedFragment() { @@ -36,7 +36,7 @@ abstract class BaseAccountFragment : ConnectedFragment() { val hashedPIN = CryptoUtils.createSHA256Hash(salt + pin) // Stores the user selected PIN, hashed - PreferenceManager.getDefaultSharedPreferences(context!!).edit() + PreferenceManager.getDefaultSharedPreferences(requireContext()).edit() .putString(Constants.KEY_HASHED_PIN_PATTERN, hashedPIN) .putString(Constants.KEY_PIN_PATTERN_SALT, salt) .putInt(Constants.KEY_SECURITY_LOCK_SELECTED, 1).apply() // 1 -> PIN @@ -44,15 +44,17 @@ abstract class BaseAccountFragment : ConnectedFragment() { // Stores the accounts this key refers to val id = accountProperties.id val name = accountProperties.name - val isLTM = accountProperties.membership_expiration_date == Constants.LIFETIME_EXPIRATION_DATE + val isLTM = + accountProperties.membership_expiration_date == Constants.LIFETIME_EXPIRATION_DATE - val userAccount = cy.agorise.bitsybitshareswallet.database.entities.UserAccount(id, name, isLTM) + val userAccount = + cy.agorise.bitsybitshareswallet.database.entities.UserAccount(id, name, isLTM) - val userAccountRepository = UserAccountRepository(context!!.applicationContext) + val userAccountRepository = UserAccountRepository(requireContext().applicationContext) userAccountRepository.insert(userAccount) // Stores the id of the currently active user account - PreferenceManager.getDefaultSharedPreferences(context!!).edit() + PreferenceManager.getDefaultSharedPreferences(requireContext()).edit() .putString(Constants.KEY_CURRENT_ACCOUNT_ID, accountProperties.id).apply() // Trying to store all possible authorities (owner, active and memo) into the database @@ -65,13 +67,25 @@ abstract class BaseAccountFragment : ConnectedFragment() { val publicKey = PublicKey(ECKey.fromPublicOnly(mBrainKey!!.privateKey.pubKey)) if (ownerAuthority.keyAuths.keys.contains(publicKey)) { - addAuthorityToDatabase(accountProperties.id, AuthorityType.OWNER.ordinal, mBrainKey!!) + addAuthorityToDatabase( + accountProperties.id, + AuthorityType.OWNER.ordinal, + mBrainKey!! + ) } if (activeAuthority.keyAuths.keys.contains(publicKey)) { - addAuthorityToDatabase(accountProperties.id, AuthorityType.ACTIVE.ordinal, mBrainKey!!) + addAuthorityToDatabase( + accountProperties.id, + AuthorityType.ACTIVE.ordinal, + mBrainKey!! + ) } if (options.memoKey == publicKey) { - addAuthorityToDatabase(accountProperties.id, AuthorityType.MEMO.ordinal, mBrainKey!!) + addAuthorityToDatabase( + accountProperties.id, + AuthorityType.MEMO.ordinal, + mBrainKey!! + ) } } @@ -91,13 +105,21 @@ abstract class BaseAccountFragment : ConnectedFragment() { val wif = brainKey.walletImportFormat val sequenceNumber = brainKey.sequenceNumber - val encryptedBrainKey = CryptoUtils.encrypt(context!!, brainKeyWords) - val encryptedSequenceNumber = CryptoUtils.encrypt(context!!, sequenceNumber.toString()) - val encryptedWIF = CryptoUtils.encrypt(context!!, wif) + val encryptedBrainKey = CryptoUtils.encrypt(requireContext(), brainKeyWords) + val encryptedSequenceNumber = + CryptoUtils.encrypt(requireContext(), sequenceNumber.toString()) + val encryptedWIF = CryptoUtils.encrypt(requireContext(), wif) - val authority = Authority(0, userId, authorityType, encryptedWIF, encryptedBrainKey, encryptedSequenceNumber) + val authority = Authority( + 0, + userId, + authorityType, + encryptedWIF, + encryptedBrainKey, + encryptedSequenceNumber + ) - val authorityRepository = AuthorityRepository(context!!) + val authorityRepository = AuthorityRepository(requireContext()) authorityRepository.insert(authority) } } \ 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 a83b18a..9ea44bb 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/CreateAccountFragment.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/CreateAccountFragment.kt @@ -362,8 +362,9 @@ class CreateAccountFragment : BaseAccountFragment() { var reader: BufferedReader? = null val dictionary: String try { - reader = - BufferedReader(InputStreamReader(context!!.assets.open(BRAINKEY_FILE), "UTF-8")) + reader = BufferedReader( + InputStreamReader(requireContext().assets.open(BRAINKEY_FILE), "UTF-8") + ) dictionary = reader.readLine() val brainKeySuggestion = BrainKey.suggest(dictionary) 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 d203142..e4cacbb 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/EReceiptFragment.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/EReceiptFragment.kt @@ -12,8 +12,7 @@ import android.view.* import androidx.core.content.ContextCompat import androidx.core.os.ConfigurationCompat import androidx.fragment.app.Fragment -import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModelProviders +import androidx.fragment.app.viewModels import androidx.navigation.fragment.navArgs import com.google.firebase.crashlytics.FirebaseCrashlytics import cy.agorise.bitsybitshareswallet.R @@ -38,12 +37,13 @@ class EReceiptFragment : Fragment() { private const val REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION = 100 } + private val args: EReceiptFragmentArgs by navArgs() + + private val viewModel: EReceiptViewModel by viewModels() + 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( @@ -75,12 +75,9 @@ class EReceiptFragment : Fragment() { val transferId = args.transferId - mEReceiptViewModel = ViewModelProviders.of(this).get(EReceiptViewModel::class.java) - - mEReceiptViewModel.get(userId, transferId) - .observe(this, Observer { transferDetail -> - bindTransferDetail(transferDetail) - }) + viewModel.get(userId, transferId).observe(viewLifecycleOwner, { transferDetail -> + bindTransferDetail(transferDetail) + }) } private fun bindTransferDetail(transferDetail: TransferDetail) { @@ -165,7 +162,7 @@ class EReceiptFragment : Fragment() { * 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) + .checkSelfPermission(requireActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ) { // Permission is not already granted 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 e3545da..df27dfb 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/FilterOptionsDialog.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/FilterOptionsDialog.kt @@ -10,8 +10,7 @@ import android.widget.TextView import androidx.core.os.ConfigurationCompat import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment -import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModelProviders +import androidx.fragment.app.viewModels import com.google.firebase.crashlytics.FirebaseCrashlytics import cy.agorise.bitsybitshareswallet.R import cy.agorise.bitsybitshareswallet.adapters.BalancesDetailsAdapter @@ -42,6 +41,8 @@ class FilterOptionsDialog : DialogFragment(), DatePickerFragment.OnDateSetListen const val END_DATE_PICKER = 1 } + private val viewModel: BalanceDetailViewModel by viewModels() + private var _binding: DialogFilterOptionsBinding? = null private val binding get() = _binding!! @@ -56,8 +57,6 @@ class FilterOptionsDialog : DialogFragment(), DatePickerFragment.OnDateSetListen private var mBalanceDetails = ArrayList() - private lateinit var mBalanceDetailViewModel: BalanceDetailViewModel - private var mBalancesDetailsAdapter: BalancesDetailsAdapter? = null private lateinit var mCurrency: Currency @@ -117,7 +116,7 @@ class FilterOptionsDialog : DialogFragment(), DatePickerFragment.OnDateSetListen override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - onAttachToParentFragment(parentFragment!!) + onAttachToParentFragment(requireParentFragment()) val crashlytics = FirebaseCrashlytics.getInstance() crashlytics.setCustomKey(Constants.CRASHLYTICS_KEY_LAST_SCREEN, TAG) @@ -150,31 +149,25 @@ class FilterOptionsDialog : DialogFragment(), DatePickerFragment.OnDateSetListen binding.cbAsset.isChecked = mFilterOptions.assetAll // Configure BalanceDetailViewModel to obtain the user's Balances - mBalanceDetailViewModel = - ViewModelProviders.of(this).get(BalanceDetailViewModel::class.java) + viewModel.getAll().observe(viewLifecycleOwner, { balancesDetails -> + mBalanceDetails.clear() + mBalanceDetails.addAll(balancesDetails) + mBalanceDetails.sortWith { a, b -> a.toString().compareTo(b.toString(), true) } + mBalancesDetailsAdapter = BalancesDetailsAdapter( + requireContext(), + android.R.layout.simple_spinner_item, + mBalanceDetails + ) + binding.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) { - binding.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 binding.cbEquivalentValue.setOnCheckedChangeListener { _, isChecked -> 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 2d0860b..327b8d6 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/HomeFragment.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/HomeFragment.kt @@ -6,16 +6,15 @@ import android.view.* import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.Toolbar import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat 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.fragment.app.viewModels 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 @@ -27,11 +26,11 @@ class HomeFragment : Fragment() { private const val TAG = "HomeFragment" } + private val viewModel: UserAccountViewModel by viewModels() + private var _binding: FragmentHomeBinding? = null private val binding get() = _binding!! - private lateinit var mUserAccountViewModel: UserAccountViewModel - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -47,7 +46,8 @@ class HomeFragment : Fragment() { // MerchantsFragment (visibility) val toolbar: Toolbar? = activity?.findViewById(R.id.toolbar) (activity as AppCompatActivity).setSupportActionBar(toolbar) - toolbar?.navigationIcon = resources.getDrawable(R.drawable.ic_bitsy_logo_2, null) + toolbar?.navigationIcon = + ResourcesCompat.getDrawable(resources, R.drawable.ic_bitsy_logo_2, null) toolbar?.setBackgroundResource(if (!nightMode) R.color.colorPrimary else R.color.colorToolbarDark) toolbar?.visibility = View.VISIBLE toolbar?.title = getString(R.string.app_name) @@ -94,22 +94,19 @@ 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) { - 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 - } + viewModel.getUserAccount(userId).observe(viewLifecycleOwner, { 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 binding.fabReceiveTransaction.setOnClickListener( @@ -149,11 +146,11 @@ class HomeFragment : Fragment() { } override fun getPageTitle(position: Int): CharSequence { - return listOf( - getString(R.string.title_balances), - getString(R.string.title_net_worth), - "" - )[position] + return when (position) { + 0 -> getString(R.string.title_balances) + 1 -> getString(R.string.title_net_worth) + else -> "" + } } 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 3580e70..626c5f6 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/ImportBrainkeyFragment.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/ImportBrainkeyFragment.kt @@ -402,7 +402,7 @@ class ImportBrainkeyFragment : BaseAccountFragment() { for (accountProperties in accountPropertiesList) { candidates.add(accountProperties.name) } - MaterialDialog(context!!) + MaterialDialog(requireContext()) .title(R.string.dialog__account_candidates_title) .message(R.string.dialog__account_candidates_content) .listItemsSingleChoice( 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 8a37863..5c89b4a 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/MerchantsFragment.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/MerchantsFragment.kt @@ -18,8 +18,7 @@ import androidx.appcompat.widget.Toolbar import androidx.core.content.ContextCompat import androidx.cursoradapter.widget.SimpleCursorAdapter import androidx.fragment.app.Fragment -import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModelProviders +import androidx.fragment.app.viewModels import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.customview.customView import com.google.android.gms.maps.CameraUpdateFactory @@ -73,13 +72,13 @@ class MerchantsFragment : Fragment(), OnMapReadyCallback, SearchView.OnSuggestio private const val SUGGEST_COLUMN_IMAGE_RESOURCE = "suggest_image_resource" } + private val viewModel: MerchantViewModel by viewModels() + private var _binding: FragmentMerchantsBinding? = null private val binding get() = _binding!! private var mMap: GoogleMap? = null - private lateinit var mMerchantViewModel: MerchantViewModel - private var mMarkerManager: MarkerManager? = null /** Keeps track of all RxJava disposables, to make sure they are all disposed when the fragment is destroyed */ @@ -166,8 +165,6 @@ class MerchantsFragment : Fragment(), OnMapReadyCallback, SearchView.OnSuggestio val mapFragment = childFragmentManager.findFragmentById(R.id.map) as SupportMapFragment mapFragment.getMapAsync(this) - mMerchantViewModel = ViewModelProviders.of(this).get(MerchantViewModel::class.java) - setupPopupWindow() // Gets the screen width to correctly place the merchants and tellers popup menu @@ -245,12 +242,12 @@ class MerchantsFragment : Fragment(), OnMapReadyCallback, SearchView.OnSuggestio private fun updateSearchViewSuggestions(query: String) { // Obtain observable of the list of merchants matching the query - val merchantsObs = mMerchantViewModel.queryMerchants(query) + val merchantsObs = viewModel.queryMerchants(query) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()).toObservable() // Obtain observable of the list of tellers matching the query - val tellerObs = mMerchantViewModel.queryTellers(query) + val tellerObs = viewModel.queryTellers(query) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()).toObservable() @@ -449,7 +446,10 @@ class MerchantsFragment : Fragment(), OnMapReadyCallback, SearchView.OnSuggestio } private fun verifyLocationPermission() { - if (ContextCompat.checkSelfPermission(activity!!, Manifest.permission.ACCESS_FINE_LOCATION) + if (ContextCompat.checkSelfPermission( + requireActivity(), + Manifest.permission.ACCESS_FINE_LOCATION + ) != PackageManager.PERMISSION_GRANTED ) { // Permission is not already granted @@ -482,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 -> + viewModel.getAllMerchants().observe(viewLifecycleOwner, { merchants -> this.merchants.clear() this.merchants.addAll(merchants) showHideMerchantsMarkers() @@ -508,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 -> + viewModel.getAllTellers().observe(viewLifecycleOwner, { tellers -> this.tellers.clear() this.tellers.addAll(tellers) showHideTellersMarkers() 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 b54883b..7008e5b 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/ReceiveTransactionFragment.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/ReceiveTransactionFragment.kt @@ -11,8 +11,7 @@ import android.widget.AdapterView import androidx.appcompat.widget.Toolbar import androidx.collection.LongSparseArray import androidx.core.content.ContextCompat -import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModelProviders +import androidx.fragment.app.viewModels import com.google.common.primitives.UnsignedLong import com.google.firebase.crashlytics.FirebaseCrashlytics import com.jakewharton.rxbinding3.widget.textChanges @@ -52,11 +51,11 @@ class ReceiveTransactionFragment : ConnectedFragment() { private const val OTHER_ASSET = "other_asset" } + private val viewModel: ReceiveTransactionViewModel by viewModels() + private var _binding: FragmentReceiveTransactionBinding? = null private val binding get() = _binding!! - private lateinit var mViewModel: ReceiveTransactionViewModel - /** Current user account */ private var mUserAccount: UserAccount? = null @@ -118,53 +117,49 @@ class ReceiveTransactionFragment : ConnectedFragment() { crashlytics.setCustomKey(Constants.CRASHLYTICS_KEY_LAST_SCREEN, TAG) // Configure ViewModel - mViewModel = ViewModelProviders.of(this).get(ReceiveTransactionViewModel::class.java) - val userId = PreferenceManager.getDefaultSharedPreferences(context) .getString(Constants.KEY_CURRENT_ACCOUNT_ID, "") - mViewModel.getUserAccount(userId!!).observe(this, - Observer { user -> - mUserAccount = UserAccount(user.id, user.name) - }) + viewModel.getUserAccount(userId!!).observe(viewLifecycleOwner, { user -> + mUserAccount = UserAccount(user.id, user.name) + }) - mViewModel.getAllNonZero().observe(this, - Observer> { assets -> - mAssets.clear() - mAssets.addAll(assets) + viewModel.getAllNonZero().observe(viewLifecycleOwner, { assets -> + mAssets.clear() + mAssets.addAll(assets) - // Add BTS to always show a QR - if (mAssets.isEmpty()) - mAssets.add( - cy.agorise.bitsybitshareswallet.database.entities.Asset( - "1.3.0", "BTS", 5, "", "" - ) + // Add BTS to always show a QR + if (mAssets.isEmpty()) + mAssets.add( + cy.agorise.bitsybitshareswallet.database.entities.Asset( + "1.3.0", "BTS", 5, "", "" ) - - mAssets.sortWith( - Comparator { a, b -> a.toString().compareTo(b.toString(), true) } ) - // Add an option at the end so the user can search for an asset other than the ones saved in the db - val asset = cy.agorise.bitsybitshareswallet.database.entities.Asset( - OTHER_ASSET, getString(R.string.text__other), 0, "", "" - ) - mAssets.add(asset) + mAssets.sortWith( + Comparator { a, b -> a.toString().compareTo(b.toString(), true) } + ) - mAssetsAdapter = - AssetsAdapter(context!!, android.R.layout.simple_spinner_item, mAssets) - binding.spAsset.adapter = mAssetsAdapter + // Add an option at the end so the user can search for an asset other than the ones saved in the db + val asset = cy.agorise.bitsybitshareswallet.database.entities.Asset( + OTHER_ASSET, getString(R.string.text__other), 0, "", "" + ) + mAssets.add(asset) - // Try to select the selectedAssetSymbol - for (i in 0 until mAssetsAdapter!!.count) { - if (mAssetsAdapter!!.getItem(i)!!.symbol == selectedAssetSymbol) { - binding.spAsset.setSelection(i) - break - } + mAssetsAdapter = + AssetsAdapter(requireContext(), 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) { + binding.spAsset.setSelection(i) + break } - }) + } + }) - mViewModel.qrCodeBitmap.observe(this, Observer { bitmap -> + viewModel.qrCodeBitmap.observe(viewLifecycleOwner, { bitmap -> binding.ivQR.setImageBitmap(bitmap) }) @@ -204,7 +199,7 @@ class ReceiveTransactionFragment : ConnectedFragment() { // Add adapter to the Assets AutoCompleteTextView mAutoSuggestAssetAdapter = - AutoSuggestAssetAdapter(context!!, android.R.layout.simple_dropdown_item_1line) + AutoSuggestAssetAdapter(requireContext(), 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 @@ -311,7 +306,7 @@ class ReceiveTransactionFragment : ConnectedFragment() { ) Log.d(TAG, "invoice: " + invoice.toJsonString()) try { - mViewModel.updateInvoice(invoice, min(binding.ivQR.width, binding.ivQR.height)) + viewModel.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) @@ -366,7 +361,7 @@ class ReceiveTransactionFragment : ConnectedFragment() { private fun verifyStoragePermission() { if (ContextCompat.checkSelfPermission( - activity!!, + requireActivity(), android.Manifest.permission.WRITE_EXTERNAL_STORAGE ) != PackageManager.PERMISSION_GRANTED @@ -411,7 +406,7 @@ class ReceiveTransactionFragment : ConnectedFragment() { // Get Screenshot val screenshot = Helper.loadBitmapFromView(binding.container) - val imageUri = Helper.saveTemporalBitmap(context!!, screenshot) + val imageUri = Helper.saveTemporalBitmap(requireContext(), screenshot) // Prepare information for share intent val subject = getString(R.string.msg__invoice_subject, mUserAccount?.name) @@ -428,4 +423,4 @@ class ReceiveTransactionFragment : ConnectedFragment() { shareIntent.type = "*/*" startActivity(Intent.createChooser(shareIntent, getString(R.string.text__share_with))) } -} \ No newline at end of file +} 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 5734858..cca7fbe 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/SendTransactionFragment.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/SendTransactionFragment.kt @@ -12,8 +12,7 @@ import android.widget.Toast import androidx.appcompat.widget.Toolbar import androidx.collection.LongSparseArray import androidx.core.content.ContextCompat -import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModelProviders +import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import com.afollestad.materialdialogs.MaterialDialog @@ -74,13 +73,15 @@ 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() - private lateinit var mViewModel: SendTransactionViewModel + // TODO consolidate ViewModels + private val viewModel: SendTransactionViewModel by viewModels() + + private val balanceDetailViewModel: BalanceDetailViewModel by viewModels() + + private var _binding: FragmentSendTransactionBinding? = null + private val binding get() = _binding!! /** Variables used in field's validation */ private var isCameraPreviewVisible = false @@ -90,8 +91,6 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand private var mBalancesDetails = ArrayList() - private lateinit var mBalanceDetailViewModel: BalanceDetailViewModel - private var mBalancesDetailsAdapter: BalancesDetailsAdapter? = null /** Keeps track of the asset's symbol selected in the Asset spinner */ @@ -162,10 +161,8 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand mUserAccount = UserAccount(userId) // Configure ViewModel - mViewModel = ViewModelProviders.of(this).get(SendTransactionViewModel::class.java) - - mViewModel.getWIF(userId, AuthorityType.ACTIVE.ordinal).observe(this, - Observer { encryptedWIF -> + viewModel.getWIF(userId, AuthorityType.ACTIVE.ordinal) + .observe(viewLifecycleOwner, { encryptedWIF -> context?.let { try { wifKey = CryptoUtils.decrypt(it, encryptedWIF) @@ -186,31 +183,27 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand binding.fabOpenCamera.setOnClickListener { if (isCameraPreviewVisible) stopCameraPreview() else verifyCameraPermission() } // Configure BalanceDetailViewModel to show the current balances - mBalanceDetailViewModel = - ViewModelProviders.of(this).get(BalanceDetailViewModel::class.java) + balanceDetailViewModel.getAll().observe(viewLifecycleOwner, { balancesDetails -> + mBalancesDetails.clear() + mBalancesDetails.addAll(balancesDetails) + mBalancesDetails.sortWith( + Comparator { a, b -> a.toString().compareTo(b.toString(), true) } + ) + mBalancesDetailsAdapter = BalancesDetailsAdapter( + requireContext(), + android.R.layout.simple_spinner_item, + mBalancesDetails + ) + binding.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) { - binding.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 } - }) + } + }) binding.spAsset.onItemSelectedListener = assetItemSelectedListener @@ -387,7 +380,7 @@ 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) + if (ContextCompat.checkSelfPermission(requireActivity(), android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED ) { // Permission is not already granted @@ -711,7 +704,7 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand override fun onOptionsItemSelected(item: MenuItem): Boolean { if (item.itemId == R.id.menu_info) { - MaterialDialog(context!!).show { + MaterialDialog(requireContext()).show { customView(R.layout.dialog_send_transaction_info, scrollable = true) positiveButton(android.R.string.ok) { dismiss() } } 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 b94e780..b963a76 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/SettingsFragment.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/SettingsFragment.kt @@ -11,7 +11,7 @@ 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.fragment.app.viewModels import androidx.recyclerview.widget.LinearLayoutManager import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.callbacks.onDismiss @@ -60,11 +60,11 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter private const val RESPONSE_BROADCAST_TRANSACTION = 3 } + private val viewModel: SettingsFragmentViewModel by viewModels() + private var _binding: FragmentSettingsBinding? = null private val binding get() = _binding!! - private lateinit var mViewModel: SettingsFragmentViewModel - private var mUserAccount: UserAccount? = null private var privateKey: String? = null @@ -119,19 +119,16 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter .getString(Constants.KEY_CURRENT_ACCOUNT_ID, "") ?: "" // Configure ViewModel - mViewModel = ViewModelProviders.of(this).get(SettingsFragmentViewModel::class.java) + viewModel.getUserAccount(userId).observe(viewLifecycleOwner, { userAccount -> + if (userAccount != null) { + mUserAccount = UserAccount(userAccount.id, userAccount.name) + binding.btnUpgradeToLTM.isEnabled = + !userAccount.isLtm // Disable button if already LTM + } + }) - mViewModel.getUserAccount(userId).observe(this, - androidx.lifecycle.Observer { userAccount -> - if (userAccount != null) { - mUserAccount = UserAccount(userAccount.id, userAccount.name) - binding.btnUpgradeToLTM.isEnabled = - !userAccount.isLtm // Disable button if already LTM - } - }) - - mViewModel.getWIF(userId, AuthorityType.ACTIVE.ordinal).observe(this, - androidx.lifecycle.Observer { encryptedWIF -> + viewModel.getWIF(userId, AuthorityType.ACTIVE.ordinal) + .observe(viewLifecycleOwner, { encryptedWIF -> context?.let { try { privateKey = CryptoUtils.decrypt(it, encryptedWIF) @@ -624,7 +621,7 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter private fun removeAccount(context: Context) { // Clears the database. - mViewModel.clearDatabase(context) + viewModel.clearDatabase(context) // Clears the shared preferences. val pref = PreferenceManager.getDefaultSharedPreferences(context) 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 f19752b..9004951 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/TransactionsFragment.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/TransactionsFragment.kt @@ -11,8 +11,7 @@ import androidx.appcompat.widget.SearchView import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity -import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModelProviders +import androidx.fragment.app.viewModels import androidx.recyclerview.widget.LinearLayoutManager import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.list.listItemsMultiChoice @@ -20,7 +19,6 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics 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.* @@ -42,11 +40,11 @@ class TransactionsFragment : Fragment(), FilterOptionsDialog.OnFilterOptionsSele private const val REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION = 100 } + private val viewModel: TransactionsViewModel by viewModels() + private var _binding: FragmentTransactionsBinding? = null private val binding get() = _binding!! - private lateinit var mViewModel: TransactionsViewModel - private var mDisposables = CompositeDisposable() override fun onCreateView( @@ -74,31 +72,28 @@ class TransactionsFragment : Fragment(), FilterOptionsDialog.OnFilterOptionsSele val userId = PreferenceManager.getDefaultSharedPreferences(context) .getString(Constants.KEY_CURRENT_ACCOUNT_ID, "") ?: "" - val transfersDetailsAdapter = TransfersDetailsAdapter(context!!) + val transfersDetailsAdapter = TransfersDetailsAdapter(requireContext()) binding.rvTransactions.adapter = transfersDetailsAdapter binding.rvTransactions.layoutManager = LinearLayoutManager(context) // Configure TransactionsViewModel to fetch the transaction history - mViewModel = ViewModelProviders.of(this).get(TransactionsViewModel::class.java) + viewModel.getFilteredTransactions(userId).observe(viewLifecycleOwner, { transactions -> + if (transactions.isEmpty()) { + binding.rvTransactions.visibility = View.GONE + binding.tvEmpty.visibility = View.VISIBLE + } else { + binding.rvTransactions.visibility = View.VISIBLE + binding.tvEmpty.visibility = View.GONE - mViewModel.getFilteredTransactions(userId).observe(this, - Observer> { transactions -> - if (transactions.isEmpty()) { - binding.rvTransactions.visibility = View.GONE - binding.tvEmpty.visibility = View.VISIBLE - } else { - binding.rvTransactions.visibility = View.VISIBLE - binding.tvEmpty.visibility = View.GONE + val shouldScrollUp = transactions.size - transfersDetailsAdapter.itemCount == 1 + transfersDetailsAdapter.replaceAll(transactions) - val shouldScrollUp = transactions.size - transfersDetailsAdapter.itemCount == 1 - transfersDetailsAdapter.replaceAll(transactions) - - // 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) - binding.rvTransactions.scrollToPosition(0) - } - }) + // 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) + binding.rvTransactions.scrollToPosition(0) + } + }) // Set custom touch listener to handle bounce/stretch effect val bounceTouchListener = BounceTouchListener(binding.rvTransactions) @@ -118,7 +113,7 @@ class TransactionsFragment : Fragment(), FilterOptionsDialog.OnFilterOptionsSele .map { it.queryText.toString().toLowerCase() } .observeOn(AndroidSchedulers.mainThread()) .subscribe { - mViewModel.setFilterQuery(it) + viewModel.setFilterQuery(it) } ) @@ -133,7 +128,7 @@ class TransactionsFragment : Fragment(), FilterOptionsDialog.OnFilterOptionsSele val args = Bundle() args.putParcelable( FilterOptionsDialog.KEY_FILTER_OPTIONS, - mViewModel.getFilterOptions() + viewModel.getFilterOptions() ) filterOptionsDialog.arguments = args filterOptionsDialog.show(childFragmentManager, "filter-options-tag") @@ -163,13 +158,13 @@ class TransactionsFragment : Fragment(), FilterOptionsDialog.OnFilterOptionsSele * Gets called when the user selects some filter options in the [FilterOptionsDialog] and wants to apply them. */ override fun onFilterOptionsSelected(filterOptions: FilterOptions) { - mViewModel.applyFilterOptions(filterOptions) + viewModel.applyFilterOptions(filterOptions) } /** Verifies that the storage permission has been granted before attempting to generate the export options */ private fun verifyStoragePermission() { if (ContextCompat.checkSelfPermission( - activity!!, + requireActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE ) != PackageManager.PERMISSION_GRANTED @@ -205,7 +200,7 @@ class TransactionsFragment : Fragment(), FilterOptionsDialog.OnFilterOptionsSele } private fun showExportOptionsDialog() { - MaterialDialog(context!!).show { + MaterialDialog(requireContext()).show { title(R.string.title_export_transactions) listItemsMultiChoice( R.array.export_options, @@ -228,7 +223,7 @@ class TransactionsFragment : Fragment(), FilterOptionsDialog.OnFilterOptionsSele return } - mViewModel.getFilteredTransactionsOnce()?.let { filteredTransactions -> + viewModel.getFilteredTransactionsOnce()?.let { filteredTransactions -> if (exportPDF) activity?.let { PDFGeneratorTask(it).execute(filteredTransactions) } diff --git a/build.gradle b/build.gradle index ab576f3..12b9d34 100644 --- a/build.gradle +++ b/build.gradle @@ -3,8 +3,8 @@ buildscript { ext { - kotlin_version = '1.4.30' - nav_version = '2.1.0' + kotlin_version = '1.4.31' + nav_version = '2.3.4' } repositories { From dd1bf98e5dc579332cd70a11e526bf0869fb73f6 Mon Sep 17 00:00:00 2001 From: Severiano Jaramillo Date: Tue, 23 Mar 2021 21:27:49 -0700 Subject: [PATCH 02/27] Update Firebase Crashlytics and Gradle plugins. - Added the new Firebase Android BoM (Bill of Materials), to easily manage Firebase libraries: https://firebase.google.com/docs/android/learn-more#bom. - Updated the Gradle, Google Services and Crashlytics gradle plugins. --- app/build.gradle | 7 ++++--- build.gradle | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 0ef4c63..63a2808 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -59,7 +59,6 @@ android { useLibrary 'android.test.runner' useLibrary 'android.test.base' useLibrary 'android.test.mock' - } dependencies { @@ -108,8 +107,10 @@ dependencies { implementation 'com.squareup.okhttp3:logging-interceptor:4.2.0' implementation 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0' //Firebase - implementation 'com.google.firebase:firebase-analytics:18.0.2' - implementation 'com.google.firebase:firebase-crashlytics:17.3.1' + implementation platform('com.google.firebase:firebase-bom:26.7.0') // Import the BoM for the Firebase platform + implementation 'com.google.firebase:firebase-crashlytics-ktx' + implementation 'com.google.firebase:firebase-analytics-ktx' + // CSV generation implementation 'com.opencsv:opencsv:3.7' // Others diff --git a/build.gradle b/build.gradle index 12b9d34..b5ae386 100644 --- a/build.gradle +++ b/build.gradle @@ -13,11 +13,11 @@ buildscript { maven { url 'https://plugins.gradle.org/m2/' } } dependencies { - classpath 'com.android.tools.build:gradle:4.1.2' + classpath 'com.android.tools.build:gradle:4.1.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" - classpath 'com.google.gms:google-services:4.3.4' - classpath 'com.google.firebase:firebase-crashlytics-gradle:2.4.1' + classpath 'com.google.gms:google-services:4.3.5' + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.5.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files From 7e12224795baa315d79b1c35ff65abbb87e3af75 Mon Sep 17 00:00:00 2001 From: Severiano Jaramillo Date: Tue, 23 Mar 2021 22:31:44 -0700 Subject: [PATCH 03/27] Update Material Components library. - Introduced the new material date range picker, to be used in the FilterOptionsDialog to select a date range. This was necessary because the old MaterialDatePicker that was used inside DatePickerFragment is no longer available in the latest version of the Material Components library. --- app/build.gradle | 2 +- .../fragments/FilterOptionsDialog.kt | 110 +++++++----------- .../views/DatePickerFragment.kt | 93 --------------- .../views/MyTextInputEditText.kt | 2 +- .../main/res/layout/dialog_filter_options.xml | 2 +- 5 files changed, 46 insertions(+), 163 deletions(-) delete mode 100644 app/src/main/java/cy/agorise/bitsybitshareswallet/views/DatePickerFragment.kt diff --git a/app/build.gradle b/app/build.gradle index 63a2808..9d310f5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -78,7 +78,7 @@ dependencies { // Google implementation 'com.google.zxing:core:3.4.0' implementation 'com.google.code.gson:gson:2.8.6' - implementation 'com.google.android.material:material:1.1.0-alpha04' + implementation 'com.google.android.material:material:1.3.0' implementation 'com.google.android.gms:play-services-maps:17.0.0' implementation 'com.google.maps.android:android-maps-utils:0.5' // AAC Lifecycle 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 df27dfb..e4bb5d1 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/FilterOptionsDialog.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/FilterOptionsDialog.kt @@ -11,8 +11,10 @@ import androidx.core.os.ConfigurationCompat import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import com.google.android.material.datepicker.CalendarConstraints +import com.google.android.material.datepicker.DateValidatorPointBackward +import com.google.android.material.datepicker.MaterialDatePicker 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 @@ -20,7 +22,6 @@ 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 java.text.SimpleDateFormat import java.util.* import kotlin.collections.ArrayList @@ -30,15 +31,11 @@ import kotlin.collections.ArrayList * Creates a Dialog that communicates with {@link TransactionsActivity} to give it parameters about * how to filter the list of Transactions */ -class FilterOptionsDialog : DialogFragment(), DatePickerFragment.OnDateSetListener { +class FilterOptionsDialog : DialogFragment() { - companion object { - private const val TAG = "FilterOptionsDialog" - - const val KEY_FILTER_OPTIONS = "key_filter_options" - - const val START_DATE_PICKER = 0 - const val END_DATE_PICKER = 1 + // Container Fragment must implement this interface + interface OnFilterOptionsSelectedListener { + fun onFilterOptionsSelected(filterOptions: FilterOptions) } private val viewModel: BalanceDetailViewModel by viewModels() @@ -61,44 +58,6 @@ class FilterOptionsDialog : DialogFragment(), DatePickerFragment.OnDateSetListen private lateinit var mCurrency: Currency - override fun onDateSet(which: Int, timestamp: Long) { - when (which) { - START_DATE_PICKER -> { - mFilterOptions.startDate = timestamp - - updateDateTextViews() - } - END_DATE_PICKER -> { - mFilterOptions.endDate = timestamp - - // Make sure there is at least one moth difference between start and end time - val calendar = Calendar.getInstance() - calendar.timeInMillis = mFilterOptions.endDate - calendar.add(Calendar.MONTH, -1) - - val tmpTime = calendar.timeInMillis - - if (tmpTime < mFilterOptions.startDate) - mFilterOptions.startDate = tmpTime - - updateDateTextViews() - } - } - } - - private fun updateDateTextViews() { - var date = Date(mFilterOptions.startDate) - binding.tvStartDate.text = dateFormat.format(date) - - date = Date(mFilterOptions.endDate) - binding.tvEndDate.text = dateFormat.format(date) - } - - // Container Fragment must implement this interface - interface OnFilterOptionsSelectedListener { - fun onFilterOptionsSelected(filterOptions: FilterOptions) - } - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -136,9 +95,9 @@ class FilterOptionsDialog : DialogFragment(), DatePickerFragment.OnDateSetListen } binding.cbDateRange.isChecked = mFilterOptions.dateRangeAll - binding.tvStartDate.setOnClickListener(mDateClickListener) + binding.tvStartDate.setOnClickListener { showDateRangePicker() } - binding.tvEndDate.setOnClickListener(mDateClickListener) + binding.tvEndDate.setOnClickListener { showDateRangePicker() } updateDateTextViews() @@ -220,27 +179,38 @@ class FilterOptionsDialog : DialogFragment(), DatePickerFragment.OnDateSetListen } } - private val mDateClickListener = View.OnClickListener { v -> - val calendar = Calendar.getInstance() + private fun updateDateTextViews() { + var date = Date(mFilterOptions.startDate) + binding.tvStartDate.text = dateFormat.format(date) - // Variable used to select that date on the calendar - var currentTime = calendar.timeInMillis - var maxTime = currentTime + date = Date(mFilterOptions.endDate) + binding.tvEndDate.text = dateFormat.format(date) + } - var which = -1 - if (v.id == R.id.tvStartDate) { - which = START_DATE_PICKER - currentTime = mFilterOptions.startDate - calendar.timeInMillis = mFilterOptions.endDate - calendar.add(Calendar.MONTH, -1) - maxTime = calendar.timeInMillis - } else if (v.id == R.id.tvEndDate) { - which = END_DATE_PICKER - currentTime = mFilterOptions.endDate + private fun showDateRangePicker() { + // Makes only dates until today selectable. + val constraintsBuilder = + CalendarConstraints.Builder() + .setValidator(DateValidatorPointBackward.now()) + + val dateRangePicker = + MaterialDatePicker.Builder.dateRangePicker() + .setSelection( + androidx.core.util.Pair( + mFilterOptions.startDate, + mFilterOptions.endDate + ) + ) + .setCalendarConstraints(constraintsBuilder.build()) + .build() + + dateRangePicker.addOnPositiveButtonClickListener { + mFilterOptions.startDate = it.first!! // This is safe cause these should never be null + mFilterOptions.endDate = it.second!! + updateDateTextViews() } - val datePickerFragment = DatePickerFragment.newInstance(which, currentTime, maxTime) - datePickerFragment.show(childFragmentManager, "date-picker") + dateRangePicker.show(childFragmentManager, "date-picker") } private fun validateFields() { @@ -283,4 +253,10 @@ class FilterOptionsDialog : DialogFragment(), DatePickerFragment.OnDateSetListen mCallback!!.onFilterOptionsSelected(mFilterOptions) dismiss() } + + companion object { + private const val TAG = "FilterOptionsDialog" + + const val KEY_FILTER_OPTIONS = "key_filter_options" + } } \ No newline at end of file diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/views/DatePickerFragment.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/views/DatePickerFragment.kt deleted file mode 100644 index 92aaa75..0000000 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/views/DatePickerFragment.kt +++ /dev/null @@ -1,93 +0,0 @@ -package cy.agorise.bitsybitshareswallet.views - -import android.app.DatePickerDialog -import android.app.Dialog -import android.os.Bundle -import android.widget.DatePicker -import androidx.fragment.app.DialogFragment -import androidx.fragment.app.Fragment -import com.google.android.material.picker.MaterialDatePickerDialog -import java.util.* - -/** - * Lets the user select a Date and communicates the selection back to the parent fragment - * using the OnDateSetListener interface, which has to be implemented by the parent. - */ -class DatePickerFragment : DialogFragment(), DatePickerDialog.OnDateSetListener { - - companion object { - const val TAG = "DatePickerFragment" - - const val KEY_WHICH = "key_which" - const val KEY_CURRENT = "key_current" - const val KEY_MAX = "key_max" - - fun newInstance(which: Int, currentTime: Long, maxTime: Long): DatePickerFragment { - val f = DatePickerFragment() - val bundle = Bundle() - bundle.putInt(KEY_WHICH, which) - bundle.putLong(KEY_CURRENT, currentTime) - bundle.putLong(KEY_MAX, maxTime) - f.arguments = bundle - return f - } - } - - /** - * Callback used to communicate the date selection back to the parent - */ - private var mCallback: OnDateSetListener? = null - - private var which: Int = 0 - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - which = arguments!!.getInt(KEY_WHICH) - } - - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - onAttachToParentFragment(parentFragment) - - val currentTime = arguments!!.getLong(KEY_CURRENT) - val maxTime = arguments!!.getLong(KEY_MAX) - - // Use the current date as the default date in the picker - val calendar = Calendar.getInstance() - calendar.timeInMillis = currentTime - - val year = calendar.get(Calendar.YEAR) - val month = calendar.get(Calendar.MONTH) - val day = calendar.get(Calendar.DAY_OF_MONTH) - - // Create a new instance of DatePickerDialog and return it - val datePicker = MaterialDatePickerDialog(activity!!, this, year, month, day) - - // Set maximum date allowed to today - datePicker.datePicker.maxDate = maxTime - - return datePicker - } - - override fun onDateSet(view: DatePicker, year: Int, month: Int, day: Int) { - val calendar = GregorianCalendar() - calendar.set(year, month, day) - mCallback?.onDateSet(which, calendar.time.time) - } - - /** - * Attaches the current [DialogFragment] to its [Fragment] parent, to initialize the - * [OnDateSetListener] interface - */ - private fun onAttachToParentFragment(fragment: Fragment?) { - try { - mCallback = fragment as OnDateSetListener - } catch (e: ClassCastException) { - throw ClassCastException("$fragment must implement OnDateSetListener") - } - } - - // Container Activity must implement this interface - interface OnDateSetListener { - fun onDateSet(which: Int, timestamp: Long) - } -} \ No newline at end of file diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/views/MyTextInputEditText.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/views/MyTextInputEditText.kt index 0012e44..20ea8d2 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/views/MyTextInputEditText.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/views/MyTextInputEditText.kt @@ -13,7 +13,7 @@ import cy.agorise.bitsybitshareswallet.utils.hideKeyboard * A TextInputEditText that hides the keyboard when the focus is removed from it and also lets you * use actions ("Done", "Go", etc.) on multi-line edits. */ -class MyTextInputEditText(context: Context?, attrs: AttributeSet?) : TextInputEditText(context, attrs){ +class MyTextInputEditText(context: Context, attrs: AttributeSet?) : TextInputEditText(context, attrs){ override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection? { val connection = super.onCreateInputConnection(outAttrs) diff --git a/app/src/main/res/layout/dialog_filter_options.xml b/app/src/main/res/layout/dialog_filter_options.xml index 510b0d8..b20725c 100644 --- a/app/src/main/res/layout/dialog_filter_options.xml +++ b/app/src/main/res/layout/dialog_filter_options.xml @@ -138,7 +138,7 @@ - Date: Wed, 24 Mar 2021 22:34:49 -0700 Subject: [PATCH 04/27] Fix date range selection issue. - The issue was that the MaterialDatePicker uses the UTC time zone internally. We had to account for that to avoid showing an off by one wrong selection on the Filter Options date range and the Date Range picker (MaterialDatePicker). --- .../fragments/FilterOptionsDialog.kt | 2 ++ .../viewmodels/TransactionsViewModel.kt | 13 +++++++++++-- app/src/main/res/layout/dialog_filter_options.xml | 5 ++++- 3 files changed, 17 insertions(+), 3 deletions(-) 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 e4bb5d1..c040519 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/FilterOptionsDialog.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/FilterOptionsDialog.kt @@ -99,6 +99,8 @@ class FilterOptionsDialog : DialogFragment() { binding.tvEndDate.setOnClickListener { showDateRangePicker() } + dateFormat.timeZone = TimeZone.getTimeZone("UTC") + updateDateTextViews() // Initialize Asset diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/viewmodels/TransactionsViewModel.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/viewmodels/TransactionsViewModel.kt index 5b0966c..7a1f952 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/viewmodels/TransactionsViewModel.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/viewmodels/TransactionsViewModel.kt @@ -2,6 +2,7 @@ package cy.agorise.bitsybitshareswallet.viewmodels import android.app.Application import androidx.lifecycle.* +import com.google.android.material.datepicker.MaterialDatePicker import cy.agorise.bitsybitshareswallet.database.joins.TransferDetail import cy.agorise.bitsybitshareswallet.models.FilterOptions import cy.agorise.bitsybitshareswallet.repositories.TransferDetailRepository @@ -11,6 +12,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.util.* + class TransactionsViewModel(application: Application) : AndroidViewModel(application) { companion object { const val TAG = "TransactionsViewModel" @@ -32,12 +34,19 @@ class TransactionsViewModel(application: Application) : AndroidViewModel(applica init { // Initialize the start and end dates for the FilterOptions - val calendar = Calendar.getInstance() + val calendar = getClearedUtc() + calendar.timeInMillis = MaterialDatePicker.todayInUtcMilliseconds() mFilterOptions.endDate = calendar.timeInMillis - calendar.add(Calendar.MONTH, -2) + calendar.roll(Calendar.MONTH, -2) mFilterOptions.startDate = calendar.timeInMillis } + private fun getClearedUtc(): Calendar { + val utc = Calendar.getInstance(TimeZone.getTimeZone("UTC")) + utc.clear() + return utc + } + internal fun getFilteredTransactions(userId: String): LiveData> { val currencyCode = Helper.getCoingeckoSupportedCurrency(Locale.getDefault()) transactions = mRepository.getAll(userId, currencyCode) diff --git a/app/src/main/res/layout/dialog_filter_options.xml b/app/src/main/res/layout/dialog_filter_options.xml index b20725c..efcb3a9 100644 --- a/app/src/main/res/layout/dialog_filter_options.xml +++ b/app/src/main/res/layout/dialog_filter_options.xml @@ -67,6 +67,7 @@ android:text="@string/text__date_range" android:textSize="16sp" app:layout_constraintTop_toTopOf="@id/cbDateRange" + app:layout_constraintBottom_toBottomOf="@id/cbDateRange" app:layout_constraintStart_toStartOf="parent"/> - \ No newline at end of file + From e1dd01c43dff85717b1a9a4caac939b473572098 Mon Sep 17 00:00:00 2001 From: Severiano Jaramillo Date: Wed, 24 Mar 2021 22:45:05 -0700 Subject: [PATCH 05/27] Add colorOnPrimary style color. --- app/src/main/res/values/styles.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index f051fbf..4f84ddb 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -19,6 +19,7 @@