1+ package eu.darken.butler.searcher.ui.search
2+
3+ import androidx.compose.foundation.layout.Arrangement
4+ import androidx.compose.foundation.layout.Column
5+ import androidx.compose.foundation.layout.Row
6+ import androidx.compose.foundation.layout.Spacer
7+ import androidx.compose.foundation.layout.fillMaxWidth
8+ import androidx.compose.foundation.layout.padding
9+ import androidx.compose.foundation.layout.size
10+ import androidx.compose.foundation.layout.width
11+ import androidx.compose.material.icons.Icons
12+ import androidx.compose.material.icons.twotone.Storage
13+ import androidx.compose.material.icons.twotone.Tune
14+ import androidx.compose.material3.Button
15+ import androidx.compose.material3.ButtonDefaults
16+ import androidx.compose.material3.Card
17+ import androidx.compose.material3.CardDefaults
18+ import androidx.compose.material3.Icon
19+ import androidx.compose.material3.MaterialTheme
20+ import androidx.compose.material3.Text
21+ import androidx.compose.runtime.Composable
22+ import androidx.compose.ui.Alignment
23+ import androidx.compose.ui.Modifier
24+ import androidx.compose.ui.platform.LocalContext
25+ import androidx.compose.ui.res.stringResource
26+ import androidx.compose.ui.text.font.FontFamily
27+ import androidx.compose.ui.text.style.TextAlign
28+ import androidx.compose.ui.unit.dp
29+ import eu.darken.butler.common.ca.toCaString
30+ import eu.darken.butler.common.compose.Preview2
31+ import eu.darken.butler.common.compose.PreviewWrapper
32+ import eu.darken.butler.common.files.APath
33+ import eu.darken.butler.common.files.RawPath
34+ import eu.darken.butler.common.permissions.Permission
35+ import eu.darken.butler.workspace.core.permissions.PermissionState
36+ import eu.darken.butler.workspace.core.permissions.SetupRequirement
37+
38+ @Composable
39+ fun PermissionSetupCard (
40+ searchPath : APath ,
41+ permissionState : PermissionState ,
42+ onOpenSetup : () -> Unit ,
43+ modifier : Modifier = Modifier ,
44+ ) {
45+ Card (
46+ modifier = modifier.fillMaxWidth(),
47+ colors = CardDefaults .cardColors(
48+ containerColor = MaterialTheme .colorScheme.errorContainer.copy(alpha = 0.3f )
49+ )
50+ ) {
51+ Column (
52+ modifier = Modifier
53+ .fillMaxWidth()
54+ .padding(16 .dp),
55+ ) {
56+ Row (
57+ modifier = Modifier .fillMaxWidth(),
58+ verticalAlignment = Alignment .CenterVertically ,
59+ horizontalArrangement = Arrangement .spacedBy(12 .dp)
60+ ) {
61+ val primaryPermission = permissionState.requirements.firstOrNull()?.permission
62+ Icon (
63+ imageVector = when (primaryPermission) {
64+ is Permission .MANAGE_EXTERNAL_STORAGE -> Icons .TwoTone .Storage
65+ is Permission .WRITE_EXTERNAL_STORAGE -> Icons .TwoTone .Storage
66+ else -> Icons .TwoTone .Storage
67+ },
68+ contentDescription = null ,
69+ modifier = Modifier .size(24 .dp),
70+ tint = MaterialTheme .colorScheme.error
71+ )
72+
73+ Text (
74+ text = stringResource(eu.darken.butler.common.R .string.setup_required_card_title),
75+ style = MaterialTheme .typography.titleSmall,
76+ color = MaterialTheme .colorScheme.error
77+ )
78+ }
79+
80+ Text (
81+ text = stringResource(eu.darken.butler.common.R .string.setup_required_card_body),
82+ style = MaterialTheme .typography.bodyMedium,
83+ color = MaterialTheme .colorScheme.onSurface.copy(alpha = 0.8f ),
84+ modifier = Modifier
85+ .fillMaxWidth()
86+ .padding(top = 8 .dp)
87+ )
88+
89+ Text (
90+ text = searchPath.path,
91+ style = MaterialTheme .typography.bodySmall,
92+ fontFamily = FontFamily .Monospace ,
93+ color = MaterialTheme .colorScheme.onSurface.copy(alpha = 0.6f ),
94+ textAlign = TextAlign .Center ,
95+ modifier = Modifier
96+ .fillMaxWidth()
97+ .padding(top = 8 .dp)
98+ )
99+
100+ // Setup requirement description if available
101+ val primaryRequirement = permissionState.requirements.firstOrNull()
102+ primaryRequirement?.let { requirement ->
103+ Text (
104+ text = requirement.description.get(LocalContext .current),
105+ style = MaterialTheme .typography.bodySmall,
106+ color = MaterialTheme .colorScheme.onSurface.copy(alpha = 0.7f ),
107+ modifier = Modifier
108+ .fillMaxWidth()
109+ .padding(top = 8 .dp)
110+ )
111+ }
112+
113+ Button (
114+ onClick = onOpenSetup,
115+ modifier = Modifier
116+ .fillMaxWidth()
117+ .padding(top = 16 .dp),
118+ colors = ButtonDefaults .buttonColors(
119+ containerColor = MaterialTheme .colorScheme.error
120+ )
121+ ) {
122+ Icon (
123+ imageVector = Icons .TwoTone .Tune ,
124+ contentDescription = null ,
125+ modifier = Modifier .size(16 .dp)
126+ )
127+ Spacer (modifier = Modifier .width(8 .dp))
128+ Text (
129+ text = stringResource(eu.darken.butler.common.R .string.setup_required_card_setup_action),
130+ style = MaterialTheme .typography.labelMedium
131+ )
132+ }
133+ }
134+ }
135+ }
136+
137+ @Preview2
138+ @Composable
139+ private fun PermissionSetupCardPreview () {
140+ PreviewWrapper {
141+ PermissionSetupCard (
142+ searchPath = RawPath .build(" /storage/emulated/0/Documents" ),
143+ permissionState = PermissionState (
144+ requirements = listOf (
145+ SetupRequirement (
146+ permission = Permission .MANAGE_EXTERNAL_STORAGE ,
147+ isRequired = true ,
148+ description = " Grant access to manage all files on device storage" .toCaString(),
149+ )
150+ ),
151+ hasSufficientPermissions = false ,
152+ missingCritical = listOf (Permission .MANAGE_EXTERNAL_STORAGE ),
153+ ),
154+ onOpenSetup = {},
155+ )
156+ }
157+ }
0 commit comments