Skip to content

Commit

Permalink
Break behavior out into its own screen
Browse files Browse the repository at this point in the history
  • Loading branch information
pyamsoft committed Dec 31, 2024
1 parent 9712b68 commit 95404bf
Show file tree
Hide file tree
Showing 80 changed files with 1,442 additions and 709 deletions.
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ dependencies {
implementation("com.github.pyamsoft.pydroid:notify:${rootProject.extra["pydroid"]}")
implementation("com.github.pyamsoft.pydroid:ui:${rootProject.extra["pydroid"]}")

implementation(project(":behavior"))
implementation(project(":connections"))
implementation(project(":core"))
implementation(project(":info"))
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/java/com/pyamsoft/tetherfi/TetherFiComponent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import com.pyamsoft.pydroid.bus.internal.DefaultEventBus
import com.pyamsoft.pydroid.core.ThreadEnforcer
import com.pyamsoft.pydroid.notify.NotifyGuard
import com.pyamsoft.pydroid.ui.theme.Theming
import com.pyamsoft.tetherfi.behavior.BehaviorAppModule
import com.pyamsoft.tetherfi.core.CoreAppModule
import com.pyamsoft.tetherfi.core.InAppRatingPreferences
import com.pyamsoft.tetherfi.foreground.ForegroundServiceComponent
Expand All @@ -45,7 +46,6 @@ import com.pyamsoft.tetherfi.server.broadcast.BroadcastServerAppModule
import com.pyamsoft.tetherfi.service.ServiceAppModule
import com.pyamsoft.tetherfi.status.PermissionRequests
import com.pyamsoft.tetherfi.status.PermissionResponse
import com.pyamsoft.tetherfi.status.StatusAppModule
import com.pyamsoft.tetherfi.tile.ProxyTileActivity
import com.pyamsoft.tetherfi.tile.ProxyTileComponent
import com.pyamsoft.tetherfi.tile.ProxyTileService
Expand All @@ -71,7 +71,7 @@ import kotlinx.coroutines.CoroutineScope
BroadcastServerAppModule::class,
TileAppModule::class,
CoreAppModule::class,
StatusAppModule::class,
BehaviorAppModule::class,
],
)
internal interface TetherFiComponent {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2024 pyamsoft
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.pyamsoft.tetherfi.behavior

import androidx.annotation.CheckResult
import dagger.Subcomponent

@Subcomponent
internal interface BehaviorComponent {

fun inject(injector: BehaviorInjector)

@Subcomponent.Factory
interface Factory {

@CheckResult fun create(): BehaviorComponent
}
}
195 changes: 195 additions & 0 deletions app/src/main/java/com/pyamsoft/tetherfi/behavior/BehaviorEntry.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
/*
* Copyright 2024 pyamsoft
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.pyamsoft.tetherfi.behavior

import android.provider.Settings
import androidx.activity.ComponentActivity
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LifecycleEventEffect
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.pyamsoft.pydroid.arch.SaveStateDisposableEffect
import com.pyamsoft.pydroid.bus.EventBus
import com.pyamsoft.pydroid.bus.EventConsumer
import com.pyamsoft.pydroid.ui.inject.ComposableInjector
import com.pyamsoft.pydroid.ui.inject.rememberComposableInjector
import com.pyamsoft.pydroid.ui.util.rememberNotNull
import com.pyamsoft.tetherfi.ObjectGraph
import com.pyamsoft.tetherfi.status.PermissionRequests
import com.pyamsoft.tetherfi.status.PermissionResponse
import com.pyamsoft.tetherfi.ui.ServerViewState
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch

internal class BehaviorInjector : ComposableInjector() {

@JvmField @Inject internal var viewModel: BehaviorViewModeler? = null

@JvmField @Inject internal var permissionRequestBus: EventBus<PermissionRequests>? = null

@JvmField @Inject internal var permissionResponseBus: EventConsumer<PermissionResponse>? = null

override fun onInject(activity: ComponentActivity) {
ObjectGraph.ActivityScope.retrieve(activity).plusBehavior().create().inject(this)
}

override fun onDispose() {
viewModel = null
permissionRequestBus = null
permissionResponseBus = null
}
}

/** Sets up permission request interaction */
@Composable
private fun RegisterPermissionRequests(
permissionResponseBus: Flow<PermissionResponse>,
onRefreshSystemInfo: CoroutineScope.() -> Unit,
) {
// Create requesters
val handleRefreshSystemInfo by rememberUpdatedState(onRefreshSystemInfo)

LaunchedEffect(
permissionResponseBus,
) {
// See MainActivity
permissionResponseBus.flowOn(context = Dispatchers.Default).also { f ->
launch(context = Dispatchers.Default) {
f.collect { resp ->
when (resp) {
is PermissionResponse.RefreshNotification -> {
// Call to the VM to refresh info
handleRefreshSystemInfo(this)
}
is PermissionResponse.ToggleProxy -> {
// Blank
}
}
}
}
}
}
}

/** On mount hooks */
@Composable
private fun MountHooks(
viewModel: BehaviorViewModeler,
permissionResponseBus: Flow<PermissionResponse>,
onRefreshConnection: () -> Unit
) {
val scope = rememberCoroutineScope()

val handleRefreshSystemInfo by rememberUpdatedState { s: CoroutineScope ->
viewModel.handleRefreshSystemInfo(scope = s)
}

// As early as possible because of Lifecycle quirks
RegisterPermissionRequests(
permissionResponseBus = permissionResponseBus,
onRefreshSystemInfo = { handleRefreshSystemInfo(this) },
)

SaveStateDisposableEffect(viewModel)

LaunchedEffect(
viewModel,
) {
viewModel.bind(scope = this)
}

val handleRefreshConnectionInfo by rememberUpdatedState { onRefreshConnection() }
val bindLifecycleResumed by rememberUpdatedState {
viewModel.bindLifecycleResumed(
scope = scope,
onRefreshConnectionInfo = { handleRefreshConnectionInfo() },
)
}
LifecycleEventEffect(
event = Lifecycle.Event.ON_RESUME,
) {
bindLifecycleResumed()
}
}

@Composable
fun BehaviorEntry(
modifier: Modifier = Modifier,
appName: String,
serverViewState: ServerViewState,

// Actions
onRefreshConnection: () -> Unit,
onLaunchIntent: (String) -> Unit,
) {
val component = rememberComposableInjector { BehaviorInjector() }
val viewModel = rememberNotNull(component.viewModel)
val permissionRequestBus = rememberNotNull(component.permissionRequestBus)
val permissionResponseBus = rememberNotNull(component.permissionResponseBus)

// Use the LifecycleOwner.CoroutineScope (Activity usually)
// so that the scope does not die because of navigation events
val owner = LocalLifecycleOwner.current
val lifecycleScope = owner.lifecycleScope

// Hooks that run on mount
MountHooks(
viewModel = viewModel,
permissionResponseBus = permissionResponseBus,
onRefreshConnection = onRefreshConnection,
)

BehaviorScreen(
modifier = modifier,
state = viewModel,
serverViewState = serverViewState,
appName = appName,
onOpenBatterySettings = {
onLaunchIntent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS)
},
onRequestNotificationPermission = {
lifecycleScope.launch(context = Dispatchers.Default) {
// See MainActivity
permissionRequestBus.emit(PermissionRequests.Notification)
}
},
onToggleIgnoreVpn = { viewModel.handleToggleTweak(BehaviorViewTweaks.IGNORE_VPN) },
onToggleIgnoreLocation = { viewModel.handleToggleTweak(BehaviorViewTweaks.IGNORE_LOCATION) },
onToggleShutdownWithNoClients = {
viewModel.handleToggleTweak(BehaviorViewTweaks.SHUTDOWN_NO_CLIENTS)
},
onToggleKeepScreenOn = { viewModel.handleToggleTweak(BehaviorViewTweaks.KEEP_SCREEN_ON) },
onShowPowerBalance = { viewModel.handleOpenDialog(BehaviorViewDialogs.POWER_BALANCE) },
onHidePowerBalance = { viewModel.handleCloseDialog(BehaviorViewDialogs.POWER_BALANCE) },
onUpdatePowerBalance = { viewModel.handleUpdatePowerBalance(it) },
onSelectBroadcastType = { viewModel.handleUpdateBroadcastType(it) },
onSelectPreferredNetwork = { viewModel.handleUpdatePreferredNetwork(it) },
onHideSocketTimeout = { viewModel.handleCloseDialog(BehaviorViewDialogs.SOCKET_TIMEOUT) },
onShowSocketTimeout = { viewModel.handleOpenDialog(BehaviorViewDialogs.SOCKET_TIMEOUT) },
onUpdateSocketTimeout = { viewModel.handleUpdateSocketTimeout(it) },
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ import com.pyamsoft.pydroid.ui.util.fillUpToPortraitSize
import com.pyamsoft.tetherfi.ObjectGraph
import com.pyamsoft.tetherfi.R
import com.pyamsoft.tetherfi.TetherFiTheme
import com.pyamsoft.tetherfi.behavior.tweaks.ScreenOnHandler
import com.pyamsoft.tetherfi.core.Timber
import com.pyamsoft.tetherfi.getSystemDarkMode
import com.pyamsoft.tetherfi.service.ServiceLauncher
import com.pyamsoft.tetherfi.status.tweaks.ScreenOnHandler
import com.pyamsoft.tetherfi.tile.ProxyTileService
import com.pyamsoft.tetherfi.ui.InstallPYDroidExtras
import com.pyamsoft.tetherfi.ui.LANDSCAPE_MAX_WIDTH
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/java/com/pyamsoft/tetherfi/main/MainComponent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.pyamsoft.tetherfi.main

import androidx.annotation.CheckResult
import com.pyamsoft.tetherfi.behavior.BehaviorComponent
import com.pyamsoft.tetherfi.connections.ConnectionComponent
import com.pyamsoft.tetherfi.core.ActivityScope
import com.pyamsoft.tetherfi.info.InfoComponent
Expand All @@ -28,6 +29,8 @@ import dagger.Subcomponent
@ActivityScope
internal interface MainComponent {

@CheckResult fun plusBehavior(): BehaviorComponent.Factory

@CheckResult fun plusStatus(): StatusComponent.Factory

@CheckResult fun plusConnection(): ConnectionComponent.Factory
Expand Down
11 changes: 10 additions & 1 deletion app/src/main/java/com/pyamsoft/tetherfi/main/MainContent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import androidx.compose.foundation.pager.PagerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import com.pyamsoft.tetherfi.behavior.BehaviorEntry
import com.pyamsoft.tetherfi.connections.ConnectionEntry
import com.pyamsoft.tetherfi.core.FeatureFlags
import com.pyamsoft.tetherfi.info.InfoEntry
Expand Down Expand Up @@ -78,6 +79,15 @@ fun MainContent(
onShowSlowSpeedHelp = onShowSlowSpeedHelp,
)
}
MainView.BEHAVIOR -> {
BehaviorEntry(
modifier = Modifier.fillMaxSize(),
appName = appName,
serverViewState = state,
onRefreshConnection = onRefreshConnection,
onLaunchIntent = onLaunchIntent,
)
}
MainView.STATUS -> {
StatusEntry(
modifier = Modifier.fillMaxSize(),
Expand All @@ -88,7 +98,6 @@ fun MainContent(
onRefreshConnection = onRefreshConnection,
onJumpToHowTo = onJumpToHowTo,
onUpdateTile = onUpdateTile,
onLaunchIntent = onLaunchIntent,
onShowSlowSpeedHelp = onShowSlowSpeedHelp,
onToggleProxy = onToggleProxy,
onOpenNetworkError = onOpenNetworkError,
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/pyamsoft/tetherfi/main/MainEntry.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ import com.pyamsoft.tetherfi.settings.SettingsDialog
import com.pyamsoft.tetherfi.status.PermissionRequests
import com.pyamsoft.tetherfi.status.PermissionResponse
import com.pyamsoft.tetherfi.ui.LANDSCAPE_MAX_WIDTH
import com.pyamsoft.tetherfi.ui.SlowSpeedsDialog
import com.pyamsoft.tetherfi.ui.dialog.SlowSpeedsDialog
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/pyamsoft/tetherfi/qr/QRCodeEntry.kt
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ import com.pyamsoft.pydroid.ui.inject.ComposableInjector
import com.pyamsoft.pydroid.ui.inject.rememberComposableInjector
import com.pyamsoft.pydroid.ui.util.rememberNotNull
import com.pyamsoft.tetherfi.ObjectGraph
import com.pyamsoft.tetherfi.ui.DialogToolbar
import com.pyamsoft.tetherfi.ui.R
import com.pyamsoft.tetherfi.ui.dialog.DialogToolbar
import com.pyamsoft.tetherfi.ui.qr.QRCodeScreen
import com.pyamsoft.tetherfi.ui.qr.QRCodeViewModeler
import com.pyamsoft.tetherfi.ui.qr.QRCodeViewState
Expand Down
Loading

0 comments on commit 95404bf

Please sign in to comment.