Continue migrating BugReport/Rageshake/Crash screens
This commit is contained in:
parent
6d2e5edc82
commit
56e54bb172
21 changed files with 366 additions and 80 deletions
|
|
@ -49,7 +49,8 @@ class MainActivity : NodeComponentActivity() {
|
||||||
buildContext = it,
|
buildContext = it,
|
||||||
appComponentOwner = applicationContext as DaggerComponentOwner,
|
appComponentOwner = applicationContext as DaggerComponentOwner,
|
||||||
matrix = appBindings.matrix(),
|
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 com.squareup.anvil.annotations.ContributesTo
|
||||||
import io.element.android.x.matrix.Matrix
|
import io.element.android.x.matrix.Matrix
|
||||||
import io.element.android.x.matrix.ui.MatrixUi
|
import io.element.android.x.matrix.ui.MatrixUi
|
||||||
|
import io.element.android.x.root.RootPresenter
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
||||||
@ContributesTo(AppScope::class)
|
@ContributesTo(AppScope::class)
|
||||||
interface AppBindings {
|
interface AppBindings {
|
||||||
fun coroutineScope(): CoroutineScope
|
fun coroutineScope(): CoroutineScope
|
||||||
|
fun rootPresenter(): RootPresenter
|
||||||
fun matrix(): Matrix
|
fun matrix(): Matrix
|
||||||
fun matrixUi(): MatrixUi
|
fun matrixUi(): MatrixUi
|
||||||
fun sessionComponentsOwner(): SessionComponentsOwner
|
fun sessionComponentsOwner(): SessionComponentsOwner
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import kotlinx.parcelize.Parcelize
|
||||||
class LoggedInFlowNode(
|
class LoggedInFlowNode(
|
||||||
buildContext: BuildContext,
|
buildContext: BuildContext,
|
||||||
val sessionId: SessionId,
|
val sessionId: SessionId,
|
||||||
|
private val onOpenBugReport: () -> Unit,
|
||||||
private val backstack: BackStack<NavTarget> = BackStack(
|
private val backstack: BackStack<NavTarget> = BackStack(
|
||||||
initialElement = NavTarget.RoomList,
|
initialElement = NavTarget.RoomList,
|
||||||
savedStateMap = buildContext.savedStateMap,
|
savedStateMap = buildContext.savedStateMap,
|
||||||
|
|
@ -64,7 +65,7 @@ class LoggedInFlowNode(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
NavTarget.Settings -> {
|
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.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.core.content.ContextCompat.startActivity
|
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.airbnb.android.showkase.models.Showkase
|
|
||||||
import com.bumble.appyx.core.children.whenChildAttached
|
import com.bumble.appyx.core.children.whenChildAttached
|
||||||
import com.bumble.appyx.core.clienthelper.interactor.Interactor
|
import com.bumble.appyx.core.clienthelper.interactor.Interactor
|
||||||
import com.bumble.appyx.core.composable.Children
|
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.core.node.node
|
||||||
import com.bumble.appyx.navmodel.backstack.BackStack
|
import com.bumble.appyx.navmodel.backstack.BackStack
|
||||||
import com.bumble.appyx.navmodel.backstack.operation.newRoot
|
import com.bumble.appyx.navmodel.backstack.operation.newRoot
|
||||||
import io.element.android.x.BuildConfig
|
import com.bumble.appyx.navmodel.backstack.operation.pop
|
||||||
import io.element.android.x.component.ShowkaseButton
|
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.di.DaggerComponentOwner
|
||||||
|
import io.element.android.x.core.screenshot.ImageResult
|
||||||
import io.element.android.x.di.SessionComponentsOwner
|
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.Matrix
|
||||||
import io.element.android.x.matrix.core.SessionId
|
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.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
|
@ -64,6 +65,7 @@ class RootFlowNode(
|
||||||
private val appComponentOwner: DaggerComponentOwner,
|
private val appComponentOwner: DaggerComponentOwner,
|
||||||
private val matrix: Matrix,
|
private val matrix: Matrix,
|
||||||
private val sessionComponentsOwner: SessionComponentsOwner,
|
private val sessionComponentsOwner: SessionComponentsOwner,
|
||||||
|
rootPresenter: RootPresenter
|
||||||
) :
|
) :
|
||||||
ParentNode<RootFlowNode.NavTarget>(
|
ParentNode<RootFlowNode.NavTarget>(
|
||||||
navModel = backstack,
|
navModel = backstack,
|
||||||
|
|
@ -73,10 +75,20 @@ class RootFlowNode(
|
||||||
|
|
||||||
DaggerComponentOwner by appComponentOwner {
|
DaggerComponentOwner by appComponentOwner {
|
||||||
|
|
||||||
|
private val presenterConnector = presenterConnector(rootPresenter)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Timber.v("Init")
|
Timber.v("Init")
|
||||||
lifecycle.subscribe(
|
lifecycle.subscribe(
|
||||||
onCreate = { Timber.v("OnCreate") },
|
onCreate = { Timber.v("OnCreate") },
|
||||||
|
onResume = {
|
||||||
|
Timber.v("OnResume")
|
||||||
|
presenterConnector.emitEvent(RootEvents.StartRageshakeDetection)
|
||||||
|
},
|
||||||
|
onPause = {
|
||||||
|
Timber.v("OnPause")
|
||||||
|
presenterConnector.emitEvent(RootEvents.StopRageshakeDetection)
|
||||||
|
},
|
||||||
onDestroy = { Timber.v("OnDestroy") }
|
onDestroy = { Timber.v("OnDestroy") }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -85,7 +97,7 @@ class RootFlowNode(
|
||||||
matrix.isLoggedIn()
|
matrix.isLoggedIn()
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.onEach { isLoggedIn ->
|
.onEach { isLoggedIn ->
|
||||||
Timber.v("IsLoggedIn")
|
Timber.v("isLoggedIn=$isLoggedIn")
|
||||||
if (isLoggedIn) {
|
if (isLoggedIn) {
|
||||||
val matrixClient = matrix.restoreSession()
|
val matrixClient = matrix.restoreSession()
|
||||||
if (matrixClient == null) {
|
if (matrixClient == null) {
|
||||||
|
|
@ -102,43 +114,50 @@ class RootFlowNode(
|
||||||
.launchIn(lifecycleScope)
|
.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
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
var isShowkaseButtonVisible by remember { mutableStateOf(BuildConfig.DEBUG) }
|
val state by presenterConnector.stateFlow.collectAsState()
|
||||||
Box(
|
RootView(
|
||||||
modifier = modifier
|
state = state,
|
||||||
.fillMaxSize(),
|
onHideShowkaseClicked = this::hideShowkaseButton,
|
||||||
contentAlignment = Alignment.TopCenter,
|
onOpenBugReport = this::onOpenBugReport,
|
||||||
|
onCrashDetectedDismissed = this::onCrashDetectedDismissed,
|
||||||
|
onDisableRageshake = this::onDisableRageshake,
|
||||||
|
onDismissRageshake = this::onDismissRageshake,
|
||||||
|
onScreenshotTaken = this::onScreenshotTaken
|
||||||
) {
|
) {
|
||||||
Children(navModel = backstack)
|
Children(navModel = backstack)
|
||||||
val context = LocalContext.current
|
}
|
||||||
ShowkaseButton(
|
}
|
||||||
isVisible = isShowkaseButtonVisible,
|
|
||||||
onCloseClicked = { isShowkaseButtonVisible = false },
|
|
||||||
onClick = { startActivity(context, Showkase.getBrowserIntent(context), null) }
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
private fun onScreenshotTaken(imageResult: ImageResult) {
|
||||||
var isBugReportVisible by rememberSaveable {
|
presenterConnector.emitEvent(RootEvents.ProcessScreenshot(imageResult))
|
||||||
mutableStateOf(false)
|
}
|
||||||
}
|
|
||||||
RageshakeDetectionScreen(
|
private val bugReportNodeCallback = object : BugReportNode.Callback {
|
||||||
onOpenBugReport = {
|
override fun onBugReportSent() {
|
||||||
isBugReportVisible = true
|
backstack.pop()
|
||||||
}
|
|
||||||
)
|
|
||||||
CrashDetectionScreen(
|
|
||||||
onOpenBugReport = {
|
|
||||||
isBugReportVisible = true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if (isBugReportVisible) {
|
|
||||||
// TODO Improve the navigation, when pressing back here, it closes the app.
|
|
||||||
BugReportScreen(
|
|
||||||
onDone = { isBugReportVisible = false }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -151,12 +170,19 @@ class RootFlowNode(
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class LoggedInFlow(val sessionId: SessionId) : NavTarget
|
data class LoggedInFlow(val sessionId: SessionId) : NavTarget
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
object BugReport : NavTarget
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||||
return when (navTarget) {
|
return when (navTarget) {
|
||||||
is NavTarget.LoggedInFlow -> {
|
is NavTarget.LoggedInFlow -> {
|
||||||
LoggedInFlowNode(buildContext, navTarget.sessionId)
|
LoggedInFlowNode(
|
||||||
|
buildContext = buildContext,
|
||||||
|
sessionId = navTarget.sessionId,
|
||||||
|
onOpenBugReport = this::onOpenBugReport
|
||||||
|
)
|
||||||
}
|
}
|
||||||
NavTarget.NotLoggedInFlow -> NotLoggedInFlowNode(buildContext)
|
NavTarget.NotLoggedInFlow -> NotLoggedInFlowNode(buildContext)
|
||||||
NavTarget.SplashScreen -> node(buildContext) {
|
NavTarget.SplashScreen -> node(buildContext) {
|
||||||
|
|
@ -164,6 +190,7 @@ class RootFlowNode(
|
||||||
CircularProgressIndicator()
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,7 @@ import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
class PreferencesFlowNode(
|
class PreferencesFlowNode(
|
||||||
buildContext: BuildContext,
|
buildContext: BuildContext,
|
||||||
|
private val onOpenBugReport: () -> Unit,
|
||||||
private val backstack: BackStack<NavTarget> = BackStack(
|
private val backstack: BackStack<NavTarget> = BackStack(
|
||||||
initialElement = NavTarget.Root,
|
initialElement = NavTarget.Root,
|
||||||
savedStateMap = buildContext.savedStateMap,
|
savedStateMap = buildContext.savedStateMap,
|
||||||
|
|
@ -23,6 +24,12 @@ class PreferencesFlowNode(
|
||||||
buildContext = buildContext
|
buildContext = buildContext
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
private val preferencesRootNodeCallback = object : PreferencesRootNode.Callback {
|
||||||
|
override fun onOpenBugReport() {
|
||||||
|
onOpenBugReport.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sealed interface NavTarget : Parcelable {
|
sealed interface NavTarget : Parcelable {
|
||||||
@Parcelize
|
@Parcelize
|
||||||
object Root : NavTarget
|
object Root : NavTarget
|
||||||
|
|
@ -30,7 +37,7 @@ class PreferencesFlowNode(
|
||||||
|
|
||||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||||
return when (navTarget) {
|
return when (navTarget) {
|
||||||
NavTarget.Root -> createNode<PreferencesRootNode>(buildContext)
|
NavTarget.Root -> createNode<PreferencesRootNode>(buildContext, plugins = listOf(preferencesRootNodeCallback))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import androidx.compose.ui.Modifier
|
||||||
import com.bumble.appyx.core.modality.BuildContext
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
import com.bumble.appyx.core.node.Node
|
import com.bumble.appyx.core.node.Node
|
||||||
import com.bumble.appyx.core.plugin.Plugin
|
import com.bumble.appyx.core.plugin.Plugin
|
||||||
|
import com.bumble.appyx.core.plugin.plugins
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.x.anvilannotations.ContributesNode
|
import io.element.android.x.anvilannotations.ContributesNode
|
||||||
|
|
@ -20,6 +21,10 @@ class PreferencesRootNode @AssistedInject constructor(
|
||||||
private val presenter: PreferencesRootPresenter,
|
private val presenter: PreferencesRootPresenter,
|
||||||
) : Node(buildContext, plugins = plugins) {
|
) : Node(buildContext, plugins = plugins) {
|
||||||
|
|
||||||
|
public interface Callback : Plugin {
|
||||||
|
fun onOpenBugReport()
|
||||||
|
}
|
||||||
|
|
||||||
private val presenterConnector = presenterConnector(presenter)
|
private val presenterConnector = presenterConnector(presenter)
|
||||||
|
|
||||||
private fun onLogoutClicked() {
|
private fun onLogoutClicked() {
|
||||||
|
|
@ -34,6 +39,10 @@ class PreferencesRootNode @AssistedInject constructor(
|
||||||
presenterConnector.emitEvent(PreferencesRootEvents.SetRageshakeSensitivity(sensitivity))
|
presenterConnector.emitEvent(PreferencesRootEvents.SetRageshakeSensitivity(sensitivity))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onOpenBugReport() {
|
||||||
|
plugins<Callback>().forEach { it.onOpenBugReport() }
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
val state by presenterConnector.stateFlow.collectAsState()
|
val state by presenterConnector.stateFlow.collectAsState()
|
||||||
|
|
@ -42,7 +51,8 @@ class PreferencesRootNode @AssistedInject constructor(
|
||||||
onLogoutClicked = this::onLogoutClicked,
|
onLogoutClicked = this::onLogoutClicked,
|
||||||
onBackPressed = this::navigateUp,
|
onBackPressed = this::navigateUp,
|
||||||
onRageshakeEnabledChanged = this::onRageshakeEnabledChanged,
|
onRageshakeEnabledChanged = this::onRageshakeEnabledChanged,
|
||||||
onRageshakeSensitivityChanged = this::onRageshakeSensitivityChanged
|
onRageshakeSensitivityChanged = this::onRageshakeSensitivityChanged,
|
||||||
|
onOpenRageShake = this::onOpenBugReport
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
package io.element.android.x.features.rageshake.bugreport
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
|
import com.bumble.appyx.core.node.Node
|
||||||
|
import com.bumble.appyx.core.plugin.Plugin
|
||||||
|
import com.bumble.appyx.core.plugin.plugins
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import io.element.android.x.anvilannotations.ContributesNode
|
||||||
|
import io.element.android.x.architecture.presenterConnector
|
||||||
|
import io.element.android.x.di.AppScope
|
||||||
|
|
||||||
|
@ContributesNode(AppScope::class)
|
||||||
|
class BugReportNode @AssistedInject constructor(
|
||||||
|
@Assisted buildContext: BuildContext,
|
||||||
|
@Assisted plugins: List<Plugin>,
|
||||||
|
presenter: BugReportPresenter,
|
||||||
|
) : Node(buildContext, plugins = plugins) {
|
||||||
|
|
||||||
|
private val presenterConnector = presenterConnector(presenter)
|
||||||
|
|
||||||
|
interface Callback : Plugin {
|
||||||
|
fun onBugReportSent()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun View(modifier: Modifier) {
|
||||||
|
val state by presenterConnector.stateFlow.collectAsState()
|
||||||
|
BugReportView(
|
||||||
|
state = state,
|
||||||
|
modifier = modifier,
|
||||||
|
onDescriptionChanged = this::onDescriptionChanged,
|
||||||
|
onSetSendLog = this::onSetSendLog,
|
||||||
|
onSetSendCrashLog = this::onSetSendCrashLog,
|
||||||
|
onSetCanContact = this::onSetCanContact,
|
||||||
|
onSetSendScreenshot = this::onSetSendScreenshot,
|
||||||
|
onSubmit = this::onSubmit,
|
||||||
|
onDone = this::onDone
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onDone() {
|
||||||
|
presenterConnector.emitEvent(BugReportEvents.ResetAll)
|
||||||
|
plugins<Callback>().forEach { it.onBugReportSent() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onSubmit() {
|
||||||
|
presenterConnector.emitEvent(BugReportEvents.SendBugReport)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onSetSendLog(sendLog: Boolean) {
|
||||||
|
presenterConnector.emitEvent(BugReportEvents.SetSendLog(sendLog))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onSetSendCrashLog(sendCrashLog: Boolean) {
|
||||||
|
presenterConnector.emitEvent(BugReportEvents.SetSendCrashLog(sendCrashLog))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onSetSendScreenshot(sendScreenshot: Boolean) {
|
||||||
|
presenterConnector.emitEvent(BugReportEvents.SetSendScreenshot(sendScreenshot))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onSetCanContact(canContact: Boolean) {
|
||||||
|
presenterConnector.emitEvent(BugReportEvents.SetCanContact(canContact))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onDescriptionChanged(description: String) {
|
||||||
|
presenterConnector.emitEvent(BugReportEvents.SetDescription(description))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -4,10 +4,12 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.core.net.toUri
|
||||||
import io.element.android.x.architecture.Async
|
import io.element.android.x.architecture.Async
|
||||||
import io.element.android.x.architecture.Presenter
|
import io.element.android.x.architecture.Presenter
|
||||||
import io.element.android.x.features.rageshake.crash.CrashDataStore
|
import io.element.android.x.features.rageshake.crash.CrashDataStore
|
||||||
|
|
@ -31,6 +33,7 @@ class BugReportPresenter @Inject constructor(
|
||||||
private val sendingProgress: MutableState<Float>,
|
private val sendingProgress: MutableState<Float>,
|
||||||
private val sendingAction: MutableState<Async<Unit>>
|
private val sendingAction: MutableState<Async<Unit>>
|
||||||
) : BugReporter.IMXBugReportListener {
|
) : BugReporter.IMXBugReportListener {
|
||||||
|
|
||||||
override fun onUploadCancelled() {
|
override fun onUploadCancelled() {
|
||||||
sendingProgress.value = 0f
|
sendingProgress.value = 0f
|
||||||
sendingAction.value = Async.Uninitialized
|
sendingAction.value = Async.Uninitialized
|
||||||
|
|
@ -54,6 +57,11 @@ class BugReportPresenter @Inject constructor(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(events: Flow<BugReportEvents>): BugReportState {
|
override fun present(events: Flow<BugReportEvents>): BugReportState {
|
||||||
|
val screenshotUri = rememberSaveable {
|
||||||
|
mutableStateOf(
|
||||||
|
screenshotHolder.getFile()?.toUri()?.toString()
|
||||||
|
)
|
||||||
|
}
|
||||||
val crashInfo: String by crashDataStore
|
val crashInfo: String by crashDataStore
|
||||||
.crashInfo()
|
.crashInfo()
|
||||||
.collectAsState(initial = "")
|
.collectAsState(initial = "")
|
||||||
|
|
@ -64,15 +72,21 @@ class BugReportPresenter @Inject constructor(
|
||||||
val sendingAction: MutableState<Async<Unit>> = remember {
|
val sendingAction: MutableState<Async<Unit>> = remember {
|
||||||
mutableStateOf(Async.Uninitialized)
|
mutableStateOf(Async.Uninitialized)
|
||||||
}
|
}
|
||||||
val formState: MutableState<BugReportFormState> = rememberSaveable {
|
val formState: MutableState<BugReportFormState> = remember {
|
||||||
mutableStateOf(BugReportFormState.Default)
|
mutableStateOf(BugReportFormState.Default)
|
||||||
}
|
}
|
||||||
val uploadListener = BugReporterUploadListener(sendingProgress, sendingAction)
|
val uploadListener = BugReporterUploadListener(sendingProgress, sendingAction)
|
||||||
val state = BugReportState(
|
val state by remember {
|
||||||
hasCrashLogs = crashInfo.isNotEmpty(),
|
derivedStateOf {
|
||||||
sendingProgress = sendingProgress.value,
|
BugReportState(
|
||||||
sending = sendingAction.value
|
hasCrashLogs = crashInfo.isNotEmpty(),
|
||||||
)
|
sendingProgress = sendingProgress.value,
|
||||||
|
sending = sendingAction.value,
|
||||||
|
formState = formState.value,
|
||||||
|
screenshotUri = screenshotUri.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
events.collect { event ->
|
events.collect { event ->
|
||||||
when (event) {
|
when (event) {
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,7 @@ data class BugReportFormState(
|
||||||
val sendCrashLogs: Boolean,
|
val sendCrashLogs: Boolean,
|
||||||
val canContact: Boolean,
|
val canContact: Boolean,
|
||||||
val sendScreenshot: Boolean
|
val sendScreenshot: Boolean
|
||||||
|
) : Parcelable {
|
||||||
): Parcelable {
|
|
||||||
companion object {
|
companion object {
|
||||||
val Default = BugReportFormState(
|
val Default = BugReportFormState(
|
||||||
description = "",
|
description = "",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package io.element.android.x.features.rageshake.crash.ui
|
package io.element.android.x.features.rageshake.crash.ui
|
||||||
|
|
||||||
sealed interface CrashDetectionEvents {
|
sealed interface CrashDetectionEvents {
|
||||||
object ResetAll : CrashDetectionEvents
|
object ResetAllCrashData : CrashDetectionEvents
|
||||||
object ResetAppHasCrashed : CrashDetectionEvents
|
object ResetAppHasCrashed : CrashDetectionEvents
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ class CrashDetectionPresenter @Inject constructor(private val crashDataStore: Cr
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
events.collect { event ->
|
events.collect { event ->
|
||||||
when (event) {
|
when (event) {
|
||||||
CrashDetectionEvents.ResetAll -> resetAll()
|
CrashDetectionEvents.ResetAllCrashData -> resetAll()
|
||||||
CrashDetectionEvents.ResetAppHasCrashed -> resetAppHasCrashed()
|
CrashDetectionEvents.ResetAppHasCrashed -> resetAppHasCrashed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -32,7 +32,7 @@ class CrashDetectionPresenter @Inject constructor(private val crashDataStore: Cr
|
||||||
crashDataStore.resetAppHasCrashed()
|
crashDataStore.resetAppHasCrashed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun CoroutineScope.resetAll() = launch {
|
private fun CoroutineScope.resetAll() = launch {
|
||||||
crashDataStore.reset()
|
crashDataStore.reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import io.element.android.x.core.screenshot.ImageResult
|
||||||
import io.element.android.x.features.rageshake.preferences.RageshakePreferencesEvents
|
import io.element.android.x.features.rageshake.preferences.RageshakePreferencesEvents
|
||||||
import io.element.android.x.features.rageshake.preferences.RageshakePreferencesPresenter
|
import io.element.android.x.features.rageshake.preferences.RageshakePreferencesPresenter
|
||||||
import io.element.android.x.features.rageshake.rageshake.RageShake
|
import io.element.android.x.features.rageshake.rageshake.RageShake
|
||||||
import io.element.android.x.features.rageshake.rageshake.RageshakeDataStore
|
|
||||||
import io.element.android.x.features.rageshake.screenshot.ScreenshotHolder
|
import io.element.android.x.features.rageshake.screenshot.ScreenshotHolder
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
@ -22,7 +21,6 @@ import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class RageshakeDetectionPresenter @Inject constructor(
|
class RageshakeDetectionPresenter @Inject constructor(
|
||||||
private val rageshakeDataStore: RageshakeDataStore,
|
|
||||||
private val screenshotHolder: ScreenshotHolder,
|
private val screenshotHolder: ScreenshotHolder,
|
||||||
private val rageShake: RageShake,
|
private val rageShake: RageShake,
|
||||||
private val preferencesPresenter: RageshakePreferencesPresenter,
|
private val preferencesPresenter: RageshakePreferencesPresenter,
|
||||||
|
|
@ -42,12 +40,14 @@ class RageshakeDetectionPresenter @Inject constructor(
|
||||||
val showDialog = rememberSaveable {
|
val showDialog = rememberSaveable {
|
||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
}
|
}
|
||||||
val state = RageshakeDetectionState(
|
val state = remember(preferencesState, isStarted.value, takeScreenshot.value, showDialog.value) {
|
||||||
isStarted = isStarted.value,
|
RageshakeDetectionState(
|
||||||
takeScreenshot = takeScreenshot.value,
|
isStarted = isStarted.value,
|
||||||
showDialog = showDialog.value,
|
takeScreenshot = takeScreenshot.value,
|
||||||
preferenceState = preferencesState
|
showDialog = showDialog.value,
|
||||||
)
|
preferenceState = preferencesState
|
||||||
|
)
|
||||||
|
}
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
events.collect { event ->
|
events.collect { event ->
|
||||||
when (event) {
|
when (event) {
|
||||||
|
|
@ -62,22 +62,18 @@ class RageshakeDetectionPresenter @Inject constructor(
|
||||||
LaunchedEffect(preferencesState.sensitivity) {
|
LaunchedEffect(preferencesState.sensitivity) {
|
||||||
rageShake.setSensitivity(preferencesState.sensitivity)
|
rageShake.setSensitivity(preferencesState.sensitivity)
|
||||||
}
|
}
|
||||||
val shouldStart = remember {
|
val shouldStart = preferencesState.isEnabled &&
|
||||||
derivedStateOf {
|
|
||||||
preferencesState.isEnabled &&
|
|
||||||
preferencesState.isSupported &&
|
preferencesState.isSupported &&
|
||||||
isStarted.value &&
|
isStarted.value &&
|
||||||
!takeScreenshot.value &&
|
!takeScreenshot.value &&
|
||||||
!showDialog.value
|
!showDialog.value
|
||||||
}
|
|
||||||
}
|
|
||||||
LaunchedEffect(shouldStart) {
|
LaunchedEffect(shouldStart) {
|
||||||
handleRageShake(shouldStart.value, state, takeScreenshot)
|
handleRageShake(shouldStart, state, takeScreenshot)
|
||||||
}
|
}
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun handleRageShake(start: Boolean, state: RageshakeDetectionState, takeScreenshot: MutableState<Boolean>) {
|
private fun handleRageShake(start: Boolean, state: RageshakeDetectionState, takeScreenshot: MutableState<Boolean>) {
|
||||||
if (start) {
|
if (start) {
|
||||||
rageShake.start(state.preferenceState.sensitivity)
|
rageShake.start(state.preferenceState.sensitivity)
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,10 @@
|
||||||
|
|
||||||
package io.element.android.x.features.rageshake.detection
|
package io.element.android.x.features.rageshake.detection
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
import io.element.android.x.features.rageshake.preferences.RageshakePreferencesState
|
import io.element.android.x.features.rageshake.preferences.RageshakePreferencesState
|
||||||
|
|
||||||
|
@Stable
|
||||||
data class RageshakeDetectionState(
|
data class RageshakeDetectionState(
|
||||||
val takeScreenshot: Boolean = false,
|
val takeScreenshot: Boolean = false,
|
||||||
val showDialog: Boolean = false,
|
val showDialog: Boolean = false,
|
||||||
|
|
|
||||||
|
|
@ -34,9 +34,9 @@ import io.element.android.x.element.resources.R as ElementR
|
||||||
fun RageshakeDetectionView(
|
fun RageshakeDetectionView(
|
||||||
state: RageshakeDetectionState,
|
state: RageshakeDetectionState,
|
||||||
onOpenBugReport: () -> Unit = { },
|
onOpenBugReport: () -> Unit = { },
|
||||||
onScreenshotTaken: (ImageResult) -> Unit,
|
onScreenshotTaken: (ImageResult) -> Unit = {},
|
||||||
onDisableClicked: () -> Unit,
|
onDisableClicked: () -> Unit = {},
|
||||||
onNoClicked: () -> Unit
|
onNoClicked: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
LogCompositions(tag = "Rageshake", msg = "RageshakeDetectionScreen")
|
LogCompositions(tag = "Rageshake", msg = "RageshakeDetectionScreen")
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
@ -63,8 +63,10 @@ private fun TakeScreenshot(
|
||||||
onScreenshotTaken: (ImageResult) -> Unit = {}
|
onScreenshotTaken: (ImageResult) -> Unit = {}
|
||||||
) {
|
) {
|
||||||
val view = LocalView.current
|
val view = LocalView.current
|
||||||
view.screenshot {
|
LaunchedEffect(Unit) {
|
||||||
onScreenshotTaken(it)
|
view.screenshot {
|
||||||
|
onScreenshotTaken(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,7 @@ import javax.inject.Inject
|
||||||
class RageshakePreferencesPresenter @Inject constructor(
|
class RageshakePreferencesPresenter @Inject constructor(
|
||||||
private val rageshake: RageShake,
|
private val rageshake: RageShake,
|
||||||
private val rageshakeDataStore: RageshakeDataStore,
|
private val rageshakeDataStore: RageshakeDataStore,
|
||||||
|
) : Presenter<RageshakePreferencesState, RageshakePreferencesEvents> {
|
||||||
) : Presenter<RageshakePreferencesState, RageshakePreferencesEvents> {
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(events: Flow<RageshakePreferencesEvents>): RageshakePreferencesState {
|
override fun present(events: Flow<RageshakePreferencesEvents>): RageshakePreferencesState {
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,13 @@ package io.element.android.x.features.rageshake.screenshot
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import io.element.android.x.core.bitmap.writeBitmap
|
import io.element.android.x.core.bitmap.writeBitmap
|
||||||
|
import io.element.android.x.di.AppScope
|
||||||
import io.element.android.x.di.ApplicationContext
|
import io.element.android.x.di.ApplicationContext
|
||||||
|
import io.element.android.x.di.SingleIn
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@SingleIn(AppScope::class)
|
||||||
class ScreenshotHolder @Inject constructor(
|
class ScreenshotHolder @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
@ApplicationContext private val context: Context,
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package io.element.android.x.architecture
|
package io.element.android.x.architecture
|
||||||
|
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
|
||||||
|
@Stable
|
||||||
sealed interface Async<out T> {
|
sealed interface Async<out T> {
|
||||||
object Uninitialized : Async<Nothing>
|
object Uninitialized : Async<Nothing>
|
||||||
data class Loading<out T>(val prevState: T? = null) : Async<T>
|
data class Loading<out T>(val prevState: T? = null) : Async<T>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue