12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679 |
- import 'dart:async';
- import 'dart:io';
- import 'dart:math';
- import 'package:extended_text/extended_text.dart';
- import 'package:flutter_hbb/desktop/widgets/dragable_divider.dart';
- import 'package:percent_indicator/percent_indicator.dart';
- import 'package:desktop_drop/desktop_drop.dart';
- import 'package:flutter/gestures.dart';
- import 'package:flutter/material.dart';
- import 'package:flutter/services.dart';
- import 'package:flutter_breadcrumb/flutter_breadcrumb.dart';
- import 'package:flutter_hbb/desktop/widgets/list_search_action_listener.dart';
- import 'package:flutter_hbb/desktop/widgets/menu_button.dart';
- import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
- import 'package:flutter_hbb/models/file_model.dart';
- import 'package:flutter_svg/flutter_svg.dart';
- import 'package:get/get.dart';
- import 'package:wakelock_plus/wakelock_plus.dart';
- import 'package:flutter_hbb/web/dummy.dart'
- if (dart.library.html) 'package:flutter_hbb/web/web_unique.dart';
- import '../../consts.dart';
- import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu;
- import '../../common.dart';
- import '../../models/model.dart';
- import '../../models/platform_model.dart';
- import '../widgets/popup_menu.dart';
- /// status of location bar
- enum LocationStatus {
- /// normal bread crumb bar
- bread,
- /// show path text field
- pathLocation,
- /// show file search bar text field
- fileSearchBar
- }
- /// The status of currently focused scope of the mouse
- enum MouseFocusScope {
- /// Mouse is in local field.
- local,
- /// Mouse is in remote field.
- remote,
- /// Mouse is not in local field, remote neither.
- none
- }
- class FileManagerPage extends StatefulWidget {
- const FileManagerPage(
- {Key? key,
- required this.id,
- required this.password,
- required this.isSharedPassword,
- this.tabController,
- this.connToken,
- this.forceRelay})
- : super(key: key);
- final String id;
- final String? password;
- final bool? isSharedPassword;
- final bool? forceRelay;
- final String? connToken;
- final DesktopTabController? tabController;
- @override
- State<StatefulWidget> createState() => _FileManagerPageState();
- }
- class _FileManagerPageState extends State<FileManagerPage>
- with AutomaticKeepAliveClientMixin, WidgetsBindingObserver {
- final _mouseFocusScope = Rx<MouseFocusScope>(MouseFocusScope.none);
- final _dropMaskVisible = false.obs; // TODO impl drop mask
- final _overlayKeyState = OverlayKeyState();
- late FFI _ffi;
- FileModel get model => _ffi.fileModel;
- JobController get jobController => model.jobController;
- @override
- void initState() {
- super.initState();
- _ffi = FFI(null);
- _ffi.start(widget.id,
- isFileTransfer: true,
- password: widget.password,
- isSharedPassword: widget.isSharedPassword,
- connToken: widget.connToken,
- forceRelay: widget.forceRelay);
- WidgetsBinding.instance.addPostFrameCallback((_) {
- _ffi.dialogManager
- .showLoading(translate('Connecting...'), onCancel: closeConnection);
- });
- Get.put<FFI>(_ffi, tag: 'ft_${widget.id}');
- if (!isLinux) {
- WakelockPlus.enable();
- }
- if (isWeb) {
- _ffi.ffiModel.updateEventListener(_ffi.sessionId, widget.id);
- }
- debugPrint("File manager page init success with id ${widget.id}");
- _ffi.dialogManager.setOverlayState(_overlayKeyState);
- // Call onSelected in post frame callback, since we cannot guarantee that the callback will not call setState.
- WidgetsBinding.instance.addPostFrameCallback((_) {
- widget.tabController?.onSelected?.call(widget.id);
- });
- WidgetsBinding.instance.addObserver(this);
- }
- @override
- void dispose() {
- model.close().whenComplete(() {
- _ffi.close();
- _ffi.dialogManager.dismissAll();
- if (!isLinux) {
- WakelockPlus.disable();
- }
- Get.delete<FFI>(tag: 'ft_${widget.id}');
- });
- WidgetsBinding.instance.removeObserver(this);
- super.dispose();
- }
- @override
- bool get wantKeepAlive => true;
- @override
- void didChangeAppLifecycleState(AppLifecycleState state) {
- super.didChangeAppLifecycleState(state);
- if (state == AppLifecycleState.resumed) {
- jobController.jobTable.refresh();
- }
- }
- @override
- Widget build(BuildContext context) {
- super.build(context);
- return Overlay(key: _overlayKeyState.key, initialEntries: [
- OverlayEntry(builder: (_) {
- return Scaffold(
- backgroundColor: Theme.of(context).scaffoldBackgroundColor,
- body: Row(
- children: [
- if (!isWeb)
- Flexible(
- flex: 3,
- child: dropArea(FileManagerView(
- model.localController, _ffi, _mouseFocusScope))),
- Flexible(
- flex: 3,
- child: dropArea(FileManagerView(
- model.remoteController, _ffi, _mouseFocusScope))),
- Flexible(flex: 2, child: statusList())
- ],
- ),
- );
- })
- ]);
- }
- Widget dropArea(FileManagerView fileView) {
- return DropTarget(
- onDragDone: (detail) =>
- handleDragDone(detail, fileView.controller.isLocal),
- onDragEntered: (enter) {
- _dropMaskVisible.value = true;
- },
- onDragExited: (exit) {
- _dropMaskVisible.value = false;
- },
- child: fileView);
- }
- Widget generateCard(Widget child) {
- return Container(
- decoration: BoxDecoration(
- color: Theme.of(context).cardColor,
- borderRadius: BorderRadius.all(
- Radius.circular(15.0),
- ),
- ),
- child: child,
- );
- }
- /// transfer status list
- /// watch transfer status
- Widget statusList() {
- Widget getIcon(JobProgress job) {
- final color = Theme.of(context).tabBarTheme.labelColor;
- switch (job.type) {
- case JobType.deleteDir:
- case JobType.deleteFile:
- return Icon(Icons.delete_outline, color: color);
- default:
- return Transform.rotate(
- angle: isWeb
- ? job.isRemoteToLocal
- ? pi / 2
- : pi / 2 * 3
- : job.isRemoteToLocal
- ? pi
- : 0,
- child: Icon(Icons.arrow_forward_ios, color: color),
- );
- }
- }
- statusListView(List<JobProgress> jobs) => ListView.builder(
- controller: ScrollController(),
- itemBuilder: (BuildContext context, int index) {
- final item = jobs[index];
- final status = item.getStatus();
- return Padding(
- padding: const EdgeInsets.only(bottom: 5),
- child: generateCard(
- Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- Row(
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- getIcon(item)
- .marginSymmetric(horizontal: 10, vertical: 12),
- Expanded(
- child: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Tooltip(
- waitDuration: Duration(milliseconds: 500),
- message: item.jobName,
- child: ExtendedText(
- item.jobName,
- maxLines: 1,
- overflow: TextOverflow.ellipsis,
- overflowWidget: TextOverflowWidget(
- child: Text("..."),
- position: TextOverflowPosition.start),
- ),
- ),
- Tooltip(
- waitDuration: Duration(milliseconds: 500),
- message: status,
- child: Text(status,
- style: TextStyle(
- fontSize: 12,
- color: MyTheme.darkGray,
- )).marginOnly(top: 6),
- ),
- Offstage(
- offstage: item.type != JobType.transfer ||
- item.state != JobState.inProgress,
- child: LinearPercentIndicator(
- animateFromLastPercent: true,
- center: Text(
- '${(item.finishedSize / item.totalSize * 100).toStringAsFixed(0)}%',
- ),
- barRadius: Radius.circular(15),
- percent: item.finishedSize / item.totalSize,
- progressColor: MyTheme.accent,
- backgroundColor: Theme.of(context).hoverColor,
- lineHeight: kDesktopFileTransferRowHeight,
- ).paddingSymmetric(vertical: 8),
- ),
- ],
- ),
- ),
- Row(
- mainAxisAlignment: MainAxisAlignment.end,
- children: [
- Offstage(
- offstage: item.state != JobState.paused,
- child: MenuButton(
- tooltip: translate("Resume"),
- onPressed: () {
- jobController.resumeJob(item.id);
- },
- child: SvgPicture.asset(
- "assets/refresh.svg",
- colorFilter: svgColor(Colors.white),
- ),
- color: MyTheme.accent,
- hoverColor: MyTheme.accent80,
- ),
- ),
- MenuButton(
- tooltip: translate("Delete"),
- child: SvgPicture.asset(
- "assets/close.svg",
- colorFilter: svgColor(Colors.white),
- ),
- onPressed: () {
- jobController.jobTable.removeAt(index);
- jobController.cancelJob(item.id);
- },
- color: MyTheme.accent,
- hoverColor: MyTheme.accent80,
- ),
- ],
- ).marginAll(12),
- ],
- ),
- ],
- ),
- ),
- );
- },
- itemCount: jobController.jobTable.length,
- );
- return PreferredSize(
- preferredSize: const Size(200, double.infinity),
- child: Container(
- margin: const EdgeInsets.only(top: 16.0, bottom: 16.0, right: 16.0),
- padding: const EdgeInsets.all(8.0),
- child: Obx(
- () => jobController.jobTable.isEmpty
- ? generateCard(
- Center(
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- SvgPicture.asset(
- "assets/transfer.svg",
- colorFilter: svgColor(
- Theme.of(context).tabBarTheme.labelColor),
- height: 40,
- ).paddingOnly(bottom: 10),
- Text(
- translate("No transfers in progress"),
- textAlign: TextAlign.center,
- textScaler: TextScaler.linear(1.20),
- style: TextStyle(
- color:
- Theme.of(context).tabBarTheme.labelColor),
- ),
- ],
- ),
- ),
- )
- : statusListView(jobController.jobTable),
- )),
- );
- }
- void handleDragDone(DropDoneDetails details, bool isLocal) {
- if (isLocal) {
- // ignore local
- return;
- }
- final items = SelectedItems(isLocal: false);
- for (var file in details.files) {
- final f = File(file.path);
- items.add(Entry()
- ..path = file.path
- ..name = file.name
- ..size = FileSystemEntity.isDirectorySync(f.path) ? 0 : f.lengthSync());
- }
- final otherSideData = model.localController.directoryData();
- model.remoteController.sendFiles(items, otherSideData);
- }
- }
- class FileManagerView extends StatefulWidget {
- final FileController controller;
- final FFI _ffi;
- final Rx<MouseFocusScope> _mouseFocusScope;
- FileManagerView(this.controller, this._ffi, this._mouseFocusScope);
- @override
- State<StatefulWidget> createState() => _FileManagerViewState();
- }
- class _FileManagerViewState extends State<FileManagerView> {
- final _locationStatus = LocationStatus.bread.obs;
- final _locationNode = FocusNode();
- final _locationBarKey = GlobalKey();
- final _searchText = "".obs;
- final _breadCrumbScroller = ScrollController();
- final _keyboardNode = FocusNode();
- final _listSearchBuffer = TimeoutStringBuffer();
- final _nameColWidth = 0.0.obs;
- final _modifiedColWidth = 0.0.obs;
- final _sizeColWidth = 0.0.obs;
- final _fileListScrollController = ScrollController();
- final _globalHeaderKey = GlobalKey();
- /// [_lastClickTime], [_lastClickEntry] help to handle double click
- var _lastClickTime =
- DateTime.now().millisecondsSinceEpoch - bind.getDoubleClickTime() - 1000;
- Entry? _lastClickEntry;
- double? _windowWidthPrev;
- double _fileTransferMinimumWidth = 0.0;
- FileController get controller => widget.controller;
- bool get isLocal => widget.controller.isLocal;
- FFI get _ffi => widget._ffi;
- SelectedItems get selectedItems => controller.selectedItems;
- @override
- void initState() {
- super.initState();
- // register location listener
- _locationNode.addListener(onLocationFocusChanged);
- controller.directory.listen((e) => breadCrumbScrollToEnd());
- }
- @override
- void dispose() {
- _locationNode.removeListener(onLocationFocusChanged);
- _locationNode.dispose();
- _keyboardNode.dispose();
- _breadCrumbScroller.dispose();
- _fileListScrollController.dispose();
- super.dispose();
- }
- @override
- Widget build(BuildContext context) {
- _handleColumnPorportions();
- return Container(
- margin: const EdgeInsets.all(16.0),
- padding: const EdgeInsets.all(8.0),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- headTools(),
- Expanded(
- child: Row(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Expanded(
- child: MouseRegion(
- onEnter: (evt) {
- widget._mouseFocusScope.value = isLocal
- ? MouseFocusScope.local
- : MouseFocusScope.remote;
- _keyboardNode.requestFocus();
- },
- onExit: (evt) =>
- widget._mouseFocusScope.value = MouseFocusScope.none,
- child: _buildFileList(context, _fileListScrollController),
- ))
- ],
- ),
- ),
- ],
- ),
- );
- }
- void _handleColumnPorportions() {
- final windowWidthNow = MediaQuery.of(context).size.width;
- if (_windowWidthPrev == null) {
- _windowWidthPrev = windowWidthNow;
- final defaultColumnWidth = windowWidthNow * 0.115;
- _fileTransferMinimumWidth = defaultColumnWidth / 3;
- _nameColWidth.value = defaultColumnWidth;
- _modifiedColWidth.value = defaultColumnWidth;
- _sizeColWidth.value = defaultColumnWidth;
- }
- if (_windowWidthPrev != windowWidthNow) {
- final difference = windowWidthNow / _windowWidthPrev!;
- _windowWidthPrev = windowWidthNow;
- _fileTransferMinimumWidth *= difference;
- _nameColWidth.value *= difference;
- _modifiedColWidth.value *= difference;
- _sizeColWidth.value *= difference;
- }
- }
- void onLocationFocusChanged() {
- debugPrint("focus changed on local");
- if (_locationNode.hasFocus) {
- // ignore
- } else {
- // lost focus, change to bread
- if (_locationStatus.value != LocationStatus.fileSearchBar) {
- _locationStatus.value = LocationStatus.bread;
- }
- }
- }
- Widget headTools() {
- var uploadButtonTapPosition = RelativeRect.fill;
- RxBool isUploadFolder =
- (bind.mainGetLocalOption(key: 'upload-folder-button') == 'Y').obs;
- return Container(
- child: Column(
- children: [
- // symbols
- PreferredSize(
- child: Row(
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- Container(
- width: 50,
- height: 50,
- decoration: BoxDecoration(
- borderRadius: BorderRadius.all(Radius.circular(8)),
- color: MyTheme.accent,
- ),
- padding: EdgeInsets.all(8.0),
- child: FutureBuilder<String>(
- future: bind.sessionGetPlatform(
- sessionId: _ffi.sessionId,
- isRemote: !isLocal),
- builder: (context, snapshot) {
- if (snapshot.hasData &&
- snapshot.data!.isNotEmpty) {
- return getPlatformImage('${snapshot.data}');
- } else {
- return CircularProgressIndicator(
- color: Theme.of(context)
- .tabBarTheme
- .labelColor,
- );
- }
- })),
- Text(isLocal
- ? translate("Local Computer")
- : translate("Remote Computer"))
- .marginOnly(left: 8.0)
- ],
- ),
- preferredSize: Size(double.infinity, 70))
- .paddingOnly(bottom: 15),
- // buttons
- Row(
- children: [
- Row(
- children: [
- MenuButton(
- tooltip: translate('Back'),
- padding: EdgeInsets.only(
- right: 3,
- ),
- child: RotatedBox(
- quarterTurns: 2,
- child: SvgPicture.asset(
- "assets/arrow.svg",
- colorFilter:
- svgColor(Theme.of(context).tabBarTheme.labelColor),
- ),
- ),
- color: Theme.of(context).cardColor,
- hoverColor: Theme.of(context).hoverColor,
- onPressed: () {
- selectedItems.clear();
- controller.goBack();
- },
- ),
- MenuButton(
- tooltip: translate('Parent directory'),
- child: RotatedBox(
- quarterTurns: 3,
- child: SvgPicture.asset(
- "assets/arrow.svg",
- colorFilter:
- svgColor(Theme.of(context).tabBarTheme.labelColor),
- ),
- ),
- color: Theme.of(context).cardColor,
- hoverColor: Theme.of(context).hoverColor,
- onPressed: () {
- selectedItems.clear();
- controller.goToParentDirectory();
- },
- ),
- ],
- ),
- Expanded(
- child: Padding(
- padding: const EdgeInsets.symmetric(horizontal: 3.0),
- child: Container(
- decoration: BoxDecoration(
- color: Theme.of(context).cardColor,
- borderRadius: BorderRadius.all(
- Radius.circular(8.0),
- ),
- ),
- child: Padding(
- padding: EdgeInsets.symmetric(vertical: 2.5),
- child: GestureDetector(
- onTap: () {
- _locationStatus.value =
- _locationStatus.value == LocationStatus.bread
- ? LocationStatus.pathLocation
- : LocationStatus.bread;
- Future.delayed(Duration.zero, () {
- if (_locationStatus.value ==
- LocationStatus.pathLocation) {
- _locationNode.requestFocus();
- }
- });
- },
- child: Obx(
- () => Container(
- child: Row(
- children: [
- Expanded(
- child: _locationStatus.value ==
- LocationStatus.bread
- ? buildBread()
- : buildPathLocation()),
- ],
- ),
- ),
- ),
- ),
- ),
- ),
- ),
- ),
- Obx(() {
- switch (_locationStatus.value) {
- case LocationStatus.bread:
- return MenuButton(
- tooltip: translate('Search'),
- onPressed: () {
- _locationStatus.value = LocationStatus.fileSearchBar;
- Future.delayed(
- Duration.zero, () => _locationNode.requestFocus());
- },
- child: SvgPicture.asset(
- "assets/search.svg",
- colorFilter:
- svgColor(Theme.of(context).tabBarTheme.labelColor),
- ),
- color: Theme.of(context).cardColor,
- hoverColor: Theme.of(context).hoverColor,
- );
- case LocationStatus.pathLocation:
- return MenuButton(
- onPressed: null,
- child: SvgPicture.asset(
- "assets/close.svg",
- colorFilter:
- svgColor(Theme.of(context).tabBarTheme.labelColor),
- ),
- color: Theme.of(context).disabledColor,
- hoverColor: Theme.of(context).hoverColor,
- );
- case LocationStatus.fileSearchBar:
- return MenuButton(
- tooltip: translate('Clear'),
- onPressed: () {
- onSearchText("", isLocal);
- _locationStatus.value = LocationStatus.bread;
- },
- child: SvgPicture.asset(
- "assets/close.svg",
- colorFilter:
- svgColor(Theme.of(context).tabBarTheme.labelColor),
- ),
- color: Theme.of(context).cardColor,
- hoverColor: Theme.of(context).hoverColor,
- );
- }
- }),
- MenuButton(
- tooltip: translate('Refresh File'),
- padding: EdgeInsets.only(
- left: 3,
- ),
- onPressed: () {
- controller.refresh();
- },
- child: SvgPicture.asset(
- "assets/refresh.svg",
- colorFilter:
- svgColor(Theme.of(context).tabBarTheme.labelColor),
- ),
- color: Theme.of(context).cardColor,
- hoverColor: Theme.of(context).hoverColor,
- ),
- ],
- ),
- Row(
- textDirection: isLocal ? TextDirection.ltr : TextDirection.rtl,
- children: [
- Expanded(
- child: Row(
- mainAxisAlignment:
- isLocal ? MainAxisAlignment.start : MainAxisAlignment.end,
- children: [
- MenuButton(
- tooltip: translate('Home'),
- padding: EdgeInsets.only(
- right: 3,
- ),
- onPressed: () {
- controller.goToHomeDirectory();
- },
- child: SvgPicture.asset(
- "assets/home.svg",
- colorFilter:
- svgColor(Theme.of(context).tabBarTheme.labelColor),
- ),
- color: Theme.of(context).cardColor,
- hoverColor: Theme.of(context).hoverColor,
- ),
- MenuButton(
- tooltip: translate('Create Folder'),
- onPressed: () {
- final name = TextEditingController();
- String? errorText;
- _ffi.dialogManager.show((setState, close, context) {
- name.addListener(() {
- if (errorText != null) {
- setState(() {
- errorText = null;
- });
- }
- });
- submit() {
- if (name.value.text.isNotEmpty) {
- if (!PathUtil.validName(name.value.text,
- controller.options.value.isWindows)) {
- setState(() {
- errorText = translate("Invalid folder name");
- });
- return;
- }
- controller.createDir(PathUtil.join(
- controller.directory.value.path,
- name.value.text,
- controller.options.value.isWindows,
- ));
- close();
- }
- }
- cancel() => close(false);
- return CustomAlertDialog(
- title: Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- SvgPicture.asset("assets/folder_new.svg",
- colorFilter: svgColor(MyTheme.accent)),
- Text(
- translate("Create Folder"),
- ).paddingOnly(
- left: 10,
- ),
- ],
- ),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- TextFormField(
- decoration: InputDecoration(
- labelText: translate(
- "Please enter the folder name",
- ),
- errorText: errorText,
- ),
- controller: name,
- autofocus: true,
- ).workaroundFreezeLinuxMint(),
- ],
- ),
- actions: [
- dialogButton(
- "Cancel",
- icon: Icon(Icons.close_rounded),
- onPressed: cancel,
- isOutline: true,
- ),
- dialogButton(
- "Ok",
- icon: Icon(Icons.done_rounded),
- onPressed: submit,
- ),
- ],
- onSubmit: submit,
- onCancel: cancel,
- );
- });
- },
- child: SvgPicture.asset(
- "assets/folder_new.svg",
- colorFilter:
- svgColor(Theme.of(context).tabBarTheme.labelColor),
- ),
- color: Theme.of(context).cardColor,
- hoverColor: Theme.of(context).hoverColor,
- ),
- Obx(() => MenuButton(
- tooltip: translate('Delete'),
- onPressed: SelectedItems.valid(selectedItems.items)
- ? () async {
- await (controller
- .removeAction(selectedItems));
- selectedItems.clear();
- }
- : null,
- child: SvgPicture.asset(
- "assets/trash.svg",
- colorFilter: svgColor(
- Theme.of(context).tabBarTheme.labelColor),
- ),
- color: Theme.of(context).cardColor,
- hoverColor: Theme.of(context).hoverColor,
- )),
- menu(isLocal: isLocal),
- ],
- ),
- ),
- if (isWeb)
- Obx(() => ElevatedButton.icon(
- style: ButtonStyle(
- padding: MaterialStateProperty.all<EdgeInsetsGeometry>(
- isLocal
- ? EdgeInsets.only(left: 10)
- : EdgeInsets.only(right: 10)),
- backgroundColor: MaterialStateProperty.all(
- selectedItems.items.isEmpty
- ? MyTheme.accent80
- : MyTheme.accent,
- ),
- ),
- onPressed: () =>
- {webselectFiles(is_folder: isUploadFolder.value)},
- label: InkWell(
- hoverColor: Colors.transparent,
- splashColor: Colors.transparent,
- highlightColor: Colors.transparent,
- focusColor: Colors.transparent,
- onTapDown: (e) {
- final x = e.globalPosition.dx;
- final y = e.globalPosition.dy;
- uploadButtonTapPosition =
- RelativeRect.fromLTRB(x, y, x, y);
- },
- onTap: () async {
- final value = await showMenu<bool>(
- context: context,
- position: uploadButtonTapPosition,
- items: [
- PopupMenuItem<bool>(
- value: false,
- child: Text(translate('Upload files')),
- ),
- PopupMenuItem<bool>(
- value: true,
- child: Text(translate('Upload folder')),
- ),
- ]);
- if (value != null) {
- isUploadFolder.value = value;
- bind.mainSetLocalOption(
- key: 'upload-folder-button',
- value: value ? 'Y' : '');
- webselectFiles(is_folder: value);
- }
- },
- child: Icon(Icons.arrow_drop_down),
- ),
- icon: Text(
- translate(isUploadFolder.isTrue
- ? 'Upload folder'
- : 'Upload files'),
- textAlign: TextAlign.right,
- style: TextStyle(
- color: Colors.white,
- ),
- ).marginOnly(left: 8),
- )).marginOnly(left: 16),
- Obx(() => ElevatedButton.icon(
- style: ButtonStyle(
- padding: MaterialStateProperty.all<EdgeInsetsGeometry>(
- isLocal
- ? EdgeInsets.only(left: 10)
- : EdgeInsets.only(right: 10)),
- backgroundColor: MaterialStateProperty.all(
- selectedItems.items.isEmpty
- ? MyTheme.accent80
- : MyTheme.accent,
- ),
- ),
- onPressed: SelectedItems.valid(selectedItems.items)
- ? () {
- final otherSideData =
- controller.getOtherSideDirectoryData();
- controller.sendFiles(selectedItems, otherSideData);
- selectedItems.clear();
- }
- : null,
- icon: isLocal
- ? Text(
- translate('Send'),
- textAlign: TextAlign.right,
- style: TextStyle(
- color: selectedItems.items.isEmpty
- ? Theme.of(context).brightness ==
- Brightness.light
- ? MyTheme.grayBg
- : MyTheme.darkGray
- : Colors.white,
- ),
- )
- : isWeb
- ? Offstage()
- : RotatedBox(
- quarterTurns: 2,
- child: SvgPicture.asset(
- "assets/arrow.svg",
- colorFilter: svgColor(
- selectedItems.items.isEmpty
- ? Theme.of(context).brightness ==
- Brightness.light
- ? MyTheme.grayBg
- : MyTheme.darkGray
- : Colors.white),
- alignment: Alignment.bottomRight,
- ),
- ),
- label: isLocal
- ? SvgPicture.asset(
- "assets/arrow.svg",
- colorFilter: svgColor(selectedItems.items.isEmpty
- ? Theme.of(context).brightness ==
- Brightness.light
- ? MyTheme.grayBg
- : MyTheme.darkGray
- : Colors.white),
- )
- : Text(
- translate(isWeb ? 'Download' : 'Receive'),
- style: TextStyle(
- color: selectedItems.items.isEmpty
- ? Theme.of(context).brightness ==
- Brightness.light
- ? MyTheme.grayBg
- : MyTheme.darkGray
- : Colors.white,
- ),
- ),
- )),
- ],
- ).marginOnly(top: 8.0)
- ],
- ),
- );
- }
- Widget menu({bool isLocal = false}) {
- var menuPos = RelativeRect.fill;
- final List<MenuEntryBase<String>> items = [
- MenuEntrySwitch<String>(
- switchType: SwitchType.scheckbox,
- text: translate("Show Hidden Files"),
- getter: () async {
- return controller.options.value.showHidden;
- },
- setter: (bool v) async {
- controller.toggleShowHidden();
- },
- padding: kDesktopMenuPadding,
- dismissOnClicked: true,
- ),
- MenuEntryButton(
- childBuilder: (style) => Text(translate("Select All"), style: style),
- proc: () => setState(() =>
- selectedItems.selectAll(controller.directory.value.entries)),
- padding: kDesktopMenuPadding,
- dismissOnClicked: true),
- MenuEntryButton(
- childBuilder: (style) =>
- Text(translate("Unselect All"), style: style),
- proc: () => selectedItems.clear(),
- padding: kDesktopMenuPadding,
- dismissOnClicked: true)
- ];
- return Listener(
- onPointerDown: (e) {
- final x = e.position.dx;
- final y = e.position.dy;
- menuPos = RelativeRect.fromLTRB(x, y, x, y);
- },
- child: MenuButton(
- tooltip: translate('More'),
- onPressed: () => mod_menu.showMenu(
- context: context,
- position: menuPos,
- items: items
- .map(
- (e) => e.build(
- context,
- MenuConfig(
- commonColor: CustomPopupMenuTheme.commonColor,
- height: CustomPopupMenuTheme.height,
- dividerHeight: CustomPopupMenuTheme.dividerHeight),
- ),
- )
- .expand((i) => i)
- .toList(),
- elevation: 8,
- ),
- child: SvgPicture.asset(
- "assets/dots.svg",
- colorFilter: svgColor(Theme.of(context).tabBarTheme.labelColor),
- ),
- color: Theme.of(context).cardColor,
- hoverColor: Theme.of(context).hoverColor,
- ),
- );
- }
- Widget _buildFileList(
- BuildContext context, ScrollController scrollController) {
- final fd = controller.directory.value;
- final entries = fd.entries;
- Rx<Entry?> rightClickEntry = Rx(null);
- return ListSearchActionListener(
- node: _keyboardNode,
- buffer: _listSearchBuffer,
- onNext: (buffer) {
- debugPrint("searching next for $buffer");
- assert(buffer.length == 1);
- assert(selectedItems.items.length <= 1);
- var skipCount = 0;
- if (selectedItems.items.isNotEmpty) {
- final index = entries.indexOf(selectedItems.items.first);
- if (index < 0) {
- return;
- }
- skipCount = index + 1;
- }
- var searchResult = entries
- .skip(skipCount)
- .where((element) => element.name.toLowerCase().startsWith(buffer));
- if (searchResult.isEmpty) {
- // cannot find next, lets restart search from head
- debugPrint("restart search from head");
- searchResult = entries.where(
- (element) => element.name.toLowerCase().startsWith(buffer));
- }
- if (searchResult.isEmpty) {
- selectedItems.clear();
- return;
- }
- _jumpToEntry(isLocal, searchResult.first, scrollController,
- kDesktopFileTransferRowHeight);
- },
- onSearch: (buffer) {
- debugPrint("searching for $buffer");
- final selectedEntries = selectedItems;
- final searchResult = entries
- .where((element) => element.name.toLowerCase().startsWith(buffer));
- selectedEntries.clear();
- if (searchResult.isEmpty) {
- selectedItems.clear();
- return;
- }
- _jumpToEntry(isLocal, searchResult.first, scrollController,
- kDesktopFileTransferRowHeight);
- },
- child: Obx(() {
- final entries = controller.directory.value.entries;
- final filteredEntries = _searchText.isNotEmpty
- ? entries.where((element) {
- return element.name.contains(_searchText.value);
- }).toList(growable: false)
- : entries;
- final rows = filteredEntries.map((entry) {
- final sizeStr =
- entry.isFile ? readableFileSize(entry.size.toDouble()) : "";
- final lastModifiedStr = entry.isDrive
- ? " "
- : "${entry.lastModified().toString().replaceAll(".000", "")} ";
- var secondaryPosition = RelativeRect.fromLTRB(0, 0, 0, 0);
- onTap() {
- final items = selectedItems;
- // handle double click
- if (_checkDoubleClick(entry)) {
- controller.openDirectory(entry.path);
- items.clear();
- return;
- }
- _onSelectedChanged(items, filteredEntries, entry, isLocal);
- }
- onSecondaryTap() {
- final items = [
- if (!entry.isDrive &&
- versionCmp(_ffi.ffiModel.pi.version, "1.3.0") >= 0)
- mod_menu.PopupMenuItem(
- child: Text(translate("Rename")),
- height: CustomPopupMenuTheme.height,
- onTap: () {
- controller.renameAction(entry, isLocal);
- },
- )
- ];
- if (items.isNotEmpty) {
- rightClickEntry.value = entry;
- final future = mod_menu.showMenu(
- context: context,
- position: secondaryPosition,
- items: items,
- );
- future.then((value) {
- rightClickEntry.value = null;
- });
- future.onError((error, stackTrace) {
- rightClickEntry.value = null;
- });
- }
- }
- onSecondaryTapDown(details) {
- secondaryPosition = RelativeRect.fromLTRB(
- details.globalPosition.dx,
- details.globalPosition.dy,
- details.globalPosition.dx,
- details.globalPosition.dy);
- }
- return Padding(
- padding: EdgeInsets.symmetric(vertical: 1),
- child: Obx(() => Container(
- decoration: BoxDecoration(
- color: selectedItems.items.contains(entry)
- ? MyTheme.button
- : Theme.of(context).cardColor,
- borderRadius: BorderRadius.all(
- Radius.circular(5.0),
- ),
- border: rightClickEntry.value == entry
- ? Border.all(
- color: MyTheme.button,
- width: 1.0,
- )
- : null,
- ),
- key: ValueKey(entry.name),
- height: kDesktopFileTransferRowHeight,
- child: Column(
- mainAxisAlignment: MainAxisAlignment.spaceAround,
- children: [
- Expanded(
- child: InkWell(
- child: Row(
- children: [
- GestureDetector(
- child: Obx(
- () => Container(
- width: _nameColWidth.value,
- child: Tooltip(
- waitDuration: Duration(milliseconds: 500),
- message: entry.name,
- child: Row(children: [
- entry.isDrive
- ? Image(
- image: iconHardDrive,
- fit: BoxFit.scaleDown,
- color: Theme.of(context)
- .iconTheme
- .color
- ?.withOpacity(0.7))
- .paddingAll(4)
- : SvgPicture.asset(
- entry.isFile
- ? "assets/file.svg"
- : "assets/folder.svg",
- colorFilter: svgColor(
- Theme.of(context)
- .tabBarTheme
- .labelColor),
- ),
- Expanded(
- child: Text(entry.name.nonBreaking,
- style: TextStyle(
- color: selectedItems.items
- .contains(entry)
- ? Colors.white
- : null),
- overflow:
- TextOverflow.ellipsis))
- ]),
- )),
- ),
- onTap: onTap,
- onSecondaryTap: onSecondaryTap,
- onSecondaryTapDown: onSecondaryTapDown,
- ),
- SizedBox(
- width: 2.0,
- ),
- GestureDetector(
- child: Obx(
- () => SizedBox(
- width: _modifiedColWidth.value,
- child: Tooltip(
- waitDuration: Duration(milliseconds: 500),
- message: lastModifiedStr,
- child: Text(
- lastModifiedStr,
- overflow: TextOverflow.ellipsis,
- style: TextStyle(
- fontSize: 12,
- color: selectedItems.items
- .contains(entry)
- ? Colors.white70
- : MyTheme.darkGray,
- ),
- )),
- ),
- ),
- onTap: onTap,
- onSecondaryTap: onSecondaryTap,
- onSecondaryTapDown: onSecondaryTapDown,
- ),
- // Divider from header.
- SizedBox(
- width: 2.0,
- ),
- Expanded(
- // width: 100,
- child: GestureDetector(
- child: Tooltip(
- waitDuration: Duration(milliseconds: 500),
- message: sizeStr,
- child: Text(
- sizeStr,
- overflow: TextOverflow.ellipsis,
- style: TextStyle(
- fontSize: 10,
- color:
- selectedItems.items.contains(entry)
- ? Colors.white70
- : MyTheme.darkGray),
- ),
- ),
- onTap: onTap,
- onSecondaryTap: onSecondaryTap,
- onSecondaryTapDown: onSecondaryTapDown,
- ),
- ),
- ],
- ),
- ),
- ),
- ],
- ))),
- );
- }).toList(growable: false);
- return Column(
- children: [
- // Header
- Row(
- children: [
- Expanded(child: _buildFileBrowserHeader(context)),
- ],
- ),
- // Body
- Expanded(
- child: ListView.builder(
- controller: scrollController,
- itemExtent: kDesktopFileTransferRowHeight,
- itemBuilder: (context, index) {
- return rows[index];
- },
- itemCount: rows.length,
- ),
- ),
- ],
- );
- }),
- );
- }
- onSearchText(String searchText, bool isLocal) {
- selectedItems.clear();
- _searchText.value = searchText;
- }
- void _jumpToEntry(bool isLocal, Entry entry,
- ScrollController scrollController, double rowHeight) {
- final entries = controller.directory.value.entries;
- final index = entries.indexOf(entry);
- if (index == -1) {
- debugPrint("entry is not valid: ${entry.path}");
- }
- final selectedEntries = selectedItems;
- final searchResult = entries.where((element) => element == entry);
- selectedEntries.clear();
- if (searchResult.isEmpty) {
- return;
- }
- final offset = min(
- max(scrollController.position.minScrollExtent,
- entries.indexOf(searchResult.first) * rowHeight),
- scrollController.position.maxScrollExtent);
- scrollController.jumpTo(offset);
- selectedEntries.add(searchResult.first);
- debugPrint("focused on ${searchResult.first.name}");
- }
- void _onSelectedChanged(SelectedItems selectedItems, List<Entry> entries,
- Entry entry, bool isLocal) {
- final isCtrlDown = RawKeyboard.instance.keysPressed
- .contains(LogicalKeyboardKey.controlLeft) ||
- RawKeyboard.instance.keysPressed
- .contains(LogicalKeyboardKey.controlRight);
- final isShiftDown = RawKeyboard.instance.keysPressed
- .contains(LogicalKeyboardKey.shiftLeft) ||
- RawKeyboard.instance.keysPressed
- .contains(LogicalKeyboardKey.shiftRight);
- if (isCtrlDown) {
- if (selectedItems.items.contains(entry)) {
- selectedItems.remove(entry);
- } else {
- selectedItems.add(entry);
- }
- } else if (isShiftDown) {
- final List<int> indexGroup = [];
- for (var selected in selectedItems.items) {
- indexGroup.add(entries.indexOf(selected));
- }
- indexGroup.add(entries.indexOf(entry));
- indexGroup.removeWhere((e) => e == -1);
- final maxIndex = indexGroup.reduce(max);
- final minIndex = indexGroup.reduce(min);
- selectedItems.clear();
- entries
- .getRange(minIndex, maxIndex + 1)
- .forEach((e) => selectedItems.add(e));
- } else {
- selectedItems.clear();
- selectedItems.add(entry);
- }
- setState(() {});
- }
- bool _checkDoubleClick(Entry entry) {
- final current = DateTime.now().millisecondsSinceEpoch;
- final elapsed = current - _lastClickTime;
- _lastClickTime = current;
- if (_lastClickEntry == entry) {
- if (elapsed < bind.getDoubleClickTime()) {
- return true;
- }
- } else {
- _lastClickEntry = entry;
- }
- return false;
- }
- void _onDrag(double dx, RxDouble column1, RxDouble column2) {
- if (column1.value + dx <= _fileTransferMinimumWidth ||
- column2.value - dx <= _fileTransferMinimumWidth) {
- return;
- }
- column1.value += dx;
- column2.value -= dx;
- column1.value = max(_fileTransferMinimumWidth, column1.value);
- column2.value = max(_fileTransferMinimumWidth, column2.value);
- }
- Widget _buildFileBrowserHeader(BuildContext context) {
- final padding = EdgeInsets.all(1.0);
- return SizedBox(
- key: _globalHeaderKey,
- height: kDesktopFileTransferHeaderHeight,
- child: Row(
- children: [
- Obx(
- () => headerItemFunc(
- _nameColWidth.value, SortBy.name, translate("Name")),
- ),
- DraggableDivider(
- axis: Axis.vertical,
- onPointerMove: (dx) =>
- _onDrag(dx, _nameColWidth, _modifiedColWidth),
- padding: padding,
- ),
- Obx(
- () => headerItemFunc(_modifiedColWidth.value, SortBy.modified,
- translate("Modified")),
- ),
- DraggableDivider(
- axis: Axis.vertical,
- onPointerMove: (dx) =>
- _onDrag(dx, _modifiedColWidth, _sizeColWidth),
- padding: padding),
- Expanded(
- child: headerItemFunc(
- _sizeColWidth.value, SortBy.size, translate("Size")))
- ],
- ),
- );
- }
- Widget headerItemFunc(double? width, SortBy sortBy, String name) {
- final headerTextStyle =
- Theme.of(context).dataTableTheme.headingTextStyle ?? TextStyle();
- return ObxValue<Rx<bool?>>(
- (ascending) => InkWell(
- onTap: () {
- if (ascending.value == null) {
- ascending.value = true;
- } else {
- ascending.value = !ascending.value!;
- }
- controller.changeSortStyle(sortBy,
- isLocal: isLocal, ascending: ascending.value!);
- },
- child: SizedBox(
- width: width,
- height: kDesktopFileTransferHeaderHeight,
- child: Row(
- children: [
- Expanded(
- child: Text(
- name,
- style: headerTextStyle,
- overflow: TextOverflow.ellipsis,
- ).marginOnly(left: 4),
- ),
- ascending.value != null
- ? Icon(
- ascending.value!
- ? Icons.keyboard_arrow_up_rounded
- : Icons.keyboard_arrow_down_rounded,
- )
- : SizedBox()
- ],
- ),
- ),
- ), () {
- if (controller.sortBy.value == sortBy) {
- return controller.sortAscending.obs;
- } else {
- return Rx<bool?>(null);
- }
- }());
- }
- Widget buildBread() {
- final items = getPathBreadCrumbItems(isLocal, (list) {
- var path = "";
- for (var item in list) {
- path = PathUtil.join(path, item, controller.options.value.isWindows);
- }
- controller.openDirectory(path);
- });
- return items.isEmpty
- ? Offstage()
- : Row(
- key: _locationBarKey,
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Expanded(
- child: Listener(
- // handle mouse wheel
- onPointerSignal: (e) {
- if (e is PointerScrollEvent) {
- final sc = _breadCrumbScroller;
- final scale = isWindows ? 2 : 4;
- sc.jumpTo(sc.offset + e.scrollDelta.dy / scale);
- }
- },
- child: BreadCrumb(
- items: items,
- divider: const Icon(Icons.keyboard_arrow_right_rounded),
- overflow: ScrollableOverflow(
- controller: _breadCrumbScroller,
- ),
- ),
- ),
- ),
- ActionIcon(
- message: "",
- icon: Icons.keyboard_arrow_down_rounded,
- onTap: () async {
- final renderBox = _locationBarKey.currentContext
- ?.findRenderObject() as RenderBox;
- _locationBarKey.currentContext?.size;
- final size = renderBox.size;
- final offset = renderBox.localToGlobal(Offset.zero);
- final x = offset.dx;
- final y = offset.dy + size.height + 1;
- final isPeerWindows = controller.options.value.isWindows;
- final List<MenuEntryBase> menuItems = [
- MenuEntryButton(
- childBuilder: (TextStyle? style) => isPeerWindows
- ? buildWindowsThisPC(context, style)
- : Text(
- '/',
- style: style,
- ),
- proc: () {
- controller.openDirectory('/');
- },
- dismissOnClicked: true),
- MenuEntryDivider()
- ];
- if (isPeerWindows) {
- var loadingTag = "";
- if (!isLocal) {
- loadingTag = _ffi.dialogManager.showLoading("Waiting");
- }
- try {
- final showHidden = controller.options.value.showHidden;
- final fd = await controller.fileFetcher
- .fetchDirectory("/", isLocal, showHidden);
- for (var entry in fd.entries) {
- menuItems.add(MenuEntryButton(
- childBuilder: (TextStyle? style) =>
- Row(children: [
- Image(
- image: iconHardDrive,
- fit: BoxFit.scaleDown,
- color: Theme.of(context)
- .iconTheme
- .color
- ?.withOpacity(0.7)),
- SizedBox(width: 10),
- Text(
- entry.name,
- style: style,
- )
- ]),
- proc: () {
- controller.openDirectory('${entry.name}\\');
- },
- dismissOnClicked: true));
- }
- menuItems.add(MenuEntryDivider());
- } catch (e) {
- debugPrint("buildBread fetchDirectory err=$e");
- } finally {
- if (!isLocal) {
- _ffi.dialogManager.dismissByTag(loadingTag);
- }
- }
- }
- mod_menu.showMenu(
- context: context,
- position: RelativeRect.fromLTRB(x, y, x, y),
- elevation: 4,
- items: menuItems
- .map((e) => e.build(
- context,
- MenuConfig(
- commonColor:
- CustomPopupMenuTheme.commonColor,
- height: CustomPopupMenuTheme.height,
- dividerHeight:
- CustomPopupMenuTheme.dividerHeight,
- boxWidth: size.width)))
- .expand((i) => i)
- .toList());
- },
- iconSize: 20,
- )
- ]);
- }
- List<BreadCrumbItem> getPathBreadCrumbItems(
- bool isLocal, void Function(List<String>) onPressed) {
- final path = controller.directory.value.path;
- final breadCrumbList = List<BreadCrumbItem>.empty(growable: true);
- final isWindows = controller.options.value.isWindows;
- if (isWindows && path == '/') {
- breadCrumbList.add(BreadCrumbItem(
- content: TextButton(
- child: buildWindowsThisPC(context),
- style: ButtonStyle(
- minimumSize: MaterialStateProperty.all(Size(0, 0))),
- onPressed: () => onPressed(['/']))
- .marginSymmetric(horizontal: 4)));
- } else {
- final list = PathUtil.split(path, isWindows);
- breadCrumbList.addAll(
- list.asMap().entries.map(
- (e) => BreadCrumbItem(
- content: TextButton(
- child: Text(e.value),
- style: ButtonStyle(
- minimumSize: MaterialStateProperty.all(
- Size(0, 0),
- ),
- ),
- onPressed: () => onPressed(
- list.sublist(0, e.key + 1),
- ),
- ).marginSymmetric(horizontal: 4),
- ),
- ),
- );
- }
- return breadCrumbList;
- }
- breadCrumbScrollToEnd() {
- Future.delayed(Duration(milliseconds: 200), () {
- if (_breadCrumbScroller.hasClients) {
- _breadCrumbScroller.animateTo(
- _breadCrumbScroller.position.maxScrollExtent,
- duration: Duration(milliseconds: 200),
- curve: Curves.fastLinearToSlowEaseIn);
- }
- });
- }
- Widget buildPathLocation() {
- final text = _locationStatus.value == LocationStatus.pathLocation
- ? controller.directory.value.path
- : _searchText.value;
- final textController = TextEditingController(text: text)
- ..selection = TextSelection.collapsed(offset: text.length);
- return Row(
- children: [
- SvgPicture.asset(
- _locationStatus.value == LocationStatus.pathLocation
- ? "assets/folder.svg"
- : "assets/search.svg",
- colorFilter: svgColor(Theme.of(context).tabBarTheme.labelColor),
- ),
- Expanded(
- child: TextField(
- focusNode: _locationNode,
- decoration: InputDecoration(
- border: InputBorder.none,
- isDense: true,
- prefix: Padding(
- padding: EdgeInsets.only(left: 4.0),
- ),
- ),
- controller: textController,
- onSubmitted: (path) {
- controller.openDirectory(path);
- },
- onChanged: _locationStatus.value == LocationStatus.fileSearchBar
- ? (searchText) => onSearchText(searchText, isLocal)
- : null,
- ).workaroundFreezeLinuxMint(),
- )
- ],
- );
- }
- // openDirectory(String path, {bool isLocal = false}) {
- // model.openDirectory(path, isLocal: isLocal);
- // }
- }
- Widget buildWindowsThisPC(BuildContext context, [TextStyle? textStyle]) {
- final color = Theme.of(context).iconTheme.color?.withOpacity(0.7);
- return Row(children: [
- Icon(Icons.computer, size: 20, color: color),
- SizedBox(width: 10),
- Text(translate('This PC'), style: textStyle)
- ]);
- }
|