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

feat: about page #74

Merged
merged 6 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.ConnectivityManager
import android.net.Uri
import android.os.BatteryManager
import android.os.Build
import androidx.datastore.core.DataStore
Expand Down Expand Up @@ -35,9 +36,42 @@ class AndroidApplication : Application() {
AndroidNetworkTypeFinder(getSystemService(ConnectivityManager::class.java)),
buildDataStore = ::buildDataStore,
isBatteryCharging = ::checkBatteryCharging,
launchUrl = ::launchUrl,
)
}

private fun launchUrl(
url: String,
extras: Map<String, String>?,
) {
val uri = Uri.parse(url)
val intent = Intent(
when (uri.scheme) {
"mailto" -> Intent.ACTION_SENDTO
else -> Intent.ACTION_VIEW
},
uri,
).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}

if (uri.scheme == "mailto") {
val chooserTitle = extras?.get("chooserTitle") ?: "Send email"
val mailerIntent = Intent.createChooser(intent, chooserTitle).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
extras?.forEach { (key, value) ->
when (key) {
"subject" -> putExtra(Intent.EXTRA_SUBJECT, value)
"body" -> putExtra(Intent.EXTRA_TEXT, value)
}
}
}
startActivity(mailerIntent)
} else {
startActivity(intent)
}
}

