zircles-android/app/src/main/java/cash/z/ecc/android/ui/MainActivity.kt

260 lines
8.7 KiB
Kotlin
Raw Normal View History

2019-11-26 12:29:16 -08:00
package cash.z.ecc.android.ui
2020-01-09 23:53:16 -08:00
import android.Manifest
2019-11-26 12:29:16 -08:00
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
2020-01-09 23:53:16 -08:00
import android.content.pm.PackageManager
2019-11-26 12:29:16 -08:00
import android.graphics.Color
import android.media.MediaPlayer
2020-01-09 23:53:16 -08:00
import android.os.Build
2019-11-26 12:29:16 -08:00
import android.os.Bundle
import android.os.Vibrator
import android.util.Log
2019-11-26 12:29:16 -08:00
import android.view.View
import android.view.ViewGroup
2019-11-26 12:29:16 -08:00
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity
2020-01-09 23:53:16 -08:00
import androidx.core.content.ContextCompat
2019-11-26 12:29:16 -08:00
import androidx.core.content.getSystemService
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
2019-11-26 12:29:16 -08:00
import androidx.navigation.NavController
import androidx.navigation.findNavController
import cash.z.ecc.android.R
import cash.z.ecc.android.ZcashWalletApp
import cash.z.ecc.android.di.component.MainActivitySubcomponent
import cash.z.ecc.android.di.component.SynchronizerSubcomponent
import cash.z.ecc.android.feedback.Feedback
import cash.z.ecc.android.feedback.FeedbackCoordinator
import cash.z.ecc.android.feedback.LaunchMetric
import cash.z.ecc.android.feedback.Report.NonUserAction.FEEDBACK_STOPPED
import cash.z.ecc.android.feedback.Report.NonUserAction.SYNC_START
import cash.z.wallet.sdk.Initializer
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.launch
import javax.inject.Inject
2019-11-26 12:29:16 -08:00
class MainActivity : AppCompatActivity() {
@Inject
lateinit var feedback: Feedback
@Inject
lateinit var feedbackCoordinator: FeedbackCoordinator
@Inject
lateinit var clipboard: ClipboardManager
private val mediaPlayer: MediaPlayer = MediaPlayer()
private var snackbar: Snackbar? = null
lateinit var navController: NavController
lateinit var component: MainActivitySubcomponent
lateinit var synchronizerComponent: SynchronizerSubcomponent
2020-01-09 23:53:16 -08:00
private val hasCameraPermission
get() = ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED
2019-11-26 12:29:16 -08:00
override fun onCreate(savedInstanceState: Bundle?) {
component = ZcashWalletApp.component.mainActivitySubcomponent().create(this).also {
it.inject(this)
}
2019-11-26 12:29:16 -08:00
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
initNavigation()
window.statusBarColor = Color.TRANSPARENT
window.navigationBarColor = Color.TRANSPARENT
2019-11-26 12:29:16 -08:00
window.setFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
)
setWindowFlag(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, false)
setWindowFlag(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, false)
lifecycleScope.launch {
feedback.start()
}
}
override fun onResume() {
super.onResume()
// keep track of app launch metrics
// (how long does it take the app to open when it is not already in the foreground)
ZcashWalletApp.instance.let { app ->
if (!app.creationMeasured) {
app.creationMeasured = true
feedback.report(LaunchMetric())
}
}
}
override fun onDestroy() {
lifecycleScope.launch {
feedback.report(FEEDBACK_STOPPED)
feedback.stop()
}
super.onDestroy()
2019-11-26 12:29:16 -08:00
}
private fun setWindowFlag(bits: Int, on: Boolean) {
val win = window
val winParams = win.attributes
if (on) {
winParams.flags = winParams.flags or bits
} else {
winParams.flags = winParams.flags and bits.inv()
}
win.attributes = winParams
}
2019-11-26 12:29:16 -08:00
private fun initNavigation() {
navController = findNavController(R.id.nav_host_fragment)
navController.addOnDestinationChangedListener { _, _, _ ->
// hide the keyboard anytime we change destinations
getSystemService<InputMethodManager>()?.hideSoftInputFromWindow(
this@MainActivity.window.decorView.rootView.windowToken,
InputMethodManager.HIDE_NOT_ALWAYS
)
}
}
fun startSync(initializer: Initializer) {
synchronizerComponent = ZcashWalletApp.component.synchronizerSubcomponent().create(initializer)
feedback.report(SYNC_START)
synchronizerComponent.synchronizer().start(lifecycleScope)
}
fun playSound(fileName: String) {
mediaPlayer.apply {
if (isPlaying) stop()
try {
reset()
assets.openFd(fileName).let { afd ->
setDataSource(afd.fileDescriptor, afd.startOffset, afd.length)
}
prepare()
start()
} catch (t: Throwable) {
Log.e("SDK_ERROR", "ERROR: unable to play sound due to $t")
}
}
}
// TODO: spruce this up with API 26 stuff
fun vibrateSuccess() {
val vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
if (vibrator.hasVibrator()) {
vibrator.vibrate(longArrayOf(0, 200, 200, 100, 100, 800), -1)
}
}
fun copyAddress(view: View? = null) {
lifecycleScope.launch {
clipboard.setPrimaryClip(
ClipData.newPlainText(
"Z-Address",
synchronizerComponent.synchronizer().getAddress()
)
2019-11-26 12:29:16 -08:00
)
showMessage("Address copied!", "Sweet")
}
2019-11-26 12:29:16 -08:00
}
2020-01-13 16:09:22 -08:00
fun copyText(textToCopy: String, label: String = "zECC Wallet Text") {
clipboard.setPrimaryClip(
ClipData.newPlainText(label, textToCopy)
)
showMessage("$label copied!", "Sweet")
}
fun preventBackPress(fragment: Fragment) {
onFragmentBackPressed(fragment){}
}
fun onFragmentBackPressed(fragment: Fragment, block: () -> Unit) {
onBackPressedDispatcher.addCallback(fragment, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
block()
}
})
}
2019-11-26 12:29:16 -08:00
private fun showMessage(message: String, action: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
fun showSnackbar(message: String, action: String = "OK"): Snackbar {
return if (snackbar == null) {
val view = findViewById<View>(R.id.main_activity_container)
val snacks = Snackbar
.make(view, "$message", Snackbar.LENGTH_INDEFINITE)
.setAction(action) { /*auto-close*/ }
val snackBarView = snacks.view as ViewGroup
val navigationBarHeight = resources.getDimensionPixelSize(resources.getIdentifier("navigation_bar_height", "dimen", "android"))
val params = snackBarView.getChildAt(0).layoutParams as ViewGroup.MarginLayoutParams
params.setMargins(
params.leftMargin,
params.topMargin,
params.rightMargin,
navigationBarHeight
)
snackBarView.getChildAt(0).setLayoutParams(params)
snacks
} else {
snackbar!!.setText(message).setAction(action) {/*auto-close*/}
}.also {
if (!it.isShownOrQueued) it.show()
}
}
2020-01-09 23:53:16 -08:00
2020-01-13 16:09:22 -08:00
/**
* @param popUpToInclusive the destination to remove from the stack before opening the camera.
* This only takes effect in the common case where the permission is granted.
*/
fun maybeOpenScan(popUpToInclusive: Int? = null) {
2020-01-09 23:53:16 -08:00
if (hasCameraPermission) {
2020-01-13 16:09:22 -08:00
openCamera(popUpToInclusive)
2020-01-09 23:53:16 -08:00
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(arrayOf(Manifest.permission.CAMERA), 101)
} else {
onNoCamera()
}
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == 101) {
if (grantResults.firstOrNull() == PackageManager.PERMISSION_GRANTED) {
openCamera()
} else {
onNoCamera()
}
}
}
2020-01-13 16:09:22 -08:00
private fun openCamera(popUpToInclusive: Int? = null) {
navController.navigate(popUpToInclusive ?: R.id.action_global_nav_scan)
2020-01-09 23:53:16 -08:00
}
private fun onNoCamera() {
showSnackbar("Well, this is awkward. You denied permission for the camera.")
}
2019-11-26 12:29:16 -08:00
}