[Architecture] split all feature modules to api/impl

This commit is contained in:
ganfra 2023-03-08 16:13:45 +01:00
parent 84bfb14bd9
commit bc9f3b69cc
214 changed files with 626 additions and 1090 deletions

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.rageshake.api.bugreport
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import io.element.android.libraries.architecture.FeatureEntryPoint
interface BugReportEntryPoint : FeatureEntryPoint {
fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder
interface NodeBuilder {
fun callback(callback: Callback): NodeBuilder
fun build(): Node
}
interface Callback : Plugin {
fun onBugReportSent()
}
}

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.rageshake.api.crash
import kotlinx.coroutines.flow.Flow
interface CrashDataStore {
fun setCrashData(crashData: String)
suspend fun resetAppHasCrashed()
fun appHasCrashed(): Flow<Boolean>
fun crashInfo(): Flow<String>
suspend fun reset()
}

View file

@ -0,0 +1,22 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.rageshake.api.crash
sealed interface CrashDetectionEvents {
object ResetAllCrashData : CrashDetectionEvents
object ResetAppHasCrashed : CrashDetectionEvents
}

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.rageshake.api.crash
import io.element.android.libraries.architecture.Presenter
interface CrashDetectionPresenter : Presenter<CrashDetectionState>

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.rageshake.api.crash
import androidx.compose.runtime.Immutable
@Immutable
data class CrashDetectionState(
val crashDetected: Boolean,
val eventSink: (CrashDetectionEvents) -> Unit
)

View file

@ -0,0 +1,22 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.rageshake.api.crash
fun aCrashDetectionState() = CrashDetectionState(
crashDetected = false,
eventSink = {}
)

View file

@ -0,0 +1,81 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.rageshake.api.crash
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.utils.LogCompositions
import io.element.android.libraries.ui.strings.R as StringR
@Composable
fun CrashDetectionView(
state: CrashDetectionState,
onOpenBugReport: () -> Unit = { },
) {
LogCompositions(
tag = "Crash",
msg = "CrashDetectionScreen"
)
fun onPopupDismissed() {
state.eventSink(CrashDetectionEvents.ResetAllCrashData)
}
if (state.crashDetected) {
CrashDetectionContent(
onYesClicked = onOpenBugReport,
onNoClicked = ::onPopupDismissed,
onDismiss = ::onPopupDismissed,
)
}
}
@Composable
fun CrashDetectionContent(
onNoClicked: () -> Unit = { },
onYesClicked: () -> Unit = { },
onDismiss: () -> Unit = { },
) {
ConfirmationDialog(
title = stringResource(id = StringR.string.send_bug_report),
content = stringResource(id = StringR.string.send_bug_report_app_crashed),
submitText = stringResource(id = StringR.string.yes),
cancelText = stringResource(id = StringR.string.no),
onCancelClicked = onNoClicked,
onSubmitClicked = onYesClicked,
onDismiss = onDismiss,
)
}
@Preview
@Composable
internal fun CrashDetectionViewLightPreview() = ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
internal fun CrashDetectionViewDarkPreview() = ElementPreviewDark { ContentToPreview() }
@Composable
private fun ContentToPreview() {
CrashDetectionView(
state = aCrashDetectionState().copy(crashDetected = true)
)
}

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.rageshake.api.detection
import io.element.android.features.rageshake.api.screenshot.ImageResult
sealed interface RageshakeDetectionEvents {
object Dismiss : RageshakeDetectionEvents
object Disable : RageshakeDetectionEvents
object StartDetection : RageshakeDetectionEvents
object StopDetection : RageshakeDetectionEvents
data class ProcessScreenshot(val imageResult: ImageResult) : RageshakeDetectionEvents
}

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.rageshake.api.detection
import io.element.android.libraries.architecture.Presenter
interface RageshakeDetectionPresenter : Presenter<RageshakeDetectionState>