private val platformInfo by lazy {
object : PlatformInfo {
override val version = "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

logo.xml

logo_probe.xml
logo_probe.xml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
<string name="Settings_Proxy_Label">OONI backend proxy</string>
<string name="Settings_Advanced_Label">Advanced</string>
<string name="Settings_SendEmail_Label">Send email to support</string>
<string name="Settings_SendEmail_Message">Please describe the problem you are experiencing:</string>

<string name="Settings_Advanced_DebugLogs">Debug logs</string>
<string name="Settings_Advanced_RecentLogs">See recent logs</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<resources>
<string name="shareEmailTo" translatable="false">mailto:[email protected]</string>
<string name="shareSubject" translatable="false">[bug-report] OONI Probe %1$s</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import org.ooni.probe.ui.dashboard.DashboardViewModel
import org.ooni.probe.ui.result.ResultViewModel
import org.ooni.probe.ui.results.ResultsViewModel
import org.ooni.probe.ui.settings.SettingsViewModel
import org.ooni.probe.ui.settings.about.AboutViewModel
import org.ooni.probe.ui.settings.category.SettingsCategoryViewModel

class Dependencies(
Expand All @@ -60,6 +61,7 @@ class Dependencies(
private val networkTypeFinder: NetworkTypeFinder,
private val buildDataStore: () -> DataStore<Preferences>,
private val isBatteryCharging: () -> Boolean,
private val launchUrl: (String, Map<String, String>?) -> Unit,
) {
// Common

Expand Down Expand Up @@ -190,7 +192,13 @@ class Dependencies(

fun resultsViewModel(goToResult: (ResultModel.Id) -> Unit) = ResultsViewModel(goToResult, getResults::invoke)

fun settingsViewModel(goToSettingsForCategory: (PreferenceCategoryKey) -> Unit) = SettingsViewModel(goToSettingsForCategory)
fun settingsViewModel(
goToSettingsForCategory: (PreferenceCategoryKey) -> Unit,
sendSupportEmail: () -> Unit,
) = SettingsViewModel(
goToSettingsForCategory,
sendSupportEmail,
)

fun settingsCategoryViewModel(
goToSettingsForCategory: (PreferenceCategoryKey) -> Unit,
Expand All @@ -215,6 +223,13 @@ class Dependencies(
markResultAsViewed = resultRepository::markAsViewed,
)

fun aboutViewModel(onBack: () -> Unit) =
AboutViewModel(onBack) {
launchUrl(it, emptyMap())
}

fun sendSupportEmail(): (String, Map<String, String>) -> Unit = launchUrl

companion object {
@VisibleForTesting
fun buildJson() =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import ooniprobe.composeapp.generated.resources.Res
import ooniprobe.composeapp.generated.resources.Settings_SendEmail_Label
import ooniprobe.composeapp.generated.resources.Settings_SendEmail_Message
import ooniprobe.composeapp.generated.resources.shareEmailTo
import ooniprobe.composeapp.generated.resources.shareSubject
import org.jetbrains.compose.resources.stringResource
import org.ooni.probe.data.models.MeasurementModel
import org.ooni.probe.data.models.PreferenceCategoryKey
import org.ooni.probe.data.models.ResultModel
Expand All @@ -20,6 +26,7 @@ import org.ooni.probe.ui.measurement.MeasurementScreen
import org.ooni.probe.ui.result.ResultScreen
import org.ooni.probe.ui.results.ResultsScreen
import org.ooni.probe.ui.settings.SettingsScreen
import org.ooni.probe.ui.settings.about.AboutScreen
import org.ooni.probe.ui.settings.category.SettingsCategoryScreen

@Composable
Expand Down Expand Up @@ -50,12 +57,27 @@ fun Navigation(
}

composable(route = Screen.Settings.route) {
val sendSupportEmail = dependencies.sendSupportEmail()
val supportEmail = stringResource(Res.string.shareEmailTo)
val subject = stringResource(Res.string.shareSubject, dependencies.platformInfo.version)
val chooserTitle = stringResource(Res.string.Settings_SendEmail_Label)
val platformInfo = dependencies.platformInfo
val body = stringResource(Res.string.Settings_SendEmail_Message) + "\n\n\n" +
"PLATFORM: ${platformInfo.platform}\n" +
"MODEL: ${platformInfo.model}\n" +
"OS Version: ${platformInfo.osVersion}"
val viewModel =
viewModel {
dependencies.settingsViewModel(
goToSettingsForCategory = {
navController.navigate(Screen.SettingsCategory(it).route)
},
sendSupportEmail = {
sendSupportEmail.invoke(
supportEmail,
mapOf("subject" to subject, "body" to body, "chooserTitle" to chooserTitle),
)
},
)
}
SettingsScreen(viewModel::onEvent)
Expand Down Expand Up @@ -99,8 +121,14 @@ fun Navigation(
) { entry ->
val category = entry.arguments?.getString("category") ?: return@composable
when (category) {
PreferenceCategoryKey.SEND_EMAIL.name -> {
// TODO: Implement based on platform
PreferenceCategoryKey.ABOUT_OONI.name -> {
val viewModel =
aanorbel marked this conversation as resolved.
Show resolved Hide resolved
viewModel {
dependencies.aboutViewModel(
onBack = { navController.navigateUp() },
)
}
AboutScreen(onEvent = viewModel::onEvent)
}

else -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,21 @@ import org.ooni.probe.data.models.PreferenceCategoryKey

open class SettingsViewModel(
goToSettingsForCategory: (PreferenceCategoryKey) -> Unit,
sendSupportEmail: () -> Unit,
) : ViewModel() {
private val events = MutableSharedFlow<Event>(extraBufferCapacity = 1)

init {
events.filterIsInstance<Event.SettingsCategoryClick>()
.onEach { goToSettingsForCategory(it.category) }.launchIn(viewModelScope)
.onEach {
when (it.category) {
PreferenceCategoryKey.SEND_EMAIL -> {
sendSupportEmail()
} else -> {
goToSettingsForCategory(it.category)
}
}
}.launchIn(viewModelScope)
}

fun onEvent(event: Event) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.ooni.probe.ui.settings.about

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LargeTopAppBar
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import ooniprobe.composeapp.generated.resources.Res
import ooniprobe.composeapp.generated.resources.Settings_About_Content_Paragraph
import ooniprobe.composeapp.generated.resources.Settings_About_Label
import ooniprobe.composeapp.generated.resources.back
import org.jetbrains.compose.resources.stringResource
import org.ooni.probe.ui.shared.MarkdownViewer

@Composable
fun AboutScreen(onEvent: (AboutViewModel.Event) -> Unit) {
LazyColumn {
item {
LargeTopAppBar(
title = {
Text(stringResource(Res.string.Settings_About_Label))
},
navigationIcon = {
IconButton(onClick = { onEvent(AboutViewModel.Event.BackClicked) }) {
Icon(
Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = stringResource(Res.string.back),
)
}
},
)
}
item {
Column(
verticalArrangement = Arrangement.Center,
) {
MarkdownViewer(
markdown = stringResource(Res.string.Settings_About_Content_Paragraph),
) { url -> onEvent(AboutViewModel.Event.LaunchUrlClicked(url)) }

Spacer(modifier = Modifier.height(16.dp))
InfoLinks(
launchUrl = { url -> onEvent(AboutViewModel.Event.LaunchUrlClicked(url)) },
)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.ooni.probe.ui.settings.about

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach

class AboutViewModel(
onBack: () -> Unit,
launchUrl: (String) -> Unit,
) : ViewModel() {
private val events = MutableSharedFlow<Event>(extraBufferCapacity = 1)

init {
events.filterIsInstance<Event.BackClicked>().onEach { onBack() }.launchIn(viewModelScope)
events.filterIsInstance<Event.LaunchUrlClicked>().onEach { url -> launchUrl(url.url) }
.launchIn(viewModelScope)
}

fun onEvent(event: Event) {
events.tryEmit(event)
}

sealed interface Event {
data object BackClicked : Event

data class LaunchUrlClicked(val url: String) : Event
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.ooni.probe.ui.shared

import androidx.compose.runtime.Composable
import com.multiplatform.webview.request.RequestInterceptor
import com.multiplatform.webview.request.WebRequest
import com.multiplatform.webview.request.WebRequestInterceptResult
import com.multiplatform.webview.web.WebView
import com.multiplatform.webview.web.WebViewNavigator
import com.multiplatform.webview.web.rememberWebViewNavigator
import com.multiplatform.webview.web.rememberWebViewStateWithHTMLData
import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor
import org.intellij.markdown.html.HtmlGenerator
import org.intellij.markdown.parser.MarkdownParser

@Composable
fun MarkdownViewer(
markdown: String,
onUrlClicked: (String) -> Unit,
) {
val flavour = CommonMarkFlavourDescriptor()
val html = HtmlGenerator(
markdown,
MarkdownParser(flavour).buildMarkdownTreeFromString(markdown),
flavour,
).generateHtml()
val webViewState = rememberWebViewStateWithHTMLData(
data = html,
)
val navigator =
rememberWebViewNavigator(
requestInterceptor =
object : RequestInterceptor {
override fun onInterceptUrlRequest(
request: WebRequest,
navigator: WebViewNavigator,
): WebRequestInterceptResult {
onUrlClicked(request.url)
return WebRequestInterceptResult.Reject
}
},
)
WebView(state = webViewState, navigator = navigator)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
<string name="app_name">News Media Scan</string>

<string name="Settings_About_Label">About News Media Scan</string>
<string name="Settings_About_Content_Paragraph">This app is the product of close cooperation between Deutsche Welle (DW) and OONI.\n\n_About DW:_ Unbiased information for free minds – that is the DW brand promise. As an independent media company, Germany’s international news broadcaster informs people around the world. With programming in 32 languages, DW connects people across the globe via TV, radio, Internet and on social media. \n\nFurther information:[ About DW](https://corporate.dw.com/en/about-dw/s-30688) \n\n_About OONI:_ Founded in 2012, the Open Observatory of Network Interference (OONI) is a non-profit free software project that aims to empower decentralized efforts in documenting internet censorship around the world. Thanks to their global community, [more than a billion network measurements](https://explorer.ooni.org/) have been published from more than 200 countries, shedding light on cases of internet censorship worldwide. \n\nBe part of the internet freedom movement by providing data from the networks you’re using.</string>
</resources>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.ooni.probe.ui.settings.about

import androidx.compose.runtime.Composable

@Composable
fun InfoLinks(launchUrl: (String) -> Unit) {
}
Loading