file manager redesign implementation

This commit is contained in:
NicKoehler 2023-02-22 22:13:21 +01:00
parent c831a38c83
commit 325077435c
No known key found for this signature in database
GPG Key ID: BAE01394EB51AC58
7 changed files with 652 additions and 431 deletions

View File

@ -152,7 +152,7 @@ class MyTheme {
static const Color canvasColor = Color(0xFF212121); static const Color canvasColor = Color(0xFF212121);
static const Color border = Color(0xFFCCCCCC); static const Color border = Color(0xFFCCCCCC);
static const Color idColor = Color(0xFF00B6F0); static const Color idColor = Color(0xFF00B6F0);
static const Color darkGray = Color(0xFFB9BABC); static const Color darkGray = Color.fromARGB(255, 148, 148, 148);
static const Color cmIdColor = Color(0xFF21790B); static const Color cmIdColor = Color(0xFF21790B);
static const Color dark = Colors.black87; static const Color dark = Colors.black87;
static const Color button = Color(0xFF2C8CFF); static const Color button = Color(0xFF2C8CFF);
@ -160,8 +160,9 @@ class MyTheme {
static ThemeData lightTheme = ThemeData( static ThemeData lightTheme = ThemeData(
brightness: Brightness.light, brightness: Brightness.light,
backgroundColor: Color(0xFFFFFFFF), backgroundColor: Color(0xFFEEEEEE),
scaffoldBackgroundColor: Color(0xFFEEEEEE), hoverColor: Color.fromARGB(255, 224, 224, 224),
scaffoldBackgroundColor: Color(0xFFFFFFFF),
textTheme: const TextTheme( textTheme: const TextTheme(
titleLarge: TextStyle(fontSize: 19, color: Colors.black87), titleLarge: TextStyle(fontSize: 19, color: Colors.black87),
titleSmall: TextStyle(fontSize: 14, color: Colors.black87), titleSmall: TextStyle(fontSize: 14, color: Colors.black87),
@ -169,6 +170,7 @@ class MyTheme {
bodyMedium: bodyMedium:
TextStyle(fontSize: 14, color: Colors.black87, height: 1.25), TextStyle(fontSize: 14, color: Colors.black87, height: 1.25),
labelLarge: TextStyle(fontSize: 16.0, color: MyTheme.accent80)), labelLarge: TextStyle(fontSize: 16.0, color: MyTheme.accent80)),
cardColor: Color(0xFFEEEEEE),
hintColor: Color(0xFFAAAAAA), hintColor: Color(0xFFAAAAAA),
primarySwatch: Colors.blue, primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity, visualDensity: VisualDensity.adaptivePlatformDensity,
@ -191,8 +193,9 @@ class MyTheme {
); );
static ThemeData darkTheme = ThemeData( static ThemeData darkTheme = ThemeData(
brightness: Brightness.dark, brightness: Brightness.dark,
backgroundColor: Color(0xFF252525), backgroundColor: Color(0xFF24252B),
scaffoldBackgroundColor: Color(0xFF141414), hoverColor: Color.fromARGB(255, 45, 46, 53),
scaffoldBackgroundColor: Color(0xFF18191E),
textTheme: const TextTheme( textTheme: const TextTheme(
titleLarge: TextStyle(fontSize: 19), titleLarge: TextStyle(fontSize: 19),
titleSmall: TextStyle(fontSize: 14), titleSmall: TextStyle(fontSize: 14),
@ -200,7 +203,7 @@ class MyTheme {
bodyMedium: TextStyle(fontSize: 14, height: 1.25), bodyMedium: TextStyle(fontSize: 14, height: 1.25),
labelLarge: TextStyle( labelLarge: TextStyle(
fontSize: 16.0, fontWeight: FontWeight.bold, color: accent80)), fontSize: 16.0, fontWeight: FontWeight.bold, color: accent80)),
cardColor: Color(0xFF252525), cardColor: Color(0xFF24252B),
primarySwatch: Colors.blue, primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity, visualDensity: VisualDensity.adaptivePlatformDensity,
tabBarTheme: const TabBarTheme( tabBarTheme: const TabBarTheme(
@ -217,9 +220,8 @@ class MyTheme {
style: ButtonStyle(splashFactory: NoSplash.splashFactory), style: ButtonStyle(splashFactory: NoSplash.splashFactory),
) )
: null, : null,
checkboxTheme: const CheckboxThemeData( checkboxTheme:
checkColor: MaterialStatePropertyAll(dark) const CheckboxThemeData(checkColor: MaterialStatePropertyAll(dark)),
),
).copyWith( ).copyWith(
extensions: <ThemeExtension<dynamic>>[ extensions: <ThemeExtension<dynamic>>[
ColorThemeExtension.dark, ColorThemeExtension.dark,

View File

@ -52,7 +52,7 @@ const int kDesktopMaxDisplayHeight = 1080;
const double kDesktopFileTransferNameColWidth = 200; const double kDesktopFileTransferNameColWidth = 200;
const double kDesktopFileTransferModifiedColWidth = 120; const double kDesktopFileTransferModifiedColWidth = 120;
const double kDesktopFileTransferRowHeight = 25.0; const double kDesktopFileTransferRowHeight = 30.0;
const double kDesktopFileTransferHeaderHeight = 25.0; const double kDesktopFileTransferHeaderHeight = 25.0;
// https://en.wikipedia.org/wiki/Non-breaking_space // https://en.wikipedia.org/wiki/Non-breaking_space

View File

@ -2,20 +2,23 @@ import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'package:percent_indicator/percent_indicator.dart';
import 'package:desktop_drop/desktop_drop.dart'; import 'package:desktop_drop/desktop_drop.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_breadcrumb/flutter_breadcrumb.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/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/desktop/widgets/tabbar_widget.dart';
import 'package:flutter_hbb/models/file_model.dart'; import 'package:flutter_hbb/models/file_model.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:wakelock/wakelock.dart'; import 'package:wakelock/wakelock.dart';
import '../../consts.dart'; import '../../consts.dart';
import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu; import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu;
import '../../common.dart'; import '../../common.dart';
import '../../models/model.dart'; import '../../models/model.dart';
import '../../models/platform_model.dart'; import '../../models/platform_model.dart';
@ -147,7 +150,7 @@ class _FileManagerPageState extends State<FileManagerPage>
value: _ffi.fileModel, value: _ffi.fileModel,
child: Consumer<FileModel>(builder: (context, model, child) { child: Consumer<FileModel>(builder: (context, model, child) {
return Scaffold( return Scaffold(
backgroundColor: Theme.of(context).backgroundColor, backgroundColor: Theme.of(context).scaffoldBackgroundColor,
body: Row( body: Row(
children: [ children: [
Flexible(flex: 3, child: body(isLocal: true)), Flexible(flex: 3, child: body(isLocal: true)),
@ -197,30 +200,37 @@ class _FileManagerPageState extends State<FileManagerPage>
final y = e.position.dy; final y = e.position.dy;
menuPos = RelativeRect.fromLTRB(x, y, x, y); menuPos = RelativeRect.fromLTRB(x, y, x, y);
}, },
child: IconButton( child: MenuButton(
icon: const Icon(Icons.more_vert),
splashRadius: kDesktopIconButtonSplashRadius,
onPressed: () => mod_menu.showMenu( onPressed: () => mod_menu.showMenu(
context: context, context: context,
position: menuPos, position: menuPos,
items: items items: items
.map((e) => e.build( .map(
(e) => e.build(
context, context,
MenuConfig( MenuConfig(
commonColor: CustomPopupMenuTheme.commonColor, commonColor: CustomPopupMenuTheme.commonColor,
height: CustomPopupMenuTheme.height, height: CustomPopupMenuTheme.height,
dividerHeight: CustomPopupMenuTheme.dividerHeight))) dividerHeight: CustomPopupMenuTheme.dividerHeight),
),
)
.expand((i) => i) .expand((i) => i)
.toList(), .toList(),
elevation: 8, elevation: 8,
), ),
)); child: SvgPicture.asset(
"assets/dots.svg",
color: Theme.of(context).tabBarTheme.labelColor,
),
color: Theme.of(context).cardColor,
hoverColor: Theme.of(context).hoverColor,
),
);
} }
Widget body({bool isLocal = false}) { Widget body({bool isLocal = false}) {
final scrollController = ScrollController(); final scrollController = ScrollController();
return Container( return Container(
decoration: BoxDecoration(border: Border.all(color: Colors.black26)),
margin: const EdgeInsets.all(16.0), margin: const EdgeInsets.all(16.0),
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: DropTarget( child: DropTarget(
@ -231,7 +241,9 @@ class _FileManagerPageState extends State<FileManagerPage>
onDragExited: (exit) { onDragExited: (exit) {
_dropMaskVisible.value = false; _dropMaskVisible.value = false;
}, },
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
headTools(isLocal), headTools(isLocal),
Expanded( Expanded(
child: Row( child: Row(
@ -241,8 +253,10 @@ class _FileManagerPageState extends State<FileManagerPage>
child: _buildFileList(context, isLocal, scrollController), child: _buildFileList(context, isLocal, scrollController),
) )
], ],
)), ),
]), ),
],
),
), ),
); );
} }
@ -295,8 +309,7 @@ class _FileManagerPageState extends State<FileManagerPage>
}); });
return; return;
} }
_jumpToEntry( _jumpToEntry(isLocal, searchResult.first, scrollController,
isLocal, searchResult.first, scrollController,
kDesktopFileTransferRowHeight, buffer); kDesktopFileTransferRowHeight, buffer);
}, },
onSearch: (buffer) { onSearch: (buffer) {
@ -311,8 +324,7 @@ class _FileManagerPageState extends State<FileManagerPage>
}); });
return; return;
} }
_jumpToEntry( _jumpToEntry(isLocal, searchResult.first, scrollController,
isLocal, searchResult.first, scrollController,
kDesktopFileTransferRowHeight, buffer); kDesktopFileTransferRowHeight, buffer);
}, },
child: ObxValue<RxString>( child: ObxValue<RxString>(
@ -329,27 +341,32 @@ class _FileManagerPageState extends State<FileManagerPage>
? " " ? " "
: "${entry.lastModified().toString().replaceAll(".000", "")} "; : "${entry.lastModified().toString().replaceAll(".000", "")} ";
final isSelected = selectedEntries.contains(entry); final isSelected = selectedEntries.contains(entry);
return SizedBox( return Padding(
padding: EdgeInsets.symmetric(vertical: 1),
child: Container(
decoration: BoxDecoration(
color: isSelected
? Theme.of(context).hoverColor
: Theme.of(context).cardColor,
borderRadius: BorderRadius.all(
Radius.circular(5.0),
),
),
key: ValueKey(entry.name), key: ValueKey(entry.name),
height: kDesktopFileTransferRowHeight, height: kDesktopFileTransferRowHeight,
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ children: [
const Divider(
height: 1,
),
Expanded( Expanded(
child: Ink(
decoration: isSelected
? BoxDecoration(color: Theme.of(context).hoverColor)
: null,
child: InkWell( child: InkWell(
child: Row(children: [ child: Row(
children: [
GestureDetector( GestureDetector(
child: Container( child: Container(
width: kDesktopFileTransferNameColWidth, width: kDesktopFileTransferNameColWidth,
child: Tooltip( child: Tooltip(
waitDuration: Duration(milliseconds: 500), waitDuration:
Duration(milliseconds: 500),
message: entry.name, message: entry.name,
child: Row(children: [ child: Row(children: [
entry.isDrive entry.isDrive
@ -361,26 +378,27 @@ class _FileManagerPageState extends State<FileManagerPage>
.color .color
?.withOpacity(0.7)) ?.withOpacity(0.7))
.paddingAll(4) .paddingAll(4)
: Icon( : SvgPicture.asset(
entry.isFile entry.isFile
? Icons.feed_outlined ? "assets/file.svg"
: Icons.folder, : "assets/folder.svg",
size: 20,
color: Theme.of(context) color: Theme.of(context)
.iconTheme .tabBarTheme
.color .labelColor,
?.withOpacity(0.7), ),
).marginSymmetric(horizontal: 2),
Expanded( Expanded(
child: Text(entry.name.nonBreaking, child: Text(
overflow: TextOverflow.ellipsis)) entry.name.nonBreaking,
overflow:
TextOverflow.ellipsis))
]), ]),
)), )),
onTap: () { onTap: () {
final items = getSelectedItems(isLocal); final items = getSelectedItems(isLocal);
// handle double click // handle double click
if (_checkDoubleClick(entry)) { if (_checkDoubleClick(entry)) {
openDirectory(entry.path, isLocal: isLocal); openDirectory(entry.path,
isLocal: isLocal);
items.clear(); items.clear();
return; return;
} }
@ -388,19 +406,28 @@ class _FileManagerPageState extends State<FileManagerPage>
items, filteredEntries, entry, isLocal); items, filteredEntries, entry, isLocal);
}, },
), ),
GestureDetector( Expanded(
child: GestureDetector(
child: SizedBox( child: SizedBox(
width: kDesktopFileTransferModifiedColWidth, width:
kDesktopFileTransferModifiedColWidth,
child: Tooltip( child: Tooltip(
waitDuration: Duration(milliseconds: 500), waitDuration:
Duration(milliseconds: 500),
message: lastModifiedStr, message: lastModifiedStr,
child: Text( child: Text(
lastModifiedStr, lastModifiedStr,
style: TextStyle( style: TextStyle(
fontSize: 12, color: MyTheme.darkGray), fontSize: 12,
color: MyTheme.darkGray,
),
)), )),
)), ),
GestureDetector( ),
),
SizedBox(
width: 100,
child: GestureDetector(
child: Tooltip( child: Tooltip(
waitDuration: Duration(milliseconds: 500), waitDuration: Duration(milliseconds: 500),
message: sizeStr, message: sizeStr,
@ -410,13 +437,16 @@ class _FileManagerPageState extends State<FileManagerPage>
style: TextStyle( style: TextStyle(
fontSize: 10, fontSize: 10,
color: MyTheme.darkGray), color: MyTheme.darkGray),
))), ),
]),
), ),
), ),
), ),
], ],
), ),
),
),
],
)),
); );
}).toList(growable: false); }).toList(growable: false);
@ -520,16 +550,27 @@ class _FileManagerPageState extends State<FileManagerPage>
Widget statusList() { Widget statusList() {
return PreferredSize( return PreferredSize(
preferredSize: const Size(200, double.infinity), preferredSize: const Size(200, double.infinity),
child: Container( child: model.jobTable.isEmpty
margin: const EdgeInsets.only(top: 16.0, bottom: 16.0, right: 16.0), ? Center(child: Text(translate("Empty")))
: Container(
margin:
const EdgeInsets.only(top: 16.0, bottom: 16.0, right: 16.0),
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(border: Border.all(color: Colors.grey)),
child: Obx( child: Obx(
() => ListView.builder( () => ListView.builder(
controller: ScrollController(), controller: ScrollController(),
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
final item = model.jobTable[index]; final item = model.jobTable[index];
return Column( return Padding(
padding: const EdgeInsets.only(bottom: 5),
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.all(
Radius.circular(8.0),
),
),
child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Row( Row(
@ -537,23 +578,32 @@ class _FileManagerPageState extends State<FileManagerPage>
children: [ children: [
Transform.rotate( Transform.rotate(
angle: item.isRemote ? pi : 0, angle: item.isRemote ? pi : 0,
child: const Icon(Icons.send)), child: SvgPicture.asset(
"assets/arrow.svg",
color: Theme.of(context)
.tabBarTheme
.labelColor,
),
),
const SizedBox( const SizedBox(
width: 16.0, width: 16.0,
), ),
Expanded( Expanded(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment:
CrossAxisAlignment.start,
children: [ children: [
Tooltip( Tooltip(
waitDuration: Duration(milliseconds: 500), waitDuration:
Duration(milliseconds: 500),
message: item.jobName, message: item.jobName,
child: Text( child: Text(
item.jobName, item.jobName,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
)), ),
),
Wrap( Wrap(
children: [ children: [
Text( Text(
@ -561,14 +611,33 @@ class _FileManagerPageState extends State<FileManagerPage>
Text( Text(
'${translate("files")} ${readableFileSize(item.totalSize.toDouble())} '), '${translate("files")} ${readableFileSize(item.totalSize.toDouble())} '),
Offstage( Offstage(
offstage: offstage: item.state !=
item.state != JobState.inProgress, JobState.inProgress,
child: Text( child: Text(
'${"${readableFileSize(item.speed)}/s"} ')), '${"${readableFileSize(item.speed)}/s"} '),
),
Offstage( Offstage(
offstage: item.totalSize <= 0, offstage: item.state !=
child: Text( JobState.inProgress,
'${(item.finishedSize.toDouble() * 100 / item.totalSize.toDouble()).toStringAsFixed(2)}%'), child: LinearPercentIndicator(
padding: EdgeInsets.all(0),
width: MediaQuery.of(context)
.size
.width *
0.15,
animateFromLastPercent: true,
center: Text(
'${(item.finishedSize / item.totalSize * 100).toStringAsFixed(0)}%',
),
barRadius: Radius.circular(15),
percent: item.finishedSize /
item.totalSize,
progressColor: MyTheme.accent,
backgroundColor:
Color(0xFF4C4F62),
lineHeight:
kDesktopFileTransferRowHeight,
).paddingSymmetric(vertical: 15),
), ),
], ],
), ),
@ -580,32 +649,42 @@ class _FileManagerPageState extends State<FileManagerPage>
children: [ children: [
Offstage( Offstage(
offstage: item.state != JobState.paused, offstage: item.state != JobState.paused,
child: IconButton( child: MenuButton(
onPressed: () { onPressed: () {
model.resumeJob(item.id); model.resumeJob(item.id);
}, },
splashRadius: kDesktopIconButtonSplashRadius, child: SvgPicture.asset(
icon: const Icon(Icons.restart_alt_rounded)), "assets/refresh.svg",
color: Theme.of(context)
.tabBarTheme
.labelColor,
),
color: MyTheme.accent,
hoverColor: MyTheme.accent80,
),
),
MenuButton(
padding: EdgeInsets.only(right: 15),
child: SvgPicture.asset(
"assets/close.svg",
color: Theme.of(context)
.tabBarTheme
.labelColor,
), ),
IconButton(
icon: const Icon(Icons.close),
splashRadius: 1,
onPressed: () { onPressed: () {
model.jobTable.removeAt(index); model.jobTable.removeAt(index);
model.cancelJob(item.id); model.cancelJob(item.id);
}, },
color: MyTheme.accent,
hoverColor: MyTheme.accent80,
), ),
], ],
) ),
], ],
), ),
SizedBox(
height: 8.0,
),
Divider(
height: 2.0,
)
], ],
).paddingSymmetric(vertical: 10),
),
); );
}, },
itemCount: model.jobTable.length, itemCount: model.jobTable.length,
@ -630,17 +709,23 @@ class _FileManagerPageState extends State<FileManagerPage>
Container( Container(
width: 50, width: 50,
height: 50, height: 50,
decoration: BoxDecoration(color: Colors.blue), decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(8)),
color: MyTheme.accent,
),
padding: EdgeInsets.all(8.0), padding: EdgeInsets.all(8.0),
child: FutureBuilder<String>( child: FutureBuilder<String>(
future: bind.sessionGetPlatform( future: bind.sessionGetPlatform(
id: _ffi.id, isRemote: !isLocal), id: _ffi.id, isRemote: !isLocal),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data!.isNotEmpty) { if (snapshot.hasData &&
snapshot.data!.isNotEmpty) {
return getPlatformImage('${snapshot.data}'); return getPlatformImage('${snapshot.data}');
} else { } else {
return CircularProgressIndicator( return CircularProgressIndicator(
color: Colors.white, color: Theme.of(context)
.tabBarTheme
.labelColor,
); );
} }
})), })),
@ -650,23 +735,38 @@ class _FileManagerPageState extends State<FileManagerPage>
.marginOnly(left: 8.0) .marginOnly(left: 8.0)
], ],
), ),
preferredSize: Size(double.infinity, 70)), preferredSize: Size(double.infinity, 70))
.paddingOnly(bottom: 15),
// buttons // buttons
Row( Row(
children: [ children: [
Row( Row(
children: [ children: [
IconButton( MenuButton(
icon: const Icon(Icons.arrow_back), padding: EdgeInsets.only(
splashRadius: kDesktopIconButtonSplashRadius, right: 3,
),
child: SvgPicture.asset(
"assets/arrow.svg",
color: Theme.of(context).tabBarTheme.labelColor,
),
color: Theme.of(context).cardColor,
hoverColor: Theme.of(context).hoverColor,
onPressed: () { onPressed: () {
selectedItems.clear(); selectedItems.clear();
model.goBack(isLocal: isLocal); model.goBack(isLocal: isLocal);
}, },
), ),
IconButton( MenuButton(
icon: const Icon(Icons.arrow_upward), child: RotatedBox(
splashRadius: kDesktopIconButtonSplashRadius, quarterTurns: 3,
child: SvgPicture.asset(
"assets/arrow.svg",
color: Theme.of(context).tabBarTheme.labelColor,
),
),
color: Theme.of(context).cardColor,
hoverColor: Theme.of(context).hoverColor,
onPressed: () { onPressed: () {
selectedItems.clear(); selectedItems.clear();
model.goToParentDirectory(isLocal: isLocal); model.goToParentDirectory(isLocal: isLocal);
@ -675,6 +775,17 @@ class _FileManagerPageState extends State<FileManagerPage>
], ],
), ),
Expanded( 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( child: GestureDetector(
onTap: () { onTap: () {
locationStatus.value = locationStatus.value =
@ -682,33 +793,34 @@ class _FileManagerPageState extends State<FileManagerPage>
? LocationStatus.pathLocation ? LocationStatus.pathLocation
: LocationStatus.bread; : LocationStatus.bread;
Future.delayed(Duration.zero, () { Future.delayed(Duration.zero, () {
if (locationStatus.value == LocationStatus.pathLocation) { if (locationStatus.value ==
LocationStatus.pathLocation) {
locationFocus.requestFocus(); locationFocus.requestFocus();
} }
}); });
}, },
child: Obx(() => Container( child: Obx(
decoration: BoxDecoration( () => Container(
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: locationStatus.value == LocationStatus.bread child: locationStatus.value ==
LocationStatus.bread
? buildBread(isLocal) ? buildBread(isLocal)
: buildPathLocation(isLocal)), : buildPathLocation(isLocal)),
], ],
))), ),
)), ),
),
),
),
),
),
),
Obx(() { Obx(() {
switch (locationStatus.value) { switch (locationStatus.value) {
case LocationStatus.bread: case LocationStatus.bread:
return IconButton( return MenuButton(
onPressed: () { onPressed: () {
locationStatus.value = LocationStatus.fileSearchBar; locationStatus.value = LocationStatus.fileSearchBar;
final focusNode = final focusNode =
@ -716,31 +828,52 @@ class _FileManagerPageState extends State<FileManagerPage>
Future.delayed( Future.delayed(
Duration.zero, () => focusNode.requestFocus()); Duration.zero, () => focusNode.requestFocus());
}, },
splashRadius: kDesktopIconButtonSplashRadius, child: SvgPicture.asset(
icon: Icon(Icons.search)); "assets/search.svg",
color: Theme.of(context).tabBarTheme.labelColor,
),
color: Theme.of(context).cardColor,
hoverColor: Theme.of(context).hoverColor,
);
case LocationStatus.pathLocation: case LocationStatus.pathLocation:
return IconButton( return MenuButton(
color: Theme.of(context).disabledColor,
onPressed: null, onPressed: null,
splashRadius: kDesktopIconButtonSplashRadius, child: SvgPicture.asset(
icon: Icon(Icons.close)); "assets/close.svg",
case LocationStatus.fileSearchBar: color: Theme.of(context).tabBarTheme.labelColor,
return IconButton( ),
color: Theme.of(context).disabledColor, color: Theme.of(context).disabledColor,
hoverColor: Theme.of(context).hoverColor,
);
case LocationStatus.fileSearchBar:
return MenuButton(
onPressed: () { onPressed: () {
onSearchText("", isLocal); onSearchText("", isLocal);
locationStatus.value = LocationStatus.bread; locationStatus.value = LocationStatus.bread;
}, },
splashRadius: 1, child: SvgPicture.asset(
icon: Icon(Icons.close)); "assets/close.svg",
color: Theme.of(context).tabBarTheme.labelColor,
),
color: Theme.of(context).cardColor,
hoverColor: Theme.of(context).hoverColor,
);
} }
}), }),
IconButton( MenuButton(
padding: EdgeInsets.only(
left: 3,
),
onPressed: () { onPressed: () {
model.refresh(isLocal: isLocal); model.refresh(isLocal: isLocal);
}, },
splashRadius: kDesktopIconButtonSplashRadius, child: SvgPicture.asset(
icon: const Icon(Icons.refresh)), "assets/refresh.svg",
color: Theme.of(context).tabBarTheme.labelColor,
),
color: Theme.of(context).cardColor,
hoverColor: Theme.of(context).hoverColor,
),
], ],
), ),
Row( Row(
@ -751,14 +884,21 @@ class _FileManagerPageState extends State<FileManagerPage>
mainAxisAlignment: mainAxisAlignment:
isLocal ? MainAxisAlignment.start : MainAxisAlignment.end, isLocal ? MainAxisAlignment.start : MainAxisAlignment.end,
children: [ children: [
IconButton( MenuButton(
padding: EdgeInsets.only(
right: 3,
),
onPressed: () { onPressed: () {
model.goHome(isLocal: isLocal); model.goHome(isLocal: isLocal);
}, },
icon: const Icon(Icons.home_outlined), child: SvgPicture.asset(
splashRadius: kDesktopIconButtonSplashRadius, "assets/home.svg",
color: Theme.of(context).tabBarTheme.labelColor,
), ),
IconButton( color: Theme.of(context).cardColor,
hoverColor: Theme.of(context).hoverColor,
),
MenuButton(
onPressed: () { onPressed: () {
final name = TextEditingController(); final name = TextEditingController();
_ffi.dialogManager.show((setState, close) { _ffi.dialogManager.show((setState, close) {
@ -800,9 +940,14 @@ class _FileManagerPageState extends State<FileManagerPage>
); );
}); });
}, },
splashRadius: kDesktopIconButtonSplashRadius, child: SvgPicture.asset(
icon: const Icon(Icons.create_new_folder_outlined)), "assets/folder_new.svg",
IconButton( color: Theme.of(context).tabBarTheme.labelColor,
),
color: Theme.of(context).cardColor,
hoverColor: Theme.of(context).hoverColor,
),
MenuButton(
onPressed: validItems(selectedItems) onPressed: validItems(selectedItems)
? () async { ? () async {
await (model.removeAction(selectedItems, await (model.removeAction(selectedItems,
@ -810,32 +955,80 @@ class _FileManagerPageState extends State<FileManagerPage>
selectedItems.clear(); selectedItems.clear();
} }
: null, : null,
splashRadius: kDesktopIconButtonSplashRadius, child: SvgPicture.asset(
icon: const Icon(Icons.delete_forever_outlined)), "assets/trash.svg",
color: Theme.of(context).tabBarTheme.labelColor,
),
color: Theme.of(context).cardColor,
hoverColor: Theme.of(context).hoverColor,
),
menu(isLocal: isLocal), menu(isLocal: isLocal),
], ],
), ),
), ),
TextButton.icon( ElevatedButton.icon(
style: ButtonStyle(
padding: MaterialStateProperty.all<EdgeInsetsGeometry>(isLocal
? EdgeInsets.only(left: 10)
: EdgeInsets.only(right: 10)),
backgroundColor: MaterialStateProperty.all(
selectedItems.length == 0
? MyTheme.accent80
: MyTheme.accent,
),
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18.0),
),
),
),
onPressed: validItems(selectedItems) onPressed: validItems(selectedItems)
? () { ? () {
model.sendFiles(selectedItems, isRemote: !isLocal); model.sendFiles(selectedItems, isRemote: !isLocal);
selectedItems.clear(); selectedItems.clear();
} }
: null, : null,
icon: Transform.rotate( icon: isLocal
angle: isLocal ? 0 : pi, ? Text(
child: const Icon( translate('Send'),
Icons.send, textAlign: TextAlign.right,
style: TextStyle(
color: selectedItems.length == 0
? MyTheme.darkGray
: Colors.white,
),
)
: RotatedBox(
quarterTurns: 2,
child: SvgPicture.asset(
"assets/arrow.svg",
color: selectedItems.length == 0
? MyTheme.darkGray
: Colors.white,
alignment: Alignment.bottomRight,
),
),
label: isLocal
? SvgPicture.asset(
"assets/arrow.svg",
color: selectedItems.length == 0
? MyTheme.darkGray
: Colors.white,
)
: Text(
translate('Receive'),
style: TextStyle(
color: selectedItems.length == 0
? MyTheme.darkGray
: Colors.white,
),
), ),
), ),
label: Text(
isLocal ? translate('Send') : translate('Receive'),
)),
], ],
).marginOnly(top: 8.0) ).marginOnly(top: 8.0)
], ],
)); ),
);
} }
bool validItems(SelectedItems items) { bool validItems(SelectedItems items) {
@ -901,14 +1094,16 @@ class _FileManagerPageState extends State<FileManagerPage>
}, },
child: BreadCrumb( child: BreadCrumb(
items: items, items: items,
divider: Icon(Icons.chevron_right), divider: const Icon(Icons.keyboard_arrow_right_rounded),
overflow: ScrollableOverflow( overflow: ScrollableOverflow(
controller: controller: getBreadCrumbScrollController(isLocal),
getBreadCrumbScrollController(isLocal)), ),
))), ),
),
),
ActionIcon( ActionIcon(
message: "", message: "",
icon: Icons.arrow_drop_down, icon: Icons.keyboard_arrow_down_rounded,
onTap: () async { onTap: () async {
final renderBox = locationBarKey.currentContext final renderBox = locationBarKey.currentContext
?.findRenderObject() as RenderBox; ?.findRenderObject() as RenderBox;
@ -1021,13 +1216,23 @@ class _FileManagerPageState extends State<FileManagerPage>
.marginSymmetric(horizontal: 4))); .marginSymmetric(horizontal: 4)));
} else { } else {
final list = PathUtil.split(path, isWindows); final list = PathUtil.split(path, isWindows);
breadCrumbList.addAll(list.asMap().entries.map((e) => BreadCrumbItem( breadCrumbList.addAll(
list.asMap().entries.map(
(e) => BreadCrumbItem(
content: TextButton( content: TextButton(
child: Text(e.value), child: Text(e.value),
style: ButtonStyle( style: ButtonStyle(
minimumSize: MaterialStateProperty.all(Size(0, 0))), minimumSize: MaterialStateProperty.all(
onPressed: () => onPressed(list.sublist(0, e.key + 1))) Size(0, 0),
.marginSymmetric(horizontal: 4)))); ),
),
onPressed: () => onPressed(
list.sublist(0, e.key + 1),
),
).marginSymmetric(horizontal: 4),
),
),
);
} }
return breadCrumbList; return breadCrumbList;
} }
@ -1054,20 +1259,24 @@ class _FileManagerPageState extends State<FileManagerPage>
: searchTextObs.value; : searchTextObs.value;
final textController = TextEditingController(text: text) final textController = TextEditingController(text: text)
..selection = TextSelection.collapsed(offset: text.length); ..selection = TextSelection.collapsed(offset: text.length);
return Row(children: [ return Row(
Icon( children: [
SvgPicture.asset(
locationStatus.value == LocationStatus.pathLocation locationStatus.value == LocationStatus.pathLocation
? Icons.folder ? "assets/folder.svg"
: Icons.search, : "assets/search.svg",
color: Theme.of(context).hintColor, color: Theme.of(context).tabBarTheme.labelColor,
).paddingSymmetric(horizontal: 2), ),
Expanded( Expanded(
child: TextField( child: TextField(
focusNode: focusNode, focusNode: focusNode,
decoration: InputDecoration( decoration: InputDecoration(
border: InputBorder.none, border: InputBorder.none,
isDense: true, isDense: true,
prefix: Padding(padding: EdgeInsets.only(left: 4.0))), prefix: Padding(
padding: EdgeInsets.only(left: 4.0),
),
),
controller: textController, controller: textController,
onSubmitted: (path) { onSubmitted: (path) {
openDirectory(path, isLocal: isLocal); openDirectory(path, isLocal: isLocal);
@ -1075,8 +1284,10 @@ class _FileManagerPageState extends State<FileManagerPage>
onChanged: locationStatus.value == LocationStatus.fileSearchBar onChanged: locationStatus.value == LocationStatus.fileSearchBar
? (searchText) => onSearchText(searchText, isLocal) ? (searchText) => onSearchText(searchText, isLocal)
: null, : null,
)) ),
]); )
],
);
} }
onSearchText(String searchText, bool isLocal) { onSearchText(String searchText, bool isLocal) {
@ -1145,12 +1356,13 @@ class _FileManagerPageState extends State<FileManagerPage>
Text( Text(
name, name,
style: headerTextStyle, style: headerTextStyle,
).marginSymmetric( ).marginSymmetric(horizontal: 4),
horizontal: sortBy == SortBy.name ? 4 : 0.0),
ascending.value != null ascending.value != null
? Icon(ascending.value! ? Icon(
? Icons.arrow_upward ascending.value!
: Icons.arrow_downward) ? Icons.keyboard_arrow_up_rounded
: Icons.keyboard_arrow_down_rounded,
)
: const Offstage() : const Offstage()
], ],
), ),

