feat: 42kit Nav

feat: Animation Optimization
This commit is contained in:
xkeyC 2025-05-04 14:07:56 +08:00
parent a2de310d84
commit 03c941c970
23 changed files with 5618 additions and 493 deletions

View File

@ -29,6 +29,10 @@ linter:
analyzer: analyzer:
plugins: plugins:
- custom_lint - custom_lint
exclude:
- "**/*.g.dart"
- "**/*.freezed.dart"
errors:
invalid_annotation_target: ignore
# Additional information about this file can be found at # Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options # https://dart.dev/guides/language/analysis-options

14
lib/api/udb.dart Normal file
View 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;
}
}

View File

@ -22,7 +22,7 @@ final routerProvider = AutoDisposeProvider<GoRouter>.internal(
@Deprecated('Will be removed in 3.0. Use Ref instead') @Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element // ignore: unused_element
typedef RouterRef = AutoDisposeProviderRef<GoRouter>; typedef RouterRef = AutoDisposeProviderRef<GoRouter>;
String _$appGlobalModelHash() => r'8aa468bda409c425a76e3ef9e7739ca4ed055d2b'; String _$appGlobalModelHash() => r'eb06413ab3a70f26712d897cee745ee62e89e75e';
/// See also [AppGlobalModel]. /// See also [AppGlobalModel].
@ProviderFor(AppGlobalModel) @ProviderFor(AppGlobalModel)

View 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
View 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);
}

File diff suppressed because it is too large Load Diff

View 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,
};

View File

