Merge pull request #1740 from Heap-Hop/master
update file transfer and chat page
This commit is contained in:
commit
b8c9113ee5
@ -36,7 +36,7 @@ class DraggableChatWindow extends StatelessWidget {
|
||||
appBar: CustomAppBar(
|
||||
onPanUpdate: onPanUpdate,
|
||||
appBar: isDesktop
|
||||
? _buildDesktopAppBar()
|
||||
? _buildDesktopAppBar(context)
|
||||
: _buildMobileAppBar(context),
|
||||
),
|
||||
body: ChatPage(chatModel: chatModel),
|
||||
@ -82,33 +82,33 @@ class DraggableChatWindow extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDesktopAppBar() {
|
||||
Widget _buildDesktopAppBar(BuildContext context) {
|
||||
return Container(
|
||||
color: MyTheme.accent50,
|
||||
height: 35,
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: Theme.of(context).hintColor.withOpacity(0.4)))),
|
||||
height: 38,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: Text(
|
||||
translate("Chat"),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontFamily: 'WorkSans',
|
||||
fontWeight: FontWeight.bold),
|
||||
)),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
ActionIcon(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 8),
|
||||
child: Row(children: [
|
||||
Icon(Icons.chat_bubble_outline,
|
||||
size: 20, color: Theme.of(context).colorScheme.primary),
|
||||
SizedBox(width: 6),
|
||||
Text(translate("Chat"))
|
||||
])),
|
||||
Padding(
|
||||
padding: EdgeInsets.all(2),
|
||||
child: ActionIcon(
|
||||
message: 'Close',
|
||||
icon: IconFont.close,
|
||||
onTap: chatModel.hideChatWindowOverlay,
|
||||
isClose: true,
|
||||
)
|
||||
],
|
||||
)
|
||||
size: 32,
|
||||
))
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -31,6 +31,8 @@ const int kMobileMaxDisplayHeight = 1280;
|
||||
const int kDesktopMaxDisplayWidth = 1920;
|
||||
const int kDesktopMaxDisplayHeight = 1080;
|
||||
|
||||
const int kDesktopDoubleClickTimeMilli = 200;
|
||||
|
||||
const Size kConnectionManagerWindowSize = Size(300, 400);
|
||||
// Tabbar transition duration, now we remove the duration
|
||||
const Duration kTabTransitionDuration = Duration.zero;
|
||||
|
@ -1,10 +1,11 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:desktop_drop/desktop_drop.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_breadcrumb/flutter_breadcrumb.dart';
|
||||
import 'package:flutter_hbb/mobile/pages/file_manager_page.dart';
|
||||
import 'package:flutter_hbb/models/file_model.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@ -44,15 +45,17 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
|
||||
final _locationStatusLocal = LocationStatus.bread.obs;
|
||||
final _locationStatusRemote = LocationStatus.bread.obs;
|
||||
final FocusNode _locationNodeLocal =
|
||||
FocusNode(debugLabel: "locationNodeLocal");
|
||||
final FocusNode _locationNodeRemote =
|
||||
FocusNode(debugLabel: "locationNodeRemote");
|
||||
final _locationNodeLocal = FocusNode(debugLabel: "locationNodeLocal");
|
||||
final _locationNodeRemote = FocusNode(debugLabel: "locationNodeRemote");
|
||||
final _searchTextLocal = "".obs;
|
||||
final _searchTextRemote = "".obs;
|
||||
final _breadCrumbScrollerLocal = ScrollController();
|
||||
final _breadCrumbScrollerRemote = ScrollController();
|
||||
|
||||
/// [_lastClickTime], [_lastClickEntry] help to handle double click
|
||||
int _lastClickTime = DateTime.now().millisecondsSinceEpoch;
|
||||
Entry? _lastClickEntry;
|
||||
|
||||
final _dropMaskVisible = false.obs; // TODO impl drop mask
|
||||
|
||||
ScrollController getBreadCrumbScrollController(bool isLocal) {
|
||||
@ -171,22 +174,6 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
}
|
||||
|
||||
Widget body({bool isLocal = false}) {
|
||||
final fd = model.getCurrentDir(isLocal);
|
||||
final entries = fd.entries;
|
||||
final sortIndex = (SortBy style) {
|
||||
switch (style) {
|
||||
case SortBy.Name:
|
||||
return 0;
|
||||
case SortBy.Type:
|
||||
return 0;
|
||||
case SortBy.Modified:
|
||||
return 1;
|
||||
case SortBy.Size:
|
||||
return 2;
|
||||
}
|
||||
}(model.getSortStyle(isLocal));
|
||||
final sortAscending =
|
||||
isLocal ? model.localSortAscending : model.remoteSortAscending;
|
||||
return Container(
|
||||
decoration: BoxDecoration(border: Border.all(color: Colors.black26)),
|
||||
margin: const EdgeInsets.all(16.0),
|
||||
@ -208,126 +195,7 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
controller: ScrollController(),
|
||||
child: ObxValue<RxString>(
|
||||
(searchText) {
|
||||
final filteredEntries = searchText.isNotEmpty
|
||||
? entries.where((element) {
|
||||
return element.name.contains(searchText.value);
|
||||
}).toList(growable: false)
|
||||
: entries;
|
||||
return DataTable(
|
||||
key: ValueKey(isLocal ? 0 : 1),
|
||||
showCheckboxColumn: true,
|
||||
dataRowHeight: 25,
|
||||
headingRowHeight: 30,
|
||||
horizontalMargin: 8,
|
||||
columnSpacing: 8,
|
||||
showBottomBorder: true,
|
||||
sortColumnIndex: sortIndex,
|
||||
sortAscending: sortAscending,
|
||||
columns: [
|
||||
DataColumn(
|
||||
label: Text(
|
||||
translate("Name"),
|
||||
).marginSymmetric(horizontal: 4),
|
||||
onSort: (columnIndex, ascending) {
|
||||
model.changeSortStyle(SortBy.Name,
|
||||
isLocal: isLocal, ascending: ascending);
|
||||
}),
|
||||
DataColumn(
|
||||
label: Text(
|
||||
translate("Modified"),
|
||||
),
|
||||
onSort: (columnIndex, ascending) {
|
||||
model.changeSortStyle(SortBy.Modified,
|
||||
isLocal: isLocal, ascending: ascending);
|
||||
}),
|
||||
DataColumn(
|
||||
label: Text(translate("Size")),
|
||||
onSort: (columnIndex, ascending) {
|
||||
model.changeSortStyle(SortBy.Size,
|
||||
isLocal: isLocal, ascending: ascending);
|
||||
}),
|
||||
],
|
||||
rows: filteredEntries.map((entry) {
|
||||
final sizeStr = entry.isFile
|
||||
? readableFileSize(entry.size.toDouble())
|
||||
: "";
|
||||
return DataRow(
|
||||
key: ValueKey(entry.name),
|
||||
onSelectChanged: (s) {
|
||||
if (s != null) {
|
||||
if (s) {
|
||||
getSelectedItem(isLocal)
|
||||
.add(isLocal, entry);
|
||||
} else {
|
||||
getSelectedItem(isLocal).remove(entry);
|
||||
}
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
selected:
|
||||
getSelectedItem(isLocal).contains(entry),
|
||||
cells: [
|
||||
DataCell(
|
||||
Container(
|
||||
width: 180,
|
||||
child: Tooltip(
|
||||
message: entry.name,
|
||||
child: Row(children: [
|
||||
Icon(
|
||||
entry.isFile
|
||||
? Icons.feed_outlined
|
||||
: Icons.folder,
|
||||
size: 20,
|
||||
color: Theme.of(context)
|
||||
.iconTheme
|
||||
.color
|
||||
?.withOpacity(0.7),
|
||||
).marginSymmetric(horizontal: 2),
|
||||
Expanded(
|
||||
child: Text(entry.name,
|
||||
overflow:
|
||||
TextOverflow.ellipsis))
|
||||
]),
|
||||
)), onTap: () {
|
||||
if (entry.isDirectory) {
|
||||
openDirectory(entry.path, isLocal: isLocal);
|
||||
if (isLocal) {
|
||||
_localSelectedItems.clear();
|
||||
} else {
|
||||
_remoteSelectedItems.clear();
|
||||
}
|
||||
} else {
|
||||
// Perform file-related tasks.
|
||||
final selectedItems =
|
||||
getSelectedItem(isLocal);
|
||||
if (selectedItems.contains(entry)) {
|
||||
selectedItems.remove(entry);
|
||||
} else {
|
||||
selectedItems.add(isLocal, entry);
|
||||
}
|
||||
setState(() {});
|
||||
}
|
||||
}),
|
||||
DataCell(FittedBox(
|
||||
child: Text(
|
||||
"${entry.lastModified().toString().replaceAll(".000", "")} ",
|
||||
style: TextStyle(
|
||||
fontSize: 12, color: MyTheme.darkGray),
|
||||
))),
|
||||
DataCell(Text(
|
||||
sizeStr,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 10, color: MyTheme.darkGray),
|
||||
)),
|
||||
]);
|
||||
}).toList(growable: false),
|
||||
);
|
||||
},
|
||||
isLocal ? _searchTextLocal : _searchTextRemote,
|
||||
),
|
||||
child: _buildDataTable(context, isLocal),
|
||||
),
|
||||
)
|
||||
],
|
||||
@ -337,6 +205,183 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDataTable(BuildContext context, bool isLocal) {
|
||||
final fd = model.getCurrentDir(isLocal);
|
||||
final entries = fd.entries;
|
||||
final sortIndex = (SortBy style) {
|
||||
switch (style) {
|
||||
case SortBy.Name:
|
||||
return 0;
|
||||
case SortBy.Type:
|
||||
return 0;
|
||||
case SortBy.Modified:
|
||||
return 1;
|
||||
case SortBy.Size:
|
||||
return 2;
|
||||
}
|
||||
}(model.getSortStyle(isLocal));
|
||||
final sortAscending =
|
||||
isLocal ? model.localSortAscending : model.remoteSortAscending;
|
||||
|
||||
return ObxValue<RxString>(
|
||||
(searchText) {
|
||||
final filteredEntries = searchText.isNotEmpty
|
||||
? entries.where((element) {
|
||||
return element.name.contains(searchText.value);
|
||||
}).toList(growable: false)
|
||||
: entries;
|
||||
return DataTable(
|
||||
key: ValueKey(isLocal ? 0 : 1),
|
||||
showCheckboxColumn: false,
|
||||
dataRowHeight: 25,
|
||||
headingRowHeight: 30,
|
||||
horizontalMargin: 8,
|
||||
columnSpacing: 8,
|
||||
showBottomBorder: true,
|
||||
sortColumnIndex: sortIndex,
|
||||
sortAscending: sortAscending,
|
||||
columns: [
|
||||
DataColumn(
|
||||
label: Text(
|
||||
translate("Name"),
|
||||
).marginSymmetric(horizontal: 4),
|
||||
onSort: (columnIndex, ascending) {
|
||||
model.changeSortStyle(SortBy.Name,
|
||||
isLocal: isLocal, ascending: ascending);
|
||||
}),
|
||||
DataColumn(
|
||||
label: Text(
|
||||
translate("Modified"),
|
||||
),
|
||||
onSort: (columnIndex, ascending) {
|
||||
model.changeSortStyle(SortBy.Modified,
|
||||
isLocal: isLocal, ascending: ascending);
|
||||
}),
|
||||
DataColumn(
|
||||
label: Text(translate("Size")),
|
||||
onSort: (columnIndex, ascending) {
|
||||
model.changeSortStyle(SortBy.Size,
|
||||
isLocal: isLocal, ascending: ascending);
|
||||
}),
|
||||
],
|
||||
rows: filteredEntries.map((entry) {
|
||||
final sizeStr =
|
||||
entry.isFile ? readableFileSize(entry.size.toDouble()) : "";
|
||||
final lastModifiedStr =
|
||||
"${entry.lastModified().toString().replaceAll(".000", "")} ";
|
||||
return DataRow(
|
||||
key: ValueKey(entry.name),
|
||||
onSelectChanged: (s) {
|
||||
_onSelectedChanged(getSelectedItem(isLocal), filteredEntries,
|
||||
entry, isLocal);
|
||||
setState(() {});
|
||||
},
|
||||
selected: getSelectedItem(isLocal).contains(entry),
|
||||
cells: [
|
||||
DataCell(
|
||||
Container(
|
||||
width: 200,
|
||||
child: Tooltip(
|
||||
waitDuration: Duration(milliseconds: 500),
|
||||
message: entry.name,
|
||||
child: Row(children: [
|
||||
Icon(
|
||||
entry.isFile ? Icons.feed_outlined : Icons.folder,
|
||||
size: 20,
|
||||
color: Theme.of(context)
|
||||
.iconTheme
|
||||
.color
|
||||
?.withOpacity(0.7),
|
||||
).marginSymmetric(horizontal: 2),
|
||||
Expanded(
|
||||
child: Text(entry.name,
|
||||
overflow: TextOverflow.ellipsis))
|
||||
]),
|
||||
)),
|
||||
onTap: () {
|
||||
final items = getSelectedItem(isLocal);
|
||||
|
||||
// handle double click
|
||||
if (_checkDoubleClick(entry)) {
|
||||
openDirectory(entry.path, isLocal: isLocal);
|
||||
items.clear();
|
||||
return;
|
||||
}
|
||||
_onSelectedChanged(
|
||||
items, filteredEntries, entry, isLocal);
|
||||
},
|
||||
),
|
||||
DataCell(FittedBox(
|
||||
child: Tooltip(
|
||||
waitDuration: Duration(milliseconds: 500),
|
||||
message: lastModifiedStr,
|
||||
child: Text(
|
||||
lastModifiedStr,
|
||||
style: TextStyle(
|
||||
fontSize: 12, color: MyTheme.darkGray),
|
||||
)))),
|
||||
DataCell(Tooltip(
|
||||
waitDuration: Duration(milliseconds: 500),
|
||||
message: sizeStr,
|
||||
child: Text(
|
||||
sizeStr,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontSize: 10, color: MyTheme.darkGray),
|
||||
))),
|
||||
]);
|
||||
}).toList(growable: false),
|
||||
);
|
||||
},
|
||||
isLocal ? _searchTextLocal : _searchTextRemote,
|
||||
);
|
||||
}
|
||||
|
||||
void _onSelectedChanged(SelectedItems selectedItems, List<Entry> entries,
|
||||
Entry entry, bool isLocal) {
|
||||
final isCtrlDown = RawKeyboard.instance.keysPressed
|
||||
.contains(LogicalKeyboardKey.controlLeft);
|
||||
final isShiftDown =
|
||||
RawKeyboard.instance.keysPressed.contains(LogicalKeyboardKey.shiftLeft);
|
||||
if (isCtrlDown) {
|
||||
if (selectedItems.contains(entry)) {
|
||||
selectedItems.remove(entry);
|
||||
} else {
|
||||
selectedItems.add(isLocal, 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(isLocal, e));
|
||||
} else {
|
||||
selectedItems.clear();
|
||||
selectedItems.add(isLocal, entry);
|
||||
}
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
bool _checkDoubleClick(Entry entry) {
|
||||
final current = DateTime.now().millisecondsSinceEpoch;
|
||||
final elapsed = current - _lastClickTime;
|
||||
_lastClickTime = current;
|
||||
if (_lastClickEntry == entry) {
|
||||
if (elapsed < kDesktopDoubleClickTimeMilli) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
_lastClickEntry = entry;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// transfer status list
|
||||
/// watch transfer status
|
||||
Widget statusList() {
|
||||
@ -369,6 +414,7 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Tooltip(
|
||||
waitDuration: Duration(milliseconds: 500),
|
||||
message: item.jobName,
|
||||
child: Text(
|
||||
item.jobName,
|
||||
@ -787,8 +833,10 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
|
||||
onSearchText(String searchText, bool isLocal) {
|
||||
if (isLocal) {
|
||||
_localSelectedItems.clear();
|
||||
_searchTextLocal.value = searchText;
|
||||
} else {
|
||||
_remoteSelectedItems.clear();
|
||||
_searchTextRemote.value = searchText;
|
||||
}
|
||||
}
|
||||
|
@ -191,6 +191,8 @@ class DesktopTab extends StatelessWidget {
|
||||
|
||||
final DesktopTabController controller;
|
||||
Rx<DesktopTabState> get state => controller.state;
|
||||
final isMaximized = false.obs;
|
||||
|
||||
late final DesktopTabType tabType;
|
||||
late final bool isMainWindow;
|
||||
|
||||
@ -299,8 +301,10 @@ class DesktopTab extends StatelessWidget {
|
||||
width: 78,
|
||||
)),
|
||||
GestureDetector(
|
||||
onDoubleTap: () =>
|
||||
showMaximize ? toggleMaximize(isMainWindow) : null,
|
||||
onDoubleTap: showMaximize
|
||||
? () => toggleMaximize(isMainWindow)
|
||||
.then((value) => isMaximized.value = value)
|
||||
: null,
|
||||
onPanStart: (_) => startDragging(isMainWindow),
|
||||
child: Row(children: [
|
||||
Offstage(
|
||||
@ -333,6 +337,7 @@ class DesktopTab extends StatelessWidget {
|
||||
tabType: tabType,
|
||||
state: state,
|
||||
tail: tail,
|
||||
isMaximized: isMaximized,
|
||||
showMinimize: showMinimize,
|
||||
showMaximize: showMaximize,
|
||||
showClose: showClose,
|
||||
@ -347,6 +352,7 @@ class WindowActionPanel extends StatefulWidget {
|
||||
final bool isMainWindow;
|
||||
final DesktopTabType tabType;
|
||||
final Rx<DesktopTabState> state;
|
||||
final RxBool isMaximized;
|
||||
|
||||
final bool showMinimize;
|
||||
final bool showMaximize;
|
||||
@ -359,6 +365,7 @@ class WindowActionPanel extends StatefulWidget {
|
||||
required this.isMainWindow,
|
||||
required this.tabType,
|
||||
required this.state,
|
||||
required this.isMaximized,
|
||||
this.tail,
|
||||
this.showMinimize = true,
|
||||
this.showMaximize = true,
|
||||
@ -374,30 +381,31 @@ class WindowActionPanel extends StatefulWidget {
|
||||
|
||||
class WindowActionPanelState extends State<WindowActionPanel>
|
||||
with MultiWindowListener, WindowListener {
|
||||
bool isMaximized = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
DesktopMultiWindow.addListener(this);
|
||||
windowManager.addListener(this);
|
||||
|
||||
if (widget.isMainWindow) {
|
||||
windowManager.isMaximized().then((maximized) {
|
||||
if (isMaximized != maximized) {
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) => setState(() => isMaximized = maximized));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
final wc = WindowController.fromWindowId(windowId!);
|
||||
wc.isMaximized().then((maximized) {
|
||||
if (isMaximized != maximized) {
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) => setState(() => isMaximized = maximized));
|
||||
}
|
||||
});
|
||||
}
|
||||
Future.delayed(Duration(milliseconds: 500), () {
|
||||
if (widget.isMainWindow) {
|
||||
windowManager.isMaximized().then((maximized) {
|
||||
if (widget.isMaximized.value != maximized) {
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) => setState(() => widget.isMaximized.value = maximized));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
final wc = WindowController.fromWindowId(windowId!);
|
||||
wc.isMaximized().then((maximized) {
|
||||
debugPrint("isMaximized $maximized");
|
||||
if (widget.isMaximized.value != maximized) {
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) => setState(() => widget.isMaximized.value = maximized));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@ -410,8 +418,8 @@ class WindowActionPanelState extends State<WindowActionPanel>
|
||||
@override
|
||||
void onWindowMaximize() {
|
||||
// catch maximize from system
|
||||
if (!isMaximized) {
|
||||
setState(() => isMaximized = true);
|
||||
if (!widget.isMaximized.value) {
|
||||
widget.isMaximized.value = true;
|
||||
}
|
||||
super.onWindowMaximize();
|
||||
}
|
||||
@ -419,8 +427,8 @@ class WindowActionPanelState extends State<WindowActionPanel>
|
||||
@override
|
||||
void onWindowUnmaximize() {
|
||||
// catch unmaximize from system
|
||||
if (isMaximized) {
|
||||
setState(() => isMaximized = false);
|
||||
if (widget.isMaximized.value) {
|
||||
widget.isMaximized.value = false;
|
||||
}
|
||||
super.onWindowUnmaximize();
|
||||
}
|
||||
@ -452,12 +460,14 @@ class WindowActionPanelState extends State<WindowActionPanel>
|
||||
)),
|
||||
Offstage(
|
||||
offstage: !widget.showMaximize,
|
||||
child: ActionIcon(
|
||||
message: isMaximized ? "Restore" : "Maximize",
|
||||
icon: isMaximized ? IconFont.restore : IconFont.max,
|
||||
onTap: _toggleMaximize,
|
||||
isClose: false,
|
||||
)),
|
||||
child: Obx(() => ActionIcon(
|
||||
message: widget.isMaximized.value ? "Restore" : "Maximize",
|
||||
icon: widget.isMaximized.value
|
||||
? IconFont.restore
|
||||
: IconFont.max,
|
||||
onTap: _toggleMaximize,
|
||||
isClose: false,
|
||||
))),
|
||||
Offstage(
|
||||
offstage: !widget.showClose,
|
||||
child: ActionIcon(
|
||||
@ -484,9 +494,9 @@ class WindowActionPanelState extends State<WindowActionPanel>
|
||||
|
||||
void _toggleMaximize() {
|
||||
toggleMaximize(widget.isMainWindow).then((maximize) {
|
||||
if (isMaximized != maximize) {
|
||||
// setState for sub window, wc.unmaximize/maximize() will not invoke onWindowMaximize/Unmaximize
|
||||
setState(() => isMaximized = !isMaximized);
|
||||
if (widget.isMaximized.value != maximize) {
|
||||
// update state for sub window, wc.unmaximize/maximize() will not invoke onWindowMaximize/Unmaximize
|
||||
widget.isMaximized.value = maximize;
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -801,13 +811,15 @@ class ActionIcon extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final Function() onTap;
|
||||
final bool isClose;
|
||||
const ActionIcon({
|
||||
Key? key,
|
||||
required this.message,
|
||||
required this.icon,
|
||||
required this.onTap,
|
||||
required this.isClose,
|
||||
}) : super(key: key);
|
||||
final double? size;
|
||||
const ActionIcon(
|
||||
{Key? key,
|
||||
required this.message,
|
||||
required this.icon,
|
||||
required this.onTap,
|
||||
required this.isClose,
|
||||
this.size})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -822,8 +834,8 @@ class ActionIcon extends StatelessWidget {
|
||||
onHover: (value) => hover.value = value,
|
||||
onTap: onTap,
|
||||
child: SizedBox(
|
||||
height: _kTabBarHeight - 1,
|
||||
width: _kTabBarHeight - 1,
|
||||
height: size ?? (_kTabBarHeight - 1),
|
||||
width: size ?? (_kTabBarHeight - 1),
|
||||
child: Icon(
|
||||
icon,
|
||||
color: hover.value && isClose
|
||||
|
@ -61,19 +61,36 @@ class ChatPage extends StatelessWidget implements PageShape {
|
||||
[],
|
||||
inputOptions: InputOptions(
|
||||
sendOnEnter: true,
|
||||
inputDecoration: defaultInputDecoration(
|
||||
hintText: "${translate('Write a message')}...",
|
||||
fillColor: Theme.of(context).backgroundColor),
|
||||
sendButtonBuilder: defaultSendButton(
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge!
|
||||
.color!),
|
||||
inputTextStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge
|
||||
?.color)),
|
||||
?.color),
|
||||
inputDecoration: isDesktop
|
||||
? InputDecoration(
|
||||
isDense: true,
|
||||
hintText:
|
||||
"${translate('Write a message')}...",
|
||||
filled: true,
|
||||
fillColor: Theme.of(context).backgroundColor,
|
||||
contentPadding: EdgeInsets.all(10),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
borderSide: const BorderSide(
|
||||
width: 0,
|
||||
style: BorderStyle.none,
|
||||
),
|
||||
),
|
||||
)
|
||||
: defaultInputDecoration(
|
||||
hintText:
|
||||
"${translate('Write a message')}...",
|
||||
fillColor: Theme.of(context).backgroundColor),
|
||||
sendButtonBuilder: defaultSendButton(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 6, vertical: 0),
|
||||
color: Theme.of(context).colorScheme.primary)),
|
||||
messageOptions: MessageOptions(
|
||||
showOtherUsersAvatar: false,
|
||||
showTime: true,
|
||||
|
@ -555,50 +555,3 @@ class BottomSheetBody extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SelectedItems {
|
||||
bool? _isLocal;
|
||||
final List<Entry> _items = [];
|
||||
|
||||
List<Entry> get items => _items;
|
||||
|
||||
int get length => _items.length;
|
||||
|
||||
bool? get isLocal => _isLocal;
|
||||
|
||||
add(bool isLocal, Entry e) {
|
||||
if (_isLocal == null) {
|
||||
_isLocal = isLocal;
|
||||
}
|
||||
if (_isLocal != null && _isLocal != isLocal) {
|
||||
return;
|
||||
}
|
||||
if (!_items.contains(e)) {
|
||||
_items.add(e);
|
||||
}
|
||||
}
|
||||
|
||||
bool contains(Entry e) {
|
||||
return _items.contains(e);
|
||||
}
|
||||
|
||||
remove(Entry e) {
|
||||
_items.remove(e);
|
||||
if (_items.length == 0) {
|
||||
_isLocal = null;
|
||||
}
|
||||
}
|
||||
|
||||
bool isOtherPage(bool currentIsLocal) {
|
||||
if (_isLocal == null) {
|
||||
return false;
|
||||
} else {
|
||||
return _isLocal != currentIsLocal;
|
||||
}
|
||||
}
|
||||
|
||||
clear() {
|
||||
_items.clear();
|
||||
_isLocal = null;
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:flutter_hbb/mobile/pages/file_manager_page.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:path/path.dart' as Path;
|
||||
|
||||
@ -1123,6 +1122,51 @@ class DirectoryOption {
|
||||
}
|
||||
}
|
||||
|
||||
class SelectedItems {
|
||||
bool? _isLocal;
|
||||
final List<Entry> _items = [];
|
||||
|
||||
List<Entry> get items => _items;
|
||||
|
||||
int get length => _items.length;
|
||||
|
||||
bool? get isLocal => _isLocal;
|
||||
|
||||
add(bool isLocal, Entry e) {
|
||||
_isLocal ??= isLocal;
|
||||
if (_isLocal != null && _isLocal != isLocal) {
|
||||
return;
|
||||
}
|
||||
if (!_items.contains(e)) {
|
||||
_items.add(e);
|
||||
}
|
||||
}
|
||||
|
||||
bool contains(Entry e) {
|
||||
return _items.contains(e);
|
||||
}
|
||||
|
||||
remove(Entry e) {
|
||||
_items.remove(e);
|
||||
if (_items.length == 0) {
|
||||
_isLocal = null;
|
||||
}
|
||||
}
|
||||
|
||||
bool isOtherPage(bool currentIsLocal) {
|
||||
if (_isLocal == null) {
|
||||
return false;
|
||||
} else {
|
||||
return _isLocal != currentIsLocal;
|
||||
}
|
||||
}
|
||||
|
||||
clear() {
|
||||
_items.clear();
|
||||
_isLocal = null;
|
||||
}
|
||||
}
|
||||
|
||||
// code from file_manager pkg after edit
|
||||
List<Entry> _sortList(List<Entry> list, SortBy sortType, bool ascending) {
|
||||
if (sortType == SortBy.Name) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user