Skip to content

Commit

Permalink
feat: add option to open URLs with custom tabs (#407)
Browse files Browse the repository at this point in the history
  • Loading branch information
svenjacobs authored May 30, 2024
1 parent d9480f8 commit 43636a8
Show file tree
Hide file tree
Showing 14 changed files with 147 additions and 28 deletions.
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ dependencies {
implementation(libs.androidx.lifecycle.viewmodel.ktx)
implementation(libs.androidx.datastore.preferences)
implementation(libs.kotlinx.coroutines.android)
implementation(libs.androidx.browser)
implementation(libs.jakewharton.timber)

debugImplementation(libs.facebook.stetho)
Expand Down
8 changes: 7 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Léon - The URL Cleaner
~ Copyright (C) 2023 Sven Jacobs
~ Copyright (C) 2024 Sven Jacobs
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -73,4 +73,10 @@
</provider>
</application>

<queries>
<intent>
<action android:name="android.support.customtabs.action.CustomTabsService" />
</intent>
</queries>

</manifest>
37 changes: 37 additions & 0 deletions app/src/main/kotlin/com/svenjacobs/app/leon/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,27 @@

package com.svenjacobs.app.leon

import android.content.ComponentName
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.browser.customtabs.CustomTabsClient
import androidx.browser.customtabs.CustomTabsServiceConnection
import androidx.compose.runtime.mutableStateOf
import androidx.core.view.WindowCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.svenjacobs.app.leon.inject.AppContainer.AppDataStoreManager
import com.svenjacobs.app.leon.ui.MainRouter
import com.svenjacobs.app.leon.ui.theme.AppTheme
import kotlinx.coroutines.launch

class MainActivity : ComponentActivity() {

private val sourceText = mutableStateOf<String?>(null)
private var customTabsInitialized = false

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand All @@ -45,6 +54,16 @@ class MainActivity : ComponentActivity() {
)
}
}

lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
AppDataStoreManager.customTabsEnabled.collect { customTabsEnabled ->
if (customTabsEnabled) {
setupCustomTabsService()
}
}
}
}
}

override fun onNewIntent(intent: Intent) {
Expand Down Expand Up @@ -72,6 +91,24 @@ class MainActivity : ComponentActivity() {
}
}

private fun setupCustomTabsService() {
if (customTabsInitialized) return

val connection = object : CustomTabsServiceConnection() {
override fun onCustomTabsServiceConnected(name: ComponentName, client: CustomTabsClient) {
client.warmup(0)
}

override fun onServiceDisconnected(name: ComponentName?) {
}
}

val packageName = CustomTabsClient.getPackageName(this, null)
CustomTabsClient.bindCustomTabsService(this, packageName, connection)

customTabsInitialized = true
}

