mirror of
https://github.com/weyne85/rustdesk.git
synced 2025-10-29 17:00:05 +00:00
Merge pull request #1763 from Heap-Hop/opt/cm_and_file
opt desktop cm and file transfer UI
This commit is contained in:
@@ -3,6 +3,7 @@ import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
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';
|
||||
@@ -229,13 +230,13 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
final entries = fd.entries;
|
||||
final sortIndex = (SortBy style) {
|
||||
switch (style) {
|
||||
case SortBy.Name:
|
||||
case SortBy.name:
|
||||
return 0;
|
||||
case SortBy.Type:
|
||||
case SortBy.type:
|
||||
return 0;
|
||||
case SortBy.Modified:
|
||||
case SortBy.modified:
|
||||
return 1;
|
||||
case SortBy.Size:
|
||||
case SortBy.size:
|
||||
return 2;
|
||||
}
|
||||
}(model.getSortStyle(isLocal));
|
||||
@@ -265,7 +266,7 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
translate("Name"),
|
||||
).marginSymmetric(horizontal: 4),
|
||||
onSort: (columnIndex, ascending) {
|
||||
model.changeSortStyle(SortBy.Name,
|
||||
model.changeSortStyle(SortBy.name,
|
||||
isLocal: isLocal, ascending: ascending);
|
||||
}),
|
||||
DataColumn(
|
||||
@@ -273,13 +274,13 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
translate("Modified"),
|
||||
),
|
||||
onSort: (columnIndex, ascending) {
|
||||
model.changeSortStyle(SortBy.Modified,
|
||||
model.changeSortStyle(SortBy.modified,
|
||||
isLocal: isLocal, ascending: ascending);
|
||||
}),
|
||||
DataColumn(
|
||||
label: Text(translate("Size")),
|
||||
onSort: (columnIndex, ascending) {
|
||||
model.changeSortStyle(SortBy.Size,
|
||||
model.changeSortStyle(SortBy.size,
|
||||
isLocal: isLocal, ascending: ascending);
|
||||
}),
|
||||
],
|
||||
@@ -304,18 +305,25 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
waitDuration: Duration(milliseconds: 500),
|
||||
message: entry.name,
|
||||
child: Row(children: [
|
||||
Icon(
|
||||
entry.isFile
|
||||
? Icons.feed_outlined
|
||||
: entry.isDrive
|
||||
? Icons.computer
|
||||
: Icons.folder,
|
||||
size: 20,
|
||||
color: Theme.of(context)
|
||||
.iconTheme
|
||||
.color
|
||||
?.withOpacity(0.7),
|
||||
).marginSymmetric(horizontal: 2),
|
||||
entry.isDrive
|
||||
? Image(
|
||||
image: iconHardDrive,
|
||||
fit: BoxFit.scaleDown,
|
||||
color: Theme.of(context)
|
||||
.iconTheme
|
||||
.color
|
||||
?.withOpacity(0.7))
|
||||
.paddingAll(4)
|
||||
: 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))
|
||||
@@ -546,13 +554,6 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
model.goHome(isLocal: isLocal);
|
||||
},
|
||||
icon: const Icon(Icons.home_outlined),
|
||||
splashRadius: 20,
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
splashRadius: 20,
|
||||
@@ -649,6 +650,13 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
mainAxisAlignment:
|
||||
isLocal ? MainAxisAlignment.start : MainAxisAlignment.end,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
model.goHome(isLocal: isLocal);
|
||||
},
|
||||
icon: const Icon(Icons.home_outlined),
|
||||
splashRadius: 20,
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
final name = TextEditingController();
|
||||
@@ -786,14 +794,23 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: BreadCrumb(
|
||||
items: items,
|
||||
divider: Text("/",
|
||||
style: TextStyle(color: Theme.of(context).hintColor))
|
||||
.paddingSymmetric(horizontal: 2.0),
|
||||
overflow: ScrollableOverflow(
|
||||
controller: getBreadCrumbScrollController(isLocal)),
|
||||
)),
|
||||
child: Listener(
|
||||
// handle mouse wheel
|
||||
onPointerSignal: (e) {
|
||||
if (e is PointerScrollEvent) {
|
||||
final sc = getBreadCrumbScrollController(isLocal);
|
||||
sc.jumpTo(sc.offset + e.scrollDelta.dy / 4);
|
||||
}
|
||||
},
|
||||
child: BreadCrumb(
|
||||
items: items,
|
||||
divider: Text("/",
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).hintColor)),
|
||||
overflow: ScrollableOverflow(
|
||||
controller:
|
||||
getBreadCrumbScrollController(isLocal)),
|
||||
))),
|
||||
ActionIcon(
|
||||
message: "",
|
||||
icon: Icons.arrow_drop_down,
|
||||
@@ -833,15 +850,25 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
await model.fetchDirectory("/", isLocal, isLocal);
|
||||
for (var entry in fd.entries) {
|
||||
menuItems.add(MenuEntryButton(
|
||||
childBuilder: (TextStyle? style) => Text(
|
||||
entry.name,
|
||||
style: style,
|
||||
),
|
||||
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: () {
|
||||
openDirectory(entry.name, isLocal: isLocal);
|
||||
},
|
||||
dismissOnClicked: true));
|
||||
menuItems.add(MenuEntryDivider());
|
||||
}
|
||||
} finally {
|
||||
if (!isLocal) {
|
||||
@@ -849,7 +876,7 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
menuItems.add(MenuEntryDivider());
|
||||
mod_menu.showMenu(
|
||||
context: context,
|
||||
position: RelativeRect.fromLTRB(x, y, x, y),
|
||||
@@ -879,10 +906,11 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
final breadCrumbList = List<BreadCrumbItem>.empty(growable: true);
|
||||
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))))));
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -124,6 +124,22 @@ class ConnectionManagerState extends State<ConnectionManager> {
|
||||
showMinimize: true,
|
||||
showClose: true,
|
||||
controller: serverModel.tabController,
|
||||
maxLabelWidth: 100,
|
||||
tail: buildScrollJumper(),
|
||||
selectedTabBackgroundColor:
|
||||
Theme.of(context).hintColor.withOpacity(0.2),
|
||||
tabBuilder: (key, icon, label, themeConf) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
icon,
|
||||
Tooltip(
|
||||
message: key,
|
||||
waitDuration: Duration(seconds: 1),
|
||||
child: label),
|
||||
],
|
||||
);
|
||||
},
|
||||
pageViewBuilder: (pageView) => Row(children: [
|
||||
Expanded(child: pageView),
|
||||
Consumer<ChatModel>(
|
||||
@@ -158,6 +174,21 @@ class ConnectionManagerState extends State<ConnectionManager> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildScrollJumper() {
|
||||
final offstage = gFFI.serverModel.clients.length < 2;
|
||||
final sc = gFFI.serverModel.tabController.state.value.scrollController;
|
||||
return Offstage(
|
||||
offstage: offstage,
|
||||
child: Row(
|
||||
children: [
|
||||
ActionIcon(
|
||||
icon: Icons.arrow_left, iconSize: 22, onTap: sc.backward),
|
||||
ActionIcon(
|
||||
icon: Icons.arrow_right, iconSize: 22, onTap: sc.forward),
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Widget buildConnectionCard(Client client) {
|
||||
|
||||
@@ -3,12 +3,14 @@ import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart' hide TabBarTheme;
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/main.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:get/get_rx/src/rx_workers/utils/debouncer.dart';
|
||||
import 'package:scroll_pos/scroll_pos.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
@@ -132,7 +134,8 @@ class DesktopTabController {
|
||||
if (val.scrollController.hasClients &&
|
||||
val.scrollController.canScroll &&
|
||||
val.scrollController.itemCount > index) {
|
||||
val.scrollController.scrollToItem(index, center: true, animate: true);
|
||||
val.scrollController
|
||||
.scrollToItem(index, center: false, animate: true);
|
||||
}
|
||||
}));
|
||||
});
|
||||
@@ -175,6 +178,9 @@ typedef TabBuilder = Widget Function(
|
||||
String key, Widget icon, Widget label, TabThemeConf themeConf);
|
||||
typedef LabelGetter = Rx<String> Function(String key);
|
||||
|
||||
/// [_lastClickTime], help to handle double click
|
||||
int _lastClickTime = DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
class DesktopTab extends StatelessWidget {
|
||||
final Function(String)? onTabClose;
|
||||
final bool showTabBar;
|
||||
@@ -188,10 +194,14 @@ class DesktopTab extends StatelessWidget {
|
||||
final Future<bool> Function()? onWindowCloseButton;
|
||||
final TabBuilder? tabBuilder;
|
||||
final LabelGetter? labelGetter;
|
||||
final double? maxLabelWidth;
|
||||
final Color? selectedTabBackgroundColor;
|
||||
final Color? unSelectedTabBackgroundColor;
|
||||
|
||||
final DesktopTabController controller;
|
||||
Rx<DesktopTabState> get state => controller.state;
|
||||
final isMaximized = false.obs;
|
||||
final _scrollDebounce = Debouncer(delay: Duration(milliseconds: 50));
|
||||
|
||||
late final DesktopTabType tabType;
|
||||
late final bool isMainWindow;
|
||||
@@ -211,6 +221,9 @@ class DesktopTab extends StatelessWidget {
|
||||
this.onWindowCloseButton,
|
||||
this.tabBuilder,
|
||||
this.labelGetter,
|
||||
this.maxLabelWidth,
|
||||
this.selectedTabBackgroundColor,
|
||||
this.unSelectedTabBackgroundColor,
|
||||
}) : super(key: key) {
|
||||
tabType = controller.tabType;
|
||||
isMainWindow =
|
||||
@@ -292,46 +305,76 @@ class DesktopTab extends StatelessWidget {
|
||||
|
||||
Widget _buildBar() {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Offstage(
|
||||
offstage: !Platform.isMacOS,
|
||||
child: const SizedBox(
|
||||
width: 78,
|
||||
)),
|
||||
GestureDetector(
|
||||
onDoubleTap: showMaximize
|
||||
? () => toggleMaximize(isMainWindow)
|
||||
.then((value) => isMaximized.value = value)
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
// custom double tap handler
|
||||
onTap: showMaximize
|
||||
? () {
|
||||
final current = DateTime.now().millisecondsSinceEpoch;
|
||||
final elapsed = current - _lastClickTime;
|
||||
_lastClickTime = current;
|
||||
if (elapsed < kDesktopDoubleClickTimeMilli) {
|
||||
// onDoubleTap
|
||||
toggleMaximize(isMainWindow)
|
||||
.then((value) => isMaximized.value = value);
|
||||
}
|
||||
}
|
||||
: null,
|
||||
onPanStart: (_) => startDragging(isMainWindow),
|
||||
child: Row(children: [
|
||||
Offstage(
|
||||
offstage: !showLogo,
|
||||
child: SvgPicture.asset(
|
||||
'assets/logo.svg',
|
||||
width: 16,
|
||||
height: 16,
|
||||
)),
|
||||
Offstage(
|
||||
offstage: !showTitle,
|
||||
child: const Text(
|
||||
"RustDesk",
|
||||
style: TextStyle(fontSize: 13),
|
||||
).marginOnly(left: 2))
|
||||
]).marginOnly(
|
||||
left: 5,
|
||||
right: 10,
|
||||
)),
|
||||
_ListView(
|
||||
controller: controller,
|
||||
onTabClose: onTabClose,
|
||||
tabBuilder: tabBuilder,
|
||||
labelGetter: labelGetter,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Offstage(
|
||||
offstage: !Platform.isMacOS,
|
||||
child: const SizedBox(
|
||||
width: 78,
|
||||
)),
|
||||
Row(children: [
|
||||
Offstage(
|
||||
offstage: !showLogo,
|
||||
child: SvgPicture.asset(
|
||||
'assets/logo.svg',
|
||||
width: 16,
|
||||
height: 16,
|
||||
)),
|
||||
Offstage(
|
||||
offstage: !showTitle,
|
||||
child: const Text(
|
||||
"RustDesk",
|
||||
style: TextStyle(fontSize: 13),
|
||||
).marginOnly(left: 2))
|
||||
]).marginOnly(
|
||||
left: 5,
|
||||
right: 10,
|
||||
),
|
||||
Expanded(
|
||||
child: Listener(
|
||||
// handle mouse wheel
|
||||
onPointerSignal: (e) {
|
||||
if (e is PointerScrollEvent) {
|
||||
final sc =
|
||||
controller.state.value.scrollController;
|
||||
if (!sc.canScroll) return;
|
||||
_scrollDebounce.call(() {
|
||||
sc.animateTo(sc.offset + e.scrollDelta.dy,
|
||||
duration: Duration(milliseconds: 200),
|
||||
curve: Curves.ease);
|
||||
});
|
||||
}
|
||||
},
|
||||
child: _ListView(
|
||||
controller: controller,
|
||||
onTabClose: onTabClose,
|
||||
tabBuilder: tabBuilder,
|
||||
labelGetter: labelGetter,
|
||||
maxLabelWidth: maxLabelWidth,
|
||||
selectedTabBackgroundColor:
|
||||
selectedTabBackgroundColor,
|
||||
unSelectedTabBackgroundColor:
|
||||
unSelectedTabBackgroundColor))),
|
||||
],
|
||||
))),
|
||||
WindowActionPanel(
|
||||
isMainWindow: isMainWindow,
|
||||
tabType: tabType,
|
||||
@@ -435,14 +478,9 @@ class WindowActionPanelState extends State<WindowActionPanel>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Expanded(
|
||||
child: Row(
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onDoubleTap: widget.showMaximize ? _toggleMaximize : null,
|
||||
onPanStart: (_) => startDragging(widget.isMainWindow),
|
||||
)),
|
||||
Offstage(offstage: widget.tail == null, child: widget.tail),
|
||||
Offstage(
|
||||
offstage: !widget.showMinimize,
|
||||
@@ -489,7 +527,7 @@ class WindowActionPanelState extends State<WindowActionPanel>
|
||||
isClose: true,
|
||||
)),
|
||||
],
|
||||
));
|
||||
);
|
||||
}
|
||||
|
||||
void _toggleMaximize() {
|
||||
@@ -580,13 +618,15 @@ Future<bool> closeConfirmDialog() async {
|
||||
return res == true;
|
||||
}
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class _ListView extends StatelessWidget {
|
||||
final DesktopTabController controller;
|
||||
final Function(String key)? onTabClose;
|
||||
|
||||
final TabBuilder? tabBuilder;
|
||||
final LabelGetter? labelGetter;
|
||||
final double? maxLabelWidth;
|
||||
final Color? selectedTabBackgroundColor;
|
||||
final Color? unSelectedTabBackgroundColor;
|
||||
|
||||
Rx<DesktopTabState> get state => controller.state;
|
||||
|
||||
@@ -594,7 +634,10 @@ class _ListView extends StatelessWidget {
|
||||
{required this.controller,
|
||||
required this.onTabClose,
|
||||
this.tabBuilder,
|
||||
this.labelGetter});
|
||||
this.labelGetter,
|
||||
this.maxLabelWidth,
|
||||
this.selectedTabBackgroundColor,
|
||||
this.unSelectedTabBackgroundColor});
|
||||
|
||||
/// Check whether to show ListView
|
||||
///
|
||||
@@ -636,7 +679,7 @@ class _ListView extends StatelessWidget {
|
||||
onSelected: () => controller.jumpTo(index),
|
||||
tabBuilder: tabBuilder == null
|
||||
? null
|
||||
: (Widget icon, Widget labelWidget,
|
||||
: (String key, Widget icon, Widget labelWidget,
|
||||
TabThemeConf themeConf) {
|
||||
return tabBuilder!(
|
||||
tab.label,
|
||||
@@ -645,6 +688,9 @@ class _ListView extends StatelessWidget {
|
||||
themeConf,
|
||||
);
|
||||
},
|
||||
maxLabelWidth: maxLabelWidth,
|
||||
selectedTabBackgroundColor: selectedTabBackgroundColor,
|
||||
unSelectedTabBackgroundColor: unSelectedTabBackgroundColor,
|
||||
);
|
||||
}).toList()));
|
||||
}
|
||||
@@ -659,8 +705,10 @@ class _Tab extends StatefulWidget {
|
||||
final int selected;
|
||||
final Function() onClose;
|
||||
final Function() onSelected;
|
||||
final Widget Function(Widget icon, Widget label, TabThemeConf themeConf)?
|
||||
tabBuilder;
|
||||
final TabBuilder? tabBuilder;
|
||||
final double? maxLabelWidth;
|
||||
final Color? selectedTabBackgroundColor;
|
||||
final Color? unSelectedTabBackgroundColor;
|
||||
|
||||
const _Tab({
|
||||
Key? key,
|
||||
@@ -673,6 +721,9 @@ class _Tab extends StatefulWidget {
|
||||
required this.selected,
|
||||
required this.onClose,
|
||||
required this.onSelected,
|
||||
this.maxLabelWidth,
|
||||
this.selectedTabBackgroundColor,
|
||||
this.unSelectedTabBackgroundColor,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -697,14 +748,17 @@ class _TabState extends State<_Tab> with RestorationMixin {
|
||||
: MyTheme.tabbar(context).unSelectedTabIconColor,
|
||||
).paddingOnly(right: 5));
|
||||
final labelWidget = Obx(() {
|
||||
return Text(
|
||||
translate(widget.label.value),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: isSelected
|
||||
? MyTheme.tabbar(context).selectedTextColor
|
||||
: MyTheme.tabbar(context).unSelectedTextColor),
|
||||
);
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: widget.maxLabelWidth ?? 200),
|
||||
child: Text(
|
||||
translate(widget.label.value),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: isSelected
|
||||
? MyTheme.tabbar(context).selectedTextColor
|
||||
: MyTheme.tabbar(context).unSelectedTextColor),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
));
|
||||
});
|
||||
|
||||
if (widget.tabBuilder == null) {
|
||||
@@ -716,8 +770,8 @@ class _TabState extends State<_Tab> with RestorationMixin {
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return widget.tabBuilder!(
|
||||
icon, labelWidget, TabThemeConf(iconSize: _kIconSize));
|
||||
return widget.tabBuilder!(widget.label.value, icon, labelWidget,
|
||||
TabThemeConf(iconSize: _kIconSize));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -734,32 +788,36 @@ class _TabState extends State<_Tab> with RestorationMixin {
|
||||
restoreHover.value = value;
|
||||
},
|
||||
onTap: () => widget.onSelected(),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: _kTabBarHeight,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
_buildTabContent(),
|
||||
Obx((() => _CloseButton(
|
||||
visiable: hover.value && widget.closable,
|
||||
tabSelected: isSelected,
|
||||
onClose: () => widget.onClose(),
|
||||
)))
|
||||
])).paddingSymmetric(horizontal: 10),
|
||||
Offstage(
|
||||
offstage: !showDivider,
|
||||
child: VerticalDivider(
|
||||
width: 1,
|
||||
indent: _kDividerIndent,
|
||||
endIndent: _kDividerIndent,
|
||||
color: MyTheme.tabbar(context).dividerColor,
|
||||
thickness: 1,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
child: Container(
|
||||
color: isSelected
|
||||
? widget.selectedTabBackgroundColor
|
||||
: widget.unSelectedTabBackgroundColor,
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: _kTabBarHeight,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
_buildTabContent(),
|
||||
Obx((() => _CloseButton(
|
||||
visiable: hover.value && widget.closable,
|
||||
tabSelected: isSelected,
|
||||
onClose: () => widget.onClose(),
|
||||
)))
|
||||
])).paddingSymmetric(horizontal: 10),
|
||||
Offstage(
|
||||
offstage: !showDivider,
|
||||
child: VerticalDivider(
|
||||
width: 1,
|
||||
indent: _kDividerIndent,
|
||||
endIndent: _kDividerIndent,
|
||||
color: MyTheme.tabbar(context).dividerColor,
|
||||
thickness: 1,
|
||||
),
|
||||
)
|
||||
],
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -807,7 +865,7 @@ class _CloseButton extends StatelessWidget {
|
||||
}
|
||||
|
||||
class ActionIcon extends StatelessWidget {
|
||||
final String message;
|
||||
final String? message;
|
||||
final IconData icon;
|
||||
final Function() onTap;
|
||||
final bool isClose;
|
||||
@@ -815,7 +873,7 @@ class ActionIcon extends StatelessWidget {
|
||||
final double boxSize;
|
||||
const ActionIcon(
|
||||
{Key? key,
|
||||
required this.message,
|
||||
this.message,
|
||||
required this.icon,
|
||||
required this.onTap,
|
||||
this.isClose = false,
|
||||
@@ -827,7 +885,7 @@ class ActionIcon extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
RxBool hover = false.obs;
|
||||
return Obx(() => Tooltip(
|
||||
message: translate(message),
|
||||
message: message != null ? translate(message!) : "",
|
||||
waitDuration: const Duration(seconds: 1),
|
||||
child: InkWell(
|
||||
hoverColor: isClose
|
||||
|
||||
Reference in New Issue
Block a user