mirror of
https://mirror.ghproxy.com/https://github.com/StarCitizenToolBox/app.git
synced 2024-12-22 23:23:43 +08:00
launch game
This commit is contained in:
parent
0640761299
commit
e465bc913d
@ -1,5 +1,3 @@
|
||||
/// https://github.com/CxJuice/Uex_Chinese_Translate
|
||||
|
||||
/// ------- WebLocalization Script --------------
|
||||
let SCLocalizationReplaceLocalesMap = {};
|
||||
let enable_webview_localization_capture = false;
|
||||
@ -220,7 +218,7 @@ InitWebLocalization();
|
||||
|
||||
|
||||
/// ----- Login Script ----
|
||||
async function getRSILauncherToken() {
|
||||
async function getRSILauncherToken(channelId) {
|
||||
// check login
|
||||
let r = await fetch("api/launcher/v3/account/check", {
|
||||
method: 'POST', headers: {
|
||||
@ -253,12 +251,12 @@ async function getRSILauncherToken() {
|
||||
});
|
||||
|
||||
if (tokenR.status !== 200) return;
|
||||
let TokenData = (await tokenR.json())["data"];
|
||||
let TokenData = (await tokenR.json())["data"]["token"];
|
||||
console.log(TokenData);
|
||||
|
||||
// get release Data
|
||||
let releaseFormData = new FormData();
|
||||
releaseFormData.append("channelId", "LIVE");
|
||||
releaseFormData.append("channelId", channelId);
|
||||
releaseFormData.append("claims", claimsData);
|
||||
releaseFormData.append("gameId", "SC");
|
||||
releaseFormData.append("platformId", "prod");
|
||||
@ -271,6 +269,9 @@ async function getRSILauncherToken() {
|
||||
if (releaseR.status !== 200) return;
|
||||
let releaseDataJson = (await releaseR.json())['data'];
|
||||
console.log(releaseDataJson);
|
||||
// get user avatar
|
||||
let $avatarElement = $(".c-account-sidebar__profile-metas-avatar");
|
||||
let avatarUrl = $avatarElement.css("background-image");
|
||||
|
||||
// post message
|
||||
window.chrome.webview.postMessage({
|
||||
@ -278,7 +279,8 @@ async function getRSILauncherToken() {
|
||||
'webToken': $.cookie('Rsi-Token'),
|
||||
'claims': claimsData,
|
||||
'authToken': TokenData,
|
||||
'releaseInfo': releaseDataJson
|
||||
'releaseInfo': releaseDataJson,
|
||||
"avatar": avatarUrl
|
||||
}
|
||||
});
|
||||
}
|
@ -237,10 +237,10 @@ class HomeUI extends BaseUI<HomeUIModel> {
|
||||
child: Center(
|
||||
child: Icon(
|
||||
FontAwesomeIcons.solidCircle,
|
||||
color:
|
||||
model.isRSIServerStatusOK(item)
|
||||
? Colors.green
|
||||
: Colors.red,
|
||||
color: model
|
||||
.isRSIServerStatusOK(item)
|
||||
? Colors.green
|
||||
: Colors.red,
|
||||
size: 12,
|
||||
),
|
||||
),
|
||||
@ -296,14 +296,14 @@ class HomeUI extends BaseUI<HomeUIModel> {
|
||||
const SizedBox(width: 12),
|
||||
AnimatedSize(
|
||||
duration: const Duration(milliseconds: 130),
|
||||
child: model.isRsiLauncherStarting
|
||||
? makeLoading(context, width: 28)
|
||||
: Button(
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Icon(FluentIcons.play),
|
||||
),
|
||||
onPressed: () => model.launchRSI()),
|
||||
child: Button(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Icon(model.isCurGameRunning
|
||||
? FluentIcons.stop_solid
|
||||
: FluentIcons.play),
|
||||
),
|
||||
onPressed: () => model.launchRSI()),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Button(
|
||||
|
@ -13,6 +13,8 @@ import 'package:starcitizen_doctor/data/app_placard_data.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/dialogs/md_content_dialog_ui.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/dialogs/md_content_dialog_ui_model.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/localization/localization_ui_model.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/login/login_dialog_ui.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/login/login_dialog_ui_model.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/performance/performance_ui_model.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/webview/webview.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/webview/webview_localization_capture_ui_model.dart';
|
||||
@ -36,6 +38,10 @@ class HomeUIModel extends BaseUIModel {
|
||||
bool isFixing = false;
|
||||
String isFixingString = "";
|
||||
|
||||
final Map<String, bool> _isGameRunning = {};
|
||||
|
||||
bool get isCurGameRunning => _isGameRunning[scInstalledPath] ?? false;
|
||||
|
||||
set lastScreenInfo(String info) {
|
||||
_lastScreenInfo = info;
|
||||
notifyListeners();
|
||||
@ -58,8 +64,6 @@ class HomeUIModel extends BaseUIModel {
|
||||
"Arena Commander": "竞技场指挥官"
|
||||
};
|
||||
|
||||
bool isRsiLauncherStarting = false;
|
||||
|
||||
@override
|
||||
Future loadData() async {
|
||||
if (AppConf.networkVersionData == null) return;
|
||||
@ -110,7 +114,7 @@ class HomeUIModel extends BaseUIModel {
|
||||
return;
|
||||
}
|
||||
scInstallPaths = await SCLoggerHelper.getGameInstallPath(listData,
|
||||
withVersion: ["LIVE", "PTU", "EPTU"], checkExists: true);
|
||||
withVersion: ["LIVE", "PTU", "EVO"], checkExists: true);
|
||||
if (scInstallPaths.isNotEmpty) {
|
||||
scInstalledPath = scInstallPaths.first;
|
||||
}
|
||||
@ -402,8 +406,8 @@ class HomeUIModel extends BaseUIModel {
|
||||
context!,
|
||||
"星际公民官网汉化",
|
||||
const Text(
|
||||
"\n\n\n本插功能件仅供大致浏览使用,不对任何有关本功能产生的问题负责!在涉及账号操作前请注意确认网站的原本内容!"
|
||||
"\n\n\n使用此功能登录账号时请确保您的 StarCitizenDoctor 是从可信任的来源下载。",
|
||||
"本插功能件仅供大致浏览使用,不对任何有关本功能产生的问题负责!在涉及账号操作前请注意确认网站的原本内容!"
|
||||
"\n\n\n使用此功能登录账号时请确保您的 星际公民盒子 是从可信任的来源下载。",
|
||||
style: TextStyle(fontSize: 16),
|
||||
),
|
||||
constraints: BoxConstraints(
|
||||
@ -460,25 +464,39 @@ class HomeUIModel extends BaseUIModel {
|
||||
}
|
||||
|
||||
launchRSI() async {
|
||||
isRsiLauncherStarting = true;
|
||||
notifyListeners();
|
||||
// final rsiLauncherInstalledPath = await SystemHelper.getRSILauncherPath();
|
||||
// if (rsiLauncherInstalledPath.isEmpty) {
|
||||
// isRsiLauncherStarting = false;
|
||||
// notifyListeners();
|
||||
// showToast(context!, "未找到 RSI 启动器目录");
|
||||
// return;
|
||||
// }
|
||||
// SystemHelper.checkAndLaunchRSILauncher(rsiLauncherInstalledPath);
|
||||
goWebView("登录 RSI 账户", "https://robertsspaceindustries.com/connect",
|
||||
loginMode: true, rsiLoginCallback: (data, ok) {
|
||||
dPrint("======rsiLoginCallback=== $ok =====\n$data}");
|
||||
isRsiLauncherStarting = false;
|
||||
notifyListeners();
|
||||
}, useLocalization: true);
|
||||
if (scInstalledPath == "not_install") {
|
||||
showToast(context!, "该功能需要一个有效的安装位置");
|
||||
return;
|
||||
}
|
||||
if (isCurGameRunning) {
|
||||
await Process.run("powershell.exe", ["ps \"StarCitizen\" | kill"]);
|
||||
return;
|
||||
}
|
||||
showDialog(
|
||||
context: context!,
|
||||
dismissWithEsc: false,
|
||||
builder: (context) {
|
||||
return BaseUIContainer(
|
||||
uiCreate: () => LoginDialog(),
|
||||
modelCreate: () => LoginDialogModel(scInstalledPath, this));
|
||||
});
|
||||
}
|
||||
|
||||
bool isRSIServerStatusOK(Map map) {
|
||||
return (map["status"] == "ok" || map["status"] == "operational");
|
||||
}
|
||||
|
||||
doLaunchGame(String launchExe, List<String> args, String installPath) async {
|
||||
_isGameRunning[installPath] = true;
|
||||
notifyListeners();
|
||||
try {
|
||||
await Process.run(launchExe, args);
|
||||
final launchFile = File("$installPath\\loginData.json");
|
||||
if (await launchFile.exists()) {
|
||||
await launchFile.delete();
|
||||
}
|
||||
} catch (_) {}
|
||||
_isGameRunning[installPath] = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
106
lib/ui/home/login/login_dialog_ui.dart
Normal file
106
lib/ui/home/login/login_dialog_ui.dart
Normal file
@ -0,0 +1,106 @@
|
||||
import 'package:starcitizen_doctor/base/ui.dart';
|
||||
import 'package:starcitizen_doctor/widgets/cache_image.dart';
|
||||
|
||||
import 'login_dialog_ui_model.dart';
|
||||
|
||||
class LoginDialog extends BaseUI<LoginDialogModel> {
|
||||
@override
|
||||
Widget? buildBody(BuildContext context, LoginDialogModel model) {
|
||||
return ContentDialog(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * .56,
|
||||
),
|
||||
title: (model.loginStatus == 2) ? null : const Text("一键启动"),
|
||||
content: AnimatedSize(
|
||||
duration: const Duration(milliseconds: 230),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Row(),
|
||||
if (model.loginStatus == 0) ...[
|
||||
const Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Text("登录中..."),
|
||||
SizedBox(height: 12),
|
||||
ProgressRing()
|
||||
],
|
||||
),
|
||||
),
|
||||
] else if (model.loginStatus == 1) ...[
|
||||
Text("请输入RSI账户 [${model.nickname}] 的邮箱,以保存登录状态(输入错误会导致无法进入游戏!)"),
|
||||
const SizedBox(height: 12),
|
||||
TextFormBox(
|
||||
controller: model.emailCtrl,
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
"*该操作同一账号只需执行一次,输入错误请在盒子设置中清理,切换账号请在汉化浏览器中操作。",
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Colors.white.withOpacity(.6),
|
||||
),
|
||||
)
|
||||
] else if (model.loginStatus == 2) ...[
|
||||
Center(
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 12),
|
||||
const Text(
|
||||
"欢迎回来!",
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
if (model.avatarUrl != null)
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(1000),
|
||||
child: CacheNetImage(
|
||||
url: model.avatarUrl!,
|
||||
width: 128,
|
||||
height: 128,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
model.nickname,
|
||||
style: const TextStyle(
|
||||
fontSize: 24, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
const Text("正在为您启动游戏..."),
|
||||
const SizedBox(height: 12),
|
||||
const ProgressRing(),
|
||||
],
|
||||
),
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
if (model.loginStatus == 1) ...[
|
||||
Button(
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(4),
|
||||
child: Text("取消"),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
}),
|
||||
const SizedBox(width: 80),
|
||||
FilledButton(
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(4),
|
||||
child: Text("保存"),
|
||||
),
|
||||
onPressed: () => model.onSaveEmail()),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String getUITitle(BuildContext context, LoginDialogModel model) => "";
|
||||
}
|
174
lib/ui/home/login/login_dialog_ui_model.dart
Normal file
174
lib/ui/home/login/login_dialog_ui_model.dart
Normal file
@ -0,0 +1,174 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:desktop_webview_window/desktop_webview_window.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:jwt_decode/jwt_decode.dart';
|
||||
import 'package:starcitizen_doctor/base/ui_model.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/home_ui_model.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/webview/webview.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class LoginDialogModel extends BaseUIModel {
|
||||
int loginStatus = 0;
|
||||
|
||||
String nickname = "";
|
||||
String? avatarUrl;
|
||||
String? authToken;
|
||||
String? webToken;
|
||||
Map? releaseInfo;
|
||||
|
||||
final String installPath;
|
||||
|
||||
final HomeUIModel homeUIModel;
|
||||
|
||||
TextEditingController emailCtrl = TextEditingController();
|
||||
|
||||
LoginDialogModel(this.installPath, this.homeUIModel);
|
||||
|
||||
@override
|
||||
void initModel() {
|
||||
_launchWebLogin();
|
||||
super.initModel();
|
||||
}
|
||||
|
||||
void _launchWebLogin() {
|
||||
goWebView("登录 RSI 账户", "https://robertsspaceindustries.com/connect",
|
||||
loginMode: true, rsiLoginCallback: (message, ok) async {
|
||||
dPrint(
|
||||
"======rsiLoginCallback=== $ok ===== data==\n${json.encode(message)}");
|
||||
if (message == null || !ok) {
|
||||
Navigator.pop(context!);
|
||||
return;
|
||||
}
|
||||
final emailBox = await Hive.openBox("quick_login_email");
|
||||
final data = message["data"];
|
||||
authToken = data["authToken"];
|
||||
webToken = data["webToken"];
|
||||
releaseInfo = data["releaseInfo"];
|
||||
avatarUrl = data["avatar"]
|
||||
?.toString()
|
||||
.replaceAll("url(\"", "")
|
||||
.replaceAll("\")", "");
|
||||
Map<String, dynamic> payload = Jwt.parseJwt(authToken!);
|
||||
nickname = payload["nickname"] ?? "";
|
||||
if (emailBox.get(nickname, defaultValue: "") == "") {
|
||||
loginStatus = 1;
|
||||
notifyListeners();
|
||||
} else {
|
||||
emailCtrl.text = emailBox.get(nickname, defaultValue: "");
|
||||
_readyForLaunch();
|
||||
}
|
||||
}, useLocalization: true);
|
||||
}
|
||||
|
||||
goWebView(String title, String url,
|
||||
{bool useLocalization = false,
|
||||
bool loginMode = false,
|
||||
RsiLoginCallback? rsiLoginCallback}) async {
|
||||
if (useLocalization) {
|
||||
const tipVersion = 2;
|
||||
final box = await Hive.openBox("app_conf");
|
||||
final skip = await box.get("skip_web_login_version", defaultValue: 0);
|
||||
if (skip != tipVersion) {
|
||||
final ok = await showConfirmDialogs(
|
||||
context!,
|
||||
"星际公民盒子一键启动",
|
||||
const Text(
|
||||
"本功能可以帮您更加便利的启动游戏。\n\n为确保账户安全 ,本功能使用汉化浏览器保留登录状态,且不会保存您的密码信息,与 RSI 启动器行为一致。"
|
||||
"\n\n使用此功能登录账号时请确保您的 星际公民盒子 是从可信任的来源下载。",
|
||||
style: TextStyle(fontSize: 16),
|
||||
),
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context!).size.width * .6));
|
||||
if (!ok) {
|
||||
if (loginMode) {
|
||||
rsiLoginCallback?.call(null, false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
await box.put("skip_web_login_version", tipVersion);
|
||||
}
|
||||
}
|
||||
if (!await WebviewWindow.isWebviewAvailable()) {
|
||||
await showToast(context!, "需要安装 WebView2 Runtime");
|
||||
await launchUrlString(
|
||||
"https://developer.microsoft.com/en-us/microsoft-edge/webview2/");
|
||||
Navigator.pop(context!);
|
||||
return;
|
||||
}
|
||||
final webViewModel = WebViewModel(context!,
|
||||
loginMode: loginMode,
|
||||
loginCallback: rsiLoginCallback,
|
||||
loginChannel: getChannelID());
|
||||
if (useLocalization) {
|
||||
try {
|
||||
await webViewModel.initLocalization();
|
||||
} catch (_) {}
|
||||
}
|
||||
await webViewModel.initWebView(
|
||||
title: title,
|
||||
);
|
||||
await webViewModel.launch(url);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
onSaveEmail() async {
|
||||
final RegExp emailRegex = RegExp(r'^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$');
|
||||
if (!emailRegex.hasMatch(emailCtrl.text.trim())) {
|
||||
showToast(context!, "邮箱输入有误!");
|
||||
return;
|
||||
}
|
||||
final emailBox = await Hive.openBox("quick_login_email");
|
||||
await emailBox.put(nickname, emailCtrl.text.trim());
|
||||
_readyForLaunch();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> _readyForLaunch() async {
|
||||
loginStatus = 2;
|
||||
notifyListeners();
|
||||
final launchData = {
|
||||
"username": emailCtrl.text.trim(),
|
||||
"token": webToken,
|
||||
"auth_token": authToken,
|
||||
"star_network": {
|
||||
"services_endpoint": releaseInfo?["servicesEndpoint"],
|
||||
"hostname": releaseInfo?["universeHost"],
|
||||
"port": releaseInfo?["universePort"],
|
||||
},
|
||||
"TMid": const Uuid().v4(),
|
||||
};
|
||||
final executable = releaseInfo?["executable"];
|
||||
final launchOptions = releaseInfo?["launchOptions"];
|
||||
dPrint("----------launch data ====== -----------\n$launchData");
|
||||
dPrint(
|
||||
"----------executable data ====== -----------\n$installPath\\$executable $launchOptions");
|
||||
final launchFile = File("$installPath\\loginData.json");
|
||||
if (await launchFile.exists()) {
|
||||
await launchFile.delete();
|
||||
}
|
||||
await launchFile.create();
|
||||
await launchFile.writeAsString(json.encode(launchData));
|
||||
notifyListeners();
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
homeUIModel.doLaunchGame(
|
||||
'$installPath\\$executable',
|
||||
["-no_login_dialog", ...launchOptions.toString().split(" ")],
|
||||
installPath);
|
||||
await Future.delayed(const Duration(seconds: 3));
|
||||
Navigator.pop(context!);
|
||||
}
|
||||
|
||||
String getChannelID() {
|
||||
if (installPath.endsWith("\\LIVE")) {
|
||||
return "LIVE";
|
||||
} else if (installPath.endsWith("\\PTU")) {
|
||||
return "PTU";
|
||||
} else if (installPath.endsWith("\\EVO")) {
|
||||
return "EVO";
|
||||
}
|
||||
return "LIVE";
|
||||
}
|
||||
}
|
@ -23,7 +23,8 @@ class WebViewModel {
|
||||
|
||||
bool get isClosed => _isClosed;
|
||||
|
||||
WebViewModel(this.context, {this.loginMode = false, this.loginCallback});
|
||||
WebViewModel(this.context,
|
||||
{this.loginMode = false, this.loginCallback, this.loginChannel = "LIVE"});
|
||||
|
||||
String url = "";
|
||||
bool canGoBack = false;
|
||||
@ -39,6 +40,7 @@ class WebViewModel {
|
||||
Map<String, String>? get curReplaceWords => _curReplaceWords;
|
||||
|
||||
final bool loginMode;
|
||||
final String loginChannel;
|
||||
|
||||
bool _loginModeSuccess = false;
|
||||
|
||||
@ -56,7 +58,6 @@ class WebViewModel {
|
||||
if (loginMode) {
|
||||
await webview.setWebviewWindowVisibility(false);
|
||||
}
|
||||
|
||||
// webview.openDevToolsWindow();
|
||||
webview.isNavigating.addListener(() async {
|
||||
if (!webview.isNavigating.value && localizationResource.isNotEmpty) {
|
||||
@ -117,10 +118,14 @@ class WebViewModel {
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
await webview.evaluateJavaScript(
|
||||
"WebLocalizationUpdateReplaceWords(${json.encode(replaceWords)},$enableCapture)");
|
||||
|
||||
/// loginMode
|
||||
if (loginMode) {
|
||||
dPrint("--- do rsi login ---");
|
||||
dPrint(
|
||||
"--- do rsi login ---\n run === getRSILauncherToken(\"$loginChannel\");");
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
webview.evaluateJavaScript("getRSILauncherToken();");
|
||||
webview.evaluateJavaScript(
|
||||
"getRSILauncherToken(\"$loginChannel\");");
|
||||
}
|
||||
} else if (uri.host.contains("www.erkul.games") ||
|
||||
uri.host.contains("uexcorp.space") ||
|
||||
@ -163,8 +168,7 @@ class WebViewModel {
|
||||
}
|
||||
|
||||
initLocalization() async {
|
||||
localizationScript =
|
||||
await rootBundle.loadString('assets/localization_web_script.js');
|
||||
localizationScript = await rootBundle.loadString('assets/web_script.js');
|
||||
|
||||
/// https://github.com/CxJuice/Uex_Chinese_Translate
|
||||
// get versions
|
||||
@ -172,6 +176,7 @@ class WebViewModel {
|
||||
|
||||
final v = AppWebLocalizationVersionsData.fromJson(
|
||||
await _getJson("$hostUrl/versions.json"));
|
||||
|
||||
dPrint("AppWebLocalizationVersionsData === ${v.toJson()}");
|
||||
|
||||
localizationResource["zh-CN"] = await _getJson("$hostUrl/zh-CN-rsi.json",
|
||||
|
@ -183,7 +183,7 @@ class ToolsUIModel extends BaseUIModel {
|
||||
return;
|
||||
}
|
||||
scInstallPaths = await SCLoggerHelper.getGameInstallPath(listData,
|
||||
checkExists: false, withVersion: ["LIVE", "PTU", "EPTU"]);
|
||||
checkExists: false, withVersion: ["LIVE", "PTU", "EVO"]);
|
||||
if (scInstallPaths.isNotEmpty) {
|
||||
scInstalledPath = scInstallPaths.first;
|
||||
}
|
||||
|
41
lib/widgets/cache_image.dart
Normal file
41
lib/widgets/cache_image.dart
Normal 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;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -49,6 +49,9 @@ dependencies:
|
||||
desktop_webview_window: ^0.2.3
|
||||
flutter_svg: ^2.0.7
|
||||
archive: ^3.4.4
|
||||
jwt_decode: ^0.3.1
|
||||
html: ^0.15.4
|
||||
uuid: ^4.1.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
Loading…
Reference in New Issue
Block a user