Initial commit

This commit is contained in:
admin1
2026-07-14 11:11:36 +08:00
commit 656499cf94
604 changed files with 119518 additions and 0 deletions
+164
View File
@@ -0,0 +1,164 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:forui/forui.dart';
import '../../core/contracts/auth.dart';
import '../../core/contracts/server.dart';
import '../../providers/item_counts_provider.dart';
import '../../providers/server_connectivity_provider.dart';
import '../../providers/server_provider.dart';
import '../../providers/server_sync_provider.dart';
import '../../providers/session_provider.dart';
import 'add_server_modal.dart';
import 'app_dialog.dart';
import 'icon_picker_dialog.dart';
import 'server_change_password_dialog.dart';
typedef ServerActionCallbacks = ({
VoidCallback? onRefresh,
VoidCallback? onChangeIcon,
VoidCallback? onEdit,
VoidCallback? onChangePassword,
VoidCallback? onTogglePause,
VoidCallback? onDelete,
});
ServerActionCallbacks buildServerActionCallbacks({
required BuildContext context,
required WidgetRef ref,
required EmbyServer? server,
AuthedSession? session,
}) {
Future<void> changeIcon(EmbyServer server) async {
final picked = await showIconPickerDialog(
context,
currentIconUrl: server.iconUrl,
);
if (picked == null) return;
await ref
.read(serverListProvider.notifier)
.save(
EmbyServerSaveReq(
id: server.id,
name: server.name,
baseUrl: server.baseUrl,
iconUrl: picked,
),
);
}
Future<void> togglePause(EmbyServer server) async {
final wasPaused = server.paused;
final sessions = ref.read(sessionProvider).value;
final wasActive = sessions?.activeServerId == server.id;
await ref.read(serverListProvider.notifier).togglePause(server.id);
if (!wasPaused && wasActive) {
final updatedServers = ref.read(serverListProvider).value ?? const [];
final pausedIds = {
for (final s in updatedServers)
if (s.paused) s.id,
};
final nextSession = sessions?.sessions
.where(
(s) => s.serverId != server.id && !pausedIds.contains(s.serverId),
)
.firstOrNull;
if (nextSession != null) {
await ref
.read(sessionProvider.notifier)
.switchSession(nextSession.serverId);
} else {
await ref.read(sessionProvider.notifier).logout(serverId: server.id);
}
}
await ref.read(serverSyncProvider.notifier).upload();
}
Future<void> delete(EmbyServer server) async {
final confirmed = await showAppConfirmDialog(
context,
title: '删除服务器',
body: Text('确定删除「${server.name}」?此操作不可撤销。'),
confirmLabel: '删除',
destructive: true,
);
if (!confirmed) return;
await ref.read(serverListProvider.notifier).delete(server.id);
await ref.read(sessionProvider.notifier).refresh();
}
final s = server;
return (
onRefresh: s == null
? null
: () {
ref.invalidate(serverConnectivityProvider(s.baseUrl));
ref.invalidate(serverItemCountsProvider(s.id));
},
onChangeIcon: s == null ? null : () => changeIcon(s),
onEdit: s == null ? null : () => showAddServerModal(context, initial: s),
onChangePassword: session == null
? null
: () => showServerChangePasswordDialog(
context: context,
serverId: session.serverId,
serverName: s?.name ?? session.serverName,
),
onTogglePause: s == null ? null : () => togglePause(s),
onDelete: s == null ? null : () => delete(s),
);
}
List<FItem> buildServerActionMenuItems({
VoidCallback? onRefresh,
VoidCallback? onChangeIcon,
VoidCallback? onEdit,
VoidCallback? onChangePassword,
bool paused = false,
VoidCallback? onTogglePause,
VoidCallback? onDelete,
}) => [
if (onRefresh != null)
FItem(
prefix: const Icon(Icons.refresh_outlined, size: 16),
title: const Text('刷新'),
onPress: onRefresh,
),
if (onChangeIcon != null)
FItem(
prefix: const Icon(Icons.image_outlined, size: 16),
title: const Text('更换图标'),
onPress: onChangeIcon,
),
if (onEdit != null)
FItem(
prefix: const Icon(Icons.edit_outlined, size: 16),
title: const Text('编辑'),
onPress: onEdit,
),
if (onChangePassword != null)
FItem(
prefix: const Icon(Icons.lock_outline, size: 16),
title: const Text('修改密码'),
onPress: onChangePassword,
),
if (onTogglePause != null)
FItem(
prefix: Icon(
paused ? Icons.play_circle_outline : Icons.pause_circle_outline,
size: 16,
),
title: Text(paused ? '恢复使用' : '暂停使用'),
onPress: onTogglePause,
),
if (onDelete != null)
FItem(
variant: FItemVariant.destructive,
prefix: const Icon(Icons.delete_outline, size: 16),
title: const Text('删除'),
onPress: onDelete,
),
];