package cash.z.ecc.android.ui.profile import android.content.Intent import android.net.Uri import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.widget.Toast import androidx.core.content.FileProvider.getUriForFile import androidx.fragment.app.viewModels import androidx.lifecycle.viewModelScope import cash.z.ecc.android.BuildConfig import cash.z.ecc.android.R import cash.z.ecc.android.ZcashWalletApp import cash.z.ecc.android.databinding.FragmentProfileBinding import cash.z.ecc.android.ext.* import cash.z.ecc.android.feedback.FeedbackFile import cash.z.ecc.android.feedback.Report import cash.z.ecc.android.feedback.Report.Funnel.UserFeedback import cash.z.ecc.android.feedback.Report.Tap.* import cash.z.ecc.android.sdk.SdkSynchronizer import cash.z.ecc.android.sdk.ext.toAbbreviatedAddress import cash.z.ecc.android.ui.MainActivity import cash.z.ecc.android.ui.base.BaseFragment import cash.z.ecc.android.ui.util.DebugFileTwig import cash.z.ecc.android.util.Bush import cash.z.ecc.android.util.twig import kotlinx.coroutines.launch import java.io.File class ProfileFragment : BaseFragment() { override val screen = Report.Screen.PROFILE private val viewModel: ProfileViewModel by viewModels() override fun inflate(inflater: LayoutInflater): FragmentProfileBinding = FragmentProfileBinding.inflate(inflater) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.hitAreaSettings.onClickNavTo(R.id.action_nav_profile_to_nav_settings) binding.hitAreaExit.onClickNavBack() { tapped(PROFILE_CLOSE) } binding.buttonBackup.setOnClickListener { tapped(PROFILE_BACKUP) mainActivity?.let { main -> main.authenticate( getString(R.string.biometric_backup_phrase_description), getString(R.string.biometric_backup_phrase_title) ) { main.safeNavigate(R.id.action_nav_profile_to_nav_backup) } } } binding.buttonRescan.setOnClickListener { tapped(PROFILE_RESCAN) onRescanWallet() } binding.textVersion.text = BuildConfig.VERSION_NAME onClick(binding.buttonLogs) { tapped(PROFILE_VIEW_USER_LOGS) onViewLogs() } binding.buttonLogs.setOnLongClickListener { tapped(PROFILE_VIEW_DEV_LOGS) onViewDevLogs() true } binding.iconProfile.setOnLongClickListener { tapped(AWESOME_OPEN) onEnterAwesomeMode() true } binding.textBannerMessage.setOnClickListener { openPlayStoreLink() } if (viewModel.isEasterEggTriggered()) { binding.iconProfile.setImageResource(R.drawable.ic_profile_zebra_02) } } private fun openPlayStoreLink() { getString(R.string.play_store_url).takeUnless { it.isBlank() }?.let { url -> mainActivity?.onLaunchUrl(url) } } private fun onEnterAwesomeMode() { (context as? MainActivity)?.safeNavigate(R.id.action_nav_profile_to_nav_awesome) ?: throw IllegalStateException( "Cannot navigate from this activity. " + "Expected MainActivity but found ${context?.javaClass?.simpleName}" ) } override fun onResume() { super.onResume() resumedScope.launch { binding.textAddress.text = viewModel.getShieldedAddress().toAbbreviatedAddress(12, 12) } } // TODO: reduce these to one function private fun onFullRescan() { twig("TMP: onFullRescan: CALLED") (viewModel.synchronizer as SdkSynchronizer).coroutineScope.launch { try { twig("TMP: onFullRescan: START") viewModel.fullRescan() Toast.makeText(ZcashWalletApp.instance, "Performing full rescan!", Toast.LENGTH_LONG).show() mainActivity?.navController?.popBackStack() } catch (t: Throwable) { mainActivity?.showCriticalMessage( "Full Rescan Failed", "Unable to perform full rescan due to error:\n\n${t.message}" ) } } } private fun onQuickRescan() { twig("TMP: onQuickRescan: CALLED") viewModel.viewModelScope.launch { try { twig("TMP: onQuickRescan: START") viewModel.quickRescan() Toast.makeText(ZcashWalletApp.instance, "Performing quick rescan!", Toast.LENGTH_LONG).show() mainActivity?.navController?.popBackStack() } catch (t: Throwable) { mainActivity?.showCriticalMessage("Quick Rescan Failed", "Unable to perform quick rescan due to error:\n\n${t.message}") } } } private fun onWipe() { mainActivity?.showConfirmation( "Are you sure?", "Wiping your data will close the app. Since your seed is preserved, " + "this operation is probably safe but please backup your seed anyway." + "\n\nContinue?", "Wipe" ) { viewModel.wipe() mainActivity?.finish() } } private fun onRescanWallet() { val quickDistance = viewModel.quickScanDistance() val fullDistance = viewModel.fullScanDistance() mainActivity?.showRescanWalletDialog( String.format("%,d", quickDistance), viewModel.blocksToMinutesString(quickDistance), String.format("%,d", fullDistance), viewModel.blocksToMinutesString(fullDistance), onFullRescan = ::onFullRescan, onQuickRescan = ::onQuickRescan, onWipe = ::onWipe ) } private fun onViewLogs() { shareFile(userLogFile()) } private fun onViewDevLogs() { developerLogFile().let { if (it == null) { mainActivity?.showSnackbar("Error: No developer log found!") } else { shareFile(it) } } } private fun shareFiles(vararg files: File?) { val uris = arrayListOf().apply { files.filterNotNull().mapNotNull { getUriForFile(ZcashWalletApp.instance, "${BuildConfig.APPLICATION_ID}.fileprovider", it) }.forEach { add(it) } } val intent = Intent(Intent.ACTION_SEND_MULTIPLE).apply { putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris) type = "text/*" } startActivity(Intent.createChooser(intent, getString(R.string.profile_share_log_title))) } fun shareFile(file: File?) { file ?: return val uri = getUriForFile(ZcashWalletApp.instance, "${BuildConfig.APPLICATION_ID}.fileprovider", file) val intent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_STREAM, uri) type = "text/plain" } startActivity(Intent.createChooser(intent, getString(R.string.profile_share_log_title))) } private fun userLogFile(): File? { return mainActivity?.feedbackCoordinator?.findObserver()?.file } private fun developerLogFile(): File? { return Bush.trunk.find()?.file } }