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(
|
appBar: CustomAppBar(
|
||||||
onPanUpdate: onPanUpdate,
|
onPanUpdate: onPanUpdate,
|
||||||
appBar: isDesktop
|
appBar: isDesktop
|
||||||
? _buildDesktopAppBar()
|
? _buildDesktopAppBar(context)
|
||||||
: _buildMobileAppBar(context),
|
: _buildMobileAppBar(context),
|
||||||
),
|
),
|
||||||
body: ChatPage(chatModel: chatModel),
|
body: ChatPage(chatModel: chatModel),
|
||||||
@ -82,33 +82,33 @@ class DraggableChatWindow extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDesktopAppBar() {
|
Widget _buildDesktopAppBar(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
color: MyTheme.accent50,
|
decoration: BoxDecoration(
|
||||||
height: 35,
|
border: Border(
|
||||||
|
bottom: BorderSide(
|
||||||
|
color: Theme.of(context).hintColor.withOpacity(0.4)))),
|
||||||
|
height: 38,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 8),
|
||||||
child: Text(
|
child: Row(children: [
|
||||||
translate("Chat"),
|
Icon(Icons.chat_bubble_outline,
|
||||||
style: const TextStyle(
|
size: 20, color: Theme.of(context).colorScheme.primary),
|
||||||
color: Colors.white,
|
SizedBox(width: 6),
|
||||||
fontFamily: 'WorkSans',
|
Text(translate("Chat"))
|
||||||
fontWeight: FontWeight.bold),
|
])),
|
||||||
)),
|
Padding(
|
||||||
Row(
|
padding: EdgeInsets.all(2),
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
child: ActionIcon(
|
||||||
children: [
|
|
||||||
ActionIcon(
|
|
||||||
message: 'Close',
|
message: 'Close',
|
||||||
icon: IconFont.close,
|
icon: IconFont.close,
|
||||||
onTap: chatModel.hideChatWindowOverlay,
|
onTap: chatModel.hideChatWindowOverlay,
|
||||||
isClose: true,
|
isClose: true,
|
||||||
)
|
size: 32,
|
||||||
],
|
))
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -31,6 +31,8 @@ const int kMobileMaxDisplayHeight = 1280;
|
|||||||
const int kDesktopMaxDisplayWidth = 1920;
|
const int kDesktopMaxDisplayWidth = 1920;
|
||||||
const int kDesktopMaxDisplayHeight = 1080;
|
const int kDesktopMaxDisplayHeight = 1080;
|
||||||
|
|
||||||
|
const int kDesktopDoubleClickTimeMilli = 200;
|
||||||
|
|
||||||
const Size kConnectionManagerWindowSize = Size(300, 400);
|
const Size kConnectionManagerWindowSize = Size(300, 400);
|
||||||
// Tabbar transition duration, now we remove the duration
|
// Tabbar transition duration, now we remove the duration
|
||||||
const Duration kTabTransitionDuration = Duration.zero;
|
const Duration kTabTransitionDuration = Duration.zero;
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:desktop_drop/desktop_drop.dart';
|
import 'package:desktop_drop/desktop_drop.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_breadcrumb/flutter_breadcrumb.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:flutter_hbb/models/file_model.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
@ -44,15 +45,17 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
|
|
||||||
final _locationStatusLocal = LocationStatus.bread.obs;
|
final _locationStatusLocal = LocationStatus.bread.obs;
|
||||||
final _locationStatusRemote = LocationStatus.bread.obs;
|
final _locationStatusRemote = LocationStatus.bread.obs;
|
||||||
final FocusNode _locationNodeLocal =
|
final _locationNodeLocal = FocusNode(debugLabel: "locationNodeLocal");
|
||||||
FocusNode(debugLabel: "locationNodeLocal");
|
final _locationNodeRemote = FocusNode(debugLabel: "locationNodeRemote");
|
||||||
final FocusNode _locationNodeRemote =
|
|
||||||
FocusNode(debugLabel: "locationNodeRemote");
|
|
||||||
final _searchTextLocal = "".obs;
|
final _searchTextLocal = "".obs;
|
||||||
final _searchTextRemote = "".obs;
|
final _searchTextRemote = "".obs;
|
||||||
final _breadCrumbScrollerLocal = ScrollController();
|
final _breadCrumbScrollerLocal = ScrollController();
|
||||||
final _breadCrumbScrollerRemote = 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
|
final _dropMaskVisible = false.obs; // TODO impl drop mask
|
||||||
|
|
||||||
ScrollController getBreadCrumbScrollController(bool isLocal) {
|
ScrollController getBreadCrumbScrollController(bool isLocal) {
|
||||||
@ -171,22 +174,6 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget body({bool isLocal = false}) {
|
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(
|
return Container(
|
||||||
decoration: BoxDecoration(border: Border.all(color: Colors.black26)),
|
decoration: BoxDecoration(border: Border.all(color: Colors.black26)),
|
||||||
margin: const EdgeInsets.all(16.0),
|
margin: const EdgeInsets.all(16.0),
|
||||||
@ -208,126 +195,7 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
controller: ScrollController(),
|
controller: ScrollController(),
|
||||||
child: ObxValue<RxString>(
|
child: _buildDataTable(context, isLocal),
|
||||||
(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,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@ -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
|
/// transfer status list
|
||||||
/// watch transfer status
|
/// watch transfer status
|
||||||
Widget statusList() {
|
Widget statusList() {
|
||||||
@ -369,6 +414,7 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Tooltip(
|
Tooltip(
|
||||||
|
waitDuration: Duration(milliseconds: 500),
|
||||||
message: item.jobName,
|
message: item.jobName,
|
||||||
child: Text(
|
child: Text(
|
||||||
item.jobName,
|
item.jobName,
|
||||||
@ -787,8 +833,10 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
|
|
||||||
onSearchText(String searchText, bool isLocal) {
|
onSearchText(String searchText, bool isLocal) {
|
||||||
if (isLocal) {
|
if (isLocal) {
|
||||||
|
_localSelectedItems.clear();
|
||||||
_searchTextLocal.value = searchText;
|
_searchTextLocal.value = searchText;
|
||||||
} else {
|
} else {
|
||||||
|
_remoteSelectedItems.clear();
|
||||||
_searchTextRemote.value = searchText;
|
_searchTextRemote.value = searchText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -191,6 +191,8 @@ class DesktopTab extends StatelessWidget {
|
|||||||
|
|
||||||
final DesktopTabController controller;
|
final DesktopTabController controller;
|
||||||
Rx<DesktopTabState> get state => controller.state;
|
Rx<DesktopTabState> get state => controller.state;
|
||||||
|
final isMaximized = false.obs;
|
||||||
|
|
||||||
late final DesktopTabType tabType;
|
late final DesktopTabType tabType;
|
||||||
late final bool isMainWindow;
|
late final bool isMainWindow;
|
||||||
|
|
||||||
@ -299,8 +301,10 @@ class DesktopTab extends StatelessWidget {
|
|||||||
width: 78,
|
width: 78,
|
||||||
)),
|
)),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onDoubleTap: () =>
|
onDoubleTap: showMaximize
|
||||||
showMaximize ? toggleMaximize(isMainWindow) : null,
|
? () => toggleMaximize(isMainWindow)
|
||||||
|
.then((value) => isMaximized.value = value)
|
||||||
|
: null,
|
||||||
onPanStart: (_) => startDragging(isMainWindow),
|
onPanStart: (_) => startDragging(isMainWindow),
|
||||||
child: Row(children: [
|
child: Row(children: [
|
||||||
Offstage(
|
Offstage(
|
||||||
@ -333,6 +337,7 @@ class DesktopTab extends StatelessWidget {
|
|||||||
tabType: tabType,
|
tabType: tabType,
|
||||||
state: state,
|
state: state,
|
||||||
tail: tail,
|
tail: tail,
|
||||||
|
isMaximized: isMaximized,
|
||||||
showMinimize: showMinimize,
|
showMinimize: showMinimize,
|
||||||
showMaximize: showMaximize,
|
showMaximize: showMaximize,
|
||||||
showClose: showClose,
|
showClose: showClose,
|
||||||
@ -347,6 +352,7 @@ class WindowActionPanel extends StatefulWidget {
|
|||||||
final bool isMainWindow;
|
final bool isMainWindow;
|
||||||
final DesktopTabType tabType;
|
final DesktopTabType tabType;
|
||||||
final Rx<DesktopTabState> state;
|
final Rx<DesktopTabState> state;
|
||||||
|
final RxBool isMaximized;
|
||||||
|
|
||||||
final bool showMinimize;
|
final bool showMinimize;
|
||||||
final bool showMaximize;
|
final bool showMaximize;
|
||||||
@ -359,6 +365,7 @@ class WindowActionPanel extends StatefulWidget {
|
|||||||
required this.isMainWindow,
|
required this.isMainWindow,
|
||||||
required this.tabType,
|
required this.tabType,
|
||||||
required this.state,
|
required this.state,
|
||||||
|
required this.isMaximized,
|
||||||
this.tail,
|
this.tail,
|
||||||
this.showMinimize = true,
|
this.showMinimize = true,
|
||||||
this.showMaximize = true,
|
this.showMaximize = true,
|
||||||
@ -374,30 +381,31 @@ class WindowActionPanel extends StatefulWidget {
|
|||||||
|
|
||||||
class WindowActionPanelState extends State<WindowActionPanel>
|
class WindowActionPanelState extends State<WindowActionPanel>
|
||||||
with MultiWindowListener, WindowListener {
|
with MultiWindowListener, WindowListener {
|
||||||
bool isMaximized = false;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
DesktopMultiWindow.addListener(this);
|
DesktopMultiWindow.addListener(this);
|
||||||
windowManager.addListener(this);
|
windowManager.addListener(this);
|
||||||
|
|
||||||
if (widget.isMainWindow) {
|
Future.delayed(Duration(milliseconds: 500), () {
|
||||||
windowManager.isMaximized().then((maximized) {
|
if (widget.isMainWindow) {
|
||||||
if (isMaximized != maximized) {
|
windowManager.isMaximized().then((maximized) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback(
|
if (widget.isMaximized.value != maximized) {
|
||||||
(_) => setState(() => isMaximized = maximized));
|
WidgetsBinding.instance.addPostFrameCallback(
|
||||||
}
|
(_) => setState(() => widget.isMaximized.value = maximized));
|
||||||
});
|
}
|
||||||
} else {
|
});
|
||||||
final wc = WindowController.fromWindowId(windowId!);
|
} else {
|
||||||
wc.isMaximized().then((maximized) {
|
final wc = WindowController.fromWindowId(windowId!);
|
||||||
if (isMaximized != maximized) {
|
wc.isMaximized().then((maximized) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback(
|
debugPrint("isMaximized $maximized");
|
||||||
(_) => setState(() => isMaximized = maximized));
|
if (widget.isMaximized.value != maximized) {
|
||||||
}
|
WidgetsBinding.instance.addPostFrameCallback(
|
||||||
});
|
(_) => setState(() => widget.isMaximized.value = maximized));
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -410,8 +418,8 @@ class WindowActionPanelState extends State<WindowActionPanel>
|
|||||||
@override
|
@override
|
||||||
void onWindowMaximize() {
|
void onWindowMaximize() {
|
||||||
// catch maximize from system
|
// catch maximize from system
|
||||||
if (!isMaximized) {
|
if (!widget.isMaximized.value) {
|
||||||
setState(() => isMaximized = true);
|
widget.isMaximized.value = true;
|
||||||
}
|
}
|
||||||
super.onWindowMaximize();
|
super.onWindowMaximize();
|
||||||
}
|
}
|
||||||
@ -419,8 +427,8 @@ class WindowActionPanelState extends State<WindowActionPanel>
|
|||||||
@override
|
@override
|
||||||
void onWindowUnmaximize() {
|
void onWindowUnmaximize() {
|
||||||
// catch unmaximize from system
|
// catch unmaximize from system
|
||||||
if (isMaximized) {
|
if (widget.isMaximized.value) {
|
||||||
setState(() => isMaximized = false);
|
widget.isMaximized.value = false;
|
||||||
}
|
}
|
||||||
super.onWindowUnmaximize();
|
super.onWindowUnmaximize();
|
||||||
}
|
}
|
||||||
@ -452,12 +460,14 @@ class WindowActionPanelState extends State<WindowActionPanel>
|
|||||||
)),
|
)),
|
||||||
Offstage(
|
Offstage(
|
||||||
offstage: !widget.showMaximize,
|
offstage: !widget.showMaximize,
|
||||||
child: ActionIcon(
|
child: Obx(() => ActionIcon(
|
||||||
message: isMaximized ? "Restore" : "Maximize",
|
message: widget.isMaximized.value ? "Restore" : "Maximize",
|
||||||
icon: isMaximized ? IconFont.restore : IconFont.max,
|
icon: widget.isMaximized.value
|
||||||
onTap: _toggleMaximize,
|
? IconFont.restore
|
||||||
isClose: false,
|
: IconFont.max,
|
||||||
)),
|
onTap: _toggleMaximize,
|
||||||
|
isClose: false,
|
||||||
|
))),
|
||||||
Offstage(
|
Offstage(
|
||||||
offstage: !widget.showClose,
|
offstage: !widget.showClose,
|
||||||
child: ActionIcon(
|
child: ActionIcon(
|
||||||
@ -484,9 +494,9 @@ class WindowActionPanelState extends State<WindowActionPanel>
|
|||||||
|
|
||||||
void _toggleMaximize() {
|
void _toggleMaximize() {
|
||||||
toggleMaximize(widget.isMainWindow).then((maximize) {
|
toggleMaximize(widget.isMainWindow).then((maximize) {
|
||||||
if (isMaximized != maximize) {
|
if (widget.isMaximized.value != maximize) {
|
||||||
// setState for sub window, wc.unmaximize/maximize() will not invoke onWindowMaximize/Unmaximize
|
// update state for sub window, wc.unmaximize/maximize() will not invoke onWindowMaximize/Unmaximize
|
||||||
setState(() => isMaximized = !isMaximized);
|
widget.isMaximized.value = maximize;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -801,13 +811,15 @@ class ActionIcon extends StatelessWidget {
|
|||||||
final IconData icon;
|
final IconData icon;
|
||||||
final Function() onTap;
|
final Function() onTap;
|
||||||
final bool isClose;
|
final bool isClose;
|
||||||
const ActionIcon({
|
final double? size;
|
||||||
Key? key,
|
const ActionIcon(
|
||||||
required this.message,
|
{Key? key,
|
||||||
required this.icon,
|
required this.message,
|
||||||
required this.onTap,
|
required this.icon,
|
||||||
required this.isClose,
|
required this.onTap,
|
||||||
}) : super(key: key);
|
required this.isClose,
|
||||||
|
this.size})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -822,8 +834,8 @@ class ActionIcon extends StatelessWidget {
|
|||||||
onHover: (value) => hover.value = value,
|
onHover: (value) => hover.value = value,
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: _kTabBarHeight - 1,
|
height: size ?? (_kTabBarHeight - 1),
|
||||||
width: _kTabBarHeight - 1,
|
width: size ?? (_kTabBarHeight - 1),
|
||||||
child: Icon(
|
child: Icon(
|
||||||
icon,
|
icon,
|
||||||
color: hover.value && isClose
|
color: hover.value && isClose
|
||||||
|
|||||||
@ -61,19 +61,36 @@ class ChatPage extends StatelessWidget implements PageShape {
|
|||||||
[],
|
[],
|
||||||
inputOptions: InputOptions(
|
inputOptions: InputOptions(
|
||||||
sendOnEnter: true,
|
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(
|
inputTextStyle: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
.titleLarge
|
.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(
|
messageOptions: MessageOptions(
|
||||||
showOtherUsersAvatar: false,
|
showOtherUsersAvatar: false,
|
||||||
showTime: true,
|
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/material.dart';
|
||||||
import 'package:flutter_hbb/common.dart';
|
import 'package:flutter_hbb/common.dart';
|
||||||
import 'package:flutter_hbb/mobile/pages/file_manager_page.dart';
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:path/path.dart' as Path;
|
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
|
// code from file_manager pkg after edit
|
||||||
List<Entry> _sortList(List<Entry> list, SortBy sortType, bool ascending) {
|
List<Entry> _sortList(List<Entry> list, SortBy sortType, bool ascending) {
|
||||||
if (sortType == SortBy.Name) {
|
if (sortType == SortBy.Name) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user