Add the ability to export logs.

This includes bare bones UI for the wallet details screen. Closes #26 and Closes #27.
This commit is contained in:
Kevin Gorham 2019-12-18 11:09:13 -05:00
parent 83056ad492
commit 93916a45f7
No known key found for this signature in database
GPG Key ID: CCA55602DF49FC38
10 changed files with 168 additions and 15 deletions

View File

@ -0,0 +1,18 @@
package cash.z.ecc.android.integration
import android.content.Context
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Before
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class IntegrationTest {
private lateinit var appContext: Context
@Before
fun start() {
appContext = InstrumentationRegistry.getInstrumentation().targetContext
}
}

View File

@ -3,6 +3,8 @@ package cash.z.ecc.android.ext
import android.view.View
import android.view.View.*
import cash.z.ecc.android.ui.MainActivity
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.channelFlow
fun View.goneIf(isGone: Boolean) {
visibility = if (isGone) GONE else VISIBLE
@ -28,4 +30,13 @@ fun View.onClickNavUp() {
"Expected MainActivity but found ${context.javaClass.simpleName}"
)
}
}
fun View.clicks() = channelFlow<View> {
setOnClickListener {
offer(this@clicks)
}
awaitClose {
setOnClickListener(null)
}
}

View File

@ -8,7 +8,7 @@ import java.text.SimpleDateFormat
class FeedbackFile(fileName: String = "feedback.log") :
FeedbackCoordinator.FeedbackObserver {
private val file = File(ZcashWalletApp.instance.noBackupFilesDir, fileName)
val file = File(ZcashWalletApp.instance.noBackupFilesDir, fileName)
private val format = SimpleDateFormat("MM-dd HH:mm:ss.SSS")

View File

@ -50,15 +50,14 @@ class MainActivity : DaggerAppCompatActivity() {
setContentView(R.layout.main_activity)
initNavigation()
window.statusBarColor = Color.TRANSPARENT;
window.statusBarColor = Color.TRANSPARENT
window.navigationBarColor = Color.TRANSPARENT
window.setFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
)
setWindowFlag(
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
false
)// | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, false)
setWindowFlag(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, false)
setWindowFlag(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, false)
lifecycleScope.launch {
feedback.start()

View File

@ -1,22 +1,60 @@
package cash.z.ecc.android.ui.detail
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import androidx.lifecycle.lifecycleScope
import cash.z.ecc.android.databinding.FragmentDetailBinding
import cash.z.ecc.android.di.annotation.FragmentScope
import cash.z.ecc.android.ext.clicks
import cash.z.ecc.android.ext.onClickNavUp
import cash.z.ecc.android.feedback.FeedbackFile
import cash.z.ecc.android.ui.base.BaseFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import okio.Okio
class WalletDetailFragment : BaseFragment<FragmentDetailBinding>() {
override fun inflate(inflater: LayoutInflater): FragmentDetailBinding =
FragmentDetailBinding.inflate(inflater)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.backButtonHitArea.onClickNavUp()
binding.buttonLogs.clicks().debounce(250L).onEach {
onViewFeedback()
}.launchIn(lifecycleScope)
}
private fun onViewFeedback() {
loadLogFileAsText().let { logText ->
if (logText == null) {
mainActivity?.showSnackbar("Log file not found!")
} else {
val sendIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_TEXT, logText)
type = "text/plain"
}
val shareIntent = Intent.createChooser(sendIntent, "Share Log File")
startActivity(shareIntent)
}
}
}
private fun loadLogFileAsText(): String? {
val feedbackFile: FeedbackFile =
mainActivity?.feedbackCoordinator?.findObserver() ?: return null
Okio.buffer(Okio.source(feedbackFile.file)).use {
return it.readUtf8()
}
}
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="16dp" />
<stroke android:width="1dp" android:color="#282828"/>
<solid android:color="@color/background_banner"/>
</shape>

View File

