4
4
package com.tailscale.ipn.ui.view
5
5
6
6
import android.text.format.Formatter
7
+ import androidx.compose.foundation.focusable
7
8
import androidx.compose.foundation.layout.Arrangement
8
9
import androidx.compose.foundation.layout.Column
9
10
import androidx.compose.foundation.layout.Row
@@ -19,10 +20,14 @@ import androidx.compose.material3.MaterialTheme
19
20
import androidx.compose.material3.Scaffold
20
21
import androidx.compose.material3.Text
21
22
import androidx.compose.runtime.Composable
23
+ import androidx.compose.runtime.LaunchedEffect
22
24
import androidx.compose.runtime.collectAsState
23
25
import androidx.compose.runtime.getValue
26
+ import androidx.compose.runtime.remember
24
27
import androidx.compose.ui.Alignment
25
28
import androidx.compose.ui.Modifier
29
+ import androidx.compose.ui.focus.FocusRequester
30
+ import androidx.compose.ui.focus.focusRequester
26
31
import androidx.compose.ui.platform.LocalContext
27
32
import androidx.compose.ui.res.painterResource
28
33
import androidx.compose.ui.res.stringResource
@@ -36,6 +41,7 @@ import com.tailscale.ipn.ui.util.Lists.SectionDivider
36
41
import com.tailscale.ipn.ui.util.set
37
42
import com.tailscale.ipn.ui.viewModel.TaildropViewModel
38
43
import com.tailscale.ipn.ui.viewModel.TaildropViewModelFactory
44
+ import com.tailscale.ipn.util.TSLog
39
45
import kotlinx.coroutines.CoroutineScope
40
46
import kotlinx.coroutines.flow.StateFlow
41
47
@@ -46,36 +52,44 @@ fun TaildropView(
46
52
viewModel : TaildropViewModel =
47
53
viewModel(factory = TaildropViewModelFactory (requestedTransfers, applicationScope))
48
54
) {
49
- Scaffold (
50
- contentWindowInsets = WindowInsets .Companion .statusBars,
51
- topBar = { Header (R .string.share) }) { paddingInsets ->
52
- val showDialog = viewModel.showDialog.collectAsState().value
55
+ val TAG = " TaildropView"
56
+ val focusRequester = remember { FocusRequester () }
53
57
54
- // Show the error overlay
55
- showDialog?.let { ErrorDialog (type = it, action = { viewModel.showDialog.set(null ) }) }
58
+ // Automatically request focus when the composable is displayed
59
+ LaunchedEffect (Unit ) {
60
+ try {
61
+ focusRequester.requestFocus()
62
+ } catch (e: Exception ) {
63
+ TSLog .w(TAG , " Focus request failed: ${e.message} " )
64
+ }
65
+ }
56
66
57
- Column (modifier = Modifier .padding(paddingInsets) ) {
58
- FileShareHeader (
59
- fileTransfers = requestedTransfers.collectAsState().value,
60
- totalSize = viewModel.totalSize)
67
+ Scaffold (contentWindowInsets = WindowInsets .statusBars, topBar = { Header ( R .string.share) } ) {
68
+ paddingInsets ->
69
+ Column (modifier = Modifier .focusRequester(focusRequester).focusable().padding(paddingInsets)) {
70
+ val showDialog = viewModel.showDialog.collectAsState().value
61
71
62
- when (viewModel.state.collectAsState().value) {
63
- Ipn .State .Running -> {
64
- val peers by viewModel.myPeers.collectAsState()
65
- val context = LocalContext .current
66
- FileSharePeerList (
67
- peers = peers,
68
- stateViewGenerator = { peerId ->
69
- viewModel.TrailingContentForPeer (peerId = peerId)
70
- },
71
- onShare = { viewModel.share(context, it) })
72
- }
73
- else -> {
74
- FileShareConnectView { viewModel.startVPN() }
75
- }
76
- }
72
+ showDialog?.let { ErrorDialog (type = it, action = { viewModel.showDialog.set(null ) }) }
73
+
74
+ FileShareHeader (
75
+ fileTransfers = requestedTransfers.collectAsState().value,
76
+ totalSize = viewModel.totalSize)
77
+
78
+ when (viewModel.state.collectAsState().value) {
79
+ Ipn .State .Running -> {
80
+ val peers by viewModel.myPeers.collectAsState()
81
+ val context = LocalContext .current
82
+ FileSharePeerList (
83
+ peers = peers,
84
+ stateViewGenerator = { peerId -> viewModel.TrailingContentForPeer (peerId = peerId) },
85
+ onShare = { viewModel.share(context, it) })
86
+ }
87
+ else -> {
88
+ FileShareConnectView { viewModel.startVPN() }
77
89
}
78
90
}
91
+ }
92
+ }
79
93
}
80
94
81
95
@Composable
0 commit comments