Skip to content

Commit

Permalink
Add link to send logcat messages via email (#45)
Browse files Browse the repository at this point in the history
* Add link to send logcat messages via email

* Clean up

* Error handling

* Make file provider depending on applicationId
  • Loading branch information
ruixhuang authored Mar 30, 2024
1 parent 32e3968 commit bd5ce3e
Show file tree
Hide file tree
Showing 11 changed files with 331 additions and 17 deletions.
13 changes: 13 additions & 0 deletions v4/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="com.google.android.gms.permission.AD_ID" tools:node="remove"/>

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

<!-- <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>-->

<application
Expand Down Expand Up @@ -165,6 +168,16 @@
android:name="com.google.mlkit.vision.DEPENDENCIES"
android:value="qr_code, data_matrix" />

<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>

</application>

<queries>
Expand Down
14 changes: 7 additions & 7 deletions v4/app/src/main/assets/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,21 @@
},
{
"title" : {
"text" : "APP.V4.SYSTEM_STATUS"
"text" : "APP.V4.DIRECTION_COLOR_PREFERENCE"
},
"link" : {
"text" : "settings/status"
"text" : "settings/direction_color_preference"
},
"field":{
"field":"direction_color_preference"
}
},
{
"title" : {
"text" : "APP.V4.DIRECTION_COLOR_PREFERENCE"
"text" : "APP.V4.SYSTEM_STATUS"
},
"link" : {
"text" : "settings/direction_color_preference"
},
"field":{
"field":"direction_color_preference"
"text" : "settings/status"
}
}
]
Expand Down
26 changes: 17 additions & 9 deletions v4/app/src/main/assets/settings_debug.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@
"field":"v4_theme"
}
},
{
"title" : {
"text" : "APP.V4.DIRECTION_COLOR_PREFERENCE"
},
"link" : {
"text" : "settings/direction_color_preference"
},
"field":{
"field":"direction_color_preference"
}
},
{
"title" : {
"text" : "APP.V4.TRADING_NETWORK"
Expand All @@ -44,29 +55,26 @@
},
{
"title" : {
"text" : "Feature Flag Overrides"
"text" : "APP.ISSUE_REPORT.SETTINGS_TITLE"
},
"link" : {
"text" : "features"
"text" : "settings/report_issue"
}
},
{
"title" : {
"text" : "Debug Settings"
"text" : "Feature Flag Overrides"
},
"link" : {
"text" : "settings/debug"
"text" : "features"
}
},
{
"title" : {
"text" : "APP.V4.DIRECTION_COLOR_PREFERENCE"
"text" : "Debug Settings"
},
"link" : {
"text" : "settings/direction_color_preference"
},
"field":{
"field":"direction_color_preference"
"text" : "settings/debug"
}
}
]
Expand Down
4 changes: 4 additions & 0 deletions v4/app/src/main/res/xml/file_paths.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-files-path name="documents" path="." />
</paths>
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ object ProfileRoutes {
const val help = "help"
const val rewards = "rewards"
const val debug_enable = "action/debug/enable"
const val report_issue = "settings/report_issue"
}

