mirror of
https://ghfast.top/https://github.com/StarCitizenToolBox/app.git
synced 2025-05-09 19:41:25 +08:00
feat: 42kit Nav
feat: Animation Optimization
This commit is contained in:
parent
a2de310d84
commit
03c941c970
@ -29,6 +29,10 @@ linter:
|
||||
analyzer:
|
||||
plugins:
|
||||
- custom_lint
|
||||
|
||||
exclude:
|
||||
- "**/*.g.dart"
|
||||
- "**/*.freezed.dart"
|
||||
errors:
|
||||
invalid_annotation_target: ignore
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
|
14
lib/api/udb.dart
Normal file
14
lib/api/udb.dart
Normal file
@ -0,0 +1,14 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:starcitizen_doctor/common/io/rs_http.dart';
|
||||
import 'package:starcitizen_doctor/data/nav_api_data.dart';
|
||||
|
||||
class UDBNavApi {
|
||||
static Future<NavApiData> getNavItems({int pageNo = 1}) async {
|
||||
final r = await RSHttp.getText(
|
||||
"https://payload.citizenwiki.cn/api/community-navs?sort=is_sponsored&depth=2&page=$pageNo&limit=1000");
|
||||
if (r.isEmpty) throw "Network Error";
|
||||
final result = NavApiData.fromJson(jsonDecode(r));
|
||||
return result;
|
||||
}
|
||||
}
|
@ -22,7 +22,7 @@ final routerProvider = AutoDisposeProvider<GoRouter>.internal(
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef RouterRef = AutoDisposeProviderRef<GoRouter>;
|
||||
String _$appGlobalModelHash() => r'8aa468bda409c425a76e3ef9e7739ca4ed055d2b';
|
||||
String _$appGlobalModelHash() => r'eb06413ab3a70f26712d897cee745ee62e89e75e';
|
||||
|
||||
/// See also [AppGlobalModel].
|
||||
@ProviderFor(AppGlobalModel)
|
||||
|
101
lib/common/utils/file_cache_utils.dart
Normal file
101
lib/common/utils/file_cache_utils.dart
Normal file
@ -0,0 +1,101 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'dart:convert';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:starcitizen_doctor/common/io/rs_http.dart';
|
||||
|
||||
class FileCacheUtils {
|
||||
// 存储正在进行的下载任务
|
||||
static final Map<String, Future<File>> _downloadingTasks = {};
|
||||
|
||||
// 缓存目录
|
||||
static Directory? _cacheDir;
|
||||
|
||||
/// 获取缓存目录
|
||||
static Future<Directory> _getCacheDirectory() async {
|
||||
if (_cacheDir != null) return _cacheDir!;
|
||||
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
_cacheDir = Directory(path.join(tempDir.path, 'ScToolbox_File_Cache'));
|
||||
|
||||
if (!await _cacheDir!.exists()) {
|
||||
await _cacheDir!.create(recursive: true);
|
||||
}
|
||||
|
||||
return _cacheDir!;
|
||||
}
|
||||
|
||||
/// 从URL获取文件,如果已经在下载中,则共享同一个下载任务
|
||||
static Future<File> getFile(String url) async {
|
||||
// 如果已经在下载中,直接返回正在进行的下载任务
|
||||
if (_downloadingTasks.containsKey(url)) {
|
||||
return _downloadingTasks[url]!;
|
||||
}
|
||||
|
||||
final fileTask = _downloadFile(url);
|
||||
_downloadingTasks[url] = fileTask;
|
||||
|
||||
try {
|
||||
final file = await fileTask;
|
||||
return file;
|
||||
} finally {
|
||||
// 无论成功失败,下载完成后从任务列表中移除
|
||||
_downloadingTasks.remove(url);
|
||||
}
|
||||
}
|
||||
|
||||
/// 实际进行下载的方法
|
||||
static Future<File> _downloadFile(String url) async {
|
||||
// 生成文件名 (使用URL的MD5哈希作为文件名)
|
||||
final filename = md5.convert(utf8.encode(url)).toString();
|
||||
final cacheDir = await _getCacheDirectory();
|
||||
final file = File(path.join(cacheDir.path, filename));
|
||||
|
||||
// 检查文件是否已经存在
|
||||
if (await file.exists()) {
|
||||
return file;
|
||||
}
|
||||
|
||||
// 下载文件
|
||||
final response = await RSHttp.get(url);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
await file.writeAsBytes(response.data ?? []);
|
||||
return file;
|
||||
} else {
|
||||
throw Exception('Failed to download file: ${response.statusCode}');
|
||||
}
|
||||
}
|
||||
|
||||
/// 清除特定URL的缓存
|
||||
static Future<bool> clearCache(String url) async {
|
||||
try {
|
||||
final filename = md5.convert(utf8.encode(url)).toString();
|
||||
final cacheDir = await _getCacheDirectory();
|
||||
final file = File(path.join(cacheDir.path, filename));
|
||||
|
||||
if (await file.exists()) {
|
||||
await file.delete();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 清除所有缓存
|
||||
static Future<void> clearAllCache() async {
|
||||
try {
|
||||
final cacheDir = await _getCacheDirectory();
|
||||
if (await cacheDir.exists()) {
|
||||
await cacheDir.delete(recursive: true);
|
||||
await cacheDir.create();
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('清除缓存失败: $e');
|
||||
}
|
||||
}
|
||||
}
|
246
lib/data/nav_api_data.dart
Normal file
246
lib/data/nav_api_data.dart
Normal file
@ -0,0 +1,246 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'nav_api_data.freezed.dart';
|
||||
|
||||
part 'nav_api_data.g.dart';
|
||||
|
||||
@freezed
|
||||
class NavApiDocsItemData with _$NavApiDocsItemData {
|
||||
const factory NavApiDocsItemData({
|
||||
@Default('') @JsonKey(name: 'id') String id,
|
||||
@Default('') @JsonKey(name: 'name') String name,
|
||||
@Default('') @JsonKey(name: 'slug') String slug,
|
||||
@Default('') @JsonKey(name: 'abstract') String abstract_,
|
||||
@Default('') @JsonKey(name: 'description') String description,
|
||||
@Default(NavApiDocsItemImageData())
|
||||
@JsonKey(name: 'image')
|
||||
NavApiDocsItemImageData image,
|
||||
@Default('') @JsonKey(name: 'link') String link,
|
||||
@Default(false) @JsonKey(name: 'is_sponsored') bool isSponsored,
|
||||
@Default(<NavApiDocsItemTagsItemData>[])
|
||||
@JsonKey(name: 'tags')
|
||||
List<NavApiDocsItemTagsItemData> tags,
|
||||
@Default('') @JsonKey(name: 'updatedAt') String updatedAt,
|
||||
@Default('') @JsonKey(name: 'createdAt') String createdAt,
|
||||
}) = _NavApiDocsItemData;
|
||||
|
||||
const NavApiDocsItemData._();
|
||||
|
||||
factory NavApiDocsItemData.fromJson(Map<String, Object?> json) =>
|
||||
_$NavApiDocsItemDataFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class NavApiDocsItemImageData with _$NavApiDocsItemImageData {
|
||||
const factory NavApiDocsItemImageData({
|
||||
@Default('') @JsonKey(name: 'id') String id,
|
||||
@Default(NavApiDocsItemImageCreatedByData())
|
||||
@JsonKey(name: 'createdBy')
|
||||
NavApiDocsItemImageCreatedByData createdBy,
|
||||
@Default('') @JsonKey(name: 'title') String title,
|
||||
@Default(false) @JsonKey(name: 'original') bool original,
|
||||
@Default('') @JsonKey(name: 'credit') String credit,
|
||||
@Default('') @JsonKey(name: 'source') String source,
|
||||
@Default('') @JsonKey(name: 'license') String license,
|
||||
@JsonKey(name: 'caption') dynamic caption,
|
||||
@Default('') @JsonKey(name: 'updatedAt') String updatedAt,
|
||||
@Default('') @JsonKey(name: 'createdAt') String createdAt,
|
||||
@Default('') @JsonKey(name: 'url') String url,
|
||||
@Default('') @JsonKey(name: 'filename') String filename,
|
||||
@Default('') @JsonKey(name: 'mimeType') String mimeType,
|
||||
@Default(0) @JsonKey(name: 'filesize') int filesize,
|
||||
@Default(0) @JsonKey(name: 'width') int width,
|
||||
@Default(0) @JsonKey(name: 'height') int height,
|
||||
@Default(NavApiDocsItemImageSizesData())
|
||||
@JsonKey(name: 'sizes')
|
||||
NavApiDocsItemImageSizesData sizes,
|
||||
}) = _NavApiDocsItemImageData;
|
||||
|
||||
const NavApiDocsItemImageData._();
|
||||
|
||||
factory NavApiDocsItemImageData.fromJson(Map<String, Object?> json) =>
|
||||
_$NavApiDocsItemImageDataFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class NavApiDocsItemImageCreatedByData with _$NavApiDocsItemImageCreatedByData {
|
||||
const factory NavApiDocsItemImageCreatedByData({
|
||||
@Default('') @JsonKey(name: 'id') String id,
|
||||
@Default('') @JsonKey(name: 'sub') String sub,
|
||||
@Default('') @JsonKey(name: 'external_provider') String externalProvider,
|
||||
@Default('') @JsonKey(name: 'username') String username,
|
||||
@Default('') @JsonKey(name: 'name') String name,
|
||||
@Default(<String>[]) @JsonKey(name: 'roles') List<String> roles,
|
||||
@Default('') @JsonKey(name: 'avatar_url') String avatarUrl,
|
||||
@Default('') @JsonKey(name: 'updatedAt') String updatedAt,
|
||||
@Default('') @JsonKey(name: 'createdAt') String createdAt,
|
||||
@Default('') @JsonKey(name: 'email') String email,
|
||||
@Default(0) @JsonKey(name: 'loginAttempts') int loginAttempts,
|
||||
@Default('') @JsonKey(name: 'avatar') String avatar,
|
||||
}) = _NavApiDocsItemImageCreatedByData;
|
||||
|
||||
const NavApiDocsItemImageCreatedByData._();
|
||||
|
||||
factory NavApiDocsItemImageCreatedByData.fromJson(
|
||||
Map<String, Object?> json) =>
|
||||
_$NavApiDocsItemImageCreatedByDataFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class NavApiDocsItemImageSizesThumbnailData
|
||||
with _$NavApiDocsItemImageSizesThumbnailData {
|
||||
const factory NavApiDocsItemImageSizesThumbnailData({
|
||||
@Default('') @JsonKey(name: 'url') String url,
|
||||
@Default(0) @JsonKey(name: 'width') int width,
|
||||
@Default(0) @JsonKey(name: 'height') int height,
|
||||
@Default('') @JsonKey(name: 'mimeType') String mimeType,
|
||||
@Default(0) @JsonKey(name: 'filesize') int filesize,
|
||||
@Default('') @JsonKey(name: 'filename') String filename,
|
||||
}) = _NavApiDocsItemImageSizesThumbnailData;
|
||||
|
||||
const NavApiDocsItemImageSizesThumbnailData._();
|
||||
|
||||
factory NavApiDocsItemImageSizesThumbnailData.fromJson(
|
||||
Map<String, Object?> json) =>
|
||||
_$NavApiDocsItemImageSizesThumbnailDataFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class NavApiDocsItemImageSizesData with _$NavApiDocsItemImageSizesData {
|
||||
const factory NavApiDocsItemImageSizesData({
|
||||
@Default(NavApiDocsItemImageSizesThumbnailData())
|
||||
@JsonKey(name: 'thumbnail')
|
||||
NavApiDocsItemImageSizesThumbnailData thumbnail,
|
||||
@Default(NavApiDocsItemImageSizesPreloadData())
|
||||
@JsonKey(name: 'preload')
|
||||
NavApiDocsItemImageSizesPreloadData preload,
|
||||
@Default(NavApiDocsItemImageSizesCardData())
|
||||
@JsonKey(name: 'card')
|
||||
NavApiDocsItemImageSizesCardData card,
|
||||
@Default(NavApiDocsItemImageSizesTabletData())
|
||||
@JsonKey(name: 'tablet')
|
||||
NavApiDocsItemImageSizesTabletData tablet,
|
||||
@Default(NavApiDocsItemImageSizesAvatarData())
|
||||
@JsonKey(name: 'avatar')
|
||||
NavApiDocsItemImageSizesAvatarData avatar,
|
||||
}) = _NavApiDocsItemImageSizesData;
|
||||
|
||||
const NavApiDocsItemImageSizesData._();
|
||||
|
||||
factory NavApiDocsItemImageSizesData.fromJson(Map<String, Object?> json) =>
|
||||
_$NavApiDocsItemImageSizesDataFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class NavApiDocsItemImageSizesPreloadData
|
||||
with _$NavApiDocsItemImageSizesPreloadData {
|
||||
const factory NavApiDocsItemImageSizesPreloadData({
|
||||
@JsonKey(name: 'url') dynamic url,
|
||||
@JsonKey(name: 'width') dynamic width,
|
||||
@JsonKey(name: 'height') dynamic height,
|
||||
@JsonKey(name: 'mimeType') dynamic mimeType,
|
||||
@JsonKey(name: 'filesize') dynamic filesize,
|
||||
@JsonKey(name: 'filename') dynamic filename,
|
||||
}) = _NavApiDocsItemImageSizesPreloadData;
|
||||
|
||||
const NavApiDocsItemImageSizesPreloadData._();
|
||||
|
||||
factory NavApiDocsItemImageSizesPreloadData.fromJson(
|
||||
Map<String, Object?> json) =>
|
||||
_$NavApiDocsItemImageSizesPreloadDataFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class NavApiDocsItemImageSizesCardData with _$NavApiDocsItemImageSizesCardData {
|
||||
const factory NavApiDocsItemImageSizesCardData({
|
||||
@Default('') @JsonKey(name: 'url') String url,
|
||||
@Default(0) @JsonKey(name: 'width') int width,
|
||||
@Default(0) @JsonKey(name: 'height') int height,
|
||||
@Default('') @JsonKey(name: 'mimeType') String mimeType,
|
||||
@Default(0) @JsonKey(name: 'filesize') int filesize,
|
||||
@Default('') @JsonKey(name: 'filename') String filename,
|
||||
}) = _NavApiDocsItemImageSizesCardData;
|
||||
|
||||
const NavApiDocsItemImageSizesCardData._();
|
||||
|
||||
factory NavApiDocsItemImageSizesCardData.fromJson(
|
||||
Map<String, Object?> json) =>
|
||||
_$NavApiDocsItemImageSizesCardDataFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class NavApiDocsItemImageSizesTabletData
|
||||
with _$NavApiDocsItemImageSizesTabletData {
|
||||
const factory NavApiDocsItemImageSizesTabletData({
|
||||
@Default('') @JsonKey(name: 'url') String url,
|
||||
@Default(0) @JsonKey(name: 'width') int width,
|
||||
@Default(0) @JsonKey(name: 'height') int height,
|
||||
@Default('') @JsonKey(name: 'mimeType') String mimeType,
|
||||
@Default(0) @JsonKey(name: 'filesize') int filesize,
|
||||
@Default('') @JsonKey(name: 'filename') String filename,
|
||||
}) = _NavApiDocsItemImageSizesTabletData;
|
||||
|
||||
const NavApiDocsItemImageSizesTabletData._();
|
||||
|
||||
factory NavApiDocsItemImageSizesTabletData.fromJson(
|
||||
Map<String, Object?> json) =>
|
||||
_$NavApiDocsItemImageSizesTabletDataFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class NavApiDocsItemImageSizesAvatarData
|
||||
with _$NavApiDocsItemImageSizesAvatarData {
|
||||
const factory NavApiDocsItemImageSizesAvatarData({
|
||||
@Default('') @JsonKey(name: 'url') String url,
|
||||
@Default(0) @JsonKey(name: 'width') int width,
|
||||
@Default(0) @JsonKey(name: 'height') int height,
|
||||
@Default('') @JsonKey(name: 'mimeType') String mimeType,
|
||||
@Default(0) @JsonKey(name: 'filesize') int filesize,
|
||||
@Default('') @JsonKey(name: 'filename') String filename,
|
||||
}) = _NavApiDocsItemImageSizesAvatarData;
|
||||
|
||||
const NavApiDocsItemImageSizesAvatarData._();
|
||||
|
||||
factory NavApiDocsItemImageSizesAvatarData.fromJson(
|
||||
Map<String, Object?> json) =>
|
||||
_$NavApiDocsItemImageSizesAvatarDataFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class NavApiDocsItemTagsItemData with _$NavApiDocsItemTagsItemData {
|
||||
const factory NavApiDocsItemTagsItemData({
|
||||
@Default('') @JsonKey(name: 'id') String id,
|
||||
@Default('') @JsonKey(name: 'name') String name,
|
||||
@Default('') @JsonKey(name: 'slug') String slug,
|
||||
@Default('') @JsonKey(name: 'updatedAt') String updatedAt,
|
||||
@Default('') @JsonKey(name: 'createdAt') String createdAt,
|
||||
}) = _NavApiDocsItemTagsItemData;
|
||||
|
||||
const NavApiDocsItemTagsItemData._();
|
||||
|
||||
factory NavApiDocsItemTagsItemData.fromJson(Map<String, Object?> json) =>
|
||||
_$NavApiDocsItemTagsItemDataFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class NavApiData with _$NavApiData {
|
||||
const factory NavApiData({
|
||||
@Default(<NavApiDocsItemData>[])
|
||||
@JsonKey(name: 'docs')
|
||||
List<NavApiDocsItemData> docs,
|
||||
@Default(false) @JsonKey(name: 'hasNextPage') bool hasNextPage,
|
||||
@Default(false) @JsonKey(name: 'hasPrevPage') bool hasPrevPage,
|
||||
@Default(0) @JsonKey(name: 'limit') int limit,
|
||||
@JsonKey(name: 'nextPage') dynamic nextPage,
|
||||
@Default(0) @JsonKey(name: 'page') int page,
|
||||
@Default(0) @JsonKey(name: 'pagingCounter') int pagingCounter,
|
||||
@JsonKey(name: 'prevPage') dynamic prevPage,
|
||||
@Default(0) @JsonKey(name: 'totalDocs') int totalDocs,
|
||||
@Default(0) @JsonKey(name: 'totalPages') int totalPages,
|
||||
}) = _NavApiData;
|
||||
|
||||
const NavApiData._();
|
||||
|
||||
factory NavApiData.fromJson(Map<String, Object?> json) =>
|
||||
_$NavApiDataFromJson(json);
|
||||
}
|
3959
lib/data/nav_api_data.freezed.dart
Normal file
3959
lib/data/nav_api_data.freezed.dart
Normal file
File diff suppressed because it is too large
Load Diff
336
lib/data/nav_api_data.g.dart
Normal file
336
lib/data/nav_api_data.g.dart
Normal file
@ -0,0 +1,336 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'nav_api_data.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$NavApiDocsItemDataImpl _$$NavApiDocsItemDataImplFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$NavApiDocsItemDataImpl(
|
||||
id: json['id'] as String? ?? '',
|
||||
name: json['name'] as String? ?? '',
|
||||
slug: json['slug'] as String? ?? '',
|
||||
abstract_: json['abstract'] as String? ?? '',
|
||||
description: json['description'] as String? ?? '',
|
||||
image: json['image'] == null
|
||||
? const NavApiDocsItemImageData()
|
||||
: NavApiDocsItemImageData.fromJson(
|
||||
json['image'] as Map<String, dynamic>),
|
||||
link: json['link'] as String? ?? '',
|
||||
isSponsored: json['is_sponsored'] as bool? ?? false,
|
||||
tags: (json['tags'] as List<dynamic>?)
|
||||
?.map((e) => NavApiDocsItemTagsItemData.fromJson(
|
||||
e as Map<String, dynamic>))
|
||||
.toList() ??
|
||||
const <NavApiDocsItemTagsItemData>[],
|
||||
updatedAt: json['updatedAt'] as String? ?? '',
|
||||
createdAt: json['createdAt'] as String? ?? '',
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$NavApiDocsItemDataImplToJson(
|
||||
_$NavApiDocsItemDataImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'name': instance.name,
|
||||
'slug': instance.slug,
|
||||
'abstract': instance.abstract_,
|
||||
'description': instance.description,
|
||||
'image': instance.image,
|
||||
'link': instance.link,
|
||||
'is_sponsored': instance.isSponsored,
|
||||
'tags': instance.tags,
|
||||
'updatedAt': instance.updatedAt,
|
||||
'createdAt': instance.createdAt,
|
||||
};
|
||||
|
||||
_$NavApiDocsItemImageDataImpl _$$NavApiDocsItemImageDataImplFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$NavApiDocsItemImageDataImpl(
|
||||
id: json['id'] as String? ?? '',
|
||||
createdBy: json['createdBy'] == null
|
||||
? const NavApiDocsItemImageCreatedByData()
|
||||
: NavApiDocsItemImageCreatedByData.fromJson(
|
||||
json['createdBy'] as Map<String, dynamic>),
|
||||
title: json['title'] as String? ?? '',
|
||||
original: json['original'] as bool? ?? false,
|
||||
credit: json['credit'] as String? ?? '',
|
||||
source: json['source'] as String? ?? '',
|
||||
license: json['license'] as String? ?? '',
|
||||
caption: json['caption'],
|
||||
updatedAt: json['updatedAt'] as String? ?? '',
|
||||
createdAt: json['createdAt'] as String? ?? '',
|
||||
url: json['url'] as String? ?? '',
|
||||
filename: json['filename'] as String? ?? '',
|
||||
mimeType: json['mimeType'] as String? ?? '',
|
||||
filesize: (json['filesize'] as num?)?.toInt() ?? 0,
|
||||
width: (json['width'] as num?)?.toInt() ?? 0,
|
||||
height: (json['height'] as num?)?.toInt() ?? 0,
|
||||
sizes: json['sizes'] == null
|
||||
? const NavApiDocsItemImageSizesData()
|
||||
: NavApiDocsItemImageSizesData.fromJson(
|
||||
json['sizes'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$NavApiDocsItemImageDataImplToJson(
|
||||
_$NavApiDocsItemImageDataImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'createdBy': instance.createdBy,
|
||||
'title': instance.title,
|
||||
'original': instance.original,
|
||||
'credit': instance.credit,
|
||||
'source': instance.source,
|
||||
'license': instance.license,
|
||||
'caption': instance.caption,
|
||||
'updatedAt': instance.updatedAt,
|
||||
'createdAt': instance.createdAt,
|
||||
'url': instance.url,
|
||||
'filename': instance.filename,
|
||||
'mimeType': instance.mimeType,
|
||||
'filesize': instance.filesize,
|
||||
'width': instance.width,
|
||||
'height': instance.height,
|
||||
'sizes': instance.sizes,
|
||||
};
|
||||
|
||||
_$NavApiDocsItemImageCreatedByDataImpl
|
||||
_$$NavApiDocsItemImageCreatedByDataImplFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$NavApiDocsItemImageCreatedByDataImpl(
|
||||
id: json['id'] as String? ?? '',
|
||||
sub: json['sub'] as String? ?? '',
|
||||
externalProvider: json['external_provider'] as String? ?? '',
|
||||
username: json['username'] as String? ?? '',
|
||||
name: json['name'] as String? ?? '',
|
||||
roles: (json['roles'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toList() ??
|
||||
const <String>[],
|
||||
avatarUrl: json['avatar_url'] as String? ?? '',
|
||||
updatedAt: json['updatedAt'] as String? ?? '',
|
||||
createdAt: json['createdAt'] as String? ?? '',
|
||||
email: json['email'] as String? ?? '',
|
||||
loginAttempts: (json['loginAttempts'] as num?)?.toInt() ?? 0,
|
||||
avatar: json['avatar'] as String? ?? '',
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$NavApiDocsItemImageCreatedByDataImplToJson(
|
||||
_$NavApiDocsItemImageCreatedByDataImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'sub': instance.sub,
|
||||
'external_provider': instance.externalProvider,
|
||||
'username': instance.username,
|
||||
'name': instance.name,
|
||||
'roles': instance.roles,
|
||||
'avatar_url': instance.avatarUrl,
|
||||
'updatedAt': instance.updatedAt,
|
||||
'createdAt': instance.createdAt,
|
||||
'email': instance.email,
|
||||
'loginAttempts': instance.loginAttempts,
|
||||
'avatar': instance.avatar,
|
||||
};
|
||||
|
||||
_$NavApiDocsItemImageSizesThumbnailDataImpl
|
||||
_$$NavApiDocsItemImageSizesThumbnailDataImplFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$NavApiDocsItemImageSizesThumbnailDataImpl(
|
||||
url: json['url'] as String? ?? '',
|
||||
width: (json['width'] as num?)?.toInt() ?? 0,
|
||||
height: (json['height'] as num?)?.toInt() ?? 0,
|
||||
mimeType: json['mimeType'] as String? ?? '',
|
||||
filesize: (json['filesize'] as num?)?.toInt() ?? 0,
|
||||
filename: json['filename'] as String? ?? '',
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$NavApiDocsItemImageSizesThumbnailDataImplToJson(
|
||||
_$NavApiDocsItemImageSizesThumbnailDataImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'url': instance.url,
|
||||
'width': instance.width,
|
||||
'height': instance.height,
|
||||
'mimeType': instance.mimeType,
|
||||
'filesize': instance.filesize,
|
||||
'filename': instance.filename,
|
||||
};
|
||||
|
||||
_$NavApiDocsItemImageSizesDataImpl _$$NavApiDocsItemImageSizesDataImplFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$NavApiDocsItemImageSizesDataImpl(
|
||||
thumbnail: json['thumbnail'] == null
|
||||
? const NavApiDocsItemImageSizesThumbnailData()
|
||||
: NavApiDocsItemImageSizesThumbnailData.fromJson(
|
||||
json['thumbnail'] as Map<String, dynamic>),
|
||||
preload: json['preload'] == null
|
||||
? const NavApiDocsItemImageSizesPreloadData()
|
||||
: NavApiDocsItemImageSizesPreloadData.fromJson(
|
||||
json['preload'] as Map<String, dynamic>),
|
||||
card: json['card'] == null
|
||||
? const NavApiDocsItemImageSizesCardData()
|
||||
: NavApiDocsItemImageSizesCardData.fromJson(
|
||||
json['card'] as Map<String, dynamic>),
|
||||
tablet: json['tablet'] == null
|
||||
? const NavApiDocsItemImageSizesTabletData()
|
||||
: NavApiDocsItemImageSizesTabletData.fromJson(
|
||||
json['tablet'] as Map<String, dynamic>),
|
||||
avatar: json['avatar'] == null
|
||||
? const NavApiDocsItemImageSizesAvatarData()
|
||||
: NavApiDocsItemImageSizesAvatarData.fromJson(
|
||||
json['avatar'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$NavApiDocsItemImageSizesDataImplToJson(
|
||||
_$NavApiDocsItemImageSizesDataImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'thumbnail': instance.thumbnail,
|
||||
'preload': instance.preload,
|
||||
'card': instance.card,
|
||||
'tablet': instance.tablet,
|
||||
'avatar': instance.avatar,
|
||||
};
|
||||
|
||||
_$NavApiDocsItemImageSizesPreloadDataImpl
|
||||
_$$NavApiDocsItemImageSizesPreloadDataImplFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$NavApiDocsItemImageSizesPreloadDataImpl(
|
||||
url: json['url'],
|
||||
width: json['width'],
|
||||
height: json['height'],
|
||||
mimeType: json['mimeType'],
|
||||
filesize: json['filesize'],
|
||||
filename: json['filename'],
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$NavApiDocsItemImageSizesPreloadDataImplToJson(
|
||||
_$NavApiDocsItemImageSizesPreloadDataImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'url': instance.url,
|
||||
'width': instance.width,
|
||||
'height': instance.height,
|
||||
'mimeType': instance.mimeType,
|
||||
'filesize': instance.filesize,
|
||||
'filename': instance.filename,
|
||||
};
|
||||
|
||||
_$NavApiDocsItemImageSizesCardDataImpl
|
||||
_$$NavApiDocsItemImageSizesCardDataImplFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$NavApiDocsItemImageSizesCardDataImpl(
|
||||
url: json['url'] as String? ?? '',
|
||||
width: (json['width'] as num?)?.toInt() ?? 0,
|
||||
height: (json['height'] as num?)?.toInt() ?? 0,
|
||||
mimeType: json['mimeType'] as String? ?? '',
|
||||
filesize: (json['filesize'] as num?)?.toInt() ?? 0,
|
||||
filename: json['filename'] as String? ?? '',
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$NavApiDocsItemImageSizesCardDataImplToJson(
|
||||
_$NavApiDocsItemImageSizesCardDataImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'url': instance.url,
|
||||
'width': instance.width,
|
||||
'height': instance.height,
|
||||
'mimeType': instance.mimeType,
|
||||
'filesize': instance.filesize,
|
||||
'filename': instance.filename,
|
||||
};
|
||||
|
||||
_$NavApiDocsItemImageSizesTabletDataImpl
|
||||
_$$NavApiDocsItemImageSizesTabletDataImplFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$NavApiDocsItemImageSizesTabletDataImpl(
|
||||
url: json['url'] as String? ?? '',
|
||||
width: (json['width'] as num?)?.toInt() ?? 0,
|
||||
height: (json['height'] as num?)?.toInt() ?? 0,
|
||||
mimeType: json['mimeType'] as String? ?? '',
|
||||
filesize: (json['filesize'] as num?)?.toInt() ?? 0,
|
||||
filename: json['filename'] as String? ?? '',
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$NavApiDocsItemImageSizesTabletDataImplToJson(
|
||||
_$NavApiDocsItemImageSizesTabletDataImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'url': instance.url,
|
||||
'width': instance.width,
|
||||
'height': instance.height,
|
||||
'mimeType': instance.mimeType,
|
||||
'filesize': instance.filesize,
|
||||
'filename': instance.filename,
|
||||
};
|
||||
|
||||
_$NavApiDocsItemImageSizesAvatarDataImpl
|
||||
_$$NavApiDocsItemImageSizesAvatarDataImplFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$NavApiDocsItemImageSizesAvatarDataImpl(
|
||||
url: json['url'] as String? ?? '',
|
||||
width: (json['width'] as num?)?.toInt() ?? 0,
|
||||
height: (json['height'] as num?)?.toInt() ?? 0,
|
||||
mimeType: json['mimeType'] as String? ?? '',
|
||||
filesize: (json['filesize'] as num?)?.toInt() ?? 0,
|
||||
filename: json['filename'] as String? ?? '',
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$NavApiDocsItemImageSizesAvatarDataImplToJson(
|
||||
_$NavApiDocsItemImageSizesAvatarDataImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'url': instance.url,
|
||||
'width': instance.width,
|
||||
'height': instance.height,
|
||||
'mimeType': instance.mimeType,
|
||||
'filesize': instance.filesize,
|
||||
'filename': instance.filename,
|
||||
};
|
||||
|
||||
_$NavApiDocsItemTagsItemDataImpl _$$NavApiDocsItemTagsItemDataImplFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$NavApiDocsItemTagsItemDataImpl(
|
||||
id: json['id'] as String? ?? '',
|
||||
name: json['name'] as String? ?? '',
|
||||
slug: json['slug'] as String? ?? '',
|
||||
updatedAt: json['updatedAt'] as String? ?? '',
|
||||
createdAt: json['createdAt'] as String? ?? '',
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$NavApiDocsItemTagsItemDataImplToJson(
|
||||
_$NavApiDocsItemTagsItemDataImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'name': instance.name,
|
||||
'slug': instance.slug,
|
||||
'updatedAt': instance.updatedAt,
|
||||
'createdAt': instance.createdAt,
|
||||
};
|
||||
|
||||
_$NavApiDataImpl _$$NavApiDataImplFromJson(Map<String, dynamic> json) =>
|
||||
_$NavApiDataImpl(
|
||||
docs: (json['docs'] as List<dynamic>?)
|
||||
?.map(
|
||||
(e) => NavApiDocsItemData.fromJson(e as Map<String, dynamic>))
|
||||
.toList() ??
|
||||
const <NavApiDocsItemData>[],
|
||||
hasNextPage: json['hasNextPage'] as bool? ?? false,
|
||||
hasPrevPage: json['hasPrevPage'] as bool? ?? false,
|
||||
limit: (json['limit'] as num?)?.toInt() ?? 0,
|
||||
nextPage: json['nextPage'],
|
||||
page: (json['page'] as num?)?.toInt() ?? 0,
|
||||
pagingCounter: (json['pagingCounter'] as num?)?.toInt() ?? 0,
|
||||
prevPage: json['prevPage'],
|
||||
totalDocs: (json['totalDocs'] as num?)?.toInt() ?? 0,
|
||||
totalPages: (json['totalPages'] as num?)?.toInt() ?? 0,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$NavApiDataImplToJson(_$NavApiDataImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'docs': instance.docs,
|
||||
'hasNextPage': instance.hasNextPage,
|
||||
'hasPrevPage': instance.hasPrevPage,
|
||||
'limit': instance.limit,
|
||||
'nextPage': instance.nextPage,
|
||||
'page': instance.page,
|
||||
'pagingCounter': instance.pagingCounter,
|
||||
'prevPage': instance.prevPage,
|
||||
'totalDocs': instance.totalDocs,
|
||||
'totalPages': instance.totalPages,
|
||||
};
|
@ -30,8 +30,7 @@ class AboutUI extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _makeAbout(BuildContext context, WidgetRef ref,
|
||||
ValueNotifier<bool> isTipTextCn, PageController pageCtrl) {
|
||||
Widget _makeAbout(BuildContext context, WidgetRef ref, ValueNotifier<bool> isTipTextCn, PageController pageCtrl) {
|
||||
return Stack(
|
||||
children: [
|
||||
Center(
|
||||
@ -42,9 +41,7 @@ class AboutUI extends HookConsumerWidget {
|
||||
const SizedBox(height: 32),
|
||||
Image.asset("assets/app_logo.png", width: 128, height: 128),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
S.current.app_index_version_info(
|
||||
ConstConf.appVersion, ConstConf.isMSE ? "" : " Dev"),
|
||||
Text(S.current.app_index_version_info(ConstConf.appVersion, ConstConf.isMSE ? "" : " Dev"),
|
||||
style: const TextStyle(fontSize: 18)),
|
||||
const SizedBox(height: 12),
|
||||
Button(
|
||||
@ -56,18 +53,15 @@ class AboutUI extends HookConsumerWidget {
|
||||
const SizedBox(height: 32),
|
||||
Container(
|
||||
margin: const EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
color: FluentTheme.of(context).cardColor,
|
||||
borderRadius: BorderRadius.circular(12)),
|
||||
decoration:
|
||||
BoxDecoration(color: FluentTheme.of(context).cardColor, borderRadius: BorderRadius.circular(12)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
S.current.about_app_description,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.white.withValues(alpha: .9)),
|
||||
style: TextStyle(fontSize: 14, color: Colors.white.withValues(alpha: .9)),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -86,9 +80,7 @@ class AboutUI extends HookConsumerWidget {
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width * .35,
|
||||
decoration: BoxDecoration(
|
||||
color: FluentTheme.of(context)
|
||||
.cardColor
|
||||
.withValues(alpha: .06),
|
||||
color: FluentTheme.of(context).cardColor.withValues(alpha: .06),
|
||||
borderRadius: BorderRadius.circular(12)),
|
||||
child: IconButton(
|
||||
icon: Padding(
|
||||
@ -96,9 +88,7 @@ class AboutUI extends HookConsumerWidget {
|
||||
child: Text(
|
||||
isTipTextCn.value ? tipTextCN : tipTextEN,
|
||||
textAlign: TextAlign.start,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.white.withValues(alpha: .9)),
|
||||
style: TextStyle(fontSize: 12, color: Colors.white.withValues(alpha: .9)),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
@ -126,8 +116,7 @@ class AboutUI extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _makeDonate(
|
||||
BuildContext context, WidgetRef ref, PageController pageCtrl) {
|
||||
Widget _makeDonate(BuildContext context, WidgetRef ref, PageController pageCtrl) {
|
||||
final donationTypeNotifier = useState('alipay');
|
||||
final bubbleMessages = [
|
||||
S.current.support_dev_thanks_message,
|
||||
@ -171,8 +160,7 @@ class AboutUI extends HookConsumerWidget {
|
||||
for (var i = 0; i < bubbleMessages.length; i++)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: SelectionArea(
|
||||
child: ChatBubble(message: bubbleMessages[i])),
|
||||
child: SelectionArea(child: ChatBubble(message: bubbleMessages[i])),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -261,14 +249,10 @@ class AboutUI extends HookConsumerWidget {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Button(
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty.resolveWith((states) =>
|
||||
isSelected
|
||||
? ButtonThemeData.buttonColor(context, states)
|
||||
.withAlpha((255.0 * 0.08).round())
|
||||
: ButtonThemeData.buttonColor(context, states)
|
||||
.withAlpha((255.0 * 0.005).round())),
|
||||
padding: WidgetStateProperty.all(
|
||||
EdgeInsets.symmetric(horizontal: 16, vertical: 8)),
|
||||
backgroundColor: WidgetStateProperty.resolveWith((states) => isSelected
|
||||
? ButtonThemeData.buttonColor(context, states).withAlpha((255.0 * 0.08).round())
|
||||
: ButtonThemeData.buttonColor(context, states).withAlpha((255.0 * 0.005).round())),
|
||||
padding: WidgetStateProperty.all(EdgeInsets.symmetric(horizontal: 16, vertical: 8)),
|
||||
),
|
||||
onPressed: onTap,
|
||||
child: Column(
|
||||
@ -328,16 +312,13 @@ class AboutUI extends HookConsumerWidget {
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: FluentTheme.of(context)
|
||||
.cardColor
|
||||
.withAlpha((255 * .1).round()),
|
||||
color: FluentTheme.of(context).cardColor.withAlpha((255 * .1).round()),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(S.current.support_dev_in_game_id,
|
||||
style: TextStyle(fontSize: 16)),
|
||||
Text(S.current.support_dev_in_game_id, style: TextStyle(fontSize: 16)),
|
||||
const SizedBox(width: 12),
|
||||
Button(
|
||||
onPressed: () {
|
||||
@ -386,8 +367,7 @@ class AboutUI extends HookConsumerWidget {
|
||||
return Column(
|
||||
key: ValueKey(type),
|
||||
children: [
|
||||
Text(title,
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
Text(title, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
width: 200,
|
||||
@ -413,19 +393,13 @@ class AboutUI extends HookConsumerWidget {
|
||||
icon: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
pageIndex == 0
|
||||
? FluentIcons.chevron_up
|
||||
: FluentIcons.chevron_down,
|
||||
size: 12),
|
||||
Icon(pageIndex == 0 ? FluentIcons.chevron_up : FluentIcons.chevron_down, size: 12),
|
||||
SizedBox(width: 8),
|
||||
Text(pageIndex == 0
|
||||
? S.current.support_dev_back_button
|
||||
: S.current.support_dev_scroll_hint),
|
||||
Text(pageIndex == 0 ? S.current.support_dev_back_button : S.current.support_dev_scroll_hint),
|
||||
],
|
||||
),
|
||||
onPressed: () => pageCtrl.animateToPage(pageIndex,
|
||||
duration: const Duration(milliseconds: 300), curve: Curves.ease),
|
||||
onPressed: () =>
|
||||
pageCtrl.animateToPage(pageIndex, duration: const Duration(milliseconds: 300), curve: Curves.ease),
|
||||
);
|
||||
}
|
||||
|
||||
@ -440,8 +414,7 @@ class AboutUI extends HookConsumerWidget {
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
S.current.about_action_btn_faq,
|
||||
style: TextStyle(
|
||||
fontSize: 14, color: Colors.white.withValues(alpha: .6)),
|
||||
style: TextStyle(fontSize: 14, color: Colors.white.withValues(alpha: .6)),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -457,8 +430,7 @@ class AboutUI extends HookConsumerWidget {
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
S.current.about_online_feedback,
|
||||
style: TextStyle(
|
||||
fontSize: 14, color: Colors.white.withValues(alpha: .6)),
|
||||
style: TextStyle(fontSize: 14, color: Colors.white.withValues(alpha: .6)),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -474,8 +446,7 @@ class AboutUI extends HookConsumerWidget {
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
S.current.about_action_qq_group,
|
||||
style: TextStyle(
|
||||
fontSize: 14, color: Colors.white.withValues(alpha: .6)),
|
||||
style: TextStyle(fontSize: 14, color: Colors.white.withValues(alpha: .6)),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -492,8 +463,7 @@ class AboutUI extends HookConsumerWidget {
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
S.current.about_action_email,
|
||||
style: TextStyle(
|
||||
fontSize: 14, color: Colors.white.withValues(alpha: .6)),
|
||||
style: TextStyle(fontSize: 14, color: Colors.white.withValues(alpha: .6)),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -509,8 +479,7 @@ class AboutUI extends HookConsumerWidget {
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
S.current.about_action_open_source,
|
||||
style: TextStyle(
|
||||
fontSize: 14, color: Colors.white.withValues(alpha: .6)),
|
||||
style: TextStyle(fontSize: 14, color: Colors.white.withValues(alpha: .6)),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -528,6 +497,7 @@ class AboutUI extends HookConsumerWidget {
|
||||
static String get tipTextCN => S.current.about_disclaimer;
|
||||
|
||||
Widget makeAnalyticsWidget(BuildContext context) {
|
||||
var buildIndex = 0;
|
||||
return LoadingWidget(
|
||||
onLoadData: AnalyticsApi.getAnalyticsData,
|
||||
autoRefreshDuration: const Duration(seconds: 60),
|
||||
@ -547,20 +517,18 @@ class AboutUI extends HookConsumerWidget {
|
||||
"performance_apply",
|
||||
"p4k_download",
|
||||
].contains(item["Type"]))
|
||||
makeAnalyticsItem(
|
||||
context: context,
|
||||
name: item["Type"] as String,
|
||||
value: item["Count"] as int)
|
||||
GridItemAnimator(
|
||||
index: buildIndex++,
|
||||
child: makeAnalyticsItem(
|
||||
context: context, name: item["Type"] as String, value: item["Count"] as int),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget makeAnalyticsItem(
|
||||
{required BuildContext context,
|
||||
required String name,
|
||||
required int value}) {
|
||||
Widget makeAnalyticsItem({required BuildContext context, required String name, required int value}) {
|
||||
final names = {
|
||||
"launch": S.current.about_analytics_launch,
|
||||
"gameLaunch": S.current.about_analytics_launch_game,
|
||||
@ -573,14 +541,12 @@ class AboutUI extends HookConsumerWidget {
|
||||
padding: const EdgeInsets.all(12),
|
||||
margin: const EdgeInsets.only(left: 18, right: 18),
|
||||
decoration: BoxDecoration(
|
||||
color: FluentTheme.of(context).cardColor.withValues(alpha: .06),
|
||||
borderRadius: BorderRadius.circular(12)),
|
||||
color: FluentTheme.of(context).cardColor.withValues(alpha: .06), borderRadius: BorderRadius.circular(12)),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
names[name] ?? name,
|
||||
style: TextStyle(
|
||||
fontSize: 13, color: Colors.white.withValues(alpha: .6)),
|
||||
style: TextStyle(fontSize: 13, color: Colors.white.withValues(alpha: .6)),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
@ -606,8 +572,7 @@ class AboutUI extends HookConsumerWidget {
|
||||
launchUrlString("ms-windows-store://pdp/?productid=9NF3SWFWNKL1");
|
||||
return;
|
||||
} else {
|
||||
final hasUpdate =
|
||||
await ref.read(appGlobalModelProvider.notifier).checkUpdate(context);
|
||||
final hasUpdate = await ref.read(appGlobalModelProvider.notifier).checkUpdate(context);
|
||||
if (!hasUpdate) {
|
||||
if (!context.mounted) return;
|
||||
showToast(context, S.current.about_info_latest_version);
|
||||
@ -626,8 +591,7 @@ class ChatBubble extends StatelessWidget {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
FluentTheme.of(context).accentColor.withAlpha((255.0 * .2).round()),
|
||||
color: FluentTheme.of(context).accentColor.withAlpha((255.0 * .2).round()),
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(4),
|
||||
topRight: Radius.circular(18),
|
||||
@ -645,8 +609,7 @@ class ChatBubble extends StatelessWidget {
|
||||
|
||||
class DonationQrCodeData {
|
||||
static const alipay = "https://qr.alipay.com/tsx16308c4uai0ticmz4j96";
|
||||
static const wechat =
|
||||
"wxp://f2f0J40rTCX7Vt79yooWNbiqH3U6UmwGJkqjcAYnrv9OZVzKyS5_W6trp8mo3KP-CTQ5";
|
||||
static const wechat = "wxp://f2f0J40rTCX7Vt79yooWNbiqH3U6UmwGJkqjcAYnrv9OZVzKyS5_W6trp8mo3KP-CTQ5";
|
||||
static const qq =
|
||||
"https://i.qianbao.qq.com/wallet/sqrcode.htm?m=tenpay&f=wallet&a=1&u=3334969096&n=xkeyC&ac=CAEQiK6etgwY8ZuKvgYyGOa1geWKqOaRiuS9jee7j-iQpeaUtuasvjgBQiAzY2Y4NWY3MDI1MWUxYWEwMGYyN2Q0OTM4Y2U2ODFlMw%3D%3D_xxx_sign";
|
||||
}
|
||||
|
@ -28,8 +28,7 @@ class AdvancedLocalizationUI extends HookConsumerWidget {
|
||||
onSwitchFile() async {
|
||||
final sb = await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) =>
|
||||
const LocalizationFromFileDialogUI(isInAdvancedMode: true),
|
||||
builder: (BuildContext context) => const LocalizationFromFileDialogUI(isInAdvancedMode: true),
|
||||
);
|
||||
if (sb is (StringBuffer, bool)) {
|
||||
model.setCustomizeGlobalIni(sb.$1.toString());
|
||||
@ -42,8 +41,7 @@ class AdvancedLocalizationUI extends HookConsumerWidget {
|
||||
}, const []);
|
||||
|
||||
return makeDefaultPage(
|
||||
title: S.current.home_localization_advanced_title(
|
||||
homeUIState.scInstalledPath ?? "-"),
|
||||
title: S.current.home_localization_advanced_title(homeUIState.scInstalledPath ?? "-"),
|
||||
context,
|
||||
content: state.workingText.isNotEmpty
|
||||
? Center(
|
||||
@ -71,15 +69,13 @@ class AdvancedLocalizationUI extends HookConsumerWidget {
|
||||
children: [
|
||||
Text(
|
||||
S.current.home_localization_advanced_msg_version(
|
||||
state.apiLocalizationData?.versionName ??
|
||||
"-"),
|
||||
state.apiLocalizationData?.versionName ?? "-"),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Button(
|
||||
onPressed: onSwitchFile,
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 6, vertical: 3),
|
||||
padding: EdgeInsets.symmetric(horizontal: 6, vertical: 3),
|
||||
child: Icon(FluentIcons.switch_widget),
|
||||
)),
|
||||
if (state.customizeGlobalIni != null) ...[
|
||||
@ -89,23 +85,19 @@ class AdvancedLocalizationUI extends HookConsumerWidget {
|
||||
model.setCustomizeGlobalIni(null);
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 6, vertical: 3),
|
||||
padding: EdgeInsets.symmetric(horizontal: 6, vertical: 3),
|
||||
child: Icon(FluentIcons.delete),
|
||||
)),
|
||||
]
|
||||
],
|
||||
)),
|
||||
Text(S.current.home_localization_advanced_title_msg(
|
||||
state.serverGlobalIniLines,
|
||||
state.p4kGlobalIniLines)),
|
||||
Text(S.current
|
||||
.home_localization_advanced_title_msg(state.serverGlobalIniLines, state.p4kGlobalIniLines)),
|
||||
const SizedBox(width: 32),
|
||||
Button(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 12, right: 12, top: 4, bottom: 4),
|
||||
child: Text(S.current
|
||||
.home_localization_advanced_action_install),
|
||||
padding: const EdgeInsets.only(left: 12, right: 12, top: 4, bottom: 4),
|
||||
child: Text(S.current.home_localization_advanced_action_install),
|
||||
),
|
||||
onPressed: () {
|
||||
model.onInstall(context);
|
||||
@ -113,19 +105,13 @@ class AdvancedLocalizationUI extends HookConsumerWidget {
|
||||
const SizedBox(width: 12),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child:
|
||||
_makeBody(context, homeUIState, state, ref, model)),
|
||||
Expanded(child: _makeBody(context, homeUIState, state, ref, model)),
|
||||
]
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
Widget _makeBody(
|
||||
BuildContext context,
|
||||
HomeUIModelState homeUIState,
|
||||
AdvancedLocalizationUIState state,
|
||||
WidgetRef ref,
|
||||
Widget _makeBody(BuildContext context, HomeUIModelState homeUIState, AdvancedLocalizationUIState state, WidgetRef ref,
|
||||
AdvancedLocalizationUIModel model) {
|
||||
return AlignedGridView.count(
|
||||
crossAxisCount: 4,
|
||||
@ -134,109 +120,104 @@ class AdvancedLocalizationUI extends HookConsumerWidget {
|
||||
padding: const EdgeInsets.all(12),
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final item = state.classMap!.values.elementAt(index);
|
||||
return Container(
|
||||
padding: const EdgeInsets.only(top: 6, bottom: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: .05),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed:
|
||||
item.isWorking ? null : () => _showContent(context, item),
|
||||
icon: Padding(
|
||||
padding: const EdgeInsets.only(left: 12, right: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"${item.className}",
|
||||
style: const TextStyle(
|
||||
fontSize: 16, fontWeight: FontWeight.bold),
|
||||
textAlign: TextAlign.start,
|
||||
)),
|
||||
Text(
|
||||
"${item.valuesMap.length}",
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.white.withValues(alpha: .6),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Icon(
|
||||
FluentIcons.chevron_right,
|
||||
color: Colors.white.withValues(alpha: .6),
|
||||
size: 16,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 6, bottom: 12),
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: 1,
|
||||
color: Colors.white.withValues(alpha: .1),
|
||||
),
|
||||
if (item.isWorking)
|
||||
Column(
|
||||
children: [
|
||||
makeLoading(context),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
S.current.home_localization_advanced_action_mod_change),
|
||||
],
|
||||
)
|
||||
else ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 12, right: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(S
|
||||
.current.home_localization_advanced_action_mode)),
|
||||
ComboBox(
|
||||
value: item.mode,
|
||||
items: [
|
||||
for (final type
|
||||
in AppAdvancedLocalizationClassKeysDataMode
|
||||
.values)
|
||||
ComboBoxItem(
|
||||
value: type,
|
||||
child: Text(state.typeNames[type] ?? "-"),
|
||||
),
|
||||
],
|
||||
onChanged: item.lockMod
|
||||
? null
|
||||
: (v) => model.onChangeMod(item,
|
||||
v as AppAdvancedLocalizationClassKeysDataMode),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
SizedBox(
|
||||
height: 180,
|
||||
child: SuperListView.builder(
|
||||
itemCount: item.valuesMap.length,
|
||||
return GridItemAnimator(
|
||||
index: index,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(top: 6, bottom: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: .05),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: item.isWorking ? null : () => _showContent(context, item),
|
||||
icon: Padding(
|
||||
padding: const EdgeInsets.only(left: 12, right: 12),
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final itemKey = item.valuesMap.keys.elementAt(index);
|
||||
return Text(
|
||||
"${item.valuesMap[itemKey]}",
|
||||
maxLines: 1,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"${item.className}",
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
textAlign: TextAlign.start,
|
||||
)),
|
||||
Text(
|
||||
"${item.valuesMap.length}",
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.white.withValues(alpha: .6),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
const SizedBox(width: 6),
|
||||
Icon(
|
||||
FluentIcons.chevron_right,
|
||||
color: Colors.white.withValues(alpha: .6),
|
||||
size: 16,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 6, bottom: 12),
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: 1,
|
||||
color: Colors.white.withValues(alpha: .1),
|
||||
),
|
||||
if (item.isWorking)
|
||||
Column(
|
||||
children: [
|
||||
makeLoading(context),
|
||||
const SizedBox(height: 6),
|
||||
Text(S.current.home_localization_advanced_action_mod_change),
|
||||
],
|
||||
)
|
||||
else ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 12, right: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(child: Text(S.current.home_localization_advanced_action_mode)),
|
||||
ComboBox(
|
||||
value: item.mode,
|
||||
items: [
|
||||
for (final type in AppAdvancedLocalizationClassKeysDataMode.values)
|
||||
ComboBoxItem(
|
||||
value: type,
|
||||
child: Text(state.typeNames[type] ?? "-"),
|
||||
),
|
||||
],
|
||||
onChanged: item.lockMod
|
||||
? null
|
||||
: (v) => model.onChangeMod(item, v as AppAdvancedLocalizationClassKeysDataMode),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
SizedBox(
|
||||
height: 180,
|
||||
child: SuperListView.builder(
|
||||
itemCount: item.valuesMap.length,
|
||||
padding: const EdgeInsets.only(left: 12, right: 12),
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final itemKey = item.valuesMap.keys.elementAt(index);
|
||||
return Text(
|
||||
"${item.valuesMap[itemKey]}",
|
||||
maxLines: 1,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
@ -244,8 +225,7 @@ class AdvancedLocalizationUI extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
_showContent(
|
||||
BuildContext context, AppAdvancedLocalizationClassKeysData item) {
|
||||
_showContent(BuildContext context, AppAdvancedLocalizationClassKeysData item) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
@ -282,8 +262,7 @@ class AdvancedLocalizationUI extends HookConsumerWidget {
|
||||
const SizedBox(
|
||||
width: 24,
|
||||
),
|
||||
Text(S.current.home_localization_advanced_title_preview(
|
||||
item.className ?? "-")),
|
||||
Text(S.current.home_localization_advanced_title_preview(item.className ?? "-")),
|
||||
],
|
||||
),
|
||||
content: textData.value.isEmpty
|
||||
@ -295,13 +274,10 @@ class AdvancedLocalizationUI extends HookConsumerWidget {
|
||||
),
|
||||
child: CodeEditor(
|
||||
readOnly: true,
|
||||
controller:
|
||||
CodeLineEditingController.fromText(textData.value),
|
||||
controller: CodeLineEditingController.fromText(textData.value),
|
||||
style: CodeEditorStyle(
|
||||
codeTheme: CodeHighlightTheme(
|
||||
languages: {
|
||||
'ini': CodeHighlightThemeMode(mode: langIni)
|
||||
},
|
||||
languages: {'ini': CodeHighlightThemeMode(mode: langIni)},
|
||||
theme: vs2015Theme,
|
||||
),
|
||||
),
|
||||
|
@ -30,8 +30,7 @@ class LocalizationDialogUI extends HookConsumerWidget {
|
||||
return ContentDialog(
|
||||
title: makeTitle(context, model, state),
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * .7,
|
||||
minHeight: MediaQuery.of(context).size.height * .9),
|
||||
maxWidth: MediaQuery.of(context).size.width * .7, minHeight: MediaQuery.of(context).size.height * .9),
|
||||
content: Padding(
|
||||
padding: const EdgeInsets.only(left: 12, right: 12, top: 12),
|
||||
child: SingleChildScrollView(
|
||||
@ -40,18 +39,15 @@ class LocalizationDialogUI extends HookConsumerWidget {
|
||||
AnimatedSize(
|
||||
duration: const Duration(milliseconds: 130),
|
||||
child: state.patchStatus?.key == true &&
|
||||
state.patchStatus?.value ==
|
||||
S.current.home_action_info_game_built_in
|
||||
state.patchStatus?.value == S.current.home_action_info_game_built_in
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: InfoBar(
|
||||
title: Text(S.current.home_action_info_warning),
|
||||
content: Text(S.current
|
||||
.localization_info_machine_translation_warning),
|
||||
content: Text(S.current.localization_info_machine_translation_warning),
|
||||
severity: InfoBarSeverity.info,
|
||||
style: InfoBarThemeData(decoration: (severity) {
|
||||
return const BoxDecoration(
|
||||
color: Color.fromRGBO(155, 7, 7, 1.0));
|
||||
return const BoxDecoration(color: Color.fromRGBO(155, 7, 7, 1.0));
|
||||
}, iconColor: (severity) {
|
||||
return Colors.white;
|
||||
}),
|
||||
@ -65,10 +61,8 @@ class LocalizationDialogUI extends HookConsumerWidget {
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: InfoBar(
|
||||
title: Text(S.current
|
||||
.home_localization_ptu_advanced_localization_tip_title),
|
||||
content: Text(S.current
|
||||
.home_localization_ptu_advanced_localization_tip_title_info),
|
||||
title: Text(S.current.home_localization_ptu_advanced_localization_tip_title),
|
||||
content: Text(S.current.home_localization_ptu_advanced_localization_tip_title_info),
|
||||
severity: InfoBarSeverity.info,
|
||||
style: InfoBarThemeData(decoration: (severity) {
|
||||
return BoxDecoration(color: Colors.orange);
|
||||
@ -89,9 +83,7 @@ class LocalizationDialogUI extends HookConsumerWidget {
|
||||
children: [
|
||||
Center(
|
||||
child: Text(S.current.localization_info_enabled(
|
||||
LocalizationUIModel.languageSupport[
|
||||
state.selectedLanguage] ??
|
||||
"")),
|
||||
LocalizationUIModel.languageSupport[state.selectedLanguage] ?? "")),
|
||||
),
|
||||
const Spacer(),
|
||||
ToggleSwitch(
|
||||
@ -109,20 +101,15 @@ class LocalizationDialogUI extends HookConsumerWidget {
|
||||
Text(S.current.localization_info_installed_version(
|
||||
"${state.patchStatus?.value ?? ""} ${(state.isInstalledAdvanced ?? false) ? S.current.home_localization_msg_version_advanced : ""}")),
|
||||
SizedBox(width: 24),
|
||||
if (state
|
||||
.installedCommunityInputMethodSupportVersion !=
|
||||
null)
|
||||
if (state.installedCommunityInputMethodSupportVersion != null)
|
||||
Text(
|
||||
S.current
|
||||
.input_method_community_input_method_support_version(
|
||||
state.installedCommunityInputMethodSupportVersion ??
|
||||
"?"),
|
||||
S.current.input_method_community_input_method_support_version(
|
||||
state.installedCommunityInputMethodSupportVersion ?? "?"),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
if (state.patchStatus?.value !=
|
||||
S.current.home_action_info_game_built_in)
|
||||
if (state.patchStatus?.value != S.current.home_action_info_game_built_in)
|
||||
Row(
|
||||
children: [
|
||||
Button(
|
||||
@ -133,8 +120,7 @@ class LocalizationDialogUI extends HookConsumerWidget {
|
||||
children: [
|
||||
const Icon(FluentIcons.feedback),
|
||||
const SizedBox(width: 6),
|
||||
Text(S.current
|
||||
.localization_action_translation_feedback),
|
||||
Text(S.current.localization_action_translation_feedback),
|
||||
],
|
||||
),
|
||||
)),
|
||||
@ -147,8 +133,7 @@ class LocalizationDialogUI extends HookConsumerWidget {
|
||||
children: [
|
||||
const Icon(FluentIcons.delete),
|
||||
const SizedBox(width: 6),
|
||||
Text(S.current
|
||||
.localization_action_uninstall_translation),
|
||||
Text(S.current.localization_action_uninstall_translation),
|
||||
],
|
||||
),
|
||||
)),
|
||||
@ -167,11 +152,8 @@ class LocalizationDialogUI extends HookConsumerWidget {
|
||||
else if (state.apiLocalizationData!.isEmpty)
|
||||
Center(
|
||||
child: Text(
|
||||
S.current
|
||||
.localization_info_no_translation_available,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Colors.white.withValues(alpha: .8)),
|
||||
S.current.localization_info_no_translation_available,
|
||||
style: TextStyle(fontSize: 13, color: Colors.white.withValues(alpha: .8)),
|
||||
),
|
||||
)
|
||||
else
|
||||
@ -180,9 +162,8 @@ class LocalizationDialogUI extends HookConsumerWidget {
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final item = state.apiLocalizationData!.entries
|
||||
.elementAt(index);
|
||||
return makeRemoteList(context, model, item, state);
|
||||
final item = state.apiLocalizationData!.entries.elementAt(index);
|
||||
return makeRemoteList(context, model, item, state, index);
|
||||
},
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
@ -198,136 +179,124 @@ class LocalizationDialogUI extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget makeRemoteList(BuildContext context, LocalizationUIModel model,
|
||||
MapEntry<String, ScLocalizationData> item, LocalizationUIState state) {
|
||||
Widget makeRemoteList(BuildContext context, LocalizationUIModel model, MapEntry<String, ScLocalizationData> item,
|
||||
LocalizationUIState state, int index) {
|
||||
final isWorking = state.workingVersion.isNotEmpty;
|
||||
final isMineWorking = state.workingVersion == item.key;
|
||||
final isInstalled = state.patchStatus?.value == item.key;
|
||||
final isItemEnabled = ((item.value.enable ?? false));
|
||||
final tapDisabled =
|
||||
isInstalled || isWorking || !isItemEnabled || isMineWorking;
|
||||
return Tilt(
|
||||
shadowConfig: const ShadowConfig(maxIntensity: .3),
|
||||
borderRadius: BorderRadius.circular(7),
|
||||
disable: tapDisabled,
|
||||
child: GestureDetector(
|
||||
onTap: tapDisabled
|
||||
? null
|
||||
: () => model.onRemoteInsTall(context, item, state),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: tapDisabled ? .03 : .05),
|
||||
borderRadius: BorderRadius.circular(7),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"${item.value.info}",
|
||||
style: const TextStyle(fontSize: 19),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
S.current.localization_info_version_number(
|
||||
item.value.versionName ?? ""),
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: .6)),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
S.current.localization_info_channel(
|
||||
item.value.gameChannel ?? ""),
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: .6)),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
S.current.localization_info_update_time(
|
||||
item.value.updateAt ?? ""),
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: .6)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (isMineWorking)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(right: 12),
|
||||
child: ProgressRing(),
|
||||
)
|
||||
else ...[
|
||||
Icon(
|
||||
isInstalled
|
||||
? FluentIcons.check_mark
|
||||
: isItemEnabled
|
||||
? FluentIcons.download
|
||||
: FluentIcons.disable_updates,
|
||||
color: Colors.white.withValues(alpha: .8),
|
||||
size: 18,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
isInstalled
|
||||
? S.current.localization_info_installed
|
||||
: (isItemEnabled
|
||||
? S.current.localization_action_install
|
||||
: S.current.localization_info_unavailable),
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: .8),
|
||||
final tapDisabled = isInstalled || isWorking || !isItemEnabled || isMineWorking;
|
||||
return GridItemAnimator(
|
||||
index: index,
|
||||
child: Tilt(
|
||||
shadowConfig: const ShadowConfig(maxIntensity: .3),
|
||||
borderRadius: BorderRadius.circular(7),
|
||||
disable: tapDisabled,
|
||||
child: GestureDetector(
|
||||
onTap: tapDisabled ? null : () => model.onRemoteInsTall(context, item, state),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: tapDisabled ? .03 : .05),
|
||||
borderRadius: BorderRadius.circular(7),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"${item.value.info}",
|
||||
style: const TextStyle(fontSize: 19),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
S.current.localization_info_version_number(item.value.versionName ?? ""),
|
||||
style: TextStyle(color: Colors.white.withValues(alpha: .6)),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
S.current.localization_info_channel(item.value.gameChannel ?? ""),
|
||||
style: TextStyle(color: Colors.white.withValues(alpha: .6)),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
S.current.localization_info_update_time(item.value.updateAt ?? ""),
|
||||
style: TextStyle(color: Colors.white.withValues(alpha: .6)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
if ((!isInstalled) && isItemEnabled)
|
||||
Icon(
|
||||
FluentIcons.chevron_right,
|
||||
size: 14,
|
||||
color: Colors.white.withValues(alpha: .6),
|
||||
if (isMineWorking)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(right: 12),
|
||||
child: ProgressRing(),
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
if (item.value.note != null) ...[
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
"${item.value.note}",
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: .4),
|
||||
fontSize: 13,
|
||||
),
|
||||
else ...[
|
||||
Icon(
|
||||
isInstalled
|
||||
? FluentIcons.check_mark
|
||||
: isItemEnabled
|
||||
? FluentIcons.download
|
||||
: FluentIcons.disable_updates,
|
||||
color: Colors.white.withValues(alpha: .8),
|
||||
size: 18,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
isInstalled
|
||||
? S.current.localization_info_installed
|
||||
: (isItemEnabled
|
||||
? S.current.localization_action_install
|
||||
: S.current.localization_info_unavailable),
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: .8),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
if ((!isInstalled) && isItemEnabled)
|
||||
Icon(
|
||||
FluentIcons.chevron_right,
|
||||
size: 14,
|
||||
color: Colors.white.withValues(alpha: .6),
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
if (item.value.note != null) ...[
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
"${item.value.note}",
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: .4),
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget makeListContainer(
|
||||
String title, List<Widget> children, BuildContext context,
|
||||
{List<Widget> actions = const [],
|
||||
bool gridViewMode = false,
|
||||
int gridViewCrossAxisCount = 2}) {
|
||||
Widget makeListContainer(String title, List<Widget> children, BuildContext context,
|
||||
{List<Widget> actions = const [], bool gridViewMode = false, int gridViewCrossAxisCount = 2}) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: AnimatedSize(
|
||||
duration: const Duration(milliseconds: 130),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: FluentTheme.of(context).cardColor,
|
||||
borderRadius: BorderRadius.circular(7)),
|
||||
decoration: BoxDecoration(color: FluentTheme.of(context).cardColor, borderRadius: BorderRadius.circular(7)),
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(top: 12, bottom: 12, left: 24, right: 24),
|
||||
padding: const EdgeInsets.only(top: 12, bottom: 12, left: 24, right: 24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@ -371,8 +340,7 @@ class LocalizationDialogUI extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget makeTitle(BuildContext context, LocalizationUIModel model,
|
||||
LocalizationUIState state) {
|
||||
Widget makeTitle(BuildContext context, LocalizationUIModel model, LocalizationUIState state) {
|
||||
return Row(
|
||||
children: [
|
||||
IconButton(
|
||||
@ -401,8 +369,7 @@ class LocalizationDialogUI extends HookConsumerWidget {
|
||||
ComboBox<String>(
|
||||
value: state.selectedLanguage,
|
||||
items: [
|
||||
for (final lang
|
||||
in LocalizationUIModel.languageSupport.entries)
|
||||
for (final lang in LocalizationUIModel.languageSupport.entries)
|
||||
ComboBoxItem(
|
||||
value: lang.key,
|
||||
child: Text(lang.value),
|
||||
@ -429,8 +396,7 @@ class LocalizationDialogUI extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget makeToolsListContainer(BuildContext context, LocalizationUIModel model,
|
||||
LocalizationUIState state) {
|
||||
Widget makeToolsListContainer(BuildContext context, LocalizationUIModel model, LocalizationUIState state) {
|
||||
final toolsMenu = {
|
||||
"launcher_mod": (
|
||||
const Icon(FluentIcons.c_plus_plus, size: 24),
|
||||
@ -469,8 +435,7 @@ class LocalizationDialogUI extends HookConsumerWidget {
|
||||
case "custom_files":
|
||||
final sb = await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) =>
|
||||
const LocalizationFromFileDialogUI(),
|
||||
builder: (BuildContext context) => const LocalizationFromFileDialogUI(),
|
||||
);
|
||||
if (sb is (StringBuffer, bool)) {
|
||||
await model.installFormString(
|
||||
|
@ -12,6 +12,7 @@ import 'package:window_manager/window_manager.dart';
|
||||
|
||||
import 'about/about_ui.dart';
|
||||
import 'home/home_ui.dart';
|
||||
import 'nav/nav_ui.dart';
|
||||
import 'settings/settings_ui.dart';
|
||||
import 'tools/tools_ui.dart';
|
||||
|
||||
@ -42,8 +43,7 @@ class IndexUI extends HookConsumerWidget {
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(S.current.app_index_version_info(
|
||||
ConstConf.appVersion, ConstConf.isMSE ? "" : " Dev")),
|
||||
Text(S.current.app_index_version_info(ConstConf.appVersion, ConstConf.isMSE ? "" : " Dev")),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -77,8 +77,7 @@ class IndexUI extends HookConsumerWidget {
|
||||
key: Key("NavigationPane_${S.current.app_language_code}"),
|
||||
selected: curIndex.value,
|
||||
items: getNavigationPaneItems(curIndex),
|
||||
size: NavigationPaneSize(
|
||||
openWidth: S.current.app_language_code.startsWith("zh") ? 64 : 74),
|
||||
size: NavigationPaneSize(openWidth: S.current.app_language_code.startsWith("zh") ? 64 : 74),
|
||||
),
|
||||
paneBodyBuilder: (item, child) {
|
||||
return item!.body;
|
||||
@ -95,18 +94,15 @@ class IndexUI extends HookConsumerWidget {
|
||||
S.current.app_index_menu_tools,
|
||||
const ToolsUI(),
|
||||
),
|
||||
FluentIcons.settings: (
|
||||
S.current.app_index_menu_settings,
|
||||
const SettingsUI()
|
||||
),
|
||||
FluentIcons.power_apps: ("导航", const NavUI()),
|
||||
FluentIcons.settings: (S.current.app_index_menu_settings, const SettingsUI()),
|
||||
FluentIcons.info: (
|
||||
S.current.app_index_menu_about,
|
||||
const AboutUI(),
|
||||
),
|
||||
};
|
||||
|
||||
List<NavigationPaneItem> getNavigationPaneItems(
|
||||
ValueNotifier<int> curIndexState) {
|
||||
List<NavigationPaneItem> getNavigationPaneItems(ValueNotifier<int> curIndexState) {
|
||||
// width = 64
|
||||
return [
|
||||
for (final kv in pageMenus.entries)
|
||||
@ -136,8 +132,7 @@ class IndexUI extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
void _onTapIndexMenu(String value, ValueNotifier<int> curIndexState) {
|
||||
final pageIndex =
|
||||
pageMenus.values.toList().indexWhere((element) => element.$1 == value);
|
||||
final pageIndex = pageMenus.values.toList().indexWhere((element) => element.$1 == value);
|
||||
curIndexState.value = pageIndex;
|
||||
}
|
||||
|
||||
@ -156,8 +151,7 @@ class IndexUI extends HookConsumerWidget {
|
||||
color: Colors.red,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
padding: const EdgeInsets.only(
|
||||
left: 6, right: 6, bottom: 1.5, top: 1.5),
|
||||
padding: const EdgeInsets.only(left: 6, right: 6, bottom: 1.5, top: 1.5),
|
||||
child: Text(
|
||||
"${aria2cState.aria2TotalTaskNum}",
|
||||
style: const TextStyle(
|
||||
|
41
lib/ui/nav/nav_state.dart
Normal file
41
lib/ui/nav/nav_state.dart
Normal file
@ -0,0 +1,41 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:starcitizen_doctor/api/udb.dart';
|
||||
import 'package:starcitizen_doctor/data/nav_api_data.dart';
|
||||
|
||||
part 'nav_state.freezed.dart';
|
||||
|
||||
part 'nav_state.g.dart';
|
||||
|
||||
@freezed
|
||||
class NavState with _$NavState {
|
||||
const factory NavState({
|
||||
List<NavApiDocsItemData>? items,
|
||||
@Default("") String errorInfo,
|
||||
}) = _NavState;
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class Nav extends _$Nav {
|
||||
bool _mounted = true;
|
||||
|
||||
@override
|
||||
NavState build() {
|
||||
state = NavState();
|
||||
loadData(1);
|
||||
ref.onDispose(() {
|
||||
_mounted = false;
|
||||
});
|
||||
return state;
|
||||
}
|
||||
|
||||
void loadData(int pageNo) async {
|
||||
if (!_mounted) return;
|
||||
try {
|
||||
final r = await UDBNavApi.getNavItems(pageNo: pageNo);
|
||||
state = state.copyWith(items: r.docs, errorInfo: "");
|
||||
} catch (e) {
|
||||
state = state.copyWith(errorInfo: e.toString());
|
||||
}
|
||||
}
|
||||
}
|
173
lib/ui/nav/nav_state.freezed.dart
Normal file
173
lib/ui/nav/nav_state.freezed.dart
Normal file
@ -0,0 +1,173 @@
|
||||
// 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 'nav_state.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 _$NavState {
|
||||
List<NavApiDocsItemData>? get items => throw _privateConstructorUsedError;
|
||||
String get errorInfo => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of NavState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$NavStateCopyWith<NavState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $NavStateCopyWith<$Res> {
|
||||
factory $NavStateCopyWith(NavState value, $Res Function(NavState) then) =
|
||||
_$NavStateCopyWithImpl<$Res, NavState>;
|
||||
@useResult
|
||||
$Res call({List<NavApiDocsItemData>? items, String errorInfo});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$NavStateCopyWithImpl<$Res, $Val extends NavState>
|
||||
implements $NavStateCopyWith<$Res> {
|
||||
_$NavStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of NavState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? items = freezed,
|
||||
Object? errorInfo = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
items: freezed == items
|
||||
? _value.items
|
||||
: items // ignore: cast_nullable_to_non_nullable
|
||||
as List<NavApiDocsItemData>?,
|
||||
errorInfo: null == errorInfo
|
||||
? _value.errorInfo
|
||||
: errorInfo // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$NavStateImplCopyWith<$Res>
|
||||
implements $NavStateCopyWith<$Res> {
|
||||
factory _$$NavStateImplCopyWith(
|
||||
_$NavStateImpl value, $Res Function(_$NavStateImpl) then) =
|
||||
__$$NavStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({List<NavApiDocsItemData>? items, String errorInfo});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$NavStateImplCopyWithImpl<$Res>
|
||||
extends _$NavStateCopyWithImpl<$Res, _$NavStateImpl>
|
||||
implements _$$NavStateImplCopyWith<$Res> {
|
||||
__$$NavStateImplCopyWithImpl(
|
||||
_$NavStateImpl _value, $Res Function(_$NavStateImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of NavState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? items = freezed,
|
||||
Object? errorInfo = null,
|
||||
}) {
|
||||
return _then(_$NavStateImpl(
|
||||
items: freezed == items
|
||||
? _value._items
|
||||
: items // ignore: cast_nullable_to_non_nullable
|
||||
as List<NavApiDocsItemData>?,
|
||||
errorInfo: null == errorInfo
|
||||
? _value.errorInfo
|
||||
: errorInfo // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$NavStateImpl implements _NavState {
|
||||
const _$NavStateImpl(
|
||||
{final List<NavApiDocsItemData>? items, this.errorInfo = ""})
|
||||
: _items = items;
|
||||
|
||||
final List<NavApiDocsItemData>? _items;
|
||||
@override
|
||||
List<NavApiDocsItemData>? get items {
|
||||
final value = _items;
|
||||
if (value == null) return null;
|
||||
if (_items is EqualUnmodifiableListView) return _items;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(value);
|
||||
}
|
||||
|
||||
@override
|
||||
@JsonKey()
|
||||
final String errorInfo;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'NavState(items: $items, errorInfo: $errorInfo)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$NavStateImpl &&
|
||||
const DeepCollectionEquality().equals(other._items, _items) &&
|
||||
(identical(other.errorInfo, errorInfo) ||
|
||||
other.errorInfo == errorInfo));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType, const DeepCollectionEquality().hash(_items), errorInfo);
|
||||
|
||||
/// Create a copy of NavState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$NavStateImplCopyWith<_$NavStateImpl> get copyWith =>
|
||||
__$$NavStateImplCopyWithImpl<_$NavStateImpl>(this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _NavState implements NavState {
|
||||
const factory _NavState(
|
||||
{final List<NavApiDocsItemData>? items,
|
||||
final String errorInfo}) = _$NavStateImpl;
|
||||
|
||||
@override
|
||||
List<NavApiDocsItemData>? get items;
|
||||
@override
|
||||
String get errorInfo;
|
||||
|
||||
/// Create a copy of NavState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$NavStateImplCopyWith<_$NavStateImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
24
lib/ui/nav/nav_state.g.dart
Normal file
24
lib/ui/nav/nav_state.g.dart
Normal file
@ -0,0 +1,24 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'nav_state.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$navHash() => r'2019b3f675fbaec4be794049d900bf2dcc8d5e37';
|
||||
|
||||
/// See also [Nav].
|
||||
@ProviderFor(Nav)
|
||||
final navProvider = AutoDisposeNotifierProvider<Nav, NavState>.internal(
|
||||
Nav.new,
|
||||
name: r'navProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product') ? null : _$navHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$Nav = AutoDisposeNotifier<NavState>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
203
lib/ui/nav/nav_ui.dart
Normal file
203
lib/ui/nav/nav_ui.dart
Normal file
@ -0,0 +1,203 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter/gestures.dart' show TapGestureRecognizer;
|
||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
import 'package:flutter_tilt/flutter_tilt.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:starcitizen_doctor/ui/nav/nav_state.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
import 'package:starcitizen_doctor/widgets/widgets.dart';
|
||||
|
||||
class NavUI extends HookConsumerWidget {
|
||||
const NavUI({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: buildBody(context, ref),
|
||||
),
|
||||
SizedBox(height: 6),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Text.rich(
|
||||
TextSpan(children: [
|
||||
TextSpan(text: "*对应链接指向的服务由第三方提供,我们不对其做任何担保,请用户自行判断使用风险 | "),
|
||||
TextSpan(text: "网站导航数据由"),
|
||||
TextSpan(
|
||||
text: " 42kit ",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
launchUrlString("https://42kit.citizenwiki.cn/nav");
|
||||
},
|
||||
),
|
||||
TextSpan(text: "提供"),
|
||||
]),
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Colors.white.withValues(alpha: .6),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildBody(BuildContext context, WidgetRef ref) {
|
||||
final data = ref.watch(navProvider);
|
||||
if (data.errorInfo.isNotEmpty) {
|
||||
return Center(
|
||||
child: Text(
|
||||
data.errorInfo,
|
||||
style: TextStyle(color: Colors.red),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (data.items == null) {
|
||||
return const Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ProgressRing(),
|
||||
SizedBox(height: 12),
|
||||
Text("正在获取数据..."),
|
||||
],
|
||||
));
|
||||
}
|
||||
return MasonryGridView.count(
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 12,
|
||||
crossAxisSpacing: 12,
|
||||
itemCount: data.items!.length,
|
||||
padding: EdgeInsets.only(left: 12, right: 12, bottom: 12),
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
const itemHeight = 160.0;
|
||||
|
||||
final item = data.items![index];
|
||||
final itemName = item.name;
|
||||
final itemImage = item.image.url;
|
||||
return GridItemAnimator(
|
||||
index: index,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
launchUrlString(item.link);
|
||||
},
|
||||
child: Tilt(
|
||||
shadowConfig: const ShadowConfig(maxIntensity: .3),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: SizedBox(
|
||||
height: itemHeight,
|
||||
width: double.infinity,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: Stack(
|
||||
children: [
|
||||
Center(
|
||||
child: CacheNetImage(
|
||||
height: itemHeight,
|
||||
width: double.infinity,
|
||||
url: itemImage,
|
||||
fit: BoxFit.fitWidth,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: .55),
|
||||
),
|
||||
),
|
||||
ClipRect(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 15.0, sigmaY: 15.0),
|
||||
blendMode: BlendMode.srcOver,
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
height: itemHeight,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(),
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: CacheNetImage(
|
||||
url: itemImage,
|
||||
height: 48,
|
||||
width: 48,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
Flexible(
|
||||
child: Text(
|
||||
itemName,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
item.abstract_,
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(color: Colors.white.withValues(alpha: .75)),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
for (var value in item.tags)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.withValues(alpha: .6),
|
||||
borderRadius: BorderRadius.circular(12)),
|
||||
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 3),
|
||||
margin: EdgeInsets.only(right: 6),
|
||||
child: Text(
|
||||
value.name,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -40,13 +40,9 @@ class ToolsUI extends HookConsumerWidget {
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Button(
|
||||
onPressed: state.working
|
||||
? null
|
||||
: () =>
|
||||
model.loadToolsCard(context, skipPathScan: false),
|
||||
onPressed: state.working ? null : () => model.loadToolsCard(context, skipPathScan: false),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 30, bottom: 30, left: 12, right: 12),
|
||||
padding: EdgeInsets.only(top: 30, bottom: 30, left: 12, right: 12),
|
||||
child: Icon(FluentIcons.refresh),
|
||||
),
|
||||
),
|
||||
@ -75,90 +71,86 @@ class ToolsUI extends HookConsumerWidget {
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 12,
|
||||
crossAxisSpacing: 12,
|
||||
itemCount: (state.isItemLoading)
|
||||
? state.items.length + 1
|
||||
: state.items.length,
|
||||
itemCount: (state.isItemLoading) ? state.items.length + 1 : state.items.length,
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == state.items.length) {
|
||||
return Container(
|
||||
width: 300,
|
||||
height: 200,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: FluentTheme.of(context).cardColor,
|
||||
),
|
||||
child: makeLoading(context));
|
||||
return GridItemAnimator(
|
||||
index: index,
|
||||
child: Container(
|
||||
width: 300,
|
||||
height: 160,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: FluentTheme.of(context).cardColor,
|
||||
),
|
||||
child: makeLoading(context)),
|
||||
);
|
||||
}
|
||||
final item = state.items[index];
|
||||
return Container(
|
||||
width: 300,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: FluentTheme.of(context).cardColor,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
Colors.white.withValues(alpha: .2),
|
||||
borderRadius:
|
||||
BorderRadius.circular(1000)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: item.icon,
|
||||
return GridItemAnimator(
|
||||
index: index,
|
||||
child: Container(
|
||||
width: 300,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: FluentTheme.of(context).cardColor,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: .2),
|
||||
borderRadius: BorderRadius.circular(1000)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: item.icon,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
item.name,
|
||||
style: const TextStyle(fontSize: 16),
|
||||
)),
|
||||
const SizedBox(width: 12),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
item.infoString,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.white.withValues(alpha: .6)),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
const Spacer(),
|
||||
Button(
|
||||
onPressed: state.working
|
||||
? null
|
||||
: item.onTap == null
|
||||
? null
|
||||
: () {
|
||||
try {
|
||||
item.onTap?.call();
|
||||
} catch (e) {
|
||||
showToast(
|
||||
context,
|
||||
S.current
|
||||
.tools_info_processing_failed(
|
||||
e));
|
||||
}
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(6),
|
||||
child: Icon(FluentIcons.play),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
item.name,
|
||||
style: const TextStyle(fontSize: 16),
|
||||
)),
|
||||
const SizedBox(width: 12),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
item.infoString,
|
||||
style: TextStyle(fontSize: 14, color: Colors.white.withValues(alpha: .6)),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
const Spacer(),
|
||||
Button(
|
||||
onPressed: state.working
|
||||
? null
|
||||
: item.onTap == null
|
||||
? null
|
||||
: () {
|
||||
try {
|
||||
item.onTap?.call();
|
||||
} catch (e) {
|
||||
showToast(context, S.current.tools_info_processing_failed(e));
|
||||
}
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(6),
|
||||
child: Icon(FluentIcons.play),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -188,8 +180,7 @@ class ToolsUI extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget makeGamePathSelect(
|
||||
BuildContext context, ToolsUIModel model, ToolsUIState state) {
|
||||
Widget makeGamePathSelect(BuildContext context, ToolsUIModel model, ToolsUIState state) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
@ -223,8 +214,7 @@ class ToolsUI extends HookConsumerWidget {
|
||||
),
|
||||
onPressed: () {
|
||||
if (state.scInstalledPath.trim().isEmpty) {
|
||||
showToast(context,
|
||||
S.current.tools_action_info_star_citizen_not_found);
|
||||
showToast(context, S.current.tools_action_info_star_citizen_not_found);
|
||||
return;
|
||||
}
|
||||
model.openDir(state.scInstalledPath);
|
||||
@ -233,8 +223,7 @@ class ToolsUI extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget makeGameLauncherPathSelect(
|
||||
BuildContext context, ToolsUIModel model, ToolsUIState state) {
|
||||
Widget makeGameLauncherPathSelect(BuildContext context, ToolsUIModel model, ToolsUIState state) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
@ -268,10 +257,7 @@ class ToolsUI extends HookConsumerWidget {
|
||||
),
|
||||
onPressed: () {
|
||||
if (state.scInstalledPath.trim().isEmpty) {
|
||||
showToast(
|
||||
context,
|
||||
S.current
|
||||
.tools_rsi_launcher_enhance_msg_error_launcher_notfound);
|
||||
showToast(context, S.current.tools_rsi_launcher_enhance_msg_error_launcher_notfound);
|
||||
return;
|
||||
}
|
||||
model.openDir(state.rsiLauncherInstalledPath);
|
||||
|
@ -6,7 +6,7 @@ part of 'tools_ui_model.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$toolsUIModelHash() => r'cd72f7833fa5696baf9022d16d10d7951387df7e';
|
||||
String _$toolsUIModelHash() => r'c8830e26df6c0ee572dd5e78c4ccef3317f8b4e6';
|
||||
|
||||
/// See also [ToolsUIModel].
|
||||
@ProviderFor(ToolsUIModel)
|
||||
|
@ -1,5 +1,8 @@
|
||||
import 'package:extended_image/extended_image.dart';
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
import 'cache_svg_image.dart';
|
||||
|
||||
class CacheNetImage extends StatelessWidget {
|
||||
final String url;
|
||||
@ -7,11 +10,18 @@ class CacheNetImage extends StatelessWidget {
|
||||
final double? height;
|
||||
final BoxFit? fit;
|
||||
|
||||
const CacheNetImage(
|
||||
{super.key, required this.url, this.width, this.height, this.fit});
|
||||
const CacheNetImage({super.key, required this.url, this.width, this.height, this.fit});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (url.endsWith(".svg")) {
|
||||
return CachedSvgImage(
|
||||
url,
|
||||
width: width,
|
||||
height: height,
|
||||
fit: fit ?? BoxFit.contain,
|
||||
);
|
||||
}
|
||||
return ExtendedImage.network(
|
||||
url,
|
||||
width: width,
|
||||
@ -20,14 +30,11 @@ class CacheNetImage extends StatelessWidget {
|
||||
loadStateChanged: (ExtendedImageState state) {
|
||||
switch (state.extendedImageLoadState) {
|
||||
case LoadState.loading:
|
||||
return const Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
children: [
|
||||
ProgressRing(),
|
||||
],
|
||||
),
|
||||
return SizedBox(
|
||||
width: width,
|
||||
height: height,
|
||||
child: Center(
|
||||
child: ProgressRing(),
|
||||
),
|
||||
);
|
||||
case LoadState.failed:
|
||||
|
64
lib/widgets/src/cache_svg_image.dart
Normal file
64
lib/widgets/src/cache_svg_image.dart
Normal file
@ -0,0 +1,64 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/file_cache_utils.dart';
|
||||
|
||||
class CachedSvgImage extends HookWidget {
|
||||
final String url;
|
||||
final double? width;
|
||||
final double? height;
|
||||
final BoxFit? fit;
|
||||
|
||||
const CachedSvgImage(this.url, {super.key, this.width, this.height, this.fit});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cachedFile = useState<File?>(null);
|
||||
final errorInfo = useState<String?>(null);
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
() async {
|
||||
try {
|
||||
cachedFile.value = await FileCacheUtils.getFile(url);
|
||||
} catch (e) {
|
||||
debugPrint("Error loading SVG: $e");
|
||||
errorInfo.value = "Error loading SVG: $e";
|
||||
}
|
||||
}();
|
||||
return null;
|
||||
},
|
||||
[url],
|
||||
);
|
||||
|
||||
if (errorInfo.value != null) {
|
||||
return SizedBox(
|
||||
width: width,
|
||||
height: height,
|
||||
child: Center(
|
||||
child: Text(
|
||||
errorInfo.value!,
|
||||
style: TextStyle(color: Colors.red),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return cachedFile.value != null
|
||||
? SvgPicture.file(
|
||||
cachedFile.value!,
|
||||
width: width,
|
||||
height: height,
|
||||
fit: fit ?? BoxFit.contain,
|
||||
)
|
||||
: SizedBox(
|
||||
width: width,
|
||||
height: height,
|
||||
child: Center(
|
||||
child: ProgressRing(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
71
lib/widgets/src/grid_item_animator.dart
Normal file
71
lib/widgets/src/grid_item_animator.dart
Normal file
@ -0,0 +1,71 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
|
||||
class GridItemAnimator extends HookWidget {
|
||||
final Widget child; // 要添加动画效果的子组件
|
||||
final int index; // 条目在网格中的索引位置
|
||||
final Duration duration; // 动画持续时间
|
||||
final Duration delayPerItem; // 每个条目之间的延迟时间
|
||||
final double slideOffset; // 上移的偏移量(像素)
|
||||
|
||||
const GridItemAnimator({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.index,
|
||||
this.duration = const Duration(milliseconds: 230),
|
||||
this.delayPerItem = const Duration(milliseconds: 50),
|
||||
this.slideOffset = 20.0,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// 创建动画控制器
|
||||
final animationController = useAnimationController(
|
||||
duration: duration,
|
||||
);
|
||||
|
||||
// 创建不透明度动画
|
||||
final opacityAnimation = useAnimation(
|
||||
Tween<double>(
|
||||
begin: 0.0, // 开始时完全透明
|
||||
end: 1.0, // 结束时完全不透明
|
||||
).animate(CurvedAnimation(
|
||||
parent: animationController,
|
||||
curve: Curves.easeOut,
|
||||
)),
|
||||
);
|
||||
|
||||
// 创建位移动画
|
||||
final slideAnimation = useAnimation(
|
||||
Tween<double>(
|
||||
begin: 1.0, // 开始位置
|
||||
end: 0.0, // 结束位置
|
||||
).animate(CurvedAnimation(
|
||||
parent: animationController,
|
||||
curve: Curves.easeOutCubic,
|
||||
)),
|
||||
);
|
||||
|
||||
// 组件挂载后启动动画
|
||||
useEffect(() {
|
||||
// 根据索引计算延迟时间,实现逐个条目入场
|
||||
final delay = delayPerItem * index;
|
||||
|
||||
Future.delayed(delay, () {
|
||||
if (animationController.status != AnimationStatus.completed) {
|
||||
animationController.forward();
|
||||
}
|
||||
});
|
||||
return null;
|
||||
}, const []);
|
||||
|
||||
// 应用动画效果
|
||||
return Opacity(
|
||||
opacity: opacityAnimation,
|
||||
child: Transform.translate(
|
||||
offset: Offset(0, slideOffset * slideAnimation), // 向上平移动画
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -15,6 +15,9 @@ import 'dart:ui' as ui;
|
||||
|
||||
export 'src/cache_image.dart';
|
||||
export 'src/countdown_time_text.dart';
|
||||
export 'src/cache_svg_image.dart';
|
||||
export 'src/grid_item_animator.dart';
|
||||
|
||||
export '../common/utils/async.dart';
|
||||
export '../common/utils/base_utils.dart';
|
||||
export 'package:starcitizen_doctor/generated/l10n.dart';
|
||||
@ -137,14 +140,13 @@ ColorFilter makeSvgColor(Color color) {
|
||||
return ui.ColorFilter.mode(color, ui.BlendMode.srcIn);
|
||||
}
|
||||
|
||||
CustomTransitionPage<T> myPageBuilder<T>(
|
||||
BuildContext context, GoRouterState state, Widget child) {
|
||||
CustomTransitionPage<T> myPageBuilder<T>(BuildContext context, GoRouterState state, Widget child) {
|
||||
return CustomTransitionPage(
|
||||
child: child,
|
||||
transitionDuration: const Duration(milliseconds: 150),
|
||||
reverseTransitionDuration: const Duration(milliseconds: 150),
|
||||
transitionsBuilder: (BuildContext context, Animation<double> animation,
|
||||
Animation<double> secondaryAnimation, Widget child) {
|
||||
transitionsBuilder:
|
||||
(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
|
||||
return SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(0.0, 1.0),
|
||||
@ -164,12 +166,7 @@ class LoadingWidget<T> extends HookConsumerWidget {
|
||||
final Widget Function(BuildContext context, T data) childBuilder;
|
||||
final Duration? autoRefreshDuration;
|
||||
|
||||
const LoadingWidget(
|
||||
{super.key,
|
||||
this.data,
|
||||
required this.childBuilder,
|
||||
this.onLoadData,
|
||||
this.autoRefreshDuration});
|
||||
const LoadingWidget({super.key, this.data, required this.childBuilder, this.onLoadData, this.autoRefreshDuration});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@ -204,8 +201,7 @@ class LoadingWidget<T> extends HookConsumerWidget {
|
||||
return childBuilder(context, (data ?? dataState.value) as T);
|
||||
}
|
||||
|
||||
void _loadData(
|
||||
ValueNotifier<T?> dataState, ValueNotifier<String> errorMsg) async {
|
||||
void _loadData(ValueNotifier<T?> dataState, ValueNotifier<String> errorMsg) async {
|
||||
errorMsg.value = "";
|
||||
try {
|
||||
final r = await onLoadData!();
|
||||
|
@ -232,7 +232,7 @@ packages:
|
||||
source: hosted
|
||||
version: "0.3.4+2"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: crypto
|
||||
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
|
||||
@ -916,7 +916,7 @@ packages:
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
|
@ -68,6 +68,8 @@ dependencies:
|
||||
qr_flutter: ^4.1.0
|
||||
desktop_multi_window: ^0.2.1
|
||||
watcher: ^1.1.1
|
||||
path: ^1.9.1
|
||||
crypto: ^3.0.6
|
||||
dependency_overrides:
|
||||
http: ^1.1.2
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user