View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.rageshake.api.detection
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import io.element.android.features.rageshake.api.preferences.RageshakePreferencesState
@Immutable
data class RageshakeDetectionState(
val takeScreenshot: Boolean,
val showDialog: Boolean,
val isStarted: Boolean,
val preferenceState: RageshakePreferencesState,
val eventSink: (RageshakeDetectionEvents) -> Unit
)

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.rageshake.api.detection
import io.element.android.features.rageshake.api.preferences.aRageshakePreferencesState
fun aRageshakeDetectionState() = RageshakeDetectionState(
takeScreenshot = false,
showDialog = false,
isStarted = false,
preferenceState = aRageshakePreferencesState(),
eventSink = {}
)

View file

@ -0,0 +1,113 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.rageshake.api.detection
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.Lifecycle
import io.element.android.features.rageshake.api.screenshot.ImageResult
import io.element.android.features.rageshake.api.screenshot.screenshot
import io.element.android.libraries.androidutils.hardware.vibrate
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.utils.LogCompositions
import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
import io.element.android.libraries.ui.strings.R as StringR
@Composable
fun RageshakeDetectionView(
state: RageshakeDetectionState,
onOpenBugReport: () -> Unit = { },
) {
LogCompositions(
tag = "Rageshake",
msg = "RageshakeDetectionScreen"
)
val eventSink = state.eventSink
val context = LocalContext.current
OnLifecycleEvent { _, event ->
when (event) {
Lifecycle.Event.ON_RESUME -> eventSink(RageshakeDetectionEvents.StartDetection)
Lifecycle.Event.ON_PAUSE -> eventSink(RageshakeDetectionEvents.StopDetection)
else -> Unit
}
}
when {
state.takeScreenshot -> TakeScreenshot(
onScreenshotTaken = { eventSink(RageshakeDetectionEvents.ProcessScreenshot(it)) }
)
state.showDialog -> {
LaunchedEffect(Unit) {
context.vibrate()
}
RageshakeDialogContent(
onNoClicked = { eventSink(RageshakeDetectionEvents.Dismiss) },
onDisableClicked = { eventSink(RageshakeDetectionEvents.Disable) },
onYesClicked = onOpenBugReport
)
}
}
}
@Composable
private fun TakeScreenshot(
onScreenshotTaken: (ImageResult) -> Unit = {}
) {
val view = LocalView.current
LaunchedEffect(Unit) {
view.screenshot {
onScreenshotTaken(it)
}
}
}
@Composable
fun RageshakeDialogContent(
onNoClicked: () -> Unit = { },
onDisableClicked: () -> Unit = { },
onYesClicked: () -> Unit = { },
) {
ConfirmationDialog(
title = stringResource(id = StringR.string.send_bug_report),
content = stringResource(id = StringR.string.send_bug_report_alert_message),
thirdButtonText = stringResource(id = StringR.string.action_disable),
submitText = stringResource(id = StringR.string.yes),
cancelText = stringResource(id = StringR.string.no),
onCancelClicked = onNoClicked,
onThirdButtonClicked = onDisableClicked,
onSubmitClicked = onYesClicked,
onDismiss = onNoClicked,
)
}
@Preview
@Composable
internal fun RageshakeDialogContentLightPreview() = ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
internal fun RageshakeDialogContentDarkPreview() = ElementPreviewDark { ContentToPreview() }
@Composable
private fun ContentToPreview() {
RageshakeDialogContent()
}

View file

@ -0,0 +1,22 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.rageshake.api.preferences
sealed interface RageshakePreferencesEvents {
data class SetSensitivity(val sensitivity: Float) : RageshakePreferencesEvents
data class SetIsEnabled(val isEnabled: Boolean) : RageshakePreferencesEvents
}

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.rageshake.api.preferences
import io.element.android.libraries.architecture.Presenter
interface RageshakePreferencesPresenter : Presenter<RageshakePreferencesState>

View file