private companion object {
private const val MIME_TYPE_TEXT_PLAIN = "text/plain"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,22 @@ class AppDataStoreManager(private val context: Context = AppContext) {
preferences[KEY_EXTRACT_URL] ?: false
}

suspend fun setCustomTabsEnabled(enabled: Boolean) {
context.dataStore.edit {
it[KEY_CUSTOM_TABS] = enabled
}
}

val customTabsEnabled: Flow<Boolean> =
context.dataStore.data.map { preferences ->
preferences[KEY_CUSTOM_TABS] ?: false
}

private companion object {
private val KEY_VERSION_CODE = intPreferencesKey("version_code")
private val KEY_ACTION_AFTER_CLEAN = stringPreferencesKey("action_after_clean")
private val KEY_URL_DECODE = booleanPreferencesKey("url_decode")
private val KEY_EXTRACT_URL = booleanPreferencesKey("extract_url")
private val KEY_CUSTOM_TABS = booleanPreferencesKey("custom_tabs")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import android.content.ContextWrapper
import android.content.Intent
import android.net.Uri
import android.view.Window
import androidx.browser.customtabs.CustomTabsIntent
import androidx.compose.foundation.Image
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
Expand Down Expand Up @@ -143,6 +144,24 @@ fun MainScreen(
)
}

fun openInCustomTabs(result: Result.Success) {
result.urls.firstOrNull()?.let { url ->
val intent = CustomTabsIntent.Builder()
.setColorScheme(CustomTabsIntent.COLOR_SCHEME_SYSTEM)
.build()

intent.launchUrl(context, Uri.parse(url))
}
}

fun openUrl(result: Result.Success) {
if (uiState.isCustomTabsEnabled) {
openInCustomTabs(result)
} else {
openInDefaultApp(result)
}
}

fun copyToClipboard(result: Result.Success) {
clipboard.setText(AnnotatedString(result.cleanedText))
coroutineScope.launch {
Expand Down Expand Up @@ -172,7 +191,7 @@ fun MainScreen(
(uiState.result as? Result.Success)?.let { result ->
when (uiState.actionAfterClean) {
ActionAfterClean.OpenShareMenu -> openShareMenu(result)
ActionAfterClean.OpenUrl -> openInDefaultApp(result)
ActionAfterClean.OpenUrl -> openUrl(result)
ActionAfterClean.CopyToClipboard -> copyToClipboard(result)
ActionAfterClean.DoNothing -> {}
}
Expand All @@ -192,7 +211,7 @@ fun MainScreen(
},
onShareClick = ::openShareMenu,
onCopyToClipboardClick = ::copyToClipboard,
onOpenClick = ::openInDefaultApp,
onOpenClick = ::openUrl,
onResetClick = viewModel::onResetClick,
onUrlDecodeCheckedChange = viewModel::onUrlDecodeCheckedChange,
onExtractUrlCheckedChange = viewModel::onExtractUrlCheckedChange,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class MainScreenViewModel(
val isLoading: Boolean = true,
val isUrlDecodeEnabled: Boolean = false,
val isExtractUrlEnabled: Boolean = false,
val isCustomTabsEnabled: Boolean = false,
val result: Result = Result.Empty,
val actionAfterClean: ActionAfterClean = ActionAfterClean.DoNothing,
) {
Expand All @@ -65,8 +66,9 @@ class MainScreenViewModel(
text,
appDataStoreManager.urlDecodeEnabled,
appDataStoreManager.extractUrlEnabled,
appDataStoreManager.customTabsEnabled,
appDataStoreManager.actionAfterClean,
) { text, urlDecodeEnabled, extractUrlEnabled, actionAfterClean ->
) { text, urlDecodeEnabled, extractUrlEnabled, isCustomTabsEnabled, actionAfterClean ->
val result = text?.let {
clean(
text = text,
Expand All @@ -79,6 +81,7 @@ class MainScreenViewModel(
isLoading = text == null,
isUrlDecodeEnabled = urlDecodeEnabled,
isExtractUrlEnabled = extractUrlEnabled,
isCustomTabsEnabled = isCustomTabsEnabled,
result = result,
actionAfterClean = actionAfterClean ?: ActionAfterClean.DoNothing,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,12 @@ fun SettingsScreen(
modifier = modifier,
isLoading = uiState.isLoading,
browserEnabled = uiState.browserEnabled,
customTabsEnabled = uiState.customTabsEnabled,
actionAfterClean = uiState.actionAfterClean,
onSanitizersClick = onNavigateToSettingsSanitizers,
onLicensesClick = onNavigateToSettingsLicenses,
onBrowserSwitchCheckedChange = viewModel::onBrowserSwitchCheckedChange,
onCustomTabsSwitchCheckedChange = viewModel::onCustomTabsSwitchCheckedChange,
onActionAfterCleanClick = viewModel::onActionAfterCleanClick,
)
}
Expand All @@ -77,10 +79,12 @@ fun SettingsScreen(
private fun Content(
isLoading: Boolean,
browserEnabled: Boolean,
customTabsEnabled: Boolean,
actionAfterClean: ActionAfterClean,
onSanitizersClick: () -> Unit,
onLicensesClick: () -> Unit,
onBrowserSwitchCheckedChange: (Boolean) -> Unit,
onCustomTabsSwitchCheckedChange: (Boolean) -> Unit,
onActionAfterCleanClick: (ActionAfterClean) -> Unit,
modifier: Modifier = Modifier,
) {
Expand Down Expand Up @@ -111,24 +115,19 @@ private fun Content(
Text(stringResource(R.string.licenses))
}

Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
modifier = Modifier
.padding(end = 8.dp)
.weight(1f),
text = stringResource(R.string.register_as_browser),
)

Switch(
checked = browserEnabled,
onCheckedChange = onBrowserSwitchCheckedChange,
)
}
SwitchRow(
modifier = Modifier.padding(top = 16.dp),
text = stringResource(R.string.register_as_browser),
checked = browserEnabled,
onCheckedChange = onBrowserSwitchCheckedChange,
)

SwitchRow(
modifier = Modifier.padding(top = 16.dp),
text = stringResource(R.string.open_in_custom_tabs),
checked = customTabsEnabled,
onCheckedChange = onCustomTabsSwitchCheckedChange,
)

Column(
modifier = Modifier.padding(top = 8.dp),
Expand Down Expand Up @@ -210,6 +209,31 @@ private fun Content(
}
}

@Composable
private fun SwitchRow(
text: String,
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
modifier = Modifier
.padding(end = 8.dp)
.weight(1f),
text = text,
)

Switch(
checked = checked,
onCheckedChange = onCheckedChange,
)
}
}

@Composable
private fun ActionAfterClean.text(): String = when (this) {
ActionAfterClean.DoNothing -> stringResource(R.string.do_nothing)
Expand All @@ -225,10 +249,12 @@ private fun ContentPreview() {
Content(
isLoading = false,
browserEnabled = false,
customTabsEnabled = false,
actionAfterClean = ActionAfterClean.OpenShareMenu,
onSanitizersClick = {},
onLicensesClick = {},
onBrowserSwitchCheckedChange = {},
onCustomTabsSwitchCheckedChange = {},
onActionAfterCleanClick = {},
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Léon - The URL Cleaner
* Copyright (C) 2023 Sven Jacobs
* Copyright (C) 2024 Sven Jacobs
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -44,6 +44,7 @@ class SettingsScreenViewModel(
data class UiState(
val isLoading: Boolean = true,
val browserEnabled: Boolean = false,
val customTabsEnabled: Boolean = false,
val actionAfterClean: ActionAfterClean = ActionAfterClean.DoNothing,
)

Expand All @@ -52,11 +53,13 @@ class SettingsScreenViewModel(
val uiState: StateFlow<UiState> =
combine(
browserEnabled,
appDataStoreManager.customTabsEnabled,
appDataStoreManager.actionAfterClean,
) { browserEnabled, actionAfterClean ->
) { browserEnabled, customTabsEnabled, actionAfterClean ->
UiState(
isLoading = false,
browserEnabled = browserEnabled,
customTabsEnabled = customTabsEnabled,
actionAfterClean = actionAfterClean ?: ActionAfterClean.DoNothing,
)
}.stateIn(
Expand All @@ -83,6 +86,12 @@ class SettingsScreenViewModel(
)
}

fun onCustomTabsSwitchCheckedChange(checked: Boolean) {
viewModelScope.launch {
appDataStoreManager.setCustomTabsEnabled(checked)
}
}

fun onActionAfterCleanClick(actionAfterClean: ActionAfterClean) {
viewModelScope.launch {
appDataStoreManager.setActionAfterClean(actionAfterClean)
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/res/values-de/strings.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!--
~ Léon - The URL Cleaner
~ Copyright (C) 2023 Sven Jacobs
~ Copyright (C) 2024 Sven Jacobs
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -36,6 +36,7 @@
<string name="reset">Zurücksetzen</string>
<string name="extract_url">Nur URL extrahieren</string>
<string name="register_as_browser">Registriere Léon als Browser</string>
<string name="open_in_custom_tabs">URLs in Browser Custom Tabs öffnen</string>
<string name="action_after_clean">Aktion nach Reinigung</string>
<string name="do_nothing">Nichts unternehmen</string>
<string name="open_share_menu">Teilenmenü öffnen</string>
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/res/values-pl/strings.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!--
~ Léon - The URL Cleaner
~ Copyright (C) 2023 Sven Jacobs
~ Copyright (C) 2024 Sven Jacobs
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -36,6 +36,7 @@
<string name="reset">Wyzeruj</string>
<string name="extract_url">Wyodrębnij tylko URL</string>
<string name="register_as_browser">Zarejestruj Léona jako przeglądarkę</string>
<string name="open_in_custom_tabs">Open URLs in browser custom tabs</string> <!-- TODO: translate -->
<string name="action_after_clean">Action after clean</string> <!-- TODO: translate -->
<string name="do_nothing">Do nothing</string> <!-- TODO: translate -->
<string name="open_share_menu">Open share menu</string> <!-- TODO: translate -->
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/res/values-ru/strings.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!--
~ Léon - The URL Cleaner
~ Copyright (C) 2023 Sven Jacobs
~ Copyright (C) 2024 Sven Jacobs
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -36,6 +36,7 @@
<string name="reset">Сброс</string>
<string name="extract_url">Извлечение только URL-адреса</string>
<string name="register_as_browser">Register Léon as browser</string> <!-- TODO: translate -->
<string name="open_in_custom_tabs">Open URLs in browser custom tabs</string> <!-- TODO: translate -->
<string name="action_after_clean">Action after clean</string> <!-- TODO: translate -->
<string name="do_nothing">Do nothing</string> <!-- TODO: translate -->
<string name="open_share_menu">Open share menu</string> <!-- TODO: translate -->
Expand Down
Loading

0 comments on commit 43636a8

Please sign in to comment.