diff --git a/app/src/main/java/net/waterfox/android/compose/preference/RadioGroupPreference.kt b/app/src/main/java/net/waterfox/android/compose/preference/RadioGroupPreference.kt index 1cfbda465..f609f8869 100644 --- a/app/src/main/java/net/waterfox/android/compose/preference/RadioGroupPreference.kt +++ b/app/src/main/java/net/waterfox/android/compose/preference/RadioGroupPreference.kt @@ -5,29 +5,56 @@ package net.waterfox.android.compose.preference import android.content.res.Configuration +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* import androidx.compose.foundation.selection.toggleable +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.text.selection.LocalTextSelectionColors +import androidx.compose.foundation.text.selection.TextSelectionColors +import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.RadioButton import androidx.compose.material.RadioButtonDefaults import androidx.compose.material.Text +import androidx.compose.material.TextFieldDefaults +import androidx.compose.material.TextFieldDefaults.indicatorLine import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.Color.Companion.Green +import androidx.compose.ui.graphics.Color.Companion.Red +import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTag import androidx.compose.ui.semantics.testTagsAsResourceId +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardCapitalization +import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import net.waterfox.android.ext.readBooleanPreference import net.waterfox.android.ext.writeBooleanPreference import net.waterfox.android.theme.Theme import net.waterfox.android.theme.WaterfoxTheme +import net.waterfox.android.R +import net.waterfox.android.ext.readStringPreference +import net.waterfox.android.ext.writeStringPreference @Composable fun RadioGroupPreference( @@ -41,17 +68,33 @@ fun RadioGroupPreference( } return Column { + val onValueChange: (Boolean, RadioGroupItem) -> Unit = { _, item -> + items.forEach { + context.writeBooleanPreference( + it.key, + it.key == item.key, + ) + } + item.onClick?.invoke() + setSelected(item) + } items.forEach { item -> if (item.visible) { - RadioButtonPreference( - title = item.title, - selected = selected?.key == item.key, - onValueChange = { - items.forEach { context.writeBooleanPreference(it.key, it.key == item.key) } - item.onClick?.invoke() - setSelected(item) - }, - ) + if (item.editable) { + val key = stringResource(R.string.pref_key_new_tab_web_address_value) + RadioButtonWithInputPreference( + value = context.readStringPreference(key, "")!!, + selected = selected?.key == item.key, + onValueChange = { onValueChange(it, item) }, + onInputValueChange = { context.writeStringPreference(key, it) }, + ) + } else { + RadioButtonPreference( + title = item.title, + selected = selected?.key == item.key, + onValueChange = { onValueChange(it, item) }, + ) + } } } } @@ -62,6 +105,7 @@ data class RadioGroupItem( val key: String, val defaultValue: Boolean, val visible: Boolean = true, + val editable: Boolean = false, val onClick: (() -> Unit)? = null, ) @@ -140,3 +184,162 @@ private fun RadioButtonPreferenceOffPreview() { ) } } + + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun RadioButtonWithInputPreference( + value: String, + selected: Boolean, + onValueChange: (Boolean) -> Unit, + onInputValueChange: (String) -> Unit, + enabled: Boolean = true, +) { + val focusManager = LocalFocusManager.current + val keyboardController = LocalSoftwareKeyboardController.current + return Row( + modifier = Modifier + .fillMaxWidth() + .toggleable( + value = selected, + onValueChange = { newValue -> + if (enabled) { + onValueChange(newValue) + } + }, + role = Role.RadioButton, + ) + .alpha(if (enabled) 1f else 0.5f) + .semantics { + testTagsAsResourceId = true + testTag = "radio.button.preference" + }, + ) { + RadioButton( + selected = selected, + onClick = null, + modifier = Modifier + .size(48.dp) + .padding(start = 16.dp), + colors = RadioButtonDefaults.colors( + selectedColor = WaterfoxTheme.colors.formSelected, + unselectedColor = WaterfoxTheme.colors.formDefault, + ), + ) + + TextField( + text = value, + onValueChange = { + onInputValueChange(it) + keyboardController?.hide() + focusManager.clearFocus() + }, + selected = selected, + modifier = Modifier + .align(Alignment.CenterVertically), + ) + } +} + +@OptIn(ExperimentalMaterialApi::class) +@Composable +private fun TextField( + text: String, + onValueChange: (String) -> Unit, + selected: Boolean, + modifier: Modifier = Modifier, +) { + var value by remember { mutableStateOf(text) } + val interactionSource = remember { MutableInteractionSource() } + val customTextSelectionColors = TextSelectionColors( + handleColor = WaterfoxTheme.colors.formSelected, + backgroundColor = WaterfoxTheme.colors.formSelected.copy(alpha = 0.4f), + ) + CompositionLocalProvider(LocalTextSelectionColors provides customTextSelectionColors) { + BasicTextField( + value = value, + onValueChange = { value = it }, + modifier = modifier + .fillMaxWidth() + .padding( + start = 24.dp, + end = 16.dp, + ) + .indicatorLine( + enabled = selected, + false, + interactionSource, + TextFieldDefaults.textFieldColors( + unfocusedIndicatorColor = WaterfoxTheme.colors.formDisabled, + focusedIndicatorColor = WaterfoxTheme.colors.formSelected, + ), + ), + singleLine = true, + enabled = selected, + textStyle = WaterfoxTheme.typography.subtitle1.merge( + TextStyle(color = WaterfoxTheme.colors.textPrimary), + ), + cursorBrush = SolidColor(WaterfoxTheme.colors.formSelected), + keyboardOptions = KeyboardOptions( + imeAction = ImeAction.Done, + capitalization = KeyboardCapitalization.None, + ), + keyboardActions = KeyboardActions(onDone = { onValueChange(value) }), + ) { innerTextField -> + TextFieldDefaults.TextFieldDecorationBox( + value = value, + visualTransformation = VisualTransformation.None, + innerTextField = innerTextField, + placeholder = { + Text( + text = stringResource(id = R.string.preferences_open_new_tab_web_address), + modifier = Modifier.fillMaxWidth(), + color = WaterfoxTheme.colors.textSecondary, + style = WaterfoxTheme.typography.subtitle1, + ) + }, + singleLine = true, + enabled = selected, + interactionSource = interactionSource, + contentPadding = TextFieldDefaults.textFieldWithoutLabelPadding( + start = 0.dp, end = 0.dp, + ), + colors = TextFieldDefaults.textFieldColors( + textColor = WaterfoxTheme.colors.textPrimary, + cursorColor = WaterfoxTheme.colors.formSelected, + ), + ) + } + } +} + + +@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO, showBackground = true) +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true) +@Composable +private fun RadioButtonWithInputPreferenceOnPreview() { + WaterfoxTheme(theme = Theme.getTheme()) { + RadioButtonWithInputPreference( + value = "example.com", + selected = true, + onValueChange = {}, + onInputValueChange = {}, + enabled = true, + ) + } +} + +@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO, showBackground = true) +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true) +@Composable +private fun RadioButtonWithInputPreferenceOffPreview() { + WaterfoxTheme(theme = Theme.getTheme()) { + RadioButtonWithInputPreference( + value = "example.com", + selected = false, + onValueChange = {}, + onInputValueChange = {}, + enabled = true, + ) + } +} diff --git a/app/src/main/java/net/waterfox/android/ext/Context.kt b/app/src/main/java/net/waterfox/android/ext/Context.kt index eff33aec6..3cf679187 100644 --- a/app/src/main/java/net/waterfox/android/ext/Context.kt +++ b/app/src/main/java/net/waterfox/android/ext/Context.kt @@ -51,6 +51,12 @@ fun Context.readFloatPreference(key: String, defaultValue: Float) = fun Context.writeFloatPreference(key: String, value: Float) = settings().preferences.edit().putFloat(key, value).apply() +fun Context.readStringPreference(key: String, defaultValue: String) = + settings().preferences.getString(key, defaultValue) + +fun Context.writeStringPreference(key: String, value: String) = + settings().preferences.edit().putString(key, value).apply() + /** * Gets the Root View with an activity context * diff --git a/app/src/main/java/net/waterfox/android/settings/TabsSettingsComposeView.kt b/app/src/main/java/net/waterfox/android/settings/TabsSettingsComposeView.kt index 511106f60..e752df4ac 100644 --- a/app/src/main/java/net/waterfox/android/settings/TabsSettingsComposeView.kt +++ b/app/src/main/java/net/waterfox/android/settings/TabsSettingsComposeView.kt @@ -7,10 +7,15 @@ package net.waterfox.android.settings import android.content.Context import android.util.AttributeSet import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier import androidx.compose.ui.platform.AbstractComposeView import androidx.compose.ui.res.stringResource import net.waterfox.android.R @@ -35,7 +40,12 @@ class TabsSettingsComposeView @JvmOverloads constructor( @Composable override fun Content() { WaterfoxTheme { - Column { + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .imePadding(), + ) { PreferenceCategory( title = stringResource(R.string.preferences_tab_view), allowDividerAbove = false, @@ -98,6 +108,34 @@ class TabsSettingsComposeView @JvmOverloads constructor( enabled = inactiveTabsCategoryEnabled, ) } + + PreferenceCategory( + title = stringResource(id = R.string.preferences_open_new_tab), + ) { + RadioGroupPreference( + items = listOf( + RadioGroupItem( + title = stringResource(id = R.string.preferences_open_new_tab_show_home), + key = stringResource(R.string.pref_key_new_tab_show_home), + defaultValue = true, + onClick = {}, + ), + RadioGroupItem( + title = stringResource(id = R.string.preferences_open_new_tab_blank_tab), + key = stringResource(R.string.pref_key_new_tab_blank), + defaultValue = false, + onClick = {}, + ), + RadioGroupItem( + title = "", + key = stringResource(R.string.pref_key_new_tab_web_address), + defaultValue = false, + editable = true, + onClick = {}, + ), + ), + ) + } } } } diff --git a/app/src/main/java/net/waterfox/android/tabstray/TabsTrayController.kt b/app/src/main/java/net/waterfox/android/tabstray/TabsTrayController.kt index b0ee076fc..feea9fc24 100644 --- a/app/src/main/java/net/waterfox/android/tabstray/TabsTrayController.kt +++ b/app/src/main/java/net/waterfox/android/tabstray/TabsTrayController.kt @@ -36,6 +36,7 @@ import net.waterfox.android.components.appstate.AppAction import net.waterfox.android.components.bookmarks.BookmarksUseCase import net.waterfox.android.ext.DEFAULT_ACTIVE_DAYS import net.waterfox.android.ext.potentialInactiveTabs +import net.waterfox.android.ext.settings import net.waterfox.android.home.HomeFragment import net.waterfox.android.library.bookmarks.BookmarksSharedViewModel import net.waterfox.android.tabstray.browser.InactiveTabsController @@ -240,10 +241,35 @@ class DefaultTabsTrayController( private fun openNewTab(isPrivate: Boolean) { val startTime = profiler?.getProfilerTime() browsingModeManager.mode = BrowsingMode.fromBoolean(isPrivate) - navController.navigate( - TabsTrayFragmentDirections.actionGlobalHome(focusOnAddressBar = true), - ) - navigationInteractor.onTabTrayDismissed() + + val settings = navController.context.settings() + if (settings.openTabShowHome) { + navController.navigate( + TabsTrayFragmentDirections.actionGlobalHome(focusOnAddressBar = true), + ) + navigationInteractor.onTabTrayDismissed() + } else { + val url = if (settings.openTabShowBlank) { + "about:blank" + } else { + val address = settings.openTabShowWebAddressValue + if (address.startsWith("http")) { + address + } else { + "https://$address" + } + } + val tab = tabsUseCases.addTab( + url, + selectTab = false, + startLoading = true, + parentId = null, + contextId = null, + ) + tabsUseCases.selectTab(tab) + handleNavigateToBrowser() + } + profiler?.addMarker( "DefaultTabTrayController.onNewTabTapped", startTime, diff --git a/app/src/main/java/net/waterfox/android/utils/Settings.kt b/app/src/main/java/net/waterfox/android/utils/Settings.kt index 3b9b8c837..81211eef2 100644 --- a/app/src/main/java/net/waterfox/android/utils/Settings.kt +++ b/app/src/main/java/net/waterfox/android/utils/Settings.kt @@ -278,6 +278,26 @@ class Settings(private val appContext: Context) : PreferencesHolder { default = false ) + var openTabShowHome by booleanPreference( + appContext.getPreferenceKey(R.string.pref_key_new_tab_show_home), + default = true + ) + + var openTabShowBlank by booleanPreference( + appContext.getPreferenceKey(R.string.pref_key_new_tab_blank), + default = false + ) + + var openTabShowWebAddress by booleanPreference( + appContext.getPreferenceKey(R.string.pref_key_new_tab_web_address), + default = true + ) + + var openTabShowWebAddressValue by stringPreference( + appContext.getPreferenceKey(R.string.pref_key_new_tab_web_address_value), + default = "" + ) + var allowThirdPartyRootCerts by booleanPreference( appContext.getPreferenceKey(R.string.pref_key_allow_third_party_root_certs), default = false diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml index 2523e9360..d1af9a45c 100644 --- a/app/src/main/res/values/preference_keys.xml +++ b/app/src/main/res/values/preference_keys.xml @@ -263,6 +263,10 @@ pref_key_camera_permissions_needed pref_key_inactive_tabs_category pref_key_inactive_tabs + pref_key_new_tab_show_home + pref_key_new_tab_blank + pref_key_new_tab_web_address + pref_key_new_tab_web_address_value pref_key_return_to_browser diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 795be3084..7803dad1a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -662,6 +662,13 @@ Tabs you haven’t viewed for two weeks get moved to the inactive section. + + + Open new tab + Show home + Blank tab + Enter web address + Open tabs