Files
MWClash/lib/views/about.dart
chen08209 31de2e51bc Fix windows some issues
Optimize overwrite handle

Optimize access control page

Optimize some details
2025-12-09 16:28:10 +08:00

252 lines
6.7 KiB
Dart

import 'dart:async';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/providers/config.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/list.dart';
import 'package:fl_clash/widgets/scaffold.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@immutable
class Contributor {
final String avatar;
final String name;
final String link;
const Contributor({
required this.avatar,
required this.name,
required this.link,
});
}
class AboutView extends StatelessWidget {
const AboutView({super.key});
Future<void> _checkUpdate(BuildContext context) async {
final data = await globalState.appController.safeRun<Map<String, dynamic>?>(
request.checkForUpdate,
title: appLocalizations.checkUpdate,
needLoading: true,
);
globalState.appController.checkUpdateResultHandle(data: data, isUser: true);
}
List<Widget> _buildMoreSection(BuildContext context) {
return generateSection(
separated: false,
title: appLocalizations.more,
items: [
ListItem(
title: Text(appLocalizations.checkUpdate),
onTap: () {
_checkUpdate(context);
},
),
ListItem(
title: const Text('Telegram'),
onTap: () {
globalState.openUrl('https://t.me/FlClash');
},
trailing: const Icon(Icons.launch),
),
ListItem(
title: Text(appLocalizations.project),
onTap: () {
globalState.openUrl('https://github.com/$repository');
},
trailing: const Icon(Icons.launch),
),
ListItem(
title: Text(appLocalizations.core),
onTap: () {
globalState.openUrl(
'https://github.com/chen08209/Clash.Meta/tree/FlClash',
);
},
trailing: const Icon(Icons.launch),
),
],
);
}
List<Widget> _buildContributorsSection() {
const contributors = [
Contributor(
avatar: 'assets/images/avatar/june2.jpg',
name: 'June2',
link: 'https://t.me/Jibadong',
),
Contributor(
avatar: 'assets/images/avatar/arue.jpg',
name: 'Arue',
link: 'https://t.me/xrcm6868',
),
];
return generateSection(
separated: false,
title: appLocalizations.otherContributors,
items: [
ListItem(
title: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Wrap(
spacing: 24,
children: [
for (final contributor in contributors)
Avatar(contributor: contributor),
],
),
),
),
],
);
}
@override
Widget build(BuildContext context) {
final items = [
ListTile(
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Consumer(
builder: (_, ref, _) {
return _DeveloperModeDetector(
child: Wrap(
spacing: 16,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(12),
child: Image.asset(
'assets/images/icon.png',
width: 64,
height: 64,
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
appName,
style: Theme.of(context).textTheme.headlineSmall,
),
Text(
globalState.packageInfo.version,
style: Theme.of(context).textTheme.labelLarge,
),
],
),
],
),
onEnterDeveloperMode: () {
ref
.read(appSettingProvider.notifier)
.updateState(
(state) => state.copyWith(developerMode: true),
);
context.showNotifier(
appLocalizations.developerModeEnableTip,
);
},
);
},
),
const SizedBox(height: 24),
Text(
appLocalizations.desc,
style: Theme.of(context).textTheme.bodySmall,
),
],
),
),
const SizedBox(height: 12),
..._buildContributorsSection(),
..._buildMoreSection(context),
];
return BaseScaffold(
title: appLocalizations.about,
body: Padding(
padding: kMaterialListPadding.copyWith(top: 16, bottom: 16),
child: generateListView(items),
),
);
}
}
class Avatar extends StatelessWidget {
final Contributor contributor;
const Avatar({super.key, required this.contributor});
@override
Widget build(BuildContext context) {
return GestureDetector(
child: Column(
children: [
SizedBox(
width: 36,
height: 36,
child: CircleAvatar(
foregroundImage: AssetImage(contributor.avatar),
),
),
const SizedBox(height: 4),
Text(contributor.name, style: context.textTheme.bodySmall),
],
),
onTap: () {
globalState.openUrl(contributor.link);
},
);
}
}
class _DeveloperModeDetector extends StatefulWidget {
final Widget child;
final VoidCallback onEnterDeveloperMode;
const _DeveloperModeDetector({
required this.child,
required this.onEnterDeveloperMode,
});
@override
State<_DeveloperModeDetector> createState() => _DeveloperModeDetectorState();
}
class _DeveloperModeDetectorState extends State<_DeveloperModeDetector> {
int _counter = 0;
Timer? _timer;
void _handleTap() {
_counter++;
if (_counter >= 5) {
widget.onEnterDeveloperMode();
_resetCounter();
} else {
_timer?.cancel();
_timer = Timer(Duration(seconds: 1), _resetCounter);
}
}
void _resetCounter() {
_counter = 0;
_timer?.cancel();
_timer = null;
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return GestureDetector(onTap: _handleTap, child: widget.child);
}
}