From c2a512699cd1dcfdd95219df217a81e506e85f37 Mon Sep 17 00:00:00 2001 From: xkeyC <3334969096@qq.com> Date: Sun, 26 Oct 2025 17:44:55 +0800 Subject: [PATCH] feat: citizen news support --- lib/api/news_api.dart | 23 + lib/api/rss.dart | 25 - lib/common/conf/url_conf.dart | 63 +- lib/data/citizen_news_data.dart | 54 ++ lib/data/citizen_news_data.freezed.dart | 854 ++++++++++++++++++ lib/data/citizen_news_data.g.dart | 91 ++ .../dialogs/home_md_content_dialog_ui.dart | 32 +- lib/ui/home/home_ui.dart | 770 ++++++++-------- lib/ui/home/home_ui_model.dart | 80 +- lib/ui/home/home_ui_model.freezed.dart | 93 +- lib/ui/home/home_ui_model.g.dart | 2 +- lib/ui/settings/settings_ui_model.g.dart | 2 +- lib/widgets/src/flow_number_text.dart | 18 +- lib/widgets/src/grid_item_animator.dart | 22 +- lib/widgets/src/swiper.dart | 115 +++ lib/widgets/widgets.dart | 187 ++-- pubspec.lock | 10 +- pubspec.yaml | 2 - 18 files changed, 1747 insertions(+), 696 deletions(-) create mode 100644 lib/api/news_api.dart delete mode 100644 lib/api/rss.dart create mode 100644 lib/data/citizen_news_data.dart create mode 100644 lib/data/citizen_news_data.freezed.dart create mode 100644 lib/data/citizen_news_data.g.dart create mode 100644 lib/widgets/src/swiper.dart diff --git a/lib/api/news_api.dart b/lib/api/news_api.dart new file mode 100644 index 0000000..94b628e --- /dev/null +++ b/lib/api/news_api.dart @@ -0,0 +1,23 @@ +import 'dart:convert'; + +import '../common/conf/url_conf.dart'; +import '../common/io/rs_http.dart'; +import '../common/utils/log.dart'; +import '../data/citizen_news_data.dart'; +import 'api.dart'; + +class NewsApi { + static Future getLatest() async { + try { + final data = await RSHttp.getText( + "${URLConf.newsApiHome}/api/latest", + withCustomDns: await Api.isUseInternalDNS(), + ); + final map = json.decode(data); + return CitizenNewsData.fromJson(map); + } catch (e) { + dPrint("getLatestNews error: $e"); + } + return null; + } +} diff --git a/lib/api/rss.dart b/lib/api/rss.dart deleted file mode 100644 index 58b703a..0000000 --- a/lib/api/rss.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'dart:io'; - -import 'package:dart_rss/dart_rss.dart'; -import 'package:starcitizen_doctor/common/io/rs_http.dart'; -import 'package:starcitizen_doctor/common/conf/url_conf.dart'; - -class RSSApi { - static Future> getRssVideo() async { - final r = await RSHttp.getText(URLConf.rssVideoUrl); - final f = RssFeed.parse(r); - return f.items.sublist(0, 8); - } - - static Future> getRssText() async { - final r2 = await RSHttp.getText(URLConf.rssTextUrl2); - final r2f = RssFeed.parse(r2); - final items = r2f.items; - items.sort((a, b) { - final aDate = HttpDate.parse(a.pubDate ?? "").millisecondsSinceEpoch; - final bDate = HttpDate.parse(b.pubDate ?? "").millisecondsSinceEpoch; - return bDate - aDate; - }); - return items; - } -} diff --git a/lib/common/conf/url_conf.dart b/lib/common/conf/url_conf.dart index 3a69a07..471baa7 100644 --- a/lib/common/conf/url_conf.dart +++ b/lib/common/conf/url_conf.dart @@ -7,7 +7,7 @@ import 'package:starcitizen_doctor/common/utils/log.dart'; class URLConf { /// HOME API static String gitApiHome = "https://git.scbox.xkeyc.cn"; - static String rssApiHome = "https://rss.scbox.xkeyc.cn"; + static String newsApiHome = "https://scbox.citizenwiki.cn"; static const String analyticsApiHome = "https://scbox.org"; static bool isUrlCheckPass = false; @@ -15,31 +15,22 @@ class URLConf { /// URLS static String get giteaAttachmentsUrl => "$gitApiHome/SCToolBox/Release"; - static String get gitlabLocalizationUrl => - "$gitApiHome/SCToolBox/LocalizationData"; + static String get gitlabLocalizationUrl => "$gitApiHome/SCToolBox/LocalizationData"; - static String get gitApiRSILauncherEnhanceUrl => - "$gitApiHome/SCToolBox/RSILauncherEnhance"; + static String get gitApiRSILauncherEnhanceUrl => "$gitApiHome/SCToolBox/RSILauncherEnhance"; static String get apiRepoPath => "$gitApiHome/SCToolBox/api/raw/branch/main"; static String get gitlabApiPath => "$gitApiHome/api/v1/"; - static String get webTranslateHomeUrl => - "$gitApiHome/SCToolBox/ScWeb_Chinese_Translate/raw/branch/main/json/locales"; + static String get webTranslateHomeUrl => "$gitApiHome/SCToolBox/ScWeb_Chinese_Translate/raw/branch/main/json/locales"; - static String get rssVideoUrl => - "$rssApiHome/bilibili/user/channel/27976358/290653"; - - static String get rssTextUrl2 => - "$rssApiHome/baidu/tieba/user/%E7%81%AC%E7%81%ACG%E7%81%AC%E7%81%AC&"; - - static const String googleTranslateApiUrl = - "https://translate-g-proxy.xkeyc.com"; + static const String googleTranslateApiUrl = "https://translate-g-proxy.xkeyc.com"; static const feedbackUrl = "https://support.citizenwiki.cn/all"; static const feedbackFAQUrl = "https://support.citizenwiki.cn/t/sc-toolbox"; - static String nav42KitUrl = "https://payload.citizenwiki.cn/api/community-navs?sort=is_sponsored&depth=2&page=1&limit=1000"; + static String nav42KitUrl = + "https://payload.citizenwiki.cn/api/community-navs?sort=is_sponsored&depth=2&page=1&limit=1000"; static String get devReleaseUrl => "$gitApiHome/SCToolBox/Release/releases"; @@ -47,19 +38,19 @@ class URLConf { // 使用 DNS 获取可用列表 final gitApiList = _genFinalList(await dnsLookupTxt("git.dns.scbox.org")); dPrint("DNS gitApiList ==== $gitApiList"); - final fasterGit = await getFasterUrl(gitApiList); + final fasterGit = await getFasterUrl(gitApiList, "git"); dPrint("gitApiList.Faster ==== $fasterGit"); if (fasterGit != null) { gitApiHome = fasterGit; } - final rssApiList = _genFinalList(await dnsLookupTxt("rss.dns.scbox.org")); - final fasterRss = await getFasterUrl(rssApiList); - dPrint("DNS rssApiList ==== $rssApiList"); - dPrint("rssApiList.Faster ==== $fasterRss"); - if (fasterRss != null) { - rssApiHome = fasterRss; + final newsApiList = _genFinalList(await dnsLookupTxt("news.dns.scbox.org")); + final fasterNews = await getFasterUrl(newsApiList, "news"); + dPrint("DNS newsApiList ==== $newsApiList"); + dPrint("newsApiList.Faster ==== $fasterNews"); + if (fasterNews != null) { + newsApiHome = fasterNews; } - isUrlCheckPass = fasterGit != null && fasterRss != null; + isUrlCheckPass = fasterGit != null && fasterNews != null; return isUrlCheckPass; } @@ -72,7 +63,7 @@ class URLConf { return (await DohClient.resolveTXT(host)) ?? []; } - static Future getFasterUrl(List urls) async { + static Future getFasterUrl(List urls, String mode) async { String firstUrl = ""; int callLen = 0; @@ -83,11 +74,23 @@ class URLConf { } } - for (var value in urls) { - RSHttp.head(value).then((resp) => onCall(resp, value), onError: (err) { - callLen++; - dPrint("RSHttp.head error $err"); - }); + for (var url in urls) { + var reqUrl = url; + switch (mode) { + case "git": + reqUrl = "$url/SCToolBox/Api/raw/branch/main/sc_doctor/version.json"; + break; + case "news": + reqUrl = "$url/api/latest"; + break; + } + RSHttp.head(reqUrl).then( + (resp) => onCall(resp, url), + onError: (err) { + callLen++; + dPrint("RSHttp.head error $err"); + }, + ); } while (true) { diff --git a/lib/data/citizen_news_data.dart b/lib/data/citizen_news_data.dart new file mode 100644 index 0000000..1712b1e --- /dev/null +++ b/lib/data/citizen_news_data.dart @@ -0,0 +1,54 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'citizen_news_data.freezed.dart'; + +part 'citizen_news_data.g.dart'; + +@freezed +sealed class CitizenNewsData with _$CitizenNewsData { + const factory CitizenNewsData({ + @Default([]) @JsonKey(name: 'videos') List videos, + @Default([]) @JsonKey(name: 'articles') List articles, + }) = _CitizenNewsData; + + const CitizenNewsData._(); + + factory CitizenNewsData.fromJson(Map json) => _$CitizenNewsDataFromJson(json); +} + +@freezed +sealed class CitizenNewsVideosItemData with _$CitizenNewsVideosItemData { + const factory CitizenNewsVideosItemData({ + @Default('') @JsonKey(name: 'title') String title, + @Default('') @JsonKey(name: 'author') String author, + @Default('') @JsonKey(name: 'description') String description, + @Default('') @JsonKey(name: 'link') String link, + @Default('') @JsonKey(name: 'pubDate') String pubDate, + @Default('') @JsonKey(name: 'postId') String postId, + @Default([]) @JsonKey(name: 'detailedDescription') List detailedDescription, + @Default('') @JsonKey(name: 'tag') String tag, + }) = _CitizenNewsVideosItemData; + + const CitizenNewsVideosItemData._(); + + factory CitizenNewsVideosItemData.fromJson(Map json) => _$CitizenNewsVideosItemDataFromJson(json); +} + +@freezed +sealed class CitizenNewsArticlesItemData with _$CitizenNewsArticlesItemData { + const factory CitizenNewsArticlesItemData({ + @Default('') @JsonKey(name: 'title') String title, + @Default('') @JsonKey(name: 'author') String author, + @Default('') @JsonKey(name: 'description') String description, + @Default('') @JsonKey(name: 'link') String link, + @Default('') @JsonKey(name: 'pubDate') String pubDate, + @Default('') @JsonKey(name: 'postId') String postId, + @Default([]) @JsonKey(name: 'detailedDescription') List detailedDescription, + @Default('') @JsonKey(name: 'tag') String tag, + }) = _CitizenNewsArticlesItemData; + + const CitizenNewsArticlesItemData._(); + + factory CitizenNewsArticlesItemData.fromJson(Map json) => + _$CitizenNewsArticlesItemDataFromJson(json); +} diff --git a/lib/data/citizen_news_data.freezed.dart b/lib/data/citizen_news_data.freezed.dart new file mode 100644 index 0000000..591c808 --- /dev/null +++ b/lib/data/citizen_news_data.freezed.dart @@ -0,0 +1,854 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// 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 'citizen_news_data.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$CitizenNewsData { + +@JsonKey(name: 'videos') List get videos;@JsonKey(name: 'articles') List get articles; +/// Create a copy of CitizenNewsData +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$CitizenNewsDataCopyWith get copyWith => _$CitizenNewsDataCopyWithImpl(this as CitizenNewsData, _$identity); + + /// Serializes this CitizenNewsData to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is CitizenNewsData&&const DeepCollectionEquality().equals(other.videos, videos)&&const DeepCollectionEquality().equals(other.articles, articles)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(videos),const DeepCollectionEquality().hash(articles)); + +@override +String toString() { + return 'CitizenNewsData(videos: $videos, articles: $articles)'; +} + + +} + +/// @nodoc +abstract mixin class $CitizenNewsDataCopyWith<$Res> { + factory $CitizenNewsDataCopyWith(CitizenNewsData value, $Res Function(CitizenNewsData) _then) = _$CitizenNewsDataCopyWithImpl; +@useResult +$Res call({ +@JsonKey(name: 'videos') List videos,@JsonKey(name: 'articles') List articles +}); + + + + +} +/// @nodoc +class _$CitizenNewsDataCopyWithImpl<$Res> + implements $CitizenNewsDataCopyWith<$Res> { + _$CitizenNewsDataCopyWithImpl(this._self, this._then); + + final CitizenNewsData _self; + final $Res Function(CitizenNewsData) _then; + +/// Create a copy of CitizenNewsData +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? videos = null,Object? articles = null,}) { + return _then(_self.copyWith( +videos: null == videos ? _self.videos : videos // ignore: cast_nullable_to_non_nullable +as List,articles: null == articles ? _self.articles : articles // ignore: cast_nullable_to_non_nullable +as List, + )); +} + +} + + +/// Adds pattern-matching-related methods to [CitizenNewsData]. +extension CitizenNewsDataPatterns on CitizenNewsData { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _CitizenNewsData value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _CitizenNewsData() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _CitizenNewsData value) $default,){ +final _that = this; +switch (_that) { +case _CitizenNewsData(): +return $default(_that);} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _CitizenNewsData value)? $default,){ +final _that = this; +switch (_that) { +case _CitizenNewsData() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function(@JsonKey(name: 'videos') List videos, @JsonKey(name: 'articles') List articles)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _CitizenNewsData() when $default != null: +return $default(_that.videos,_that.articles);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function(@JsonKey(name: 'videos') List videos, @JsonKey(name: 'articles') List articles) $default,) {final _that = this; +switch (_that) { +case _CitizenNewsData(): +return $default(_that.videos,_that.articles);} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function(@JsonKey(name: 'videos') List videos, @JsonKey(name: 'articles') List articles)? $default,) {final _that = this; +switch (_that) { +case _CitizenNewsData() when $default != null: +return $default(_that.videos,_that.articles);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _CitizenNewsData extends CitizenNewsData { + const _CitizenNewsData({@JsonKey(name: 'videos') final List videos = const [], @JsonKey(name: 'articles') final List articles = const []}): _videos = videos,_articles = articles,super._(); + factory _CitizenNewsData.fromJson(Map json) => _$CitizenNewsDataFromJson(json); + + final List _videos; +@override@JsonKey(name: 'videos') List get videos { + if (_videos is EqualUnmodifiableListView) return _videos; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_videos); +} + + final List _articles; +@override@JsonKey(name: 'articles') List get articles { + if (_articles is EqualUnmodifiableListView) return _articles; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_articles); +} + + +/// Create a copy of CitizenNewsData +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$CitizenNewsDataCopyWith<_CitizenNewsData> get copyWith => __$CitizenNewsDataCopyWithImpl<_CitizenNewsData>(this, _$identity); + +@override +Map toJson() { + return _$CitizenNewsDataToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _CitizenNewsData&&const DeepCollectionEquality().equals(other._videos, _videos)&&const DeepCollectionEquality().equals(other._articles, _articles)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_videos),const DeepCollectionEquality().hash(_articles)); + +@override +String toString() { + return 'CitizenNewsData(videos: $videos, articles: $articles)'; +} + + +} + +/// @nodoc +abstract mixin class _$CitizenNewsDataCopyWith<$Res> implements $CitizenNewsDataCopyWith<$Res> { + factory _$CitizenNewsDataCopyWith(_CitizenNewsData value, $Res Function(_CitizenNewsData) _then) = __$CitizenNewsDataCopyWithImpl; +@override @useResult +$Res call({ +@JsonKey(name: 'videos') List videos,@JsonKey(name: 'articles') List articles +}); + + + + +} +/// @nodoc +class __$CitizenNewsDataCopyWithImpl<$Res> + implements _$CitizenNewsDataCopyWith<$Res> { + __$CitizenNewsDataCopyWithImpl(this._self, this._then); + + final _CitizenNewsData _self; + final $Res Function(_CitizenNewsData) _then; + +/// Create a copy of CitizenNewsData +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? videos = null,Object? articles = null,}) { + return _then(_CitizenNewsData( +videos: null == videos ? _self._videos : videos // ignore: cast_nullable_to_non_nullable +as List,articles: null == articles ? _self._articles : articles // ignore: cast_nullable_to_non_nullable +as List, + )); +} + + +} + + +/// @nodoc +mixin _$CitizenNewsVideosItemData { + +@JsonKey(name: 'title') String get title;@JsonKey(name: 'author') String get author;@JsonKey(name: 'description') String get description;@JsonKey(name: 'link') String get link;@JsonKey(name: 'pubDate') String get pubDate;@JsonKey(name: 'postId') String get postId;@JsonKey(name: 'detailedDescription') List get detailedDescription;@JsonKey(name: 'tag') String get tag; +/// Create a copy of CitizenNewsVideosItemData +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$CitizenNewsVideosItemDataCopyWith get copyWith => _$CitizenNewsVideosItemDataCopyWithImpl(this as CitizenNewsVideosItemData, _$identity); + + /// Serializes this CitizenNewsVideosItemData to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is CitizenNewsVideosItemData&&(identical(other.title, title) || other.title == title)&&(identical(other.author, author) || other.author == author)&&(identical(other.description, description) || other.description == description)&&(identical(other.link, link) || other.link == link)&&(identical(other.pubDate, pubDate) || other.pubDate == pubDate)&&(identical(other.postId, postId) || other.postId == postId)&&const DeepCollectionEquality().equals(other.detailedDescription, detailedDescription)&&(identical(other.tag, tag) || other.tag == tag)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,title,author,description,link,pubDate,postId,const DeepCollectionEquality().hash(detailedDescription),tag); + +@override +String toString() { + return 'CitizenNewsVideosItemData(title: $title, author: $author, description: $description, link: $link, pubDate: $pubDate, postId: $postId, detailedDescription: $detailedDescription, tag: $tag)'; +} + + +} + +/// @nodoc +abstract mixin class $CitizenNewsVideosItemDataCopyWith<$Res> { + factory $CitizenNewsVideosItemDataCopyWith(CitizenNewsVideosItemData value, $Res Function(CitizenNewsVideosItemData) _then) = _$CitizenNewsVideosItemDataCopyWithImpl; +@useResult +$Res call({ +@JsonKey(name: 'title') String title,@JsonKey(name: 'author') String author,@JsonKey(name: 'description') String description,@JsonKey(name: 'link') String link,@JsonKey(name: 'pubDate') String pubDate,@JsonKey(name: 'postId') String postId,@JsonKey(name: 'detailedDescription') List detailedDescription,@JsonKey(name: 'tag') String tag +}); + + + + +} +/// @nodoc +class _$CitizenNewsVideosItemDataCopyWithImpl<$Res> + implements $CitizenNewsVideosItemDataCopyWith<$Res> { + _$CitizenNewsVideosItemDataCopyWithImpl(this._self, this._then); + + final CitizenNewsVideosItemData _self; + final $Res Function(CitizenNewsVideosItemData) _then; + +/// Create a copy of CitizenNewsVideosItemData +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? title = null,Object? author = null,Object? description = null,Object? link = null,Object? pubDate = null,Object? postId = null,Object? detailedDescription = null,Object? tag = null,}) { + return _then(_self.copyWith( +title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,author: null == author ? _self.author : author // ignore: cast_nullable_to_non_nullable +as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable +as String,link: null == link ? _self.link : link // ignore: cast_nullable_to_non_nullable +as String,pubDate: null == pubDate ? _self.pubDate : pubDate // ignore: cast_nullable_to_non_nullable +as String,postId: null == postId ? _self.postId : postId // ignore: cast_nullable_to_non_nullable +as String,detailedDescription: null == detailedDescription ? _self.detailedDescription : detailedDescription // ignore: cast_nullable_to_non_nullable +as List,tag: null == tag ? _self.tag : tag // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [CitizenNewsVideosItemData]. +extension CitizenNewsVideosItemDataPatterns on CitizenNewsVideosItemData { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _CitizenNewsVideosItemData value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _CitizenNewsVideosItemData() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _CitizenNewsVideosItemData value) $default,){ +final _that = this; +switch (_that) { +case _CitizenNewsVideosItemData(): +return $default(_that);} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _CitizenNewsVideosItemData value)? $default,){ +final _that = this; +switch (_that) { +case _CitizenNewsVideosItemData() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function(@JsonKey(name: 'title') String title, @JsonKey(name: 'author') String author, @JsonKey(name: 'description') String description, @JsonKey(name: 'link') String link, @JsonKey(name: 'pubDate') String pubDate, @JsonKey(name: 'postId') String postId, @JsonKey(name: 'detailedDescription') List detailedDescription, @JsonKey(name: 'tag') String tag)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _CitizenNewsVideosItemData() when $default != null: +return $default(_that.title,_that.author,_that.description,_that.link,_that.pubDate,_that.postId,_that.detailedDescription,_that.tag);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function(@JsonKey(name: 'title') String title, @JsonKey(name: 'author') String author, @JsonKey(name: 'description') String description, @JsonKey(name: 'link') String link, @JsonKey(name: 'pubDate') String pubDate, @JsonKey(name: 'postId') String postId, @JsonKey(name: 'detailedDescription') List detailedDescription, @JsonKey(name: 'tag') String tag) $default,) {final _that = this; +switch (_that) { +case _CitizenNewsVideosItemData(): +return $default(_that.title,_that.author,_that.description,_that.link,_that.pubDate,_that.postId,_that.detailedDescription,_that.tag);} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function(@JsonKey(name: 'title') String title, @JsonKey(name: 'author') String author, @JsonKey(name: 'description') String description, @JsonKey(name: 'link') String link, @JsonKey(name: 'pubDate') String pubDate, @JsonKey(name: 'postId') String postId, @JsonKey(name: 'detailedDescription') List detailedDescription, @JsonKey(name: 'tag') String tag)? $default,) {final _that = this; +switch (_that) { +case _CitizenNewsVideosItemData() when $default != null: +return $default(_that.title,_that.author,_that.description,_that.link,_that.pubDate,_that.postId,_that.detailedDescription,_that.tag);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _CitizenNewsVideosItemData extends CitizenNewsVideosItemData { + const _CitizenNewsVideosItemData({@JsonKey(name: 'title') this.title = '', @JsonKey(name: 'author') this.author = '', @JsonKey(name: 'description') this.description = '', @JsonKey(name: 'link') this.link = '', @JsonKey(name: 'pubDate') this.pubDate = '', @JsonKey(name: 'postId') this.postId = '', @JsonKey(name: 'detailedDescription') final List detailedDescription = const [], @JsonKey(name: 'tag') this.tag = ''}): _detailedDescription = detailedDescription,super._(); + factory _CitizenNewsVideosItemData.fromJson(Map json) => _$CitizenNewsVideosItemDataFromJson(json); + +@override@JsonKey(name: 'title') final String title; +@override@JsonKey(name: 'author') final String author; +@override@JsonKey(name: 'description') final String description; +@override@JsonKey(name: 'link') final String link; +@override@JsonKey(name: 'pubDate') final String pubDate; +@override@JsonKey(name: 'postId') final String postId; + final List _detailedDescription; +@override@JsonKey(name: 'detailedDescription') List get detailedDescription { + if (_detailedDescription is EqualUnmodifiableListView) return _detailedDescription; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_detailedDescription); +} + +@override@JsonKey(name: 'tag') final String tag; + +/// Create a copy of CitizenNewsVideosItemData +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$CitizenNewsVideosItemDataCopyWith<_CitizenNewsVideosItemData> get copyWith => __$CitizenNewsVideosItemDataCopyWithImpl<_CitizenNewsVideosItemData>(this, _$identity); + +@override +Map toJson() { + return _$CitizenNewsVideosItemDataToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _CitizenNewsVideosItemData&&(identical(other.title, title) || other.title == title)&&(identical(other.author, author) || other.author == author)&&(identical(other.description, description) || other.description == description)&&(identical(other.link, link) || other.link == link)&&(identical(other.pubDate, pubDate) || other.pubDate == pubDate)&&(identical(other.postId, postId) || other.postId == postId)&&const DeepCollectionEquality().equals(other._detailedDescription, _detailedDescription)&&(identical(other.tag, tag) || other.tag == tag)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,title,author,description,link,pubDate,postId,const DeepCollectionEquality().hash(_detailedDescription),tag); + +@override +String toString() { + return 'CitizenNewsVideosItemData(title: $title, author: $author, description: $description, link: $link, pubDate: $pubDate, postId: $postId, detailedDescription: $detailedDescription, tag: $tag)'; +} + + +} + +/// @nodoc +abstract mixin class _$CitizenNewsVideosItemDataCopyWith<$Res> implements $CitizenNewsVideosItemDataCopyWith<$Res> { + factory _$CitizenNewsVideosItemDataCopyWith(_CitizenNewsVideosItemData value, $Res Function(_CitizenNewsVideosItemData) _then) = __$CitizenNewsVideosItemDataCopyWithImpl; +@override @useResult +$Res call({ +@JsonKey(name: 'title') String title,@JsonKey(name: 'author') String author,@JsonKey(name: 'description') String description,@JsonKey(name: 'link') String link,@JsonKey(name: 'pubDate') String pubDate,@JsonKey(name: 'postId') String postId,@JsonKey(name: 'detailedDescription') List detailedDescription,@JsonKey(name: 'tag') String tag +}); + + + + +} +/// @nodoc +class __$CitizenNewsVideosItemDataCopyWithImpl<$Res> + implements _$CitizenNewsVideosItemDataCopyWith<$Res> { + __$CitizenNewsVideosItemDataCopyWithImpl(this._self, this._then); + + final _CitizenNewsVideosItemData _self; + final $Res Function(_CitizenNewsVideosItemData) _then; + +/// Create a copy of CitizenNewsVideosItemData +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? title = null,Object? author = null,Object? description = null,Object? link = null,Object? pubDate = null,Object? postId = null,Object? detailedDescription = null,Object? tag = null,}) { + return _then(_CitizenNewsVideosItemData( +title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,author: null == author ? _self.author : author // ignore: cast_nullable_to_non_nullable +as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable +as String,link: null == link ? _self.link : link // ignore: cast_nullable_to_non_nullable +as String,pubDate: null == pubDate ? _self.pubDate : pubDate // ignore: cast_nullable_to_non_nullable +as String,postId: null == postId ? _self.postId : postId // ignore: cast_nullable_to_non_nullable +as String,detailedDescription: null == detailedDescription ? _self._detailedDescription : detailedDescription // ignore: cast_nullable_to_non_nullable +as List,tag: null == tag ? _self.tag : tag // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + + +/// @nodoc +mixin _$CitizenNewsArticlesItemData { + +@JsonKey(name: 'title') String get title;@JsonKey(name: 'author') String get author;@JsonKey(name: 'description') String get description;@JsonKey(name: 'link') String get link;@JsonKey(name: 'pubDate') String get pubDate;@JsonKey(name: 'postId') String get postId;@JsonKey(name: 'detailedDescription') List get detailedDescription;@JsonKey(name: 'tag') String get tag; +/// Create a copy of CitizenNewsArticlesItemData +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$CitizenNewsArticlesItemDataCopyWith get copyWith => _$CitizenNewsArticlesItemDataCopyWithImpl(this as CitizenNewsArticlesItemData, _$identity); + + /// Serializes this CitizenNewsArticlesItemData to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is CitizenNewsArticlesItemData&&(identical(other.title, title) || other.title == title)&&(identical(other.author, author) || other.author == author)&&(identical(other.description, description) || other.description == description)&&(identical(other.link, link) || other.link == link)&&(identical(other.pubDate, pubDate) || other.pubDate == pubDate)&&(identical(other.postId, postId) || other.postId == postId)&&const DeepCollectionEquality().equals(other.detailedDescription, detailedDescription)&&(identical(other.tag, tag) || other.tag == tag)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,title,author,description,link,pubDate,postId,const DeepCollectionEquality().hash(detailedDescription),tag); + +@override +String toString() { + return 'CitizenNewsArticlesItemData(title: $title, author: $author, description: $description, link: $link, pubDate: $pubDate, postId: $postId, detailedDescription: $detailedDescription, tag: $tag)'; +} + + +} + +/// @nodoc +abstract mixin class $CitizenNewsArticlesItemDataCopyWith<$Res> { + factory $CitizenNewsArticlesItemDataCopyWith(CitizenNewsArticlesItemData value, $Res Function(CitizenNewsArticlesItemData) _then) = _$CitizenNewsArticlesItemDataCopyWithImpl; +@useResult +$Res call({ +@JsonKey(name: 'title') String title,@JsonKey(name: 'author') String author,@JsonKey(name: 'description') String description,@JsonKey(name: 'link') String link,@JsonKey(name: 'pubDate') String pubDate,@JsonKey(name: 'postId') String postId,@JsonKey(name: 'detailedDescription') List detailedDescription,@JsonKey(name: 'tag') String tag +}); + + + + +} +/// @nodoc +class _$CitizenNewsArticlesItemDataCopyWithImpl<$Res> + implements $CitizenNewsArticlesItemDataCopyWith<$Res> { + _$CitizenNewsArticlesItemDataCopyWithImpl(this._self, this._then); + + final CitizenNewsArticlesItemData _self; + final $Res Function(CitizenNewsArticlesItemData) _then; + +/// Create a copy of CitizenNewsArticlesItemData +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? title = null,Object? author = null,Object? description = null,Object? link = null,Object? pubDate = null,Object? postId = null,Object? detailedDescription = null,Object? tag = null,}) { + return _then(_self.copyWith( +title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,author: null == author ? _self.author : author // ignore: cast_nullable_to_non_nullable +as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable +as String,link: null == link ? _self.link : link // ignore: cast_nullable_to_non_nullable +as String,pubDate: null == pubDate ? _self.pubDate : pubDate // ignore: cast_nullable_to_non_nullable +as String,postId: null == postId ? _self.postId : postId // ignore: cast_nullable_to_non_nullable +as String,detailedDescription: null == detailedDescription ? _self.detailedDescription : detailedDescription // ignore: cast_nullable_to_non_nullable +as List,tag: null == tag ? _self.tag : tag // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [CitizenNewsArticlesItemData]. +extension CitizenNewsArticlesItemDataPatterns on CitizenNewsArticlesItemData { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _CitizenNewsArticlesItemData value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _CitizenNewsArticlesItemData() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _CitizenNewsArticlesItemData value) $default,){ +final _that = this; +switch (_that) { +case _CitizenNewsArticlesItemData(): +return $default(_that);} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _CitizenNewsArticlesItemData value)? $default,){ +final _that = this; +switch (_that) { +case _CitizenNewsArticlesItemData() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function(@JsonKey(name: 'title') String title, @JsonKey(name: 'author') String author, @JsonKey(name: 'description') String description, @JsonKey(name: 'link') String link, @JsonKey(name: 'pubDate') String pubDate, @JsonKey(name: 'postId') String postId, @JsonKey(name: 'detailedDescription') List detailedDescription, @JsonKey(name: 'tag') String tag)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _CitizenNewsArticlesItemData() when $default != null: +return $default(_that.title,_that.author,_that.description,_that.link,_that.pubDate,_that.postId,_that.detailedDescription,_that.tag);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function(@JsonKey(name: 'title') String title, @JsonKey(name: 'author') String author, @JsonKey(name: 'description') String description, @JsonKey(name: 'link') String link, @JsonKey(name: 'pubDate') String pubDate, @JsonKey(name: 'postId') String postId, @JsonKey(name: 'detailedDescription') List detailedDescription, @JsonKey(name: 'tag') String tag) $default,) {final _that = this; +switch (_that) { +case _CitizenNewsArticlesItemData(): +return $default(_that.title,_that.author,_that.description,_that.link,_that.pubDate,_that.postId,_that.detailedDescription,_that.tag);} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function(@JsonKey(name: 'title') String title, @JsonKey(name: 'author') String author, @JsonKey(name: 'description') String description, @JsonKey(name: 'link') String link, @JsonKey(name: 'pubDate') String pubDate, @JsonKey(name: 'postId') String postId, @JsonKey(name: 'detailedDescription') List detailedDescription, @JsonKey(name: 'tag') String tag)? $default,) {final _that = this; +switch (_that) { +case _CitizenNewsArticlesItemData() when $default != null: +return $default(_that.title,_that.author,_that.description,_that.link,_that.pubDate,_that.postId,_that.detailedDescription,_that.tag);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _CitizenNewsArticlesItemData extends CitizenNewsArticlesItemData { + const _CitizenNewsArticlesItemData({@JsonKey(name: 'title') this.title = '', @JsonKey(name: 'author') this.author = '', @JsonKey(name: 'description') this.description = '', @JsonKey(name: 'link') this.link = '', @JsonKey(name: 'pubDate') this.pubDate = '', @JsonKey(name: 'postId') this.postId = '', @JsonKey(name: 'detailedDescription') final List detailedDescription = const [], @JsonKey(name: 'tag') this.tag = ''}): _detailedDescription = detailedDescription,super._(); + factory _CitizenNewsArticlesItemData.fromJson(Map json) => _$CitizenNewsArticlesItemDataFromJson(json); + +@override@JsonKey(name: 'title') final String title; +@override@JsonKey(name: 'author') final String author; +@override@JsonKey(name: 'description') final String description; +@override@JsonKey(name: 'link') final String link; +@override@JsonKey(name: 'pubDate') final String pubDate; +@override@JsonKey(name: 'postId') final String postId; + final List _detailedDescription; +@override@JsonKey(name: 'detailedDescription') List get detailedDescription { + if (_detailedDescription is EqualUnmodifiableListView) return _detailedDescription; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_detailedDescription); +} + +@override@JsonKey(name: 'tag') final String tag; + +/// Create a copy of CitizenNewsArticlesItemData +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$CitizenNewsArticlesItemDataCopyWith<_CitizenNewsArticlesItemData> get copyWith => __$CitizenNewsArticlesItemDataCopyWithImpl<_CitizenNewsArticlesItemData>(this, _$identity); + +@override +Map toJson() { + return _$CitizenNewsArticlesItemDataToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _CitizenNewsArticlesItemData&&(identical(other.title, title) || other.title == title)&&(identical(other.author, author) || other.author == author)&&(identical(other.description, description) || other.description == description)&&(identical(other.link, link) || other.link == link)&&(identical(other.pubDate, pubDate) || other.pubDate == pubDate)&&(identical(other.postId, postId) || other.postId == postId)&&const DeepCollectionEquality().equals(other._detailedDescription, _detailedDescription)&&(identical(other.tag, tag) || other.tag == tag)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,title,author,description,link,pubDate,postId,const DeepCollectionEquality().hash(_detailedDescription),tag); + +@override +String toString() { + return 'CitizenNewsArticlesItemData(title: $title, author: $author, description: $description, link: $link, pubDate: $pubDate, postId: $postId, detailedDescription: $detailedDescription, tag: $tag)'; +} + + +} + +/// @nodoc +abstract mixin class _$CitizenNewsArticlesItemDataCopyWith<$Res> implements $CitizenNewsArticlesItemDataCopyWith<$Res> { + factory _$CitizenNewsArticlesItemDataCopyWith(_CitizenNewsArticlesItemData value, $Res Function(_CitizenNewsArticlesItemData) _then) = __$CitizenNewsArticlesItemDataCopyWithImpl; +@override @useResult +$Res call({ +@JsonKey(name: 'title') String title,@JsonKey(name: 'author') String author,@JsonKey(name: 'description') String description,@JsonKey(name: 'link') String link,@JsonKey(name: 'pubDate') String pubDate,@JsonKey(name: 'postId') String postId,@JsonKey(name: 'detailedDescription') List detailedDescription,@JsonKey(name: 'tag') String tag +}); + + + + +} +/// @nodoc +class __$CitizenNewsArticlesItemDataCopyWithImpl<$Res> + implements _$CitizenNewsArticlesItemDataCopyWith<$Res> { + __$CitizenNewsArticlesItemDataCopyWithImpl(this._self, this._then); + + final _CitizenNewsArticlesItemData _self; + final $Res Function(_CitizenNewsArticlesItemData) _then; + +/// Create a copy of CitizenNewsArticlesItemData +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? title = null,Object? author = null,Object? description = null,Object? link = null,Object? pubDate = null,Object? postId = null,Object? detailedDescription = null,Object? tag = null,}) { + return _then(_CitizenNewsArticlesItemData( +title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,author: null == author ? _self.author : author // ignore: cast_nullable_to_non_nullable +as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable +as String,link: null == link ? _self.link : link // ignore: cast_nullable_to_non_nullable +as String,pubDate: null == pubDate ? _self.pubDate : pubDate // ignore: cast_nullable_to_non_nullable +as String,postId: null == postId ? _self.postId : postId // ignore: cast_nullable_to_non_nullable +as String,detailedDescription: null == detailedDescription ? _self._detailedDescription : detailedDescription // ignore: cast_nullable_to_non_nullable +as List,tag: null == tag ? _self.tag : tag // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +// dart format on diff --git a/lib/data/citizen_news_data.g.dart b/lib/data/citizen_news_data.g.dart new file mode 100644 index 0000000..0b95fa7 --- /dev/null +++ b/lib/data/citizen_news_data.g.dart @@ -0,0 +1,91 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'citizen_news_data.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_CitizenNewsData _$CitizenNewsDataFromJson( + Map json, +) => _CitizenNewsData( + videos: + (json['videos'] as List?) + ?.map( + (e) => + CitizenNewsVideosItemData.fromJson(e as Map), + ) + .toList() ?? + const [], + articles: + (json['articles'] as List?) + ?.map( + (e) => + CitizenNewsArticlesItemData.fromJson(e as Map), + ) + .toList() ?? + const [], +); + +Map _$CitizenNewsDataToJson(_CitizenNewsData instance) => + {'videos': instance.videos, 'articles': instance.articles}; + +_CitizenNewsVideosItemData _$CitizenNewsVideosItemDataFromJson( + Map json, +) => _CitizenNewsVideosItemData( + title: json['title'] as String? ?? '', + author: json['author'] as String? ?? '', + description: json['description'] as String? ?? '', + link: json['link'] as String? ?? '', + pubDate: json['pubDate'] as String? ?? '', + postId: json['postId'] as String? ?? '', + detailedDescription: + (json['detailedDescription'] as List?) + ?.map((e) => e as String) + .toList() ?? + const [], + tag: json['tag'] as String? ?? '', +); + +Map _$CitizenNewsVideosItemDataToJson( + _CitizenNewsVideosItemData instance, +) => { + 'title': instance.title, + 'author': instance.author, + 'description': instance.description, + 'link': instance.link, + 'pubDate': instance.pubDate, + 'postId': instance.postId, + 'detailedDescription': instance.detailedDescription, + 'tag': instance.tag, +}; + +_CitizenNewsArticlesItemData _$CitizenNewsArticlesItemDataFromJson( + Map json, +) => _CitizenNewsArticlesItemData( + title: json['title'] as String? ?? '', + author: json['author'] as String? ?? '', + description: json['description'] as String? ?? '', + link: json['link'] as String? ?? '', + pubDate: json['pubDate'] as String? ?? '', + postId: json['postId'] as String? ?? '', + detailedDescription: + (json['detailedDescription'] as List?) + ?.map((e) => e as String) + .toList() ?? + const [], + tag: json['tag'] as String? ?? '', +); + +Map _$CitizenNewsArticlesItemDataToJson( + _CitizenNewsArticlesItemData instance, +) => { + 'title': instance.title, + 'author': instance.author, + 'description': instance.description, + 'link': instance.link, + 'pubDate': instance.pubDate, + 'postId': instance.postId, + 'detailedDescription': instance.detailedDescription, + 'tag': instance.tag, +}; diff --git a/lib/ui/home/dialogs/home_md_content_dialog_ui.dart b/lib/ui/home/dialogs/home_md_content_dialog_ui.dart index bf8655f..e7b5375 100644 --- a/lib/ui/home/dialogs/home_md_content_dialog_ui.dart +++ b/lib/ui/home/dialogs/home_md_content_dialog_ui.dart @@ -6,18 +6,17 @@ import 'package:starcitizen_doctor/widgets/widgets.dart'; class HomeMdContentDialogUI extends HookConsumerWidget { final String title; - final String url; + final String? url; + final String? mdContent; - const HomeMdContentDialogUI( - {super.key, required this.title, required this.url}); + const HomeMdContentDialogUI({super.key, required this.title, this.url, this.mdContent}) + : assert(url != null || mdContent != null, "Either url or htmlContent must be provided"); @override Widget build(BuildContext context, WidgetRef ref) { return Material( child: ContentDialog( - constraints: BoxConstraints( - maxWidth: MediaQuery.of(context).size.width * .6, - ), + constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .6), title: Text(title), content: LoadingWidget( onLoadData: _getContent, @@ -36,21 +35,24 @@ class HomeMdContentDialogUI extends HookConsumerWidget { ), actions: [ FilledButton( - child: Padding( - padding: - const EdgeInsets.only(left: 8, right: 8, top: 2, bottom: 2), - child: Text(S.current.action_close), - ), - onPressed: () { - Navigator.pop(context); - }) + child: Padding( + padding: const EdgeInsets.only(left: 8, right: 8, top: 2, bottom: 2), + child: Text(S.current.action_close), + ), + onPressed: () { + Navigator.pop(context); + }, + ), ], ), ); } Future _getContent() async { - final r = await RSHttp.getText(url); + if (mdContent != null) { + return mdContent!; + } + final r = await RSHttp.getText(url!); return r; } } diff --git a/lib/ui/home/home_ui.dart b/lib/ui/home/home_ui.dart index 870fead..2720932 100644 --- a/lib/ui/home/home_ui.dart +++ b/lib/ui/home/home_ui.dart @@ -59,29 +59,27 @@ class HomeUI extends HookConsumerWidget { ), const SizedBox(height: 6), ], - ...makeIndex(context, model, homeState, ref) + ...makeIndex(context, model, homeState, ref), ], ), ), ), if (homeState.isFixing) Container( - decoration: BoxDecoration( - color: Colors.black.withAlpha(150), - ), + decoration: BoxDecoration(color: Colors.black.withAlpha(150)), child: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ const ProgressRing(), const SizedBox(height: 12), - Text(homeState.isFixingString.isNotEmpty - ? homeState.isFixingString - : S.current.doctor_info_processing), + Text( + homeState.isFixingString.isNotEmpty ? homeState.isFixingString : S.current.doctor_info_processing, + ), ], ), ), - ) + ), ], ); } @@ -100,28 +98,16 @@ class HomeUI extends HookConsumerWidget { children: [ Padding( padding: const EdgeInsets.only(bottom: 30), - child: Image.asset( - "assets/sc_logo.png", - fit: BoxFit.fitHeight, - height: 260, - ), + child: Image.asset("assets/sc_logo.png", fit: BoxFit.fitHeight, height: 260), ), - makeGameStatusCard(context, model, 340, homeState) + makeGameStatusCard(context, model, 340, homeState), ], ), ), ), ), - Positioned( - top: 0, - left: 24, - child: makeLeftColumn(context, model, width, homeState), - ), - Positioned( - right: 24, - top: 0, - child: makeNewsCard(context, model, homeState), - ), + Positioned(top: 0, left: 24, child: makeLeftColumn(context, model, width, homeState)), + Positioned(right: 24, top: 0, child: makeNewsCard(context, model, homeState)), ], ), const SizedBox(height: 24), @@ -137,10 +123,7 @@ class HomeUI extends HookConsumerWidget { await context.push("/guide"); await model.reScanPath(); }, - child: const Padding( - padding: EdgeInsets.all(6), - child: Icon(FluentIcons.settings), - ), + child: const Padding(padding: EdgeInsets.all(6), child: Icon(FluentIcons.settings)), ), const SizedBox(width: 6), Expanded( @@ -148,17 +131,12 @@ class HomeUI extends HookConsumerWidget { value: homeState.scInstalledPath, isExpanded: true, items: [ - ComboBoxItem( - value: "not_install", - child: Text(S.current.home_not_installed_or_failed), - ), + ComboBoxItem(value: "not_install", child: Text(S.current.home_not_installed_or_failed)), for (final path in homeState.scInstallPaths) ComboBoxItem( value: path, - child: Row( - children: [Text(path)], - ), - ) + child: Row(children: [Text(path)]), + ), ], onChanged: model.onChangeInstallPath, ), @@ -166,38 +144,29 @@ class HomeUI extends HookConsumerWidget { if (S.current.app_language_code == NoL10n.langCodeZhCn) ...[ const SizedBox(width: 12), Button( - onPressed: homeState.webLocalizationVersionsData == null ? null : () => model.launchRSI(context), - style: homeState.isCurGameRunning - ? null - : ButtonStyle( - backgroundColor: WidgetStateProperty.resolveWith(_getRunButtonColor), - ), - child: Padding( - padding: const EdgeInsets.all(6), - child: Icon( - homeState.isCurGameRunning ? FluentIcons.stop_solid : FluentIcons.play_solid, - color: homeState.isCurGameRunning ? Colors.red.withValues(alpha: .8) : Colors.white, - ), - )), + onPressed: homeState.webLocalizationVersionsData == null ? null : () => model.launchRSI(context), + style: homeState.isCurGameRunning + ? null + : ButtonStyle(backgroundColor: WidgetStateProperty.resolveWith(_getRunButtonColor)), + child: Padding( + padding: const EdgeInsets.all(6), + child: Icon( + homeState.isCurGameRunning ? FluentIcons.stop_solid : FluentIcons.play_solid, + color: homeState.isCurGameRunning ? Colors.red.withValues(alpha: .8) : Colors.white, + ), + ), + ), ], const SizedBox(width: 12), Button( onPressed: () => _checkAndGoInputMethod(context, homeState, model, ref), - style: ButtonStyle( - backgroundColor: WidgetStateProperty.resolveWith((_) => Colors.blue), - ), - child: Padding( - padding: const EdgeInsets.all(6), - child: Icon(FluentIcons.keyboard_classic), - ), + style: ButtonStyle(backgroundColor: WidgetStateProperty.resolveWith((_) => Colors.blue)), + child: Padding(padding: const EdgeInsets.all(6), child: Icon(FluentIcons.keyboard_classic)), ), const SizedBox(width: 12), Button( onPressed: model.reScanPath, - child: const Padding( - padding: EdgeInsets.all(6), - child: Icon(FluentIcons.refresh), - ), + child: const Padding(padding: EdgeInsets.all(6), child: Icon(FluentIcons.refresh)), ), ], ), @@ -223,55 +192,44 @@ class HomeUI extends HookConsumerWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - makeWebViewButton(context, model, - icon: SvgPicture.asset( - "assets/rsi.svg", - colorFilter: makeSvgColor(Colors.white), - height: 18, - ), - name: S.current.home_action_star_citizen_website_localization, - webTitle: S.current.home_action_star_citizen_website_localization, - webURL: "https://robertsspaceindustries.com", - info: S.current.home_action_info_roberts_space_industries_origin, - useLocalization: true, - width: width, - touchKey: "webLocalization_rsi"), + makeWebViewButton( + context, + model, + icon: SvgPicture.asset("assets/rsi.svg", colorFilter: makeSvgColor(Colors.white), height: 18), + name: S.current.home_action_star_citizen_website_localization, + webTitle: S.current.home_action_star_citizen_website_localization, + webURL: "https://robertsspaceindustries.com", + info: S.current.home_action_info_roberts_space_industries_origin, + useLocalization: true, + width: width, + touchKey: "webLocalization_rsi", + ), const SizedBox(height: 12), - makeWebViewButton(context, model, - icon: Row( - children: [ - SvgPicture.asset( - "assets/uex.svg", - height: 18, - ), - const SizedBox(width: 12), - ], - ), - name: S.current.home_action_uex_localization, - webTitle: S.current.home_action_uex_localization, - webURL: "https://uexcorp.space/", - info: S.current.home_action_info_mining_refining_trade_calculator, - useLocalization: true, - width: width, - touchKey: "webLocalization_uex"), + makeWebViewButton( + context, + model, + icon: Row(children: [SvgPicture.asset("assets/uex.svg", height: 18), const SizedBox(width: 12)]), + name: S.current.home_action_uex_localization, + webTitle: S.current.home_action_uex_localization, + webURL: "https://uexcorp.space/", + info: S.current.home_action_info_mining_refining_trade_calculator, + useLocalization: true, + width: width, + touchKey: "webLocalization_uex", + ), const SizedBox(height: 12), - makeWebViewButton(context, model, - icon: Row( - children: [ - Image.asset( - "assets/dps.png", - height: 20, - ), - const SizedBox(width: 12), - ], - ), - name: S.current.home_action_dps_calculator_localization, - webTitle: S.current.home_action_dps_calculator_localization, - webURL: "https://www.erkul.games/live/calculator", - info: S.current.home_action_info_ship_upgrade_damage_value_query, - useLocalization: true, - width: width, - touchKey: "webLocalization_dps"), + makeWebViewButton( + context, + model, + icon: Row(children: [Image.asset("assets/dps.png", height: 20), const SizedBox(width: 12)]), + name: S.current.home_action_dps_calculator_localization, + webTitle: S.current.home_action_dps_calculator_localization, + webURL: "https://www.erkul.games/live/calculator", + info: S.current.home_action_info_ship_upgrade_damage_value_query, + useLocalization: true, + width: width, + touchKey: "webLocalization_dps", + ), const SizedBox(height: 12), Text(S.current.home_action_external_browser_extension), const SizedBox(height: 12), @@ -281,7 +239,8 @@ class HomeUI extends HookConsumerWidget { child: const FaIcon(FontAwesomeIcons.chrome, size: 18), onPressed: () { launchUrlString( - "https://chrome.google.com/webstore/detail/gocnjckojmledijgmadmacoikibcggja?authuser=0&hl=zh-CN"); + "https://chrome.google.com/webstore/detail/gocnjckojmledijgmadmacoikibcggja?authuser=0&hl=zh-CN", + ); }, ), const SizedBox(width: 12), @@ -289,15 +248,18 @@ class HomeUI extends HookConsumerWidget { child: const FaIcon(FontAwesomeIcons.edge, size: 18), onPressed: () { launchUrlString( - "https://microsoftedge.microsoft.com/addons/detail/lipbbcckldklpdcpfagicipecaacikgi"); + "https://microsoftedge.microsoft.com/addons/detail/lipbbcckldklpdcpfagicipecaacikgi", + ); }, ), const SizedBox(width: 12), Button( child: const FaIcon(FontAwesomeIcons.firefoxBrowser, size: 18), onPressed: () { - launchUrlString("https://addons.mozilla.org/zh-CN/firefox/" - "addon/%E6%98%9F%E9%99%85%E5%85%AC%E6%B0%91%E7%9B%92%E5%AD%90%E6%B5%8F%E8%A7%88%E5%99%A8%E6%8B%93%E5%B1%95/"); + launchUrlString( + "https://addons.mozilla.org/zh-CN/firefox/" + "addon/%E6%98%9F%E9%99%85%E5%85%AC%E6%B0%91%E7%9B%92%E5%AD%90%E6%B5%8F%E8%A7%88%E5%99%A8%E6%8B%93%E5%B1%95/", + ); }, ), const SizedBox(width: 12), @@ -308,7 +270,7 @@ class HomeUI extends HookConsumerWidget { }, ), ], - ) + ), ], ), ), @@ -319,13 +281,14 @@ class HomeUI extends HookConsumerWidget { ), if (homeState.webLocalizationVersionsData == null) Positioned.fill( - child: Container( - decoration: - BoxDecoration(color: Colors.black.withValues(alpha: .3), borderRadius: BorderRadius.circular(12)), - child: const Center( - child: ProgressRing(), + child: Container( + decoration: BoxDecoration( + color: Colors.black.withValues(alpha: .3), + borderRadius: BorderRadius.circular(12), + ), + child: const Center(child: ProgressRing()), ), - )) + ), ], ); } @@ -334,95 +297,113 @@ class HomeUI extends HookConsumerWidget { return ScrollConfiguration( behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false), child: Container( - width: 316, - height: 386, - decoration: BoxDecoration(color: Colors.white.withValues(alpha: .1), borderRadius: BorderRadius.circular(12)), - child: SingleChildScrollView( - child: Column( - children: [ - SizedBox( - height: 190, - width: 316, - child: Tilt( - shadowConfig: const ShadowConfig(maxIntensity: .3), - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(12), - topRight: Radius.circular(12), - ), - child: homeState.rssVideoItems == null - ? Container( - decoration: BoxDecoration(color: Colors.white.withValues(alpha: .1)), - child: makeLoading(context), - ) - : Swiper( - itemCount: getMinNumber([homeState.rssVideoItems?.length ?? 0, 6]), - itemBuilder: (context, index) { - final item = homeState.rssVideoItems![index]; - return GestureDetector( - onTap: () { - if (item.link != null) { - launchUrlString(item.link!); - } - }, - child: CacheNetImage( - url: model.getRssImage(item), - fit: BoxFit.cover, - ), - ); - }, - autoplay: true, - ), - )), - const SizedBox(height: 1), - if (homeState.rssTextItems == null) - makeLoading(context) - else - ListView.builder( - physics: const NeverScrollableScrollPhysics(), - shrinkWrap: true, - itemBuilder: (BuildContext context, int index) { - final item = homeState.rssTextItems![index]; - return Tilt( - shadowConfig: const ShadowConfig(maxIntensity: .3), - borderRadius: BorderRadius.circular(12), - child: GestureDetector( + width: 316, + height: 386, + decoration: BoxDecoration(color: Colors.white.withValues(alpha: .1), borderRadius: BorderRadius.circular(12)), + child: SingleChildScrollView( + child: Column( + children: [ + SizedBox( + height: 210, + width: 316, + child: homeState.citizenNewsData == null + ? Container( + decoration: BoxDecoration(color: Colors.white.withValues(alpha: .1)), + child: makeLoading(context), + ) + : HoverSwiper( + itemCount: getMinNumber([homeState.citizenNewsData?.videos.length ?? 0, 6]), + itemBuilder: (context, index) { + final item = homeState.citizenNewsData?.videos[index]; + return GestureDetector( onTap: () { - if (item.link != null) { - launchUrlString(item.link!); - } + launchUrlString(item?.link ?? ""); }, - child: Padding( - padding: const EdgeInsets.only(left: 12, right: 12, top: 4, bottom: 4), - child: Row( - children: [ - getRssIcon(item.link ?? ""), - const SizedBox(width: 6), - Expanded( - child: Text( - model.handleTitle(item.title), - textAlign: TextAlign.start, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: 12.2), + child: Column( + children: [ + CacheNetImage( + url: model.getRssImage(item?.description), + fit: BoxFit.cover, + height: 180, + ), + SizedBox( + height: 30, + child: Container( + width: double.infinity, + decoration: BoxDecoration(color: Colors.black), + padding: EdgeInsets.symmetric(horizontal: 6), + child: Center( + child: Row( + children: [ + Expanded( + child: Text( + "${index + 1}. ${(item?.title ?? "").replaceAll("【寰宇周刊】", "")}", + style: TextStyle(fontSize: 12), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), ), ), - const SizedBox(width: 12), - Icon( - FluentIcons.chevron_right, - size: 12, - color: Colors.white.withValues(alpha: .4), - ) - ], - ), + ), + ], ), - )); - }, - itemCount: homeState.rssTextItems?.length, - ), - const SizedBox(height: 12), - ], - ), - )), + ); + }, + paginationActiveSize: 8.0, + controlSize: 24, + controlPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 0), + autoplayDelay: 5000, + ), + ), + const SizedBox(height: 1), + if (homeState.citizenNewsData?.articles == null) + makeLoading(context) + else + ListView.builder( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemBuilder: (BuildContext context, int index) { + final item = homeState.citizenNewsData!.articles[index]; + return Tilt( + shadowConfig: const ShadowConfig(maxIntensity: .3), + borderRadius: BorderRadius.circular(12), + child: GestureDetector( + onTap: () { + launchUrlString(item.link); + }, + child: Padding( + padding: const EdgeInsets.only(left: 12, right: 12, top: 4, bottom: 4), + child: Row( + children: [ + getRssIcon(item.link), + const SizedBox(width: 6), + Expanded( + child: Text( + model.handleTitle(item.title), + textAlign: TextAlign.start, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle(fontSize: 12.2), + ), + ), + const SizedBox(width: 12), + Icon(FluentIcons.chevron_right, size: 12, color: Colors.white.withValues(alpha: .4)), + ], + ), + ), + ), + ); + }, + itemCount: homeState.citizenNewsData?.articles.length ?? 0, + ), + const SizedBox(height: 12), + ], + ), + ), + ), ); } @@ -432,11 +413,7 @@ class HomeUI extends HookConsumerWidget { } if (url.startsWith("https://www.bilibili.com")) { - return const FaIcon( - FontAwesomeIcons.bilibili, - size: 14, - color: Color.fromRGBO(0, 161, 214, 1), - ); + return const FaIcon(FontAwesomeIcons.bilibili, size: 14, color: Color.fromRGBO(0, 161, 214, 1)); } return const FaIcon(FontAwesomeIcons.rss, size: 14); @@ -444,97 +421,104 @@ class HomeUI extends HookConsumerWidget { Widget makeIndexActionLists(BuildContext context, HomeUIModel model, HomeUIModelState homeState, WidgetRef ref) { final items = [ - _HomeItemData("game_doctor", S.current.home_action_one_click_diagnosis, - S.current.home_action_info_one_click_diagnosis_star_citizen, FluentIcons.auto_deploy_settings), - _HomeItemData("localization", S.current.home_action_localization_management, - S.current.home_action_info_quick_install_localization_resources, FluentIcons.locale_language), - _HomeItemData("performance", S.current.home_action_performance_optimization, - S.current.home_action_info_engine_config_optimization, FluentIcons.process_meta_task), + _HomeItemData( + "game_doctor", + S.current.home_action_one_click_diagnosis, + S.current.home_action_info_one_click_diagnosis_star_citizen, + FluentIcons.auto_deploy_settings, + ), + _HomeItemData( + "localization", + S.current.home_action_localization_management, + S.current.home_action_info_quick_install_localization_resources, + FluentIcons.locale_language, + ), + _HomeItemData( + "performance", + S.current.home_action_performance_optimization, + S.current.home_action_info_engine_config_optimization, + FluentIcons.process_meta_task, + ), ]; return Padding( padding: const EdgeInsets.all(24), child: AlignedGridView.count( - crossAxisCount: 3, - mainAxisSpacing: 12, - crossAxisSpacing: 12, - itemCount: items.length, - shrinkWrap: true, - itemBuilder: (context, index) { - final item = items.elementAt(index); - return HoverButton( - onPressed: () => _onMenuTap(context, item.key, homeState, ref), - builder: (BuildContext context, Set states) { - return Container( - width: 300, - height: 120, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: states.isHovered - ? FluentTheme.of(context).cardColor.withValues(alpha: .1) - : FluentTheme.of(context).cardColor, - ), - child: Padding( - padding: const EdgeInsets.all(12), - child: Row( - children: [ - Container( - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: .2), borderRadius: BorderRadius.circular(1000)), - child: Padding( - padding: const EdgeInsets.all(12), - child: Icon( - item.icon, - size: 24, - ), - ), + crossAxisCount: 3, + mainAxisSpacing: 12, + crossAxisSpacing: 12, + itemCount: items.length, + shrinkWrap: true, + itemBuilder: (context, index) { + final item = items.elementAt(index); + return HoverButton( + onPressed: () => _onMenuTap(context, item.key, homeState, ref), + builder: (BuildContext context, Set states) { + return Container( + width: 300, + height: 120, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: states.isHovered + ? FluentTheme.of(context).cardColor.withValues(alpha: .1) + : FluentTheme.of(context).cardColor, + ), + child: Padding( + padding: const EdgeInsets.all(12), + child: Row( + children: [ + Container( + decoration: BoxDecoration( + color: Colors.white.withValues(alpha: .2), + borderRadius: BorderRadius.circular(1000), ), - const SizedBox(width: 24), - Expanded( - child: Column( + child: Padding(padding: const EdgeInsets.all(12), child: Icon(item.icon, size: 24)), + ), + const SizedBox(width: 24), + Expanded( + child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - item.name, - style: const TextStyle(fontSize: 18), - ), + Text(item.name, style: const TextStyle(fontSize: 18)), const SizedBox(height: 4), Text( item.infoString, style: TextStyle(fontSize: 14, color: Colors.white.withValues(alpha: .6)), ), ], - )), - if (item.key == "localization" && homeState.localizationUpdateInfo != null) - Container( - padding: const EdgeInsets.only(top: 3, bottom: 3, left: 8, right: 8), - decoration: BoxDecoration(color: Colors.red, borderRadius: BorderRadius.circular(12)), - child: Text(homeState.localizationUpdateInfo?.key ?? " "), - ), - const SizedBox(width: 12), - const Icon( - FluentIcons.chevron_right, - size: 16, - ) - ], - ), + ), + ), + if (item.key == "localization" && homeState.localizationUpdateInfo != null) + Container( + padding: const EdgeInsets.only(top: 3, bottom: 3, left: 8, right: 8), + decoration: BoxDecoration(color: Colors.red, borderRadius: BorderRadius.circular(12)), + child: Text(homeState.localizationUpdateInfo?.key ?? " "), + ), + const SizedBox(width: 12), + const Icon(FluentIcons.chevron_right, size: 16), + ], ), - ); - }, - ); - }), + ), + ); + }, + ); + }, + ), ); } - Widget makeWebViewButton(BuildContext context, HomeUIModel model, - {required Widget icon, - required String name, - required String webTitle, - required String webURL, - required bool useLocalization, - required double width, - String? info, - String? touchKey}) { + Widget makeWebViewButton( + BuildContext context, + HomeUIModel model, { + required Widget icon, + required String name, + required String webTitle, + required String webURL, + required bool useLocalization, + required double width, + String? info, + String? touchKey, + }) { return Tilt( shadowConfig: const ShadowConfig(maxIntensity: .3), borderRadius: BorderRadius.circular(12), @@ -547,9 +531,7 @@ class HomeUI extends HookConsumerWidget { }, child: Container( width: width, - decoration: BoxDecoration( - color: FluentTheme.of(context).cardColor, - ), + decoration: BoxDecoration(color: FluentTheme.of(context).cardColor), child: Padding( padding: const EdgeInsets.all(8.0), child: Row( @@ -561,10 +543,7 @@ class HomeUI extends HookConsumerWidget { Row( children: [ icon, - Text( - name, - style: const TextStyle(fontSize: 14), - ), + Text(name, style: const TextStyle(fontSize: 14)), ], ), if (info != null) @@ -575,16 +554,12 @@ class HomeUI extends HookConsumerWidget { maxLines: 1, style: TextStyle(fontSize: 12, color: Colors.white.withValues(alpha: .6)), ), - ) + ), ], ), ), const SizedBox(width: 12), - Icon( - FluentIcons.chevron_right, - size: 14, - color: Colors.white.withValues(alpha: .6), - ) + Icon(FluentIcons.chevron_right, size: 14, color: Colors.white.withValues(alpha: .6)), ], ), ), @@ -598,7 +573,7 @@ class HomeUI extends HookConsumerWidget { "Platform": S.current.home_action_rsi_status_platform, "Persistent Universe": S.current.home_action_rsi_status_persistent_universe, "Electronic Access": S.current.home_action_rsi_status_electronic_access, - "Arena Commander": S.current.home_action_rsi_status_arena_commander + "Arena Commander": S.current.home_action_rsi_status_arena_commander, }; return Tilt( @@ -607,52 +582,48 @@ class HomeUI extends HookConsumerWidget { child: GestureDetector( onTap: () { model.goWebView( - context, S.current.home_action_rsi_status_rsi_server_status, "https://status.robertsspaceindustries.com/", - useLocalization: true); + context, + S.current.home_action_rsi_status_rsi_server_status, + "https://status.robertsspaceindustries.com/", + useLocalization: true, + ); }, child: Container( width: width, - decoration: BoxDecoration( - color: FluentTheme.of(context).cardColor, - ), + decoration: BoxDecoration(color: FluentTheme.of(context).cardColor), child: Padding( padding: const EdgeInsets.all(12), - child: Column(children: [ - if (homeState.scServerStatus == null) - makeLoading(context, width: 20) - else - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(S.current.home_action_rsi_status_status), - for (final item in homeState.scServerStatus ?? []) - Row( - children: [ - SizedBox( - height: 14, - child: Center( - child: Icon( - FontAwesomeIcons.solidCircle, - color: model.isRSIServerStatusOK(item) ? Colors.green : Colors.red, - size: 12, + child: Column( + children: [ + if (homeState.scServerStatus == null) + makeLoading(context, width: 20) + else + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(S.current.home_action_rsi_status_status), + for (final item in homeState.scServerStatus ?? []) + Row( + children: [ + SizedBox( + height: 14, + child: Center( + child: Icon( + FontAwesomeIcons.solidCircle, + color: model.isRSIServerStatusOK(item) ? Colors.green : Colors.red, + size: 12, + ), ), ), - ), - const SizedBox(width: 5), - Text( - "${statusCnName[item["name"]] ?? item["name"]}", - style: const TextStyle(fontSize: 13), - ), - ], - ), - Icon( - FluentIcons.chevron_right, - size: 12, - color: Colors.white.withValues(alpha: .4), - ) - ], - ) - ]), + const SizedBox(width: 5), + Text("${statusCnName[item["name"]] ?? item["name"]}", style: const TextStyle(fontSize: 13)), + ], + ), + Icon(FluentIcons.chevron_right, size: 12, color: Colors.white.withValues(alpha: .4)), + ], + ), + ], + ), ), ), ), @@ -666,65 +637,55 @@ class HomeUI extends HookConsumerWidget { child: GestureDetector( onTap: () => _onTapFestival(context), child: Container( - width: width + 24, - decoration: BoxDecoration(color: FluentTheme.of(context).cardColor), - child: Padding( - padding: const EdgeInsets.only(left: 12, right: 12, top: 8, bottom: 8), - child: (homeState.countdownFestivalListData == null) - ? SizedBox( - width: width, - height: 62, - child: const Center( - child: ProgressRing(), - ), - ) - : SizedBox( - width: width, - height: 62, - child: Swiper( - itemCount: getMinNumber([homeState.countdownFestivalListData!.length, 6]), - autoplay: true, - autoplayDelay: 5000, - itemBuilder: (context, index) { - final item = homeState.countdownFestivalListData![index]; - return Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - if (item.icon != null && item.icon != "") ...[ - ClipRRect( - borderRadius: BorderRadius.circular(1000), - child: Image.asset( - "assets/countdown/${item.icon}", - width: 48, - height: 48, - fit: BoxFit.cover, - ), + width: width + 24, + decoration: BoxDecoration(color: FluentTheme.of(context).cardColor), + child: Padding( + padding: const EdgeInsets.only(left: 12, right: 12, top: 8, bottom: 8), + child: (homeState.countdownFestivalListData == null) + ? SizedBox( + width: width, + height: 62, + child: const Center(child: ProgressRing()), + ) + : SizedBox( + width: width, + height: 62, + child: Swiper( + itemCount: getMinNumber([homeState.countdownFestivalListData!.length, 6]), + autoplay: true, + autoplayDelay: 5000, + itemBuilder: (context, index) { + final item = homeState.countdownFestivalListData![index]; + return Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + if (item.icon != null && item.icon != "") ...[ + ClipRRect( + borderRadius: BorderRadius.circular(1000), + child: Image.asset( + "assets/countdown/${item.icon}", + width: 48, + height: 48, + fit: BoxFit.cover, ), - ], - Column( - children: [ - Text( - item.name ?? "", - style: const TextStyle(fontSize: 15), - ), - const SizedBox(height: 3), - CountdownTimeText( - targetTime: DateTime.fromMillisecondsSinceEpoch(item.time ?? 0), - ), - ], ), - const SizedBox(width: 12), - Icon( - FluentIcons.chevron_right, - size: 14, - color: Colors.white.withValues(alpha: .6), - ) ], - ); - }, - ), + Column( + children: [ + Text(item.name ?? "", style: const TextStyle(fontSize: 15)), + const SizedBox(height: 3), + CountdownTimeText(targetTime: DateTime.fromMillisecondsSinceEpoch(item.time ?? 0)), + ], + ), + const SizedBox(width: 12), + Icon(FluentIcons.chevron_right, size: 14, color: Colors.white.withValues(alpha: .6)), + ], + ); + }, ), - )), + ), + ), + ), ), ); } @@ -736,13 +697,14 @@ class HomeUI extends HookConsumerWidget { return; case "doc": showDialog( - context: context, - builder: (context) { - return HomeMdContentDialogUI( - title: homeState.appPlacardData?.title ?? S.current.home_announcement_details, - url: homeState.appPlacardData?.link, - ); - }); + context: context, + builder: (context) { + return HomeMdContentDialogUI( + title: homeState.appPlacardData?.title ?? S.current.home_announcement_details, + url: homeState.appPlacardData?.link, + ); + }, + ); return; } } @@ -804,15 +766,22 @@ class HomeUI extends HookConsumerWidget { } void _checkAndGoInputMethod( - BuildContext context, HomeUIModelState homeState, HomeUIModel model, WidgetRef ref) async { + BuildContext context, + HomeUIModelState homeState, + HomeUIModel model, + WidgetRef ref, + ) async { final localizationState = ref.read(localizationUIModelProvider); if (localizationState.communityInputMethodLanguageData == null) { showToast(context, S.current.input_method_feature_maintenance); return; } if (localizationState.installedCommunityInputMethodSupportVersion == null) { - final userOK = await showConfirmDialogs(context, S.current.input_method_community_input_method_not_installed, - Text(S.current.input_method_install_community_input_method_prompt)); + final userOK = await showConfirmDialogs( + context, + S.current.input_method_community_input_method_not_installed, + Text(S.current.input_method_install_community_input_method_prompt), + ); if (userOK) { if (!context.mounted) return; () async { @@ -837,10 +806,7 @@ class HomeUI extends HookConsumerWidget { } Future _goInputMethod(BuildContext context, HomeUIModel model) async { - await showDialog( - context: context, - builder: (context) => const InputMethodDialogUI(), - ); + await showDialog(context: context, builder: (context) => const InputMethodDialogUI()); } } diff --git a/lib/ui/home/home_ui_model.dart b/lib/ui/home/home_ui_model.dart index b3d7075..e0a4d83 100644 --- a/lib/ui/home/home_ui_model.dart +++ b/lib/ui/home/home_ui_model.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'package:dart_rss/domain/rss_item.dart'; import 'package:desktop_webview_window/desktop_webview_window.dart'; import 'package:fluent_ui/fluent_ui.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -10,7 +9,7 @@ import 'package:hive_ce/hive.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:starcitizen_doctor/api/analytics.dart'; import 'package:starcitizen_doctor/api/api.dart'; -import 'package:starcitizen_doctor/api/rss.dart'; +import 'package:starcitizen_doctor/api/news_api.dart'; import 'package:starcitizen_doctor/common/conf/conf.dart'; import 'package:starcitizen_doctor/common/conf/url_conf.dart'; import 'package:starcitizen_doctor/common/helper/log_helper.dart'; @@ -23,12 +22,12 @@ import 'package:starcitizen_doctor/common/utils/log.dart'; import 'package:starcitizen_doctor/common/utils/provider.dart'; import 'package:starcitizen_doctor/data/app_placard_data.dart'; import 'package:starcitizen_doctor/data/app_web_localization_versions_data.dart'; +import 'package:starcitizen_doctor/data/citizen_news_data.dart'; import 'package:starcitizen_doctor/data/countdown_festival_item_data.dart'; import 'package:starcitizen_doctor/ui/home/dialogs/home_game_login_dialog_ui.dart'; import 'package:url_launcher/url_launcher_string.dart'; import 'package:html/parser.dart' as html; import 'package:html/dom.dart' as html_dom; - import '../webview/webview.dart'; import 'localization/localization_ui_model.dart'; @@ -46,8 +45,7 @@ abstract class HomeUIModelState with _$HomeUIModelState { @Default([]) List scInstallPaths, AppWebLocalizationVersionsData? webLocalizationVersionsData, @Default("") String lastScreenInfo, - List? rssVideoItems, - List? rssTextItems, + CitizenNewsData? citizenNewsData, MapEntry? localizationUpdateInfo, List? scServerStatus, List? countdownFestivalListData, @@ -115,8 +113,9 @@ class HomeUIModel extends _$HomeUIModel { } } - String getRssImage(RssItem item) { - final h = html.parse(item.description ?? ""); + String getRssImage(String? htmlString) { + if (htmlString == null) return ""; + final h = html.parse(htmlString); if (h.body == null) return ""; for (var node in h.body!.nodes) { if (node is html_dom.Element) { @@ -135,14 +134,15 @@ class HomeUIModel extends _$HomeUIModel { return title; } - // ignore: avoid_build_context_in_providers - Future goWebView(BuildContext context, - String title, - String url, { - bool useLocalization = false, - bool loginMode = false, - RsiLoginCallback? rsiLoginCallback, - }) async { + Future goWebView( + // ignore: avoid_build_context_in_providers + BuildContext context, + String title, + String url, { + bool useLocalization = false, + bool loginMode = false, + RsiLoginCallback? rsiLoginCallback, + }) async { if (useLocalization) { const tipVersion = 2; final box = await Hive.openBox("app_conf"); @@ -153,10 +153,7 @@ class HomeUIModel extends _$HomeUIModel { context, S.current.home_action_title_star_citizen_website_localization, Text(S.current.home_action_info_web_localization_plugin_disclaimer, style: const TextStyle(fontSize: 16)), - constraints: BoxConstraints(maxWidth: MediaQuery - .of(context) - .size - .width * .6), + constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .6), ); if (!ok) { if (loginMode) { @@ -227,7 +224,8 @@ class HomeUIModel extends _$HomeUIModel { final box = await Hive.openBox("app_conf"); final version = box.get("close_placard", defaultValue: ""); if (r.enable == true) { - if (r.alwaysShow != true && version == r.version) {} else { + if (r.alwaysShow != true && version == r.version) { + } else { state = state.copyWith(appPlacardData: r); } } @@ -241,7 +239,7 @@ class HomeUIModel extends _$HomeUIModel { countdownFestivalListData: _fixFestivalCountdownListDateTime(countdownFestivalListData), ); _updateSCServerStatus(); - _loadRRS(); + _loadNews(); } catch (e) { dPrint(e); } @@ -255,10 +253,10 @@ class HomeUIModel extends _$HomeUIModel { return list.map((item) { if (item.time == null) return item; - final itemDateTime = DateTime.fromMillisecondsSinceEpoch(item.time! * 1000); + final itemDateTime = DateTime.fromMillisecondsSinceEpoch(item.time!); final itemDatePlusSeven = itemDateTime.add(const Duration(days: 7)); if (itemDatePlusSeven.isBefore(now)) { - final nextYearDate = DateTime( + final nextDate = DateTime( now.year + 1, itemDateTime.month, itemDateTime.day, @@ -266,7 +264,7 @@ class HomeUIModel extends _$HomeUIModel { itemDateTime.minute, itemDateTime.second, ); - final newTimestamp = (nextYearDate.millisecondsSinceEpoch / 1000).round(); + final newTimestamp = (nextDate.millisecondsSinceEpoch).round(); return CountdownFestivalItemData(name: item.name, time: newTimestamp, icon: item.icon); } @@ -284,23 +282,9 @@ class HomeUIModel extends _$HomeUIModel { } } - Future _loadRRS() async { - try { - final rssVideoItems = await RSSApi.getRssVideo(); - state = state.copyWith(rssVideoItems: rssVideoItems); - final rssTextItems = await RSSApi.getRssText(); - state = state.copyWith(rssTextItems: rssTextItems); - dPrint("RSS update Success !"); - } catch (e) { - dPrint("_loadRRS Error:$e"); - // 避免持续显示 loading - if (state.rssTextItems == null) { - state = state.copyWith(rssTextItems: []); - } - if (state.rssVideoItems == null) { - state = state.copyWith(rssVideoItems: []); - } - } + Future _loadNews() async { + final news = await NewsApi.getLatest(); + state = state.copyWith(citizenNewsData: news ?? CitizenNewsData()); } Future checkLocalizationUpdate({bool skipReload = false}) async { @@ -362,12 +346,14 @@ class HomeUIModel extends _$HomeUIModel { ref.read(localizationUIModelProvider.notifier).onChangeGameInstallPath(value); } - Future doLaunchGame(// ignore: avoid_build_context_in_providers - BuildContext context, - String launchExe, - List args, - String installPath, - String? processorAffinity,) async { + Future doLaunchGame( + // ignore: avoid_build_context_in_providers + BuildContext context, + String launchExe, + List args, + String installPath, + String? processorAffinity, + ) async { var runningMap = Map.from(state.isGameRunning); runningMap[installPath] = true; state = state.copyWith(isGameRunning: runningMap); diff --git a/lib/ui/home/home_ui_model.freezed.dart b/lib/ui/home/home_ui_model.freezed.dart index 0377b31..0606c37 100644 --- a/lib/ui/home/home_ui_model.freezed.dart +++ b/lib/ui/home/home_ui_model.freezed.dart @@ -14,7 +14,7 @@ T _$identity(T value) => value; /// @nodoc mixin _$HomeUIModelState { - AppPlacardData? get appPlacardData; bool get isFixing; String get isFixingString; String? get scInstalledPath; List get scInstallPaths; AppWebLocalizationVersionsData? get webLocalizationVersionsData; String get lastScreenInfo; List? get rssVideoItems; List? get rssTextItems; MapEntry? get localizationUpdateInfo; List? get scServerStatus; List? get countdownFestivalListData; Map get isGameRunning; + AppPlacardData? get appPlacardData; bool get isFixing; String get isFixingString; String? get scInstalledPath; List get scInstallPaths; AppWebLocalizationVersionsData? get webLocalizationVersionsData; String get lastScreenInfo; CitizenNewsData? get citizenNewsData; MapEntry? get localizationUpdateInfo; List? get scServerStatus; List? get countdownFestivalListData; Map get isGameRunning; /// Create a copy of HomeUIModelState /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -25,16 +25,16 @@ $HomeUIModelStateCopyWith get copyWith => _$HomeUIModelStateCo @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is HomeUIModelState&&(identical(other.appPlacardData, appPlacardData) || other.appPlacardData == appPlacardData)&&(identical(other.isFixing, isFixing) || other.isFixing == isFixing)&&(identical(other.isFixingString, isFixingString) || other.isFixingString == isFixingString)&&(identical(other.scInstalledPath, scInstalledPath) || other.scInstalledPath == scInstalledPath)&&const DeepCollectionEquality().equals(other.scInstallPaths, scInstallPaths)&&(identical(other.webLocalizationVersionsData, webLocalizationVersionsData) || other.webLocalizationVersionsData == webLocalizationVersionsData)&&(identical(other.lastScreenInfo, lastScreenInfo) || other.lastScreenInfo == lastScreenInfo)&&const DeepCollectionEquality().equals(other.rssVideoItems, rssVideoItems)&&const DeepCollectionEquality().equals(other.rssTextItems, rssTextItems)&&(identical(other.localizationUpdateInfo, localizationUpdateInfo) || other.localizationUpdateInfo == localizationUpdateInfo)&&const DeepCollectionEquality().equals(other.scServerStatus, scServerStatus)&&const DeepCollectionEquality().equals(other.countdownFestivalListData, countdownFestivalListData)&&const DeepCollectionEquality().equals(other.isGameRunning, isGameRunning)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is HomeUIModelState&&(identical(other.appPlacardData, appPlacardData) || other.appPlacardData == appPlacardData)&&(identical(other.isFixing, isFixing) || other.isFixing == isFixing)&&(identical(other.isFixingString, isFixingString) || other.isFixingString == isFixingString)&&(identical(other.scInstalledPath, scInstalledPath) || other.scInstalledPath == scInstalledPath)&&const DeepCollectionEquality().equals(other.scInstallPaths, scInstallPaths)&&(identical(other.webLocalizationVersionsData, webLocalizationVersionsData) || other.webLocalizationVersionsData == webLocalizationVersionsData)&&(identical(other.lastScreenInfo, lastScreenInfo) || other.lastScreenInfo == lastScreenInfo)&&(identical(other.citizenNewsData, citizenNewsData) || other.citizenNewsData == citizenNewsData)&&(identical(other.localizationUpdateInfo, localizationUpdateInfo) || other.localizationUpdateInfo == localizationUpdateInfo)&&const DeepCollectionEquality().equals(other.scServerStatus, scServerStatus)&&const DeepCollectionEquality().equals(other.countdownFestivalListData, countdownFestivalListData)&&const DeepCollectionEquality().equals(other.isGameRunning, isGameRunning)); } @override -int get hashCode => Object.hash(runtimeType,appPlacardData,isFixing,isFixingString,scInstalledPath,const DeepCollectionEquality().hash(scInstallPaths),webLocalizationVersionsData,lastScreenInfo,const DeepCollectionEquality().hash(rssVideoItems),const DeepCollectionEquality().hash(rssTextItems),localizationUpdateInfo,const DeepCollectionEquality().hash(scServerStatus),const DeepCollectionEquality().hash(countdownFestivalListData),const DeepCollectionEquality().hash(isGameRunning)); +int get hashCode => Object.hash(runtimeType,appPlacardData,isFixing,isFixingString,scInstalledPath,const DeepCollectionEquality().hash(scInstallPaths),webLocalizationVersionsData,lastScreenInfo,citizenNewsData,localizationUpdateInfo,const DeepCollectionEquality().hash(scServerStatus),const DeepCollectionEquality().hash(countdownFestivalListData),const DeepCollectionEquality().hash(isGameRunning)); @override String toString() { - return 'HomeUIModelState(appPlacardData: $appPlacardData, isFixing: $isFixing, isFixingString: $isFixingString, scInstalledPath: $scInstalledPath, scInstallPaths: $scInstallPaths, webLocalizationVersionsData: $webLocalizationVersionsData, lastScreenInfo: $lastScreenInfo, rssVideoItems: $rssVideoItems, rssTextItems: $rssTextItems, localizationUpdateInfo: $localizationUpdateInfo, scServerStatus: $scServerStatus, countdownFestivalListData: $countdownFestivalListData, isGameRunning: $isGameRunning)'; + return 'HomeUIModelState(appPlacardData: $appPlacardData, isFixing: $isFixing, isFixingString: $isFixingString, scInstalledPath: $scInstalledPath, scInstallPaths: $scInstallPaths, webLocalizationVersionsData: $webLocalizationVersionsData, lastScreenInfo: $lastScreenInfo, citizenNewsData: $citizenNewsData, localizationUpdateInfo: $localizationUpdateInfo, scServerStatus: $scServerStatus, countdownFestivalListData: $countdownFestivalListData, isGameRunning: $isGameRunning)'; } @@ -45,11 +45,11 @@ abstract mixin class $HomeUIModelStateCopyWith<$Res> { factory $HomeUIModelStateCopyWith(HomeUIModelState value, $Res Function(HomeUIModelState) _then) = _$HomeUIModelStateCopyWithImpl; @useResult $Res call({ - AppPlacardData? appPlacardData, bool isFixing, String isFixingString, String? scInstalledPath, List scInstallPaths, AppWebLocalizationVersionsData? webLocalizationVersionsData, String lastScreenInfo, List? rssVideoItems, List? rssTextItems, MapEntry? localizationUpdateInfo, List? scServerStatus, List? countdownFestivalListData, Map isGameRunning + AppPlacardData? appPlacardData, bool isFixing, String isFixingString, String? scInstalledPath, List scInstallPaths, AppWebLocalizationVersionsData? webLocalizationVersionsData, String lastScreenInfo, CitizenNewsData? citizenNewsData, MapEntry? localizationUpdateInfo, List? scServerStatus, List? countdownFestivalListData, Map isGameRunning }); - +$CitizenNewsDataCopyWith<$Res>? get citizenNewsData; } /// @nodoc @@ -62,7 +62,7 @@ class _$HomeUIModelStateCopyWithImpl<$Res> /// Create a copy of HomeUIModelState /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? appPlacardData = freezed,Object? isFixing = null,Object? isFixingString = null,Object? scInstalledPath = freezed,Object? scInstallPaths = null,Object? webLocalizationVersionsData = freezed,Object? lastScreenInfo = null,Object? rssVideoItems = freezed,Object? rssTextItems = freezed,Object? localizationUpdateInfo = freezed,Object? scServerStatus = freezed,Object? countdownFestivalListData = freezed,Object? isGameRunning = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? appPlacardData = freezed,Object? isFixing = null,Object? isFixingString = null,Object? scInstalledPath = freezed,Object? scInstallPaths = null,Object? webLocalizationVersionsData = freezed,Object? lastScreenInfo = null,Object? citizenNewsData = freezed,Object? localizationUpdateInfo = freezed,Object? scServerStatus = freezed,Object? countdownFestivalListData = freezed,Object? isGameRunning = null,}) { return _then(_self.copyWith( appPlacardData: freezed == appPlacardData ? _self.appPlacardData : appPlacardData // ignore: cast_nullable_to_non_nullable as AppPlacardData?,isFixing: null == isFixing ? _self.isFixing : isFixing // ignore: cast_nullable_to_non_nullable @@ -71,16 +71,27 @@ as String,scInstalledPath: freezed == scInstalledPath ? _self.scInstalledPath : as String?,scInstallPaths: null == scInstallPaths ? _self.scInstallPaths : scInstallPaths // ignore: cast_nullable_to_non_nullable as List,webLocalizationVersionsData: freezed == webLocalizationVersionsData ? _self.webLocalizationVersionsData : webLocalizationVersionsData // ignore: cast_nullable_to_non_nullable as AppWebLocalizationVersionsData?,lastScreenInfo: null == lastScreenInfo ? _self.lastScreenInfo : lastScreenInfo // ignore: cast_nullable_to_non_nullable -as String,rssVideoItems: freezed == rssVideoItems ? _self.rssVideoItems : rssVideoItems // ignore: cast_nullable_to_non_nullable -as List?,rssTextItems: freezed == rssTextItems ? _self.rssTextItems : rssTextItems // ignore: cast_nullable_to_non_nullable -as List?,localizationUpdateInfo: freezed == localizationUpdateInfo ? _self.localizationUpdateInfo : localizationUpdateInfo // ignore: cast_nullable_to_non_nullable +as String,citizenNewsData: freezed == citizenNewsData ? _self.citizenNewsData : citizenNewsData // ignore: cast_nullable_to_non_nullable +as CitizenNewsData?,localizationUpdateInfo: freezed == localizationUpdateInfo ? _self.localizationUpdateInfo : localizationUpdateInfo // ignore: cast_nullable_to_non_nullable as MapEntry?,scServerStatus: freezed == scServerStatus ? _self.scServerStatus : scServerStatus // ignore: cast_nullable_to_non_nullable as List?,countdownFestivalListData: freezed == countdownFestivalListData ? _self.countdownFestivalListData : countdownFestivalListData // ignore: cast_nullable_to_non_nullable as List?,isGameRunning: null == isGameRunning ? _self.isGameRunning : isGameRunning // ignore: cast_nullable_to_non_nullable as Map, )); } +/// Create a copy of HomeUIModelState +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$CitizenNewsDataCopyWith<$Res>? get citizenNewsData { + if (_self.citizenNewsData == null) { + return null; + } + return $CitizenNewsDataCopyWith<$Res>(_self.citizenNewsData!, (value) { + return _then(_self.copyWith(citizenNewsData: value)); + }); +} } @@ -162,10 +173,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( AppPlacardData? appPlacardData, bool isFixing, String isFixingString, String? scInstalledPath, List scInstallPaths, AppWebLocalizationVersionsData? webLocalizationVersionsData, String lastScreenInfo, List? rssVideoItems, List? rssTextItems, MapEntry? localizationUpdateInfo, List? scServerStatus, List? countdownFestivalListData, Map isGameRunning)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( AppPlacardData? appPlacardData, bool isFixing, String isFixingString, String? scInstalledPath, List scInstallPaths, AppWebLocalizationVersionsData? webLocalizationVersionsData, String lastScreenInfo, CitizenNewsData? citizenNewsData, MapEntry? localizationUpdateInfo, List? scServerStatus, List? countdownFestivalListData, Map isGameRunning)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _HomeUIModelState() when $default != null: -return $default(_that.appPlacardData,_that.isFixing,_that.isFixingString,_that.scInstalledPath,_that.scInstallPaths,_that.webLocalizationVersionsData,_that.lastScreenInfo,_that.rssVideoItems,_that.rssTextItems,_that.localizationUpdateInfo,_that.scServerStatus,_that.countdownFestivalListData,_that.isGameRunning);case _: +return $default(_that.appPlacardData,_that.isFixing,_that.isFixingString,_that.scInstalledPath,_that.scInstallPaths,_that.webLocalizationVersionsData,_that.lastScreenInfo,_that.citizenNewsData,_that.localizationUpdateInfo,_that.scServerStatus,_that.countdownFestivalListData,_that.isGameRunning);case _: return orElse(); } @@ -183,10 +194,10 @@ return $default(_that.appPlacardData,_that.isFixing,_that.isFixingString,_that.s /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( AppPlacardData? appPlacardData, bool isFixing, String isFixingString, String? scInstalledPath, List scInstallPaths, AppWebLocalizationVersionsData? webLocalizationVersionsData, String lastScreenInfo, List? rssVideoItems, List? rssTextItems, MapEntry? localizationUpdateInfo, List? scServerStatus, List? countdownFestivalListData, Map isGameRunning) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( AppPlacardData? appPlacardData, bool isFixing, String isFixingString, String? scInstalledPath, List scInstallPaths, AppWebLocalizationVersionsData? webLocalizationVersionsData, String lastScreenInfo, CitizenNewsData? citizenNewsData, MapEntry? localizationUpdateInfo, List? scServerStatus, List? countdownFestivalListData, Map isGameRunning) $default,) {final _that = this; switch (_that) { case _HomeUIModelState(): -return $default(_that.appPlacardData,_that.isFixing,_that.isFixingString,_that.scInstalledPath,_that.scInstallPaths,_that.webLocalizationVersionsData,_that.lastScreenInfo,_that.rssVideoItems,_that.rssTextItems,_that.localizationUpdateInfo,_that.scServerStatus,_that.countdownFestivalListData,_that.isGameRunning);case _: +return $default(_that.appPlacardData,_that.isFixing,_that.isFixingString,_that.scInstalledPath,_that.scInstallPaths,_that.webLocalizationVersionsData,_that.lastScreenInfo,_that.citizenNewsData,_that.localizationUpdateInfo,_that.scServerStatus,_that.countdownFestivalListData,_that.isGameRunning);case _: throw StateError('Unexpected subclass'); } @@ -203,10 +214,10 @@ return $default(_that.appPlacardData,_that.isFixing,_that.isFixingString,_that.s /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( AppPlacardData? appPlacardData, bool isFixing, String isFixingString, String? scInstalledPath, List scInstallPaths, AppWebLocalizationVersionsData? webLocalizationVersionsData, String lastScreenInfo, List? rssVideoItems, List? rssTextItems, MapEntry? localizationUpdateInfo, List? scServerStatus, List? countdownFestivalListData, Map isGameRunning)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( AppPlacardData? appPlacardData, bool isFixing, String isFixingString, String? scInstalledPath, List scInstallPaths, AppWebLocalizationVersionsData? webLocalizationVersionsData, String lastScreenInfo, CitizenNewsData? citizenNewsData, MapEntry? localizationUpdateInfo, List? scServerStatus, List? countdownFestivalListData, Map isGameRunning)? $default,) {final _that = this; switch (_that) { case _HomeUIModelState() when $default != null: -return $default(_that.appPlacardData,_that.isFixing,_that.isFixingString,_that.scInstalledPath,_that.scInstallPaths,_that.webLocalizationVersionsData,_that.lastScreenInfo,_that.rssVideoItems,_that.rssTextItems,_that.localizationUpdateInfo,_that.scServerStatus,_that.countdownFestivalListData,_that.isGameRunning);case _: +return $default(_that.appPlacardData,_that.isFixing,_that.isFixingString,_that.scInstalledPath,_that.scInstallPaths,_that.webLocalizationVersionsData,_that.lastScreenInfo,_that.citizenNewsData,_that.localizationUpdateInfo,_that.scServerStatus,_that.countdownFestivalListData,_that.isGameRunning);case _: return null; } @@ -218,7 +229,7 @@ return $default(_that.appPlacardData,_that.isFixing,_that.isFixingString,_that.s class _HomeUIModelState implements HomeUIModelState { - _HomeUIModelState({this.appPlacardData, this.isFixing = false, this.isFixingString = "", this.scInstalledPath, final List scInstallPaths = const [], this.webLocalizationVersionsData, this.lastScreenInfo = "", final List? rssVideoItems, final List? rssTextItems, this.localizationUpdateInfo, final List? scServerStatus, final List? countdownFestivalListData, final Map isGameRunning = const {}}): _scInstallPaths = scInstallPaths,_rssVideoItems = rssVideoItems,_rssTextItems = rssTextItems,_scServerStatus = scServerStatus,_countdownFestivalListData = countdownFestivalListData,_isGameRunning = isGameRunning; + _HomeUIModelState({this.appPlacardData, this.isFixing = false, this.isFixingString = "", this.scInstalledPath, final List scInstallPaths = const [], this.webLocalizationVersionsData, this.lastScreenInfo = "", this.citizenNewsData, this.localizationUpdateInfo, final List? scServerStatus, final List? countdownFestivalListData, final Map isGameRunning = const {}}): _scInstallPaths = scInstallPaths,_scServerStatus = scServerStatus,_countdownFestivalListData = countdownFestivalListData,_isGameRunning = isGameRunning; @override final AppPlacardData? appPlacardData; @@ -234,24 +245,7 @@ class _HomeUIModelState implements HomeUIModelState { @override final AppWebLocalizationVersionsData? webLocalizationVersionsData; @override@JsonKey() final String lastScreenInfo; - final List? _rssVideoItems; -@override List? get rssVideoItems { - final value = _rssVideoItems; - if (value == null) return null; - if (_rssVideoItems is EqualUnmodifiableListView) return _rssVideoItems; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(value); -} - - final List? _rssTextItems; -@override List? get rssTextItems { - final value = _rssTextItems; - if (value == null) return null; - if (_rssTextItems is EqualUnmodifiableListView) return _rssTextItems; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(value); -} - +@override final CitizenNewsData? citizenNewsData; @override final MapEntry? localizationUpdateInfo; final List? _scServerStatus; @override List? get scServerStatus { @@ -289,16 +283,16 @@ _$HomeUIModelStateCopyWith<_HomeUIModelState> get copyWith => __$HomeUIModelStat @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _HomeUIModelState&&(identical(other.appPlacardData, appPlacardData) || other.appPlacardData == appPlacardData)&&(identical(other.isFixing, isFixing) || other.isFixing == isFixing)&&(identical(other.isFixingString, isFixingString) || other.isFixingString == isFixingString)&&(identical(other.scInstalledPath, scInstalledPath) || other.scInstalledPath == scInstalledPath)&&const DeepCollectionEquality().equals(other._scInstallPaths, _scInstallPaths)&&(identical(other.webLocalizationVersionsData, webLocalizationVersionsData) || other.webLocalizationVersionsData == webLocalizationVersionsData)&&(identical(other.lastScreenInfo, lastScreenInfo) || other.lastScreenInfo == lastScreenInfo)&&const DeepCollectionEquality().equals(other._rssVideoItems, _rssVideoItems)&&const DeepCollectionEquality().equals(other._rssTextItems, _rssTextItems)&&(identical(other.localizationUpdateInfo, localizationUpdateInfo) || other.localizationUpdateInfo == localizationUpdateInfo)&&const DeepCollectionEquality().equals(other._scServerStatus, _scServerStatus)&&const DeepCollectionEquality().equals(other._countdownFestivalListData, _countdownFestivalListData)&&const DeepCollectionEquality().equals(other._isGameRunning, _isGameRunning)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _HomeUIModelState&&(identical(other.appPlacardData, appPlacardData) || other.appPlacardData == appPlacardData)&&(identical(other.isFixing, isFixing) || other.isFixing == isFixing)&&(identical(other.isFixingString, isFixingString) || other.isFixingString == isFixingString)&&(identical(other.scInstalledPath, scInstalledPath) || other.scInstalledPath == scInstalledPath)&&const DeepCollectionEquality().equals(other._scInstallPaths, _scInstallPaths)&&(identical(other.webLocalizationVersionsData, webLocalizationVersionsData) || other.webLocalizationVersionsData == webLocalizationVersionsData)&&(identical(other.lastScreenInfo, lastScreenInfo) || other.lastScreenInfo == lastScreenInfo)&&(identical(other.citizenNewsData, citizenNewsData) || other.citizenNewsData == citizenNewsData)&&(identical(other.localizationUpdateInfo, localizationUpdateInfo) || other.localizationUpdateInfo == localizationUpdateInfo)&&const DeepCollectionEquality().equals(other._scServerStatus, _scServerStatus)&&const DeepCollectionEquality().equals(other._countdownFestivalListData, _countdownFestivalListData)&&const DeepCollectionEquality().equals(other._isGameRunning, _isGameRunning)); } @override -int get hashCode => Object.hash(runtimeType,appPlacardData,isFixing,isFixingString,scInstalledPath,const DeepCollectionEquality().hash(_scInstallPaths),webLocalizationVersionsData,lastScreenInfo,const DeepCollectionEquality().hash(_rssVideoItems),const DeepCollectionEquality().hash(_rssTextItems),localizationUpdateInfo,const DeepCollectionEquality().hash(_scServerStatus),const DeepCollectionEquality().hash(_countdownFestivalListData),const DeepCollectionEquality().hash(_isGameRunning)); +int get hashCode => Object.hash(runtimeType,appPlacardData,isFixing,isFixingString,scInstalledPath,const DeepCollectionEquality().hash(_scInstallPaths),webLocalizationVersionsData,lastScreenInfo,citizenNewsData,localizationUpdateInfo,const DeepCollectionEquality().hash(_scServerStatus),const DeepCollectionEquality().hash(_countdownFestivalListData),const DeepCollectionEquality().hash(_isGameRunning)); @override String toString() { - return 'HomeUIModelState(appPlacardData: $appPlacardData, isFixing: $isFixing, isFixingString: $isFixingString, scInstalledPath: $scInstalledPath, scInstallPaths: $scInstallPaths, webLocalizationVersionsData: $webLocalizationVersionsData, lastScreenInfo: $lastScreenInfo, rssVideoItems: $rssVideoItems, rssTextItems: $rssTextItems, localizationUpdateInfo: $localizationUpdateInfo, scServerStatus: $scServerStatus, countdownFestivalListData: $countdownFestivalListData, isGameRunning: $isGameRunning)'; + return 'HomeUIModelState(appPlacardData: $appPlacardData, isFixing: $isFixing, isFixingString: $isFixingString, scInstalledPath: $scInstalledPath, scInstallPaths: $scInstallPaths, webLocalizationVersionsData: $webLocalizationVersionsData, lastScreenInfo: $lastScreenInfo, citizenNewsData: $citizenNewsData, localizationUpdateInfo: $localizationUpdateInfo, scServerStatus: $scServerStatus, countdownFestivalListData: $countdownFestivalListData, isGameRunning: $isGameRunning)'; } @@ -309,11 +303,11 @@ abstract mixin class _$HomeUIModelStateCopyWith<$Res> implements $HomeUIModelSta factory _$HomeUIModelStateCopyWith(_HomeUIModelState value, $Res Function(_HomeUIModelState) _then) = __$HomeUIModelStateCopyWithImpl; @override @useResult $Res call({ - AppPlacardData? appPlacardData, bool isFixing, String isFixingString, String? scInstalledPath, List scInstallPaths, AppWebLocalizationVersionsData? webLocalizationVersionsData, String lastScreenInfo, List? rssVideoItems, List? rssTextItems, MapEntry? localizationUpdateInfo, List? scServerStatus, List? countdownFestivalListData, Map isGameRunning + AppPlacardData? appPlacardData, bool isFixing, String isFixingString, String? scInstalledPath, List scInstallPaths, AppWebLocalizationVersionsData? webLocalizationVersionsData, String lastScreenInfo, CitizenNewsData? citizenNewsData, MapEntry? localizationUpdateInfo, List? scServerStatus, List? countdownFestivalListData, Map isGameRunning }); - +@override $CitizenNewsDataCopyWith<$Res>? get citizenNewsData; } /// @nodoc @@ -326,7 +320,7 @@ class __$HomeUIModelStateCopyWithImpl<$Res> /// Create a copy of HomeUIModelState /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? appPlacardData = freezed,Object? isFixing = null,Object? isFixingString = null,Object? scInstalledPath = freezed,Object? scInstallPaths = null,Object? webLocalizationVersionsData = freezed,Object? lastScreenInfo = null,Object? rssVideoItems = freezed,Object? rssTextItems = freezed,Object? localizationUpdateInfo = freezed,Object? scServerStatus = freezed,Object? countdownFestivalListData = freezed,Object? isGameRunning = null,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? appPlacardData = freezed,Object? isFixing = null,Object? isFixingString = null,Object? scInstalledPath = freezed,Object? scInstallPaths = null,Object? webLocalizationVersionsData = freezed,Object? lastScreenInfo = null,Object? citizenNewsData = freezed,Object? localizationUpdateInfo = freezed,Object? scServerStatus = freezed,Object? countdownFestivalListData = freezed,Object? isGameRunning = null,}) { return _then(_HomeUIModelState( appPlacardData: freezed == appPlacardData ? _self.appPlacardData : appPlacardData // ignore: cast_nullable_to_non_nullable as AppPlacardData?,isFixing: null == isFixing ? _self.isFixing : isFixing // ignore: cast_nullable_to_non_nullable @@ -335,9 +329,8 @@ as String,scInstalledPath: freezed == scInstalledPath ? _self.scInstalledPath : as String?,scInstallPaths: null == scInstallPaths ? _self._scInstallPaths : scInstallPaths // ignore: cast_nullable_to_non_nullable as List,webLocalizationVersionsData: freezed == webLocalizationVersionsData ? _self.webLocalizationVersionsData : webLocalizationVersionsData // ignore: cast_nullable_to_non_nullable as AppWebLocalizationVersionsData?,lastScreenInfo: null == lastScreenInfo ? _self.lastScreenInfo : lastScreenInfo // ignore: cast_nullable_to_non_nullable -as String,rssVideoItems: freezed == rssVideoItems ? _self._rssVideoItems : rssVideoItems // ignore: cast_nullable_to_non_nullable -as List?,rssTextItems: freezed == rssTextItems ? _self._rssTextItems : rssTextItems // ignore: cast_nullable_to_non_nullable -as List?,localizationUpdateInfo: freezed == localizationUpdateInfo ? _self.localizationUpdateInfo : localizationUpdateInfo // ignore: cast_nullable_to_non_nullable +as String,citizenNewsData: freezed == citizenNewsData ? _self.citizenNewsData : citizenNewsData // ignore: cast_nullable_to_non_nullable +as CitizenNewsData?,localizationUpdateInfo: freezed == localizationUpdateInfo ? _self.localizationUpdateInfo : localizationUpdateInfo // ignore: cast_nullable_to_non_nullable as MapEntry?,scServerStatus: freezed == scServerStatus ? _self._scServerStatus : scServerStatus // ignore: cast_nullable_to_non_nullable as List?,countdownFestivalListData: freezed == countdownFestivalListData ? _self._countdownFestivalListData : countdownFestivalListData // ignore: cast_nullable_to_non_nullable as List?,isGameRunning: null == isGameRunning ? _self._isGameRunning : isGameRunning // ignore: cast_nullable_to_non_nullable @@ -345,7 +338,19 @@ as Map, )); } +/// Create a copy of HomeUIModelState +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$CitizenNewsDataCopyWith<$Res>? get citizenNewsData { + if (_self.citizenNewsData == null) { + return null; + } + return $CitizenNewsDataCopyWith<$Res>(_self.citizenNewsData!, (value) { + return _then(_self.copyWith(citizenNewsData: value)); + }); +} } // dart format on diff --git a/lib/ui/home/home_ui_model.g.dart b/lib/ui/home/home_ui_model.g.dart index b42f032..00590f0 100644 --- a/lib/ui/home/home_ui_model.g.dart +++ b/lib/ui/home/home_ui_model.g.dart @@ -41,7 +41,7 @@ final class HomeUIModelProvider } } -String _$homeUIModelHash() => r'84eb149f999237410a7e0a95b74bd5729c2726d4'; +String _$homeUIModelHash() => r'9dc8191f358c2d8e21ed931b3755e08ce394558e'; abstract class _$HomeUIModel extends $Notifier { HomeUIModelState build(); diff --git a/lib/ui/settings/settings_ui_model.g.dart b/lib/ui/settings/settings_ui_model.g.dart index 4fd2682..82f5ddf 100644 --- a/lib/ui/settings/settings_ui_model.g.dart +++ b/lib/ui/settings/settings_ui_model.g.dart @@ -41,7 +41,7 @@ final class SettingsUIModelProvider } } -String _$settingsUIModelHash() => r'd19104d924f018a9230548d0372692fc344adacd'; +String _$settingsUIModelHash() => r'5c08c56bf5464ef44bee8edb8c18c08d4217f135'; abstract class _$SettingsUIModel extends $Notifier { SettingsUIState build(); diff --git a/lib/widgets/src/flow_number_text.dart b/lib/widgets/src/flow_number_text.dart index 6086044..0e5db15 100644 --- a/lib/widgets/src/flow_number_text.dart +++ b/lib/widgets/src/flow_number_text.dart @@ -10,12 +10,13 @@ class FlowNumberText extends HookConsumerWidget { final TextStyle? style; final Curve curve; - FlowNumberText( - {super.key, - required this.targetValue, - this.duration = const Duration(seconds: 1), - this.style, - this.curve = Curves.bounceOut}); + FlowNumberText({ + super.key, + required this.targetValue, + this.duration = const Duration(seconds: 1), + this.style, + this.curve = Curves.bounceOut, + }); final _formatter = NumberFormat.decimalPattern(); @@ -46,9 +47,6 @@ class FlowNumberText extends HookConsumerWidget { return timer.value?.cancel; }, [targetValue]); - return Text( - _formatter.format(value.value.toInt()), - style: style, - ); + return Text(_formatter.format(value.value.toInt()), style: style); } } diff --git a/lib/widgets/src/grid_item_animator.dart b/lib/widgets/src/grid_item_animator.dart index bb4c2c6..f0ffcad 100644 --- a/lib/widgets/src/grid_item_animator.dart +++ b/lib/widgets/src/grid_item_animator.dart @@ -20,19 +20,14 @@ class GridItemAnimator extends HookWidget { @override Widget build(BuildContext context) { // 创建动画控制器 - final animationController = useAnimationController( - duration: duration, - ); + final animationController = useAnimationController(duration: duration); // 创建不透明度动画 final opacityAnimation = useAnimation( Tween( begin: 0.0, // 开始时完全透明 end: 1.0, // 结束时完全不透明 - ).animate(CurvedAnimation( - parent: animationController, - curve: Curves.easeOut, - )), + ).animate(CurvedAnimation(parent: animationController, curve: Curves.easeOut)), ); // 创建位移动画 @@ -40,23 +35,24 @@ class GridItemAnimator extends HookWidget { Tween( begin: 1.0, // 开始位置 end: 0.0, // 结束位置 - ).animate(CurvedAnimation( - parent: animationController, - curve: Curves.easeOutCubic, - )), + ).animate(CurvedAnimation(parent: animationController, curve: Curves.easeOutCubic)), ); // 组件挂载后启动动画 useEffect(() { // 根据索引计算延迟时间,实现逐个条目入场 final delay = delayPerItem * index; + bool cancelled = false; Future.delayed(delay, () { - if (animationController.status != AnimationStatus.completed) { + if (!cancelled && animationController.status != AnimationStatus.completed) { animationController.forward(); } }); - return null; + + return () { + cancelled = true; + }; }, const []); // 应用动画效果 diff --git a/lib/widgets/src/swiper.dart b/lib/widgets/src/swiper.dart new file mode 100644 index 0000000..911fbb3 --- /dev/null +++ b/lib/widgets/src/swiper.dart @@ -0,0 +1,115 @@ +import 'package:card_swiper/card_swiper.dart'; +import 'package:fluent_ui/fluent_ui.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_tilt/flutter_tilt.dart'; + +class HoverSwiper extends HookWidget { + const HoverSwiper({ + super.key, + required this.itemCount, + required this.itemBuilder, + this.autoplayDelay = 3000, + this.paginationActiveSize = 8.0, + this.controlSize = 24, + this.controlPadding = const EdgeInsets.symmetric(horizontal: 8, vertical: 0), + }); + + final int itemCount; + final IndexedWidgetBuilder itemBuilder; + final double paginationActiveSize; + final double controlSize; + final EdgeInsets controlPadding; + final int autoplayDelay; + + @override + Widget build(BuildContext context) { + final isHovered = useState(false); + final controller = useMemoized(() => SwiperController()); + + useEffect(() { + return controller.dispose; + }, [controller]); + + return MouseRegion( + onEnter: (_) { + isHovered.value = true; + controller.stopAutoplay(); + }, + onExit: (_) { + isHovered.value = false; + controller.startAutoplay(); + }, + child: Stack( + children: [ + Tilt( + shadowConfig: const ShadowConfig(maxIntensity: .3), + borderRadius: const BorderRadius.only(topLeft: Radius.circular(12), topRight: Radius.circular(12)), + child: Swiper( + controller: controller, + itemCount: itemCount, + itemBuilder: itemBuilder, + autoplay: true, + autoplayDelay: autoplayDelay, + ), + ), + // Left control button + _buildControlButton( + isHovered: isHovered.value, + position: 'left', + onTap: () => controller.previous(), + icon: FluentIcons.chevron_left, + ), + // Right control button + _buildControlButton( + isHovered: isHovered.value, + position: 'right', + onTap: () => controller.next(), + icon: FluentIcons.chevron_right, + ), + ], + ), + ); + } + + /// 构建控制按钮(左/右箭头) + Widget _buildControlButton({ + required bool isHovered, + required String position, + required VoidCallback onTap, + required IconData icon, + }) { + final isLeft = position == 'left'; + return Positioned( + left: isLeft ? 0 : null, + right: isLeft ? null : 0, + top: 0, + bottom: 0, + child: AnimatedOpacity( + opacity: isHovered ? 1.0 : 0.0, + duration: const Duration(milliseconds: 200), + child: IgnorePointer( + ignoring: !isHovered, + child: Center( + child: Padding( + padding: controlPadding, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: onTap, + child: Container( + padding: const EdgeInsets.all(2), + decoration: BoxDecoration( + color: Colors.black.withValues(alpha: .3), + borderRadius: BorderRadius.circular(4), + ), + child: Icon(icon, size: controlSize, color: Colors.white.withValues(alpha: .8)), + ), + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/widgets/widgets.dart b/lib/widgets/widgets.dart index 9b3f4b2..5d3b935 100644 --- a/lib/widgets/widgets.dart +++ b/lib/widgets/widgets.dart @@ -17,53 +17,42 @@ export 'src/cache_image.dart'; export 'src/countdown_time_text.dart'; export 'src/cache_svg_image.dart'; export 'src/grid_item_animator.dart'; +export 'src/swiper.dart'; export '../common/utils/async.dart'; export '../common/utils/base_utils.dart'; export 'package:starcitizen_doctor/generated/l10n.dart'; -Widget makeLoading( - BuildContext context, { - double? width, -}) { +Widget makeLoading(BuildContext context, {double? width}) { width ??= 30; return Center( - child: SizedBox( - width: width, - height: width, - child: const ProgressRing(), - ), + child: SizedBox(width: width, height: width, child: const ProgressRing()), ); } -Widget makeDefaultPage(BuildContext context, - {Widget? titleRow, - List? actions, - Widget? content, - bool automaticallyImplyLeading = true, - String title = "", - bool useBodyContainer = false}) { +Widget makeDefaultPage( + BuildContext context, { + Widget? titleRow, + List? actions, + Widget? content, + bool automaticallyImplyLeading = true, + String title = "", + bool useBodyContainer = false, +}) { return NavigationView( appBar: NavigationAppBar( - automaticallyImplyLeading: automaticallyImplyLeading, - title: DragToMoveArea( - child: titleRow ?? - Column( - children: [ - Expanded( - child: Row( - children: [ - Text(title), - ], - ), - ) - ], - ), - ), - actions: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [...?actions, const WindowButtons()], - )), + automaticallyImplyLeading: automaticallyImplyLeading, + title: DragToMoveArea( + child: + titleRow ?? + Column( + children: [ + Expanded(child: Row(children: [Text(title)])), + ], + ), + ), + actions: Row(mainAxisAlignment: MainAxisAlignment.end, children: [...?actions, const WindowButtons()]), + ), content: useBodyContainer ? Container( decoration: BoxDecoration( @@ -85,55 +74,63 @@ class WindowButtons extends StatelessWidget { return SizedBox( width: 138, height: 50, - child: WindowCaption( - brightness: theme.brightness, - backgroundColor: Colors.transparent, - ), + child: WindowCaption(brightness: theme.brightness, backgroundColor: Colors.transparent), ); } } List makeMarkdownView(String description, {String? attachmentsUrl}) { - return MarkdownGenerator().buildWidgets(description, - config: MarkdownConfig(configs: [ - LinkConfig(onTap: (url) { - if (url.startsWith("/") && attachmentsUrl != null) { - url = "$attachmentsUrl/$url"; - } - launchUrlString(url); - }), - ImgConfig(builder: (String url, Map attributes) { - if (url.startsWith("/") && attachmentsUrl != null) { - url = "$attachmentsUrl/$url"; - } - return ExtendedImage.network( - url, - loadStateChanged: (ExtendedImageState state) { - switch (state.extendedImageLoadState) { - case LoadState.loading: - return Center( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - children: [ - const ProgressRing(), - const SizedBox(height: 12), - Text(S.current.app_common_loading_images) - ], + return MarkdownGenerator().buildWidgets( + description, + config: MarkdownConfig( + configs: [ + LinkConfig( + onTap: (url) { + if (url.startsWith("/") && attachmentsUrl != null) { + url = "$attachmentsUrl/$url"; + } + launchUrlString(url); + }, + ), + ImgConfig( + builder: (String url, Map attributes) { + if (url.startsWith("/") && attachmentsUrl != null) { + url = "$attachmentsUrl/$url"; + } + return ExtendedImage.network( + url, + loadStateChanged: (ExtendedImageState state) { + switch (state.extendedImageLoadState) { + case LoadState.loading: + return Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + const ProgressRing(), + const SizedBox(height: 12), + Text(S.current.app_common_loading_images), + ], + ), ), - ), - ); - case LoadState.completed: - return ExtendedRawImage( - image: state.extendedImageInfo?.image, - ); - case LoadState.failed: - return Text("Loading Image error $url"); - } - }, - ); - }) - ])); + ); + case LoadState.completed: + return ExtendedRawImage(image: state.extendedImageInfo?.image); + case LoadState.failed: + return Button( + onPressed: () { + launchUrlString(url); + }, + child: Text("Loading Image error $url"), + ); + } + }, + ); + }, + ), + ], + ), + ); } ColorFilter makeSvgColor(Color color) { @@ -142,22 +139,20 @@ ColorFilter makeSvgColor(Color color) { CustomTransitionPage myPageBuilder(BuildContext context, GoRouterState state, Widget child) { return CustomTransitionPage( - child: child, - transitionDuration: const Duration(milliseconds: 150), - reverseTransitionDuration: const Duration(milliseconds: 150), - transitionsBuilder: - (BuildContext context, Animation animation, Animation secondaryAnimation, Widget child) { - return SlideTransition( - position: Tween( - begin: const Offset(0.0, 1.0), - end: const Offset(0.0, 0.0), - ).animate(CurvedAnimation( - parent: animation, - curve: Curves.easeInOut, - )), - child: child, - ); - }); + child: child, + transitionDuration: const Duration(milliseconds: 150), + reverseTransitionDuration: const Duration(milliseconds: 150), + transitionsBuilder: + (BuildContext context, Animation animation, Animation secondaryAnimation, Widget child) { + return SlideTransition( + position: Tween( + begin: const Offset(0.0, 1.0), + end: const Offset(0.0, 0.0), + ).animate(CurvedAnimation(parent: animation, curve: Curves.easeInOut)), + child: child, + ); + }, + ); } class LoadingWidget extends HookConsumerWidget { @@ -192,9 +187,7 @@ class LoadingWidget extends HookConsumerWidget { onPressed: () { _loadData(dataState, errorMsg); }, - child: Center( - child: Text(errorMsg.value), - ), + child: Center(child: Text(errorMsg.value)), ); } if (dataState.value == null && data == null) return makeLoading(context); diff --git a/pubspec.lock b/pubspec.lock index 7f2aecb..f7695c7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -306,14 +306,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0+7.7.0" - dart_rss: - dependency: "direct main" - description: - name: dart_rss - sha256: "73539d4b7153b47beef8b51763ca55dcb6fc0bb412b29e0f5e74e93fabfd1ac6" - url: "https://pub.dev" - source: hosted - version: "3.0.3" dart_style: dependency: transitive description: @@ -1610,7 +1602,7 @@ packages: source: hosted version: "1.1.0" xml: - dependency: "direct main" + dependency: transitive description: name: xml sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" diff --git a/pubspec.yaml b/pubspec.yaml index 9a9a4fb..45b7d81 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -47,9 +47,7 @@ dependencies: freezed_annotation: ^3.1.0 meta: ^1.16.0 hexcolor: ^3.0.1 - dart_rss: ^3.0.3 html: ^0.15.6 - xml: ^6.6.1 fixnum: ^1.1.1 rust_builder: path: rust_builder