mirror of
https://mirror.ghproxy.com/https://github.com/StarCitizenToolBox/app.git
synced 2024-12-23 11:13:46 +08:00
feat:关于页面 FlowNumberText
This commit is contained in:
parent
1c73217ea7
commit
b950d5dc8a
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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");
|
||||||
|
54
lib/widgets/src/flow_number_text.dart
Normal file
54
lib/widgets/src/flow_number_text.dart
Normal 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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 []);
|
||||||
|
Loading…
Reference in New Issue
Block a user