feat:关于页面 FlowNumberText

This commit is contained in:
xkeyC 2024-03-11 20:43:16 +08:00
parent 1c73217ea7
commit b950d5dc8a
4 changed files with 244 additions and 83 deletions

View File

@ -1,3 +1,5 @@
import 'dart:convert';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:starcitizen_doctor/common/conf/url_conf.dart'; import 'package:starcitizen_doctor/common/conf/url_conf.dart';
import 'package:starcitizen_doctor/common/io/rs_http.dart'; import 'package:starcitizen_doctor/common/io/rs_http.dart';
@ -17,4 +19,12 @@ class AnalyticsApi {
dPrint("AnalyticsApi.touch === $key Error:$e"); dPrint("AnalyticsApi.touch === $key Error:$e");
} }
} }
static Future<Map<String,dynamic>> getAnalyticsData() async {
final r = await RSHttp.get("${URLConf.analyticsApiHome}/analytics");
if (r.data == null) return {};
final jsonData = json.decode(utf8.decode(r.data!));
dPrint("AnalyticsApi.getAnalyticsData");
return jsonData;
}
} }

View File

@ -2,10 +2,12 @@ import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:starcitizen_doctor/api/analytics.dart';
import 'package:starcitizen_doctor/app.dart'; import 'package:starcitizen_doctor/app.dart';
import 'package:starcitizen_doctor/common/conf/const_conf.dart'; import 'package:starcitizen_doctor/common/conf/const_conf.dart';
import 'package:starcitizen_doctor/common/conf/url_conf.dart'; import 'package:starcitizen_doctor/common/conf/url_conf.dart';
import 'package:starcitizen_doctor/common/utils/base_utils.dart'; import 'package:starcitizen_doctor/widgets/src/flow_number_text.dart';
import 'package:starcitizen_doctor/widgets/widgets.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
class AboutUI extends HookConsumerWidget { class AboutUI extends HookConsumerWidget {
@ -20,7 +22,7 @@ class AboutUI extends HookConsumerWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const Spacer(), const Spacer(),
const SizedBox(height: 64), const SizedBox(height: 32),
Image.asset("assets/app_logo.png", width: 128, height: 128), Image.asset("assets/app_logo.png", width: 128, height: 128),
const SizedBox(height: 6), const SizedBox(height: 6),
const Text( const Text(
@ -40,15 +42,56 @@ class AboutUI extends HookConsumerWidget {
borderRadius: BorderRadius.circular(12)), borderRadius: BorderRadius.circular(12)),
child: Padding( child: Padding(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(24),
child: Text( child: Column(
children: [
Text(
"不仅仅是汉化!\n\nSC汉化盒子是你探索宇宙的好帮手我们致力于为各位公民解决游戏中的常见问题并为社区汉化、性能调优、常用网站汉化 等操作提供便利。", "不仅仅是汉化!\n\nSC汉化盒子是你探索宇宙的好帮手我们致力于为各位公民解决游戏中的常见问题并为社区汉化、性能调优、常用网站汉化 等操作提供便利。",
style: TextStyle( style: TextStyle(
fontSize: 14, color: Colors.white.withOpacity(.9)), fontSize: 14, color: Colors.white.withOpacity(.9)),
), ),
],
),
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
makeAnalyticsWidget(context),
const SizedBox(height: 24),
makeLinksRow(),
const Spacer(),
Row( Row(
children: [
const Spacer(),
Container(
width: MediaQuery.of(context).size.width * .35,
decoration: BoxDecoration(
color: FluentTheme.of(context).cardColor.withOpacity(.01),
borderRadius: BorderRadius.circular(12)),
child: IconButton(
icon: Padding(
padding: const EdgeInsets.all(3),
child: Text(
isTipTextCn.value ? tipTextCN : tipTextEN,
textAlign: TextAlign.start,
style: TextStyle(
fontSize: 12, color: Colors.white.withOpacity(.9)),
),
),
onPressed: () {
isTipTextCn.value = !isTipTextCn.value;
},
),
),
const SizedBox(width: 12),
],
),
const SizedBox(height: 12),
],
),
);
}
Widget makeLinksRow() {
return Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
IconButton( IconButton(
@ -120,38 +163,6 @@ class AboutUI extends HookConsumerWidget {
}, },
), ),
], ],
),
const SizedBox(height: 24),
const Spacer(),
Row(
children: [
const Spacer(),
Container(
width: MediaQuery.of(context).size.width * .35,
decoration: BoxDecoration(
color: FluentTheme.of(context).cardColor.withOpacity(.03),
borderRadius: BorderRadius.circular(12)),
child: IconButton(
icon: Padding(
padding: const EdgeInsets.all(3),
child: Text(
isTipTextCn.value ? tipTextCN : tipTextEN,
textAlign: TextAlign.start,
style: TextStyle(
fontSize: 12, color: Colors.white.withOpacity(.9)),
),
),
onPressed: () {
isTipTextCn.value = !isTipTextCn.value;
},
),
),
const SizedBox(width: 12),
],
),
const SizedBox(height: 12),
],
),
); );
} }
@ -161,6 +172,78 @@ class AboutUI extends HookConsumerWidget {
static const tipTextCN = static const tipTextCN =
"这是一个非官方的星际公民工具,不隶属于 Cloud Imperium 公司集团。 本软件中非由其主机或用户创作的所有内容均为其各自所有者的财产。 \nStar Citizen®、Roberts Space Industries® 和 Cloud Imperium® 是 Cloud Imperium Rights LLC 的注册商标。"; "这是一个非官方的星际公民工具,不隶属于 Cloud Imperium 公司集团。 本软件中非由其主机或用户创作的所有内容均为其各自所有者的财产。 \nStar Citizen®、Roberts Space Industries® 和 Cloud Imperium® 是 Cloud Imperium Rights LLC 的注册商标。";
Widget makeAnalyticsWidget(BuildContext context) {
return LoadingWidget(
onLoadData: AnalyticsApi.getAnalyticsData,
autoRefreshDuration: const Duration(seconds: 60),
childBuilder: (BuildContext context, Map<String, dynamic> data) {
return Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (data["total"] is List)
for (var item in data["total"])
if (item is Map)
if ([
"launch",
"gameLaunch",
"firstLaunch",
"install_localization",
"performance_apply",
"p4k_download",
].contains(item["Type"]))
makeAnalyticsItem(
context: context,
name: item["Type"] as String,
value: item["Count"] as int)
],
);
},
);
}
Widget makeAnalyticsItem(
{required BuildContext context,
required String name,
required int value}) {
const names = {
"launch": "启动",
"gameLaunch": "启动游戏",
"firstLaunch": "独立用户",
"install_localization": "汉化安装",
"performance_apply": "性能调优",
"p4k_download": "P4K分流"
};
return Container(
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.only(left: 18, right: 18),
decoration: BoxDecoration(
color: FluentTheme.of(context).cardColor.withOpacity(.06),
borderRadius: BorderRadius.circular(12)),
child: Column(
children: [
Text(
names[name] ?? name,
style: TextStyle(fontSize: 13, color: Colors.white.withOpacity(.6)),
),
const SizedBox(height: 4),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
FlowNumberText(
targetValue: value,
style: const TextStyle(
fontSize: 20,
),
),
Text(" ${name == "firstLaunch" ? "" : ""}")
],
),
],
),
);
}
_onCheckUpdate(BuildContext context, WidgetRef ref) async { _onCheckUpdate(BuildContext context, WidgetRef ref) async {
if (ConstConf.isMSE) { if (ConstConf.isMSE) {
launchUrlString("ms-windows-store://pdp/?productid=9NF3SWFWNKL1"); launchUrlString("ms-windows-store://pdp/?productid=9NF3SWFWNKL1");

View File

@ -0,0 +1,54 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:intl/intl.dart';
class FlowNumberText extends HookConsumerWidget {
final int targetValue;
final Duration duration;
final TextStyle? style;
final Curve curve;
FlowNumberText(
{super.key,
required this.targetValue,
this.duration = const Duration(seconds: 1),
this.style,
this.curve = Curves.bounceOut});
final _formatter = NumberFormat.decimalPattern();
@override
Widget build(BuildContext context, WidgetRef ref) {
final value = useState<double>(0.0);
final timer = useState<Timer?>(null);
useEffect(() {
final totalTicks = duration.inMilliseconds ~/ 10;
var currentTick = 0;
if (value.value != 0) {
currentTick = (value.value / targetValue * totalTicks).toInt();
}
timer.value = Timer.periodic(const Duration(milliseconds: 10), (timer) {
final progress = curve.transform(currentTick / totalTicks);
value.value = (progress * targetValue).toDouble();
if (currentTick >= totalTicks) {
value.value = targetValue.toDouble();
timer.cancel();
} else {
currentTick++;
}
});
return timer.value?.cancel;
}, [targetValue]);
return Text(
_formatter.format(value.value.toInt()),
style: style,
);
}
}

View File

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:fluent_ui/fluent_ui.dart'; import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
@ -159,9 +161,14 @@ class LoadingWidget<T> extends HookConsumerWidget {
final T? data; final T? data;
final Future<T?> Function()? onLoadData; final Future<T?> Function()? onLoadData;
final Widget Function(BuildContext context, T data) childBuilder; final Widget Function(BuildContext context, T data) childBuilder;
final Duration? autoRefreshDuration;
const LoadingWidget( const LoadingWidget(
{super.key, this.data, required this.childBuilder, this.onLoadData}); {super.key,
this.data,
required this.childBuilder,
this.onLoadData,
this.autoRefreshDuration});
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
@ -170,7 +177,14 @@ class LoadingWidget<T> extends HookConsumerWidget {
useEffect(() { useEffect(() {
if (data == null && onLoadData != null) { if (data == null && onLoadData != null) {
_loadData(dataState, errorMsg); _loadData(dataState, errorMsg);
return null; }
if (autoRefreshDuration != null) {
final timer = Timer.periodic(autoRefreshDuration!, (timer) {
if (onLoadData != null) {
_loadData(dataState, errorMsg);
}
});
return timer.cancel;
} }
return null; return null;
}, const []); }, const []);