Continue migrating BugReport/Rageshake/Crash screens
This commit is contained in:
parent
c299ab4031
commit
e56ba5e315
21 changed files with 366 additions and 80 deletions
|
|
@ -49,7 +49,8 @@ class MainActivity : NodeComponentActivity() {
|
|||
buildContext = it,
|
||||
appComponentOwner = applicationContext as DaggerComponentOwner,
|
||||
matrix = appBindings.matrix(),
|
||||
sessionComponentsOwner = appBindings.sessionComponentsOwner()
|
||||
sessionComponentsOwner = appBindings.sessionComponentsOwner(),
|
||||
rootPresenter = appBindings.rootPresenter()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,11 +19,13 @@ package io.element.android.x.di
|
|||
import com.squareup.anvil.annotations.ContributesTo
|
||||
import io.element.android.x.matrix.Matrix
|
||||
import io.element.android.x.matrix.ui.MatrixUi
|
||||
import io.element.android.x.root.RootPresenter
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
@ContributesTo(AppScope::class)
|
||||
interface AppBindings {
|
||||
fun coroutineScope(): CoroutineScope
|
||||
fun rootPresenter(): RootPresenter
|
||||
fun matrix(): Matrix
|
||||
fun matrixUi(): MatrixUi
|
||||
fun sessionComponentsOwner(): SessionComponentsOwner
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import kotlinx.parcelize.Parcelize
|
|||
class LoggedInFlowNode(
|
||||
buildContext: BuildContext,
|
||||
val sessionId: SessionId,
|
||||
private val onOpenBugReport: () -> Unit,
|
||||
private val backstack: BackStack<NavTarget> = BackStack(
|
||||
initialElement = NavTarget.RoomList,
|
||||
savedStateMap = buildContext.savedStateMap,
|
||||
|
|
@ -64,7 +65,7 @@ class LoggedInFlowNode(
|
|||
)
|
||||
}
|
||||
NavTarget.Settings -> {
|
||||
PreferencesFlowNode(buildContext)
|
||||
PreferencesFlowNode(buildContext, onOpenBugReport)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,17 +5,12 @@ import androidx.compose.foundation.layout.Box
|
|||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.core.content.ContextCompat.startActivity
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.android.showkase.models.Showkase
|
||||
import com.bumble.appyx.core.children.whenChildAttached
|
||||
import com.bumble.appyx.core.clienthelper.interactor.Interactor
|
||||
import com.bumble.appyx.core.composable.Children
|
||||
|
|
@ -26,13 +21,19 @@ import com.bumble.appyx.core.node.ParentNode
|
|||
import com.bumble.appyx.core.node.node
|
||||
import com.bumble.appyx.navmodel.backstack.BackStack
|
||||
import com.bumble.appyx.navmodel.backstack.operation.newRoot
|
||||
import io.element.android.x.BuildConfig
|
||||
import io.element.android.x.component.ShowkaseButton
|
||||
import com.bumble.appyx.navmodel.backstack.operation.pop
|
||||
import com.bumble.appyx.navmodel.backstack.operation.push
|
||||
import io.element.android.x.architecture.createNode
|
||||
import io.element.android.x.architecture.presenterConnector
|
||||
import io.element.android.x.core.di.DaggerComponentOwner
|
||||
import io.element.android.x.core.screenshot.ImageResult
|
||||
import io.element.android.x.di.SessionComponentsOwner
|
||||
import io.element.android.x.getBrowserIntent
|
||||
import io.element.android.x.features.rageshake.bugreport.BugReportNode
|
||||
import io.element.android.x.matrix.Matrix
|
||||
import io.element.android.x.matrix.core.SessionId
|
||||
import io.element.android.x.root.RootEvents
|
||||
import io.element.android.x.root.RootPresenter
|
||||
import io.element.android.x.root.RootView
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
|
@ -64,6 +65,7 @@ class RootFlowNode(
|
|||
private val appComponentOwner: DaggerComponentOwner,
|
||||
private val matrix: Matrix,
|
||||
private val sessionComponentsOwner: SessionComponentsOwner,
|
||||
rootPresenter: RootPresenter
|
||||
) :
|
||||
ParentNode<RootFlowNode.NavTarget>(
|
||||
navModel = backstack,
|
||||
|
|
@ -73,10 +75,20 @@ class RootFlowNode(
|
|||
|
||||
DaggerComponentOwner by appComponentOwner {
|
||||
|
||||
private val presenterConnector = presenterConnector(rootPresenter)
|
||||
|
||||
init {
|
||||
Timber.v("Init")
|
||||
lifecycle.subscribe(
|
||||
onCreate = { Timber.v("OnCreate") },
|
||||
onResume = {
|
||||
Timber.v("OnResume")
|
||||
presenterConnector.emitEvent(RootEvents.StartRageshakeDetection)
|
||||
},
|
||||
onPause = {
|
||||
Timber.v("OnPause")
|
||||
presenterConnector.emitEvent(RootEvents.StopRageshakeDetection)
|
||||
},
|
||||
onDestroy = { Timber.v("OnDestroy") }
|
||||
)
|
||||
}
|
||||
|
|
@ -85,7 +97,7 @@ class RootFlowNode(
|
|||
matrix.isLoggedIn()
|
||||
.distinctUntilChanged()
|
||||
.onEach { isLoggedIn ->
|
||||
Timber.v("IsLoggedIn")
|
||||
Timber.v("isLoggedIn=$isLoggedIn")
|
||||
if (isLoggedIn) {
|
||||
val matrixClient = matrix.restoreSession()
|
||||
if (matrixClient == null) {
|
||||
|
|
@ -102,43 +114,50 @@ class RootFlowNode(
|
|||
.launchIn(lifecycleScope)
|
||||
}
|
||||
|
||||
private fun hideShowkaseButton() {
|
||||
presenterConnector.emitEvent(RootEvents.HideShowkaseButton)
|
||||
}
|
||||
|
||||
private fun onOpenBugReport() {
|
||||
presenterConnector.emitEvent(RootEvents.ResetAppHasCrashed)
|
||||
backstack.push(NavTarget.BugReport)
|
||||
}
|
||||
|
||||
private fun onCrashDetectedDismissed() {
|
||||
presenterConnector.emitEvent(RootEvents.ResetAllCrashData)
|
||||
}
|
||||
|
||||
private fun onDismissRageshake() {
|
||||
presenterConnector.emitEvent(RootEvents.DismissRageshake)
|
||||
}
|
||||
|
||||
private fun onDisableRageshake() {
|
||||
presenterConnector.emitEvent(RootEvents.DisableRageshake)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
var isShowkaseButtonVisible by remember { mutableStateOf(BuildConfig.DEBUG) }
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxSize(),
|
||||
contentAlignment = Alignment.TopCenter,
|
||||
val state by presenterConnector.stateFlow.collectAsState()
|
||||
RootView(
|
||||
state = state,
|
||||
onHideShowkaseClicked = this::hideShowkaseButton,
|
||||
onOpenBugReport = this::onOpenBugReport,
|
||||
onCrashDetectedDismissed = this::onCrashDetectedDismissed,
|
||||
onDisableRageshake = this::onDisableRageshake,
|
||||
onDismissRageshake = this::onDismissRageshake,
|
||||
onScreenshotTaken = this::onScreenshotTaken
|
||||
) {
|
||||
Children(navModel = backstack)
|
||||
val context = LocalContext.current
|
||||
ShowkaseButton(
|
||||
isVisible = isShowkaseButtonVisible,
|
||||
onCloseClicked = { isShowkaseButtonVisible = false },
|
||||
onClick = { startActivity(context, Showkase.getBrowserIntent(context), null) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
var isBugReportVisible by rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
RageshakeDetectionScreen(
|
||||
onOpenBugReport = {
|
||||
isBugReportVisible = true
|
||||
}
|
||||
)
|
||||
CrashDetectionScreen(
|
||||
onOpenBugReport = {
|
||||
isBugReportVisible = true
|
||||
}
|
||||
)
|
||||
if (isBugReportVisible) {
|
||||
// TODO Improve the navigation, when pressing back here, it closes the app.
|
||||
BugReportScreen(
|
||||
onDone = { isBugReportVisible = false }
|
||||
)
|
||||
}
|
||||
*/
|
||||
private fun onScreenshotTaken(imageResult: ImageResult) {
|
||||
presenterConnector.emitEvent(RootEvents.ProcessScreenshot(imageResult))
|
||||
}
|
||||
|
||||
private val bugReportNodeCallback = object : BugReportNode.Callback {
|
||||
override fun onBugReportSent() {
|
||||
backstack.pop()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -151,12 +170,19 @@ class RootFlowNode(
|
|||
|
||||
@Parcelize
|
||||
data class LoggedInFlow(val sessionId: SessionId) : NavTarget
|
||||
|
||||
@Parcelize
|
||||
object BugReport : NavTarget
|
||||
}
|
||||
|
||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||
return when (navTarget) {
|
||||
is NavTarget.LoggedInFlow -> {
|
||||
LoggedInFlowNode(buildContext, navTarget.sessionId)
|
||||
LoggedInFlowNode(
|
||||
buildContext = buildContext,
|
||||
sessionId = navTarget.sessionId,
|
||||
onOpenBugReport = this::onOpenBugReport
|
||||
)
|
||||
}
|
||||
NavTarget.NotLoggedInFlow -> NotLoggedInFlowNode(buildContext)
|
||||
NavTarget.SplashScreen -> node(buildContext) {
|
||||
|
|
@ -164,6 +190,7 @@ class RootFlowNode(
|
|||
CircularProgressIndicator()
|
||||
}
|
||||
}
|
||||
NavTarget.BugReport -> createNode<BugReportNode>(buildContext, plugins = listOf(bugReportNodeCallback))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
14
app/src/main/java/io/element/android/x/root/RootEvents.kt
Normal file
14
app/src/main/java/io/element/android/x/root/RootEvents.kt
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package io.element.android.x.root
|
||||
|
||||
import io.element.android.x.core.screenshot.ImageResult
|
||||
|
||||
sealed interface RootEvents {
|
||||
data class ProcessScreenshot(val imageResult: ImageResult) : RootEvents
|
||||
object HideShowkaseButton: RootEvents
|
||||
object ResetAllCrashData : RootEvents
|
||||
object ResetAppHasCrashed: RootEvents
|
||||
object DisableRageshake: RootEvents
|
||||
object DismissRageshake: RootEvents
|
||||
object StartRageshakeDetection: RootEvents
|
||||
object StopRageshakeDetection: RootEvents
|
||||
}
|
||||
62
app/src/main/java/io/element/android/x/root/RootPresenter.kt
Normal file
62
app/src/main/java/io/element/android/x/root/RootPresenter.kt
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
package io.element.android.x.root
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import io.element.android.x.architecture.Presenter
|
||||
import io.element.android.x.architecture.SharedFlowHolder
|
||||
import io.element.android.x.features.rageshake.bugreport.BugReportEvents
|
||||
import io.element.android.x.features.rageshake.bugreport.BugReportPresenter
|
||||
import io.element.android.x.features.rageshake.crash.ui.CrashDetectionEvents
|
||||
import io.element.android.x.features.rageshake.crash.ui.CrashDetectionPresenter
|
||||
import io.element.android.x.features.rageshake.detection.RageshakeDetectionEvents
|
||||
import io.element.android.x.features.rageshake.detection.RageshakeDetectionPresenter
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Inject
|
||||
|
||||
class RootPresenter @Inject constructor(
|
||||
private val bugReportPresenter: BugReportPresenter,
|
||||
private val crashDetectionPresenter: CrashDetectionPresenter,
|
||||
private val rageshakeDetectionPresenter: RageshakeDetectionPresenter,
|
||||
) : Presenter<RootState, RootEvents> {
|
||||
|
||||
private val rageshakeDetectionEventsFlow = SharedFlowHolder<RageshakeDetectionEvents>()
|
||||
private val bugReporterEventsFlow = SharedFlowHolder<BugReportEvents>()
|
||||
private val crashDetectionEventsFlow = SharedFlowHolder<CrashDetectionEvents>()
|
||||
|
||||
@Composable
|
||||
override fun present(events: Flow<RootEvents>): RootState {
|
||||
val isBugReportVisible = rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
val isShowkaseButtonVisible = rememberSaveable {
|
||||
mutableStateOf(true)
|
||||
}
|
||||
val rageshakeDetectionState = rageshakeDetectionPresenter.present(events = rageshakeDetectionEventsFlow.asSharedFlow())
|
||||
val crashDetectionState = crashDetectionPresenter.present(events = crashDetectionEventsFlow.asSharedFlow())
|
||||
val bugReportState = bugReportPresenter.present(events = bugReporterEventsFlow.asSharedFlow())
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
events.collect { event ->
|
||||
when (event) {
|
||||
RootEvents.HideShowkaseButton -> isShowkaseButtonVisible.value = false
|
||||
RootEvents.ResetAllCrashData -> crashDetectionEventsFlow.emit(CrashDetectionEvents.ResetAllCrashData)
|
||||
RootEvents.ResetAppHasCrashed -> crashDetectionEventsFlow.emit(CrashDetectionEvents.ResetAppHasCrashed)
|
||||
RootEvents.DisableRageshake -> rageshakeDetectionEventsFlow.emit(RageshakeDetectionEvents.Disable)
|
||||
RootEvents.DismissRageshake -> rageshakeDetectionEventsFlow.emit(RageshakeDetectionEvents.Dismiss)
|
||||
RootEvents.StartRageshakeDetection -> rageshakeDetectionEventsFlow.emit(RageshakeDetectionEvents.StartDetection)
|
||||
RootEvents.StopRageshakeDetection -> rageshakeDetectionEventsFlow.emit(RageshakeDetectionEvents.StopDetection)
|
||||
is RootEvents.ProcessScreenshot -> rageshakeDetectionEventsFlow.emit(RageshakeDetectionEvents.ProcessScreenshot(event.imageResult))
|
||||
}
|
||||
}
|
||||
}
|
||||
return RootState(
|
||||
isBugReportVisible = isBugReportVisible.value,
|
||||
isShowkaseButtonVisible = isShowkaseButtonVisible.value,
|
||||
rageshakeDetectionState = rageshakeDetectionState,
|
||||
crashDetectionState = crashDetectionState,
|
||||
bugReportState = bugReportState
|
||||
)
|
||||
}
|
||||
}
|
||||
15
app/src/main/java/io/element/android/x/root/RootState.kt
Normal file
15
app/src/main/java/io/element/android/x/root/RootState.kt
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
package io.element.android.x.root
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import io.element.android.x.features.rageshake.bugreport.BugReportState
|
||||
import io.element.android.x.features.rageshake.crash.ui.CrashDetectionState
|
||||
import io.element.android.x.features.rageshake.detection.RageshakeDetectionState
|
||||
|
||||
@Stable
|
||||
data class RootState(
|
||||
val isBugReportVisible: Boolean,
|
||||
val isShowkaseButtonVisible: Boolean,
|
||||
val rageshakeDetectionState: RageshakeDetectionState,
|
||||
val crashDetectionState: CrashDetectionState,
|
||||
val bugReportState: BugReportState
|
||||
)
|
||||
55
app/src/main/java/io/element/android/x/root/RootView.kt
Normal file
55
app/src/main/java/io/element/android/x/root/RootView.kt
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
package io.element.android.x.root
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.airbnb.android.showkase.models.Showkase
|
||||
import io.element.android.x.component.ShowkaseButton
|
||||
import io.element.android.x.core.screenshot.ImageResult
|
||||
import io.element.android.x.features.rageshake.crash.ui.CrashDetectionView
|
||||
import io.element.android.x.features.rageshake.detection.RageshakeDetectionView
|
||||
import io.element.android.x.getBrowserIntent
|
||||
|
||||
@Composable
|
||||
fun RootView(
|
||||
state: RootState,
|
||||
modifier: Modifier = Modifier,
|
||||
onHideShowkaseClicked: () -> Unit = { },
|
||||
onOpenBugReport: () -> Unit = {},
|
||||
onCrashDetectedDismissed: () -> Unit = {},
|
||||
onDisableRageshake: () -> Unit = {},
|
||||
onDismissRageshake: () -> Unit = {},
|
||||
onScreenshotTaken: (ImageResult) -> Unit = {},
|
||||
children: @Composable BoxScope.() -> Unit,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxSize(),
|
||||
contentAlignment = Alignment.TopCenter,
|
||||
) {
|
||||
children()
|
||||
val context = LocalContext.current
|
||||
ShowkaseButton(
|
||||
isVisible = state.isShowkaseButtonVisible,
|
||||
onCloseClicked = onHideShowkaseClicked,
|
||||
onClick = { ContextCompat.startActivity(context, Showkase.getBrowserIntent(context), null) }
|
||||
)
|
||||
RageshakeDetectionView(
|
||||
state = state.rageshakeDetectionState,
|
||||
onOpenBugReport = onOpenBugReport,
|
||||
onDisableClicked = onDisableRageshake,
|
||||
onNoClicked = onDismissRageshake,
|
||||
onScreenshotTaken = onScreenshotTaken
|
||||
)
|
||||
CrashDetectionView(
|
||||
state = state.crashDetectionState,
|
||||
onOpenBugReport = onOpenBugReport,
|
||||
onPopupDismissed = onCrashDetectedDismissed
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue