1
1
package com.skyd.anivu.ui.fragment.filepicker
2
2
3
3
import android.os.Bundle
4
- import android.os.Environment
5
4
import android.view.LayoutInflater
6
5
import android.view.View
7
6
import android.view.ViewGroup
8
7
import androidx.activity.compose.BackHandler
8
+ import androidx.compose.animation.animateContentSize
9
9
import androidx.compose.foundation.clickable
10
+ import androidx.compose.foundation.horizontalScroll
10
11
import androidx.compose.foundation.layout.Column
11
12
import androidx.compose.foundation.layout.PaddingValues
13
+ import androidx.compose.foundation.layout.Row
12
14
import androidx.compose.foundation.layout.calculateEndPadding
13
15
import androidx.compose.foundation.layout.calculateStartPadding
14
16
import androidx.compose.foundation.layout.fillMaxWidth
15
17
import androidx.compose.foundation.layout.padding
16
18
import androidx.compose.foundation.lazy.LazyColumn
19
+ import androidx.compose.foundation.rememberScrollState
20
+ import androidx.compose.foundation.shape.RoundedCornerShape
17
21
import androidx.compose.material.icons.Icons
22
+ import androidx.compose.material.icons.automirrored.outlined.NavigateNext
18
23
import androidx.compose.material.icons.outlined.Close
19
24
import androidx.compose.material.icons.outlined.PhoneAndroid
20
25
import androidx.compose.material3.Button
21
26
import androidx.compose.material3.Icon
22
27
import androidx.compose.material3.ListItem
28
+ import androidx.compose.material3.MaterialTheme
23
29
import androidx.compose.material3.Scaffold
24
30
import androidx.compose.material3.SnackbarHost
25
31
import androidx.compose.material3.SnackbarHostState
26
32
import androidx.compose.material3.Text
27
33
import androidx.compose.material3.TopAppBarDefaults
28
34
import androidx.compose.material3.rememberTopAppBarState
29
35
import androidx.compose.runtime.Composable
36
+ import androidx.compose.runtime.LaunchedEffect
30
37
import androidx.compose.runtime.getValue
31
38
import androidx.compose.runtime.remember
39
+ import androidx.compose.ui.Alignment
32
40
import androidx.compose.ui.Modifier
41
+ import androidx.compose.ui.draw.clip
33
42
import androidx.compose.ui.input.nestedscroll.nestedScroll
34
43
import androidx.compose.ui.platform.LocalLayoutDirection
35
44
import androidx.compose.ui.res.painterResource
@@ -40,6 +49,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
40
49
import com.skyd.anivu.R
41
50
import com.skyd.anivu.base.BaseComposeFragment
42
51
import com.skyd.anivu.base.mvi.getDispatcher
52
+ import com.skyd.anivu.config.Const
43
53
import com.skyd.anivu.ext.findMainNavController
44
54
import com.skyd.anivu.ext.getMimeType
45
55
import com.skyd.anivu.ext.popBackStackWithLifecycle
@@ -51,7 +61,6 @@ import com.skyd.anivu.ui.local.LocalNavController
51
61
import com.skyd.anivu.util.fileicon.getFileIcon
52
62
import dagger.hilt.android.AndroidEntryPoint
53
63
import java.io.File
54
- import java.io.Serializable
55
64
56
65
57
66
@AndroidEntryPoint
@@ -61,39 +70,31 @@ class FilePickerFragment : BaseComposeFragment() {
61
70
container : ViewGroup ? ,
62
71
savedInstanceState : Bundle ?
63
72
): View = setContentBase {
64
- val callback = arguments?.getSerializable(CALLBACK_KEY ) as ? FilePickerCallback
65
73
val path = arguments?.getString(PATH_KEY )
66
74
val pickFolder = arguments?.getBoolean(PICK_FOLDER_KEY )
67
75
val extensionName = arguments?.getString(EXTENSION_NAME_KEY )
68
- if (callback == null || path == null || pickFolder == null || extensionName == null ) {
76
+ if (path == null || pickFolder == null || extensionName == null ) {
69
77
findMainNavController().popBackStackWithLifecycle()
70
78
} else {
71
79
FilePickerScreen (
72
80
path = path,
73
81
pickFolder = pickFolder,
74
82
extensionName = extensionName,
75
- onFilePicked = { callback.onFilePicked(it) }
76
83
)
77
84
}
78
85
}
79
86
}
80
87
81
88
const val PATH_KEY = " path"
82
- const val CALLBACK_KEY = " callback"
83
89
const val PICK_FOLDER_KEY = " pickFolder"
84
90
const val EXTENSION_NAME_KEY = " extensionName"
85
-
86
-
87
- fun interface FilePickerCallback : Serializable {
88
- fun onFilePicked (file : File )
89
- }
91
+ const val FILE_PICKER_NEW_PATH_KEY = " newPath"
90
92
91
93
@Composable
92
94
fun FilePickerScreen (
93
95
path : String ,
94
96
pickFolder : Boolean = false,
95
97
extensionName : String? = null,
96
- onFilePicked : (File ) -> Unit ,
97
98
viewModel : FilePickerViewModel = hiltViewModel(),
98
99
) {
99
100
val scrollBehavior = TopAppBarDefaults .pinnedScrollBehavior(rememberTopAppBarState())
@@ -112,8 +113,8 @@ fun FilePickerScreen(
112
113
val current = File (uiState.path)
113
114
val parent = current.parent
114
115
if (! parent.isNullOrBlank() &&
115
- uiState.path != Environment .getExternalStorageDirectory().absolutePath &&
116
- uiState.path.startsWith(Environment .getExternalStorageDirectory().absolutePath )
116
+ uiState.path != Const . INTERNAL_STORAGE &&
117
+ uiState.path.startsWith(Const . INTERNAL_STORAGE )
117
118
) {
118
119
dispatch(FilePickerIntent .NewLocation (parent))
119
120
} else {
@@ -137,15 +138,19 @@ fun FilePickerScreen(
137
138
},
138
139
actions = {
139
140
AniVuIconButton (
140
- onClick = { dispatch(FilePickerIntent .NewLocation (Environment .getExternalStorageDirectory().absolutePath )) },
141
+ onClick = { dispatch(FilePickerIntent .NewLocation (Const . INTERNAL_STORAGE )) },
141
142
imageVector = Icons .Outlined .PhoneAndroid ,
142
143
contentDescription = stringResource(id = R .string.file_picker_screen_internal_storage),
143
144
)
144
145
}
145
146
)
146
147
}
147
148
) { paddingValues ->
148
- Column {
149
+ Column (modifier = Modifier .padding(top = paddingValues.calculateTopPadding())) {
150
+ PathLevelIndication (
151
+ path = uiState.path,
152
+ onRouteTo = { dispatch(FilePickerIntent .NewLocation (it)) },
153
+ )
149
154
LazyColumn (
150
155
modifier = Modifier
151
156
.fillMaxWidth()
@@ -154,7 +159,6 @@ fun FilePickerScreen(
154
159
contentPadding = PaddingValues (
155
160
start = paddingValues.calculateStartPadding(LocalLayoutDirection .current),
156
161
end = paddingValues.calculateEndPadding(LocalLayoutDirection .current),
157
- top = paddingValues.calculateTopPadding(),
158
162
),
159
163
) {
160
164
(uiState.fileListState as ? FileListState .Success )?.list?.forEach { file ->
@@ -165,7 +169,9 @@ fun FilePickerScreen(
165
169
dispatch(FilePickerIntent .NewLocation (file.path))
166
170
} else {
167
171
if (! pickFolder) {
168
- onFilePicked(file)
172
+ navController.previousBackStackEntry
173
+ ?.savedStateHandle
174
+ ?.set(FILE_PICKER_NEW_PATH_KEY , file.absolutePath)
169
175
}
170
176
}
171
177
},
@@ -195,7 +201,9 @@ fun FilePickerScreen(
195
201
.padding(horizontal = 16 .dp)
196
202
.fillMaxWidth(),
197
203
onClick = {
198
- onFilePicked(File (uiState.path))
204
+ navController.previousBackStackEntry
205
+ ?.savedStateHandle
206
+ ?.set(FILE_PICKER_NEW_PATH_KEY , uiState.path)
199
207
navController.popBackStackWithLifecycle()
200
208
},
201
209
) {
@@ -212,3 +220,57 @@ fun FilePickerScreen(
212
220
}
213
221
}
214
222
}
223
+
224
+ @Composable
225
+ private fun PathLevelIndication (path : String , onRouteTo : (String ) -> Unit ) {
226
+ val scrollState = rememberScrollState()
227
+ LaunchedEffect (scrollState.maxValue) {
228
+ if (scrollState.canScrollForward) {
229
+ scrollState.animateScrollTo(scrollState.maxValue)
230
+ }
231
+ }
232
+ Row (
233
+ modifier = Modifier
234
+ .horizontalScroll(scrollState)
235
+ .padding(horizontal = 12 .dp)
236
+ .animateContentSize(),
237
+ verticalAlignment = Alignment .CenterVertically ,
238
+ ) {
239
+ val items = remember(path) {
240
+ mutableListOf<String >().apply {
241
+ var newPath = path
242
+ if (newPath.startsWith(Const .INTERNAL_STORAGE )) {
243
+ add(Const .INTERNAL_STORAGE )
244
+ newPath = newPath.removePrefix(Const .INTERNAL_STORAGE )
245
+ }
246
+ newPath.removeSurrounding(File .separator)
247
+ newPath.split(File .separator).forEach {
248
+ if (it.isNotBlank()) add(it)
249
+ }
250
+ }
251
+ }
252
+
253
+ items.forEachIndexed { index, item ->
254
+ Text (
255
+ modifier = Modifier
256
+ .clip(RoundedCornerShape (3 .dp))
257
+ .clickable {
258
+ onRouteTo(
259
+ items
260
+ .subList(0 , index + 1 )
261
+ .joinToString(File .separator)
262
+ )
263
+ }
264
+ .padding(horizontal = 6 .dp, vertical = 8 .dp),
265
+ text = item,
266
+ style = MaterialTheme .typography.labelLarge,
267
+ )
268
+ if (index != items.size - 1 ) {
269
+ Icon (
270
+ imageVector = Icons .AutoMirrored .Outlined .NavigateNext ,
271
+ contentDescription = null ,
272
+ )
273
+ }
274
+ }
275
+ }
276
+ }
0 commit comments