View File

@ -86,18 +86,14 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final tabWidget = Container( final tabWidget = Scaffold(
decoration: BoxDecoration( backgroundColor: Theme.of(context).cardColor,
border: Border.all(color: MyTheme.color(context).border!)),
child: Scaffold(
backgroundColor: Theme.of(context).backgroundColor,
body: DesktopTab( body: DesktopTab(
controller: tabController, controller: tabController,
onWindowCloseButton: handleWindowCloseButton, onWindowCloseButton: handleWindowCloseButton,
tail: const AddButton().paddingOnly(left: 10), tail: const AddButton().paddingOnly(left: 10),
labelGetter: DesktopTab.labelGetterAlias, labelGetter: DesktopTab.labelGetterAlias,
)), ));
);
return Platform.isMacOS return Platform.isMacOS
? tabWidget ? tabWidget
: SubWindowDragToResizeArea( : SubWindowDragToResizeArea(

View File

@ -27,6 +27,7 @@ class MenuButton extends StatefulWidget {
class _MenuButtonState extends State<MenuButton> { class _MenuButtonState extends State<MenuButton> {
bool _isHover = false; bool _isHover = false;
final double _borderRadius = 8.0;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -38,16 +39,17 @@ class _MenuButtonState extends State<MenuButton> {
type: MaterialType.transparency, type: MaterialType.transparency,
child: Ink( child: Ink(
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5), borderRadius: BorderRadius.circular(_borderRadius),
color: _isHover ? widget.hoverColor : widget.color, color: _isHover ? widget.hoverColor : widget.color,
), ),
child: InkWell( child: InkWell(
hoverColor: widget.hoverColor,
onHover: (val) { onHover: (val) {
setState(() { setState(() {
_isHover = val; _isHover = val;
}); });
}, },
borderRadius: BorderRadius.circular(5), borderRadius: BorderRadius.circular(_borderRadius),
splashColor: widget.splashColor, splashColor: widget.splashColor,
enableFeedback: widget.enableFeedback, enableFeedback: widget.enableFeedback,
onTap: widget.onPressed, onTap: widget.onPressed,

View File

@ -970,6 +970,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.11.1" version: "1.11.1"
percent_indicator:
dependency: "direct main"
description:
name: percent_indicator
sha256: cec41f67181fbd5322aa68b355621d1a4eea827426b8eeb613f6cbe195ff7b4a
url: "https://pub.dev"
source: hosted
version: "4.2.2"
petitparser: petitparser:
dependency: transitive dependency: transitive
description: description:
@ -1547,5 +1555,5 @@ packages:
source: hosted source: hosted
version: "0.1.1" version: "0.1.1"
sdks: sdks:
dart: ">=2.18.0 <4.0.0" dart: ">=2.18.0 <3.0.0"
flutter: ">=3.3.0" flutter: ">=3.3.0"

View File

@ -92,6 +92,7 @@ dependencies:
password_strength: ^0.2.0 password_strength: ^0.2.0
flutter_launcher_icons: ^0.11.0 flutter_launcher_icons: ^0.11.0
flutter_keyboard_visibility: ^5.4.0 flutter_keyboard_visibility: ^5.4.0
percent_indicator: ^4.2.2
dev_dependencies: dev_dependencies: