fix: cloud sync (#769)

This commit is contained in:
lollipopkit🏳️‍⚧️
2025-06-04 00:11:31 +08:00
committed by GitHub
parent 9547d92ac5
commit 0c1ada0067
70 changed files with 2348 additions and 1906 deletions

View File

@@ -7,29 +7,27 @@ extension on _ServerPageState {
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ConstrainedBox(
constraints: BoxConstraints(maxWidth: _media.size.width / 2.3),
child: Hero(
tag: 'home_card_title_${s.spi.id}',
transitionOnUserGestures: true,
child: Material(
color: Colors.transparent,
child: Text(
s.spi.name,
style: UIs.text13Bold.copyWith(
color: context.isDark ? Colors.white : Colors.black,
LayoutBuilder(
builder: (_, cons) {
return ConstrainedBox(
constraints: BoxConstraints(maxWidth: cons.maxWidth / 2.3),
child: Hero(
tag: 'home_card_title_${s.spi.id}',
transitionOnUserGestures: true,
child: Material(
color: Colors.transparent,
child: Text(
s.spi.name,
style: UIs.text13Bold.copyWith(color: context.isDark ? Colors.white : Colors.black),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
),
),
const Icon(
Icons.keyboard_arrow_right,
size: 17,
color: Colors.grey,
);
},
),
const Icon(Icons.keyboard_arrow_right, size: 17, color: Colors.grey),
const Spacer(),
_buildTopRightText(s),
_buildTopRightWidget(s),
@@ -41,31 +39,31 @@ extension on _ServerPageState {
Widget _buildTopRightWidget(Server s) {
final (child, onTap) = switch (s.conn) {
ServerConn.connecting || ServerConn.loading || ServerConn.connected => (
SizedBox(
width: 19,
height: 19,
child: CircularProgressIndicator(
strokeWidth: 3,
valueColor: AlwaysStoppedAnimation(UIs.primaryColor),
),
SizedBox(
width: 19,
height: 19,
child: CircularProgressIndicator(
strokeWidth: 3,
valueColor: AlwaysStoppedAnimation(UIs.primaryColor),
),
null,
),
null,
),
ServerConn.failed => (
const Icon(Icons.refresh, size: 21, color: Colors.grey),
() {
TryLimiter.reset(s.spi.id);
ServerProvider.refresh(spi: s.spi);
},
),
const Icon(Icons.refresh, size: 21, color: Colors.grey),
() {
TryLimiter.reset(s.spi.id);
ServerProvider.refresh(spi: s.spi);
},
),
ServerConn.disconnected => (
const Icon(MingCute.link_3_line, size: 19, color: Colors.grey),
() => ServerProvider.refresh(spi: s.spi)
),
const Icon(MingCute.link_3_line, size: 19, color: Colors.grey),
() => ServerProvider.refresh(spi: s.spi),
),
ServerConn.finished => (
const Icon(MingCute.unlink_2_line, size: 17, color: Colors.grey),
() => ServerProvider.closeServer(id: s.spi.id),
),
const Icon(MingCute.unlink_2_line, size: 17, color: Colors.grey),
() => ServerProvider.closeServer(id: s.spi.id),
),
};
// Or the loading icon will be rescaled.
@@ -73,11 +71,7 @@ extension on _ServerPageState {
? child
: SizedBox(height: _ServerPageState._kCardHeightMin, width: 27, child: child);
if (onTap == null) return wrapped.paddingOnly(left: 10);
return InkWell(
borderRadius: BorderRadius.circular(7),
onTap: onTap,
child: wrapped,
).paddingOnly(left: 5);
return InkWell(borderRadius: BorderRadius.circular(7), onTap: onTap, child: wrapped).paddingOnly(left: 5);
}
Widget _buildTopRightText(Server s) {
@@ -94,7 +88,8 @@ extension on _ServerPageState {
}
void _showFailReason(ServerStatus ss) {
final md = '''
final md =
'''
${ss.err?.solution ?? l10n.unknown}
```sh
@@ -103,12 +98,7 @@ ${ss.err?.message ?? 'null'}
context.showRoundDialog(
title: libL10n.error,
child: SingleChildScrollView(child: SimpleMarkdown(data: md)),
actions: [
TextButton(
onPressed: () => Pfs.copy(md),
child: Text(libL10n.copy),
)
],
actions: [TextButton(onPressed: () => Pfs.copy(md), child: Text(libL10n.copy))],
);
}
@@ -156,13 +146,7 @@ ${ss.err?.message ?? 'null'}
);
}
Widget _buildIOData(
String up,
String down, {
void Function()? onTap,
Key? key,
int maxLines = 2
}) {
Widget _buildIOData(String up, String down, {void Function()? onTap, Key? key, int maxLines = 2}) {
final child = Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
@@ -181,7 +165,7 @@ ${ss.err?.message ?? 'null'}
textAlign: TextAlign.center,
textScaler: _textFactor,
maxLines: maxLines,
)
),
],
);
if (onTap == null) return child;

View File

@@ -28,9 +28,7 @@ extension on _ServerPageState {
Widget _buildLandscapeBody() {
return ServerProvider.serverOrder.listenVal((order) {
if (order.isEmpty) {
return Center(
child: Text(libL10n.empty, textAlign: TextAlign.center),
);
return Center(child: Text(libL10n.empty, textAlign: TextAlign.center));
}
return PageView.builder(
@@ -42,24 +40,18 @@ extension on _ServerPageState {
return srv.listenVal((srv) {
final title = _buildServerCardTitle(srv);
final List<Widget> children = [
title,
_buildNormalCard(srv.status, srv.spi),
];
final List<Widget> children = [title, _buildNormalCard(srv.status, srv.spi)];
return Padding(
padding: _media.padding,
child: ListenableBuilder(
listenable: _getCardNoti(id),
builder: (_, __) {
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: children,
);
},
),
return ListenableBuilder(
listenable: _getCardNoti(id),
builder: (_, __) {
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: children,
);
},
);
});
},

View File

@@ -37,18 +37,13 @@ class ServerPage extends StatefulWidget {
@override
State<ServerPage> createState() => _ServerPageState();
static const route = AppRouteNoArg(
page: ServerPage.new,
path: '/servers',
);
static const route = AppRouteNoArg(page: ServerPage.new, path: '/servers');
}
const _cardPad = 74.0;
const _cardPadSingle = 13.0;
class _ServerPageState extends State<ServerPage> with AutomaticKeepAliveClientMixin, AfterLayoutMixin {
late MediaQueryData _media;
late double _textFactorDouble;
double _offset = 1;
late TextScaler _textFactor;
@@ -80,7 +75,6 @@ class _ServerPageState extends State<ServerPage> with AutomaticKeepAliveClientMi
@override
void didChangeDependencies() {
super.didChangeDependencies();
_media = MediaQuery.of(context);
_updateOffset();
_updateTextScaler();
}
@@ -88,24 +82,22 @@ class _ServerPageState extends State<ServerPage> with AutomaticKeepAliveClientMi
@override
Widget build(BuildContext context) {
super.build(context);
return OrientationBuilder(builder: (_, orientation) {
if (orientation == Orientation.landscape) {
final useFullScreen = Stores.setting.fullScreen.fetch();
// Only enter landscape mode when the screen is wide enough and the
// full screen mode is enabled.
if (useFullScreen) return _buildLandscape();
}
return _buildPortrait();
});
return OrientationBuilder(
builder: (_, orientation) {
if (orientation == Orientation.landscape) {
final useFullScreen = Stores.setting.fullScreen.fetch();
// Only enter landscape mode when the screen is wide enough and the
// full screen mode is enabled.
if (useFullScreen) return _buildLandscape();
}
return _buildPortrait();
},
);
}
Widget _buildScaffold(Widget child) {
return Scaffold(
appBar: _TopBar(
tags: ServerProvider.tags,
onTagChanged: (p0) => _tag.value = p0,
initTag: _tag.value,
),
appBar: _TopBar(tags: ServerProvider.tags, onTagChanged: (p0) => _tag.value = p0, initTag: _tag.value),
body: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => _autoHideCtrl.show(),
@@ -134,27 +126,23 @@ class _ServerPageState extends State<ServerPage> with AutomaticKeepAliveClientMi
Widget _buildPortrait() {
// final isMobile = ResponsiveBreakpoints.of(context).isMobile;
return ServerProvider.serverOrder.listenVal(
(order) {
return _tag.listenVal(
(val) {
final filtered = _filterServers(order);
final child = _buildScaffold(_buildBodySmall(filtered: filtered));
// if (isMobile) {
return child;
// }
return ServerProvider.serverOrder.listenVal((order) {
return _tag.listenVal((val) {
final filtered = _filterServers(order);
final child = _buildScaffold(_buildBodySmall(filtered: filtered));
// if (isMobile) {
return child;
// }
// return SplitView(
// controller: _splitViewCtrl,
// leftWeight: 1,
// rightWeight: 1.3,
// initialRight: Center(child: CircularProgressIndicator()),
// leftBuilder: (_, __) => child,
// );
},
);
},
);
// return SplitView(
// controller: _splitViewCtrl,
// leftWeight: 1,
// rightWeight: 1.3,
// initialRight: Center(child: CircularProgressIndicator()),
// leftBuilder: (_, __) => child,
// );
});
});
}
Widget _buildBodySmall({
@@ -165,34 +153,38 @@ class _ServerPageState extends State<ServerPage> with AutomaticKeepAliveClientMi
return Center(child: Text(libL10n.empty, textAlign: TextAlign.center));
}
// Calculate number of columns based on available width
final columnsCount = math.max(1, (_media.size.width / UIs.columnWidth).floor());
return LayoutBuilder(
builder: (_, cons) {
// Calculate number of columns based on available width
final columnsCount = math.max(1, (cons.maxWidth / UIs.columnWidth).floor());
// Calculate number of rows needed
final rowCount = (filtered.length + columnsCount - 1) ~/ columnsCount;
return ListView.builder(
controller: _scrollController,
padding: padding,
itemCount: rowCount + 1, // +1 for the bottom space
itemBuilder: (_, rowIndex) {
// Bottom space
if (rowIndex == rowCount) return UIs.height77;
// Create a row of server cards
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: List.generate(columnsCount, (colIndex) {
final index = rowIndex * columnsCount + colIndex;
if (index >= filtered.length) return Expanded(child: Container());
final vnode = ServerProvider.pick(id: filtered[index]);
if (vnode == null) return Expanded(child: UIs.placeholder);
// Calculate which servers belong in this column
final serversInThisColumn = <String>[];
for (int i = colIndex; i < filtered.length; i += columnsCount) {
serversInThisColumn.add(filtered[i]);
}
final lens = serversInThisColumn.length;
return Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: vnode.listenVal(_buildEachServerCard),
child: ListView.builder(
controller: colIndex == 0 ? _scrollController : null,
padding: padding,
itemCount: lens + 1, // Add 1 for bottom spacing
itemBuilder: (context, index) {
// Last item is just spacing
if (index == lens) return SizedBox(height: 77);
final vnode = ServerProvider.pick(id: serversInThisColumn[index]);
if (vnode == null) return UIs.placeholder;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 0),
child: vnode.listenVal(_buildEachServerCard),
);
},
),
);
}),
@@ -227,13 +219,12 @@ class _ServerPageState extends State<ServerPage> with AutomaticKeepAliveClientMi
/// The child's width mat not equal to 1/4 of the screen width,
/// so we need to wrap it with a SizedBox.
Widget _wrapWithSizedbox(Widget child, double maxWidth, [bool circle = false]) {
return LayoutBuilder(builder: (_, cons) {
final width = (maxWidth - _cardPad) / 4;
return SizedBox(
width: width,
child: child,
);
});
return LayoutBuilder(
builder: (_, cons) {
final width = (maxWidth - _cardPad) / 4;
return SizedBox(width: width, child: child);
},
);
}
Widget _buildRealServerCard(Server srv) {
@@ -300,55 +291,52 @@ class _ServerPageState extends State<ServerPage> with AutomaticKeepAliveClientMi
icon: const Icon(Icons.edit, color: color),
text: libL10n.edit,
textStyle: textStyle,
)
),
];
return Padding(
padding: const EdgeInsets.only(top: 9),
child: LayoutBuilder(builder: (_, cons) {
final width = (cons.maxWidth - _cardPad) / children.length;
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: children.map((e) {
if (width == 0) return e;
return SizedBox(width: width, child: e);
}).toList(),
);
}),
child: LayoutBuilder(
builder: (_, cons) {
final width = (cons.maxWidth - _cardPad) / children.length;
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: children.map((e) {
if (width == 0) return e;
return SizedBox(width: width, child: e);
}).toList(),
);
},
),
);
}
Widget _buildNormalCard(ServerStatus ss, Spi spi) {
return LayoutBuilder(builder: (_, cons) {
final maxWidth = cons.maxWidth;
return Column(
mainAxisSize: MainAxisSize.min,
children: [
UIs.height13,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_wrapWithSizedbox(PercentCircle(percent: ss.cpu.usedPercent()), maxWidth, true),
_wrapWithSizedbox(
PercentCircle(percent: ss.mem.usedPercent * 100),
maxWidth,
true,
),
_wrapWithSizedbox(_buildNet(ss, spi.id), maxWidth),
_wrapWithSizedbox(_buildDisk(ss, spi.id), maxWidth),
],
),
UIs.height13,
if (Stores.setting.moveServerFuncs.fetch() &&
// Discussion #146
!Stores.setting.serverTabUseOldUI.fetch())
SizedBox(
height: 27,
child: ServerFuncBtns(spi: spi),
return LayoutBuilder(
builder: (_, cons) {
final maxWidth = cons.maxWidth;
return Column(
mainAxisSize: MainAxisSize.min,
children: [
UIs.height13,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_wrapWithSizedbox(PercentCircle(percent: ss.cpu.usedPercent()), maxWidth, true),
_wrapWithSizedbox(PercentCircle(percent: ss.mem.usedPercent * 100), maxWidth, true),
_wrapWithSizedbox(_buildNet(ss, spi.id), maxWidth),
_wrapWithSizedbox(_buildDisk(ss, spi.id), maxWidth),
],
),
],
);
});
UIs.height13,
if (Stores.setting.moveServerFuncs.fetch() &&
// Discussion #146
!Stores.setting.serverTabUseOldUI.fetch())
SizedBox(height: 27, child: ServerFuncBtns(spi: spi)),
],
);
},
);
}
@override

View File

@@ -164,7 +164,7 @@ extension _Utils on _ServerPageState {
void _updateOffset() {
if (!Stores.setting.fullScreenJitter.fetch()) return;
final x = _media.size.height * 0.03;
final x = MediaQuery.sizeOf(context).height * 0.03;
final r = math.Random().nextDouble();
final n = math.Random().nextBool() ? 1 : -1;
_offset = x * r * n;