diff --git a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/ActivityDependencies.kt b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/ActivityDependencies.kt index 3c7c6644f0..2f7ca233ac 100644 --- a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/ActivityDependencies.kt +++ b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/ActivityDependencies.kt @@ -23,6 +23,8 @@ import com.lasthopesoftware.bluewater.shared.messages.application.ApplicationMes import com.lasthopesoftware.bluewater.shared.messages.application.RegisterForApplicationMessages import com.lasthopesoftware.bluewater.shared.messages.application.SendApplicationMessages import com.lasthopesoftware.bluewater.shared.messages.application.getScopedMessageBus +import com.lasthopesoftware.bluewater.tutorials.AndroidOpennessWarningViewModel +import com.lasthopesoftware.bluewater.tutorials.TutorialManager import com.lasthopesoftware.resources.closables.ViewModelCloseableManager import com.lasthopesoftware.resources.uri.DocumentUriSelector @@ -133,4 +135,7 @@ class ActivityDependencies( activity.contentResolver ) } + override val androidOpennessWarningViewModel by activity.buildViewModelLazily { + AndroidOpennessWarningViewModel(TutorialManager(activity)) + } } diff --git a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/ResponsiveApplication.kt b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/ResponsiveApplication.kt index a6022b3583..9a252eec7e 100644 --- a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/ResponsiveApplication.kt +++ b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/ResponsiveApplication.kt @@ -123,6 +123,7 @@ import com.lasthopesoftware.bluewater.exceptions.UncaughtExceptionHandlerLogger import com.lasthopesoftware.bluewater.settings.ApplicationSettingsView import com.lasthopesoftware.bluewater.settings.hidden.HiddenSettingsView import com.lasthopesoftware.bluewater.shared.android.viewmodels.ViewModelInitAction +import com.lasthopesoftware.bluewater.tutorials.AndroidOpennessWarningDialog import com.lasthopesoftware.observables.subscribeAsState import com.lasthopesoftware.policies.ratelimiting.RateLimitingExecutionPolicy import com.lasthopesoftware.promises.extensions.suspend @@ -840,6 +841,7 @@ fun ResponsiveApplication( applicationSettingsViewModel = routedNavigationDependencies.applicationSettingsViewModel, applicationNavigation = routedNavigationDependencies.applicationNavigation, playbackService = routedNavigationDependencies.playbackServiceController, + stringResources = routedNavigationDependencies.stringResources, ) } @@ -873,6 +875,8 @@ fun ResponsiveApplication( routedNavigationDependencies.hiddenSettingsViewModel.loadApplicationSettings() } } + + AndroidOpennessWarningDialog(entryDependencies.androidOpennessWarningViewModel) } val isCheckingConnection by connectionStatusViewModel.isGettingConnection.subscribeAsState() diff --git a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/browsing/EntryDependencies.kt b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/browsing/EntryDependencies.kt index 702b5ab408..408e9aaec8 100644 --- a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/browsing/EntryDependencies.kt +++ b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/browsing/EntryDependencies.kt @@ -18,6 +18,7 @@ import com.lasthopesoftware.bluewater.settings.hidden.HiddenSettingsViewModel import com.lasthopesoftware.bluewater.shared.android.messages.ViewModelMessageBus import com.lasthopesoftware.bluewater.shared.messages.RegisterForTypedMessages import com.lasthopesoftware.bluewater.shared.messages.SendTypedMessages +import com.lasthopesoftware.bluewater.tutorials.AndroidOpennessWarningViewModel interface EntryDependencies : ApplicationDependencies { val applicationViewModel: ManageApplicationPropertyState @@ -33,6 +34,7 @@ interface EntryDependencies : ApplicationDependencies { val applicationSettingsViewModel: ApplicationSettingsViewModel val hiddenSettingsViewModel: HiddenSettingsViewModel val userSslCertificateProvider: ProvideUserSslCertificates + val androidOpennessWarningViewModel: AndroidOpennessWarningViewModel } @Composable diff --git a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/settings/LibrarySettingsView.kt b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/settings/LibrarySettingsView.kt index bc22e09e3c..67d57e90b6 100644 --- a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/settings/LibrarySettingsView.kt +++ b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/settings/LibrarySettingsView.kt @@ -311,7 +311,7 @@ private fun CustomHeadersList( ) { ProvideTextStyle(MaterialTheme.typography.h5) { Text( - text = "Edit Header", + text = stringResource(R.string.edit_header), modifier = Modifier .weight(1f) .align(Alignment.CenterVertically), @@ -342,14 +342,18 @@ private fun CustomHeadersList( value = editingHeaderKey, placeholder = stringResource(R.string.header), onValueChange = { editingHeaderKey = it }, - modifier = Modifier.focusRequester(fieldFocusRequester).padding(vertical = verticalPadding), + modifier = Modifier + .focusRequester(fieldFocusRequester) + .padding(vertical = verticalPadding), ) StandardTextField( value = editingHeaderValue, placeholder = stringResource(R.string.value), onValueChange = { editingHeaderValue = it }, - modifier = Modifier.focusRequester(fieldFocusRequester).padding(vertical = verticalPadding), + modifier = Modifier + .focusRequester(fieldFocusRequester) + .padding(vertical = verticalPadding), ) } diff --git a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/settings/ApplicationSettingsView.kt b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/settings/ApplicationSettingsView.kt index 7602ccdf55..0e7d249ccb 100644 --- a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/settings/ApplicationSettingsView.kt +++ b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/settings/ApplicationSettingsView.kt @@ -3,6 +3,7 @@ package com.lasthopesoftware.bluewater.settings import VerticalHeaderScaffold +import android.content.Intent import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -58,6 +59,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalResources @@ -67,6 +69,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.core.net.toUri import com.lasthopesoftware.bluewater.BuildConfig import com.lasthopesoftware.bluewater.NavigateApplication import com.lasthopesoftware.bluewater.R @@ -93,6 +96,7 @@ import com.lasthopesoftware.observables.subscribeAsState import com.lasthopesoftware.promises.extensions.preparePromise import com.lasthopesoftware.promises.extensions.toState import com.lasthopesoftware.resources.executors.ThreadPools +import com.lasthopesoftware.resources.strings.GetStringResources import com.mikepenz.markdown.m2.Markdown import kotlinx.coroutines.flow.first @@ -224,10 +228,11 @@ fun ThemeSettingsSection( @Composable fun AboutApplication( + stringResources: GetStringResources, playbackService: ControlPlaybackService, ) { SettingsSection( - headerText = stringResource(id = R.string.title_activity_about, stringResource(R.string.app_name)), + headerText = stringResources.aboutTitle, headerModifier = standardRowModifier, modifier = Modifier.fillMaxWidth(), ) { @@ -238,6 +243,43 @@ fun AboutApplication( ) } + SettingsSection( + headerText = stringResource(id = R.string.keep_android_open), + headerModifier = standardRowModifier, + modifier = Modifier.fillMaxWidth(), + ) { + Markdown(stringResource(R.string.side_loading_warning)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + val context = LocalContext.current + Button( + onClick = { + context + .startActivity(Intent( + Intent.ACTION_VIEW, + "https://keepandroidopen.org".toUri() + )) + }, + ) { + Text(text = stringResource(R.string.more_info)) + } + Button( + onClick = { + context + .startActivity(Intent( + Intent.ACTION_VIEW, + "https://github.com/woheller69/FreeDroidWarn?tab=readme-ov-file#solutions".toUri() + )) + }, + ) { + Text(text = stringResource(R.string.solutions)) + } + } + } + SettingsSection( headerText = stringResource(id = R.string.acknowledgements_title), headerModifier = standardRowModifier, @@ -269,7 +311,6 @@ fun AboutApplication( @Composable private fun SettingsList( applicationSettingsViewModel: ApplicationSettingsViewModel, - playbackService: ControlPlaybackService, ) { val isLoading by applicationSettingsViewModel.isLoading.subscribeAsState() SettingsSection( @@ -307,8 +348,6 @@ private fun SettingsList( AudioSettingsSection(applicationSettingsViewModel) ThemeSettingsSection(applicationSettingsViewModel) - - AboutApplication(playbackService) } @Composable @@ -353,6 +392,7 @@ fun ServersList( fun ApplicationSettingsMenu( applicationSettingsViewModel: ApplicationSettingsViewModel, applicationNavigation: NavigateApplication, + stringResources: GetStringResources, selectedLibraryId: LibraryId?, modifier: Modifier = Modifier, onTabChange: (() -> Unit)? = null, @@ -379,30 +419,30 @@ fun ApplicationSettingsMenu( enabled = selectedTab != ApplicationSettingsViewModel.SelectedTab.ViewServers ) - val settingsButtonLabel = stringResource(R.string.application_settings) + val addServerButtonLabel = stringResource(R.string.btn_add_server) ColumnMenuIcon( onClick = { - selectedTab = ApplicationSettingsViewModel.SelectedTab.ViewSettings - onTabChange?.invoke() + applicationNavigation.viewNewServerSettings() }, - iconPainter = painterResource(id = R.drawable.ic_action_settings), - contentDescription = settingsButtonLabel, - label = settingsButtonLabel, + iconPainter = painterResource(id = R.drawable.ic_add_item_36dp), + contentDescription = addServerButtonLabel, + label = addServerButtonLabel, labelMaxLines = 2, modifier = modifier, - enabled = selectedTab != ApplicationSettingsViewModel.SelectedTab.ViewSettings ) - val addServerButtonLabel = stringResource(R.string.btn_add_server) + val settingsButtonLabel = stringResource(R.string.application_settings) ColumnMenuIcon( onClick = { - applicationNavigation.viewNewServerSettings() + selectedTab = ApplicationSettingsViewModel.SelectedTab.ViewSettings + onTabChange?.invoke() }, - iconPainter = painterResource(id = R.drawable.ic_add_item_36dp), - contentDescription = addServerButtonLabel, - label = addServerButtonLabel, + iconPainter = painterResource(id = R.drawable.ic_action_settings), + contentDescription = settingsButtonLabel, + label = settingsButtonLabel, labelMaxLines = 2, modifier = modifier, + enabled = selectedTab != ApplicationSettingsViewModel.SelectedTab.ViewSettings ) val connectText = stringResource(R.string.active_server) @@ -417,6 +457,20 @@ fun ApplicationSettingsMenu( labelMaxLines = 2, enabled = selectedLibraryId != null ) + + val aboutLabel = stringResources.aboutTitle + ColumnMenuIcon( + onClick = { + selectedTab = ApplicationSettingsViewModel.SelectedTab.ViewApplicationInformation + onTabChange?.invoke() + }, + iconPainter = painterResource(id = R.drawable.information_outline_36dp), + contentDescription = aboutLabel, + label = aboutLabel, + labelMaxLines = 2, + modifier = modifier, + enabled = selectedTab != ApplicationSettingsViewModel.SelectedTab.ViewApplicationInformation + ) } } @@ -425,6 +479,7 @@ private fun ApplicationSettingsViewVertical( applicationSettingsViewModel: ApplicationSettingsViewModel, applicationNavigation: NavigateApplication, playbackService: ControlPlaybackService, + stringResources: GetStringResources, ) { val minHeaderHeight = LocalDensity.current.remember { Dimensions.appBarHeight.toPx() } val headerScaler = ConsumableConnectedScaler.remember(minHeaderHeight) @@ -502,9 +557,10 @@ private fun ApplicationSettingsViewVertical( .clipToBounds() ) { ApplicationSettingsMenu( - applicationSettingsViewModel, + applicationSettingsViewModel = applicationSettingsViewModel, applicationNavigation = applicationNavigation, selectedLibraryId = selectedLibraryId, + stringResources = stringResources, modifier = Modifier .graphicsLayer { translationY = (menuHeightPx - expandedMenuHeightPx) * 0.5f @@ -534,7 +590,10 @@ private fun ApplicationSettingsViewVertical( ) ApplicationSettingsViewModel.SelectedTab.ViewSettings -> SettingsList( applicationSettingsViewModel, - playbackService + ) + ApplicationSettingsViewModel.SelectedTab.ViewApplicationInformation -> AboutApplication( + stringResources, + playbackService, ) } } @@ -550,6 +609,7 @@ private fun BoxWithConstraintsScope.ApplicationSettingsViewHorizontal( applicationSettingsViewModel: ApplicationSettingsViewModel, applicationNavigation: NavigateApplication, playbackService: ControlPlaybackService, + stringResources: GetStringResources, ) { Row( modifier = Modifier.fillMaxSize(), @@ -585,9 +645,10 @@ private fun BoxWithConstraintsScope.ApplicationSettingsViewHorizontal( ) ApplicationSettingsMenu( - applicationSettingsViewModel, + applicationSettingsViewModel = applicationSettingsViewModel, applicationNavigation = applicationNavigation, selectedLibraryId = selectedLibraryId, + stringResources = stringResources, modifier = Modifier .fillMaxWidth() .height(expandedMenuHeight) @@ -612,7 +673,10 @@ private fun BoxWithConstraintsScope.ApplicationSettingsViewHorizontal( ) ApplicationSettingsViewModel.SelectedTab.ViewSettings -> SettingsList( applicationSettingsViewModel, - playbackService + ) + ApplicationSettingsViewModel.SelectedTab.ViewApplicationInformation -> AboutApplication( + stringResources, + playbackService, ) } } @@ -624,6 +688,7 @@ fun ApplicationSettingsView( applicationSettingsViewModel: ApplicationSettingsViewModel, applicationNavigation: NavigateApplication, playbackService: ControlPlaybackService, + stringResources: GetStringResources, ) { Box(modifier = Modifier .fillMaxSize() @@ -657,11 +722,13 @@ fun ApplicationSettingsView( if (maxWidth < maxHeight) ApplicationSettingsViewVertical( applicationSettingsViewModel, applicationNavigation, - playbackService + playbackService, + stringResources, ) else ApplicationSettingsViewHorizontal( applicationSettingsViewModel, applicationNavigation, - playbackService + playbackService, + stringResources, ) } } diff --git a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/settings/ApplicationSettingsViewModel.kt b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/settings/ApplicationSettingsViewModel.kt index edf94cae53..33d501b6e8 100644 --- a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/settings/ApplicationSettingsViewModel.kt +++ b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/settings/ApplicationSettingsViewModel.kt @@ -24,7 +24,7 @@ class ApplicationSettingsViewModel( ) : ViewModel(), TrackLoadedViewState, ImmediateAction { enum class SelectedTab { - ViewServers, ViewSettings + ViewServers, ViewSettings, ViewApplicationInformation } private val mutableLibraries = MutableInteractionState(emptyList>()) diff --git a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/settings/TvApplicationSettingsView.kt b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/settings/TvApplicationSettingsView.kt deleted file mode 100644 index c9aa0c5ef9..0000000000 --- a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/settings/TvApplicationSettingsView.kt +++ /dev/null @@ -1,137 +0,0 @@ -package com.lasthopesoftware.bluewater.settings - -import androidx.compose.foundation.background -import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.BoxWithConstraints -import androidx.compose.foundation.layout.BoxWithConstraintsScope -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.MaterialTheme -import androidx.compose.material.ProvideTextStyle -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clipToBounds -import androidx.compose.ui.res.stringResource -import com.lasthopesoftware.bluewater.NavigateApplication -import com.lasthopesoftware.bluewater.R -import com.lasthopesoftware.bluewater.android.ui.components.ApplicationLogo -import com.lasthopesoftware.bluewater.android.ui.theme.ControlSurface -import com.lasthopesoftware.bluewater.android.ui.theme.Dimensions -import com.lasthopesoftware.bluewater.android.ui.theme.Dimensions.viewPaddingUnit -import com.lasthopesoftware.bluewater.client.playback.service.ControlPlaybackService -import com.lasthopesoftware.observables.subscribeAsState - -@Composable -@OptIn(ExperimentalMaterialApi::class) -private fun SettingsList( - applicationSettingsViewModel: ApplicationSettingsViewModel, - playbackService: ControlPlaybackService -) { - AudioSettingsSection(applicationSettingsViewModel) - - ThemeSettingsSection(applicationSettingsViewModel) - - AboutApplication(playbackService) -} - -@Composable -private fun BoxWithConstraintsScope.ApplicationSettingsViewHorizontal( - applicationSettingsViewModel: ApplicationSettingsViewModel, - applicationNavigation: NavigateApplication, - playbackService: ControlPlaybackService, -) { - Row( - modifier = Modifier.fillMaxSize(), - horizontalArrangement = Arrangement.Start, - ) { - val libraries by applicationSettingsViewModel.libraries.subscribeAsState() - val selectedLibraryId by applicationSettingsViewModel.chosenLibraryId.subscribeAsState() - - Column( - modifier = Modifier.width(this@ApplicationSettingsViewHorizontal.maxHeight) - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .height(Dimensions.appBarHeight) - .background(MaterialTheme.colors.surface) - ) { - ProvideTextStyle(MaterialTheme.typography.h4) { - Text(text = stringResource(id = R.string.app_name)) - } - } - - ApplicationLogo(modifier = Modifier - .fillMaxHeight(.5f) - .align(Alignment.CenterHorizontally) - .weight(1f) - .combinedClickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null, - onClick = {}, - onLongClick = { applicationNavigation.viewHiddenSettings() }, - ) - ) - - ApplicationSettingsMenu( - applicationSettingsViewModel, - applicationNavigation = applicationNavigation, - selectedLibraryId = selectedLibraryId, - modifier = Modifier - .fillMaxWidth() - .height(expandedMenuHeight) - .clipToBounds() - .align(Alignment.Start), - ) - } - - Column( - modifier = Modifier - .fillMaxHeight() - .padding(start = viewPaddingUnit * 2) - .verticalScroll(rememberScrollState()), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - val selectedTab by applicationSettingsViewModel.selectedTab.subscribeAsState() - when (selectedTab) { - ApplicationSettingsViewModel.SelectedTab.ViewServers -> ServersList( - applicationNavigation, - libraries, - selectedLibraryId, - ) - ApplicationSettingsViewModel.SelectedTab.ViewSettings -> SettingsList( - applicationSettingsViewModel, - playbackService - ) - } - } - } -} - -@Composable -fun TvApplicationSettingsView( - applicationSettingsViewModel: ApplicationSettingsViewModel, - applicationNavigation: NavigateApplication, - playbackService: ControlPlaybackService, -) { - ControlSurface { - BoxWithConstraints { - ApplicationSettingsViewHorizontal(applicationSettingsViewModel, applicationNavigation, playbackService) - } - } -} diff --git a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/tutorials/AndroidOpennessWarningDialog.kt b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/tutorials/AndroidOpennessWarningDialog.kt new file mode 100644 index 0000000000..b1fe89c599 --- /dev/null +++ b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/tutorials/AndroidOpennessWarningDialog.kt @@ -0,0 +1,101 @@ +package com.lasthopesoftware.bluewater.tutorials + +import android.content.Intent +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.material.Button +import androidx.compose.material.MaterialTheme +import androidx.compose.material.ProvideTextStyle +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.window.Dialog +import androidx.core.net.toUri +import com.lasthopesoftware.bluewater.R +import com.lasthopesoftware.bluewater.android.ui.theme.ControlSurface +import com.lasthopesoftware.bluewater.android.ui.theme.Dimensions +import com.lasthopesoftware.bluewater.android.ui.theme.Dimensions.viewPaddingUnit +import com.lasthopesoftware.observables.subscribeAsState +import com.lasthopesoftware.promises.extensions.suspend +import com.mikepenz.markdown.m2.Markdown +import kotlinx.coroutines.launch + +@Composable +fun AndroidOpennessWarningDialog(androidOpennessWarningViewModel: AndroidOpennessWarningViewModel) { + val showSideLoadingDialog by androidOpennessWarningViewModel.isShown.subscribeAsState() + + LaunchedEffect(androidOpennessWarningViewModel) { + androidOpennessWarningViewModel + .promiseLoadedSideLoadingTutorial() + .suspend() + } + + if (showSideLoadingDialog) { + val scope = rememberCoroutineScope() + Dialog( + onDismissRequest = { + scope.launch { androidOpennessWarningViewModel.promiseSideLoadingMarkedShown().suspend() } + }, + ) { + ControlSurface { + Column( + modifier = Modifier.padding(viewPaddingUnit * 2), + ) { + ProvideTextStyle(MaterialTheme.typography.h5) { + Text( + text = stringResource(R.string.keep_android_open), + modifier = Modifier + .align(Alignment.CenterHorizontally), + ) + } + + Markdown( + content = stringResource(R.string.side_loading_warning), + modifier = Modifier + .wrapContentSize() + .padding(vertical = Dimensions.rowPadding), + ) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + val context = LocalContext.current + Button( + onClick = { + context + .startActivity(Intent( + Intent.ACTION_VIEW, + "https://keepandroidopen.org".toUri() + )) + }, + ) { + Text(text = stringResource(R.string.more_info)) + } + Button( + onClick = { + context + .startActivity(Intent( + Intent.ACTION_VIEW, + "https://github.com/woheller69/FreeDroidWarn?tab=readme-ov-file#solutions".toUri() + )) + }, + ) { + Text(text = stringResource(R.string.solutions)) + } + } + } + } + } + } +} diff --git a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/tutorials/AndroidOpennessWarningViewModel.kt b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/tutorials/AndroidOpennessWarningViewModel.kt new file mode 100644 index 0000000000..ed004fd254 --- /dev/null +++ b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/tutorials/AndroidOpennessWarningViewModel.kt @@ -0,0 +1,31 @@ +package com.lasthopesoftware.bluewater.tutorials + +import androidx.lifecycle.ViewModel +import com.lasthopesoftware.bluewater.client.browsing.TrackLoadedViewState +import com.lasthopesoftware.observables.MutableInteractionState +import com.namehillsoftware.handoff.promises.Promise + +class AndroidOpennessWarningViewModel( + private val tutorials: ManageTutorials +) : ViewModel(), TrackLoadedViewState { + + private val mutableIsLoading = MutableInteractionState(false) + private val mutableIsShown = MutableInteractionState(false) + + val isShown = mutableIsShown.asInteractionState() + + override val isLoading = mutableIsLoading.asInteractionState() + + fun promiseLoadedSideLoadingTutorial(): Promise { + mutableIsLoading.value = true + return tutorials + .promiseWasTutorialShown(TutorialManager.KnownTutorials.droidSideLoadingWarning) + .then { mutableIsShown.value = !it } + .must { _ -> mutableIsLoading.value = false } + } + + fun promiseSideLoadingMarkedShown(): Promise { + mutableIsShown.value = false + return tutorials.promiseTutorialMarked(TutorialManager.KnownTutorials.droidSideLoadingWarning) + } +} diff --git a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/tutorials/TutorialManager.kt b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/tutorials/TutorialManager.kt index 1f910fb6da..d02d681993 100644 --- a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/tutorials/TutorialManager.kt +++ b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/tutorials/TutorialManager.kt @@ -14,6 +14,7 @@ class TutorialManager(private val context: Context, private val tutorialCache: C object KnownTutorials { const val longPressListTutorial = "longPressListTutorial" const val adjustNotificationInApplicationSettingsTutorial = "adjustNotificationInApplicationSettings" + const val droidSideLoadingWarning = "droidSideLoadingWarning" } companion object { @@ -50,7 +51,7 @@ class TutorialManager(private val context: Context, private val tutorialCache: C } private fun promiseTutorial(tutorialKey: String): Promise = - promiseTableMessage { + promiseTableMessage { RepositoryAccessHelper(context).use { h -> h.beginNonExclusiveTransaction().use { h.mapSql("SELECT * FROM $tableName WHERE $tutorialKeyColumn = @$tutorialKeyColumn") diff --git a/projectBlueWater/src/main/res/drawable/information_outline_36dp.xml b/projectBlueWater/src/main/res/drawable/information_outline_36dp.xml new file mode 100644 index 0000000000..0e3f124990 --- /dev/null +++ b/projectBlueWater/src/main/res/drawable/information_outline_36dp.xml @@ -0,0 +1,10 @@ + + + + diff --git a/projectBlueWater/src/main/res/values/strings.xml b/projectBlueWater/src/main/res/values/strings.xml index 01950f5cfe..a606cbb6d4 100644 --- a/projectBlueWater/src/main/res/values/strings.xml +++ b/projectBlueWater/src/main/res/values/strings.xml @@ -1,6 +1,5 @@ - project blue %1$s logo Downloads @@ -170,4 +169,11 @@ Header Value Server Type + Edit Header + Starting in 2027, a silent update, nonconsensually pushed by Google, will **block every Android app** whose developer hasn\'t registered with Google, signed their contract, paid up, and handed over government ID. + +**Every app and every device, worldwide, with no opt-out.** + Keep Android Open! + More Info + Solutions