feat:riverpod 迁移 HomeUI

This commit is contained in:
xkeyC 2024-03-09 21:53:37 +08:00
parent 248b416277
commit e70fca4899
10 changed files with 1635 additions and 5 deletions

View File

@ -22,7 +22,6 @@ import 'api/api.dart';
import 'common/helper/system_helper.dart'; import 'common/helper/system_helper.dart';
import 'common/io/rs_http.dart'; import 'common/io/rs_http.dart';
import 'common/rust/frb_generated.dart'; import 'common/rust/frb_generated.dart';
import 'common/utils/base_utils.dart';
import 'data/app_version_data.dart'; import 'data/app_version_data.dart';
import 'ui/index_ui.dart'; import 'ui/index_ui.dart';
import 'ui/settings/upgrade_dialog.dart'; import 'ui/settings/upgrade_dialog.dart';

738
lib/ui/home/home_ui.dart Normal file
View File

@ -0,0 +1,738 @@
import 'package:card_swiper/card_swiper.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:flutter_tilt/flutter_tilt.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:starcitizen_doctor/api/analytics.dart';
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
import 'package:starcitizen_doctor/widgets/widgets.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'home_ui_model.dart';
class HomeUI extends HookConsumerWidget {
const HomeUI({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final homeState = ref.watch(homeUIModelProvider);
final model = ref.watch(homeUIModelProvider.notifier);
return Stack(
children: [
Center(
child: SingleChildScrollView(
padding: EdgeInsets.zero,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (homeState.appPlacardData != null) ...[
InfoBar(
title: Text("${homeState.appPlacardData?.title}"),
content: Text("${homeState.appPlacardData?.content}"),
severity: InfoBarSeverity.info,
action: homeState.appPlacardData?.link == null
? null
: Button(
child: const Text('查看详情'),
onPressed: () => _showPlacard(),
),
onClose: homeState.appPlacardData?.alwaysShow == true
? null
: () => model.closePlacard(),
),
const SizedBox(height: 6),
],
...makeIndex(context, model, homeState)
],
),
),
),
if (homeState.isFixing)
Container(
decoration: BoxDecoration(
color: Colors.black.withAlpha(150),
),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const ProgressRing(),
const SizedBox(height: 12),
Text(homeState.isFixingString.isNotEmpty
? homeState.isFixingString
: "正在处理..."),
],
),
),
)
],
);
}
List<Widget> makeIndex(
BuildContext context, HomeUIModel model, HomeUIModelState homeState) {
const double width = 280;
return [
Stack(
children: [
Padding(
padding: const EdgeInsets.only(top: 64, bottom: 0),
child: SizedBox(
width: MediaQuery.of(context).size.width,
child: Center(
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(bottom: 30),
child: Image.asset(
"assets/sc_logo.png",
fit: BoxFit.fitHeight,
height: 260,
),
),
makeGameStatusCard(context, model, 340, homeState)
],
),
),
),
),
Positioned(
top: 0,
left: 24,
child: makeLeftColumn(context, model, width, homeState),
),
Positioned(
right: 24,
top: 0,
child: makeNewsCard(context, model, homeState),
),
],
),
const SizedBox(height: 24),
Padding(
padding: const EdgeInsets.only(left: 24, right: 24),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Text("安装位置:"),
const SizedBox(width: 6),
Expanded(
child: ComboBox<String>(
value: homeState.scInstalledPath,
items: [
const ComboBoxItem(
value: "not_install",
child: Text("未安装 或 安装失败"),
),
for (final path in homeState.scInstallPaths)
ComboBoxItem(
value: path,
child: Text(path),
)
],
onChanged: model.onChangeInstallPath,
),
),
const SizedBox(width: 12),
Button(
onPressed: homeState.webLocalizationVersionsData == null
? null
: () => model.launchRSI(context),
child: Padding(
padding: const EdgeInsets.all(6),
child: Icon(
homeState.isCurGameRunning
? FluentIcons.stop_solid
: FluentIcons.play,
color: homeState.isCurGameRunning
? Colors.red.withOpacity(.8)
: null,
),
)),
const SizedBox(width: 12),
Button(
child: const Padding(
padding: EdgeInsets.all(6),
child: Icon(FluentIcons.folder_open),
),
onPressed: () => SystemHelper.openDir(homeState.scInstalledPath),
),
const SizedBox(width: 12),
Button(
onPressed: model.reScanPath,
child: const Padding(
padding: EdgeInsets.all(6),
child: Icon(FluentIcons.refresh),
),
),
],
),
),
const SizedBox(height: 8),
Text(homeState.lastScreenInfo, maxLines: 1),
makeIndexActionLists(context, model, homeState),
];
}
Widget makeLeftColumn(BuildContext context, HomeUIModel model, double width,
HomeUIModelState homeState) {
return Stack(
children: [
Column(
children: [
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: FluentTheme.of(context).cardColor.withOpacity(.03),
),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
makeWebViewButton(context, model,
icon: SvgPicture.asset(
"assets/rsi.svg",
colorFilter: makeSvgColor(Colors.white),
height: 18,
),
name: "星际公民官网汉化",
webTitle: "星际公民官网汉化",
webURL: "https://robertsspaceindustries.com",
info: "罗伯茨航天工业公司,万物的起源",
useLocalization: true,
width: width,
touchKey: "webLocalization_rsi"),
const SizedBox(height: 12),
makeWebViewButton(context, model,
icon: Row(
children: [
SvgPicture.asset(
"assets/uex.svg",
height: 18,
),
const SizedBox(width: 12),
],
),
name: "UEX 汉化",
webTitle: "UEX 汉化",
webURL: "https://uexcorp.space/",
info: "采矿、精炼、贸易计算器、价格、船信息",
useLocalization: true,
width: width,
touchKey: "webLocalization_uex"),
const SizedBox(height: 12),
makeWebViewButton(context, model,
icon: Row(
children: [
Image.asset(
"assets/dps.png",
height: 20,
),
const SizedBox(width: 12),
],
),
name: "DPS计算器汉化",
webTitle: "DPS计算器汉化",
webURL: "https://www.erkul.games/live/calculator",
info: "在线改船,查询伤害数值和配件购买地点",
useLocalization: true,
width: width,
touchKey: "webLocalization_dps"),
const SizedBox(height: 12),
const Text("外部浏览器拓展:"),
const SizedBox(height: 12),
Row(
children: [
Button(
child:
const FaIcon(FontAwesomeIcons.chrome, size: 18),
onPressed: () {
launchUrlString(
"https://chrome.google.com/webstore/detail/gocnjckojmledijgmadmacoikibcggja?authuser=0&hl=zh-CN");
},
),
const SizedBox(width: 12),
Button(
child: const FaIcon(FontAwesomeIcons.edge, size: 18),
onPressed: () {
launchUrlString(
"https://microsoftedge.microsoft.com/addons/detail/lipbbcckldklpdcpfagicipecaacikgi");
},
),
const SizedBox(width: 12),
Button(
child: const FaIcon(FontAwesomeIcons.firefoxBrowser,
size: 18),
onPressed: () {
launchUrlString(
"https://addons.mozilla.org/zh-CN/firefox/"
"addon/%E6%98%9F%E9%99%85%E5%85%AC%E6%B0%91%E7%9B%92%E5%AD%90%E6%B5%8F%E8%A7%88%E5%99%A8%E6%8B%93%E5%B1%95/");
},
),
const SizedBox(width: 12),
Button(
child:
const FaIcon(FontAwesomeIcons.github, size: 18),
onPressed: () {
launchUrlString(
"https://github.com/StarCitizenToolBox/StarCitizenBoxBrowserEx");
},
),
],
)
],
),
),
),
const SizedBox(height: 16),
makeActivityBanner(context, model, width, homeState),
],
),
if (homeState.webLocalizationVersionsData == null)
Positioned.fill(
child: Container(
decoration: BoxDecoration(
color: Colors.black.withOpacity(.3),
borderRadius: BorderRadius.circular(12)),
child: const Center(
child: ProgressRing(),
),
))
],
);
}
Widget makeNewsCard(
BuildContext context, HomeUIModel model, HomeUIModelState homeState) {
return ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false),
child: Container(
width: 316,
height: 386,
decoration: BoxDecoration(
color: Colors.white.withOpacity(.1),
borderRadius: BorderRadius.circular(12)),
child: SingleChildScrollView(
child: Column(
children: [
SizedBox(
height: 190,
width: 316,
child: Tilt(
shadowConfig: const ShadowConfig(maxIntensity: .3),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(12),
topRight: Radius.circular(12),
),
child: homeState.rssVideoItems == null
? Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(.1)),
child: makeLoading(context),
)
: Swiper(
itemCount: homeState.rssVideoItems?.length ?? 0,
itemBuilder: (context, index) {
final item = homeState.rssVideoItems![index];
return GestureDetector(
onTap: () {
if (item.link != null) {
launchUrlString(item.link!);
}
},
child: CacheNetImage(
url: model.getRssImage(item),
fit: BoxFit.cover,
),
);
},
autoplay: true,
),
)),
const SizedBox(height: 1),
if (homeState.rssTextItems == null)
makeLoading(context)
else
ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
final item = homeState.rssTextItems![index];
return Tilt(
shadowConfig: const ShadowConfig(maxIntensity: .3),
borderRadius: BorderRadius.circular(12),
child: GestureDetector(
onTap: () {
if (item.link != null) {
launchUrlString(item.link!);
}
},
child: Padding(
padding: const EdgeInsets.only(
left: 12, right: 12, top: 4, bottom: 4),
child: Row(
children: [
getRssIcon(item.link ?? ""),
const SizedBox(width: 6),
Expanded(
child: Text(
model.handleTitle(item.title),
textAlign: TextAlign.start,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 12.2),
),
),
const SizedBox(width: 12),
Icon(
FluentIcons.chevron_right,
size: 12,
color: Colors.white.withOpacity(.4),
)
],
),
),
));
},
itemCount: homeState.rssTextItems?.length,
),
const SizedBox(height: 12),
],
),
)),
);
}
Widget getRssIcon(String url) {
if (url.startsWith("https://tieba.baidu.com")) {
return SvgPicture.asset("assets/tieba.svg", width: 14, height: 14);
}
if (url.startsWith("https://www.bilibili.com")) {
return const FaIcon(
FontAwesomeIcons.bilibili,
size: 14,
color: Color.fromRGBO(0, 161, 214, 1),
);
}
return const FaIcon(FontAwesomeIcons.rss, size: 14);
}
Widget makeIndexActionLists(
BuildContext context, HomeUIModel model, HomeUIModelState homeState) {
final items = [
_HomeItemData("auto_check", "一键诊断", "一键诊断星际公民常见问题",
FluentIcons.auto_deploy_settings),
_HomeItemData(
"localization", "汉化管理", "快捷安装汉化资源", FluentIcons.locale_language),
_HomeItemData("performance", "性能优化", "调整引擎配置文件,优化游戏性能",
FluentIcons.process_meta_task),
];
return Padding(
padding: const EdgeInsets.all(24),
child: AlignedGridView.count(
crossAxisCount: 3,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
itemCount: items.length,
shrinkWrap: true,
itemBuilder: (context, index) {
final item = items.elementAt(index);
return HoverButton(
onPressed: () => model.onMenuTap(item.key),
builder: (BuildContext context, Set<ButtonStates> states) {
return Container(
width: 300,
height: 120,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: states.isHovering
? FluentTheme.of(context).cardColor.withOpacity(.1)
: FluentTheme.of(context).cardColor,
),
child: Padding(
padding: const EdgeInsets.all(12),
child: Row(
children: [
Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(.2),
borderRadius: BorderRadius.circular(1000)),
child: Padding(
padding: const EdgeInsets.all(8),
child: Icon(
item.icon,
size: 26,
),
),
),
const SizedBox(width: 24),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.name,
style: const TextStyle(fontSize: 18),
),
const SizedBox(height: 4),
Text(item.infoString),
],
)),
if (item.key == "localization" &&
homeState.localizationUpdateInfo != null)
Container(
padding: const EdgeInsets.only(
top: 3, bottom: 3, left: 8, right: 8),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(12)),
child: Text(
homeState.localizationUpdateInfo?.key ?? " "),
),
const SizedBox(width: 12),
const Icon(
FluentIcons.chevron_right,
size: 16,
)
],
),
),
);
},
);
}),
);
}
Widget makeWebViewButton(BuildContext context, HomeUIModel model,
{required Widget icon,
required String name,
required String webTitle,
required String webURL,
required bool useLocalization,
required double width,
String? info,
String? touchKey}) {
return Tilt(
shadowConfig: const ShadowConfig(maxIntensity: .3),
borderRadius: BorderRadius.circular(12),
child: GestureDetector(
onTap: () {
if (touchKey != null) {
AnalyticsApi.touch(touchKey);
}
model.goWebView(webTitle, webURL, useLocalization: true);
},
child: Container(
width: width,
decoration: BoxDecoration(
color: FluentTheme.of(context).cardColor,
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
icon,
Text(
name,
style: const TextStyle(fontSize: 14),
),
],
),
if (info != null)
Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(
info,
style: TextStyle(
fontSize: 12,
color: Colors.white.withOpacity(.6)),
),
)
],
),
),
const SizedBox(width: 12),
Icon(
FluentIcons.chevron_right,
size: 14,
color: Colors.white.withOpacity(.6),
)
],
),
),
),
),
);
}
Widget makeGameStatusCard(BuildContext context, HomeUIModel model,
double width, HomeUIModelState homeState) {
const statusCnName = {
"Platform": "平台",
"Persistent Universe": "持续宇宙",
"Electronic Access": "电子访问",
"Arena Commander": "竞技场指挥官"
};
return Tilt(
shadowConfig: const ShadowConfig(maxIntensity: .2),
borderRadius: BorderRadius.circular(12),
child: GestureDetector(
onTap: () {
model.goWebView(
"RSI 服务器状态", "https://status.robertsspaceindustries.com/",
useLocalization: true);
},
child: Container(
width: width,
decoration: BoxDecoration(
color: FluentTheme.of(context).cardColor,
),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(children: [
if (homeState.scServerStatus == null)
makeLoading(context, width: 20)
else
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text("状态:"),
for (final item in homeState.scServerStatus ?? [])
Row(
children: [
SizedBox(
height: 14,
child: Center(
child: Icon(
FontAwesomeIcons.solidCircle,
color: model.isRSIServerStatusOK(item)
? Colors.green
: Colors.red,
size: 12,
),
),
),
const SizedBox(width: 5),
Text(
"${statusCnName[item["name"]] ?? item["name"]}",
style: const TextStyle(fontSize: 13),
),
],
),
Icon(
FluentIcons.chevron_right,
size: 12,
color: Colors.white.withOpacity(.4),
)
],
)
]),
),
),
),
);
}
Widget makeActivityBanner(BuildContext context, HomeUIModel model,
double width, HomeUIModelState homeState) {
return Tilt(
borderRadius: BorderRadius.circular(12),
shadowConfig: const ShadowConfig(disable: true),
child: GestureDetector(
onTap: () => _onTapFestival(),
child: Container(
width: width + 24,
decoration: BoxDecoration(color: FluentTheme.of(context).cardColor),
child: Padding(
padding:
const EdgeInsets.only(left: 12, right: 12, top: 8, bottom: 8),
child: (homeState.countdownFestivalListData == null)
? SizedBox(
width: width,
height: 62,
child: const Center(
child: ProgressRing(),
),
)
: SizedBox(
width: width,
height: 62,
child: Swiper(
itemCount: homeState.countdownFestivalListData!.length,
autoplay: true,
autoplayDelay: 5000,
itemBuilder: (context, index) {
final item =
homeState.countdownFestivalListData![index];
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
if (item.icon != null && item.icon != "") ...[
ClipRRect(
borderRadius: BorderRadius.circular(1000),
child: Image.asset(
"assets/countdown/${item.icon}",
width: 48,
height: 48,
fit: BoxFit.cover,
),
),
],
Column(
children: [
Text(
item.name ?? "",
style: const TextStyle(fontSize: 15),
),
const SizedBox(height: 3),
CountdownTimeText(
targetTime:
DateTime.fromMillisecondsSinceEpoch(
item.time ?? 0),
),
],
),
const SizedBox(width: 12),
Icon(
FluentIcons.chevron_right,
size: 14,
color: Colors.white.withOpacity(.6),
)
],
);
},
),
),
)),
),
);
}
_showPlacard() {}
_onTapFestival() {}
}
class _HomeItemData {
String key;
_HomeItemData(this.key, this.name, this.infoString, this.icon);
String name;
String infoString;
IconData icon;
}

View File

@ -0,0 +1,246 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:dart_rss/domain/rss_item.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/widgets.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hive/hive.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:starcitizen_doctor/api/analytics.dart';
import 'package:starcitizen_doctor/api/api.dart';
import 'package:starcitizen_doctor/api/rss.dart';
import 'package:starcitizen_doctor/common/conf/const_conf.dart';
import 'package:starcitizen_doctor/common/conf/url_conf.dart';
import 'package:starcitizen_doctor/common/helper/log_helper.dart';
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
import 'package:starcitizen_doctor/common/io/rs_http.dart';
import 'package:starcitizen_doctor/common/utils/base_utils.dart';
import 'package:starcitizen_doctor/common/utils/log.dart';
import 'package:starcitizen_doctor/common/utils/provider.dart';
import 'package:starcitizen_doctor/data/app_placard_data.dart';
import 'package:starcitizen_doctor/data/app_web_localization_versions_data.dart';
import 'package:starcitizen_doctor/data/countdown_festival_item_data.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:html/parser.dart' as html;
import 'package:html/dom.dart' as html_dom;
part 'home_ui_model.freezed.dart';
part 'home_ui_model.g.dart';
@freezed
class HomeUIModelState with _$HomeUIModelState {
factory HomeUIModelState({
AppPlacardData? appPlacardData,
@Default(false) bool isFixing,
@Default("") String isFixingString,
String? scInstalledPath,
@Default([]) List<String> scInstallPaths,
AppWebLocalizationVersionsData? webLocalizationVersionsData,
@Default(false) bool isCurGameRunning,
@Default("") String lastScreenInfo,
List<RssItem>? rssVideoItems,
List<RssItem>? rssTextItems,
MapEntry<String, bool>? localizationUpdateInfo,
List? scServerStatus,
List<CountdownFestivalItemData>? countdownFestivalListData,
}) = _HomeUIModelState;
}
@riverpod
class HomeUIModel extends _$HomeUIModel {
@override
HomeUIModelState build() {
state = HomeUIModelState();
_init();
_loadData();
return state;
}
closePlacard() async {
final box = await Hive.openBox("app_conf");
await box.put("close_placard", state.appPlacardData?.version);
state = state.copyWith(appPlacardData: null);
}
Future<void> reScanPath() async {
state = state.copyWith(
scInstalledPath: "not_install", lastScreenInfo: "正在扫描 ...");
try {
final listData = await SCLoggerHelper.getLauncherLogList();
if (listData == null) {
state = state.copyWith(scInstalledPath: "not_install");
return;
}
final scInstallPaths = await SCLoggerHelper.getGameInstallPath(listData,
withVersion: ["LIVE", "PTU", "EPTU"], checkExists: true);
String? scInstalledPath;
if (scInstallPaths.isNotEmpty) {
scInstalledPath = scInstallPaths.first;
}
final lastScreenInfo = "扫描完毕,共找到 ${scInstallPaths.length} 个有效安装目录";
state = state.copyWith(
scInstalledPath: scInstalledPath,
scInstallPaths: scInstallPaths,
lastScreenInfo: lastScreenInfo);
} catch (e) {
state = state.copyWith(
scInstalledPath: "not_install", lastScreenInfo: "解析 log 文件失败!");
AnalyticsApi.touch("error_launchLogs");
// showToast(context!,
// "解析 log 文件失败! \n请关闭游戏退出RSI启动器后重试若仍有问题请使用工具箱中的 RSI Launcher log 修复。");
}
}
String getRssImage(RssItem item) {
final h = html.parse(item.description ?? "");
if (h.body == null) return "";
for (var node in h.body!.nodes) {
if (node is html_dom.Element) {
if (node.localName == "img") {
return node.attributes["src"]?.trim() ?? "";
}
}
}
return "";
}
String handleTitle(String? title) {
if (title == null) return "";
title = title.replaceAll("", "[ ");
title = title.replaceAll("", " ] ");
return title;
}
onMenuTap(String key) {}
void goWebView(String webTitle, String webURL,
{required bool useLocalization}) {}
bool isRSIServerStatusOK(Map map) {
return (map["status"] == "ok" || map["status"] == "operational");
}
Timer? _serverUpdateTimer;
Timer? _appUpdateTimer;
void _init() {
reScanPath();
_serverUpdateTimer = Timer.periodic(
const Duration(minutes: 10),
(timer) {
_updateSCServerStatus();
},
);
_appUpdateTimer = Timer.periodic(const Duration(minutes: 30), (timer) {
_checkLocalizationUpdate();
});
ref.onDispose(() {
_serverUpdateTimer?.cancel();
_serverUpdateTimer = null;
_appUpdateTimer?.cancel();
_appUpdateTimer = null;
});
}
void _loadData() async {
if (appGlobalState.networkVersionData == null) return;
try {
final r = await Api.getAppPlacard();
final box = await Hive.openBox("app_conf");
final version = box.get("close_placard", defaultValue: "");
if (r.enable == true) {
if (r.alwaysShow != true && version == r.version) {
} else {
state = state.copyWith(appPlacardData: r);
}
}
final appWebLocalizationVersionsData =
AppWebLocalizationVersionsData.fromJson(json.decode(
(await RSHttp.getText(
"${URLConf.webTranslateHomeUrl}/versions.json"))));
final countdownFestivalListData = await Api.getFestivalCountdownList();
state = state.copyWith(
webLocalizationVersionsData: appWebLocalizationVersionsData,
countdownFestivalListData: countdownFestivalListData);
_updateSCServerStatus();
_loadRRS();
} catch (e) {
dPrint(e);
}
// check Localization update
_checkLocalizationUpdate();
}
Future<void> _updateSCServerStatus() async {
try {
final s = await Api.getScServerStatus();
dPrint("updateSCServerStatus===$s");
state = state.copyWith(scServerStatus: s);
} catch (e) {
dPrint(e);
}
}
Future _loadRRS() async {
try {
state = state.copyWith(
rssVideoItems: await RSSApi.getRssVideo(),
rssTextItems: await RSSApi.getRssText());
dPrint("RSS update Success !");
} catch (e) {
dPrint("_loadRRS Error:$e");
}
}
void _checkLocalizationUpdate() {}
// ignore: avoid_build_context_in_providers
launchRSI(BuildContext context) async {
if (state.scInstalledPath == "not_install") {
showToast(context, "该功能需要一个有效的安装位置");
return;
}
if (ConstConf.isMSE) {
if (state.isCurGameRunning) {
await Process.run(
SystemHelper.powershellPath, ["ps \"StarCitizen\" | kill"]);
return;
}
AnalyticsApi.touch("gameLaunch");
// showDialog(
// context: context,
// dismissWithEsc: false,
// builder: (context) {
// return BaseUIContainer(
// uiCreate: () => LoginDialog(),
// modelCreate: () => LoginDialogModel(scInstalledPath, this));
// });
} else {
final ok = await showConfirmDialogs(
context,
"一键启动功能提示",
const Text("为确保账户安全,一键启动功能已在开发版中禁用,我们将在微软商店版本中提供此功能。"
"\n\n微软商店版由微软提供可靠的分发下载与数字签名,可有效防止软件被恶意篡改。\n\n提示:您无需使用盒子启动游戏也可使用汉化。"),
confirm: "安装微软商店版本",
cancel: "取消");
if (ok == true) {
await launchUrlString(
"https://apps.microsoft.com/detail/9NF3SWFWNKL1?launch=true");
await Future.delayed(const Duration(seconds: 2));
exit(0);
}
}
}
void onChangeInstallPath(String? value) {
if (value == null) return;
state = state.copyWith(scInstalledPath: value);
}
}