@ -0,0 +1,24 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.rageshake.api.preferences
data class RageshakePreferencesState(
val isEnabled: Boolean,
val isSupported: Boolean,
val sensitivity: Float,
val eventSink: (RageshakePreferencesEvents) -> Unit,
)

View file

@ -0,0 +1,34 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.rageshake.api.preferences
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
open class RageshakePreferencesStateProvider : PreviewParameterProvider<RageshakePreferencesState> {
override val values: Sequence<RageshakePreferencesState>
get() = sequenceOf(
aRageshakePreferencesState().copy(isEnabled = true, isSupported = true, sensitivity = 0.5f),
aRageshakePreferencesState().copy(isEnabled = true, isSupported = false, sensitivity = 0.5f),
)
}
fun aRageshakePreferencesState() = RageshakePreferencesState(
isEnabled = false,
isSupported = true,
sensitivity = 0.3f,
eventSink = {}
)

View file

@ -0,0 +1,92 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.rageshake.api.preferences
import androidx.compose.foundation.layout.Column
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.BugReport
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
import io.element.android.libraries.designsystem.components.preferences.PreferenceSlide
import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch
import io.element.android.libraries.designsystem.components.preferences.PreferenceText
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.ui.strings.R as StringR
@Composable
fun RageshakePreferencesView(
state: RageshakePreferencesState,
modifier: Modifier = Modifier,
onOpenRageshake: () -> Unit = {},
) {
fun onSensitivityChanged(sensitivity: Float) {
state.eventSink(RageshakePreferencesEvents.SetSensitivity(sensitivity = sensitivity))
}
fun onEnabledChanged(isEnabled: Boolean) {
state.eventSink(RageshakePreferencesEvents.SetIsEnabled(isEnabled = isEnabled))
}
Column(modifier = modifier) {
PreferenceCategory(title = stringResource(id = StringR.string.send_bug_report)) {
PreferenceText(
title = stringResource(id = StringR.string.send_bug_report),
icon = Icons.Default.BugReport,
onClick = onOpenRageshake
)
}
PreferenceCategory(title = stringResource(id = StringR.string.settings_rageshake)) {
if (state.isSupported) {
PreferenceSwitch(
title = stringResource(id = StringR.string.send_bug_report_rage_shake),
isChecked = state.isEnabled,
onCheckedChange = ::onEnabledChanged
)
PreferenceSlide(
title = stringResource(id = StringR.string.settings_rageshake_detection_threshold),
// summary = stringResource(id = StringR.string.settings_rageshake_detection_threshold_summary),
value = state.sensitivity,
enabled = state.isEnabled,
steps = 3 /* 5 possible values - steps are in ]0, 1[ */,
onValueChange = ::onSensitivityChanged
)
} else {
PreferenceText(title = "Rageshaking is not supported by your device")
}
}
}
}
@Preview
@Composable
fun RageshakePreferencesViewLightPreview(@PreviewParameter(RageshakePreferencesStateProvider::class) state: RageshakePreferencesState) =
ElementPreviewLight { ContentToPreview(state) }
@Preview
@Composable
fun RageshakePreferencesViewDarkPreview(@PreviewParameter(RageshakePreferencesStateProvider::class) state: RageshakePreferencesState) =
ElementPreviewDark { ContentToPreview(state) }
@Composable
private fun ContentToPreview(state: RageshakePreferencesState) {
RageshakePreferencesView(state)
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.rageshake.api.rageshake
interface RageShake {
/**
* Check if the feature is available on this device.
*/
fun isAvailable(): Boolean
fun start(sensitivity: Float)
fun stop()
/**
* sensitivity will be {0, O.25, 0.5, 0.75, 1} and converted to
* [ShakeDetector.SENSITIVITY_LIGHT (=11), ShakeDetector.SENSITIVITY_HARD (=15)].
*/
fun setSensitivity(sensitivity: Float)
fun setInterceptor(interceptor: (() -> Unit)?)
}

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.rageshake.api.rageshake
import kotlinx.coroutines.flow.Flow
interface RageshakeDataStore {
fun isEnabled(): Flow<Boolean>
suspend fun setIsEnabled(isEnabled: Boolean)
fun sensitivity(): Flow<Float>
suspend fun setSensitivity(sensitivity: Float)
suspend fun reset()
}

View file

@ -0,0 +1,52 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.rageshake.api.reporter
import io.element.android.features.rageshake.api.reporter.BugReporterListener
import io.element.android.features.rageshake.api.reporter.ReportType
import kotlinx.coroutines.CoroutineScope
interface BugReporter {
/**
* Send a bug report.
*
* @param coroutineScope The coroutine scope
* @param reportType The report type (bug, suggestion, feedback)
* @param withDevicesLogs true to include the device log
* @param withCrashLogs true to include the crash logs
* @param withKeyRequestHistory true to include the crash logs
* @param withScreenshot true to include the screenshot
* @param theBugDescription the bug description
* @param serverVersion version of the server
* @param canContact true if the user opt in to be contacted directly
* @param customFields fields which will be sent with the report
* @param listener the listener
*/
fun sendBugReport(
coroutineScope: CoroutineScope,
reportType: ReportType,
withDevicesLogs: Boolean,
withCrashLogs: Boolean,
withKeyRequestHistory: Boolean,
withScreenshot: Boolean,
theBugDescription: String,
serverVersion: String,
canContact: Boolean = false,
customFields: Map<String, String>? = null,
listener: BugReporterListener?
)
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.rageshake.api.reporter
/**
* Bug report upload listener.
*/
interface BugReporterListener {
/**
* The bug report has been cancelled.
*/
fun onUploadCancelled()
/**
* The bug report upload failed.
*
* @param reason the failure reason
*/
fun onUploadFailed(reason: String?)
/**
* The upload progress (in percent).
*
* @param progress the upload progress
*/
fun onProgress(progress: Int)
/**
* The bug report upload succeeded.
*/
fun onUploadSucceed(reportUrl: String?)
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.rageshake.api.reporter
enum class ReportType {
BUG_REPORT,
SUGGESTION,
SPACE_BETA_FEEDBACK,
THREADS_BETA_FEEDBACK,
AUTO_UISI,
AUTO_UISI_SENDER,
}

View file

@ -0,0 +1,73 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.rageshake.api.screenshot
import android.app.Activity
import android.graphics.Bitmap
import android.graphics.Canvas
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.view.PixelCopy
import android.view.View
fun View.screenshot(bitmapCallback: (ImageResult) -> Unit) {
try {
val handler = Handler(Looper.getMainLooper())
val bitmap = Bitmap.createBitmap(
width,
height,
Bitmap.Config.ARGB_8888,
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
PixelCopy.request(
(this.context as Activity).window,
clipBounds,
bitmap,
{
when (it) {
PixelCopy.SUCCESS -> {
bitmapCallback.invoke(ImageResult.Success(bitmap))
}
else -> {
bitmapCallback.invoke(ImageResult.Error(Exception(it.toString())))
}
}
},
handler
)
} else {
handler.post {
val canvas = Canvas(bitmap)
.apply {
translate(-clipBounds.left.toFloat(), -clipBounds.top.toFloat())
}
this.draw(canvas)
canvas.setBitmap(null)
bitmapCallback.invoke(ImageResult.Success(bitmap))
}
}
} catch (e: Exception) {
bitmapCallback.invoke(ImageResult.Error(e))
}
}
sealed interface ImageResult {
data class Error(val exception: Exception) : ImageResult
data class Success(val data: Bitmap) : ImageResult
}

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.rageshake.api.screenshot
import android.graphics.Bitmap
interface ScreenshotHolder {
fun writeBitmap(data: Bitmap)
fun getFileUri(): String?
fun reset()
}