@ -30,8 +30,7 @@ class AboutUI extends HookConsumerWidget {
); );
} }
Widget _makeAbout(BuildContext context, WidgetRef ref, Widget _makeAbout(BuildContext context, WidgetRef ref, ValueNotifier<bool> isTipTextCn, PageController pageCtrl) {
ValueNotifier<bool> isTipTextCn, PageController pageCtrl) {
return Stack( return Stack(
children: [ children: [
Center( Center(
@ -42,9 +41,7 @@ class AboutUI extends HookConsumerWidget {
const SizedBox(height: 32), const SizedBox(height: 32),
Image.asset("assets/app_logo.png", width: 128, height: 128), Image.asset("assets/app_logo.png", width: 128, height: 128),
const SizedBox(height: 6), const SizedBox(height: 6),
Text( Text(S.current.app_index_version_info(ConstConf.appVersion, ConstConf.isMSE ? "" : " Dev"),
S.current.app_index_version_info(
ConstConf.appVersion, ConstConf.isMSE ? "" : " Dev"),
style: const TextStyle(fontSize: 18)), style: const TextStyle(fontSize: 18)),
const SizedBox(height: 12), const SizedBox(height: 12),
Button( Button(
@ -56,18 +53,15 @@ class AboutUI extends HookConsumerWidget {
const SizedBox(height: 32), const SizedBox(height: 32),
Container( Container(
margin: const EdgeInsets.all(24), margin: const EdgeInsets.all(24),
decoration: BoxDecoration( decoration:
color: FluentTheme.of(context).cardColor, BoxDecoration(color: FluentTheme.of(context).cardColor, borderRadius: BorderRadius.circular(12)),
borderRadius: BorderRadius.circular(12)),
child: Padding( child: Padding(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(24),
child: Column( child: Column(
children: [ children: [
Text( Text(
S.current.about_app_description, S.current.about_app_description,
style: TextStyle( style: TextStyle(fontSize: 14, color: Colors.white.withValues(alpha: .9)),
fontSize: 14,
color: Colors.white.withValues(alpha: .9)),
), ),
], ],
), ),
@ -86,9 +80,7 @@ class AboutUI extends HookConsumerWidget {
child: Container( child: Container(
width: MediaQuery.of(context).size.width * .35, width: MediaQuery.of(context).size.width * .35,
decoration: BoxDecoration( decoration: BoxDecoration(
color: FluentTheme.of(context) color: FluentTheme.of(context).cardColor.withValues(alpha: .06),
.cardColor
.withValues(alpha: .06),
borderRadius: BorderRadius.circular(12)), borderRadius: BorderRadius.circular(12)),
child: IconButton( child: IconButton(
icon: Padding( icon: Padding(
@ -96,9 +88,7 @@ class AboutUI extends HookConsumerWidget {
child: Text( child: Text(
isTipTextCn.value ? tipTextCN : tipTextEN, isTipTextCn.value ? tipTextCN : tipTextEN,
textAlign: TextAlign.start, textAlign: TextAlign.start,
style: TextStyle( style: TextStyle(fontSize: 12, color: Colors.white.withValues(alpha: .9)),
fontSize: 12,
color: Colors.white.withValues(alpha: .9)),
), ),
), ),
onPressed: () { onPressed: () {
@ -126,8 +116,7 @@ class AboutUI extends HookConsumerWidget {
); );
} }
Widget _makeDonate( Widget _makeDonate(BuildContext context, WidgetRef ref, PageController pageCtrl) {
BuildContext context, WidgetRef ref, PageController pageCtrl) {
final donationTypeNotifier = useState('alipay'); final donationTypeNotifier = useState('alipay');
final bubbleMessages = [ final bubbleMessages = [
S.current.support_dev_thanks_message, S.current.support_dev_thanks_message,
@ -171,8 +160,7 @@ class AboutUI extends HookConsumerWidget {
for (var i = 0; i < bubbleMessages.length; i++) for (var i = 0; i < bubbleMessages.length; i++)
Padding( Padding(
padding: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.only(bottom: 8),
child: SelectionArea( child: SelectionArea(child: ChatBubble(message: bubbleMessages[i])),
child: ChatBubble(message: bubbleMessages[i])),
), ),
], ],
), ),
@ -261,14 +249,10 @@ class AboutUI extends HookConsumerWidget {
padding: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric(horizontal: 8),
child: Button( child: Button(
style: ButtonStyle( style: ButtonStyle(
backgroundColor: WidgetStateProperty.resolveWith((states) => backgroundColor: WidgetStateProperty.resolveWith((states) => isSelected
isSelected ? ButtonThemeData.buttonColor(context, states).withAlpha((255.0 * 0.08).round())
? ButtonThemeData.buttonColor(context, states) : ButtonThemeData.buttonColor(context, states).withAlpha((255.0 * 0.005).round())),
.withAlpha((255.0 * 0.08).round()) padding: WidgetStateProperty.all(EdgeInsets.symmetric(horizontal: 16, vertical: 8)),
: ButtonThemeData.buttonColor(context, states)
.withAlpha((255.0 * 0.005).round())),
padding: WidgetStateProperty.all(
EdgeInsets.symmetric(horizontal: 16, vertical: 8)),
), ),
onPressed: onTap, onPressed: onTap,
child: Column( child: Column(
@ -328,16 +312,13 @@ class AboutUI extends HookConsumerWidget {
Container( Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: FluentTheme.of(context) color: FluentTheme.of(context).cardColor.withAlpha((255 * .1).round()),
.cardColor
.withAlpha((255 * .1).round()),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text(S.current.support_dev_in_game_id, Text(S.current.support_dev_in_game_id, style: TextStyle(fontSize: 16)),
style: TextStyle(fontSize: 16)),
const SizedBox(width: 12), const SizedBox(width: 12),
Button( Button(
onPressed: () { onPressed: () {
@ -386,8 +367,7 @@ class AboutUI extends HookConsumerWidget {
return Column( return Column(
key: ValueKey(type), key: ValueKey(type),
children: [ children: [
Text(title, Text(title, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16), const SizedBox(height: 16),
Container( Container(
width: 200, width: 200,
@ -413,19 +393,13 @@ class AboutUI extends HookConsumerWidget {
icon: Row( icon: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Icon( Icon(pageIndex == 0 ? FluentIcons.chevron_up : FluentIcons.chevron_down, size: 12),
pageIndex == 0
? FluentIcons.chevron_up
: FluentIcons.chevron_down,
size: 12),
SizedBox(width: 8), SizedBox(width: 8),
Text(pageIndex == 0 Text(pageIndex == 0 ? S.current.support_dev_back_button : S.current.support_dev_scroll_hint),
? S.current.support_dev_back_button
: S.current.support_dev_scroll_hint),
], ],
), ),
onPressed: () => pageCtrl.animateToPage(pageIndex, onPressed: () =>
duration: const Duration(milliseconds: 300), curve: Curves.ease), pageCtrl.animateToPage(pageIndex, duration: const Duration(milliseconds: 300), curve: Curves.ease),
); );
} }
@ -440,8 +414,7 @@ class AboutUI extends HookConsumerWidget {
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
S.current.about_action_btn_faq, S.current.about_action_btn_faq,
style: TextStyle( style: TextStyle(fontSize: 14, color: Colors.white.withValues(alpha: .6)),
fontSize: 14, color: Colors.white.withValues(alpha: .6)),
), ),
], ],
), ),
@ -457,8 +430,7 @@ class AboutUI extends HookConsumerWidget {
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
S.current.about_online_feedback, S.current.about_online_feedback,
style: TextStyle( style: TextStyle(fontSize: 14, color: Colors.white.withValues(alpha: .6)),
fontSize: 14, color: Colors.white.withValues(alpha: .6)),
), ),
], ],
), ),
@ -474,8 +446,7 @@ class AboutUI extends HookConsumerWidget {
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
S.current.about_action_qq_group, S.current.about_action_qq_group,
style: TextStyle( style: TextStyle(fontSize: 14, color: Colors.white.withValues(alpha: .6)),
fontSize: 14, color: Colors.white.withValues(alpha: .6)),
), ),
], ],
), ),
@ -492,8 +463,7 @@ class AboutUI extends HookConsumerWidget {
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
S.current.about_action_email, S.current.about_action_email,
style: TextStyle( style: TextStyle(fontSize: 14, color: Colors.white.withValues(alpha: .6)),
fontSize: 14, color: Colors.white.withValues(alpha: .6)),
), ),
], ],
), ),
@ -509,8 +479,7 @@ class AboutUI extends HookConsumerWidget {
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
S.current.about_action_open_source, S.current.about_action_open_source,
style: TextStyle( style: TextStyle(fontSize: 14, color: Colors.white.withValues(alpha: .6)),
fontSize: 14, color: Colors.white.withValues(alpha: .6)),
), ),
], ],
), ),
@ -528,6 +497,7 @@ class AboutUI extends HookConsumerWidget {
static String get tipTextCN => S.current.about_disclaimer; static String get tipTextCN => S.current.about_disclaimer;
Widget makeAnalyticsWidget(BuildContext context) { Widget makeAnalyticsWidget(BuildContext context) {
var buildIndex = 0;
return LoadingWidget( return LoadingWidget(
onLoadData: AnalyticsApi.getAnalyticsData, onLoadData: AnalyticsApi.getAnalyticsData,
autoRefreshDuration: const Duration(seconds: 60), autoRefreshDuration: const Duration(seconds: 60),
@ -547,20 +517,18 @@ class AboutUI extends HookConsumerWidget {
"performance_apply", "performance_apply",
"p4k_download", "p4k_download",
].contains(item["Type"])) ].contains(item["Type"]))
makeAnalyticsItem( GridItemAnimator(
context: context, index: buildIndex++,
name: item["Type"] as String, child: makeAnalyticsItem(
value: item["Count"] as int) context: context, name: item["Type"] as String, value: item["Count"] as int),
)
], ],
); );
}, },
); );
} }
Widget makeAnalyticsItem( Widget makeAnalyticsItem({required BuildContext context, required String name, required int value}) {
{required BuildContext context,
required String name,
required int value}) {
final names = { final names = {
"launch": S.current.about_analytics_launch, "launch": S.current.about_analytics_launch,
"gameLaunch": S.current.about_analytics_launch_game, "gameLaunch": S.current.about_analytics_launch_game,
@ -573,14 +541,12 @@ class AboutUI extends HookConsumerWidget {
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
margin: const EdgeInsets.only(left: 18, right: 18), margin: const EdgeInsets.only(left: 18, right: 18),
decoration: BoxDecoration( decoration: BoxDecoration(
color: FluentTheme.of(context).cardColor.withValues(alpha: .06), color: FluentTheme.of(context).cardColor.withValues(alpha: .06), borderRadius: BorderRadius.circular(12)),
borderRadius: BorderRadius.circular(12)),
child: Column( child: Column(
children: [ children: [
Text( Text(
names[name] ?? name, names[name] ?? name,
style: TextStyle( style: TextStyle(fontSize: 13, color: Colors.white.withValues(alpha: .6)),
fontSize: 13, color: Colors.white.withValues(alpha: .6)),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Row( Row(
@ -606,8 +572,7 @@ class AboutUI extends HookConsumerWidget {
launchUrlString("ms-windows-store://pdp/?productid=9NF3SWFWNKL1"); launchUrlString("ms-windows-store://pdp/?productid=9NF3SWFWNKL1");
return; return;
} else { } else {
final hasUpdate = final hasUpdate = await ref.read(appGlobalModelProvider.notifier).checkUpdate(context);
await ref.read(appGlobalModelProvider.notifier).checkUpdate(context);
if (!hasUpdate) { if (!hasUpdate) {
if (!context.mounted) return; if (!context.mounted) return;
showToast(context, S.current.about_info_latest_version); showToast(context, S.current.about_info_latest_version);
@ -626,8 +591,7 @@ class ChatBubble extends StatelessWidget {
return Container( return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
decoration: BoxDecoration( decoration: BoxDecoration(
color: color: FluentTheme.of(context).accentColor.withAlpha((255.0 * .2).round()),
FluentTheme.of(context).accentColor.withAlpha((255.0 * .2).round()),
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
topLeft: Radius.circular(4), topLeft: Radius.circular(4),
topRight: Radius.circular(18), topRight: Radius.circular(18),
@ -645,8 +609,7 @@ class ChatBubble extends StatelessWidget {
class DonationQrCodeData { class DonationQrCodeData {
static const alipay = "https://qr.alipay.com/tsx16308c4uai0ticmz4j96"; static const alipay = "https://qr.alipay.com/tsx16308c4uai0ticmz4j96";
static const wechat = static const wechat = "wxp://f2f0J40rTCX7Vt79yooWNbiqH3U6UmwGJkqjcAYnrv9OZVzKyS5_W6trp8mo3KP-CTQ5";
"wxp://f2f0J40rTCX7Vt79yooWNbiqH3U6UmwGJkqjcAYnrv9OZVzKyS5_W6trp8mo3KP-CTQ5";
static const qq = 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"; "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";
} }

View File

@ -28,8 +28,7 @@ class AdvancedLocalizationUI extends HookConsumerWidget {
onSwitchFile() async { onSwitchFile() async {
final sb = await showDialog( final sb = await showDialog(
context: context, context: context,
builder: (BuildContext context) => builder: (BuildContext context) => const LocalizationFromFileDialogUI(isInAdvancedMode: true),
const LocalizationFromFileDialogUI(isInAdvancedMode: true),
); );
if (sb is (StringBuffer, bool)) { if (sb is (StringBuffer, bool)) {
model.setCustomizeGlobalIni(sb.$1.toString()); model.setCustomizeGlobalIni(sb.$1.toString());
@ -42,8 +41,7 @@ class AdvancedLocalizationUI extends HookConsumerWidget {
}, const []); }, const []);
return makeDefaultPage( return makeDefaultPage(
title: S.current.home_localization_advanced_title( title: S.current.home_localization_advanced_title(homeUIState.scInstalledPath ?? "-"),
homeUIState.scInstalledPath ?? "-"),
context, context,
content: state.workingText.isNotEmpty content: state.workingText.isNotEmpty
? Center( ? Center(
@ -71,15 +69,13 @@ class AdvancedLocalizationUI extends HookConsumerWidget {
children: [ children: [
Text( Text(
S.current.home_localization_advanced_msg_version( S.current.home_localization_advanced_msg_version(
state.apiLocalizationData?.versionName ?? state.apiLocalizationData?.versionName ?? "-"),
"-"),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
Button( Button(
onPressed: onSwitchFile, onPressed: onSwitchFile,
child: const Padding( child: const Padding(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(horizontal: 6, vertical: 3),
horizontal: 6, vertical: 3),
child: Icon(FluentIcons.switch_widget), child: Icon(FluentIcons.switch_widget),
)), )),
if (state.customizeGlobalIni != null) ...[ if (state.customizeGlobalIni != null) ...[
@ -89,23 +85,19 @@ class AdvancedLocalizationUI extends HookConsumerWidget {
model.setCustomizeGlobalIni(null); model.setCustomizeGlobalIni(null);
}, },
child: const Padding( child: const Padding(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(horizontal: 6, vertical: 3),
horizontal: 6, vertical: 3),
child: Icon(FluentIcons.delete), child: Icon(FluentIcons.delete),
)), )),
] ]
], ],
)), )),
Text(S.current.home_localization_advanced_title_msg( Text(S.current
state.serverGlobalIniLines, .home_localization_advanced_title_msg(state.serverGlobalIniLines, state.p4kGlobalIniLines)),
state.p4kGlobalIniLines)),
const SizedBox(width: 32), const SizedBox(width: 32),
Button( Button(
child: Padding( child: Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(left: 12, right: 12, top: 4, bottom: 4),
left: 12, right: 12, top: 4, bottom: 4), child: Text(S.current.home_localization_advanced_action_install),
child: Text(S.current
.home_localization_advanced_action_install),
), ),
onPressed: () { onPressed: () {
model.onInstall(context); model.onInstall(context);
@ -113,19 +105,13 @@ class AdvancedLocalizationUI extends HookConsumerWidget {
const SizedBox(width: 12), const SizedBox(width: 12),
], ],
), ),
Expanded( Expanded(child: _makeBody(context, homeUIState, state, ref, model)),
child:
_makeBody(context, homeUIState, state, ref, model)),
] ]
], ],
)); ));
} }
Widget _makeBody( Widget _makeBody(BuildContext context, HomeUIModelState homeUIState, AdvancedLocalizationUIState state, WidgetRef ref,
BuildContext context,
HomeUIModelState homeUIState,
AdvancedLocalizationUIState state,
WidgetRef ref,
AdvancedLocalizationUIModel model) { AdvancedLocalizationUIModel model) {
return AlignedGridView.count( return AlignedGridView.count(
crossAxisCount: 4, crossAxisCount: 4,
@ -134,109 +120,104 @@ class AdvancedLocalizationUI extends HookConsumerWidget {
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
final item = state.classMap!.values.elementAt(index); final item = state.classMap!.values.elementAt(index);
return Container( return GridItemAnimator(
padding: const EdgeInsets.only(top: 6, bottom: 12), index: index,
decoration: BoxDecoration( child: Container(
color: Colors.white.withValues(alpha: .05), padding: const EdgeInsets.only(top: 6, bottom: 12),
borderRadius: BorderRadius.circular(4), decoration: BoxDecoration(
), color: Colors.white.withValues(alpha: .05),
child: Column( borderRadius: BorderRadius.circular(4),
crossAxisAlignment: CrossAxisAlignment.start, ),
children: [ child: Column(
IconButton( crossAxisAlignment: CrossAxisAlignment.start,
onPressed: children: [
item.isWorking ? null : () => _showContent(context, item), IconButton(
icon: Padding( onPressed: item.isWorking ? null : () => _showContent(context, item),
padding: const EdgeInsets.only(left: 12, right: 12), icon: Padding(
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), padding: const EdgeInsets.only(left: 12, right: 12),
itemBuilder: (BuildContext context, int index) { child: Row(
final itemKey = item.valuesMap.keys.elementAt(index); children: [
return Text( Expanded(
"${item.valuesMap[itemKey]}", child: Text(
maxLines: 1, "${item.className}",
style: const TextStyle( style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
fontSize: 12, textAlign: TextAlign.start,
overflow: TextOverflow.ellipsis, )),
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( _showContent(BuildContext context, AppAdvancedLocalizationClassKeysData item) {
BuildContext context, AppAdvancedLocalizationClassKeysData item) {
showDialog( showDialog(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
@ -282,8 +262,7 @@ class AdvancedLocalizationUI extends HookConsumerWidget {
const SizedBox( const SizedBox(
width: 24, width: 24,
), ),
Text(S.current.home_localization_advanced_title_preview( Text(S.current.home_localization_advanced_title_preview(item.className ?? "-")),
item.className ?? "-")),
], ],
), ),
content: textData.value.isEmpty content: textData.value.isEmpty
@ -295,13 +274,10 @@ class AdvancedLocalizationUI extends HookConsumerWidget {
), ),
child: CodeEditor( child: CodeEditor(
readOnly: true, readOnly: true,
controller: controller: CodeLineEditingController.fromText(textData.value),
CodeLineEditingController.fromText(textData.value),
style: CodeEditorStyle( style: CodeEditorStyle(
codeTheme: CodeHighlightTheme( codeTheme: CodeHighlightTheme(
languages: { languages: {'ini': CodeHighlightThemeMode(mode: langIni)},
'ini': CodeHighlightThemeMode(mode: langIni)
},
theme: vs2015Theme, theme: vs2015Theme,
), ),
), ),

View File

@ -30,8 +30,7 @@ class LocalizationDialogUI extends HookConsumerWidget {
return ContentDialog( return ContentDialog(
title: makeTitle(context, model, state), title: makeTitle(context, model, state),
constraints: BoxConstraints( constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * .7, maxWidth: MediaQuery.of(context).size.width * .7, minHeight: MediaQuery.of(context).size.height * .9),
minHeight: MediaQuery.of(context).size.height * .9),
content: Padding( content: Padding(
padding: const EdgeInsets.only(left: 12, right: 12, top: 12), padding: const EdgeInsets.only(left: 12, right: 12, top: 12),
child: SingleChildScrollView( child: SingleChildScrollView(
@ -40,18 +39,15 @@ class LocalizationDialogUI extends HookConsumerWidget {
AnimatedSize( AnimatedSize(
duration: const Duration(milliseconds: 130), duration: const Duration(milliseconds: 130),
child: state.patchStatus?.key == true && child: state.patchStatus?.key == true &&
state.patchStatus?.value == state.patchStatus?.value == S.current.home_action_info_game_built_in
S.current.home_action_info_game_built_in
? Padding( ? Padding(
padding: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.only(bottom: 12),
child: InfoBar( child: InfoBar(
title: Text(S.current.home_action_info_warning), title: Text(S.current.home_action_info_warning),
content: Text(S.current content: Text(S.current.localization_info_machine_translation_warning),
.localization_info_machine_translation_warning),
severity: InfoBarSeverity.info, severity: InfoBarSeverity.info,
style: InfoBarThemeData(decoration: (severity) { style: InfoBarThemeData(decoration: (severity) {
return const BoxDecoration( return const BoxDecoration(color: Color.fromRGBO(155, 7, 7, 1.0));
color: Color.fromRGBO(155, 7, 7, 1.0));
}, iconColor: (severity) { }, iconColor: (severity) {
return Colors.white; return Colors.white;
}), }),
@ -65,10 +61,8 @@ class LocalizationDialogUI extends HookConsumerWidget {
Padding( Padding(
padding: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.only(bottom: 12),
child: InfoBar( child: InfoBar(
title: Text(S.current title: Text(S.current.home_localization_ptu_advanced_localization_tip_title),
.home_localization_ptu_advanced_localization_tip_title), content: Text(S.current.home_localization_ptu_advanced_localization_tip_title_info),
content: Text(S.current
.home_localization_ptu_advanced_localization_tip_title_info),
severity: InfoBarSeverity.info, severity: InfoBarSeverity.info,
style: InfoBarThemeData(decoration: (severity) { style: InfoBarThemeData(decoration: (severity) {
return BoxDecoration(color: Colors.orange); return BoxDecoration(color: Colors.orange);
@ -89,9 +83,7 @@ class LocalizationDialogUI extends HookConsumerWidget {
children: [ children: [
Center( Center(
child: Text(S.current.localization_info_enabled( child: Text(S.current.localization_info_enabled(
LocalizationUIModel.languageSupport[ LocalizationUIModel.languageSupport[state.selectedLanguage] ?? "")),
state.selectedLanguage] ??
"")),
), ),
const Spacer(), const Spacer(),
ToggleSwitch( ToggleSwitch(
@ -109,20 +101,15 @@ class LocalizationDialogUI extends HookConsumerWidget {
Text(S.current.localization_info_installed_version( Text(S.current.localization_info_installed_version(
"${state.patchStatus?.value ?? ""} ${(state.isInstalledAdvanced ?? false) ? S.current.home_localization_msg_version_advanced : ""}")), "${state.patchStatus?.value ?? ""} ${(state.isInstalledAdvanced ?? false) ? S.current.home_localization_msg_version_advanced : ""}")),
SizedBox(width: 24), SizedBox(width: 24),
if (state if (state.installedCommunityInputMethodSupportVersion != null)
.installedCommunityInputMethodSupportVersion !=
null)
Text( Text(
S.current S.current.input_method_community_input_method_support_version(
.input_method_community_input_method_support_version( state.installedCommunityInputMethodSupportVersion ?? "?"),
state.installedCommunityInputMethodSupportVersion ??
"?"),
) )
], ],
), ),
), ),
if (state.patchStatus?.value != if (state.patchStatus?.value != S.current.home_action_info_game_built_in)
S.current.home_action_info_game_built_in)
Row( Row(
children: [ children: [
Button( Button(
@ -133,8 +120,7 @@ class LocalizationDialogUI extends HookConsumerWidget {
children: [ children: [
const Icon(FluentIcons.feedback), const Icon(FluentIcons.feedback),
const SizedBox(width: 6), const SizedBox(width: 6),
Text(S.current Text(S.current.localization_action_translation_feedback),
.localization_action_translation_feedback),
], ],
), ),
)), )),
@ -147,8 +133,7 @@ class LocalizationDialogUI extends HookConsumerWidget {
children: [ children: [
const Icon(FluentIcons.delete), const Icon(FluentIcons.delete),
const SizedBox(width: 6), const SizedBox(width: 6),
Text(S.current Text(S.current.localization_action_uninstall_translation),
.localization_action_uninstall_translation),
], ],
), ),
)), )),
@ -167,11 +152,8 @@ class LocalizationDialogUI extends HookConsumerWidget {
else if (state.apiLocalizationData!.isEmpty) else if (state.apiLocalizationData!.isEmpty)
Center( Center(
child: Text( child: Text(
S.current S.current.localization_info_no_translation_available,
.localization_info_no_translation_available, style: TextStyle(fontSize: 13, color: Colors.white.withValues(alpha: .8)),
style: TextStyle(
fontSize: 13,
color: Colors.white.withValues(alpha: .8)),
), ),
) )
else else
@ -180,9 +162,8 @@ class LocalizationDialogUI extends HookConsumerWidget {
crossAxisSpacing: 12, crossAxisSpacing: 12,
mainAxisSpacing: 12, mainAxisSpacing: 12,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
final item = state.apiLocalizationData!.entries final item = state.apiLocalizationData!.entries.elementAt(index);
.elementAt(index); return makeRemoteList(context, model, item, state, index);
return makeRemoteList(context, model, item, state);
}, },
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
@ -198,136 +179,124 @@ class LocalizationDialogUI extends HookConsumerWidget {
); );
} }
Widget makeRemoteList(BuildContext context, LocalizationUIModel model, Widget makeRemoteList(BuildContext context, LocalizationUIModel model, MapEntry<String, ScLocalizationData> item,
MapEntry<String, ScLocalizationData> item, LocalizationUIState state) { LocalizationUIState state, int index) {
final isWorking = state.workingVersion.isNotEmpty; final isWorking = state.workingVersion.isNotEmpty;
final isMineWorking = state.workingVersion == item.key; final isMineWorking = state.workingVersion == item.key;
final isInstalled = state.patchStatus?.value == item.key; final isInstalled = state.patchStatus?.value == item.key;
final isItemEnabled = ((item.value.enable ?? false)); final isItemEnabled = ((item.value.enable ?? false));
final tapDisabled = final tapDisabled = isInstalled || isWorking || !isItemEnabled || isMineWorking;
isInstalled || isWorking || !isItemEnabled || isMineWorking; return GridItemAnimator(
return Tilt( index: index,
shadowConfig: const ShadowConfig(maxIntensity: .3), child: Tilt(
borderRadius: BorderRadius.circular(7), shadowConfig: const ShadowConfig(maxIntensity: .3),
disable: tapDisabled, borderRadius: BorderRadius.circular(7),
child: GestureDetector( disable: tapDisabled,
onTap: tapDisabled child: GestureDetector(
? null onTap: tapDisabled ? null : () => model.onRemoteInsTall(context, item, state),
: () => model.onRemoteInsTall(context, item, state), child: Container(
child: Container( padding: const EdgeInsets.all(12),
padding: const EdgeInsets.all(12), decoration: BoxDecoration(
decoration: BoxDecoration( color: Colors.white.withValues(alpha: tapDisabled ? .03 : .05),
color: Colors.white.withValues(alpha: tapDisabled ? .03 : .05), borderRadius: BorderRadius.circular(7),
borderRadius: BorderRadius.circular(7), ),
), child: Column(
child: Column( crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, children: [
children: [ Row(
Row( children: [
children: [ Expanded(
Expanded( child: Column(
child: Column( crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, children: [
children: [ Text(
Text( "${item.value.info}",
"${item.value.info}", style: const TextStyle(fontSize: 19),
style: const TextStyle(fontSize: 19), ),
), const SizedBox(height: 4),
const SizedBox(height: 4), Text(
Text( S.current.localization_info_version_number(item.value.versionName ?? ""),
S.current.localization_info_version_number( style: TextStyle(color: Colors.white.withValues(alpha: .6)),
item.value.versionName ?? ""), ),
style: TextStyle( const SizedBox(height: 4),
color: Colors.white.withValues(alpha: .6)), Text(
), S.current.localization_info_channel(item.value.gameChannel ?? ""),
const SizedBox(height: 4), style: TextStyle(color: Colors.white.withValues(alpha: .6)),
Text( ),
S.current.localization_info_channel( const SizedBox(height: 4),
item.value.gameChannel ?? ""), Text(
style: TextStyle( S.current.localization_info_update_time(item.value.updateAt ?? ""),
color: Colors.white.withValues(alpha: .6)), 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),
), ),
), ),
const SizedBox(width: 6), if (isMineWorking)
if ((!isInstalled) && isItemEnabled) const Padding(
Icon( padding: EdgeInsets.only(right: 12),
FluentIcons.chevron_right, child: ProgressRing(),
size: 14,
color: Colors.white.withValues(alpha: .6),
) )
] else ...[
], Icon(
), isInstalled
if (item.value.note != null) ...[ ? FluentIcons.check_mark
const SizedBox(height: 6), : isItemEnabled
Text( ? FluentIcons.download
"${item.value.note}", : FluentIcons.disable_updates,
maxLines: 1, color: Colors.white.withValues(alpha: .8),
overflow: TextOverflow.ellipsis, size: 18,
style: TextStyle( ),
color: Colors.white.withValues(alpha: .4), const SizedBox(width: 6),
fontSize: 13, 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( Widget makeListContainer(String title, List<Widget> children, BuildContext context,
String title, List<Widget> children, BuildContext context, {List<Widget> actions = const [], bool gridViewMode = false, int gridViewCrossAxisCount = 2}) {
{List<Widget> actions = const [],
bool gridViewMode = false,
int gridViewCrossAxisCount = 2}) {
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.only(bottom: 12),
child: AnimatedSize( child: AnimatedSize(
duration: const Duration(milliseconds: 130), duration: const Duration(milliseconds: 130),
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(color: FluentTheme.of(context).cardColor, borderRadius: BorderRadius.circular(7)),
color: FluentTheme.of(context).cardColor,
borderRadius: BorderRadius.circular(7)),
child: Padding( child: Padding(
padding: padding: const EdgeInsets.only(top: 12, bottom: 12, left: 24, right: 24),
const EdgeInsets.only(top: 12, bottom: 12, left: 24, right: 24),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -371,8 +340,7 @@ class LocalizationDialogUI extends HookConsumerWidget {
); );
} }
Widget makeTitle(BuildContext context, LocalizationUIModel model, Widget makeTitle(BuildContext context, LocalizationUIModel model, LocalizationUIState state) {
LocalizationUIState state) {
return Row( return Row(
children: [ children: [
IconButton( IconButton(
@ -401,8 +369,7 @@ class LocalizationDialogUI extends HookConsumerWidget {
ComboBox<String>( ComboBox<String>(
value: state.selectedLanguage, value: state.selectedLanguage,
items: [ items: [
for (final lang for (final lang in LocalizationUIModel.languageSupport.entries)
in LocalizationUIModel.languageSupport.entries)
ComboBoxItem( ComboBoxItem(
value: lang.key, value: lang.key,
child: Text(lang.value), child: Text(lang.value),
@ -429,8 +396,7 @@ class LocalizationDialogUI extends HookConsumerWidget {
); );
} }
Widget makeToolsListContainer(BuildContext context, LocalizationUIModel model, Widget makeToolsListContainer(BuildContext context, LocalizationUIModel model, LocalizationUIState state) {
LocalizationUIState state) {
final toolsMenu = { final toolsMenu = {
"launcher_mod": ( "launcher_mod": (
const Icon(FluentIcons.c_plus_plus, size: 24), const Icon(FluentIcons.c_plus_plus, size: 24),
@ -469,8 +435,7 @@ class LocalizationDialogUI extends HookConsumerWidget {
case "custom_files": case "custom_files":
final sb = await showDialog( final sb = await showDialog(
context: context, context: context,
builder: (BuildContext context) => builder: (BuildContext context) => const LocalizationFromFileDialogUI(),
const LocalizationFromFileDialogUI(),
); );
if (sb is (StringBuffer, bool)) { if (sb is (StringBuffer, bool)) {
await model.installFormString( await model.installFormString(

View File

@ -12,6 +12,7 @@ import 'package:window_manager/window_manager.dart';
import 'about/about_ui.dart'; import 'about/about_ui.dart';
import 'home/home_ui.dart'; import 'home/home_ui.dart';
import 'nav/nav_ui.dart';
import 'settings/settings_ui.dart'; import 'settings/settings_ui.dart';
import 'tools/tools_ui.dart'; import 'tools/tools_ui.dart';
@ -42,8 +43,7 @@ class IndexUI extends HookConsumerWidget {
fit: BoxFit.cover, fit: BoxFit.cover,
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
Text(S.current.app_index_version_info( Text(S.current.app_index_version_info(ConstConf.appVersion, ConstConf.isMSE ? "" : " Dev")),
ConstConf.appVersion, ConstConf.isMSE ? "" : " Dev")),
], ],
), ),
), ),
@ -77,8 +77,7 @@ class IndexUI extends HookConsumerWidget {
key: Key("NavigationPane_${S.current.app_language_code}"), key: Key("NavigationPane_${S.current.app_language_code}"),
selected: curIndex.value, selected: curIndex.value,
items: getNavigationPaneItems(curIndex), items: getNavigationPaneItems(curIndex),
size: NavigationPaneSize( size: NavigationPaneSize(openWidth: S.current.app_language_code.startsWith("zh") ? 64 : 74),
openWidth: S.current.app_language_code.startsWith("zh") ? 64 : 74),
), ),
paneBodyBuilder: (item, child) { paneBodyBuilder: (item, child) {
return item!.body; return item!.body;
@ -95,18 +94,15 @@ class IndexUI extends HookConsumerWidget {
S.current.app_index_menu_tools, S.current.app_index_menu_tools,
const ToolsUI(), const ToolsUI(),
), ),
FluentIcons.settings: ( FluentIcons.power_apps: ("导航", const NavUI()),
S.current.app_index_menu_settings, FluentIcons.settings: (S.current.app_index_menu_settings, const SettingsUI()),
const SettingsUI()
),
FluentIcons.info: ( FluentIcons.info: (
S.current.app_index_menu_about, S.current.app_index_menu_about,
const AboutUI(), const AboutUI(),
), ),
}; };
List<NavigationPaneItem> getNavigationPaneItems( List<NavigationPaneItem> getNavigationPaneItems(ValueNotifier<int> curIndexState) {
ValueNotifier<int> curIndexState) {
// width = 64 // width = 64
return [ return [
for (final kv in pageMenus.entries) for (final kv in pageMenus.entries)
@ -136,8 +132,7 @@ class IndexUI extends HookConsumerWidget {
} }
void _onTapIndexMenu(String value, ValueNotifier<int> curIndexState) { void _onTapIndexMenu(String value, ValueNotifier<int> curIndexState) {
final pageIndex = final pageIndex = pageMenus.values.toList().indexWhere((element) => element.$1 == value);
pageMenus.values.toList().indexWhere((element) => element.$1 == value);
curIndexState.value = pageIndex; curIndexState.value = pageIndex;
} }
@ -156,8 +151,7 @@ class IndexUI extends HookConsumerWidget {
color: Colors.red, color: Colors.red,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
padding: const EdgeInsets.only( padding: const EdgeInsets.only(left: 6, right: 6, bottom: 1.5, top: 1.5),
left: 6, right: 6, bottom: 1.5, top: 1.5),
child: Text( child: Text(
"${aria2cState.aria2TotalTaskNum}", "${aria2cState.aria2TotalTaskNum}",
style: const TextStyle( style: const TextStyle(

41
lib/ui/nav/nav_state.dart Normal file
View 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());
}
}
}

View 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;
}

View 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
View 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,
),
),
)
],
)
],
),
),
],
),
),
),
),
),
);
},
);
}
}

View File

@ -40,13 +40,9 @@ class ToolsUI extends HookConsumerWidget {
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
Button( Button(
onPressed: state.working onPressed: state.working ? null : () => model.loadToolsCard(context, skipPathScan: false),
? null
: () =>
model.loadToolsCard(context, skipPathScan: false),
child: const Padding( child: const Padding(
padding: EdgeInsets.only( padding: EdgeInsets.only(top: 30, bottom: 30, left: 12, right: 12),
top: 30, bottom: 30, left: 12, right: 12),
child: Icon(FluentIcons.refresh), child: Icon(FluentIcons.refresh),
), ),
), ),
@ -75,90 +71,86 @@ class ToolsUI extends HookConsumerWidget {
crossAxisCount: 3, crossAxisCount: 3,
mainAxisSpacing: 12, mainAxisSpacing: 12,
crossAxisSpacing: 12, crossAxisSpacing: 12,
itemCount: (state.isItemLoading) itemCount: (state.isItemLoading) ? state.items.length + 1 : state.items.length,
? state.items.length + 1
: state.items.length,
shrinkWrap: true, shrinkWrap: true,
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (index == state.items.length) { if (index == state.items.length) {
return Container( return GridItemAnimator(
width: 300, index: index,
height: 200, child: Container(
decoration: BoxDecoration( width: 300,
borderRadius: BorderRadius.circular(12), height: 160,
color: FluentTheme.of(context).cardColor, decoration: BoxDecoration(
), borderRadius: BorderRadius.circular(12),
child: makeLoading(context)); color: FluentTheme.of(context).cardColor,
),
child: makeLoading(context)),
);
} }
final item = state.items[index]; final item = state.items[index];
return Container( return GridItemAnimator(
width: 300, index: index,
decoration: BoxDecoration( child: Container(
borderRadius: BorderRadius.circular(12), width: 300,
color: FluentTheme.of(context).cardColor, decoration: BoxDecoration(
), borderRadius: BorderRadius.circular(12),
child: Padding( color: FluentTheme.of(context).cardColor,
padding: const EdgeInsets.all(12), ),
child: Column( child: Padding(
crossAxisAlignment: CrossAxisAlignment.start, padding: const EdgeInsets.all(12),
children: [ child: Column(
Row( crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Container( Row(
decoration: BoxDecoration( children: [
color: Container(
Colors.white.withValues(alpha: .2), decoration: BoxDecoration(
borderRadius: color: Colors.white.withValues(alpha: .2),
BorderRadius.circular(1000)), borderRadius: BorderRadius.circular(1000)),
child: Padding( child: Padding(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
child: item.icon, child: item.icon,
),
), ),
), const SizedBox(width: 8),
const SizedBox(width: 8), Expanded(
Expanded( child: Text(
child: Text( item.name,
item.name, style: const TextStyle(fontSize: 16),
style: const TextStyle(fontSize: 16), )),
)), const SizedBox(width: 12),
const SizedBox(width: 12), ],
], ),
), const SizedBox(height: 12),
const SizedBox(height: 12), Text(
Text( item.infoString,
item.infoString, style: TextStyle(fontSize: 14, color: Colors.white.withValues(alpha: .6)),
style: TextStyle( ),
fontSize: 14, const SizedBox(height: 12),
color: Colors.white.withValues(alpha: .6)), Row(
), children: [
const SizedBox(height: 12), const Spacer(),
Row( Button(
children: [ onPressed: state.working
const Spacer(), ? null
Button( : item.onTap == null
onPressed: state.working ? null
? null : () {
: item.onTap == null try {
? null item.onTap?.call();
: () { } catch (e) {
try { showToast(context, S.current.tools_info_processing_failed(e));
item.onTap?.call(); }
} catch (e) { },
showToast( child: const Padding(
context, padding: EdgeInsets.all(6),
S.current child: Icon(FluentIcons.play),
.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( Widget makeGamePathSelect(BuildContext context, ToolsUIModel model, ToolsUIState state) {
BuildContext context, ToolsUIModel model, ToolsUIState state) {
return Row( return Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@ -223,8 +214,7 @@ class ToolsUI extends HookConsumerWidget {
), ),
onPressed: () { onPressed: () {
if (state.scInstalledPath.trim().isEmpty) { if (state.scInstalledPath.trim().isEmpty) {
showToast(context, showToast(context, S.current.tools_action_info_star_citizen_not_found);
S.current.tools_action_info_star_citizen_not_found);
return; return;
} }
model.openDir(state.scInstalledPath); model.openDir(state.scInstalledPath);
@ -233,8 +223,7 @@ class ToolsUI extends HookConsumerWidget {
); );
} }
Widget makeGameLauncherPathSelect( Widget makeGameLauncherPathSelect(BuildContext context, ToolsUIModel model, ToolsUIState state) {
BuildContext context, ToolsUIModel model, ToolsUIState state) {
return Row( return Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@ -268,10 +257,7 @@ class ToolsUI extends HookConsumerWidget {
), ),
onPressed: () { onPressed: () {
if (state.scInstalledPath.trim().isEmpty) { if (state.scInstalledPath.trim().isEmpty) {
showToast( showToast(context, S.current.tools_rsi_launcher_enhance_msg_error_launcher_notfound);
context,
S.current
.tools_rsi_launcher_enhance_msg_error_launcher_notfound);
return; return;
} }
model.openDir(state.rsiLauncherInstalledPath); model.openDir(state.rsiLauncherInstalledPath);

View File

@ -6,7 +6,7 @@ part of 'tools_ui_model.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$toolsUIModelHash() => r'cd72f7833fa5696baf9022d16d10d7951387df7e'; String _$toolsUIModelHash() => r'c8830e26df6c0ee572dd5e78c4ccef3317f8b4e6';
/// See also [ToolsUIModel]. /// See also [ToolsUIModel].
@ProviderFor(ToolsUIModel) @ProviderFor(ToolsUIModel)

View File

@ -1,5 +1,8 @@
import 'package:extended_image/extended_image.dart'; import 'package:extended_image/extended_image.dart';
import 'package:fluent_ui/fluent_ui.dart'; import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'cache_svg_image.dart';
class CacheNetImage extends StatelessWidget { class CacheNetImage extends StatelessWidget {
final String url; final String url;
@ -7,11 +10,18 @@ class CacheNetImage extends StatelessWidget {
final double? height; final double? height;
final BoxFit? fit; final BoxFit? fit;
const CacheNetImage( const CacheNetImage({super.key, required this.url, this.width, this.height, this.fit});
{super.key, required this.url, this.width, this.height, this.fit});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (url.endsWith(".svg")) {
return CachedSvgImage(
url,
width: width,
height: height,
fit: fit ?? BoxFit.contain,
);
}
return ExtendedImage.network( return ExtendedImage.network(
url, url,
width: width, width: width,
@ -20,14 +30,11 @@ class CacheNetImage extends StatelessWidget {
loadStateChanged: (ExtendedImageState state) { loadStateChanged: (ExtendedImageState state) {
switch (state.extendedImageLoadState) { switch (state.extendedImageLoadState) {
case LoadState.loading: case LoadState.loading:
return const Center( return SizedBox(
child: Padding( width: width,
padding: EdgeInsets.all(8.0), height: height,
child: Column( child: Center(
children: [ child: ProgressRing(),
ProgressRing(),
],
),
), ),
); );
case LoadState.failed: case LoadState.failed:

View 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(),
),
);
}
}

View 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,
),
);
}
}

View File

@ -15,6 +15,9 @@ import 'dart:ui' as ui;
export 'src/cache_image.dart'; export 'src/cache_image.dart';
export 'src/countdown_time_text.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/async.dart';
export '../common/utils/base_utils.dart'; export '../common/utils/base_utils.dart';
export 'package:starcitizen_doctor/generated/l10n.dart'; export 'package:starcitizen_doctor/generated/l10n.dart';
@ -137,14 +140,13 @@ ColorFilter makeSvgColor(Color color) {
return ui.ColorFilter.mode(color, ui.BlendMode.srcIn); return ui.ColorFilter.mode(color, ui.BlendMode.srcIn);
} }
CustomTransitionPage<T> myPageBuilder<T>( CustomTransitionPage<T> myPageBuilder<T>(BuildContext context, GoRouterState state, Widget child) {
BuildContext context, GoRouterState state, Widget child) {
return CustomTransitionPage( return CustomTransitionPage(
child: child, child: child,
transitionDuration: const Duration(milliseconds: 150), transitionDuration: const Duration(milliseconds: 150),
reverseTransitionDuration: const Duration(milliseconds: 150), reverseTransitionDuration: const Duration(milliseconds: 150),
transitionsBuilder: (BuildContext context, Animation<double> animation, transitionsBuilder:
Animation<double> secondaryAnimation, Widget child) { (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
return SlideTransition( return SlideTransition(
position: Tween<Offset>( position: Tween<Offset>(
begin: const Offset(0.0, 1.0), 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 Widget Function(BuildContext context, T data) childBuilder;
final Duration? autoRefreshDuration; final Duration? autoRefreshDuration;
const LoadingWidget( const LoadingWidget({super.key, this.data, required this.childBuilder, this.onLoadData, this.autoRefreshDuration});
{super.key,
this.data,
required this.childBuilder,
this.onLoadData,
this.autoRefreshDuration});
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
@ -204,8 +201,7 @@ class LoadingWidget<T> extends HookConsumerWidget {
return childBuilder(context, (data ?? dataState.value) as T); return childBuilder(context, (data ?? dataState.value) as T);
} }
void _loadData( void _loadData(ValueNotifier<T?> dataState, ValueNotifier<String> errorMsg) async {
ValueNotifier<T?> dataState, ValueNotifier<String> errorMsg) async {
errorMsg.value = ""; errorMsg.value = "";
try { try {
final r = await onLoadData!(); final r = await onLoadData!();

View File

@ -232,7 +232,7 @@ packages:
source: hosted source: hosted
version: "0.3.4+2" version: "0.3.4+2"
crypto: crypto:
dependency: transitive dependency: "direct main"
description: description:
name: crypto name: crypto
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
@ -916,7 +916,7 @@ packages:
source: hosted source: hosted
version: "2.2.0" version: "2.2.0"
path: path:
dependency: transitive dependency: "direct main"
description: description:
name: path name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"

View File

@ -68,6 +68,8 @@ dependencies:
qr_flutter: ^4.1.0 qr_flutter: ^4.1.0
desktop_multi_window: ^0.2.1 desktop_multi_window: ^0.2.1
watcher: ^1.1.1 watcher: ^1.1.1
path: ^1.9.1
crypto: ^3.0.6
dependency_overrides: dependency_overrides:
http: ^1.1.2 http: ^1.1.2