integrate file search bar into path location

This commit is contained in:
csf 2022-10-13 10:17:20 +09:00
parent 5b1a12c6a7
commit baf437e6f0

View File

@ -14,7 +14,17 @@ import '../../common.dart';
import '../../models/model.dart'; import '../../models/model.dart';
import '../../models/platform_model.dart'; import '../../models/platform_model.dart';
enum LocationStatus { bread, textField } /// status of location bar
enum LocationStatus {
/// normal bread crumb bar
bread,
/// show path text field
pathLocation,
/// show file search bar text field
fileSearchBar
}
class FileManagerPage extends StatefulWidget { class FileManagerPage extends StatefulWidget {
const FileManagerPage({Key? key, required this.id}) : super(key: key); const FileManagerPage({Key? key, required this.id}) : super(key: key);
@ -40,7 +50,7 @@ class _FileManagerPageState extends State<FileManagerPage>
final _breadCrumbScrollerLocal = ScrollController(); final _breadCrumbScrollerLocal = ScrollController();
final _breadCrumbScrollerRemote = ScrollController(); final _breadCrumbScrollerRemote = ScrollController();
final _dropMaskVisible = false.obs; final _dropMaskVisible = false.obs; // TODO impl drop mask
ScrollController getBreadCrumbScrollController(bool isLocal) { ScrollController getBreadCrumbScrollController(bool isLocal) {
return isLocal ? _breadCrumbScrollerLocal : _breadCrumbScrollerRemote; return isLocal ? _breadCrumbScrollerLocal : _breadCrumbScrollerRemote;
@ -84,6 +94,8 @@ class _FileManagerPageState extends State<FileManagerPage>
Get.delete<FFI>(tag: 'ft_${widget.id}'); Get.delete<FFI>(tag: 'ft_${widget.id}');
_locationNodeLocal.removeListener(onLocalLocationFocusChanged); _locationNodeLocal.removeListener(onLocalLocationFocusChanged);
_locationNodeRemote.removeListener(onRemoteLocationFocusChanged); _locationNodeRemote.removeListener(onRemoteLocationFocusChanged);
_locationNodeLocal.dispose();
_locationNodeRemote.dispose();
super.dispose(); super.dispose();
} }
@ -96,23 +108,16 @@ class _FileManagerPageState extends State<FileManagerPage>
return ChangeNotifierProvider.value( return ChangeNotifierProvider.value(
value: _ffi.fileModel, value: _ffi.fileModel,
child: Consumer<FileModel>(builder: (context, model, child) { child: Consumer<FileModel>(builder: (context, model, child) {
return WillPopScope( return Scaffold(
onWillPop: () async { backgroundColor: Theme.of(context).backgroundColor,
if (model.selectMode) { body: Row(
model.toggleSelectMode(); children: [
} Flexible(flex: 3, child: body(isLocal: true)),
return false; Flexible(flex: 3, child: body(isLocal: false)),
}, Flexible(flex: 2, child: statusList())
child: Scaffold( ],
backgroundColor: Theme.of(context).backgroundColor, ),
body: Row( );
children: [
Flexible(flex: 3, child: body(isLocal: true)),
Flexible(flex: 3, child: body(isLocal: false)),
Flexible(flex: 2, child: statusList())
],
),
));
})); }));
}) })
]); ]);
@ -406,8 +411,6 @@ class _FileManagerPageState extends State<FileManagerPage>
final locationStatus = final locationStatus =
isLocal ? _locationStatusLocal : _locationStatusRemote; isLocal ? _locationStatusLocal : _locationStatusRemote;
final locationFocus = isLocal ? _locationNodeLocal : _locationNodeRemote; final locationFocus = isLocal ? _locationNodeLocal : _locationNodeRemote;
final searchTextObs = isLocal ? _searchTextLocal : _searchTextRemote;
final searchController = TextEditingController(text: searchTextObs.value);
return Container( return Container(
child: Column( child: Column(
children: [ children: [
@ -473,73 +476,62 @@ class _FileManagerPageState extends State<FileManagerPage>
onTap: () { onTap: () {
locationStatus.value = locationStatus.value =
locationStatus.value == LocationStatus.bread locationStatus.value == LocationStatus.bread
? LocationStatus.textField ? LocationStatus.pathLocation
: LocationStatus.bread; : LocationStatus.bread;
Future.delayed(Duration.zero, () { Future.delayed(Duration.zero, () {
if (locationStatus.value == LocationStatus.textField) { if (locationStatus.value == LocationStatus.pathLocation) {
locationFocus.requestFocus(); locationFocus.requestFocus();
} }
}); });
}, },
child: Container( child: Obx(() => Container(
decoration: decoration: BoxDecoration(
BoxDecoration(border: Border.all(color: Colors.black12)), border: Border.all(
color: locationStatus.value == LocationStatus.bread
? Colors.black12
: Theme.of(context)
.colorScheme
.primary
.withOpacity(0.5))),
child: Row( child: Row(
children: [ children: [
Expanded( Expanded(
child: Obx(() => child: locationStatus.value == LocationStatus.bread
locationStatus.value == LocationStatus.bread ? buildBread(isLocal)
? buildBread(isLocal) : buildPathLocation(isLocal)),
: buildPathLocation(isLocal))),
DropdownButton<String>(
isDense: true,
underline: Offstage(),
items: [
// TODO: favourite
DropdownMenuItem(
child: Text('/'),
value: '/',
)
],
onChanged: (path) {
if (path is String && path.isNotEmpty) {
openDirectory(path, isLocal: isLocal);
}
})
], ],
)), ))),
)), )),
PopupMenuButton( Obx(() {
tooltip: "", switch (locationStatus.value) {
itemBuilder: (context) => [ case LocationStatus.bread:
PopupMenuItem( return IconButton(
enabled: false, onPressed: () {
child: ConstrainedBox( locationStatus.value = LocationStatus.fileSearchBar;
constraints: BoxConstraints(minWidth: 200), final focusNode =
child: TextField( isLocal ? _locationNodeLocal : _locationNodeRemote;
textAlignVertical: TextAlignVertical.center, Future.delayed(
controller: searchController, Duration.zero, () => focusNode.requestFocus());
autofocus: true, },
decoration: InputDecoration( splashRadius: 20,
prefixIcon: Icon(Icons.search), icon: Icon(Icons.search));
suffix: IconButton( case LocationStatus.pathLocation:
icon: Icon(Icons.clear), return IconButton(
splashRadius: 20, color: Theme.of(context).disabledColor,
onPressed: () { onPressed: null,
searchController.clear(); splashRadius: 20,
searchTextObs.value = ""; icon: Icon(Icons.close));
Get.back(); case LocationStatus.fileSearchBar:
}, return IconButton(
), color: Theme.of(context).disabledColor,
), onPressed: () {
onChanged: (searchText) => onSearchText("", isLocal);
onSearchText(searchText, isLocal), locationStatus.value = LocationStatus.bread;
), },
)) splashRadius: 1,
], icon: Icon(Icons.close));
splashRadius: 20, }
icon: const Icon(Icons.search), }),
),
IconButton( IconButton(
onPressed: () { onPressed: () {
model.refresh(isLocal: isLocal); model.refresh(isLocal: isLocal);
@ -649,7 +641,9 @@ class _FileManagerPageState extends State<FileManagerPage>
// ignore // ignore
} else { } else {
// lost focus, change to bread // lost focus, change to bread
_locationStatusLocal.value = LocationStatus.bread; if (_locationStatusLocal.value != LocationStatus.fileSearchBar) {
_locationStatusLocal.value = LocationStatus.bread;
}
} }
} }
@ -659,7 +653,9 @@ class _FileManagerPageState extends State<FileManagerPage>
// ignore // ignore
} else { } else {
// lost focus, change to bread // lost focus, change to bread
_locationStatusRemote.value = LocationStatus.bread; if (_locationStatusRemote.value != LocationStatus.fileSearchBar) {
_locationStatusRemote.value = LocationStatus.bread;
}
} }
} }
@ -671,14 +667,33 @@ class _FileManagerPageState extends State<FileManagerPage>
} }
openDirectory(path, isLocal: isLocal); openDirectory(path, isLocal: isLocal);
}); });
breadCrumbScrollToEnd(isLocal);
return items.isEmpty return items.isEmpty
? Offstage() ? Offstage()
: BreadCrumb( : Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
items: items, Expanded(
divider: Text("/").paddingSymmetric(horizontal: 4.0), child: BreadCrumb(
overflow: ScrollableOverflow( items: items,
controller: getBreadCrumbScrollController(isLocal)), divider: Text("/").paddingSymmetric(horizontal: 4.0),
); overflow: ScrollableOverflow(
controller: getBreadCrumbScrollController(isLocal)),
)),
DropdownButton<String>(
isDense: true,
underline: Offstage(),
items: [
// TODO: favourite
DropdownMenuItem(
child: Text('/'),
value: '/',
)
],
onChanged: (path) {
if (path is String && path.isNotEmpty) {
openDirectory(path, isLocal: isLocal);
}
})
]);
} }
List<BreadCrumbItem> getPathBreadCrumbItems( List<BreadCrumbItem> getPathBreadCrumbItems(
@ -705,19 +720,37 @@ class _FileManagerPageState extends State<FileManagerPage>
} }
Widget buildPathLocation(bool isLocal) { Widget buildPathLocation(bool isLocal) {
return TextField( final searchTextObs = isLocal ? _searchTextLocal : _searchTextRemote;
focusNode: isLocal ? _locationNodeLocal : _locationNodeRemote, final locationStatus =
decoration: InputDecoration( isLocal ? _locationStatusLocal : _locationStatusRemote;
border: InputBorder.none, final focusNode = isLocal ? _locationNodeLocal : _locationNodeRemote;
isDense: true, return Row(children: [
prefix: Padding(padding: EdgeInsets.only(left: 4.0)), Icon(
), locationStatus.value == LocationStatus.pathLocation
controller: ? Icons.folder
TextEditingController(text: model.getCurrentDir(isLocal).path), : Icons.search,
onSubmitted: (path) { color: Theme.of(context).hintColor,
openDirectory(path, isLocal: isLocal); ).paddingSymmetric(horizontal: 2),
}, Expanded(
); child: TextField(
focusNode: focusNode,
decoration: InputDecoration(
border: InputBorder.none,
isDense: true,
prefix: Padding(padding: EdgeInsets.only(left: 4.0))),
controller: locationStatus.value == LocationStatus.pathLocation
? TextEditingController(text: model.getCurrentDir(isLocal).path)
: TextEditingController(text: searchTextObs.value)
..selection =
TextSelection.collapsed(offset: searchTextObs.value.length),
onSubmitted: (path) {
openDirectory(path, isLocal: isLocal);
},
onChanged: locationStatus.value == LocationStatus.fileSearchBar
? (searchText) => onSearchText(searchText, isLocal)
: null,
))
]);
} }
onSearchText(String searchText, bool isLocal) { onSearchText(String searchText, bool isLocal) {