View File

@ -0,0 +1,458 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'home_ui_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$HomeUIModelState {
AppPlacardData? get appPlacardData => throw _privateConstructorUsedError;
bool get isFixing => throw _privateConstructorUsedError;
String get isFixingString => throw _privateConstructorUsedError;
String? get scInstalledPath => throw _privateConstructorUsedError;
List<String> get scInstallPaths => throw _privateConstructorUsedError;
AppWebLocalizationVersionsData? get webLocalizationVersionsData =>
throw _privateConstructorUsedError;
bool get isCurGameRunning => throw _privateConstructorUsedError;
String get lastScreenInfo => throw _privateConstructorUsedError;
List<RssItem>? get rssVideoItems => throw _privateConstructorUsedError;
List<RssItem>? get rssTextItems => throw _privateConstructorUsedError;
MapEntry<String, bool>? get localizationUpdateInfo =>
throw _privateConstructorUsedError;
List<dynamic>? get scServerStatus => throw _privateConstructorUsedError;
List<CountdownFestivalItemData>? get countdownFestivalListData =>
throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$HomeUIModelStateCopyWith<HomeUIModelState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $HomeUIModelStateCopyWith<$Res> {
factory $HomeUIModelStateCopyWith(
HomeUIModelState value, $Res Function(HomeUIModelState) then) =
_$HomeUIModelStateCopyWithImpl<$Res, HomeUIModelState>;
@useResult
$Res call(
{AppPlacardData? appPlacardData,
bool isFixing,
String isFixingString,
String? scInstalledPath,
List<String> scInstallPaths,
AppWebLocalizationVersionsData? webLocalizationVersionsData,
bool isCurGameRunning,
String lastScreenInfo,
List<RssItem>? rssVideoItems,
List<RssItem>? rssTextItems,
MapEntry<String, bool>? localizationUpdateInfo,
List<dynamic>? scServerStatus,
List<CountdownFestivalItemData>? countdownFestivalListData});
}
/// @nodoc
class _$HomeUIModelStateCopyWithImpl<$Res, $Val extends HomeUIModelState>
implements $HomeUIModelStateCopyWith<$Res> {
_$HomeUIModelStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? appPlacardData = freezed,
Object? isFixing = null,
Object? isFixingString = null,
Object? scInstalledPath = freezed,
Object? scInstallPaths = null,
Object? webLocalizationVersionsData = freezed,
Object? isCurGameRunning = null,
Object? lastScreenInfo = null,
Object? rssVideoItems = freezed,
Object? rssTextItems = freezed,
Object? localizationUpdateInfo = freezed,
Object? scServerStatus = freezed,
Object? countdownFestivalListData = freezed,
}) {
return _then(_value.copyWith(
appPlacardData: freezed == appPlacardData
? _value.appPlacardData
: appPlacardData // ignore: cast_nullable_to_non_nullable
as AppPlacardData?,
isFixing: null == isFixing
? _value.isFixing
: isFixing // ignore: cast_nullable_to_non_nullable
as bool,
isFixingString: null == isFixingString
? _value.isFixingString
: isFixingString // ignore: cast_nullable_to_non_nullable
as String,
scInstalledPath: freezed == scInstalledPath
? _value.scInstalledPath
: scInstalledPath // ignore: cast_nullable_to_non_nullable
as String?,
scInstallPaths: null == scInstallPaths
? _value.scInstallPaths
: scInstallPaths // ignore: cast_nullable_to_non_nullable
as List<String>,
webLocalizationVersionsData: freezed == webLocalizationVersionsData
? _value.webLocalizationVersionsData
: webLocalizationVersionsData // ignore: cast_nullable_to_non_nullable
as AppWebLocalizationVersionsData?,
isCurGameRunning: null == isCurGameRunning
? _value.isCurGameRunning
: isCurGameRunning // ignore: cast_nullable_to_non_nullable
as bool,
lastScreenInfo: null == lastScreenInfo
? _value.lastScreenInfo
: lastScreenInfo // ignore: cast_nullable_to_non_nullable
as String,
rssVideoItems: freezed == rssVideoItems
? _value.rssVideoItems
: rssVideoItems // ignore: cast_nullable_to_non_nullable
as List<RssItem>?,
rssTextItems: freezed == rssTextItems
? _value.rssTextItems
: rssTextItems // ignore: cast_nullable_to_non_nullable
as List<RssItem>?,
localizationUpdateInfo: freezed == localizationUpdateInfo
? _value.localizationUpdateInfo
: localizationUpdateInfo // ignore: cast_nullable_to_non_nullable
as MapEntry<String, bool>?,
scServerStatus: freezed == scServerStatus
? _value.scServerStatus
: scServerStatus // ignore: cast_nullable_to_non_nullable
as List<dynamic>?,
countdownFestivalListData: freezed == countdownFestivalListData
? _value.countdownFestivalListData
: countdownFestivalListData // ignore: cast_nullable_to_non_nullable
as List<CountdownFestivalItemData>?,
) as $Val);
}
}
/// @nodoc
abstract class _$$HomeUIModelStateImplCopyWith<$Res>
implements $HomeUIModelStateCopyWith<$Res> {
factory _$$HomeUIModelStateImplCopyWith(_$HomeUIModelStateImpl value,
$Res Function(_$HomeUIModelStateImpl) then) =
__$$HomeUIModelStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{AppPlacardData? appPlacardData,
bool isFixing,
String isFixingString,
String? scInstalledPath,
List<String> scInstallPaths,
AppWebLocalizationVersionsData? webLocalizationVersionsData,
bool isCurGameRunning,
String lastScreenInfo,
List<RssItem>? rssVideoItems,
List<RssItem>? rssTextItems,
MapEntry<String, bool>? localizationUpdateInfo,
List<dynamic>? scServerStatus,
List<CountdownFestivalItemData>? countdownFestivalListData});
}
/// @nodoc
class __$$HomeUIModelStateImplCopyWithImpl<$Res>
extends _$HomeUIModelStateCopyWithImpl<$Res, _$HomeUIModelStateImpl>
implements _$$HomeUIModelStateImplCopyWith<$Res> {
__$$HomeUIModelStateImplCopyWithImpl(_$HomeUIModelStateImpl _value,
$Res Function(_$HomeUIModelStateImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? appPlacardData = freezed,
Object? isFixing = null,
Object? isFixingString = null,
Object? scInstalledPath = freezed,
Object? scInstallPaths = null,
Object? webLocalizationVersionsData = freezed,
Object? isCurGameRunning = null,
Object? lastScreenInfo = null,
Object? rssVideoItems = freezed,
Object? rssTextItems = freezed,
Object? localizationUpdateInfo = freezed,
Object? scServerStatus = freezed,
Object? countdownFestivalListData = freezed,
}) {
return _then(_$HomeUIModelStateImpl(
appPlacardData: freezed == appPlacardData
? _value.appPlacardData
: appPlacardData // ignore: cast_nullable_to_non_nullable
as AppPlacardData?,
isFixing: null == isFixing
? _value.isFixing
: isFixing // ignore: cast_nullable_to_non_nullable
as bool,
isFixingString: null == isFixingString
? _value.isFixingString
: isFixingString // ignore: cast_nullable_to_non_nullable
as String,
scInstalledPath: freezed == scInstalledPath
? _value.scInstalledPath
: scInstalledPath // ignore: cast_nullable_to_non_nullable
as String?,
scInstallPaths: null == scInstallPaths
? _value._scInstallPaths
: scInstallPaths // ignore: cast_nullable_to_non_nullable
as List<String>,
webLocalizationVersionsData: freezed == webLocalizationVersionsData
? _value.webLocalizationVersionsData
: webLocalizationVersionsData // ignore: cast_nullable_to_non_nullable
as AppWebLocalizationVersionsData?,
isCurGameRunning: null == isCurGameRunning
? _value.isCurGameRunning
: isCurGameRunning // ignore: cast_nullable_to_non_nullable
as bool,
lastScreenInfo: null == lastScreenInfo
? _value.lastScreenInfo
: lastScreenInfo // ignore: cast_nullable_to_non_nullable
as String,
rssVideoItems: freezed == rssVideoItems
? _value._rssVideoItems
: rssVideoItems // ignore: cast_nullable_to_non_nullable
as List<RssItem>?,
rssTextItems: freezed == rssTextItems
? _value._rssTextItems
: rssTextItems // ignore: cast_nullable_to_non_nullable
as List<RssItem>?,
localizationUpdateInfo: freezed == localizationUpdateInfo
? _value.localizationUpdateInfo
: localizationUpdateInfo // ignore: cast_nullable_to_non_nullable
as MapEntry<String, bool>?,
scServerStatus: freezed == scServerStatus
? _value._scServerStatus
: scServerStatus // ignore: cast_nullable_to_non_nullable
as List<dynamic>?,
countdownFestivalListData: freezed == countdownFestivalListData
? _value._countdownFestivalListData
: countdownFestivalListData // ignore: cast_nullable_to_non_nullable
as List<CountdownFestivalItemData>?,
));
}
}
/// @nodoc
class _$HomeUIModelStateImpl implements _HomeUIModelState {
_$HomeUIModelStateImpl(
{this.appPlacardData,
this.isFixing = false,
this.isFixingString = "",
this.scInstalledPath,
final List<String> scInstallPaths = const [],
this.webLocalizationVersionsData,
this.isCurGameRunning = false,
this.lastScreenInfo = "",
final List<RssItem>? rssVideoItems,
final List<RssItem>? rssTextItems,
this.localizationUpdateInfo,
final List<dynamic>? scServerStatus,
final List<CountdownFestivalItemData>? countdownFestivalListData})
: _scInstallPaths = scInstallPaths,
_rssVideoItems = rssVideoItems,
_rssTextItems = rssTextItems,
_scServerStatus = scServerStatus,
_countdownFestivalListData = countdownFestivalListData;
@override
final AppPlacardData? appPlacardData;
@override
@JsonKey()
final bool isFixing;
@override
@JsonKey()
final String isFixingString;
@override
final String? scInstalledPath;
final List<String> _scInstallPaths;
@override
@JsonKey()
List<String> get scInstallPaths {
if (_scInstallPaths is EqualUnmodifiableListView) return _scInstallPaths;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_scInstallPaths);
}
@override
final AppWebLocalizationVersionsData? webLocalizationVersionsData;
@override
@JsonKey()
final bool isCurGameRunning;
@override
@JsonKey()
final String lastScreenInfo;
final List<RssItem>? _rssVideoItems;
@override
List<RssItem>? get rssVideoItems {
final value = _rssVideoItems;
if (value == null) return null;
if (_rssVideoItems is EqualUnmodifiableListView) return _rssVideoItems;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value);
}
final List<RssItem>? _rssTextItems;
@override
List<RssItem>? get rssTextItems {
final value = _rssTextItems;
if (value == null) return null;
if (_rssTextItems is EqualUnmodifiableListView) return _rssTextItems;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value);
}
@override
final MapEntry<String, bool>? localizationUpdateInfo;
final List<dynamic>? _scServerStatus;
@override
List<dynamic>? get scServerStatus {
final value = _scServerStatus;
if (value == null) return null;
if (_scServerStatus is EqualUnmodifiableListView) return _scServerStatus;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value);
}
final List<CountdownFestivalItemData>? _countdownFestivalListData;
@override
List<CountdownFestivalItemData>? get countdownFestivalListData {
final value = _countdownFestivalListData;
if (value == null) return null;
if (_countdownFestivalListData is EqualUnmodifiableListView)
return _countdownFestivalListData;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value);
}
@override
String toString() {
return 'HomeUIModelState(appPlacardData: $appPlacardData, isFixing: $isFixing, isFixingString: $isFixingString, scInstalledPath: $scInstalledPath, scInstallPaths: $scInstallPaths, webLocalizationVersionsData: $webLocalizationVersionsData, isCurGameRunning: $isCurGameRunning, lastScreenInfo: $lastScreenInfo, rssVideoItems: $rssVideoItems, rssTextItems: $rssTextItems, localizationUpdateInfo: $localizationUpdateInfo, scServerStatus: $scServerStatus, countdownFestivalListData: $countdownFestivalListData)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$HomeUIModelStateImpl &&
(identical(other.appPlacardData, appPlacardData) ||
other.appPlacardData == appPlacardData) &&
(identical(other.isFixing, isFixing) ||
other.isFixing == isFixing) &&
(identical(other.isFixingString, isFixingString) ||
other.isFixingString == isFixingString) &&
(identical(other.scInstalledPath, scInstalledPath) ||
other.scInstalledPath == scInstalledPath) &&
const DeepCollectionEquality()
.equals(other._scInstallPaths, _scInstallPaths) &&
(identical(other.webLocalizationVersionsData,
webLocalizationVersionsData) ||
other.webLocalizationVersionsData ==
webLocalizationVersionsData) &&
(identical(other.isCurGameRunning, isCurGameRunning) ||
other.isCurGameRunning == isCurGameRunning) &&
(identical(other.lastScreenInfo, lastScreenInfo) ||
other.lastScreenInfo == lastScreenInfo) &&
const DeepCollectionEquality()
.equals(other._rssVideoItems, _rssVideoItems) &&
const DeepCollectionEquality()
.equals(other._rssTextItems, _rssTextItems) &&
(identical(other.localizationUpdateInfo, localizationUpdateInfo) ||
other.localizationUpdateInfo == localizationUpdateInfo) &&
const DeepCollectionEquality()
.equals(other._scServerStatus, _scServerStatus) &&
const DeepCollectionEquality().equals(
other._countdownFestivalListData, _countdownFestivalListData));
}
@override
int get hashCode => Object.hash(
runtimeType,
appPlacardData,
isFixing,
isFixingString,
scInstalledPath,
const DeepCollectionEquality().hash(_scInstallPaths),
webLocalizationVersionsData,
isCurGameRunning,
lastScreenInfo,
const DeepCollectionEquality().hash(_rssVideoItems),
const DeepCollectionEquality().hash(_rssTextItems),
localizationUpdateInfo,
const DeepCollectionEquality().hash(_scServerStatus),
const DeepCollectionEquality().hash(_countdownFestivalListData));
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$HomeUIModelStateImplCopyWith<_$HomeUIModelStateImpl> get copyWith =>
__$$HomeUIModelStateImplCopyWithImpl<_$HomeUIModelStateImpl>(
this, _$identity);
}
abstract class _HomeUIModelState implements HomeUIModelState {
factory _HomeUIModelState(
{final AppPlacardData? appPlacardData,
final bool isFixing,
final String isFixingString,
final String? scInstalledPath,
final List<String> scInstallPaths,
final AppWebLocalizationVersionsData? webLocalizationVersionsData,
final bool isCurGameRunning,
final String lastScreenInfo,
final List<RssItem>? rssVideoItems,
final List<RssItem>? rssTextItems,
final MapEntry<String, bool>? localizationUpdateInfo,
final List<dynamic>? scServerStatus,
final List<CountdownFestivalItemData>? countdownFestivalListData}) =
_$HomeUIModelStateImpl;
@override
AppPlacardData? get appPlacardData;
@override
bool get isFixing;
@override
String get isFixingString;
@override
String? get scInstalledPath;
@override
List<String> get scInstallPaths;
@override
AppWebLocalizationVersionsData? get webLocalizationVersionsData;
@override
bool get isCurGameRunning;
@override
String get lastScreenInfo;
@override
List<RssItem>? get rssVideoItems;
@override
List<RssItem>? get rssTextItems;
@override
MapEntry<String, bool>? get localizationUpdateInfo;
@override
List<dynamic>? get scServerStatus;
@override
List<CountdownFestivalItemData>? get countdownFestivalListData;
@override
@JsonKey(ignore: true)
_$$HomeUIModelStateImplCopyWith<_$HomeUIModelStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -0,0 +1,25 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'home_ui_model.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$homeUIModelHash() => r'7ab7b3721ff81a18d67717c9bc91632226c516f6';
/// See also [HomeUIModel].
@ProviderFor(HomeUIModel)
final homeUIModelProvider =
AutoDisposeNotifierProvider<HomeUIModel, HomeUIModelState>.internal(
HomeUIModel.new,
name: r'homeUIModelProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$homeUIModelHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$HomeUIModel = AutoDisposeNotifier<HomeUIModelState>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

View File

@ -3,6 +3,8 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:starcitizen_doctor/common/conf/const_conf.dart'; import 'package:starcitizen_doctor/common/conf/const_conf.dart';
import 'package:starcitizen_doctor/provider/aria2c.dart'; import 'package:starcitizen_doctor/provider/aria2c.dart';
import 'package:starcitizen_doctor/ui/home/home_ui.dart';
import 'package:starcitizen_doctor/ui/home/home_ui_model.dart';
import 'package:starcitizen_doctor/widgets/widgets.dart'; import 'package:starcitizen_doctor/widgets/widgets.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
@ -11,6 +13,9 @@ class IndexUI extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
// pre init child
ref.watch(homeUIModelProvider.select((value) => null));
final curIndex = useState(0); final curIndex = useState(0);
return NavigationView( return NavigationView(
appBar: NavigationAppBar( appBar: NavigationAppBar(
@ -107,10 +112,15 @@ class IndexUI extends HookConsumerWidget {
} }
Widget getPage(int value) { Widget getPage(int value) {
switch (value) {
case 0:
return const HomeUI();
default:
return Center( return Center(
child: Text("$value"), child: Text("UnimplPage $value"),
); );
} }
}
void _onTapIndexMenu(String value, ValueNotifier<int> curIndexState) { void _onTapIndexMenu(String value, ValueNotifier<int> curIndexState) {
final pageIndex = final pageIndex =

View File

@ -11,7 +11,6 @@ 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/helper/system_helper.dart'; import 'package:starcitizen_doctor/common/helper/system_helper.dart';
import 'package:starcitizen_doctor/common/utils/base_utils.dart';
import 'package:starcitizen_doctor/common/utils/log.dart'; import 'package:starcitizen_doctor/common/utils/log.dart';
import 'package:starcitizen_doctor/widgets/widgets.dart'; import 'package:starcitizen_doctor/widgets/widgets.dart';
import 'package:html/parser.dart' as html_parser; import 'package:html/parser.dart' as html_parser;

View File

@ -0,0 +1,41 @@
import 'package:extended_image/extended_image.dart';
import 'package:fluent_ui/fluent_ui.dart';
class CacheNetImage extends StatelessWidget {
final String url;
final double? width;
final double? height;
final BoxFit? fit;
const CacheNetImage(
{super.key, required this.url, this.width, this.height, this.fit});
@override
Widget build(BuildContext context) {
return ExtendedImage.network(
url,
width: width,
height: height,
fit: fit,
loadStateChanged: (ExtendedImageState state) {
switch (state.extendedImageLoadState) {
case LoadState.loading:
return const Center(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Column(
children: [
ProgressRing(),
],
),
),
);
case LoadState.failed:
return const Text("Loading Image error");
case LoadState.completed:
return null;
}
},
);
}
}

View File

@ -0,0 +1,91 @@
import 'dart:async';
import 'package:fluent_ui/fluent_ui.dart';
class CountdownTimeText extends StatefulWidget {
final DateTime targetTime;
const CountdownTimeText({super.key, required this.targetTime});
@override
State<CountdownTimeText> createState() => _CountdownTimeTextState();
}
class _CountdownTimeTextState extends State<CountdownTimeText> {
Timer? _timer;
Widget? textWidget;
bool stopTimer = false;
@override
initState() {
_onUpdateTime(null);
if (!stopTimer) {
_timer = Timer.periodic(const Duration(seconds: 1), _onUpdateTime);
}
super.initState();
}
@override
dispose() {
_timer?.cancel();
_timer = null;
super.dispose();
}
_onUpdateTime(_) {
final now = DateTime.now();
final dur = widget.targetTime.difference(now);
setState(() {
textWidget = _chineseTimeText(dur);
});
// 宿
if (dur.inMilliseconds <= 0) {
stopTimer = true;
setState(() {});
}
if (stopTimer) {
_timer?.cancel();
_timer = null;
}
}
Widget _chineseTimeText(Duration duration) {
final surplus = duration;
int day = (surplus.inSeconds ~/ 3600) ~/ 24;
int hour = (surplus.inSeconds ~/ 3600) % 24;
int minute = surplus.inSeconds % 3600 ~/ 60;
int second = surplus.inSeconds % 60;
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"$day天 ",
style: TextStyle(
fontSize: 24, color: day < 30 ? Colors.red : Colors.white),
),
Text("${timePart(hour)}:${timePart(minute)}:${timePart(second)}"),
],
);
}
String timePart(int p) {
if (p.toString().length == 1) return "0$p";
return "$p";
}
@override
Widget build(BuildContext context) {
if (stopTimer) {
return const Text(
"正在进行中",
style: TextStyle(
fontSize: 18,
color: Color.fromRGBO(32, 220, 89, 1.0),
fontWeight: FontWeight.bold),
);
}
return textWidget ?? const Text("");
}
}

View File

@ -5,6 +5,25 @@ import 'package:window_manager/window_manager.dart';
import 'package:markdown_widget/config/all.dart'; import 'package:markdown_widget/config/all.dart';
import 'package:markdown_widget/widget/all.dart'; import 'package:markdown_widget/widget/all.dart';
import 'package:extended_image/extended_image.dart'; import 'package:extended_image/extended_image.dart';
import 'dart:ui' as ui;
export 'src/cache_image.dart';
export 'src/countdown_time_text.dart';
export '../common/utils/base_utils.dart';
Widget makeLoading(
BuildContext context, {
double? width,
}) {
width ??= 30;
return Center(
child: SizedBox(
width: width,
height: width,
child: const ProgressRing(),
),
);
}
Widget makeDefaultPage(BuildContext context, Widget makeDefaultPage(BuildContext context,
{Widget? titleRow, {Widget? titleRow,
@ -99,6 +118,10 @@ List<Widget> makeMarkdownView(String description, {String? attachmentsUrl}) {
])); ]));
} }
ColorFilter makeSvgColor(Color color) {
return ui.ColorFilter.mode(color, ui.BlendMode.srcIn);
}
CustomTransitionPage<T> myPageBuilder<T>( CustomTransitionPage<T> myPageBuilder<T>(
BuildContext context, GoRouterState state, Widget child) { BuildContext context, GoRouterState state, Widget child) {
return CustomTransitionPage( return CustomTransitionPage(