Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

View Folders and logs #2283

Merged
merged 8 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions appnav/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ dependencies {
implementation(libs.coil)

implementation(projects.features.ftue.api)
implementation(projects.features.viewfolder.api)

implementation(projects.services.apperror.impl)
implementation(projects.services.appnavstate.api)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import io.element.android.features.login.api.oidc.OidcAction
import io.element.android.features.login.api.oidc.OidcActionFlow
import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint
import io.element.android.features.signedout.api.SignedOutEntryPoint
import io.element.android.features.viewfolder.api.ViewFolderEntryPoint
import io.element.android.libraries.architecture.BackstackView
import io.element.android.libraries.architecture.BaseFlowNode
import io.element.android.libraries.architecture.createNode
Expand All @@ -70,6 +71,7 @@ class RootFlowNode @AssistedInject constructor(
private val matrixClientsHolder: MatrixClientsHolder,
private val presenter: RootPresenter,
private val bugReportEntryPoint: BugReportEntryPoint,
private val viewFolderEntryPoint: ViewFolderEntryPoint,
private val signedOutEntryPoint: SignedOutEntryPoint,
private val intentResolver: IntentResolver,
private val oidcActionFlow: OidcActionFlow,
Expand Down Expand Up @@ -194,6 +196,11 @@ class RootFlowNode @AssistedInject constructor(

@Parcelize
data object BugReport : NavTarget

@Parcelize
data class ViewLogs(
val rootPath: String,
) : NavTarget
}

override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
Expand Down Expand Up @@ -233,12 +240,31 @@ class RootFlowNode @AssistedInject constructor(
override fun onBugReportSent() {
backstack.pop()
}

override fun onViewLogs(basePath: String) {
backstack.push(NavTarget.ViewLogs(rootPath = basePath))
}
}
bugReportEntryPoint
.nodeBuilder(this, buildContext)
.callback(callback)
.build()
}
is NavTarget.ViewLogs -> {
val callback = object : ViewFolderEntryPoint.Callback {
override fun onDone() {
backstack.pop()
}
}
val params = ViewFolderEntryPoint.Params(
rootPath = navTarget.rootPath,
)
viewFolderEntryPoint
.nodeBuilder(this, buildContext)
.params(params)
.callback(callback)
.build()
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@ interface BugReportEntryPoint : FeatureEntryPoint {

interface Callback : Plugin {
fun onBugReportSent()
fun onViewLogs(basePath: String)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,9 @@ interface BugReporter {
* Set the current tracing filter.
*/
fun setCurrentTracingFilter(tracingFilter: String)

/**
* Save the logcat.
*/
fun saveLogCat()
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint
import io.element.android.features.rageshake.api.reporter.BugReporter
import io.element.android.libraries.androidutils.system.toast
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.ui.strings.CommonStrings
Expand All @@ -37,7 +38,12 @@ class BugReportNode @AssistedInject constructor(
@Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
private val presenter: BugReportPresenter,
private val bugReporter: BugReporter,
) : Node(buildContext, plugins = plugins) {
private fun onViewLogs(basePath: String) {
plugins<BugReportEntryPoint.Callback>().forEach { it.onViewLogs(basePath) }
}

@Composable
override fun View(modifier: Modifier) {
val state = presenter.present()
Expand All @@ -50,6 +56,11 @@ class BugReportNode @AssistedInject constructor(
activity?.toast(CommonStrings.common_report_submitted)
onDone()
},
onViewLogs = {
// Force a logcat dump
bugReporter.saveLogCat()
onViewLogs(bugReporter.logDirectory().absolutePath)
}
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import io.element.android.libraries.designsystem.components.form.textFieldState
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
import io.element.android.libraries.designsystem.components.preferences.PreferenceRow
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.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.preview.debugPlaceholderBackground
Expand All @@ -55,6 +56,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
@Composable
fun BugReportView(
state: BugReportState,
onViewLogs: () -> Unit,
onDone: () -> Unit,
onBackPressed: () -> Unit,
modifier: Modifier = Modifier,
Expand Down Expand Up @@ -97,6 +99,11 @@ fun BugReportView(
)
}
Spacer(modifier = Modifier.height(16.dp))
PreferenceText(
title = stringResource(id = R.string.screen_bug_report_view_logs),
enabled = isFormEnabled,
onClick = onViewLogs,
)
PreferenceSwitch(
isChecked = state.formState.sendLogs,
onCheckedChange = { eventSink(BugReportEvents.SetSendLog(it)) },
Expand Down Expand Up @@ -169,5 +176,6 @@ internal fun BugReportViewPreview(@PreviewParameter(BugReportStateProvider::clas
state = state,
onDone = {},
onBackPressed = {},
onViewLogs = {},
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ class DefaultBugReporter @Inject constructor(
private val logcatCommandDebug = arrayOf("logcat", "-d", "-v", "threadtime", "*:*")
private var currentTracingFilter: String? = null

private val logCatErrFile = File(logDirectory().absolutePath, LOG_CAT_FILENAME)

override suspend fun sendBugReport(
withDevicesLogs: Boolean,
withCrashLogs: Boolean,
Expand Down Expand Up @@ -130,8 +132,8 @@ class DefaultBugReporter @Inject constructor(
}

if (!isCancelled && (withCrashLogs || withDevicesLogs)) {
val gzippedLogcat = saveLogCat()

saveLogCat()
val gzippedLogcat = compressFile(logCatErrFile)
if (null != gzippedLogcat) {
if (gzippedFiles.size == 0) {
gzippedFiles.add(gzippedLogcat)
Expand Down Expand Up @@ -321,7 +323,9 @@ class DefaultBugReporter @Inject constructor(
}

override fun logDirectory(): File {
return File(context.cacheDir, LOG_DIRECTORY_NAME)
return File(context.cacheDir, LOG_DIRECTORY_NAME).apply {
mkdirs()
}
}

override fun cleanLogDirectoryIfNeeded() {
Expand Down Expand Up @@ -381,30 +385,19 @@ class DefaultBugReporter @Inject constructor(
*
* @return the file if the operation succeeds
*/
private fun saveLogCat(): File? {
val logCatErrFile = File(context.cacheDir.absolutePath, LOG_CAT_FILENAME)

override fun saveLogCat() {
if (logCatErrFile.exists()) {
logCatErrFile.safeDelete()
}

try {
logCatErrFile.writer().use {
getLogCatError(it)
}

return compressFile(logCatErrFile)
} catch (error: OutOfMemoryError) {
Timber.e(error, "## saveLogCat() : fail to write logcat OOM")
} catch (e: Exception) {
Timber.e(e, "## saveLogCat() : fail to write logcat")
} finally {
if (logCatErrFile.exists()) {
logCatErrFile.safeDelete()
}
}

return null
}

/**
Expand Down
1 change: 1 addition & 0 deletions features/rageshake/impl/src/main/res/values/localazy.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@
<string name="screen_bug_report_include_logs">"Allow logs"</string>
<string name="screen_bug_report_include_screenshot">"Send screenshot"</string>
<string name="screen_bug_report_logs_description">"Logs will be included with your message to make sure that everything is working properly. To send your message without logs, turn off this setting."</string>
<string name="screen_bug_report_view_logs">"View logs"</string>
<string name="screen_bug_report_rash_logs_alert_title">"%1$s crashed the last time it was used. Would you like to share a crash report with us?"</string>
</resources>
26 changes: 26 additions & 0 deletions features/viewfolder/api/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright (c) 2024 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.
*/
plugins {
id("io.element.android-library")
}

android {
namespace = "io.element.android.features.viewfolder.api"
}

dependencies {
implementation(projects.libraries.architecture)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright (c) 2024 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.viewfolder.api

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 ViewFolderEntryPoint : FeatureEntryPoint {
data class Params(
val rootPath: String,
)

fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder

interface NodeBuilder {
fun params(params: Params): NodeBuilder
fun callback(callback: Callback): NodeBuilder
fun build(): Node
}

interface Callback : Plugin {
fun onDone()
}
}
50 changes: 50 additions & 0 deletions features/viewfolder/impl/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright (c) 2024 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.
*/

plugins {
id("io.element.android-compose-library")
alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize")
}

android {
namespace = "io.element.android.features.viewfolder.impl"
}

anvil {
generateDaggerFactories.set(true)
}

dependencies {
implementation(projects.anvilannotations)
anvil(projects.anvilcodegen)
implementation(projects.libraries.androidutils)
implementation(projects.libraries.architecture)
implementation(projects.libraries.core)
implementation(projects.libraries.designsystem)
implementation(projects.libraries.uiStrings)
api(projects.features.viewfolder.api)
ksp(libs.showkase.processor)

testImplementation(libs.test.junit)
testImplementation(libs.test.robolectric)
testImplementation(libs.coroutines.test)
testImplementation(libs.molecule.runtime)
testImplementation(libs.test.truth)
testImplementation(libs.test.turbine)
testImplementation(projects.tests.testutils)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright (c) 2024 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.viewfolder.impl

import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.features.viewfolder.api.ViewFolderEntryPoint
import io.element.android.features.viewfolder.impl.root.ViewFolderRootNode
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.di.AppScope
import javax.inject.Inject

@ContributesBinding(AppScope::class)
class DefaultViewFolderEntryPoint @Inject constructor() : ViewFolderEntryPoint {
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): ViewFolderEntryPoint.NodeBuilder {
val plugins = ArrayList<Plugin>()

return object : ViewFolderEntryPoint.NodeBuilder {
override fun params(params: ViewFolderEntryPoint.Params): ViewFolderEntryPoint.NodeBuilder {
plugins += ViewFolderRootNode.Inputs(params.rootPath)
return this
}

override fun callback(callback: ViewFolderEntryPoint.Callback): ViewFolderEntryPoint.NodeBuilder {
plugins += callback
return this
}

override fun build(): Node {
return parentNode.createNode<ViewFolderRootNode>(buildContext, plugins)
}
}
}
}
Loading
Loading