@ -6,6 +6,41 @@
android:layout_height="match_parent"
android:background="@drawable/background_home">
<!-- -->
<!-- Guidelines -->
<!-- -->
<!-- TODO: redo these keylines to match the designs, exactly -->
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guieline_bottom_buttons"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.7017784" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guieline_keyline_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.054" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guieline_keyline_end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.946" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guieline_keyline_bottom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="1.0" />
<!-- Back Button -->
<ImageView
android:id="@+id/back_button"
@ -41,4 +76,42 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/text_banner_message"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@drawable/background_banner_large"
app:layout_constraintEnd_toEndOf="@id/guieline_keyline_end"
app:layout_constraintStart_toStartOf="@id/guieline_keyline_start"
app:layout_constraintTop_toBottomOf="@id/guieline_bottom_buttons"
app:layout_constraintBottom_toBottomOf="@id/guieline_keyline_bottom"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/button_feedback"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="@style/Zcash.Button.White"
android:text="Send Feedback"
android:padding="12dp"
android:layout_marginEnd="24dp"
android:layout_marginStart="24dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@id/guieline_keyline_start"
app:layout_constraintEnd_toEndOf="@id/guieline_keyline_end"
app:layout_constraintVertical_bias="0.8"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/button_logs"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:text="View Logs"
android:textColor="@color/text_light"
app:layout_constraintTop_toBottomOf="@id/button_feedback"
app:layout_constraintBottom_toBottomOf="@id/guieline_keyline_bottom"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintVertical_bias="0.2"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -19,15 +19,20 @@
<item name="windowNoTitle">true</item>
</style>
<!-- Text Styles -->
<!-- Widgets -->
<style name="Zcash.TextView.NumberPad" parent="Widget.AppCompat.TextView">
<item name="android:textAppearance">@style/Zcash.TextAppearance.NumberPad</item>
<item name="android:background">@drawable/selector_pressed_ripple_circle</item>
</style>
<style name="Zcash.Button.White" parent="Widget.MaterialComponents.Button">
<item name="backgroundTint">@android:color/white</item>
<item name="android:textColor">@color/text_dark</item>
</style>
<style name="Zcash.Button.OutlinedButton" parent="Widget.MaterialComponents.Button.OutlinedButton">
<item name="strokeColor">@color/zcashWhite_87</item>
<item name="strokeColor">@color/zcashWhite</item>
</style>
<!-- Text Appearances -->

View File

@ -28,9 +28,7 @@ class FeedbackCoordinator(val feedback: Feedback, defaultObservers: Set<Feedback
}
}
}
if (defaultObservers.size != 3) throw IllegalStateException("BOOM")
defaultObservers.forEach {
Log.e("BOOM", "adding observer: $it to $feedback")
addObserver(it)
}
}
@ -38,7 +36,7 @@ class FeedbackCoordinator(val feedback: Feedback, defaultObservers: Set<Feedback
private var contextMetrics = Dispatchers.IO
private var contextActions = Dispatchers.IO
private val jobs = CompositeJob()
private val observers = mutableSetOf<FeedbackObserver>()
val observers = mutableSetOf<FeedbackObserver>()
/**
* Wait for any in-flight listeners to complete.
@ -91,6 +89,10 @@ class FeedbackCoordinator(val feedback: Feedback, defaultObservers: Set<Feedback
}
}
inline fun <reified T: FeedbackObserver> findObserver(): T? {
return observers.firstOrNull { it::class == T::class } as T
}
private fun observeMetrics(onMetricListener: (Feedback.Metric) -> Unit) {
feedback.metrics.onEach {
jobs += feedback.scope.launch {

View File

@ -26,27 +26,27 @@ class LockBoxText {
val testMessage = "Some Bytes To Test"
val testBytes = testMessage.toByteArray()
lockBox.setBytes("seed", testBytes)
assertEquals(testMessage, String(lockBox.getBytes("seed")))
assertEquals(testMessage, String(lockBox.getBytes("seed")!!))
}
@Test
fun testSeed_storeNegatives() {
val testBytes = byteArrayOf(0x00, 0x00, -0x0F, -0x0B)
lockBox.setBytes("seed", testBytes)
assertTrue(testBytes.contentEquals(lockBox.getBytes("seed")))
assertTrue(testBytes.contentEquals(lockBox.getBytes("seed")!!))
}
@Test
fun testSeed_storeLeadingZeros() {
val testBytes = byteArrayOf(0x00, 0x00, 0x0F, 0x0B)
lockBox.setBytes("seed", testBytes)
assertTrue(testBytes.contentEquals(lockBox.getBytes("seed")))
assertTrue(testBytes.contentEquals(lockBox.getBytes("seed")!!))
}
@Test
fun testPrivateKey_retrieve() {
val testMessage = "Some Bytes To Test"
lockBox.setCharsUtf8("spendingKey", testMessage.toCharArray())
assertEquals(testMessage, String(lockBox.getCharsUtf8("spendingKey")))
assertEquals(testMessage, String(lockBox.getCharsUtf8("spendingKey")!!))
}
}