object NewsAlertsRoutes {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import exchange.dydx.trading.feature.profile.help.DydxHelpView
import exchange.dydx.trading.feature.profile.history.DydxHistoryView
import exchange.dydx.trading.feature.profile.keyexport.DydxKeyExportView
import exchange.dydx.trading.feature.profile.language.DydxLanguageView
import exchange.dydx.trading.feature.profile.reportissue.DydxReportIssueView
import exchange.dydx.trading.feature.profile.rewards.DydxRewardsView
import exchange.dydx.trading.feature.profile.settings.DydxSettingsView
import exchange.dydx.trading.feature.profile.systemstatus.DydxSystemStatusView
Expand Down Expand Up @@ -161,4 +162,12 @@ fun NavGraphBuilder.profileGraph(
) { navBackStackEntry ->
DydxDebugEnableView.Content(Modifier)
}

dydxComposable(
router = appRouter,
route = ProfileRoutes.report_issue,
deepLinks = appRouter.deeplinks(ProfileRoutes.report_issue),
) { navBackStackEntry ->
DydxReportIssueView.Content(Modifier)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package exchange.dydx.trading.feature.profile.reportissue

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel
import exchange.dydx.abacus.protocols.LocalizerProtocol
import exchange.dydx.platformui.designSystem.theme.ThemeFont
import exchange.dydx.platformui.designSystem.theme.dydxDefault
import exchange.dydx.platformui.designSystem.theme.themeFont
import exchange.dydx.trading.common.component.DydxComponent
import exchange.dydx.trading.common.compose.collectAsStateWithLifecycle
import exchange.dydx.trading.common.theme.DydxThemedPreviewSurface
import exchange.dydx.trading.common.theme.MockLocalizer

@Preview
@Composable
fun Preview_DydxReportIssueView() {
DydxThemedPreviewSurface {
DydxReportIssueView.Content(Modifier, DydxReportIssueView.ViewState.preview)
}
}

object DydxReportIssueView : DydxComponent {
data class ViewState(
val localizer: LocalizerProtocol,
val text: String?,
) {
companion object {
val preview = ViewState(
localizer = MockLocalizer(),
text = "1.0M",
)
}
}

@Composable
override fun Content(modifier: Modifier) {
val viewModel: DydxReportIssueViewModel = hiltViewModel()

val state = viewModel.state.collectAsStateWithLifecycle(initialValue = null).value
Content(modifier, state)
}

@Composable
fun Content(modifier: Modifier, state: ViewState?) {
if (state == null) {
return
}
Column(
modifier = modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
style = TextStyle.dydxDefault.themeFont(fontSize = ThemeFont.FontSize.extra),
text = state?.text ?: "",
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package exchange.dydx.trading.feature.profile.reportissue

import android.content.Context
import android.net.Uri
import android.os.Environment
import androidx.core.content.FileProvider
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import exchange.dydx.abacus.protocols.LocalizerProtocol
import exchange.dydx.platformui.components.PlatformInfo
import exchange.dydx.trading.common.DydxViewModel
import exchange.dydx.trading.common.navigation.DydxRouter
import exchange.dydx.utilities.utils.EmailUtils
import exchange.dydx.utilities.utils.FileUtils
import exchange.dydx.utilities.utils.LogCatReader
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
import kotlinx.coroutines.withContext
import java.io.File
import javax.inject.Inject

@HiltViewModel
class DydxReportIssueViewModel @Inject constructor(
private val localizer: LocalizerProtocol,
private val router: DydxRouter,
@ApplicationContext private val context: Context,
val platformInfo: PlatformInfo,
) : ViewModel(), DydxViewModel {

private val textFlow = MutableStateFlow("")

val state: Flow<DydxReportIssueView.ViewState?> = textFlow.map { text ->
createViewState(text)
}

init {
textFlow.value = localizer.localize("APP.ISSUE_REPORT.LOADING_TITLE")

viewModelScope.launch {
var logUri: Uri? = null
withContext(Dispatchers.IO) {
// add a delay to show the loading text
kotlinx.coroutines.delay(500)
logUri = createLog()
}
if (logUri != null) {
textFlow.value = localizer.localize("APP.ISSUE_REPORT.LOADING_COMPLETED_TITLE")
EmailUtils.sendEmailWithAttachment(
context = context,
fileUri = logUri,
email = "",
subject = localizer.localize("APP.ISSUE_REPORT.EMAIL_SUBJECT"),
body = localizer.localize("APP.ISSUE_REPORT.EMAIL_BODY"),
mimeType = "application/x-zip",
chooserTitle = localizer.localize("APP.ISSUE_REPORT.CHOOSER_TITLE"),
)
} else {
val error = localizer.localize("APP.ISSUE_REPORT.LOADING_ERROR_TITLE")
textFlow.value = error
platformInfo.show(message = error)
}

router.navigateBack()
}
}

private fun createViewState(text: String): DydxReportIssueView.ViewState {
return DydxReportIssueView.ViewState(
localizer = localizer,
text = text,
)
}

private fun createLog(): Uri? {
val file =
File(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), "dydx_app.log")
if (!LogCatReader.saveLogCatToFile(file)) {
return null
}
val zipFile = createZipFile(context, file)
file.delete()
return if (zipFile != null) {
FileProvider.getUriForFile(context, context.packageName, zipFile)
} else {
null
}
}

private fun createZipFile(context: Context, file: File): File? {
val fileName = file.name
val zipFileName = "$fileName.zip"
val zipFilePath =
File(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), zipFileName)
return if (FileUtils.compressFile(context, file.absolutePath, zipFilePath.absolutePath)) {
zipFilePath
} else {
null
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package exchange.dydx.utilities.utils

import android.content.Context
import android.content.Intent
import android.net.Uri

object EmailUtils {
fun sendEmailWithAttachment(
context: Context,
fileUri: Uri?,
email: String,
subject: String?,
body: String?,
mimeType: String?,
chooserTitle: String?
) {
val emailIntent = Intent(Intent.ACTION_SEND)
emailIntent.setType(mimeType)
emailIntent.putExtra(Intent.EXTRA_EMAIL, arrayOf(email))
emailIntent.putExtra(Intent.EXTRA_SUBJECT, subject)
emailIntent.putExtra(Intent.EXTRA_TEXT, body)
emailIntent.putExtra(Intent.EXTRA_STREAM, fileUri)
val chooser = Intent.createChooser(emailIntent, chooserTitle)
if (chooser.resolveActivity(context.packageManager) != null) {
chooser.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(chooser)
}
}
}
Loading

0 comments on commit bd5ce3e

Please sign in to comment.