Support core status check and force restart Optimize proxies page and access page Update flutter and pub dependencies Update go version Optimize more details
390 lines
12 KiB
Dart
390 lines
12 KiB
Dart
import 'dart:math';
|
|
|
|
import 'package:defer_pointer/defer_pointer.dart';
|
|
import 'package:fl_clash/common/common.dart';
|
|
import 'package:fl_clash/enum/enum.dart';
|
|
import 'package:fl_clash/providers/providers.dart';
|
|
import 'package:fl_clash/state.dart';
|
|
import 'package:fl_clash/widgets/widgets.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import 'widgets/start_button.dart';
|
|
|
|
typedef _IsEditWidgetBuilder = Widget Function(bool isEdit);
|
|
|
|
class DashboardView extends ConsumerStatefulWidget {
|
|
const DashboardView({super.key});
|
|
|
|
@override
|
|
ConsumerState<DashboardView> createState() => _DashboardViewState();
|
|
}
|
|
|
|
class _DashboardViewState extends ConsumerState<DashboardView> {
|
|
final key = GlobalKey<SuperGridState>();
|
|
final _isEditNotifier = ValueNotifier<bool>(false);
|
|
final _addedWidgetsNotifier = ValueNotifier<List<GridItem>>([]);
|
|
|
|
@override
|
|
dispose() {
|
|
_isEditNotifier.dispose();
|
|
_addedWidgetsNotifier.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Widget _buildIsEdit(_IsEditWidgetBuilder builder) {
|
|
return ValueListenableBuilder(
|
|
valueListenable: _isEditNotifier,
|
|
builder: (_, isEdit, _) {
|
|
return builder(isEdit);
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<void> _handleConnection() async {
|
|
final coreStatus = ref.read(coreStatusProvider);
|
|
if (coreStatus == CoreStatus.connecting) {
|
|
return;
|
|
}
|
|
final tip = coreStatus == CoreStatus.connected
|
|
? appLocalizations.forceRestartCoreTip
|
|
: appLocalizations.restartCoreTip;
|
|
final res = await globalState.showMessage(message: TextSpan(text: tip));
|
|
if (res != true) {
|
|
return;
|
|
}
|
|
await Future.delayed(commonDuration);
|
|
globalState.appController.restartCore();
|
|
}
|
|
|
|
List<Widget> _buildActions(bool isEdit) {
|
|
return [
|
|
if (!isEdit)
|
|
Consumer(
|
|
builder: (_, ref, _) {
|
|
final coreStatus = ref.watch(coreStatusProvider);
|
|
return Tooltip(
|
|
message: appLocalizations.coreStatus,
|
|
child: FadeScaleBox(
|
|
alignment: Alignment.centerRight,
|
|
child: coreStatus == CoreStatus.connected
|
|
? IconButton.filled(
|
|
visualDensity: VisualDensity.compact,
|
|
iconSize: 20,
|
|
padding: EdgeInsets.zero,
|
|
style: IconButton.styleFrom(
|
|
backgroundColor: Colors.greenAccent,
|
|
foregroundColor: switch (Theme.brightnessOf(
|
|
context,
|
|
)) {
|
|
Brightness.light =>
|
|
context.colorScheme.onSurfaceVariant,
|
|
Brightness.dark =>
|
|
context.colorScheme.onPrimaryFixedVariant,
|
|
},
|
|
),
|
|
onPressed: _handleConnection,
|
|
icon: Icon(Icons.check, fontWeight: FontWeight.w900),
|
|
)
|
|
: FilledButton.icon(
|
|
key: ValueKey(coreStatus),
|
|
onPressed: _handleConnection,
|
|
style: FilledButton.styleFrom(
|
|
visualDensity: VisualDensity.compact,
|
|
padding: EdgeInsets.symmetric(horizontal: 12),
|
|
backgroundColor: switch (coreStatus) {
|
|
CoreStatus.connecting => null,
|
|
CoreStatus.connected => Colors.greenAccent,
|
|
CoreStatus.disconnected =>
|
|
context.colorScheme.error,
|
|
},
|
|
foregroundColor: switch (coreStatus) {
|
|
CoreStatus.connecting => null,
|
|
CoreStatus.connected => switch (Theme.brightnessOf(
|
|
context,
|
|
)) {
|
|
Brightness.light =>
|
|
context.colorScheme.onSurfaceVariant,
|
|
Brightness.dark => null,
|
|
},
|
|
CoreStatus.disconnected =>
|
|
context.colorScheme.onError,
|
|
},
|
|
),
|
|
icon: SizedBox(
|
|
height: globalState.measure.bodyMediumHeight,
|
|
width: globalState.measure.bodyMediumHeight,
|
|
child: switch (coreStatus) {
|
|
CoreStatus.connecting => Padding(
|
|
padding: EdgeInsets.all(2),
|
|
child: CircularProgressIndicator(
|
|
strokeWidth: 3,
|
|
color: context.colorScheme.onPrimary,
|
|
backgroundColor: Colors.transparent,
|
|
),
|
|
),
|
|
CoreStatus.connected => Icon(
|
|
Icons.check_sharp,
|
|
fontWeight: FontWeight.w900,
|
|
),
|
|
CoreStatus.disconnected => Icon(
|
|
Icons.restart_alt_sharp,
|
|
fontWeight: FontWeight.w900,
|
|
),
|
|
},
|
|
),
|
|
label: Text(switch (coreStatus) {
|
|
CoreStatus.connecting => appLocalizations.connecting,
|
|
CoreStatus.connected => appLocalizations.connected,
|
|
CoreStatus.disconnected =>
|
|
appLocalizations.disconnected,
|
|
}),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
if (isEdit)
|
|
ValueListenableBuilder(
|
|
valueListenable: _addedWidgetsNotifier,
|
|
builder: (_, addedChildren, child) {
|
|
if (addedChildren.isEmpty) {
|
|
return Container();
|
|
}
|
|
return child!;
|
|
},
|
|
child: IconButton(
|
|
onPressed: () {
|
|
_showAddWidgetsModal();
|
|
},
|
|
icon: Icon(Icons.add_circle),
|
|
),
|
|
),
|
|
FadeRotationScaleBox(
|
|
child: isEdit
|
|
? IconButton(
|
|
key: ValueKey(true),
|
|
icon: Icon(Icons.save, key: ValueKey('save-icon')),
|
|
onPressed: _handleUpdateIsEdit,
|
|
)
|
|
: IconButton(
|
|
key: ValueKey(false),
|
|
icon: Icon(Icons.edit, key: ValueKey('edit-icon')),
|
|
onPressed: _handleUpdateIsEdit,
|
|
),
|
|
),
|
|
];
|
|
}
|
|
|
|
void _showAddWidgetsModal() {
|
|
showSheet(
|
|
builder: (_, type) {
|
|
return ValueListenableBuilder(
|
|
valueListenable: _addedWidgetsNotifier,
|
|
builder: (_, value, _) {
|
|
return AdaptiveSheetScaffold(
|
|
type: type,
|
|
body: _AddDashboardWidgetModal(
|
|
items: value,
|
|
onAdd: (gridItem) {
|
|
key.currentState?.handleAdd(gridItem);
|
|
},
|
|
),
|
|
title: appLocalizations.add,
|
|
);
|
|
},
|
|
);
|
|
},
|
|
context: context,
|
|
);
|
|
}
|
|
|
|
Future<void> _handleUpdateIsEdit() async {
|
|
if (_isEditNotifier.value == true) {
|
|
await _handleSave();
|
|
}
|
|
_isEditNotifier.value = !_isEditNotifier.value;
|
|
}
|
|
|
|
Future<void> _handleSave() async {
|
|
final currentState = key.currentState;
|
|
if (currentState == null) {
|
|
return;
|
|
}
|
|
if (mounted) {
|
|
await currentState.isTransformCompleter;
|
|
final dashboardWidgets = currentState.children
|
|
.map((item) => DashboardWidget.getDashboardWidget(item))
|
|
.toList();
|
|
ref
|
|
.read(appSettingProvider.notifier)
|
|
.updateState(
|
|
(state) => state.copyWith(dashboardWidgets: dashboardWidgets),
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final dashboardState = ref.watch(dashboardStateProvider);
|
|
final columns = max(4 * ((dashboardState.contentWidth / 300).ceil()), 8);
|
|
final spacing = 16.ap;
|
|
final children = [
|
|
...dashboardState.dashboardWidgets
|
|
.where(
|
|
(item) => item.platforms.contains(SupportPlatform.currentPlatform),
|
|
)
|
|
.map((item) => item.widget),
|
|
];
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
_addedWidgetsNotifier.value = DashboardWidget.values
|
|
.where(
|
|
(item) =>
|
|
!children.contains(item.widget) &&
|
|
item.platforms.contains(SupportPlatform.currentPlatform),
|
|
)
|
|
.map((item) => item.widget)
|
|
.toList();
|
|
});
|
|
return _buildIsEdit(
|
|
(isEdit) => CommonScaffold(
|
|
title: appLocalizations.dashboard,
|
|
actions: _buildActions(isEdit),
|
|
floatingActionButton: const StartButton(),
|
|
body: Align(
|
|
alignment: Alignment.topCenter,
|
|
child: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(16).copyWith(bottom: 88),
|
|
child: isEdit
|
|
? SystemBackBlock(
|
|
child: CommonPopScope(
|
|
child: SuperGrid(
|
|
key: key,
|
|
crossAxisCount: columns,
|
|
crossAxisSpacing: spacing,
|
|
mainAxisSpacing: spacing,
|
|
children: [
|
|
...dashboardState.dashboardWidgets
|
|
.where(
|
|
(item) => item.platforms.contains(
|
|
SupportPlatform.currentPlatform,
|
|
),
|
|
)
|
|
.map((item) => item.widget),
|
|
],
|
|
onUpdate: () {
|
|
_handleSave();
|
|
},
|
|
),
|
|
onPop: (context) {
|
|
_handleUpdateIsEdit();
|
|
return false;
|
|
},
|
|
),
|
|
)
|
|
: Grid(
|
|
crossAxisCount: columns,
|
|
crossAxisSpacing: spacing,
|
|
mainAxisSpacing: spacing,
|
|
children: children,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _AddDashboardWidgetModal extends StatelessWidget {
|
|
final List<GridItem> items;
|
|
final Function(GridItem item) onAdd;
|
|
|
|
const _AddDashboardWidgetModal({required this.items, required this.onAdd});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return DeferredPointerHandler(
|
|
child: SingleChildScrollView(
|
|
padding: EdgeInsets.all(16),
|
|
child: Grid(
|
|
crossAxisCount: 8,
|
|
crossAxisSpacing: 16,
|
|
mainAxisSpacing: 16,
|
|
children: items
|
|
.map(
|
|
(item) => item.wrap(
|
|
builder: (child) {
|
|
return _AddedContainer(
|
|
onAdd: () {
|
|
onAdd(item);
|
|
},
|
|
child: child,
|
|
);
|
|
},
|
|
),
|
|
)
|
|
.toList(),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _AddedContainer extends StatefulWidget {
|
|
final Widget child;
|
|
final VoidCallback onAdd;
|
|
|
|
const _AddedContainer({required this.child, required this.onAdd});
|
|
|
|
@override
|
|
State<_AddedContainer> createState() => _AddedContainerState();
|
|
}
|
|
|
|
class _AddedContainerState extends State<_AddedContainer> {
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
}
|
|
|
|
@override
|
|
void didUpdateWidget(_AddedContainer oldWidget) {
|
|
super.didUpdateWidget(oldWidget);
|
|
if (oldWidget.child != widget.child) {}
|
|
}
|
|
|
|
Future<void> _handleAdd() async {
|
|
widget.onAdd();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Stack(
|
|
clipBehavior: Clip.none,
|
|
children: [
|
|
ActivateBox(child: widget.child),
|
|
Positioned(
|
|
top: -8,
|
|
right: -8,
|
|
child: DeferPointer(
|
|
child: SizedBox(
|
|
width: 24,
|
|
height: 24,
|
|
child: IconButton.filled(
|
|
iconSize: 20,
|
|
padding: EdgeInsets.all(2),
|
|
onPressed: _handleAdd,
|
|
icon: Icon(Icons.add),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|