Compare commits
33 Commits
3.1.0+116
...
1def3e1895
| Author | SHA1 | Date | |
|---|---|---|---|
| 1def3e1895 | |||
| 550c74e544 | |||
| a39565f012 | |||
| aa9755e6a7 | |||
| b25e8d661a | |||
| 4b253ac3ec | |||
| 5d1b875d3c | |||
| e2e103fa67 | |||
| 43c90da4e3 | |||
| fa210dd98f | |||
| 43d9ca92bf | |||
| 5e592c143f | |||
| 0c59816f26 | |||
| 19c2457895 | |||
| af8d87857e | |||
| d05f63a36a | |||
| e2dc520012 | |||
| cff9c15e31 | |||
| f00135c4bf | |||
| 30b8a6c30f | |||
| b9c4ee31b1 | |||
| 87870af866 | |||
| b83cb0fb0b | |||
| 7fd1fe34e5 | |||
| 1c18330891 | |||
| d320879ad0 | |||
| 950150e119 | |||
| 3c4a9767e1 | |||
| 5df2445f3f | |||
| 56543d7b4c | |||
| 4c6fea1242 | |||
| fff43de9e3 | |||
| b31a915544 |
@@ -12,7 +12,12 @@
|
|||||||
"package_name": "dev.solsynth.solian"
|
"package_name": "dev.solsynth.solian"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"oauth_client": [],
|
"oauth_client": [
|
||||||
|
{
|
||||||
|
"client_id": "961776991058-963m1qin2vtp8fv693b5fdrab5hmpl89.apps.googleusercontent.com",
|
||||||
|
"client_type": 3
|
||||||
|
}
|
||||||
|
],
|
||||||
"api_key": [
|
"api_key": [
|
||||||
{
|
{
|
||||||
"current_key": "AIzaSyDvFNudXYs29uDtcCv6pFR8h5tXBs90FYk"
|
"current_key": "AIzaSyDvFNudXYs29uDtcCv6pFR8h5tXBs90FYk"
|
||||||
@@ -20,7 +25,20 @@
|
|||||||
],
|
],
|
||||||
"services": {
|
"services": {
|
||||||
"appinvite_service": {
|
"appinvite_service": {
|
||||||
"other_platform_oauth_client": []
|
"other_platform_oauth_client": [
|
||||||
|
{
|
||||||
|
"client_id": "961776991058-963m1qin2vtp8fv693b5fdrab5hmpl89.apps.googleusercontent.com",
|
||||||
|
"client_type": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"client_id": "961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com",
|
||||||
|
"client_type": 2,
|
||||||
|
"ios_info": {
|
||||||
|
"bundle_id": "dev.solsynth.solian",
|
||||||
|
"app_store_id": "6499032345"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,6 +144,7 @@
|
|||||||
"other": "{} attachments"
|
"other": "{} attachments"
|
||||||
},
|
},
|
||||||
"edited": "Edited",
|
"edited": "Edited",
|
||||||
|
"editedAt": "Edited at {}",
|
||||||
"addVideo": "Add video",
|
"addVideo": "Add video",
|
||||||
"addPhoto": "Add photo",
|
"addPhoto": "Add photo",
|
||||||
"addAudio": "Add audio",
|
"addAudio": "Add audio",
|
||||||
@@ -761,5 +762,25 @@
|
|||||||
"publisher": "Publisher",
|
"publisher": "Publisher",
|
||||||
"publisherHint": "Enter the publisher name",
|
"publisherHint": "Enter the publisher name",
|
||||||
"publisherCannotBeEmpty": "Publisher cannot be empty",
|
"publisherCannotBeEmpty": "Publisher cannot be empty",
|
||||||
"operationFailed": "Operation failed: {}"
|
"operationFailed": "Operation failed: {}",
|
||||||
|
"stickerMarketplace": "Sticker Marketplace",
|
||||||
|
"stickerPackAdded": "Sticker pack added to your collection",
|
||||||
|
"stickerPackRemoved": "Sticker pack removed from your collection",
|
||||||
|
"addPack": "Add Pack",
|
||||||
|
"removePack": "Remove Pack",
|
||||||
|
"browseAndAddStickers": "Browse and add sticker packs",
|
||||||
|
"stickerPack": "Sticker Pack",
|
||||||
|
"postCategoryTechnology": "Technology",
|
||||||
|
"postCategoryTravel": "Travel",
|
||||||
|
"postCategoryFood": "Food",
|
||||||
|
"postCategoryHealth": "Health",
|
||||||
|
"postCategoryScience": "Science",
|
||||||
|
"postCategorySports": "Sports",
|
||||||
|
"postCategoryFinance": "Finance",
|
||||||
|
"postCategoryLife": "Life",
|
||||||
|
"postCategoryArt": "Art",
|
||||||
|
"postCategoryStudy": "Study",
|
||||||
|
"postCategoryGaming": "Gaming",
|
||||||
|
"postCategoryProgramming": "Programming",
|
||||||
|
"postCategoryMusic": "Music"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,6 +120,7 @@
|
|||||||
"other": "{}个附件"
|
"other": "{}个附件"
|
||||||
},
|
},
|
||||||
"edited": "已编辑",
|
"edited": "已编辑",
|
||||||
|
"editedAt": "编辑于 {}",
|
||||||
"addVideo": "添加视频",
|
"addVideo": "添加视频",
|
||||||
"addPhoto": "添加照片",
|
"addPhoto": "添加照片",
|
||||||
"addFile": "添加文件",
|
"addFile": "添加文件",
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"flutter":{"platforms":{"android":{"default":{"projectId":"solian-0x001","appId":"1:961776991058:android:a8d3f7995b0b8e86f4188b","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"solian-0x001","appId":"1:961776991058:ios:727229d368cc47e1f4188b","uploadDebugSymbols":false,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"macos":{"default":{"projectId":"solian-0x001","appId":"1:961776991058:ios:727229d368cc47e1f4188b","uploadDebugSymbols":false,"fileOutput":"macos/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options.dart":{"projectId":"solian-0x001","configurations":{"android":"1:961776991058:android:a8d3f7995b0b8e86f4188b","ios":"1:961776991058:ios:727229d368cc47e1f4188b","macos":"1:961776991058:ios:727229d368cc47e1f4188b","web":"1:961776991058:web:b91d12f2892a5609f4188b","windows":"1:961776991058:web:3a912c0eb14028e5f4188b"}}}}}}
|
{"flutter":{"platforms":{"android":{"default":{"projectId":"solian-0x001","appId":"1:961776991058:android:a8d3f7995b0b8e86f4188b","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"solian-0x001","appId":"1:961776991058:ios:727229d368cc47e1f4188b","uploadDebugSymbols":false,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"macos":{"default":{"projectId":"solian-0x001","appId":"1:961776991058:ios:727229d368cc47e1f4188b","uploadDebugSymbols":false,"fileOutput":"macos/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options.dart":{"projectId":"solian-0x001","configurations":{"android":"1:961776991058:android:a8d3f7995b0b8e86f4188b","ios":"1:961776991058:ios:727229d368cc47e1f4188b","macos":"1:961776991058:ios:727229d368cc47e1f4188b","web":"1:961776991058:web:3a912c0eb14028e5f4188b","windows":"1:961776991058:web:3a912c0eb14028e5f4188b"}}}}}}
|
||||||
@@ -2,6 +2,12 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>CLIENT_ID</key>
|
||||||
|
<string>961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com</string>
|
||||||
|
<key>REVERSED_CLIENT_ID</key>
|
||||||
|
<string>com.googleusercontent.apps.961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig</string>
|
||||||
|
<key>ANDROID_CLIENT_ID</key>
|
||||||
|
<string>961776991058-r4iv9qoio57ul7utbfpgfrda2etvtch8.apps.googleusercontent.com</string>
|
||||||
<key>API_KEY</key>
|
<key>API_KEY</key>
|
||||||
<string>AIzaSyCzQIyiYKoYHTpGXhN-IjgMML8z797WVD8</string>
|
<string>AIzaSyCzQIyiYKoYHTpGXhN-IjgMML8z797WVD8</string>
|
||||||
<key>GCM_SENDER_ID</key>
|
<key>GCM_SENDER_ID</key>
|
||||||
|
|||||||
@@ -29,10 +29,7 @@ class DefaultFirebaseOptions {
|
|||||||
case TargetPlatform.windows:
|
case TargetPlatform.windows:
|
||||||
return windows;
|
return windows;
|
||||||
case TargetPlatform.linux:
|
case TargetPlatform.linux:
|
||||||
throw UnsupportedError(
|
return windows;
|
||||||
'DefaultFirebaseOptions have not been configured for linux - '
|
|
||||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
|
||||||
);
|
|
||||||
default:
|
default:
|
||||||
throw UnsupportedError(
|
throw UnsupportedError(
|
||||||
'DefaultFirebaseOptions are not supported for this platform.',
|
'DefaultFirebaseOptions are not supported for this platform.',
|
||||||
@@ -41,13 +38,13 @@ class DefaultFirebaseOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static const FirebaseOptions web = FirebaseOptions(
|
static const FirebaseOptions web = FirebaseOptions(
|
||||||
apiKey: 'AIzaSyBKfIQpTouj5rXnlzkEieSlbAzepm4mgJE',
|
apiKey: 'AIzaSyCfgOdlcr7h8x8j0WKx_S2wXnGkOopq320',
|
||||||
appId: '1:961776991058:web:b91d12f2892a5609f4188b',
|
appId: '1:961776991058:web:3a912c0eb14028e5f4188b',
|
||||||
messagingSenderId: '961776991058',
|
messagingSenderId: '961776991058',
|
||||||
projectId: 'solian-0x001',
|
projectId: 'solian-0x001',
|
||||||
authDomain: 'solian-0x001.firebaseapp.com',
|
authDomain: 'solian-0x001.firebaseapp.com',
|
||||||
storageBucket: 'solian-0x001.firebasestorage.app',
|
storageBucket: 'solian-0x001.firebasestorage.app',
|
||||||
measurementId: 'G-XY3HHKG0PE',
|
measurementId: 'G-JD1YEG9D6F',
|
||||||
);
|
);
|
||||||
|
|
||||||
static const FirebaseOptions android = FirebaseOptions(
|
static const FirebaseOptions android = FirebaseOptions(
|
||||||
@@ -64,6 +61,10 @@ class DefaultFirebaseOptions {
|
|||||||
messagingSenderId: '961776991058',
|
messagingSenderId: '961776991058',
|
||||||
projectId: 'solian-0x001',
|
projectId: 'solian-0x001',
|
||||||
storageBucket: 'solian-0x001.firebasestorage.app',
|
storageBucket: 'solian-0x001.firebasestorage.app',
|
||||||
|
androidClientId:
|
||||||
|
'961776991058-r4iv9qoio57ul7utbfpgfrda2etvtch8.apps.googleusercontent.com',
|
||||||
|
iosClientId:
|
||||||
|
'961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com',
|
||||||
iosBundleId: 'dev.solsynth.solian',
|
iosBundleId: 'dev.solsynth.solian',
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -73,6 +74,10 @@ class DefaultFirebaseOptions {
|
|||||||
messagingSenderId: '961776991058',
|
messagingSenderId: '961776991058',
|
||||||
projectId: 'solian-0x001',
|
projectId: 'solian-0x001',
|
||||||
storageBucket: 'solian-0x001.firebasestorage.app',
|
storageBucket: 'solian-0x001.firebasestorage.app',
|
||||||
|
androidClientId:
|
||||||
|
'961776991058-r4iv9qoio57ul7utbfpgfrda2etvtch8.apps.googleusercontent.com',
|
||||||
|
iosClientId:
|
||||||
|
'961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com',
|
||||||
iosBundleId: 'dev.solsynth.solian',
|
iosBundleId: 'dev.solsynth.solian',
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -85,5 +90,4 @@ class DefaultFirebaseOptions {
|
|||||||
storageBucket: 'solian-0x001.firebasestorage.app',
|
storageBucket: 'solian-0x001.firebasestorage.app',
|
||||||
measurementId: 'G-JD1YEG9D6F',
|
measurementId: 'G-JD1YEG9D6F',
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -30,6 +30,7 @@ import 'package:image_picker_platform_interface/image_picker_platform_interface.
|
|||||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
import 'package:flutter_langdetect/flutter_langdetect.dart' as langdetect;
|
import 'package:flutter_langdetect/flutter_langdetect.dart' as langdetect;
|
||||||
|
import 'package:island/services/update_service.dart';
|
||||||
|
|
||||||
@pragma('vm:entry-point')
|
@pragma('vm:entry-point')
|
||||||
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
||||||
@@ -53,10 +54,16 @@ void main() async {
|
|||||||
try {
|
try {
|
||||||
await langdetect.initLangDetect();
|
await langdetect.initLangDetect();
|
||||||
await EasyLocalization.ensureInitialized();
|
await EasyLocalization.ensureInitialized();
|
||||||
|
|
||||||
|
if (kIsWeb || !Platform.isLinux) {
|
||||||
await Firebase.initializeApp(
|
await Firebase.initializeApp(
|
||||||
options: DefaultFirebaseOptions.currentPlatform,
|
options: DefaultFirebaseOptions.currentPlatform,
|
||||||
);
|
);
|
||||||
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
|
FirebaseMessaging.onBackgroundMessage(
|
||||||
|
_firebaseMessagingBackgroundHandler,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
log("[SplashScreen] Firebase is ready!");
|
log("[SplashScreen] Firebase is ready!");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showErrorAlert(err);
|
showErrorAlert(err);
|
||||||
@@ -137,6 +144,15 @@ void main() async {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Schedule update check shortly after startup, when a context is available.
|
||||||
|
// Uses the global overlay key to obtain a BuildContext safely.
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
final ctx = globalOverlay.currentContext;
|
||||||
|
if (ctx != null) {
|
||||||
|
UpdateService().checkForUpdates(ctx);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Router will be provided through Riverpod
|
// Router will be provided through Riverpod
|
||||||
|
|||||||
@@ -3,25 +3,6 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
|||||||
part 'embed.freezed.dart';
|
part 'embed.freezed.dart';
|
||||||
part 'embed.g.dart';
|
part 'embed.g.dart';
|
||||||
|
|
||||||
@freezed
|
|
||||||
sealed class SnEmbedLink with _$SnEmbedLink {
|
|
||||||
const factory SnEmbedLink({
|
|
||||||
@JsonKey(name: 'Type') required String type,
|
|
||||||
@JsonKey(name: 'Url') required String url,
|
|
||||||
@JsonKey(name: 'Title') required String title,
|
|
||||||
@JsonKey(name: 'Description') required String? description,
|
|
||||||
@JsonKey(name: 'ImageUrl') required String? imageUrl,
|
|
||||||
@JsonKey(name: 'FaviconUrl') @Default("") String faviconUrl,
|
|
||||||
@JsonKey(name: 'SiteName') @Default("") String siteName,
|
|
||||||
@JsonKey(name: 'ContentType') required String? contentType,
|
|
||||||
@JsonKey(name: 'Author') required String? author,
|
|
||||||
@JsonKey(name: 'PublishedDate') required DateTime? publishedDate,
|
|
||||||
}) = _SnEmbedLink;
|
|
||||||
|
|
||||||
factory SnEmbedLink.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$SnEmbedLinkFromJson(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
sealed class SnScrappedLink with _$SnScrappedLink {
|
sealed class SnScrappedLink with _$SnScrappedLink {
|
||||||
const factory SnScrappedLink({
|
const factory SnScrappedLink({
|
||||||
|
|||||||
@@ -12,290 +12,6 @@ part of 'embed.dart';
|
|||||||
// dart format off
|
// dart format off
|
||||||
T _$identity<T>(T value) => value;
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
mixin _$SnEmbedLink {
|
|
||||||
|
|
||||||
@JsonKey(name: 'Type') String get type;@JsonKey(name: 'Url') String get url;@JsonKey(name: 'Title') String get title;@JsonKey(name: 'Description') String? get description;@JsonKey(name: 'ImageUrl') String? get imageUrl;@JsonKey(name: 'FaviconUrl') String get faviconUrl;@JsonKey(name: 'SiteName') String get siteName;@JsonKey(name: 'ContentType') String? get contentType;@JsonKey(name: 'Author') String? get author;@JsonKey(name: 'PublishedDate') DateTime? get publishedDate;
|
|
||||||
/// Create a copy of SnEmbedLink
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$SnEmbedLinkCopyWith<SnEmbedLink> get copyWith => _$SnEmbedLinkCopyWithImpl<SnEmbedLink>(this as SnEmbedLink, _$identity);
|
|
||||||
|
|
||||||
/// Serializes this SnEmbedLink to a JSON map.
|
|
||||||
Map<String, dynamic> toJson();
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnEmbedLink&&(identical(other.type, type) || other.type == type)&&(identical(other.url, url) || other.url == url)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.imageUrl, imageUrl) || other.imageUrl == imageUrl)&&(identical(other.faviconUrl, faviconUrl) || other.faviconUrl == faviconUrl)&&(identical(other.siteName, siteName) || other.siteName == siteName)&&(identical(other.contentType, contentType) || other.contentType == contentType)&&(identical(other.author, author) || other.author == author)&&(identical(other.publishedDate, publishedDate) || other.publishedDate == publishedDate));
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(runtimeType,type,url,title,description,imageUrl,faviconUrl,siteName,contentType,author,publishedDate);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'SnEmbedLink(type: $type, url: $url, title: $title, description: $description, imageUrl: $imageUrl, faviconUrl: $faviconUrl, siteName: $siteName, contentType: $contentType, author: $author, publishedDate: $publishedDate)';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract mixin class $SnEmbedLinkCopyWith<$Res> {
|
|
||||||
factory $SnEmbedLinkCopyWith(SnEmbedLink value, $Res Function(SnEmbedLink) _then) = _$SnEmbedLinkCopyWithImpl;
|
|
||||||
@useResult
|
|
||||||
$Res call({
|
|
||||||
@JsonKey(name: 'Type') String type,@JsonKey(name: 'Url') String url,@JsonKey(name: 'Title') String title,@JsonKey(name: 'Description') String? description,@JsonKey(name: 'ImageUrl') String? imageUrl,@JsonKey(name: 'FaviconUrl') String faviconUrl,@JsonKey(name: 'SiteName') String siteName,@JsonKey(name: 'ContentType') String? contentType,@JsonKey(name: 'Author') String? author,@JsonKey(name: 'PublishedDate') DateTime? publishedDate
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
/// @nodoc
|
|
||||||
class _$SnEmbedLinkCopyWithImpl<$Res>
|
|
||||||
implements $SnEmbedLinkCopyWith<$Res> {
|
|
||||||
_$SnEmbedLinkCopyWithImpl(this._self, this._then);
|
|
||||||
|
|
||||||
final SnEmbedLink _self;
|
|
||||||
final $Res Function(SnEmbedLink) _then;
|
|
||||||
|
|
||||||
/// Create a copy of SnEmbedLink
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? type = null,Object? url = null,Object? title = null,Object? description = freezed,Object? imageUrl = freezed,Object? faviconUrl = null,Object? siteName = null,Object? contentType = freezed,Object? author = freezed,Object? publishedDate = freezed,}) {
|
|
||||||
return _then(_self.copyWith(
|
|
||||||
type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,imageUrl: freezed == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,faviconUrl: null == faviconUrl ? _self.faviconUrl : faviconUrl // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,siteName: null == siteName ? _self.siteName : siteName // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,contentType: freezed == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,author: freezed == author ? _self.author : author // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,publishedDate: freezed == publishedDate ? _self.publishedDate : publishedDate // ignore: cast_nullable_to_non_nullable
|
|
||||||
as DateTime?,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Adds pattern-matching-related methods to [SnEmbedLink].
|
|
||||||
extension SnEmbedLinkPatterns on SnEmbedLink {
|
|
||||||
/// 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 extends Object?>(TResult Function( _SnEmbedLink value)? $default,{required TResult orElse(),}){
|
|
||||||
final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _SnEmbedLink() 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 extends Object?>(TResult Function( _SnEmbedLink value) $default,){
|
|
||||||
final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _SnEmbedLink():
|
|
||||||
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 extends Object?>(TResult? Function( _SnEmbedLink value)? $default,){
|
|
||||||
final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _SnEmbedLink() 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 extends Object?>(TResult Function(@JsonKey(name: 'Type') String type, @JsonKey(name: 'Url') String url, @JsonKey(name: 'Title') String title, @JsonKey(name: 'Description') String? description, @JsonKey(name: 'ImageUrl') String? imageUrl, @JsonKey(name: 'FaviconUrl') String faviconUrl, @JsonKey(name: 'SiteName') String siteName, @JsonKey(name: 'ContentType') String? contentType, @JsonKey(name: 'Author') String? author, @JsonKey(name: 'PublishedDate') DateTime? publishedDate)? $default,{required TResult orElse(),}) {final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _SnEmbedLink() when $default != null:
|
|
||||||
return $default(_that.type,_that.url,_that.title,_that.description,_that.imageUrl,_that.faviconUrl,_that.siteName,_that.contentType,_that.author,_that.publishedDate);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 extends Object?>(TResult Function(@JsonKey(name: 'Type') String type, @JsonKey(name: 'Url') String url, @JsonKey(name: 'Title') String title, @JsonKey(name: 'Description') String? description, @JsonKey(name: 'ImageUrl') String? imageUrl, @JsonKey(name: 'FaviconUrl') String faviconUrl, @JsonKey(name: 'SiteName') String siteName, @JsonKey(name: 'ContentType') String? contentType, @JsonKey(name: 'Author') String? author, @JsonKey(name: 'PublishedDate') DateTime? publishedDate) $default,) {final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _SnEmbedLink():
|
|
||||||
return $default(_that.type,_that.url,_that.title,_that.description,_that.imageUrl,_that.faviconUrl,_that.siteName,_that.contentType,_that.author,_that.publishedDate);}
|
|
||||||
}
|
|
||||||
/// 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 extends Object?>(TResult? Function(@JsonKey(name: 'Type') String type, @JsonKey(name: 'Url') String url, @JsonKey(name: 'Title') String title, @JsonKey(name: 'Description') String? description, @JsonKey(name: 'ImageUrl') String? imageUrl, @JsonKey(name: 'FaviconUrl') String faviconUrl, @JsonKey(name: 'SiteName') String siteName, @JsonKey(name: 'ContentType') String? contentType, @JsonKey(name: 'Author') String? author, @JsonKey(name: 'PublishedDate') DateTime? publishedDate)? $default,) {final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _SnEmbedLink() when $default != null:
|
|
||||||
return $default(_that.type,_that.url,_that.title,_that.description,_that.imageUrl,_that.faviconUrl,_that.siteName,_that.contentType,_that.author,_that.publishedDate);case _:
|
|
||||||
return null;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
@JsonSerializable()
|
|
||||||
|
|
||||||
class _SnEmbedLink implements SnEmbedLink {
|
|
||||||
const _SnEmbedLink({@JsonKey(name: 'Type') required this.type, @JsonKey(name: 'Url') required this.url, @JsonKey(name: 'Title') required this.title, @JsonKey(name: 'Description') required this.description, @JsonKey(name: 'ImageUrl') required this.imageUrl, @JsonKey(name: 'FaviconUrl') this.faviconUrl = "", @JsonKey(name: 'SiteName') this.siteName = "", @JsonKey(name: 'ContentType') required this.contentType, @JsonKey(name: 'Author') required this.author, @JsonKey(name: 'PublishedDate') required this.publishedDate});
|
|
||||||
factory _SnEmbedLink.fromJson(Map<String, dynamic> json) => _$SnEmbedLinkFromJson(json);
|
|
||||||
|
|
||||||
@override@JsonKey(name: 'Type') final String type;
|
|
||||||
@override@JsonKey(name: 'Url') final String url;
|
|
||||||
@override@JsonKey(name: 'Title') final String title;
|
|
||||||
@override@JsonKey(name: 'Description') final String? description;
|
|
||||||
@override@JsonKey(name: 'ImageUrl') final String? imageUrl;
|
|
||||||
@override@JsonKey(name: 'FaviconUrl') final String faviconUrl;
|
|
||||||
@override@JsonKey(name: 'SiteName') final String siteName;
|
|
||||||
@override@JsonKey(name: 'ContentType') final String? contentType;
|
|
||||||
@override@JsonKey(name: 'Author') final String? author;
|
|
||||||
@override@JsonKey(name: 'PublishedDate') final DateTime? publishedDate;
|
|
||||||
|
|
||||||
/// Create a copy of SnEmbedLink
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
_$SnEmbedLinkCopyWith<_SnEmbedLink> get copyWith => __$SnEmbedLinkCopyWithImpl<_SnEmbedLink>(this, _$identity);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return _$SnEmbedLinkToJson(this, );
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnEmbedLink&&(identical(other.type, type) || other.type == type)&&(identical(other.url, url) || other.url == url)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.imageUrl, imageUrl) || other.imageUrl == imageUrl)&&(identical(other.faviconUrl, faviconUrl) || other.faviconUrl == faviconUrl)&&(identical(other.siteName, siteName) || other.siteName == siteName)&&(identical(other.contentType, contentType) || other.contentType == contentType)&&(identical(other.author, author) || other.author == author)&&(identical(other.publishedDate, publishedDate) || other.publishedDate == publishedDate));
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(runtimeType,type,url,title,description,imageUrl,faviconUrl,siteName,contentType,author,publishedDate);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'SnEmbedLink(type: $type, url: $url, title: $title, description: $description, imageUrl: $imageUrl, faviconUrl: $faviconUrl, siteName: $siteName, contentType: $contentType, author: $author, publishedDate: $publishedDate)';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract mixin class _$SnEmbedLinkCopyWith<$Res> implements $SnEmbedLinkCopyWith<$Res> {
|
|
||||||
factory _$SnEmbedLinkCopyWith(_SnEmbedLink value, $Res Function(_SnEmbedLink) _then) = __$SnEmbedLinkCopyWithImpl;
|
|
||||||
@override @useResult
|
|
||||||
$Res call({
|
|
||||||
@JsonKey(name: 'Type') String type,@JsonKey(name: 'Url') String url,@JsonKey(name: 'Title') String title,@JsonKey(name: 'Description') String? description,@JsonKey(name: 'ImageUrl') String? imageUrl,@JsonKey(name: 'FaviconUrl') String faviconUrl,@JsonKey(name: 'SiteName') String siteName,@JsonKey(name: 'ContentType') String? contentType,@JsonKey(name: 'Author') String? author,@JsonKey(name: 'PublishedDate') DateTime? publishedDate
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
/// @nodoc
|
|
||||||
class __$SnEmbedLinkCopyWithImpl<$Res>
|
|
||||||
implements _$SnEmbedLinkCopyWith<$Res> {
|
|
||||||
__$SnEmbedLinkCopyWithImpl(this._self, this._then);
|
|
||||||
|
|
||||||
final _SnEmbedLink _self;
|
|
||||||
final $Res Function(_SnEmbedLink) _then;
|
|
||||||
|
|
||||||
/// Create a copy of SnEmbedLink
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? type = null,Object? url = null,Object? title = null,Object? description = freezed,Object? imageUrl = freezed,Object? faviconUrl = null,Object? siteName = null,Object? contentType = freezed,Object? author = freezed,Object? publishedDate = freezed,}) {
|
|
||||||
return _then(_SnEmbedLink(
|
|
||||||
type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,imageUrl: freezed == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,faviconUrl: null == faviconUrl ? _self.faviconUrl : faviconUrl // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,siteName: null == siteName ? _self.siteName : siteName // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,contentType: freezed == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,author: freezed == author ? _self.author : author // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,publishedDate: freezed == publishedDate ? _self.publishedDate : publishedDate // ignore: cast_nullable_to_non_nullable
|
|
||||||
as DateTime?,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$SnScrappedLink {
|
mixin _$SnScrappedLink {
|
||||||
|
|
||||||
|
|||||||
@@ -6,36 +6,6 @@ part of 'embed.dart';
|
|||||||
// JsonSerializableGenerator
|
// JsonSerializableGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
_SnEmbedLink _$SnEmbedLinkFromJson(Map<String, dynamic> json) => _SnEmbedLink(
|
|
||||||
type: json['Type'] as String,
|
|
||||||
url: json['Url'] as String,
|
|
||||||
title: json['Title'] as String,
|
|
||||||
description: json['Description'] as String?,
|
|
||||||
imageUrl: json['ImageUrl'] as String?,
|
|
||||||
faviconUrl: json['FaviconUrl'] as String? ?? "",
|
|
||||||
siteName: json['SiteName'] as String? ?? "",
|
|
||||||
contentType: json['ContentType'] as String?,
|
|
||||||
author: json['Author'] as String?,
|
|
||||||
publishedDate:
|
|
||||||
json['PublishedDate'] == null
|
|
||||||
? null
|
|
||||||
: DateTime.parse(json['PublishedDate'] as String),
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$SnEmbedLinkToJson(_SnEmbedLink instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'Type': instance.type,
|
|
||||||
'Url': instance.url,
|
|
||||||
'Title': instance.title,
|
|
||||||
'Description': instance.description,
|
|
||||||
'ImageUrl': instance.imageUrl,
|
|
||||||
'FaviconUrl': instance.faviconUrl,
|
|
||||||
'SiteName': instance.siteName,
|
|
||||||
'ContentType': instance.contentType,
|
|
||||||
'Author': instance.author,
|
|
||||||
'PublishedDate': instance.publishedDate?.toIso8601String(),
|
|
||||||
};
|
|
||||||
|
|
||||||
_SnScrappedLink _$SnScrappedLinkFromJson(Map<String, dynamic> json) =>
|
_SnScrappedLink _$SnScrappedLinkFromJson(Map<String, dynamic> json) =>
|
||||||
_SnScrappedLink(
|
_SnScrappedLink(
|
||||||
type: json['type'] as String,
|
type: json['type'] as String,
|
||||||
|
|||||||
@@ -90,3 +90,19 @@ enum SnPollQuestionType {
|
|||||||
@JsonValue(4)
|
@JsonValue(4)
|
||||||
freeText,
|
freeText,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class SnPollAnswer with _$SnPollAnswer {
|
||||||
|
const factory SnPollAnswer({
|
||||||
|
required String id,
|
||||||
|
required Map<String, dynamic> answer,
|
||||||
|
required String accountId,
|
||||||
|
required String pollId,
|
||||||
|
required DateTime createdAt,
|
||||||
|
required DateTime updatedAt,
|
||||||
|
required DateTime? deletedAt,
|
||||||
|
}) = _SnPollAnswer;
|
||||||
|
|
||||||
|
factory SnPollAnswer.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnPollAnswerFromJson(json);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1181,6 +1181,287 @@ as int,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnPollAnswer {
|
||||||
|
|
||||||
|
String get id; Map<String, dynamic> get answer; String get accountId; String get pollId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
|
||||||
|
/// Create a copy of SnPollAnswer
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnPollAnswerCopyWith<SnPollAnswer> get copyWith => _$SnPollAnswerCopyWithImpl<SnPollAnswer>(this as SnPollAnswer, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this SnPollAnswer to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPollAnswer&&(identical(other.id, id) || other.id == id)&&const DeepCollectionEquality().equals(other.answer, answer)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.pollId, pollId) || other.pollId == pollId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,id,const DeepCollectionEquality().hash(answer),accountId,pollId,createdAt,updatedAt,deletedAt);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnPollAnswer(id: $id, answer: $answer, accountId: $accountId, pollId: $pollId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $SnPollAnswerCopyWith<$Res> {
|
||||||
|
factory $SnPollAnswerCopyWith(SnPollAnswer value, $Res Function(SnPollAnswer) _then) = _$SnPollAnswerCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String id, Map<String, dynamic> answer, String accountId, String pollId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnPollAnswerCopyWithImpl<$Res>
|
||||||
|
implements $SnPollAnswerCopyWith<$Res> {
|
||||||
|
_$SnPollAnswerCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final SnPollAnswer _self;
|
||||||
|
final $Res Function(SnPollAnswer) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnPollAnswer
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? answer = null,Object? accountId = null,Object? pollId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,answer: null == answer ? _self.answer : answer // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Map<String, dynamic>,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,pollId: null == pollId ? _self.pollId : pollId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Adds pattern-matching-related methods to [SnPollAnswer].
|
||||||
|
extension SnPollAnswerPatterns on SnPollAnswer {
|
||||||
|
/// 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 extends Object?>(TResult Function( _SnPollAnswer value)? $default,{required TResult orElse(),}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnPollAnswer() 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 extends Object?>(TResult Function( _SnPollAnswer value) $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnPollAnswer():
|
||||||
|
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 extends Object?>(TResult? Function( _SnPollAnswer value)? $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnPollAnswer() 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 extends Object?>(TResult Function( String id, Map<String, dynamic> answer, String accountId, String pollId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnPollAnswer() when $default != null:
|
||||||
|
return $default(_that.id,_that.answer,_that.accountId,_that.pollId,_that.createdAt,_that.updatedAt,_that.deletedAt);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 extends Object?>(TResult Function( String id, Map<String, dynamic> answer, String accountId, String pollId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnPollAnswer():
|
||||||
|
return $default(_that.id,_that.answer,_that.accountId,_that.pollId,_that.createdAt,_that.updatedAt,_that.deletedAt);}
|
||||||
|
}
|
||||||
|
/// 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 extends Object?>(TResult? Function( String id, Map<String, dynamic> answer, String accountId, String pollId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnPollAnswer() when $default != null:
|
||||||
|
return $default(_that.id,_that.answer,_that.accountId,_that.pollId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _SnPollAnswer implements SnPollAnswer {
|
||||||
|
const _SnPollAnswer({required this.id, required final Map<String, dynamic> answer, required this.accountId, required this.pollId, required this.createdAt, required this.updatedAt, required this.deletedAt}): _answer = answer;
|
||||||
|
factory _SnPollAnswer.fromJson(Map<String, dynamic> json) => _$SnPollAnswerFromJson(json);
|
||||||
|
|
||||||
|
@override final String id;
|
||||||
|
final Map<String, dynamic> _answer;
|
||||||
|
@override Map<String, dynamic> get answer {
|
||||||
|
if (_answer is EqualUnmodifiableMapView) return _answer;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableMapView(_answer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override final String accountId;
|
||||||
|
@override final String pollId;
|
||||||
|
@override final DateTime createdAt;
|
||||||
|
@override final DateTime updatedAt;
|
||||||
|
@override final DateTime? deletedAt;
|
||||||
|
|
||||||
|
/// Create a copy of SnPollAnswer
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$SnPollAnswerCopyWith<_SnPollAnswer> get copyWith => __$SnPollAnswerCopyWithImpl<_SnPollAnswer>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$SnPollAnswerToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPollAnswer&&(identical(other.id, id) || other.id == id)&&const DeepCollectionEquality().equals(other._answer, _answer)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.pollId, pollId) || other.pollId == pollId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,id,const DeepCollectionEquality().hash(_answer),accountId,pollId,createdAt,updatedAt,deletedAt);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnPollAnswer(id: $id, answer: $answer, accountId: $accountId, pollId: $pollId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$SnPollAnswerCopyWith<$Res> implements $SnPollAnswerCopyWith<$Res> {
|
||||||
|
factory _$SnPollAnswerCopyWith(_SnPollAnswer value, $Res Function(_SnPollAnswer) _then) = __$SnPollAnswerCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String id, Map<String, dynamic> answer, String accountId, String pollId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$SnPollAnswerCopyWithImpl<$Res>
|
||||||
|
implements _$SnPollAnswerCopyWith<$Res> {
|
||||||
|
__$SnPollAnswerCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _SnPollAnswer _self;
|
||||||
|
final $Res Function(_SnPollAnswer) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnPollAnswer
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? answer = null,Object? accountId = null,Object? pollId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||||
|
return _then(_SnPollAnswer(
|
||||||
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,answer: null == answer ? _self._answer : answer // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Map<String, dynamic>,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,pollId: null == pollId ? _self.pollId : pollId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// dart format on
|
// dart format on
|
||||||
|
|||||||
@@ -131,3 +131,28 @@ Map<String, dynamic> _$SnPollOptionToJson(_SnPollOption instance) =>
|
|||||||
'description': instance.description,
|
'description': instance.description,
|
||||||
'order': instance.order,
|
'order': instance.order,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_SnPollAnswer _$SnPollAnswerFromJson(Map<String, dynamic> json) =>
|
||||||
|
_SnPollAnswer(
|
||||||
|
id: json['id'] as String,
|
||||||
|
answer: json['answer'] as Map<String, dynamic>,
|
||||||
|
accountId: json['account_id'] as String,
|
||||||
|
pollId: json['poll_id'] as String,
|
||||||
|
createdAt: DateTime.parse(json['created_at'] as String),
|
||||||
|
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||||
|
deletedAt:
|
||||||
|
json['deleted_at'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['deleted_at'] as String),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$SnPollAnswerToJson(_SnPollAnswer instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'answer': instance.answer,
|
||||||
|
'account_id': instance.accountId,
|
||||||
|
'poll_id': instance.pollId,
|
||||||
|
'created_at': instance.createdAt.toIso8601String(),
|
||||||
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
|
};
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ sealed class SnPost with _$SnPost {
|
|||||||
@Default({}) Map<String, int> reactionsCount,
|
@Default({}) Map<String, int> reactionsCount,
|
||||||
@Default({}) Map<String, bool> reactionsMade,
|
@Default({}) Map<String, bool> reactionsMade,
|
||||||
@Default([]) List<dynamic> reactions,
|
@Default([]) List<dynamic> reactions,
|
||||||
@Default([]) List<PostTag> tags,
|
@Default([]) List<SnPostTag> tags,
|
||||||
@Default([]) List<PostCategory> categories,
|
@Default([]) List<SnPostCategory> categories,
|
||||||
@Default([]) List<dynamic> collections,
|
@Default([]) List<dynamic> collections,
|
||||||
@Default(null) DateTime? createdAt,
|
@Default(null) DateTime? createdAt,
|
||||||
@Default(null) DateTime? updatedAt,
|
@Default(null) DateTime? updatedAt,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ T _$identity<T>(T value) => value;
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$SnPost {
|
mixin _$SnPost {
|
||||||
|
|
||||||
String get id; String? get title; String? get description; String? get language; DateTime? get editedAt; DateTime? get publishedAt; int get visibility; String? get content; int get type; Map<String, dynamic>? get meta; int get viewsUnique; int get viewsTotal; int get upvotes; int get downvotes; int get repliesCount; String? get threadedPostId; SnPost? get threadedPost; String? get repliedPostId; SnPost? get repliedPost; String? get forwardedPostId; SnPost? get forwardedPost; List<SnCloudFile> get attachments; SnPublisher get publisher; Map<String, int> get reactionsCount; Map<String, bool> get reactionsMade; List<dynamic> get reactions; List<PostTag> get tags; List<PostCategory> get categories; List<dynamic> get collections; DateTime? get createdAt; DateTime? get updatedAt; DateTime? get deletedAt; bool get isTruncated;
|
String get id; String? get title; String? get description; String? get language; DateTime? get editedAt; DateTime? get publishedAt; int get visibility; String? get content; int get type; Map<String, dynamic>? get meta; int get viewsUnique; int get viewsTotal; int get upvotes; int get downvotes; int get repliesCount; String? get threadedPostId; SnPost? get threadedPost; String? get repliedPostId; SnPost? get repliedPost; String? get forwardedPostId; SnPost? get forwardedPost; List<SnCloudFile> get attachments; SnPublisher get publisher; Map<String, int> get reactionsCount; Map<String, bool> get reactionsMade; List<dynamic> get reactions; List<SnPostTag> get tags; List<SnPostCategory> get categories; List<dynamic> get collections; DateTime? get createdAt; DateTime? get updatedAt; DateTime? get deletedAt; bool get isTruncated;
|
||||||
/// Create a copy of SnPost
|
/// Create a copy of SnPost
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -48,7 +48,7 @@ abstract mixin class $SnPostCopyWith<$Res> {
|
|||||||
factory $SnPostCopyWith(SnPost value, $Res Function(SnPost) _then) = _$SnPostCopyWithImpl;
|
factory $SnPostCopyWith(SnPost value, $Res Function(SnPost) _then) = _$SnPostCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<PostTag> tags, List<PostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated
|
String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -94,8 +94,8 @@ as SnPublisher,reactionsCount: null == reactionsCount ? _self.reactionsCount : r
|
|||||||
as Map<String, int>,reactionsMade: null == reactionsMade ? _self.reactionsMade : reactionsMade // ignore: cast_nullable_to_non_nullable
|
as Map<String, int>,reactionsMade: null == reactionsMade ? _self.reactionsMade : reactionsMade // ignore: cast_nullable_to_non_nullable
|
||||||
as Map<String, bool>,reactions: null == reactions ? _self.reactions : reactions // ignore: cast_nullable_to_non_nullable
|
as Map<String, bool>,reactions: null == reactions ? _self.reactions : reactions // ignore: cast_nullable_to_non_nullable
|
||||||
as List<dynamic>,tags: null == tags ? _self.tags : tags // ignore: cast_nullable_to_non_nullable
|
as List<dynamic>,tags: null == tags ? _self.tags : tags // ignore: cast_nullable_to_non_nullable
|
||||||
as List<PostTag>,categories: null == categories ? _self.categories : categories // ignore: cast_nullable_to_non_nullable
|
as List<SnPostTag>,categories: null == categories ? _self.categories : categories // ignore: cast_nullable_to_non_nullable
|
||||||
as List<PostCategory>,collections: null == collections ? _self.collections : collections // ignore: cast_nullable_to_non_nullable
|
as List<SnPostCategory>,collections: null == collections ? _self.collections : collections // ignore: cast_nullable_to_non_nullable
|
||||||
as List<dynamic>,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
as List<dynamic>,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime?,updatedAt: freezed == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime?,updatedAt: freezed == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime?,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime?,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -227,7 +227,7 @@ return $default(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<PostTag> tags, List<PostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated)? $default,{required TResult orElse(),}) {final _that = this;
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SnPost() when $default != null:
|
case _SnPost() when $default != null:
|
||||||
return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.type,_that.meta,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);case _:
|
return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.type,_that.meta,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);case _:
|
||||||
@@ -248,7 +248,7 @@ return $default(_that.id,_that.title,_that.description,_that.language,_that.edit
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<PostTag> tags, List<PostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated) $default,) {final _that = this;
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated) $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SnPost():
|
case _SnPost():
|
||||||
return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.type,_that.meta,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);}
|
return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.type,_that.meta,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);}
|
||||||
@@ -265,7 +265,7 @@ return $default(_that.id,_that.title,_that.description,_that.language,_that.edit
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<PostTag> tags, List<PostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated)? $default,) {final _that = this;
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated)? $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SnPost() when $default != null:
|
case _SnPost() when $default != null:
|
||||||
return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.type,_that.meta,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);case _:
|
return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.type,_that.meta,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);case _:
|
||||||
@@ -280,7 +280,7 @@ return $default(_that.id,_that.title,_that.description,_that.language,_that.edit
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _SnPost implements SnPost {
|
class _SnPost implements SnPost {
|
||||||
const _SnPost({required this.id, this.title, this.description, this.language, this.editedAt, this.publishedAt = null, this.visibility = 0, this.content, this.type = 0, final Map<String, dynamic>? meta, this.viewsUnique = 0, this.viewsTotal = 0, this.upvotes = 0, this.downvotes = 0, this.repliesCount = 0, this.threadedPostId, this.threadedPost, this.repliedPostId, this.repliedPost, this.forwardedPostId, this.forwardedPost, final List<SnCloudFile> attachments = const [], required this.publisher, final Map<String, int> reactionsCount = const {}, final Map<String, bool> reactionsMade = const {}, final List<dynamic> reactions = const [], final List<PostTag> tags = const [], final List<PostCategory> categories = const [], final List<dynamic> collections = const [], this.createdAt = null, this.updatedAt = null, this.deletedAt, this.isTruncated = false}): _meta = meta,_attachments = attachments,_reactionsCount = reactionsCount,_reactionsMade = reactionsMade,_reactions = reactions,_tags = tags,_categories = categories,_collections = collections;
|
const _SnPost({required this.id, this.title, this.description, this.language, this.editedAt, this.publishedAt = null, this.visibility = 0, this.content, this.type = 0, final Map<String, dynamic>? meta, this.viewsUnique = 0, this.viewsTotal = 0, this.upvotes = 0, this.downvotes = 0, this.repliesCount = 0, this.threadedPostId, this.threadedPost, this.repliedPostId, this.repliedPost, this.forwardedPostId, this.forwardedPost, final List<SnCloudFile> attachments = const [], required this.publisher, final Map<String, int> reactionsCount = const {}, final Map<String, bool> reactionsMade = const {}, final List<dynamic> reactions = const [], final List<SnPostTag> tags = const [], final List<SnPostCategory> categories = const [], final List<dynamic> collections = const [], this.createdAt = null, this.updatedAt = null, this.deletedAt, this.isTruncated = false}): _meta = meta,_attachments = attachments,_reactionsCount = reactionsCount,_reactionsMade = reactionsMade,_reactions = reactions,_tags = tags,_categories = categories,_collections = collections;
|
||||||
factory _SnPost.fromJson(Map<String, dynamic> json) => _$SnPostFromJson(json);
|
factory _SnPost.fromJson(Map<String, dynamic> json) => _$SnPostFromJson(json);
|
||||||
|
|
||||||
@override final String id;
|
@override final String id;
|
||||||
@@ -341,15 +341,15 @@ class _SnPost implements SnPost {
|
|||||||
return EqualUnmodifiableListView(_reactions);
|
return EqualUnmodifiableListView(_reactions);
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<PostTag> _tags;
|
final List<SnPostTag> _tags;
|
||||||
@override@JsonKey() List<PostTag> get tags {
|
@override@JsonKey() List<SnPostTag> get tags {
|
||||||
if (_tags is EqualUnmodifiableListView) return _tags;
|
if (_tags is EqualUnmodifiableListView) return _tags;
|
||||||
// ignore: implicit_dynamic_type
|
// ignore: implicit_dynamic_type
|
||||||
return EqualUnmodifiableListView(_tags);
|
return EqualUnmodifiableListView(_tags);
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<PostCategory> _categories;
|
final List<SnPostCategory> _categories;
|
||||||
@override@JsonKey() List<PostCategory> get categories {
|
@override@JsonKey() List<SnPostCategory> get categories {
|
||||||
if (_categories is EqualUnmodifiableListView) return _categories;
|
if (_categories is EqualUnmodifiableListView) return _categories;
|
||||||
// ignore: implicit_dynamic_type
|
// ignore: implicit_dynamic_type
|
||||||
return EqualUnmodifiableListView(_categories);
|
return EqualUnmodifiableListView(_categories);
|
||||||
@@ -400,7 +400,7 @@ abstract mixin class _$SnPostCopyWith<$Res> implements $SnPostCopyWith<$Res> {
|
|||||||
factory _$SnPostCopyWith(_SnPost value, $Res Function(_SnPost) _then) = __$SnPostCopyWithImpl;
|
factory _$SnPostCopyWith(_SnPost value, $Res Function(_SnPost) _then) = __$SnPostCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<PostTag> tags, List<PostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated
|
String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -446,8 +446,8 @@ as SnPublisher,reactionsCount: null == reactionsCount ? _self._reactionsCount :
|
|||||||
as Map<String, int>,reactionsMade: null == reactionsMade ? _self._reactionsMade : reactionsMade // ignore: cast_nullable_to_non_nullable
|
as Map<String, int>,reactionsMade: null == reactionsMade ? _self._reactionsMade : reactionsMade // ignore: cast_nullable_to_non_nullable
|
||||||
as Map<String, bool>,reactions: null == reactions ? _self._reactions : reactions // ignore: cast_nullable_to_non_nullable
|
as Map<String, bool>,reactions: null == reactions ? _self._reactions : reactions // ignore: cast_nullable_to_non_nullable
|
||||||
as List<dynamic>,tags: null == tags ? _self._tags : tags // ignore: cast_nullable_to_non_nullable
|
as List<dynamic>,tags: null == tags ? _self._tags : tags // ignore: cast_nullable_to_non_nullable
|
||||||
as List<PostTag>,categories: null == categories ? _self._categories : categories // ignore: cast_nullable_to_non_nullable
|
as List<SnPostTag>,categories: null == categories ? _self._categories : categories // ignore: cast_nullable_to_non_nullable
|
||||||
as List<PostCategory>,collections: null == collections ? _self._collections : collections // ignore: cast_nullable_to_non_nullable
|
as List<SnPostCategory>,collections: null == collections ? _self._collections : collections // ignore: cast_nullable_to_non_nullable
|
||||||
as List<dynamic>,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
as List<dynamic>,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime?,updatedAt: freezed == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime?,updatedAt: freezed == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime?,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime?,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
|||||||
@@ -62,12 +62,12 @@ _SnPost _$SnPostFromJson(Map<String, dynamic> json) => _SnPost(
|
|||||||
reactions: json['reactions'] as List<dynamic>? ?? const [],
|
reactions: json['reactions'] as List<dynamic>? ?? const [],
|
||||||
tags:
|
tags:
|
||||||
(json['tags'] as List<dynamic>?)
|
(json['tags'] as List<dynamic>?)
|
||||||
?.map((e) => PostTag.fromJson(e as Map<String, dynamic>))
|
?.map((e) => SnPostTag.fromJson(e as Map<String, dynamic>))
|
||||||
.toList() ??
|
.toList() ??
|
||||||
const [],
|
const [],
|
||||||
categories:
|
categories:
|
||||||
(json['categories'] as List<dynamic>?)
|
(json['categories'] as List<dynamic>?)
|
||||||
?.map((e) => PostCategory.fromJson(e as Map<String, dynamic>))
|
?.map((e) => SnPostCategory.fromJson(e as Map<String, dynamic>))
|
||||||
.toList() ??
|
.toList() ??
|
||||||
const [],
|
const [],
|
||||||
collections: json['collections'] as List<dynamic>? ?? const [],
|
collections: json['collections'] as List<dynamic>? ?? const [],
|
||||||
|
|||||||
@@ -1,19 +1,30 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:island/models/post.dart';
|
import 'package:island/models/post.dart';
|
||||||
|
import 'package:island/services/text.dart';
|
||||||
|
|
||||||
part 'post_category.freezed.dart';
|
part 'post_category.freezed.dart';
|
||||||
part 'post_category.g.dart';
|
part 'post_category.g.dart';
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
sealed class PostCategory with _$PostCategory {
|
sealed class SnPostCategory with _$SnPostCategory {
|
||||||
const factory PostCategory({
|
const SnPostCategory._();
|
||||||
|
|
||||||
|
const factory SnPostCategory({
|
||||||
required String id,
|
required String id,
|
||||||
required String slug,
|
required String slug,
|
||||||
String? name,
|
String? name,
|
||||||
@Default([]) List<SnPost> posts,
|
@Default([]) List<SnPost> posts,
|
||||||
}) = _PostCategory;
|
}) = _SnPostCategory;
|
||||||
|
|
||||||
factory PostCategory.fromJson(Map<String, dynamic> json) =>
|
factory SnPostCategory.fromJson(Map<String, dynamic> json) =>
|
||||||
_$PostCategoryFromJson(json);
|
_$SnPostCategoryFromJson(json);
|
||||||
|
|
||||||
|
String get categoryDisplayTitle {
|
||||||
|
final capitalizedSlug = slug.capitalizeEachWord();
|
||||||
|
if ('postCategory$capitalizedSlug'.trExists()) {
|
||||||
|
return 'postCategory$capitalizedSlug'.tr();
|
||||||
|
}
|
||||||
|
return name ?? slug;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,22 +13,22 @@ part of 'post_category.dart';
|
|||||||
T _$identity<T>(T value) => value;
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$PostCategory {
|
mixin _$SnPostCategory {
|
||||||
|
|
||||||
String get id; String get slug; String? get name; List<SnPost> get posts;
|
String get id; String get slug; String? get name; List<SnPost> get posts;
|
||||||
/// Create a copy of PostCategory
|
/// Create a copy of SnPostCategory
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
$PostCategoryCopyWith<PostCategory> get copyWith => _$PostCategoryCopyWithImpl<PostCategory>(this as PostCategory, _$identity);
|
$SnPostCategoryCopyWith<SnPostCategory> get copyWith => _$SnPostCategoryCopyWithImpl<SnPostCategory>(this as SnPostCategory, _$identity);
|
||||||
|
|
||||||
/// Serializes this PostCategory to a JSON map.
|
/// Serializes this SnPostCategory to a JSON map.
|
||||||
Map<String, dynamic> toJson();
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is PostCategory&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other.posts, posts));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPostCategory&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other.posts, posts));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -37,15 +37,15 @@ int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEqu
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'PostCategory(id: $id, slug: $slug, name: $name, posts: $posts)';
|
return 'SnPostCategory(id: $id, slug: $slug, name: $name, posts: $posts)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
abstract mixin class $PostCategoryCopyWith<$Res> {
|
abstract mixin class $SnPostCategoryCopyWith<$Res> {
|
||||||
factory $PostCategoryCopyWith(PostCategory value, $Res Function(PostCategory) _then) = _$PostCategoryCopyWithImpl;
|
factory $SnPostCategoryCopyWith(SnPostCategory value, $Res Function(SnPostCategory) _then) = _$SnPostCategoryCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String id, String slug, String? name, List<SnPost> posts
|
String id, String slug, String? name, List<SnPost> posts
|
||||||
@@ -56,14 +56,14 @@ $Res call({
|
|||||||
|
|
||||||
}
|
}
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
class _$PostCategoryCopyWithImpl<$Res>
|
class _$SnPostCategoryCopyWithImpl<$Res>
|
||||||
implements $PostCategoryCopyWith<$Res> {
|
implements $SnPostCategoryCopyWith<$Res> {
|
||||||
_$PostCategoryCopyWithImpl(this._self, this._then);
|
_$SnPostCategoryCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
final PostCategory _self;
|
final SnPostCategory _self;
|
||||||
final $Res Function(PostCategory) _then;
|
final $Res Function(SnPostCategory) _then;
|
||||||
|
|
||||||
/// Create a copy of PostCategory
|
/// Create a copy of SnPostCategory
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? slug = null,Object? name = freezed,Object? posts = null,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? slug = null,Object? name = freezed,Object? posts = null,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
@@ -78,8 +78,8 @@ as List<SnPost>,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Adds pattern-matching-related methods to [PostCategory].
|
/// Adds pattern-matching-related methods to [SnPostCategory].
|
||||||
extension PostCategoryPatterns on PostCategory {
|
extension SnPostCategoryPatterns on SnPostCategory {
|
||||||
/// A variant of `map` that fallback to returning `orElse`.
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
///
|
///
|
||||||
/// It is equivalent to doing:
|
/// It is equivalent to doing:
|
||||||
@@ -92,10 +92,10 @@ extension PostCategoryPatterns on PostCategory {
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _PostCategory value)? $default,{required TResult orElse(),}){
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnPostCategory value)? $default,{required TResult orElse(),}){
|
||||||
final _that = this;
|
final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _PostCategory() when $default != null:
|
case _SnPostCategory() when $default != null:
|
||||||
return $default(_that);case _:
|
return $default(_that);case _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
@@ -114,10 +114,10 @@ return $default(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _PostCategory value) $default,){
|
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnPostCategory value) $default,){
|
||||||
final _that = this;
|
final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _PostCategory():
|
case _SnPostCategory():
|
||||||
return $default(_that);}
|
return $default(_that);}
|
||||||
}
|
}
|
||||||
/// A variant of `map` that fallback to returning `null`.
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
@@ -132,10 +132,10 @@ return $default(_that);}
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _PostCategory value)? $default,){
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnPostCategory value)? $default,){
|
||||||
final _that = this;
|
final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _PostCategory() when $default != null:
|
case _SnPostCategory() when $default != null:
|
||||||
return $default(_that);case _:
|
return $default(_that);case _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ return $default(_that);case _:
|
|||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String slug, String? name, List<SnPost> posts)? $default,{required TResult orElse(),}) {final _that = this;
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String slug, String? name, List<SnPost> posts)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _PostCategory() when $default != null:
|
case _SnPostCategory() when $default != null:
|
||||||
return $default(_that.id,_that.slug,_that.name,_that.posts);case _:
|
return $default(_that.id,_that.slug,_that.name,_that.posts);case _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
@@ -176,7 +176,7 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);case _:
|
|||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String slug, String? name, List<SnPost> posts) $default,) {final _that = this;
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String slug, String? name, List<SnPost> posts) $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _PostCategory():
|
case _SnPostCategory():
|
||||||
return $default(_that.id,_that.slug,_that.name,_that.posts);}
|
return $default(_that.id,_that.slug,_that.name,_that.posts);}
|
||||||
}
|
}
|
||||||
/// A variant of `when` that fallback to returning `null`
|
/// A variant of `when` that fallback to returning `null`
|
||||||
@@ -193,7 +193,7 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);}
|
|||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String slug, String? name, List<SnPost> posts)? $default,) {final _that = this;
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String slug, String? name, List<SnPost> posts)? $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _PostCategory() when $default != null:
|
case _SnPostCategory() when $default != null:
|
||||||
return $default(_that.id,_that.slug,_that.name,_that.posts);case _:
|
return $default(_that.id,_that.slug,_that.name,_that.posts);case _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@@ -205,9 +205,9 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);case _:
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _PostCategory implements PostCategory {
|
class _SnPostCategory extends SnPostCategory {
|
||||||
const _PostCategory({required this.id, required this.slug, this.name, final List<SnPost> posts = const []}): _posts = posts;
|
const _SnPostCategory({required this.id, required this.slug, this.name, final List<SnPost> posts = const []}): _posts = posts,super._();
|
||||||
factory _PostCategory.fromJson(Map<String, dynamic> json) => _$PostCategoryFromJson(json);
|
factory _SnPostCategory.fromJson(Map<String, dynamic> json) => _$SnPostCategoryFromJson(json);
|
||||||
|
|
||||||
@override final String id;
|
@override final String id;
|
||||||
@override final String slug;
|
@override final String slug;
|
||||||
@@ -220,20 +220,20 @@ class _PostCategory implements PostCategory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Create a copy of PostCategory
|
/// Create a copy of SnPostCategory
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
_$PostCategoryCopyWith<_PostCategory> get copyWith => __$PostCategoryCopyWithImpl<_PostCategory>(this, _$identity);
|
_$SnPostCategoryCopyWith<_SnPostCategory> get copyWith => __$SnPostCategoryCopyWithImpl<_SnPostCategory>(this, _$identity);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
return _$PostCategoryToJson(this, );
|
return _$SnPostCategoryToJson(this, );
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _PostCategory&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other._posts, _posts));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPostCategory&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other._posts, _posts));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -242,15 +242,15 @@ int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEqu
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'PostCategory(id: $id, slug: $slug, name: $name, posts: $posts)';
|
return 'SnPostCategory(id: $id, slug: $slug, name: $name, posts: $posts)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
abstract mixin class _$PostCategoryCopyWith<$Res> implements $PostCategoryCopyWith<$Res> {
|
abstract mixin class _$SnPostCategoryCopyWith<$Res> implements $SnPostCategoryCopyWith<$Res> {
|
||||||
factory _$PostCategoryCopyWith(_PostCategory value, $Res Function(_PostCategory) _then) = __$PostCategoryCopyWithImpl;
|
factory _$SnPostCategoryCopyWith(_SnPostCategory value, $Res Function(_SnPostCategory) _then) = __$SnPostCategoryCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String id, String slug, String? name, List<SnPost> posts
|
String id, String slug, String? name, List<SnPost> posts
|
||||||
@@ -261,17 +261,17 @@ $Res call({
|
|||||||
|
|
||||||
}
|
}
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
class __$PostCategoryCopyWithImpl<$Res>
|
class __$SnPostCategoryCopyWithImpl<$Res>
|
||||||
implements _$PostCategoryCopyWith<$Res> {
|
implements _$SnPostCategoryCopyWith<$Res> {
|
||||||
__$PostCategoryCopyWithImpl(this._self, this._then);
|
__$SnPostCategoryCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
final _PostCategory _self;
|
final _SnPostCategory _self;
|
||||||
final $Res Function(_PostCategory) _then;
|
final $Res Function(_SnPostCategory) _then;
|
||||||
|
|
||||||
/// Create a copy of PostCategory
|
/// Create a copy of SnPostCategory
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? slug = null,Object? name = freezed,Object? posts = null,}) {
|
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? slug = null,Object? name = freezed,Object? posts = null,}) {
|
||||||
return _then(_PostCategory(
|
return _then(_SnPostCategory(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable
|
as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable
|
||||||
as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ part of 'post_category.dart';
|
|||||||
// JsonSerializableGenerator
|
// JsonSerializableGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
_PostCategory _$PostCategoryFromJson(Map<String, dynamic> json) =>
|
_SnPostCategory _$SnPostCategoryFromJson(Map<String, dynamic> json) =>
|
||||||
_PostCategory(
|
_SnPostCategory(
|
||||||
id: json['id'] as String,
|
id: json['id'] as String,
|
||||||
slug: json['slug'] as String,
|
slug: json['slug'] as String,
|
||||||
name: json['name'] as String?,
|
name: json['name'] as String?,
|
||||||
@@ -18,7 +18,7 @@ _PostCategory _$PostCategoryFromJson(Map<String, dynamic> json) =>
|
|||||||
const [],
|
const [],
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$PostCategoryToJson(_PostCategory instance) =>
|
Map<String, dynamic> _$SnPostCategoryToJson(_SnPostCategory instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'id': instance.id,
|
'id': instance.id,
|
||||||
'slug': instance.slug,
|
'slug': instance.slug,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:island/models/post.dart';
|
import 'package:island/models/post.dart';
|
||||||
|
|
||||||
@@ -6,14 +5,14 @@ part 'post_tag.freezed.dart';
|
|||||||
part 'post_tag.g.dart';
|
part 'post_tag.g.dart';
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
sealed class PostTag with _$PostTag {
|
sealed class SnPostTag with _$SnPostTag {
|
||||||
const factory PostTag({
|
const factory SnPostTag({
|
||||||
required String id,
|
required String id,
|
||||||
required String slug,
|
required String slug,
|
||||||
String? name,
|
String? name,
|
||||||
@Default([]) List<SnPost> posts,
|
@Default([]) List<SnPost> posts,
|
||||||
}) = _PostTag;
|
}) = _SnPostTag;
|
||||||
|
|
||||||
factory PostTag.fromJson(Map<String, dynamic> json) =>
|
factory SnPostTag.fromJson(Map<String, dynamic> json) =>
|
||||||
_$PostTagFromJson(json);
|
_$SnPostTagFromJson(json);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,22 +13,22 @@ part of 'post_tag.dart';
|
|||||||
T _$identity<T>(T value) => value;
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$PostTag {
|
mixin _$SnPostTag {
|
||||||
|
|
||||||
String get id; String get slug; String? get name; List<SnPost> get posts;
|
String get id; String get slug; String? get name; List<SnPost> get posts;
|
||||||
/// Create a copy of PostTag
|
/// Create a copy of SnPostTag
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
$PostTagCopyWith<PostTag> get copyWith => _$PostTagCopyWithImpl<PostTag>(this as PostTag, _$identity);
|
$SnPostTagCopyWith<SnPostTag> get copyWith => _$SnPostTagCopyWithImpl<SnPostTag>(this as SnPostTag, _$identity);
|
||||||
|
|
||||||
/// Serializes this PostTag to a JSON map.
|
/// Serializes this SnPostTag to a JSON map.
|
||||||
Map<String, dynamic> toJson();
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is PostTag&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other.posts, posts));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPostTag&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other.posts, posts));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -37,15 +37,15 @@ int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEqu
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'PostTag(id: $id, slug: $slug, name: $name, posts: $posts)';
|
return 'SnPostTag(id: $id, slug: $slug, name: $name, posts: $posts)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
abstract mixin class $PostTagCopyWith<$Res> {
|
abstract mixin class $SnPostTagCopyWith<$Res> {
|
||||||
factory $PostTagCopyWith(PostTag value, $Res Function(PostTag) _then) = _$PostTagCopyWithImpl;
|
factory $SnPostTagCopyWith(SnPostTag value, $Res Function(SnPostTag) _then) = _$SnPostTagCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String id, String slug, String? name, List<SnPost> posts
|
String id, String slug, String? name, List<SnPost> posts
|
||||||
@@ -56,14 +56,14 @@ $Res call({
|
|||||||
|
|
||||||
}
|
}
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
class _$PostTagCopyWithImpl<$Res>
|
class _$SnPostTagCopyWithImpl<$Res>
|
||||||
implements $PostTagCopyWith<$Res> {
|
implements $SnPostTagCopyWith<$Res> {
|
||||||
_$PostTagCopyWithImpl(this._self, this._then);
|
_$SnPostTagCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
final PostTag _self;
|
final SnPostTag _self;
|
||||||
final $Res Function(PostTag) _then;
|
final $Res Function(SnPostTag) _then;
|
||||||
|
|
||||||
/// Create a copy of PostTag
|
/// Create a copy of SnPostTag
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? slug = null,Object? name = freezed,Object? posts = null,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? slug = null,Object? name = freezed,Object? posts = null,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
@@ -78,8 +78,8 @@ as List<SnPost>,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Adds pattern-matching-related methods to [PostTag].
|
/// Adds pattern-matching-related methods to [SnPostTag].
|
||||||
extension PostTagPatterns on PostTag {
|
extension SnPostTagPatterns on SnPostTag {
|
||||||
/// A variant of `map` that fallback to returning `orElse`.
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
///
|
///
|
||||||
/// It is equivalent to doing:
|
/// It is equivalent to doing:
|
||||||
@@ -92,10 +92,10 @@ extension PostTagPatterns on PostTag {
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _PostTag value)? $default,{required TResult orElse(),}){
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnPostTag value)? $default,{required TResult orElse(),}){
|
||||||
final _that = this;
|
final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _PostTag() when $default != null:
|
case _SnPostTag() when $default != null:
|
||||||
return $default(_that);case _:
|
return $default(_that);case _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
@@ -114,10 +114,10 @@ return $default(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _PostTag value) $default,){
|
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnPostTag value) $default,){
|
||||||
final _that = this;
|
final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _PostTag():
|
case _SnPostTag():
|
||||||
return $default(_that);}
|
return $default(_that);}
|
||||||
}
|
}
|
||||||
/// A variant of `map` that fallback to returning `null`.
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
@@ -132,10 +132,10 @@ return $default(_that);}
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _PostTag value)? $default,){
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnPostTag value)? $default,){
|
||||||
final _that = this;
|
final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _PostTag() when $default != null:
|
case _SnPostTag() when $default != null:
|
||||||
return $default(_that);case _:
|
return $default(_that);case _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ return $default(_that);case _:
|
|||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String slug, String? name, List<SnPost> posts)? $default,{required TResult orElse(),}) {final _that = this;
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String slug, String? name, List<SnPost> posts)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _PostTag() when $default != null:
|
case _SnPostTag() when $default != null:
|
||||||
return $default(_that.id,_that.slug,_that.name,_that.posts);case _:
|
return $default(_that.id,_that.slug,_that.name,_that.posts);case _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
@@ -176,7 +176,7 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);case _:
|
|||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String slug, String? name, List<SnPost> posts) $default,) {final _that = this;
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String slug, String? name, List<SnPost> posts) $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _PostTag():
|
case _SnPostTag():
|
||||||
return $default(_that.id,_that.slug,_that.name,_that.posts);}
|
return $default(_that.id,_that.slug,_that.name,_that.posts);}
|
||||||
}
|
}
|
||||||
/// A variant of `when` that fallback to returning `null`
|
/// A variant of `when` that fallback to returning `null`
|
||||||
@@ -193,7 +193,7 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);}
|
|||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String slug, String? name, List<SnPost> posts)? $default,) {final _that = this;
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String slug, String? name, List<SnPost> posts)? $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _PostTag() when $default != null:
|
case _SnPostTag() when $default != null:
|
||||||
return $default(_that.id,_that.slug,_that.name,_that.posts);case _:
|
return $default(_that.id,_that.slug,_that.name,_that.posts);case _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@@ -205,9 +205,9 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);case _:
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _PostTag implements PostTag {
|
class _SnPostTag implements SnPostTag {
|
||||||
const _PostTag({required this.id, required this.slug, this.name, final List<SnPost> posts = const []}): _posts = posts;
|
const _SnPostTag({required this.id, required this.slug, this.name, final List<SnPost> posts = const []}): _posts = posts;
|
||||||
factory _PostTag.fromJson(Map<String, dynamic> json) => _$PostTagFromJson(json);
|
factory _SnPostTag.fromJson(Map<String, dynamic> json) => _$SnPostTagFromJson(json);
|
||||||
|
|
||||||
@override final String id;
|
@override final String id;
|
||||||
@override final String slug;
|
@override final String slug;
|
||||||
@@ -220,20 +220,20 @@ class _PostTag implements PostTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Create a copy of PostTag
|
/// Create a copy of SnPostTag
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
_$PostTagCopyWith<_PostTag> get copyWith => __$PostTagCopyWithImpl<_PostTag>(this, _$identity);
|
_$SnPostTagCopyWith<_SnPostTag> get copyWith => __$SnPostTagCopyWithImpl<_SnPostTag>(this, _$identity);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
return _$PostTagToJson(this, );
|
return _$SnPostTagToJson(this, );
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _PostTag&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other._posts, _posts));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPostTag&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other._posts, _posts));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -242,15 +242,15 @@ int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEqu
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'PostTag(id: $id, slug: $slug, name: $name, posts: $posts)';
|
return 'SnPostTag(id: $id, slug: $slug, name: $name, posts: $posts)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
abstract mixin class _$PostTagCopyWith<$Res> implements $PostTagCopyWith<$Res> {
|
abstract mixin class _$SnPostTagCopyWith<$Res> implements $SnPostTagCopyWith<$Res> {
|
||||||
factory _$PostTagCopyWith(_PostTag value, $Res Function(_PostTag) _then) = __$PostTagCopyWithImpl;
|
factory _$SnPostTagCopyWith(_SnPostTag value, $Res Function(_SnPostTag) _then) = __$SnPostTagCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String id, String slug, String? name, List<SnPost> posts
|
String id, String slug, String? name, List<SnPost> posts
|
||||||
@@ -261,17 +261,17 @@ $Res call({
|
|||||||
|
|
||||||
}
|
}
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
class __$PostTagCopyWithImpl<$Res>
|
class __$SnPostTagCopyWithImpl<$Res>
|
||||||
implements _$PostTagCopyWith<$Res> {
|
implements _$SnPostTagCopyWith<$Res> {
|
||||||
__$PostTagCopyWithImpl(this._self, this._then);
|
__$SnPostTagCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
final _PostTag _self;
|
final _SnPostTag _self;
|
||||||
final $Res Function(_PostTag) _then;
|
final $Res Function(_SnPostTag) _then;
|
||||||
|
|
||||||
/// Create a copy of PostTag
|
/// Create a copy of SnPostTag
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? slug = null,Object? name = freezed,Object? posts = null,}) {
|
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? slug = null,Object? name = freezed,Object? posts = null,}) {
|
||||||
return _then(_PostTag(
|
return _then(_SnPostTag(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable
|
as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable
|
||||||
as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ part of 'post_tag.dart';
|
|||||||
// JsonSerializableGenerator
|
// JsonSerializableGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
_PostTag _$PostTagFromJson(Map<String, dynamic> json) => _PostTag(
|
_SnPostTag _$SnPostTagFromJson(Map<String, dynamic> json) => _SnPostTag(
|
||||||
id: json['id'] as String,
|
id: json['id'] as String,
|
||||||
slug: json['slug'] as String,
|
slug: json['slug'] as String,
|
||||||
name: json['name'] as String?,
|
name: json['name'] as String?,
|
||||||
@@ -17,7 +17,8 @@ _PostTag _$PostTagFromJson(Map<String, dynamic> json) => _PostTag(
|
|||||||
const [],
|
const [],
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$PostTagToJson(_PostTag instance) => <String, dynamic>{
|
Map<String, dynamic> _$SnPostTagToJson(_SnPostTag instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
'id': instance.id,
|
'id': instance.id,
|
||||||
'slug': instance.slug,
|
'slug': instance.slug,
|
||||||
'name': instance.name,
|
'name': instance.name,
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ sealed class SnStickerPack with _$SnStickerPack {
|
|||||||
required DateTime createdAt,
|
required DateTime createdAt,
|
||||||
required DateTime updatedAt,
|
required DateTime updatedAt,
|
||||||
required DateTime? deletedAt,
|
required DateTime? deletedAt,
|
||||||
|
@Default([]) List<SnSticker> stickers,
|
||||||
}) = _SnStickerPack;
|
}) = _SnStickerPack;
|
||||||
|
|
||||||
factory SnStickerPack.fromJson(Map<String, dynamic> json) =>
|
factory SnStickerPack.fromJson(Map<String, dynamic> json) =>
|
||||||
|
|||||||
@@ -338,7 +338,7 @@ $SnStickerPackCopyWith<$Res>? get pack {
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$SnStickerPack {
|
mixin _$SnStickerPack {
|
||||||
|
|
||||||
String get id; String get name; String get description; String get prefix; String get publisherId; SnPublisher? get publisher; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
|
String get id; String get name; String get description; String get prefix; String get publisherId; SnPublisher? get publisher; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; List<SnSticker> get stickers;
|
||||||
/// Create a copy of SnStickerPack
|
/// Create a copy of SnStickerPack
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -351,16 +351,16 @@ $SnStickerPackCopyWith<SnStickerPack> get copyWith => _$SnStickerPackCopyWithImp
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnStickerPack&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.prefix, prefix) || other.prefix == prefix)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnStickerPack&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.prefix, prefix) || other.prefix == prefix)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other.stickers, stickers));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,id,name,description,prefix,publisherId,publisher,createdAt,updatedAt,deletedAt);
|
int get hashCode => Object.hash(runtimeType,id,name,description,prefix,publisherId,publisher,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(stickers));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SnStickerPack(id: $id, name: $name, description: $description, prefix: $prefix, publisherId: $publisherId, publisher: $publisher, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
return 'SnStickerPack(id: $id, name: $name, description: $description, prefix: $prefix, publisherId: $publisherId, publisher: $publisher, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, stickers: $stickers)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -371,7 +371,7 @@ abstract mixin class $SnStickerPackCopyWith<$Res> {
|
|||||||
factory $SnStickerPackCopyWith(SnStickerPack value, $Res Function(SnStickerPack) _then) = _$SnStickerPackCopyWithImpl;
|
factory $SnStickerPackCopyWith(SnStickerPack value, $Res Function(SnStickerPack) _then) = _$SnStickerPackCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnSticker> stickers
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -388,7 +388,7 @@ class _$SnStickerPackCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnStickerPack
|
/// Create a copy of SnStickerPack
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? description = null,Object? prefix = null,Object? publisherId = null,Object? publisher = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? description = null,Object? prefix = null,Object? publisherId = null,Object? publisher = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? stickers = null,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -399,7 +399,8 @@ as String,publisher: freezed == publisher ? _self.publisher : publisher // ignor
|
|||||||
as SnPublisher?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
as SnPublisher?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime?,
|
as DateTime?,stickers: null == stickers ? _self.stickers : stickers // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<SnSticker>,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
/// Create a copy of SnStickerPack
|
/// Create a copy of SnStickerPack
|
||||||
@@ -493,10 +494,10 @@ return $default(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnSticker> stickers)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SnStickerPack() when $default != null:
|
case _SnStickerPack() when $default != null:
|
||||||
return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.stickers);case _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -514,10 +515,10 @@ return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publish
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnSticker> stickers) $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SnStickerPack():
|
case _SnStickerPack():
|
||||||
return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt);}
|
return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.stickers);}
|
||||||
}
|
}
|
||||||
/// A variant of `when` that fallback to returning `null`
|
/// A variant of `when` that fallback to returning `null`
|
||||||
///
|
///
|
||||||
@@ -531,10 +532,10 @@ return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publish
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnSticker> stickers)? $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SnStickerPack() when $default != null:
|
case _SnStickerPack() when $default != null:
|
||||||
return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.stickers);case _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -546,7 +547,7 @@ return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publish
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _SnStickerPack implements SnStickerPack {
|
class _SnStickerPack implements SnStickerPack {
|
||||||
const _SnStickerPack({required this.id, required this.name, required this.description, required this.prefix, required this.publisherId, required this.publisher, required this.createdAt, required this.updatedAt, required this.deletedAt});
|
const _SnStickerPack({required this.id, required this.name, required this.description, required this.prefix, required this.publisherId, required this.publisher, required this.createdAt, required this.updatedAt, required this.deletedAt, final List<SnSticker> stickers = const []}): _stickers = stickers;
|
||||||
factory _SnStickerPack.fromJson(Map<String, dynamic> json) => _$SnStickerPackFromJson(json);
|
factory _SnStickerPack.fromJson(Map<String, dynamic> json) => _$SnStickerPackFromJson(json);
|
||||||
|
|
||||||
@override final String id;
|
@override final String id;
|
||||||
@@ -558,6 +559,13 @@ class _SnStickerPack implements SnStickerPack {
|
|||||||
@override final DateTime createdAt;
|
@override final DateTime createdAt;
|
||||||
@override final DateTime updatedAt;
|
@override final DateTime updatedAt;
|
||||||
@override final DateTime? deletedAt;
|
@override final DateTime? deletedAt;
|
||||||
|
final List<SnSticker> _stickers;
|
||||||
|
@override@JsonKey() List<SnSticker> get stickers {
|
||||||
|
if (_stickers is EqualUnmodifiableListView) return _stickers;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(_stickers);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Create a copy of SnStickerPack
|
/// Create a copy of SnStickerPack
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@@ -572,16 +580,16 @@ Map<String, dynamic> toJson() {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnStickerPack&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.prefix, prefix) || other.prefix == prefix)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnStickerPack&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.prefix, prefix) || other.prefix == prefix)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other._stickers, _stickers));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,id,name,description,prefix,publisherId,publisher,createdAt,updatedAt,deletedAt);
|
int get hashCode => Object.hash(runtimeType,id,name,description,prefix,publisherId,publisher,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(_stickers));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SnStickerPack(id: $id, name: $name, description: $description, prefix: $prefix, publisherId: $publisherId, publisher: $publisher, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
return 'SnStickerPack(id: $id, name: $name, description: $description, prefix: $prefix, publisherId: $publisherId, publisher: $publisher, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, stickers: $stickers)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -592,7 +600,7 @@ abstract mixin class _$SnStickerPackCopyWith<$Res> implements $SnStickerPackCopy
|
|||||||
factory _$SnStickerPackCopyWith(_SnStickerPack value, $Res Function(_SnStickerPack) _then) = __$SnStickerPackCopyWithImpl;
|
factory _$SnStickerPackCopyWith(_SnStickerPack value, $Res Function(_SnStickerPack) _then) = __$SnStickerPackCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnSticker> stickers
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -609,7 +617,7 @@ class __$SnStickerPackCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnStickerPack
|
/// Create a copy of SnStickerPack
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? description = null,Object? prefix = null,Object? publisherId = null,Object? publisher = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? description = null,Object? prefix = null,Object? publisherId = null,Object? publisher = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? stickers = null,}) {
|
||||||
return _then(_SnStickerPack(
|
return _then(_SnStickerPack(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -620,7 +628,8 @@ as String,publisher: freezed == publisher ? _self.publisher : publisher // ignor
|
|||||||
as SnPublisher?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
as SnPublisher?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime?,
|
as DateTime?,stickers: null == stickers ? _self._stickers : stickers // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<SnSticker>,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,11 @@ _SnStickerPack _$SnStickerPackFromJson(Map<String, dynamic> json) =>
|
|||||||
json['deleted_at'] == null
|
json['deleted_at'] == null
|
||||||
? null
|
? null
|
||||||
: DateTime.parse(json['deleted_at'] as String),
|
: DateTime.parse(json['deleted_at'] as String),
|
||||||
|
stickers:
|
||||||
|
(json['stickers'] as List<dynamic>?)
|
||||||
|
?.map((e) => SnSticker.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
const [],
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$SnStickerPackToJson(_SnStickerPack instance) =>
|
Map<String, dynamic> _$SnStickerPackToJson(_SnStickerPack instance) =>
|
||||||
@@ -67,4 +72,5 @@ Map<String, dynamic> _$SnStickerPackToJson(_SnStickerPack instance) =>
|
|||||||
'created_at': instance.createdAt.toIso8601String(),
|
'created_at': instance.createdAt.toIso8601String(),
|
||||||
'updated_at': instance.updatedAt.toIso8601String(),
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
|
'stickers': instance.stickers.map((e) => e.toJson()).toList(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
@@ -18,6 +17,8 @@ sealed class WebSocketState with _$WebSocketState {
|
|||||||
const factory WebSocketState.connected() = _Connected;
|
const factory WebSocketState.connected() = _Connected;
|
||||||
const factory WebSocketState.connecting() = _Connecting;
|
const factory WebSocketState.connecting() = _Connecting;
|
||||||
const factory WebSocketState.disconnected() = _Disconnected;
|
const factory WebSocketState.disconnected() = _Disconnected;
|
||||||
|
const factory WebSocketState.serverDown() = _ServerDown;
|
||||||
|
const factory WebSocketState.duplicateDevice() = _DuplicateDevice;
|
||||||
const factory WebSocketState.error(String message) = _Error;
|
const factory WebSocketState.error(String message) = _Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +50,7 @@ class WebSocketService {
|
|||||||
Timer? _heartbeatTimer;
|
Timer? _heartbeatTimer;
|
||||||
|
|
||||||
DateTime? _heartbeatAt;
|
DateTime? _heartbeatAt;
|
||||||
Duration? _heartbeatDelay;
|
Duration? heartbeatDelay;
|
||||||
|
|
||||||
Stream<WebSocketPacket> get dataStream => _streamController.stream;
|
Stream<WebSocketPacket> get dataStream => _streamController.stream;
|
||||||
Stream<WebSocketState> get statusStream => _statusStreamController.stream;
|
Stream<WebSocketState> get statusStream => _statusStreamController.stream;
|
||||||
@@ -81,15 +82,20 @@ class WebSocketService {
|
|||||||
final dataStr =
|
final dataStr =
|
||||||
data is Uint8List ? utf8.decode(data) : data.toString();
|
data is Uint8List ? utf8.decode(data) : data.toString();
|
||||||
final packet = WebSocketPacket.fromJson(jsonDecode(dataStr));
|
final packet = WebSocketPacket.fromJson(jsonDecode(dataStr));
|
||||||
|
if (packet.type == 'error.dupe') {
|
||||||
|
_statusStreamController.sink.add(WebSocketState.duplicateDevice());
|
||||||
|
_channel!.sink.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
_streamController.sink.add(packet);
|
_streamController.sink.add(packet);
|
||||||
log(
|
log(
|
||||||
"[WebSocket] Received packet: ${packet.type} ${packet.errorMessage}",
|
"[WebSocket] Received packet: ${packet.type} ${packet.errorMessage}",
|
||||||
);
|
);
|
||||||
if (packet.type == 'pong' && _heartbeatAt != null) {
|
if (packet.type == 'pong' && _heartbeatAt != null) {
|
||||||
var now = DateTime.now();
|
var now = DateTime.now();
|
||||||
_heartbeatDelay = now.difference(_heartbeatAt!);
|
heartbeatDelay = now.difference(_heartbeatAt!);
|
||||||
log(
|
log(
|
||||||
"[WebSocket] Server respond last heartbeat for ${_heartbeatDelay!.inMilliseconds} ms",
|
"[WebSocket] Server respond last heartbeat for ${heartbeatDelay!.inMilliseconds} ms",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -61,13 +61,15 @@ extension WebSocketStatePatterns on WebSocketState {
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>({TResult Function( _Connected value)? connected,TResult Function( _Connecting value)? connecting,TResult Function( _Disconnected value)? disconnected,TResult Function( _Error value)? error,required TResult orElse(),}){
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>({TResult Function( _Connected value)? connected,TResult Function( _Connecting value)? connecting,TResult Function( _Disconnected value)? disconnected,TResult Function( _ServerDown value)? serverDown,TResult Function( _DuplicateDevice value)? duplicateDevice,TResult Function( _Error value)? error,required TResult orElse(),}){
|
||||||
final _that = this;
|
final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _Connected() when connected != null:
|
case _Connected() when connected != null:
|
||||||
return connected(_that);case _Connecting() when connecting != null:
|
return connected(_that);case _Connecting() when connecting != null:
|
||||||
return connecting(_that);case _Disconnected() when disconnected != null:
|
return connecting(_that);case _Disconnected() when disconnected != null:
|
||||||
return disconnected(_that);case _Error() when error != null:
|
return disconnected(_that);case _ServerDown() when serverDown != null:
|
||||||
|
return serverDown(_that);case _DuplicateDevice() when duplicateDevice != null:
|
||||||
|
return duplicateDevice(_that);case _Error() when error != null:
|
||||||
return error(_that);case _:
|
return error(_that);case _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
@@ -86,13 +88,15 @@ return error(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult map<TResult extends Object?>({required TResult Function( _Connected value) connected,required TResult Function( _Connecting value) connecting,required TResult Function( _Disconnected value) disconnected,required TResult Function( _Error value) error,}){
|
@optionalTypeArgs TResult map<TResult extends Object?>({required TResult Function( _Connected value) connected,required TResult Function( _Connecting value) connecting,required TResult Function( _Disconnected value) disconnected,required TResult Function( _ServerDown value) serverDown,required TResult Function( _DuplicateDevice value) duplicateDevice,required TResult Function( _Error value) error,}){
|
||||||
final _that = this;
|
final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _Connected():
|
case _Connected():
|
||||||
return connected(_that);case _Connecting():
|
return connected(_that);case _Connecting():
|
||||||
return connecting(_that);case _Disconnected():
|
return connecting(_that);case _Disconnected():
|
||||||
return disconnected(_that);case _Error():
|
return disconnected(_that);case _ServerDown():
|
||||||
|
return serverDown(_that);case _DuplicateDevice():
|
||||||
|
return duplicateDevice(_that);case _Error():
|
||||||
return error(_that);}
|
return error(_that);}
|
||||||
}
|
}
|
||||||
/// A variant of `map` that fallback to returning `null`.
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
@@ -107,13 +111,15 @@ return error(_that);}
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>({TResult? Function( _Connected value)? connected,TResult? Function( _Connecting value)? connecting,TResult? Function( _Disconnected value)? disconnected,TResult? Function( _Error value)? error,}){
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>({TResult? Function( _Connected value)? connected,TResult? Function( _Connecting value)? connecting,TResult? Function( _Disconnected value)? disconnected,TResult? Function( _ServerDown value)? serverDown,TResult? Function( _DuplicateDevice value)? duplicateDevice,TResult? Function( _Error value)? error,}){
|
||||||
final _that = this;
|
final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _Connected() when connected != null:
|
case _Connected() when connected != null:
|
||||||
return connected(_that);case _Connecting() when connecting != null:
|
return connected(_that);case _Connecting() when connecting != null:
|
||||||
return connecting(_that);case _Disconnected() when disconnected != null:
|
return connecting(_that);case _Disconnected() when disconnected != null:
|
||||||
return disconnected(_that);case _Error() when error != null:
|
return disconnected(_that);case _ServerDown() when serverDown != null:
|
||||||
|
return serverDown(_that);case _DuplicateDevice() when duplicateDevice != null:
|
||||||
|
return duplicateDevice(_that);case _Error() when error != null:
|
||||||
return error(_that);case _:
|
return error(_that);case _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@@ -131,12 +137,14 @@ return error(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>({TResult Function()? connected,TResult Function()? connecting,TResult Function()? disconnected,TResult Function( String message)? error,required TResult orElse(),}) {final _that = this;
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>({TResult Function()? connected,TResult Function()? connecting,TResult Function()? disconnected,TResult Function()? serverDown,TResult Function()? duplicateDevice,TResult Function( String message)? error,required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _Connected() when connected != null:
|
case _Connected() when connected != null:
|
||||||
return connected();case _Connecting() when connecting != null:
|
return connected();case _Connecting() when connecting != null:
|
||||||
return connecting();case _Disconnected() when disconnected != null:
|
return connecting();case _Disconnected() when disconnected != null:
|
||||||
return disconnected();case _Error() when error != null:
|
return disconnected();case _ServerDown() when serverDown != null:
|
||||||
|
return serverDown();case _DuplicateDevice() when duplicateDevice != null:
|
||||||
|
return duplicateDevice();case _Error() when error != null:
|
||||||
return error(_that.message);case _:
|
return error(_that.message);case _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
@@ -155,12 +163,14 @@ return error(_that.message);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>({required TResult Function() connected,required TResult Function() connecting,required TResult Function() disconnected,required TResult Function( String message) error,}) {final _that = this;
|
@optionalTypeArgs TResult when<TResult extends Object?>({required TResult Function() connected,required TResult Function() connecting,required TResult Function() disconnected,required TResult Function() serverDown,required TResult Function() duplicateDevice,required TResult Function( String message) error,}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _Connected():
|
case _Connected():
|
||||||
return connected();case _Connecting():
|
return connected();case _Connecting():
|
||||||
return connecting();case _Disconnected():
|
return connecting();case _Disconnected():
|
||||||
return disconnected();case _Error():
|
return disconnected();case _ServerDown():
|
||||||
|
return serverDown();case _DuplicateDevice():
|
||||||
|
return duplicateDevice();case _Error():
|
||||||
return error(_that.message);}
|
return error(_that.message);}
|
||||||
}
|
}
|
||||||
/// A variant of `when` that fallback to returning `null`
|
/// A variant of `when` that fallback to returning `null`
|
||||||
@@ -175,12 +185,14 @@ return error(_that.message);}
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>({TResult? Function()? connected,TResult? Function()? connecting,TResult? Function()? disconnected,TResult? Function( String message)? error,}) {final _that = this;
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>({TResult? Function()? connected,TResult? Function()? connecting,TResult? Function()? disconnected,TResult? Function()? serverDown,TResult? Function()? duplicateDevice,TResult? Function( String message)? error,}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _Connected() when connected != null:
|
case _Connected() when connected != null:
|
||||||
return connected();case _Connecting() when connecting != null:
|
return connected();case _Connecting() when connecting != null:
|
||||||
return connecting();case _Disconnected() when disconnected != null:
|
return connecting();case _Disconnected() when disconnected != null:
|
||||||
return disconnected();case _Error() when error != null:
|
return disconnected();case _ServerDown() when serverDown != null:
|
||||||
|
return serverDown();case _DuplicateDevice() when duplicateDevice != null:
|
||||||
|
return duplicateDevice();case _Error() when error != null:
|
||||||
return error(_that.message);case _:
|
return error(_that.message);case _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@@ -303,6 +315,82 @@ String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
|
||||||
|
class _ServerDown with DiagnosticableTreeMixin implements WebSocketState {
|
||||||
|
const _ServerDown();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
|
properties
|
||||||
|
..add(DiagnosticsProperty('type', 'WebSocketState.serverDown'))
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ServerDown);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => runtimeType.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
|
||||||
|
return 'WebSocketState.serverDown()';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
|
||||||
|
class _DuplicateDevice with DiagnosticableTreeMixin implements WebSocketState {
|
||||||
|
const _DuplicateDevice();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
|
properties
|
||||||
|
..add(DiagnosticsProperty('type', 'WebSocketState.duplicateDevice'))
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _DuplicateDevice);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => runtimeType.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
|
||||||
|
return 'WebSocketState.duplicateDevice()';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:island/screens/developers/edit_app.dart';
|
|||||||
import 'package:island/screens/developers/new_app.dart';
|
import 'package:island/screens/developers/new_app.dart';
|
||||||
import 'package:island/screens/developers/hub.dart';
|
import 'package:island/screens/developers/hub.dart';
|
||||||
import 'package:island/screens/discovery/articles.dart';
|
import 'package:island/screens/discovery/articles.dart';
|
||||||
|
import 'package:island/screens/posts/post_category_detail.dart';
|
||||||
import 'package:island/screens/posts/post_search.dart';
|
import 'package:island/screens/posts/post_search.dart';
|
||||||
import 'package:island/widgets/app_wrapper.dart';
|
import 'package:island/widgets/app_wrapper.dart';
|
||||||
import 'package:island/screens/tabs.dart';
|
import 'package:island/screens/tabs.dart';
|
||||||
@@ -28,6 +29,8 @@ import 'package:island/screens/creators/hub.dart';
|
|||||||
import 'package:island/screens/creators/posts/post_manage_list.dart';
|
import 'package:island/screens/creators/posts/post_manage_list.dart';
|
||||||
import 'package:island/screens/creators/stickers/stickers.dart';
|
import 'package:island/screens/creators/stickers/stickers.dart';
|
||||||
import 'package:island/screens/creators/stickers/pack_detail.dart';
|
import 'package:island/screens/creators/stickers/pack_detail.dart';
|
||||||
|
import 'package:island/screens/stickers/marketplace.dart';
|
||||||
|
import 'package:island/screens/stickers/pack_detail.dart';
|
||||||
import 'package:island/screens/creators/poll/poll_list.dart';
|
import 'package:island/screens/creators/poll/poll_list.dart';
|
||||||
import 'package:island/screens/creators/publishers.dart';
|
import 'package:island/screens/creators/publishers.dart';
|
||||||
import 'package:island/screens/creators/webfeed/webfeed_list.dart';
|
import 'package:island/screens/creators/webfeed/webfeed_list.dart';
|
||||||
@@ -320,15 +323,6 @@ final routerProvider = Provider<GoRouter>((ref) {
|
|||||||
builder: (context, state) => const AboutScreen(),
|
builder: (context, state) => const AboutScreen(),
|
||||||
),
|
),
|
||||||
|
|
||||||
GoRoute(
|
|
||||||
name: 'reportDetail',
|
|
||||||
path: '/safety/reports/me/:id',
|
|
||||||
builder: (context, state) {
|
|
||||||
final id = state.pathParameters['id']!;
|
|
||||||
return AbuseReportDetailScreen(reportId: id);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
// Main tabs with TabsScreen shell
|
// Main tabs with TabsScreen shell
|
||||||
ShellRoute(
|
ShellRoute(
|
||||||
navigatorKey: _tabsShellKey,
|
navigatorKey: _tabsShellKey,
|
||||||
@@ -355,6 +349,25 @@ final routerProvider = Provider<GoRouter>((ref) {
|
|||||||
return PostDetailScreen(id: id);
|
return PostDetailScreen(id: id);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
name: 'postCategoryDetail',
|
||||||
|
path: '/posts/categories/:slug',
|
||||||
|
builder: (context, state) {
|
||||||
|
final slug = state.pathParameters['slug']!;
|
||||||
|
return PostCategoryDetailScreen(slug: slug, isCategory: true);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
name: 'postTagDetail',
|
||||||
|
path: '/posts/tags/:slug',
|
||||||
|
builder: (context, state) {
|
||||||
|
final slug = state.pathParameters['slug']!;
|
||||||
|
return PostCategoryDetailScreen(
|
||||||
|
slug: slug,
|
||||||
|
isCategory: false,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
name: 'publisherProfile',
|
name: 'publisherProfile',
|
||||||
path: '/publishers/:name',
|
path: '/publishers/:name',
|
||||||
@@ -451,6 +464,23 @@ final routerProvider = Provider<GoRouter>((ref) {
|
|||||||
path: '/account',
|
path: '/account',
|
||||||
builder: (context, state) => const AccountScreen(),
|
builder: (context, state) => const AccountScreen(),
|
||||||
),
|
),
|
||||||
|
// Sticker marketplace (user-facing, no publisher)
|
||||||
|
GoRoute(
|
||||||
|
name: 'stickerMarketplace',
|
||||||
|
path: '/stickers',
|
||||||
|
builder:
|
||||||
|
(context, state) => const MarketplaceStickersScreen(),
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
name: 'stickerPackDetail',
|
||||||
|
path: ':packId',
|
||||||
|
builder: (context, state) {
|
||||||
|
final packId = state.pathParameters['packId']!;
|
||||||
|
return MarketplaceStickerPackDetailScreen(id: packId);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
name: 'notifications',
|
name: 'notifications',
|
||||||
path: '/account/notifications',
|
path: '/account/notifications',
|
||||||
@@ -486,6 +516,14 @@ final routerProvider = Provider<GoRouter>((ref) {
|
|||||||
path: '/safety/reports/me',
|
path: '/safety/reports/me',
|
||||||
builder: (context, state) => const AbuseReportListScreen(),
|
builder: (context, state) => const AbuseReportListScreen(),
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
name: 'reportDetail',
|
||||||
|
path: '/safety/reports/me/:id',
|
||||||
|
builder: (context, state) {
|
||||||
|
final id = state.pathParameters['id']!;
|
||||||
|
return AbuseReportDetailScreen(reportId: id);
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import 'package:island/widgets/app_scaffold.dart';
|
|||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:island/services/update_service.dart';
|
||||||
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
@@ -190,6 +192,45 @@ class _AboutScreenState extends ConsumerState<AboutScreen> {
|
|||||||
context,
|
context,
|
||||||
title: 'aboutScreenLinksSectionTitle'.tr(),
|
title: 'aboutScreenLinksSectionTitle'.tr(),
|
||||||
children: [
|
children: [
|
||||||
|
_buildListTile(
|
||||||
|
context,
|
||||||
|
icon: Symbols.system_update,
|
||||||
|
title: 'Check for updates',
|
||||||
|
onTap: () async {
|
||||||
|
// Fetch latest release and show the unified sheet
|
||||||
|
final svc = UpdateService();
|
||||||
|
// Reuse service fetch + compare to decide content
|
||||||
|
final release = await svc.fetchLatestRelease();
|
||||||
|
if (release != null) {
|
||||||
|
await svc.showUpdateSheet(context, release);
|
||||||
|
} else {
|
||||||
|
// Fallback: show a simple sheet indicating no info
|
||||||
|
// Use your SheetScaffold for consistent styling
|
||||||
|
// Show a minimal message
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
useSafeArea: true,
|
||||||
|
showDragHandle: true,
|
||||||
|
backgroundColor:
|
||||||
|
Theme.of(context).colorScheme.surface,
|
||||||
|
builder:
|
||||||
|
(_) => const SheetScaffold(
|
||||||
|
titleText: 'Update',
|
||||||
|
child: Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(24),
|
||||||
|
child: Text(
|
||||||
|
'Unable to fetch release info at this time.',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
_buildListTile(
|
_buildListTile(
|
||||||
context,
|
context,
|
||||||
icon: Symbols.privacy_tip,
|
icon: Symbols.privacy_tip,
|
||||||
@@ -205,7 +246,7 @@ class _AboutScreenState extends ConsumerState<AboutScreen> {
|
|||||||
title: 'aboutScreenTermsOfServiceTitle'.tr(),
|
title: 'aboutScreenTermsOfServiceTitle'.tr(),
|
||||||
onTap:
|
onTap:
|
||||||
() => _launchURL(
|
() => _launchURL(
|
||||||
'https://solsynth.dev/terms/basic-law',
|
'https://solsynth.dev/terms/user-agreement',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
_buildListTile(
|
_buildListTile(
|
||||||
|
|||||||
@@ -189,7 +189,6 @@ class AccountScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
).padding(horizontal: 8),
|
).padding(horizontal: 8),
|
||||||
const Gap(8),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
minTileHeight: 48,
|
minTileHeight: 48,
|
||||||
leading: const Icon(Symbols.notifications),
|
leading: const Icon(Symbols.notifications),
|
||||||
@@ -228,6 +227,16 @@ class AccountScreen extends HookConsumerWidget {
|
|||||||
context.pushNamed('relationships');
|
context.pushNamed('relationships');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
minTileHeight: 48,
|
||||||
|
leading: const Icon(Symbols.emoji_emotions),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
title: Text('stickers').tr(),
|
||||||
|
onTap: () {
|
||||||
|
context.pushNamed('stickerMarketplace');
|
||||||
|
},
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
minTileHeight: 48,
|
minTileHeight: 48,
|
||||||
title: Text('abuseReports').tr(),
|
title: Text('abuseReports').tr(),
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import 'package:material_symbols_icons/symbols.dart';
|
|||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'chat.dart';
|
import 'chat.dart';
|
||||||
import 'package:island/widgets/chat/call_button.dart';
|
import 'package:island/widgets/chat/call_button.dart';
|
||||||
|
import 'package:island/widgets/stickers/picker.dart';
|
||||||
|
|
||||||
part 'room.g.dart';
|
part 'room.g.dart';
|
||||||
|
|
||||||
@@ -1066,7 +1067,10 @@ class _ChatInput extends HookConsumerWidget {
|
|||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
itemCount: attachments.length,
|
itemCount: attachments.length,
|
||||||
itemBuilder: (context, idx) {
|
itemBuilder: (context, idx) {
|
||||||
return AttachmentPreview(
|
return SizedBox(
|
||||||
|
height: 280,
|
||||||
|
width: 280,
|
||||||
|
child: AttachmentPreview(
|
||||||
item: attachments[idx],
|
item: attachments[idx],
|
||||||
onRequestUpload: () => onUploadAttachment(idx),
|
onRequestUpload: () => onUploadAttachment(idx),
|
||||||
onDelete: () => onDeleteAttachment(idx),
|
onDelete: () => onDeleteAttachment(idx),
|
||||||
@@ -1075,6 +1079,7 @@ class _ChatInput extends HookConsumerWidget {
|
|||||||
onAttachmentsChanged(attachments);
|
onAttachmentsChanged(attachments);
|
||||||
},
|
},
|
||||||
onMove: (delta) => onMoveAttachment(idx, delta),
|
onMove: (delta) => onMoveAttachment(idx, delta),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
separatorBuilder: (_, _) => const Gap(8),
|
separatorBuilder: (_, _) => const Gap(8),
|
||||||
@@ -1129,6 +1134,49 @@ class _ChatInput extends HookConsumerWidget {
|
|||||||
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 8),
|
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 8),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
tooltip: 'stickers'.tr(),
|
||||||
|
icon: const Icon(Symbols.emoji_symbols),
|
||||||
|
onPressed: () {
|
||||||
|
final size = MediaQuery.of(context).size;
|
||||||
|
showStickerPickerPopover(
|
||||||
|
context,
|
||||||
|
Offset(
|
||||||
|
20,
|
||||||
|
size.height -
|
||||||
|
480 -
|
||||||
|
MediaQuery.of(context).padding.bottom,
|
||||||
|
),
|
||||||
|
onPick: (placeholder) {
|
||||||
|
// Insert placeholder at current cursor position
|
||||||
|
final text = messageController.text;
|
||||||
|
final selection = messageController.selection;
|
||||||
|
final start =
|
||||||
|
selection.start >= 0
|
||||||
|
? selection.start
|
||||||
|
: text.length;
|
||||||
|
final end =
|
||||||
|
selection.end >= 0
|
||||||
|
? selection.end
|
||||||
|
: text.length;
|
||||||
|
final newText = text.replaceRange(
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
placeholder,
|
||||||
|
);
|
||||||
|
messageController.value = TextEditingValue(
|
||||||
|
text: newText,
|
||||||
|
selection: TextSelection.collapsed(
|
||||||
|
offset: start + placeholder.length,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
PopupMenuButton(
|
PopupMenuButton(
|
||||||
icon: const Icon(Symbols.photo_library),
|
icon: const Icon(Symbols.photo_library),
|
||||||
itemBuilder:
|
itemBuilder:
|
||||||
@@ -1155,6 +1203,8 @@ class _ChatInput extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: RawKeyboardListener(
|
child: RawKeyboardListener(
|
||||||
focusNode: FocusNode(),
|
focusNode: FocusNode(),
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/models/poll.dart';
|
import 'package:island/models/poll.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:island/widgets/poll/poll_feedback.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
||||||
@@ -164,10 +165,13 @@ class _CreatorPollItem extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// Open editor for edit
|
showModalBottomSheet(
|
||||||
// Navigator push by path to keep consistency with rest of app:
|
context: context,
|
||||||
// Note: pub name string may be required in route; when absent, route may need query or pick later.
|
useRootNavigator: true,
|
||||||
// For safety, just do nothing if no publisher in list item.
|
isScrollControlled: true,
|
||||||
|
builder:
|
||||||
|
(context) => PollFeedbackSheet(pollId: poll.id, poll: poll),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ part 'apps.g.dart';
|
|||||||
@riverpod
|
@riverpod
|
||||||
Future<List<CustomApp>> customApps(Ref ref, String publisherName) async {
|
Future<List<CustomApp>> customApps(Ref ref, String publisherName) async {
|
||||||
final client = ref.watch(apiClientProvider);
|
final client = ref.watch(apiClientProvider);
|
||||||
final resp = await client.get('/developers/$publisherName/apps');
|
final resp = await client.get('/develop/developers/$publisherName/apps');
|
||||||
return resp.data.map((e) => CustomApp.fromJson(e)).cast<CustomApp>().toList();
|
return resp.data.map((e) => CustomApp.fromJson(e)).cast<CustomApp>().toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +37,10 @@ class CustomAppsScreen extends HookConsumerWidget {
|
|||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Symbols.add),
|
icon: const Icon(Symbols.add),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.pushNamed('developerAppNew', pathParameters: {'name': publisherName});
|
context.pushNamed(
|
||||||
|
'developerAppNew',
|
||||||
|
pathParameters: {'name': publisherName},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -121,7 +124,13 @@ class CustomAppsScreen extends HookConsumerWidget {
|
|||||||
],
|
],
|
||||||
onSelected: (value) {
|
onSelected: (value) {
|
||||||
if (value == 'edit') {
|
if (value == 'edit') {
|
||||||
context.pushNamed('developerAppEdit', pathParameters: {'name': publisherName, 'id': app.id});
|
context.pushNamed(
|
||||||
|
'developerAppEdit',
|
||||||
|
pathParameters: {
|
||||||
|
'name': publisherName,
|
||||||
|
'id': app.id,
|
||||||
|
},
|
||||||
|
);
|
||||||
} else if (value == 'delete') {
|
} else if (value == 'delete') {
|
||||||
showConfirmAlert(
|
showConfirmAlert(
|
||||||
'deleteCustomAppHint'.tr(),
|
'deleteCustomAppHint'.tr(),
|
||||||
@@ -130,7 +139,7 @@ class CustomAppsScreen extends HookConsumerWidget {
|
|||||||
if (confirm) {
|
if (confirm) {
|
||||||
final client = ref.read(apiClientProvider);
|
final client = ref.read(apiClientProvider);
|
||||||
client.delete(
|
client.delete(
|
||||||
'/developers/$publisherName/apps/${app.id}',
|
'/develop/developers/$publisherName/apps/${app.id}',
|
||||||
);
|
);
|
||||||
ref.invalidate(
|
ref.invalidate(
|
||||||
customAppsProvider(publisherName),
|
customAppsProvider(publisherName),
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ part of 'apps.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$customAppsHash() => r'1dec11573b9d987c3adbdf4732b3781a6f40172a';
|
String _$customAppsHash() => r'c6ac78060eb51a2b208a749a81ecbe0a9c608ce1';
|
||||||
|
|
||||||
/// Copied from Dart SDK
|
/// Copied from Dart SDK
|
||||||
class _SystemHash {
|
class _SystemHash {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ part 'edit_app.g.dart';
|
|||||||
@riverpod
|
@riverpod
|
||||||
Future<CustomApp?> customApp(Ref ref, String publisherName, String id) async {
|
Future<CustomApp?> customApp(Ref ref, String publisherName, String id) async {
|
||||||
final client = ref.watch(apiClientProvider);
|
final client = ref.watch(apiClientProvider);
|
||||||
final resp = await client.get('/developers/$publisherName/apps/$id');
|
final resp = await client.get('/develop/developers/$publisherName/apps/$id');
|
||||||
return CustomApp.fromJson(resp.data);
|
return CustomApp.fromJson(resp.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,9 +282,15 @@ class EditAppScreen extends HookConsumerWidget {
|
|||||||
: null,
|
: null,
|
||||||
};
|
};
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
await client.post('/developers/$publisherName/apps', data: data);
|
await client.post(
|
||||||
|
'/develop/developers/$publisherName/apps',
|
||||||
|
data: data,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
await client.patch('/developers/$publisherName/apps/$id', data: data);
|
await client.patch(
|
||||||
|
'/develop/developers/$publisherName/apps/$id',
|
||||||
|
data: data,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
ref.invalidate(customAppsProvider(publisherName));
|
ref.invalidate(customAppsProvider(publisherName));
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ part of 'edit_app.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$customAppHash() => r'aa4d1fb803c47a99cbacf6d91481f4fce3fda457';
|
String _$customAppHash() => r'42ad937b8439c793e3c5c35568bb5fa4da017df3';
|
||||||
|
|
||||||
/// Copied from Dart SDK
|
/// Copied from Dart SDK
|
||||||
class _SystemHash {
|
class _SystemHash {
|
||||||
|
|||||||
@@ -25,14 +25,14 @@ part 'hub.g.dart';
|
|||||||
Future<DeveloperStats?> developerStats(Ref ref, String? uname) async {
|
Future<DeveloperStats?> developerStats(Ref ref, String? uname) async {
|
||||||
if (uname == null) return null;
|
if (uname == null) return null;
|
||||||
final apiClient = ref.watch(apiClientProvider);
|
final apiClient = ref.watch(apiClientProvider);
|
||||||
final resp = await apiClient.get('/sphere/developers/$uname/stats');
|
final resp = await apiClient.get('/develop/developers/$uname/stats');
|
||||||
return DeveloperStats.fromJson(resp.data);
|
return DeveloperStats.fromJson(resp.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@riverpod
|
@riverpod
|
||||||
Future<List<SnPublisher>> developers(Ref ref) async {
|
Future<List<SnPublisher>> developers(Ref ref) async {
|
||||||
final client = ref.watch(apiClientProvider);
|
final client = ref.watch(apiClientProvider);
|
||||||
final resp = await client.get('/sphere/developers');
|
final resp = await client.get('/develop/developers');
|
||||||
return resp.data
|
return resp.data
|
||||||
.map((e) => SnPublisher.fromJson(e))
|
.map((e) => SnPublisher.fromJson(e))
|
||||||
.cast<SnPublisher>()
|
.cast<SnPublisher>()
|
||||||
@@ -336,7 +336,7 @@ class _DeveloperEnrollmentSheet extends HookConsumerWidget {
|
|||||||
Future<void> enroll(SnPublisher publisher) async {
|
Future<void> enroll(SnPublisher publisher) async {
|
||||||
try {
|
try {
|
||||||
final client = ref.read(apiClientProvider);
|
final client = ref.read(apiClientProvider);
|
||||||
await client.post('/sphere/developers/${publisher.name}/enroll');
|
await client.post('/develop/developers/${publisher.name}/enroll');
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
Navigator.pop(context, true);
|
Navigator.pop(context, true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ part of 'hub.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$developerStatsHash() => r'baa708f3586e8987e221cc8ab825d759658c0f55';
|
String _$developerStatsHash() => r'45546f29ec7cd1a9c3a4e0f4e39275e78bf34755';
|
||||||
|
|
||||||
/// Copied from Dart SDK
|
/// Copied from Dart SDK
|
||||||
class _SystemHash {
|
class _SystemHash {
|
||||||
@@ -149,7 +149,7 @@ class _DeveloperStatsProviderElement
|
|||||||
String? get uname => (origin as DeveloperStatsProvider).uname;
|
String? get uname => (origin as DeveloperStatsProvider).uname;
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$developersHash() => r'f11335fdf553c661110281edeec70ef89c64727d';
|
String _$developersHash() => r'04f25db31f511f651a5add128d56631236ed0b39';
|
||||||
|
|
||||||
/// See also [developers].
|
/// See also [developers].
|
||||||
@ProviderFor(developers)
|
@ProviderFor(developers)
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ import 'package:island/models/webfeed.dart';
|
|||||||
import 'package:island/pods/event_calendar.dart';
|
import 'package:island/pods/event_calendar.dart';
|
||||||
import 'package:island/pods/userinfo.dart';
|
import 'package:island/pods/userinfo.dart';
|
||||||
import 'package:island/services/responsive.dart';
|
import 'package:island/services/responsive.dart';
|
||||||
import 'package:island/widgets/account/event_calendar.dart';
|
|
||||||
import 'package:island/widgets/account/fortune_graph.dart';
|
import 'package:island/widgets/account/fortune_graph.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:island/models/post.dart';
|
import 'package:island/models/post.dart';
|
||||||
import 'package:island/widgets/check_in.dart';
|
import 'package:island/widgets/check_in.dart';
|
||||||
|
import 'package:island/widgets/post/post_featured.dart';
|
||||||
import 'package:island/widgets/post/post_item.dart';
|
import 'package:island/widgets/post/post_item.dart';
|
||||||
import 'package:island/screens/tabs.dart';
|
import 'package:island/screens/tabs.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
@@ -70,15 +70,6 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
final events = ref.watch(eventCalendarProvider(query.value));
|
final events = ref.watch(eventCalendarProvider(query.value));
|
||||||
|
|
||||||
final selectedDay = useState(now);
|
final selectedDay = useState(now);
|
||||||
|
|
||||||
void onMonthChanged(int year, int month) {
|
|
||||||
query.value = EventCalendarQuery(
|
|
||||||
uname: query.value.uname,
|
|
||||||
year: year,
|
|
||||||
month: month,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to handle day selection for synchronizing between widgets
|
// Function to handle day selection for synchronizing between widgets
|
||||||
void onDaySelected(DateTime day) {
|
void onDaySelected(DateTime day) {
|
||||||
selectedDay.value = day;
|
selectedDay.value = day;
|
||||||
@@ -218,21 +209,16 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
right: 12,
|
right: 12,
|
||||||
top: 16,
|
top: 16,
|
||||||
),
|
),
|
||||||
|
onChecked: () {
|
||||||
|
ref.invalidate(
|
||||||
|
eventCalendarProvider(query.value),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
Card(
|
PostFeaturedList().padding(
|
||||||
margin: EdgeInsets.only(left: 8, right: 12, top: 8),
|
left: 8,
|
||||||
child: Column(
|
right: 12,
|
||||||
children: [
|
top: 8,
|
||||||
// Use the reusable EventCalendarWidget
|
|
||||||
EventCalendarWidget(
|
|
||||||
events: events,
|
|
||||||
initialDate: now,
|
|
||||||
showEventDetails: true,
|
|
||||||
onMonthChanged: onMonthChanged,
|
|
||||||
onDaySelected: onDaySelected,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
FortuneGraphWidget(
|
FortuneGraphWidget(
|
||||||
margin: EdgeInsets.only(left: 8, right: 12, top: 8),
|
margin: EdgeInsets.only(left: 8, right: 12, top: 8),
|
||||||
@@ -403,6 +389,10 @@ class _ActivityListView extends HookConsumerWidget {
|
|||||||
margin: EdgeInsets.only(left: 8, right: 8, bottom: 4),
|
margin: EdgeInsets.only(left: 8, right: 8, bottom: 4),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (!contentOnly)
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: PostFeaturedList().padding(horizontal: 8, bottom: 4, top: 4),
|
||||||
|
),
|
||||||
SliverList.builder(
|
SliverList.builder(
|
||||||
itemCount: widgetCount,
|
itemCount: widgetCount,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
|
|||||||
@@ -205,17 +205,7 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
builder:
|
builder: (context) => ComposeSettingsSheet(state: state),
|
||||||
(context) => ComposeSettingsSheet(
|
|
||||||
titleController: state.titleController,
|
|
||||||
descriptionController: state.descriptionController,
|
|
||||||
visibility: state.visibility,
|
|
||||||
tagsController: state.tagsController,
|
|
||||||
categoriesController: state.categoriesController,
|
|
||||||
onVisibilityChanged: () {
|
|
||||||
// Trigger rebuild if needed
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,15 +359,9 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
// Post content form
|
// Post content form
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SingleChildScrollView(
|
child: KeyboardListener(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
// Content field with borderless design
|
|
||||||
RawKeyboardListener(
|
|
||||||
focusNode: FocusNode(),
|
focusNode: FocusNode(),
|
||||||
onKey:
|
onKeyEvent:
|
||||||
(event) => ComposeLogic.handleKeyPress(
|
(event) => ComposeLogic.handleKeyPress(
|
||||||
event,
|
event,
|
||||||
state,
|
state,
|
||||||
@@ -387,13 +371,61 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
repliedPost: repliedPost,
|
repliedPost: repliedPost,
|
||||||
forwardedPost: forwardedPost,
|
forwardedPost: forwardedPost,
|
||||||
),
|
),
|
||||||
child: TextField(
|
child: SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
TextField(
|
||||||
|
controller: state.titleController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'postTitle'.tr(),
|
||||||
|
border: InputBorder.none,
|
||||||
|
isCollapsed: true,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 8,
|
||||||
|
horizontal: 8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
style: theme.textTheme.titleMedium,
|
||||||
|
onTapOutside:
|
||||||
|
(_) =>
|
||||||
|
FocusManager.instance.primaryFocus
|
||||||
|
?.unfocus(),
|
||||||
|
),
|
||||||
|
TextField(
|
||||||
|
controller: state.descriptionController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'postDescription'.tr(),
|
||||||
|
border: InputBorder.none,
|
||||||
|
isCollapsed: true,
|
||||||
|
contentPadding: const EdgeInsets.fromLTRB(
|
||||||
|
8,
|
||||||
|
4,
|
||||||
|
8,
|
||||||
|
12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
style: theme.textTheme.bodyMedium,
|
||||||
|
minLines: 1,
|
||||||
|
maxLines: 3,
|
||||||
|
onTapOutside:
|
||||||
|
(_) =>
|
||||||
|
FocusManager.instance.primaryFocus
|
||||||
|
?.unfocus(),
|
||||||
|
),
|
||||||
|
// Content field with borderless design
|
||||||
|
TextField(
|
||||||
controller: state.contentController,
|
controller: state.contentController,
|
||||||
style: theme.textTheme.bodyMedium,
|
style: theme.textTheme.bodyMedium,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
hintText: 'postContent'.tr(),
|
hintText: 'postContent'.tr(),
|
||||||
contentPadding: const EdgeInsets.all(8),
|
isCollapsed: true,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 8,
|
||||||
|
horizontal: 8,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
onTapOutside:
|
onTapOutside:
|
||||||
@@ -401,7 +433,6 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
FocusManager.instance.primaryFocus
|
FocusManager.instance.primaryFocus
|
||||||
?.unfocus(),
|
?.unfocus(),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
|
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
|
|
||||||
@@ -421,6 +452,7 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
).padding(horizontal: 16),
|
).padding(horizontal: 16),
|
||||||
).alignment(Alignment.topCenter),
|
).alignment(Alignment.topCenter),
|
||||||
|
|||||||
@@ -138,17 +138,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
builder:
|
builder: (context) => ComposeSettingsSheet(state: state),
|
||||||
(context) => ComposeSettingsSheet(
|
|
||||||
titleController: state.titleController,
|
|
||||||
descriptionController: state.descriptionController,
|
|
||||||
visibility: state.visibility,
|
|
||||||
tagsController: state.tagsController,
|
|
||||||
categoriesController: state.categoriesController,
|
|
||||||
onVisibilityChanged: () {
|
|
||||||
// Trigger rebuild if needed
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,10 +232,39 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
TextField(
|
||||||
|
controller: state.titleController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'postTitle'.tr(),
|
||||||
|
border: InputBorder.none,
|
||||||
|
isCollapsed: true,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 8,
|
||||||
|
horizontal: 8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
style: theme.textTheme.titleMedium,
|
||||||
|
onTapOutside:
|
||||||
|
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
TextField(
|
||||||
|
controller: state.descriptionController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'postDescription'.tr(),
|
||||||
|
border: InputBorder.none,
|
||||||
|
isCollapsed: true,
|
||||||
|
contentPadding: const EdgeInsets.fromLTRB(8, 4, 8, 12),
|
||||||
|
),
|
||||||
|
style: theme.textTheme.bodyMedium,
|
||||||
|
minLines: 1,
|
||||||
|
maxLines: 3,
|
||||||
|
onTapOutside:
|
||||||
|
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: RawKeyboardListener(
|
child: KeyboardListener(
|
||||||
focusNode: FocusNode(),
|
focusNode: FocusNode(),
|
||||||
onKey:
|
onKeyEvent:
|
||||||
(event) => _handleKeyPress(
|
(event) => _handleKeyPress(
|
||||||
event,
|
event,
|
||||||
state,
|
state,
|
||||||
@@ -454,7 +473,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
flex: showPreview.value ? 1 : 2,
|
flex: showPreview.value ? 1 : 2,
|
||||||
child: buildEditorPane(),
|
child: buildEditorPane(),
|
||||||
),
|
),
|
||||||
const VerticalDivider(),
|
if (showPreview.value) const VerticalDivider(),
|
||||||
if (showPreview.value)
|
if (showPreview.value)
|
||||||
Expanded(child: buildPreviewPane()),
|
Expanded(child: buildPreviewPane()),
|
||||||
],
|
],
|
||||||
@@ -475,7 +494,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
// Helper method to handle keyboard shortcuts
|
// Helper method to handle keyboard shortcuts
|
||||||
void _handleKeyPress(
|
void _handleKeyPress(
|
||||||
RawKeyEvent event,
|
KeyEvent event,
|
||||||
ComposeState state,
|
ComposeState state,
|
||||||
WidgetRef ref,
|
WidgetRef ref,
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
@@ -485,7 +504,9 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
final isPaste = event.logicalKey == LogicalKeyboardKey.keyV;
|
final isPaste = event.logicalKey == LogicalKeyboardKey.keyV;
|
||||||
final isSave = event.logicalKey == LogicalKeyboardKey.keyS;
|
final isSave = event.logicalKey == LogicalKeyboardKey.keyS;
|
||||||
final isModifierPressed = event.isMetaPressed || event.isControlPressed;
|
final isModifierPressed =
|
||||||
|
HardwareKeyboard.instance.isMetaPressed ||
|
||||||
|
HardwareKeyboard.instance.isControlPressed;
|
||||||
final isSubmit = event.logicalKey == LogicalKeyboardKey.enter;
|
final isSubmit = event.logicalKey == LogicalKeyboardKey.enter;
|
||||||
|
|
||||||
if (isPaste && isModifierPressed) {
|
if (isPaste && isModifierPressed) {
|
||||||
|
|||||||
107
lib/screens/posts/post_category_detail.dart
Normal file
107
lib/screens/posts/post_category_detail.dart
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/models/post_category.dart';
|
||||||
|
import 'package:island/models/post_tag.dart';
|
||||||
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
|
import 'package:island/widgets/post/post_list.dart';
|
||||||
|
import 'package:island/widgets/response.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
|
part 'post_category_detail.g.dart';
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
Future<SnPostCategory> postCategory(Ref ref, String slug) async {
|
||||||
|
final apiClient = ref.watch(apiClientProvider);
|
||||||
|
final resp = await apiClient.get('/sphere/posts/categories/$slug');
|
||||||
|
return SnPostCategory.fromJson(resp.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
Future<SnPostTag> postTag(Ref ref, String slug) async {
|
||||||
|
final apiClient = ref.watch(apiClientProvider);
|
||||||
|
final resp = await apiClient.get('/sphere/posts/tags/$slug');
|
||||||
|
return SnPostTag.fromJson(resp.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
class PostCategoryDetailScreen extends HookConsumerWidget {
|
||||||
|
final String slug;
|
||||||
|
final bool isCategory;
|
||||||
|
const PostCategoryDetailScreen({
|
||||||
|
super.key,
|
||||||
|
required this.slug,
|
||||||
|
required this.isCategory,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final postCategory =
|
||||||
|
isCategory ? ref.watch(postCategoryProvider(slug)) : null;
|
||||||
|
final postTag = isCategory ? null : ref.watch(postTagProvider(slug));
|
||||||
|
|
||||||
|
final postFilterTitle =
|
||||||
|
isCategory
|
||||||
|
? postCategory?.value?.categoryDisplayTitle ?? 'loading'
|
||||||
|
: postTag?.value?.name ?? postTag?.value?.slug ?? 'loading';
|
||||||
|
|
||||||
|
return AppScaffold(
|
||||||
|
isNoBackground: false,
|
||||||
|
appBar: AppBar(title: Text(postFilterTitle).tr()),
|
||||||
|
body: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (isCategory)
|
||||||
|
postCategory!.when(
|
||||||
|
data:
|
||||||
|
(category) => Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(category.categoryDisplayTitle).bold().fontSize(15),
|
||||||
|
Text('A category'),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 24, vertical: 16),
|
||||||
|
error:
|
||||||
|
(error, _) => ResponseErrorWidget(
|
||||||
|
error: error,
|
||||||
|
onRetry: () => ref.invalidate(postCategoryProvider(slug)),
|
||||||
|
),
|
||||||
|
loading: () => ResponseLoadingWidget(),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
postTag!.when(
|
||||||
|
data:
|
||||||
|
(tag) => Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(tag.name ?? '#${tag.slug}').bold().fontSize(15),
|
||||||
|
Text('A tag'),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 24, vertical: 16),
|
||||||
|
error:
|
||||||
|
(error, _) => ResponseErrorWidget(
|
||||||
|
error: error,
|
||||||
|
onRetry: () => ref.invalidate(postTagProvider(slug)),
|
||||||
|
),
|
||||||
|
loading: () => ResponseLoadingWidget(),
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
|
Expanded(
|
||||||
|
child: CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
const SliverGap(4),
|
||||||
|
SliverPostList(
|
||||||
|
categories: isCategory ? [slug] : null,
|
||||||
|
tags: isCategory ? null : [slug],
|
||||||
|
),
|
||||||
|
SliverGap(MediaQuery.of(context).padding.bottom + 8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
270
lib/screens/posts/post_category_detail.g.dart
Normal file
270
lib/screens/posts/post_category_detail.g.dart
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'post_category_detail.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$postCategoryHash() => r'0df2de729ba96819ee37377314615abef0c99547';
|
||||||
|
|
||||||
|
/// Copied from Dart SDK
|
||||||
|
class _SystemHash {
|
||||||
|
_SystemHash._();
|
||||||
|
|
||||||
|
static int combine(int hash, int value) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
hash = 0x1fffffff & (hash + value);
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||||
|
return hash ^ (hash >> 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int finish(int hash) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
hash = hash ^ (hash >> 11);
|
||||||
|
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [postCategory].
|
||||||
|
@ProviderFor(postCategory)
|
||||||
|
const postCategoryProvider = PostCategoryFamily();
|
||||||
|
|
||||||
|
/// See also [postCategory].
|
||||||
|
class PostCategoryFamily extends Family<AsyncValue<SnPostCategory>> {
|
||||||
|
/// See also [postCategory].
|
||||||
|
const PostCategoryFamily();
|
||||||
|
|
||||||
|
/// See also [postCategory].
|
||||||
|
PostCategoryProvider call(String slug) {
|
||||||
|
return PostCategoryProvider(slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
PostCategoryProvider getProviderOverride(
|
||||||
|
covariant PostCategoryProvider provider,
|
||||||
|
) {
|
||||||
|
return call(provider.slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||||
|
|
||||||
|
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||||
|
_allTransitiveDependencies;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get name => r'postCategoryProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [postCategory].
|
||||||
|
class PostCategoryProvider extends AutoDisposeFutureProvider<SnPostCategory> {
|
||||||
|
/// See also [postCategory].
|
||||||
|
PostCategoryProvider(String slug)
|
||||||
|
: this._internal(
|
||||||
|
(ref) => postCategory(ref as PostCategoryRef, slug),
|
||||||
|
from: postCategoryProvider,
|
||||||
|
name: r'postCategoryProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$postCategoryHash,
|
||||||
|
dependencies: PostCategoryFamily._dependencies,
|
||||||
|
allTransitiveDependencies:
|
||||||
|
PostCategoryFamily._allTransitiveDependencies,
|
||||||
|
slug: slug,
|
||||||
|
);
|
||||||
|
|
||||||
|
PostCategoryProvider._internal(
|
||||||
|
super._createNotifier, {
|
||||||
|
required super.name,
|
||||||
|
required super.dependencies,
|
||||||
|
required super.allTransitiveDependencies,
|
||||||
|
required super.debugGetCreateSourceHash,
|
||||||
|
required super.from,
|
||||||
|
required this.slug,
|
||||||
|
}) : super.internal();
|
||||||
|
|
||||||
|
final String slug;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Override overrideWith(
|
||||||
|
FutureOr<SnPostCategory> Function(PostCategoryRef provider) create,
|
||||||
|
) {
|
||||||
|
return ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
override: PostCategoryProvider._internal(
|
||||||
|
(ref) => create(ref as PostCategoryRef),
|
||||||
|
from: from,
|
||||||
|
name: null,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
debugGetCreateSourceHash: null,
|
||||||
|
slug: slug,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AutoDisposeFutureProviderElement<SnPostCategory> createElement() {
|
||||||
|
return _PostCategoryProviderElement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is PostCategoryProvider && other.slug == slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, slug.hashCode);
|
||||||
|
|
||||||
|
return _SystemHash.finish(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
mixin PostCategoryRef on AutoDisposeFutureProviderRef<SnPostCategory> {
|
||||||
|
/// The parameter `slug` of this provider.
|
||||||
|
String get slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PostCategoryProviderElement
|
||||||
|
extends AutoDisposeFutureProviderElement<SnPostCategory>
|
||||||
|
with PostCategoryRef {
|
||||||
|
_PostCategoryProviderElement(super.provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get slug => (origin as PostCategoryProvider).slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$postTagHash() => r'e050fdf9af81a843a9abd9cf979dd2672e0a2b93';
|
||||||
|
|
||||||
|
/// See also [postTag].
|
||||||
|
@ProviderFor(postTag)
|
||||||
|
const postTagProvider = PostTagFamily();
|
||||||
|
|
||||||
|
/// See also [postTag].
|
||||||
|
class PostTagFamily extends Family<AsyncValue<SnPostTag>> {
|
||||||
|
/// See also [postTag].
|
||||||
|
const PostTagFamily();
|
||||||
|
|
||||||
|
/// See also [postTag].
|
||||||
|
PostTagProvider call(String slug) {
|
||||||
|
return PostTagProvider(slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
PostTagProvider getProviderOverride(covariant PostTagProvider provider) {
|
||||||
|
return call(provider.slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||||
|
|
||||||
|
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||||
|
_allTransitiveDependencies;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get name => r'postTagProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [postTag].
|
||||||
|
class PostTagProvider extends AutoDisposeFutureProvider<SnPostTag> {
|
||||||
|
/// See also [postTag].
|
||||||
|
PostTagProvider(String slug)
|
||||||
|
: this._internal(
|
||||||
|
(ref) => postTag(ref as PostTagRef, slug),
|
||||||
|
from: postTagProvider,
|
||||||
|
name: r'postTagProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$postTagHash,
|
||||||
|
dependencies: PostTagFamily._dependencies,
|
||||||
|
allTransitiveDependencies: PostTagFamily._allTransitiveDependencies,
|
||||||
|
slug: slug,
|
||||||
|
);
|
||||||
|
|
||||||
|
PostTagProvider._internal(
|
||||||
|
super._createNotifier, {
|
||||||
|
required super.name,
|
||||||
|
required super.dependencies,
|
||||||
|
required super.allTransitiveDependencies,
|
||||||
|
required super.debugGetCreateSourceHash,
|
||||||
|
required super.from,
|
||||||
|
required this.slug,
|
||||||
|
}) : super.internal();
|
||||||
|
|
||||||
|
final String slug;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Override overrideWith(
|
||||||
|
FutureOr<SnPostTag> Function(PostTagRef provider) create,
|
||||||
|
) {
|
||||||
|
return ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
override: PostTagProvider._internal(
|
||||||
|
(ref) => create(ref as PostTagRef),
|
||||||
|
from: from,
|
||||||
|
name: null,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
debugGetCreateSourceHash: null,
|
||||||
|
slug: slug,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AutoDisposeFutureProviderElement<SnPostTag> createElement() {
|
||||||
|
return _PostTagProviderElement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is PostTagProvider && other.slug == slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, slug.hashCode);
|
||||||
|
|
||||||
|
return _SystemHash.finish(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
mixin PostTagRef on AutoDisposeFutureProviderRef<SnPostTag> {
|
||||||
|
/// The parameter `slug` of this provider.
|
||||||
|
String get slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PostTagProviderElement
|
||||||
|
extends AutoDisposeFutureProviderElement<SnPostTag>
|
||||||
|
with PostTagRef {
|
||||||
|
_PostTagProviderElement(super.provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get slug => (origin as PostTagProvider).slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
@@ -87,13 +87,22 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
|||||||
publisherAppbarForcegroundColorProvider(name),
|
publisherAppbarForcegroundColorProvider(name),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final categoryTabController = useTabController(initialLength: 3);
|
||||||
|
final categoryTab = useState(0);
|
||||||
|
categoryTabController.addListener(() {
|
||||||
|
categoryTab.value = categoryTabController.index;
|
||||||
|
});
|
||||||
|
|
||||||
final subscribing = useState(false);
|
final subscribing = useState(false);
|
||||||
|
|
||||||
Future<void> subscribe() async {
|
Future<void> subscribe() async {
|
||||||
final apiClient = ref.watch(apiClientProvider);
|
final apiClient = ref.watch(apiClientProvider);
|
||||||
subscribing.value = true;
|
subscribing.value = true;
|
||||||
try {
|
try {
|
||||||
await apiClient.post("/publishers/$name/subscribe", data: {'tier': 0});
|
await apiClient.post(
|
||||||
|
"/sphere/publishers/$name/subscribe",
|
||||||
|
data: {'tier': 0},
|
||||||
|
);
|
||||||
ref.invalidate(publisherSubscriptionStatusProvider(name));
|
ref.invalidate(publisherSubscriptionStatusProvider(name));
|
||||||
HapticFeedback.heavyImpact();
|
HapticFeedback.heavyImpact();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -107,7 +116,7 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
|||||||
final apiClient = ref.watch(apiClientProvider);
|
final apiClient = ref.watch(apiClientProvider);
|
||||||
subscribing.value = true;
|
subscribing.value = true;
|
||||||
try {
|
try {
|
||||||
await apiClient.post("/publishers/$name/unsubscribe");
|
await apiClient.post("/sphere/publishers/$name/unsubscribe");
|
||||||
ref.invalidate(publisherSubscriptionStatusProvider(name));
|
ref.invalidate(publisherSubscriptionStatusProvider(name));
|
||||||
HapticFeedback.heavyImpact();
|
HapticFeedback.heavyImpact();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -268,6 +277,16 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
|||||||
).padding(horizontal: 20, vertical: 16),
|
).padding(horizontal: 20, vertical: 16),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Widget publisherCategoryTabWidget() => Card(
|
||||||
|
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
|
child: TabBar(
|
||||||
|
controller: categoryTabController,
|
||||||
|
dividerColor: Colors.transparent,
|
||||||
|
splashBorderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
tabs: [Tab(text: 'All'), Tab(text: 'Posts'), Tab(text: 'Articles')],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
return publisher.when(
|
return publisher.when(
|
||||||
data:
|
data:
|
||||||
(data) => AppScaffold(
|
(data) => AppScaffold(
|
||||||
@@ -321,7 +340,18 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
|||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverGap(16),
|
SliverGap(16),
|
||||||
SliverPostList(pubName: name),
|
SliverToBoxAdapter(
|
||||||
|
child: publisherCategoryTabWidget(),
|
||||||
|
),
|
||||||
|
SliverPostList(
|
||||||
|
key: ValueKey(categoryTab.value),
|
||||||
|
pubName: name,
|
||||||
|
type: switch (categoryTab.value) {
|
||||||
|
1 => 0,
|
||||||
|
2 => 1,
|
||||||
|
_ => null,
|
||||||
|
},
|
||||||
|
),
|
||||||
SliverGap(
|
SliverGap(
|
||||||
MediaQuery.of(context).padding.bottom + 16,
|
MediaQuery.of(context).padding.bottom + 16,
|
||||||
),
|
),
|
||||||
@@ -334,9 +364,9 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
|||||||
alignment: Alignment.topLeft,
|
alignment: Alignment.topLeft,
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
publisherBasisWidget(data),
|
publisherBasisWidget(data).padding(bottom: 8),
|
||||||
publisherBadgesWidget(data),
|
publisherBadgesWidget(data),
|
||||||
publisherVerificationWidget(data),
|
publisherVerificationWidget(data),
|
||||||
publisherBioWidget(data),
|
publisherBioWidget(data),
|
||||||
@@ -398,7 +428,16 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
|||||||
child: publisherVerificationWidget(data),
|
child: publisherVerificationWidget(data),
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(child: publisherBioWidget(data)),
|
SliverToBoxAdapter(child: publisherBioWidget(data)),
|
||||||
SliverPostList(pubName: name),
|
SliverToBoxAdapter(child: publisherCategoryTabWidget()),
|
||||||
|
SliverPostList(
|
||||||
|
key: ValueKey(categoryTab.value),
|
||||||
|
pubName: name,
|
||||||
|
type: switch (categoryTab.value) {
|
||||||
|
1 => 0,
|
||||||
|
2 => 1,
|
||||||
|
_ => null,
|
||||||
|
},
|
||||||
|
),
|
||||||
SliverGap(MediaQuery.of(context).padding.bottom + 16),
|
SliverGap(MediaQuery.of(context).padding.bottom + 16),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
103
lib/screens/stickers/marketplace.dart
Normal file
103
lib/screens/stickers/marketplace.dart
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/models/sticker.dart';
|
||||||
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
||||||
|
|
||||||
|
part 'marketplace.g.dart';
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
class MarketplaceStickerPacksNotifier extends _$MarketplaceStickerPacksNotifier
|
||||||
|
with CursorPagingNotifierMixin<SnStickerPack> {
|
||||||
|
@override
|
||||||
|
Future<CursorPagingData<SnStickerPack>> build() {
|
||||||
|
return fetch(cursor: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<CursorPagingData<SnStickerPack>> fetch({
|
||||||
|
required String? cursor,
|
||||||
|
}) async {
|
||||||
|
final client = ref.read(apiClientProvider);
|
||||||
|
final offset = cursor == null ? 0 : int.parse(cursor);
|
||||||
|
|
||||||
|
final response = await client.get(
|
||||||
|
'/sphere/stickers',
|
||||||
|
queryParameters: {'offset': offset, 'take': 20},
|
||||||
|
);
|
||||||
|
|
||||||
|
final total = int.parse(response.headers.value('X-Total') ?? '0');
|
||||||
|
final List<dynamic> data = response.data;
|
||||||
|
final stickers = data.map((e) => SnStickerPack.fromJson(e)).toList();
|
||||||
|
|
||||||
|
final hasMore = offset + stickers.length < total;
|
||||||
|
final nextCursor = hasMore ? (offset + stickers.length).toString() : null;
|
||||||
|
|
||||||
|
return CursorPagingData(
|
||||||
|
items: stickers,
|
||||||
|
hasMore: hasMore,
|
||||||
|
nextCursor: nextCursor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// User-facing marketplace screen for browsing sticker packs.
|
||||||
|
/// This version does NOT rely on publisher name (no pubName).
|
||||||
|
class MarketplaceStickersScreen extends HookConsumerWidget {
|
||||||
|
const MarketplaceStickersScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('stickers').tr(),
|
||||||
|
actions: const [Gap(8)],
|
||||||
|
),
|
||||||
|
body: const SliverMarketplaceStickerPacksList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SliverMarketplaceStickerPacksList extends HookConsumerWidget {
|
||||||
|
const SliverMarketplaceStickerPacksList({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
return PagingHelperView(
|
||||||
|
provider: marketplaceStickerPacksNotifierProvider,
|
||||||
|
futureRefreshable: marketplaceStickerPacksNotifierProvider.future,
|
||||||
|
notifierRefreshable: marketplaceStickerPacksNotifierProvider.notifier,
|
||||||
|
contentBuilder:
|
||||||
|
(data, widgetCount, endItemView) => ListView.builder(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
itemCount: widgetCount,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (index == widgetCount - 1) {
|
||||||
|
return endItemView;
|
||||||
|
}
|
||||||
|
|
||||||
|
final pack = data.items[index];
|
||||||
|
return ListTile(
|
||||||
|
title: Text(pack.name),
|
||||||
|
subtitle: Text(pack.description),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
onTap: () {
|
||||||
|
// Navigate to user-facing sticker pack detail page.
|
||||||
|
// Adjust the route name/parameters if your app uses different ones.
|
||||||
|
context.pushNamed(
|
||||||
|
'stickerPackDetail',
|
||||||
|
pathParameters: {'packId': pack.id},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
32
lib/screens/stickers/marketplace.g.dart
Normal file
32
lib/screens/stickers/marketplace.g.dart
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'marketplace.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$marketplaceStickerPacksNotifierHash() =>
|
||||||
|
r'b62ae8b7f5c4f8bb3be8c17fc005ea26da355187';
|
||||||
|
|
||||||
|
/// See also [MarketplaceStickerPacksNotifier].
|
||||||
|
@ProviderFor(MarketplaceStickerPacksNotifier)
|
||||||
|
final marketplaceStickerPacksNotifierProvider =
|
||||||
|
AutoDisposeAsyncNotifierProvider<
|
||||||
|
MarketplaceStickerPacksNotifier,
|
||||||
|
CursorPagingData<SnStickerPack>
|
||||||
|
>.internal(
|
||||||
|
MarketplaceStickerPacksNotifier.new,
|
||||||
|
name: r'marketplaceStickerPacksNotifierProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$marketplaceStickerPacksNotifierHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef _$MarketplaceStickerPacksNotifier =
|
||||||
|
AutoDisposeAsyncNotifier<CursorPagingData<SnStickerPack>>;
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
230
lib/screens/stickers/pack_detail.dart
Normal file
230
lib/screens/stickers/pack_detail.dart
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/models/sticker.dart';
|
||||||
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:island/screens/creators/stickers/stickers.dart';
|
||||||
|
import 'package:island/widgets/alert.dart';
|
||||||
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
|
part 'pack_detail.g.dart'; // generated by riverpod_annotation build_runner
|
||||||
|
|
||||||
|
/// Marketplace version of sticker pack detail page (no publisher dependency).
|
||||||
|
/// Shows all stickers in the pack and provides a button to add the sticker.
|
||||||
|
/// API interactions are intentionally left blank per request.
|
||||||
|
@riverpod
|
||||||
|
Future<List<SnSticker>> marketplaceStickerPackContent(
|
||||||
|
Ref ref, {
|
||||||
|
required String packId,
|
||||||
|
}) async {
|
||||||
|
final apiClient = ref.watch(apiClientProvider);
|
||||||
|
final resp = await apiClient.get('/sphere/stickers/$packId/content');
|
||||||
|
return (resp.data as List).map((e) => SnSticker.fromJson(e)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
Future<bool> marketplaceStickerPackOwnership(
|
||||||
|
Ref ref, {
|
||||||
|
required String packId,
|
||||||
|
}) async {
|
||||||
|
final api = ref.watch(apiClientProvider);
|
||||||
|
try {
|
||||||
|
await api.get('/sphere/stickers/$packId/own');
|
||||||
|
// If not 404, consider owned
|
||||||
|
return true;
|
||||||
|
} on Object catch (e) {
|
||||||
|
// Dio error handling agnostic: treat 404 as not-owned, rethrow others
|
||||||
|
final msg = e.toString();
|
||||||
|
if (msg.contains('404')) return false;
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MarketplaceStickerPackDetailScreen extends HookConsumerWidget {
|
||||||
|
final String id;
|
||||||
|
const MarketplaceStickerPackDetailScreen({super.key, required this.id});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
// Pack metadata provider exists globally in creators file; reuse it.
|
||||||
|
final pack = ref.watch(stickerPackProvider(id));
|
||||||
|
final packContent = ref.watch(
|
||||||
|
marketplaceStickerPackContentProvider(packId: id),
|
||||||
|
);
|
||||||
|
final owned = ref.watch(
|
||||||
|
marketplaceStickerPackOwnershipProvider(packId: id),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add entire pack to user's collection
|
||||||
|
Future<void> addPackToMyCollection() async {
|
||||||
|
final apiClient = ref.watch(apiClientProvider);
|
||||||
|
await apiClient.post('/sphere/stickers/$id/own');
|
||||||
|
HapticFeedback.selectionClick();
|
||||||
|
ref.invalidate(marketplaceStickerPackOwnershipProvider(packId: id));
|
||||||
|
if (!context.mounted) return;
|
||||||
|
showSnackBar('stickerPackAdded'.tr());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove ownership of the pack
|
||||||
|
Future<void> removePackFromMyCollection() async {
|
||||||
|
final apiClient = ref.watch(apiClientProvider);
|
||||||
|
await apiClient.delete('/sphere/stickers/$id/own');
|
||||||
|
HapticFeedback.selectionClick();
|
||||||
|
ref.invalidate(marketplaceStickerPackOwnershipProvider(packId: id));
|
||||||
|
if (!context.mounted) return;
|
||||||
|
showSnackBar('stickerPackRemoved'.tr());
|
||||||
|
}
|
||||||
|
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(title: Text(pack.value?.name ?? 'loading'.tr())),
|
||||||
|
body: pack.when(
|
||||||
|
data: (p) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
// Pack meta
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Text(p?.description ?? ''),
|
||||||
|
Row(
|
||||||
|
spacing: 4,
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.folder, size: 16),
|
||||||
|
Text(
|
||||||
|
'${packContent.value?.length ?? 0}/24',
|
||||||
|
style: GoogleFonts.robotoMono(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).opacity(0.85),
|
||||||
|
Row(
|
||||||
|
spacing: 4,
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.sell, size: 16),
|
||||||
|
Text(p?.prefix ?? '', style: GoogleFonts.robotoMono()),
|
||||||
|
],
|
||||||
|
).opacity(0.85),
|
||||||
|
Row(
|
||||||
|
spacing: 4,
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.tag, size: 16),
|
||||||
|
SelectableText(
|
||||||
|
p?.id ?? id,
|
||||||
|
style: GoogleFonts.robotoMono(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).opacity(0.85),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 24, vertical: 24),
|
||||||
|
const Divider(height: 1),
|
||||||
|
// Stickers grid
|
||||||
|
Expanded(
|
||||||
|
child: packContent.when(
|
||||||
|
data:
|
||||||
|
(stickers) => RefreshIndicator(
|
||||||
|
onRefresh:
|
||||||
|
() => ref.refresh(
|
||||||
|
marketplaceStickerPackContentProvider(
|
||||||
|
packId: id,
|
||||||
|
).future,
|
||||||
|
),
|
||||||
|
child: GridView.builder(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 24,
|
||||||
|
vertical: 20,
|
||||||
|
),
|
||||||
|
gridDelegate:
|
||||||
|
const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||||
|
maxCrossAxisExtent: 96,
|
||||||
|
mainAxisSpacing: 12,
|
||||||
|
crossAxisSpacing: 12,
|
||||||
|
),
|
||||||
|
itemCount: stickers.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final sticker = stickers[index];
|
||||||
|
return Tooltip(
|
||||||
|
message: ':${p?.prefix ?? ''}${sticker.slug}:',
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.all(
|
||||||
|
Radius.circular(8),
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color:
|
||||||
|
Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.surfaceContainer,
|
||||||
|
borderRadius: const BorderRadius.all(
|
||||||
|
Radius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: 1,
|
||||||
|
child: CloudImageWidget(
|
||||||
|
fileId: sticker.imageId,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
error:
|
||||||
|
(err, _) =>
|
||||||
|
Text(
|
||||||
|
'Error: $err',
|
||||||
|
).textAlignment(TextAlign.center).center(),
|
||||||
|
loading: () => const CircularProgressIndicator().center(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 8),
|
||||||
|
child: owned.when(
|
||||||
|
data:
|
||||||
|
(isOwned) => FilledButton.icon(
|
||||||
|
onPressed:
|
||||||
|
isOwned
|
||||||
|
? removePackFromMyCollection
|
||||||
|
: addPackToMyCollection,
|
||||||
|
icon: Icon(
|
||||||
|
isOwned ? Symbols.remove_circle : Symbols.add_circle,
|
||||||
|
),
|
||||||
|
label: Text(
|
||||||
|
isOwned ? 'removePack'.tr() : 'addPack'.tr(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
loading:
|
||||||
|
() => const SizedBox(
|
||||||
|
height: 32,
|
||||||
|
width: 32,
|
||||||
|
child: CircularProgressIndicator(strokeWidth: 2),
|
||||||
|
),
|
||||||
|
error:
|
||||||
|
(_, _) => OutlinedButton.icon(
|
||||||
|
onPressed: addPackToMyCollection,
|
||||||
|
icon: const Icon(Symbols.add_circle),
|
||||||
|
label: Text('addPack').tr(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Gap(MediaQuery.of(context).padding.bottom),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
error:
|
||||||
|
(err, _) =>
|
||||||
|
Text('Error: $err').textAlignment(TextAlign.center).center(),
|
||||||
|
loading: () => const CircularProgressIndicator().center(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
317
lib/screens/stickers/pack_detail.g.dart
Normal file
317
lib/screens/stickers/pack_detail.g.dart
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'pack_detail.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$marketplaceStickerPackContentHash() =>
|
||||||
|
r'886f8305c978dbea6e5d990a7d555048ac704a5d';
|
||||||
|
|
||||||
|
/// Copied from Dart SDK
|
||||||
|
class _SystemHash {
|
||||||
|
_SystemHash._();
|
||||||
|
|
||||||
|
static int combine(int hash, int value) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
hash = 0x1fffffff & (hash + value);
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||||
|
return hash ^ (hash >> 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int finish(int hash) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
hash = hash ^ (hash >> 11);
|
||||||
|
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marketplace version of sticker pack detail page (no publisher dependency).
|
||||||
|
/// Shows all stickers in the pack and provides a button to add the sticker.
|
||||||
|
/// API interactions are intentionally left blank per request.
|
||||||
|
///
|
||||||
|
/// Copied from [marketplaceStickerPackContent].
|
||||||
|
@ProviderFor(marketplaceStickerPackContent)
|
||||||
|
const marketplaceStickerPackContentProvider =
|
||||||
|
MarketplaceStickerPackContentFamily();
|
||||||
|
|
||||||
|
/// Marketplace version of sticker pack detail page (no publisher dependency).
|
||||||
|
/// Shows all stickers in the pack and provides a button to add the sticker.
|
||||||
|
/// API interactions are intentionally left blank per request.
|
||||||
|
///
|
||||||
|
/// Copied from [marketplaceStickerPackContent].
|
||||||
|
class MarketplaceStickerPackContentFamily
|
||||||
|
extends Family<AsyncValue<List<SnSticker>>> {
|
||||||
|
/// Marketplace version of sticker pack detail page (no publisher dependency).
|
||||||
|
/// Shows all stickers in the pack and provides a button to add the sticker.
|
||||||
|
/// API interactions are intentionally left blank per request.
|
||||||
|
///
|
||||||
|
/// Copied from [marketplaceStickerPackContent].
|
||||||
|
const MarketplaceStickerPackContentFamily();
|
||||||
|
|
||||||
|
/// Marketplace version of sticker pack detail page (no publisher dependency).
|
||||||
|
/// Shows all stickers in the pack and provides a button to add the sticker.
|
||||||
|
/// API interactions are intentionally left blank per request.
|
||||||
|
///
|
||||||
|
/// Copied from [marketplaceStickerPackContent].
|
||||||
|
MarketplaceStickerPackContentProvider call({required String packId}) {
|
||||||
|
return MarketplaceStickerPackContentProvider(packId: packId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
MarketplaceStickerPackContentProvider getProviderOverride(
|
||||||
|
covariant MarketplaceStickerPackContentProvider provider,
|
||||||
|
) {
|
||||||
|
return call(packId: provider.packId);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||||
|
|
||||||
|
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||||
|
_allTransitiveDependencies;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get name => r'marketplaceStickerPackContentProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marketplace version of sticker pack detail page (no publisher dependency).
|
||||||
|
/// Shows all stickers in the pack and provides a button to add the sticker.
|
||||||
|
/// API interactions are intentionally left blank per request.
|
||||||
|
///
|
||||||
|
/// Copied from [marketplaceStickerPackContent].
|
||||||
|
class MarketplaceStickerPackContentProvider
|
||||||
|
extends AutoDisposeFutureProvider<List<SnSticker>> {
|
||||||
|
/// Marketplace version of sticker pack detail page (no publisher dependency).
|
||||||
|
/// Shows all stickers in the pack and provides a button to add the sticker.
|
||||||
|
/// API interactions are intentionally left blank per request.
|
||||||
|
///
|
||||||
|
/// Copied from [marketplaceStickerPackContent].
|
||||||
|
MarketplaceStickerPackContentProvider({required String packId})
|
||||||
|
: this._internal(
|
||||||
|
(ref) => marketplaceStickerPackContent(
|
||||||
|
ref as MarketplaceStickerPackContentRef,
|
||||||
|
packId: packId,
|
||||||
|
),
|
||||||
|
from: marketplaceStickerPackContentProvider,
|
||||||
|
name: r'marketplaceStickerPackContentProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$marketplaceStickerPackContentHash,
|
||||||
|
dependencies: MarketplaceStickerPackContentFamily._dependencies,
|
||||||
|
allTransitiveDependencies:
|
||||||
|
MarketplaceStickerPackContentFamily._allTransitiveDependencies,
|
||||||
|
packId: packId,
|
||||||
|
);
|
||||||
|
|
||||||
|
MarketplaceStickerPackContentProvider._internal(
|
||||||
|
super._createNotifier, {
|
||||||
|
required super.name,
|
||||||
|
required super.dependencies,
|
||||||
|
required super.allTransitiveDependencies,
|
||||||
|
required super.debugGetCreateSourceHash,
|
||||||
|
required super.from,
|
||||||
|
required this.packId,
|
||||||
|
}) : super.internal();
|
||||||
|
|
||||||
|
final String packId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Override overrideWith(
|
||||||
|
FutureOr<List<SnSticker>> Function(
|
||||||
|
MarketplaceStickerPackContentRef provider,
|
||||||
|
)
|
||||||
|
create,
|
||||||
|
) {
|
||||||
|
return ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
override: MarketplaceStickerPackContentProvider._internal(
|
||||||
|
(ref) => create(ref as MarketplaceStickerPackContentRef),
|
||||||
|
from: from,
|
||||||
|
name: null,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
debugGetCreateSourceHash: null,
|
||||||
|
packId: packId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AutoDisposeFutureProviderElement<List<SnSticker>> createElement() {
|
||||||
|
return _MarketplaceStickerPackContentProviderElement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is MarketplaceStickerPackContentProvider &&
|
||||||
|
other.packId == packId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, packId.hashCode);
|
||||||
|
|
||||||
|
return _SystemHash.finish(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
mixin MarketplaceStickerPackContentRef
|
||||||
|
on AutoDisposeFutureProviderRef<List<SnSticker>> {
|
||||||
|
/// The parameter `packId` of this provider.
|
||||||
|
String get packId;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MarketplaceStickerPackContentProviderElement
|
||||||
|
extends AutoDisposeFutureProviderElement<List<SnSticker>>
|
||||||
|
with MarketplaceStickerPackContentRef {
|
||||||
|
_MarketplaceStickerPackContentProviderElement(super.provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get packId => (origin as MarketplaceStickerPackContentProvider).packId;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$marketplaceStickerPackOwnershipHash() =>
|
||||||
|
r'e5dd301c309fac958729d13d984ce7a77edbe7e6';
|
||||||
|
|
||||||
|
/// See also [marketplaceStickerPackOwnership].
|
||||||
|
@ProviderFor(marketplaceStickerPackOwnership)
|
||||||
|
const marketplaceStickerPackOwnershipProvider =
|
||||||
|
MarketplaceStickerPackOwnershipFamily();
|
||||||
|
|
||||||
|
/// See also [marketplaceStickerPackOwnership].
|
||||||
|
class MarketplaceStickerPackOwnershipFamily extends Family<AsyncValue<bool>> {
|
||||||
|
/// See also [marketplaceStickerPackOwnership].
|
||||||
|
const MarketplaceStickerPackOwnershipFamily();
|
||||||
|
|
||||||
|
/// See also [marketplaceStickerPackOwnership].
|
||||||
|
MarketplaceStickerPackOwnershipProvider call({required String packId}) {
|
||||||
|
return MarketplaceStickerPackOwnershipProvider(packId: packId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
MarketplaceStickerPackOwnershipProvider getProviderOverride(
|
||||||
|
covariant MarketplaceStickerPackOwnershipProvider provider,
|
||||||
|
) {
|
||||||
|
return call(packId: provider.packId);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||||
|
|
||||||
|
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||||
|
_allTransitiveDependencies;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get name => r'marketplaceStickerPackOwnershipProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [marketplaceStickerPackOwnership].
|
||||||
|
class MarketplaceStickerPackOwnershipProvider
|
||||||
|
extends AutoDisposeFutureProvider<bool> {
|
||||||
|
/// See also [marketplaceStickerPackOwnership].
|
||||||
|
MarketplaceStickerPackOwnershipProvider({required String packId})
|
||||||
|
: this._internal(
|
||||||
|
(ref) => marketplaceStickerPackOwnership(
|
||||||
|
ref as MarketplaceStickerPackOwnershipRef,
|
||||||
|
packId: packId,
|
||||||
|
),
|
||||||
|
from: marketplaceStickerPackOwnershipProvider,
|
||||||
|
name: r'marketplaceStickerPackOwnershipProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$marketplaceStickerPackOwnershipHash,
|
||||||
|
dependencies: MarketplaceStickerPackOwnershipFamily._dependencies,
|
||||||
|
allTransitiveDependencies:
|
||||||
|
MarketplaceStickerPackOwnershipFamily._allTransitiveDependencies,
|
||||||
|
packId: packId,
|
||||||
|
);
|
||||||
|
|
||||||
|
MarketplaceStickerPackOwnershipProvider._internal(
|
||||||
|
super._createNotifier, {
|
||||||
|
required super.name,
|
||||||
|
required super.dependencies,
|
||||||
|
required super.allTransitiveDependencies,
|
||||||
|
required super.debugGetCreateSourceHash,
|
||||||
|
required super.from,
|
||||||
|
required this.packId,
|
||||||
|
}) : super.internal();
|
||||||
|
|
||||||
|
final String packId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Override overrideWith(
|
||||||
|
FutureOr<bool> Function(MarketplaceStickerPackOwnershipRef provider) create,
|
||||||
|
) {
|
||||||
|
return ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
override: MarketplaceStickerPackOwnershipProvider._internal(
|
||||||
|
(ref) => create(ref as MarketplaceStickerPackOwnershipRef),
|
||||||
|
from: from,
|
||||||
|
name: null,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
debugGetCreateSourceHash: null,
|
||||||
|
packId: packId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AutoDisposeFutureProviderElement<bool> createElement() {
|
||||||
|
return _MarketplaceStickerPackOwnershipProviderElement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is MarketplaceStickerPackOwnershipProvider &&
|
||||||
|
other.packId == packId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, packId.hashCode);
|
||||||
|
|
||||||
|
return _SystemHash.finish(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
mixin MarketplaceStickerPackOwnershipRef on AutoDisposeFutureProviderRef<bool> {
|
||||||
|
/// The parameter `packId` of this provider.
|
||||||
|
String get packId;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MarketplaceStickerPackOwnershipProviderElement
|
||||||
|
extends AutoDisposeFutureProviderElement<bool>
|
||||||
|
with MarketplaceStickerPackOwnershipRef {
|
||||||
|
_MarketplaceStickerPackOwnershipProviderElement(super.provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get packId =>
|
||||||
|
(origin as MarketplaceStickerPackOwnershipProvider).packId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
228
lib/services/update_service.dart
Normal file
228
lib/services/update_service.dart
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
|
|
||||||
|
/// Data model for a GitHub release we care about
|
||||||
|
class GithubReleaseInfo {
|
||||||
|
final String tagName; // e.g. 3.1.0+118
|
||||||
|
final String name; // release title
|
||||||
|
final String body; // changelog markdown
|
||||||
|
final String htmlUrl; // release page
|
||||||
|
final DateTime createdAt;
|
||||||
|
|
||||||
|
const GithubReleaseInfo({
|
||||||
|
required this.tagName,
|
||||||
|
required this.name,
|
||||||
|
required this.body,
|
||||||
|
required this.htmlUrl,
|
||||||
|
required this.createdAt,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses version and build number from "x.y.z+build"
|
||||||
|
class _ParsedVersion implements Comparable<_ParsedVersion> {
|
||||||
|
final int major;
|
||||||
|
final int minor;
|
||||||
|
final int patch;
|
||||||
|
final int build;
|
||||||
|
|
||||||
|
const _ParsedVersion(this.major, this.minor, this.patch, this.build);
|
||||||
|
|
||||||
|
static _ParsedVersion? tryParse(String input) {
|
||||||
|
// Expect format like 0.0.0+00 (build after '+'). Allow missing build as 0.
|
||||||
|
final partsPlus = input.split('+');
|
||||||
|
final core = partsPlus[0].trim();
|
||||||
|
final buildStr = partsPlus.length > 1 ? partsPlus[1].trim() : '0';
|
||||||
|
final coreParts = core.split('.');
|
||||||
|
if (coreParts.length != 3) return null;
|
||||||
|
|
||||||
|
final major = int.tryParse(coreParts[0]) ?? 0;
|
||||||
|
final minor = int.tryParse(coreParts[1]) ?? 0;
|
||||||
|
final patch = int.tryParse(coreParts[2]) ?? 0;
|
||||||
|
final build = int.tryParse(buildStr) ?? 0;
|
||||||
|
|
||||||
|
return _ParsedVersion(major, minor, patch, build);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int compareTo(_ParsedVersion other) {
|
||||||
|
if (major != other.major) return major.compareTo(other.major);
|
||||||
|
if (minor != other.minor) return minor.compareTo(other.minor);
|
||||||
|
if (patch != other.patch) return patch.compareTo(other.patch);
|
||||||
|
return build.compareTo(other.build);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => '$major.$minor.$patch+$build';
|
||||||
|
}
|
||||||
|
|
||||||
|
class UpdateService {
|
||||||
|
UpdateService({Dio? dio})
|
||||||
|
: _dio =
|
||||||
|
dio ??
|
||||||
|
Dio(
|
||||||
|
BaseOptions(
|
||||||
|
headers: {
|
||||||
|
// Identify the app to GitHub; avoids some rate-limits and adds clarity
|
||||||
|
'Accept': 'application/vnd.github+json',
|
||||||
|
'User-Agent': 'solian-update-checker',
|
||||||
|
},
|
||||||
|
connectTimeout: const Duration(seconds: 10),
|
||||||
|
receiveTimeout: const Duration(seconds: 15),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final Dio _dio;
|
||||||
|
|
||||||
|
static const _releasesLatestApi =
|
||||||
|
'https://api.github.com/repos/solsynth/solian/releases/latest';
|
||||||
|
|
||||||
|
/// Checks GitHub for the latest release and compares against the current app version.
|
||||||
|
/// If update is available, shows a bottom sheet with changelog and an action to open release page.
|
||||||
|
Future<void> checkForUpdates(BuildContext context) async {
|
||||||
|
try {
|
||||||
|
final release = await fetchLatestRelease();
|
||||||
|
if (release == null) return;
|
||||||
|
|
||||||
|
final info = await PackageInfo.fromPlatform();
|
||||||
|
final localVersionStr = '${info.version}+${info.buildNumber}';
|
||||||
|
|
||||||
|
final latest = _ParsedVersion.tryParse(release.tagName);
|
||||||
|
final local = _ParsedVersion.tryParse(localVersionStr);
|
||||||
|
|
||||||
|
if (latest == null || local == null) {
|
||||||
|
// If parsing fails, do nothing silently
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final needsUpdate = latest.compareTo(local) > 0;
|
||||||
|
if (!needsUpdate) return;
|
||||||
|
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
|
// Delay to ensure UI is ready (if called at startup)
|
||||||
|
await Future.delayed(const Duration(milliseconds: 100));
|
||||||
|
|
||||||
|
await showUpdateSheet(context, release);
|
||||||
|
} catch (_) {
|
||||||
|
// Ignore errors (network, api, etc.)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Manually show the update sheet with a provided release.
|
||||||
|
/// Useful for About page or testing.
|
||||||
|
Future<void> showUpdateSheet(
|
||||||
|
BuildContext context,
|
||||||
|
GithubReleaseInfo release,
|
||||||
|
) async {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
await showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
useRootNavigator: true,
|
||||||
|
builder:
|
||||||
|
(ctx) => _UpdateSheet(
|
||||||
|
release: release,
|
||||||
|
onOpen: () async {
|
||||||
|
final uri = Uri.parse(release.htmlUrl);
|
||||||
|
if (await canLaunchUrl(uri)) {
|
||||||
|
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch the latest release info from GitHub.
|
||||||
|
/// Public so other screens (e.g., About) can manually trigger update checks.
|
||||||
|
Future<GithubReleaseInfo?> fetchLatestRelease() async {
|
||||||
|
final resp = await _dio.get(_releasesLatestApi);
|
||||||
|
if (resp.statusCode != 200) return null;
|
||||||
|
final data = resp.data as Map<String, dynamic>;
|
||||||
|
|
||||||
|
final tagName = (data['tag_name'] ?? '').toString();
|
||||||
|
final name = (data['name'] ?? tagName).toString();
|
||||||
|
final body = (data['body'] ?? '').toString();
|
||||||
|
final htmlUrl = (data['html_url'] ?? '').toString();
|
||||||
|
final createdAtStr = (data['created_at'] ?? '').toString();
|
||||||
|
final createdAt = DateTime.tryParse(createdAtStr) ?? DateTime.now();
|
||||||
|
|
||||||
|
if (tagName.isEmpty || htmlUrl.isEmpty) return null;
|
||||||
|
|
||||||
|
return GithubReleaseInfo(
|
||||||
|
tagName: tagName,
|
||||||
|
name: name,
|
||||||
|
body: body,
|
||||||
|
htmlUrl: htmlUrl,
|
||||||
|
createdAt: createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _UpdateSheet extends StatelessWidget {
|
||||||
|
const _UpdateSheet({required this.release, required this.onOpen});
|
||||||
|
|
||||||
|
final GithubReleaseInfo release;
|
||||||
|
final VoidCallback onOpen;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
return SheetScaffold(
|
||||||
|
titleText: 'Update available',
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom: 16 + MediaQuery.of(context).padding.bottom,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(release.name, style: theme.textTheme.titleMedium).bold(),
|
||||||
|
Text(release.tagName).fontSize(12),
|
||||||
|
],
|
||||||
|
).padding(vertical: 16, horizontal: 16),
|
||||||
|
const Divider(height: 1),
|
||||||
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
vertical: 16,
|
||||||
|
),
|
||||||
|
child: SelectableText(
|
||||||
|
release.body.isEmpty
|
||||||
|
? 'No changelog provided.'
|
||||||
|
: release.body,
|
||||||
|
style: theme.textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: FilledButton.icon(
|
||||||
|
onPressed: onOpen,
|
||||||
|
icon: const Icon(Icons.open_in_new),
|
||||||
|
label: const Text('Open release page'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
30
lib/utils/mapping.dart
Normal file
30
lib/utils/mapping.dart
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
String _upperCamelToLowerSnake(String input) {
|
||||||
|
final regex = RegExp(r'(?<=[a-z0-9])([A-Z])');
|
||||||
|
return input
|
||||||
|
.replaceAllMapped(regex, (match) => '_${match.group(0)}')
|
||||||
|
.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> convertMapKeysToSnakeCase(Map<String, dynamic> input) {
|
||||||
|
final result = <String, dynamic>{};
|
||||||
|
|
||||||
|
input.forEach((key, value) {
|
||||||
|
final newKey = _upperCamelToLowerSnake(key);
|
||||||
|
|
||||||
|
if (value is Map<String, dynamic>) {
|
||||||
|
result[newKey] = convertMapKeysToSnakeCase(value);
|
||||||
|
} else if (value is List) {
|
||||||
|
result[newKey] =
|
||||||
|
value.map((item) {
|
||||||
|
if (item is Map<String, dynamic>) {
|
||||||
|
return convertMapKeysToSnakeCase(item);
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}).toList();
|
||||||
|
} else {
|
||||||
|
result[newKey] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -130,9 +130,22 @@ class AccountStatusWidget extends HookConsumerWidget {
|
|||||||
size: 16,
|
size: 16,
|
||||||
).padding(right: 4),
|
).padding(right: 4),
|
||||||
if (status.value?.isCustomized ?? false)
|
if (status.value?.isCustomized ?? false)
|
||||||
Text(status.value?.label ?? 'unknown'.tr())
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
status.value?.label ?? 'unknown'.tr(),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
)
|
||||||
else
|
else
|
||||||
Text((status.value?.label ?? 'offline').toLowerCase()).tr(),
|
Flexible(
|
||||||
|
child:
|
||||||
|
Text(
|
||||||
|
(status.value?.label ?? 'offline').toLowerCase(),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
).tr(),
|
||||||
|
),
|
||||||
if (!(status.value?.isOnline ?? false) &&
|
if (!(status.value?.isOnline ?? false) &&
|
||||||
account.value?.profile.lastSeenAt != null)
|
account.value?.profile.lastSeenAt != null)
|
||||||
Flexible(
|
Flexible(
|
||||||
|
|||||||
@@ -331,7 +331,7 @@ class _WebSocketIndicator extends HookConsumerWidget {
|
|||||||
final user = ref.watch(userInfoProvider);
|
final user = ref.watch(userInfoProvider);
|
||||||
final websocketState = ref.watch(websocketStateProvider);
|
final websocketState = ref.watch(websocketStateProvider);
|
||||||
final indicatorHeight =
|
final indicatorHeight =
|
||||||
MediaQuery.of(context).padding.top + (isDesktop ? 27.5 : 60);
|
MediaQuery.of(context).padding.top + (isDesktop ? 27.5 : 20);
|
||||||
|
|
||||||
Color indicatorColor;
|
Color indicatorColor;
|
||||||
String indicatorText;
|
String indicatorText;
|
||||||
@@ -343,7 +343,7 @@ class _WebSocketIndicator extends HookConsumerWidget {
|
|||||||
indicatorColor = Colors.teal;
|
indicatorColor = Colors.teal;
|
||||||
indicatorText = 'connectionReconnecting';
|
indicatorText = 'connectionReconnecting';
|
||||||
} else {
|
} else {
|
||||||
indicatorColor = Colors.orange;
|
indicatorColor = Colors.red;
|
||||||
indicatorText = 'connectionDisconnected';
|
indicatorText = 'connectionDisconnected';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ import 'dart:async';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/pods/websocket.dart';
|
||||||
import 'package:island/services/notify.dart';
|
import 'package:island/services/notify.dart';
|
||||||
import 'package:island/services/sharing_intent.dart';
|
import 'package:island/services/sharing_intent.dart';
|
||||||
|
import 'package:island/widgets/content/network_status_sheet.dart';
|
||||||
import 'package:island/widgets/tour/tour.dart';
|
import 'package:island/widgets/tour/tour.dart';
|
||||||
|
|
||||||
class AppWrapper extends HookConsumerWidget {
|
class AppWrapper extends HookConsumerWidget {
|
||||||
@@ -25,6 +27,27 @@ class AppWrapper extends HookConsumerWidget {
|
|||||||
};
|
};
|
||||||
}, const []);
|
}, const []);
|
||||||
|
|
||||||
|
final wsNotifier = ref.watch(websocketStateProvider.notifier);
|
||||||
|
final websocketState = ref.watch(websocketStateProvider);
|
||||||
|
|
||||||
|
final networkStateShowing = useState(false);
|
||||||
|
|
||||||
|
if (websocketState == WebSocketState.duplicateDevice()) {
|
||||||
|
if (!networkStateShowing.value) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
networkStateShowing.value = true;
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
isDismissible: false,
|
||||||
|
builder:
|
||||||
|
(context) =>
|
||||||
|
NetworkStatusSheet(onReconnect: () => wsNotifier.connect()),
|
||||||
|
).then((_) => networkStateShowing.value = false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return TourTriggerWidget(child: child);
|
return TourTriggerWidget(child: child);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import 'package:island/models/embed.dart';
|
|||||||
import 'package:island/pods/call.dart';
|
import 'package:island/pods/call.dart';
|
||||||
import 'package:island/pods/translate.dart';
|
import 'package:island/pods/translate.dart';
|
||||||
import 'package:island/screens/chat/room.dart';
|
import 'package:island/screens/chat/room.dart';
|
||||||
|
import 'package:island/utils/mapping.dart';
|
||||||
import 'package:island/widgets/account/account_name.dart';
|
import 'package:island/widgets/account/account_name.dart';
|
||||||
import 'package:island/widgets/account/account_pfc.dart';
|
import 'package:island/widgets/account/account_pfc.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
@@ -292,12 +293,11 @@ class MessageItem extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
if (remoteMessage.meta['embeds'] != null)
|
if (remoteMessage.meta['embeds'] != null)
|
||||||
...((remoteMessage.meta['embeds'] as List<dynamic>)
|
...((remoteMessage.meta['embeds'] as List<dynamic>)
|
||||||
.where((embed) => embed['Type'] == 'link')
|
|
||||||
.map(
|
.map(
|
||||||
(embed) => SnEmbedLink.fromJson(
|
(embed) => convertMapKeysToSnakeCase(embed),
|
||||||
embed as Map<String, dynamic>,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
.where((embed) => embed['type'] == 'link')
|
||||||
|
.map((embed) => SnScrappedLink.fromJson(embed))
|
||||||
.map(
|
.map(
|
||||||
(link) => LayoutBuilder(
|
(link) => LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ Future<SnCheckInResult?> checkInResultToday(Ref ref) async {
|
|||||||
|
|
||||||
class CheckInWidget extends HookConsumerWidget {
|
class CheckInWidget extends HookConsumerWidget {
|
||||||
final EdgeInsets? margin;
|
final EdgeInsets? margin;
|
||||||
const CheckInWidget({super.key, this.margin});
|
final VoidCallback? onChecked;
|
||||||
|
const CheckInWidget({super.key, this.margin, this.onChecked});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -52,6 +53,7 @@ class CheckInWidget extends HookConsumerWidget {
|
|||||||
ref.invalidate(checkInResultTodayProvider);
|
ref.invalidate(checkInResultTodayProvider);
|
||||||
final userNotifier = ref.read(userInfoProvider.notifier);
|
final userNotifier = ref.read(userInfoProvider.notifier);
|
||||||
userNotifier.fetchUser();
|
userNotifier.fetchUser();
|
||||||
|
onChecked?.call();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err is DioException) {
|
if (err is DioException) {
|
||||||
if (err.response?.statusCode == 423 && context.mounted) {
|
if (err.response?.statusCode == 423 && context.mounted) {
|
||||||
|
|||||||
@@ -272,8 +272,96 @@ class AttachmentPreview extends HookConsumerWidget {
|
|||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
child: Column(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
|
AspectRatio(
|
||||||
|
aspectRatio: ratio,
|
||||||
|
child: Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
Builder(
|
||||||
|
key: ValueKey(item.hashCode),
|
||||||
|
builder: (context) {
|
||||||
|
if (item.isOnCloud) {
|
||||||
|
return CloudFileWidget(item: item.data);
|
||||||
|
} else if (item.data is XFile) {
|
||||||
|
final file = item.data as XFile;
|
||||||
|
if (file.path.isEmpty) {
|
||||||
|
return FutureBuilder<Uint8List>(
|
||||||
|
future: file.readAsBytes(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.hasData) {
|
||||||
|
return Image.memory(snapshot.data!);
|
||||||
|
}
|
||||||
|
return const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (item.type) {
|
||||||
|
case UniversalFileType.image:
|
||||||
|
return kIsWeb
|
||||||
|
? Image.network(file.path)
|
||||||
|
: Image.file(File(file.path));
|
||||||
|
default:
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.document_scanner),
|
||||||
|
Text(file.name),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (item is List<int> || item is Uint8List) {
|
||||||
|
switch (item.type) {
|
||||||
|
case UniversalFileType.image:
|
||||||
|
return Image.memory(item.data);
|
||||||
|
default:
|
||||||
|
return Column(
|
||||||
|
children: [const Icon(Symbols.document_scanner)],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Placeholder();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (progress != null)
|
||||||
|
Positioned.fill(
|
||||||
|
child: Container(
|
||||||
|
color: Colors.black.withOpacity(0.3),
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 40,
|
||||||
|
vertical: 16,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
if (progress != null)
|
||||||
|
Text(
|
||||||
|
'${progress!.toStringAsFixed(2)}%',
|
||||||
|
style: TextStyle(color: Colors.white),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Text(
|
||||||
|
'uploading'.tr(),
|
||||||
|
style: TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
Gap(6),
|
||||||
|
Center(
|
||||||
|
child: LinearProgressIndicator(
|
||||||
|
value:
|
||||||
|
progress != null ? progress! / 100.0 : null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
@@ -397,94 +485,6 @@ class AttachmentPreview extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
).padding(horizontal: 12, vertical: 8),
|
).padding(horizontal: 12, vertical: 8),
|
||||||
AspectRatio(
|
|
||||||
aspectRatio: ratio,
|
|
||||||
child: Stack(
|
|
||||||
fit: StackFit.expand,
|
|
||||||
children: [
|
|
||||||
Builder(
|
|
||||||
key: ValueKey(item.hashCode),
|
|
||||||
builder: (context) {
|
|
||||||
if (item.isOnCloud) {
|
|
||||||
return CloudFileWidget(item: item.data);
|
|
||||||
} else if (item.data is XFile) {
|
|
||||||
final file = item.data as XFile;
|
|
||||||
if (file.path.isEmpty) {
|
|
||||||
return FutureBuilder<Uint8List>(
|
|
||||||
future: file.readAsBytes(),
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.hasData) {
|
|
||||||
return Image.memory(snapshot.data!);
|
|
||||||
}
|
|
||||||
return const Center(
|
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (item.type) {
|
|
||||||
case UniversalFileType.image:
|
|
||||||
return kIsWeb
|
|
||||||
? Image.network(file.path)
|
|
||||||
: Image.file(File(file.path));
|
|
||||||
default:
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
const Icon(Symbols.document_scanner),
|
|
||||||
Text(file.name),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if (item is List<int> || item is Uint8List) {
|
|
||||||
switch (item.type) {
|
|
||||||
case UniversalFileType.image:
|
|
||||||
return Image.memory(item.data);
|
|
||||||
default:
|
|
||||||
return Column(
|
|
||||||
children: [const Icon(Symbols.document_scanner)],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Placeholder();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
if (progress != null)
|
|
||||||
Positioned.fill(
|
|
||||||
child: Container(
|
|
||||||
color: Colors.black.withOpacity(0.3),
|
|
||||||
padding: EdgeInsets.symmetric(
|
|
||||||
horizontal: 40,
|
|
||||||
vertical: 16,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
if (progress != null)
|
|
||||||
Text(
|
|
||||||
'${progress!.toStringAsFixed(2)}%',
|
|
||||||
style: TextStyle(color: Colors.white),
|
|
||||||
)
|
|
||||||
else
|
|
||||||
Text(
|
|
||||||
'uploading'.tr(),
|
|
||||||
style: TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
Gap(6),
|
|
||||||
Center(
|
|
||||||
child: LinearProgressIndicator(
|
|
||||||
value:
|
|
||||||
progress != null ? progress! / 100.0 : null,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
import 'dart:ui';
|
|
||||||
|
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -103,6 +102,7 @@ class CloudVideoWidget extends HookConsumerWidget {
|
|||||||
Symbols.play_arrow,
|
Symbols.play_arrow,
|
||||||
fill: 1,
|
fill: 1,
|
||||||
size: 32,
|
size: 32,
|
||||||
|
color: Colors.white,
|
||||||
shadows: [
|
shadows: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black54,
|
color: Colors.black54,
|
||||||
@@ -114,6 +114,26 @@ class CloudVideoWidget extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Positioned(
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: IgnorePointer(
|
||||||
|
child: Container(
|
||||||
|
height: 100,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.bottomCenter,
|
||||||
|
end: Alignment.topCenter,
|
||||||
|
colors: [
|
||||||
|
Theme.of(context).colorScheme.surface.withOpacity(0.85),
|
||||||
|
Colors.transparent,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
@@ -133,6 +153,7 @@ class CloudVideoWidget extends HookConsumerWidget {
|
|||||||
.toInt(),
|
.toInt(),
|
||||||
).formatDuration(),
|
).formatDuration(),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
shadows: [
|
shadows: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black54,
|
color: Colors.black54,
|
||||||
@@ -147,6 +168,7 @@ class CloudVideoWidget extends HookConsumerWidget {
|
|||||||
Text(
|
Text(
|
||||||
'${int.parse(item.fileMeta?['bit_rate'] as String) ~/ 1000} Kbps',
|
'${int.parse(item.fileMeta?['bit_rate'] as String) ~/ 1000} Kbps',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
shadows: [
|
shadows: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black54,
|
color: Colors.black54,
|
||||||
@@ -161,7 +183,10 @@ class CloudVideoWidget extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
item.name,
|
item.name,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
shadows: [
|
shadows: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import 'package:material_symbols_icons/symbols.dart';
|
|||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
class EmbedLinkWidget extends StatelessWidget {
|
class EmbedLinkWidget extends StatelessWidget {
|
||||||
final SnEmbedLink link;
|
final SnScrappedLink link;
|
||||||
final double? maxWidth;
|
final double? maxWidth;
|
||||||
final EdgeInsetsGeometry? margin;
|
final EdgeInsetsGeometry? margin;
|
||||||
|
|
||||||
@@ -116,7 +116,8 @@ class EmbedLinkWidget extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
|
|
||||||
// Description
|
// Description
|
||||||
if (link.description != null && link.description!.isNotEmpty) ...[
|
if (link.description != null &&
|
||||||
|
link.description!.isNotEmpty) ...[
|
||||||
Text(
|
Text(
|
||||||
link.description!,
|
link.description!,
|
||||||
style: theme.textTheme.bodyMedium?.copyWith(
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
|
|||||||
@@ -145,6 +145,8 @@ class MarkdownTextContent extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
case 'stickers':
|
case 'stickers':
|
||||||
final size = doesEnlargeSticker ? 96.0 : 24.0;
|
final size = doesEnlargeSticker ? 96.0 : 24.0;
|
||||||
|
final stickerUri =
|
||||||
|
'$baseUrl/sphere/stickers/lookup/${uri.pathSegments[0]}/open';
|
||||||
return ClipRRect(
|
return ClipRRect(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
child: Container(
|
child: Container(
|
||||||
@@ -155,8 +157,7 @@ class MarkdownTextContent extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: UniversalImage(
|
child: UniversalImage(
|
||||||
uri:
|
uri: stickerUri,
|
||||||
'$baseUrl/sphere/stickers/lookup/${uri.pathSegments[0]}/open',
|
|
||||||
width: size,
|
width: size,
|
||||||
height: size,
|
height: size,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
|
|||||||
81
lib/widgets/content/network_status_sheet.dart
Normal file
81
lib/widgets/content/network_status_sheet.dart
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/pods/websocket.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
|
|
||||||
|
class NetworkStatusSheet extends HookConsumerWidget {
|
||||||
|
final VoidCallback onReconnect;
|
||||||
|
|
||||||
|
const NetworkStatusSheet({super.key, required this.onReconnect});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final ws = ref.watch(websocketProvider);
|
||||||
|
final wsState = ref.watch(websocketStateProvider);
|
||||||
|
|
||||||
|
return SheetScaffold(
|
||||||
|
titleText:
|
||||||
|
wsState == WebSocketState.connected()
|
||||||
|
? 'Connection Status'
|
||||||
|
: 'Connection Issue',
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
wsState.when(
|
||||||
|
connected:
|
||||||
|
() => Text(
|
||||||
|
'Connected to server',
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
),
|
||||||
|
connecting:
|
||||||
|
() => Text(
|
||||||
|
'Connecting to server...',
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
),
|
||||||
|
disconnected:
|
||||||
|
() => Text(
|
||||||
|
'Disconnected from server',
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
),
|
||||||
|
serverDown:
|
||||||
|
() => Text(
|
||||||
|
'The server is not available right now... Please try again later...',
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
),
|
||||||
|
duplicateDevice:
|
||||||
|
() => Text(
|
||||||
|
'Another device has connected with the same account.',
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
),
|
||||||
|
error:
|
||||||
|
(message) => Text(
|
||||||
|
'Connection error: $message',
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
if (ws.heartbeatDelay != null)
|
||||||
|
Text(
|
||||||
|
'Last heartbeat: ${ws.heartbeatDelay!.inMilliseconds}ms',
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
Center(
|
||||||
|
child: FilledButton.icon(
|
||||||
|
icon: const Icon(Symbols.wifi),
|
||||||
|
label: const Text('Reconnect'),
|
||||||
|
onPressed: () {
|
||||||
|
onReconnect();
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
236
lib/widgets/poll/poll_feedback.dart
Normal file
236
lib/widgets/poll/poll_feedback.dart
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/models/poll.dart';
|
||||||
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:island/services/time.dart';
|
||||||
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
|
part 'poll_feedback.g.dart';
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
class PollFeedbackNotifier extends _$PollFeedbackNotifier
|
||||||
|
with CursorPagingNotifierMixin<SnPollAnswer> {
|
||||||
|
static const int _pageSize = 20;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<CursorPagingData<SnPollAnswer>> build(String id) {
|
||||||
|
// immediately load first page
|
||||||
|
return fetch(cursor: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<CursorPagingData<SnPollAnswer>> fetch({
|
||||||
|
required String? cursor,
|
||||||
|
}) async {
|
||||||
|
final client = ref.read(apiClientProvider);
|
||||||
|
final offset = cursor == null ? 0 : int.parse(cursor);
|
||||||
|
|
||||||
|
final queryParams = {'offset': offset, 'take': _pageSize};
|
||||||
|
|
||||||
|
final response = await client.get(
|
||||||
|
'/sphere/polls/$id/feedback',
|
||||||
|
queryParameters: queryParams,
|
||||||
|
);
|
||||||
|
final total = int.parse(response.headers.value('X-Total') ?? '0');
|
||||||
|
final List<dynamic> data = response.data;
|
||||||
|
final items = data.map((json) => SnPollAnswer.fromJson(json)).toList();
|
||||||
|
|
||||||
|
final hasMore = offset + items.length < total;
|
||||||
|
final nextCursor = hasMore ? (offset + items.length).toString() : null;
|
||||||
|
|
||||||
|
return CursorPagingData(
|
||||||
|
items: items,
|
||||||
|
hasMore: hasMore,
|
||||||
|
nextCursor: nextCursor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PollFeedbackSheet extends HookConsumerWidget {
|
||||||
|
final String pollId;
|
||||||
|
final String? title;
|
||||||
|
final SnPoll poll;
|
||||||
|
final Map<String, dynamic>? stats; // stats object similar to PollSubmit
|
||||||
|
const PollFeedbackSheet({
|
||||||
|
super.key,
|
||||||
|
required this.pollId,
|
||||||
|
required this.poll,
|
||||||
|
this.title,
|
||||||
|
this.stats,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
return SheetScaffold(
|
||||||
|
titleText: title ?? 'Poll feedback',
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
_PollHeader(poll: poll, stats: stats),
|
||||||
|
const Divider(height: 1),
|
||||||
|
Expanded(
|
||||||
|
child: PagingHelperView(
|
||||||
|
provider: pollFeedbackNotifierProvider(pollId),
|
||||||
|
futureRefreshable: pollFeedbackNotifierProvider(pollId).future,
|
||||||
|
notifierRefreshable:
|
||||||
|
pollFeedbackNotifierProvider(pollId).notifier,
|
||||||
|
contentBuilder:
|
||||||
|
(data, widgetCount, endItemView) => ListView.separated(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||||
|
itemCount: widgetCount,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (index == widgetCount - 1) {
|
||||||
|
// Provided by PagingHelperView to indicate end/loading
|
||||||
|
return endItemView;
|
||||||
|
}
|
||||||
|
final answer = data.items[index];
|
||||||
|
return _PollAnswerTile(answer: answer, poll: poll);
|
||||||
|
},
|
||||||
|
separatorBuilder:
|
||||||
|
(context, index) =>
|
||||||
|
const Divider(height: 1).padding(vertical: 4),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PollHeader extends StatelessWidget {
|
||||||
|
const _PollHeader({required this.poll, this.stats});
|
||||||
|
final SnPoll poll;
|
||||||
|
final Map<String, dynamic>? stats;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (poll.title != null)
|
||||||
|
Text(poll.title!, style: theme.textTheme.titleLarge),
|
||||||
|
if (poll.description != null)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 2),
|
||||||
|
child: Text(
|
||||||
|
poll.description!,
|
||||||
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
|
color: theme.textTheme.bodyMedium?.color?.withOpacity(0.7),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 20, vertical: 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PollAnswerTile extends StatelessWidget {
|
||||||
|
final SnPollAnswer answer;
|
||||||
|
final SnPoll poll;
|
||||||
|
const _PollAnswerTile({required this.answer, required this.poll});
|
||||||
|
|
||||||
|
String _formatPerQuestionAnswer(
|
||||||
|
SnPollQuestion q,
|
||||||
|
Map<String, dynamic> ansMap,
|
||||||
|
) {
|
||||||
|
switch (q.type) {
|
||||||
|
case SnPollQuestionType.singleChoice:
|
||||||
|
final val = ansMap[q.id];
|
||||||
|
if (val is String) {
|
||||||
|
final opt = q.options?.firstWhere(
|
||||||
|
(o) => o.id == val,
|
||||||
|
orElse: () => SnPollOption(id: val, label: '#$val', order: 0),
|
||||||
|
);
|
||||||
|
return opt?.label ?? '#$val';
|
||||||
|
}
|
||||||
|
return '—';
|
||||||
|
case SnPollQuestionType.multipleChoice:
|
||||||
|
final val = ansMap[q.id];
|
||||||
|
if (val is List) {
|
||||||
|
final ids = val.whereType<String>().toList();
|
||||||
|
if (ids.isEmpty) return '—';
|
||||||
|
final labels =
|
||||||
|
ids.map((id) {
|
||||||
|
final opt = q.options?.firstWhere(
|
||||||
|
(o) => o.id == id,
|
||||||
|
orElse: () => SnPollOption(id: id, label: '#$id', order: 0),
|
||||||
|
);
|
||||||
|
return opt?.label ?? '#$id';
|
||||||
|
}).toList();
|
||||||
|
return labels.join(', ');
|
||||||
|
}
|
||||||
|
return '—';
|
||||||
|
case SnPollQuestionType.yesNo:
|
||||||
|
final val = ansMap[q.id];
|
||||||
|
if (val is bool) {
|
||||||
|
return val ? 'Yes' : 'No';
|
||||||
|
}
|
||||||
|
return '—';
|
||||||
|
case SnPollQuestionType.rating:
|
||||||
|
final val = ansMap[q.id];
|
||||||
|
if (val is int) return val.toString();
|
||||||
|
if (val is num) return val.toString();
|
||||||
|
return '—';
|
||||||
|
case SnPollQuestionType.freeText:
|
||||||
|
final val = ansMap[q.id];
|
||||||
|
if (val is String && val.trim().isNotEmpty) return val;
|
||||||
|
return '—';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// Submit date/time (title)
|
||||||
|
final submitText = answer.createdAt.formatSystem();
|
||||||
|
|
||||||
|
// Compose content from poll questions if provided, otherwise fallback to joined key-values
|
||||||
|
String content;
|
||||||
|
if (poll.questions.isNotEmpty) {
|
||||||
|
final questions = [...poll.questions]
|
||||||
|
..sort((a, b) => a.order.compareTo(b.order));
|
||||||
|
final buffer = StringBuffer();
|
||||||
|
for (final q in questions) {
|
||||||
|
final formatted = _formatPerQuestionAnswer(q, answer.answer);
|
||||||
|
buffer.writeln('${q.title}: $formatted');
|
||||||
|
}
|
||||||
|
content = buffer.toString().trimRight();
|
||||||
|
} else {
|
||||||
|
// Fallback formatting without poll context. We still want to show the question title
|
||||||
|
// instead of the raw question id key if we can derive it from the answer map itself.
|
||||||
|
// Since we don't have poll metadata here, we cannot resolve the title; therefore we
|
||||||
|
// will show only values line-by-line without exposing the raw id.
|
||||||
|
if (answer.answer.isEmpty) {
|
||||||
|
content = '—';
|
||||||
|
} else {
|
||||||
|
final parts = <String>[];
|
||||||
|
answer.answer.forEach((key, value) {
|
||||||
|
var question = poll.questions.firstWhere((q) => q.id == key);
|
||||||
|
if (value is List) {
|
||||||
|
parts.add('${question.title}: ${value.join(', ')}');
|
||||||
|
} else {
|
||||||
|
parts.add('${question.title}: $value');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
content = parts.join('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
isThreeLine: true,
|
||||||
|
leading: const CircleAvatar(
|
||||||
|
radius: 16,
|
||||||
|
child: Icon(Icons.how_to_vote, size: 16),
|
||||||
|
),
|
||||||
|
title: Text(submitText),
|
||||||
|
subtitle: Text(content),
|
||||||
|
trailing: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
180
lib/widgets/poll/poll_feedback.g.dart
Normal file
180
lib/widgets/poll/poll_feedback.g.dart
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'poll_feedback.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$pollFeedbackNotifierHash() =>
|
||||||
|
r'1bf3925b5b751cfd1a9abafb75274f1e95e7f27e';
|
||||||
|
|
||||||
|
/// Copied from Dart SDK
|
||||||
|
class _SystemHash {
|
||||||
|
_SystemHash._();
|
||||||
|
|
||||||
|
static int combine(int hash, int value) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
hash = 0x1fffffff & (hash + value);
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||||
|
return hash ^ (hash >> 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int finish(int hash) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
hash = hash ^ (hash >> 11);
|
||||||
|
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _$PollFeedbackNotifier
|
||||||
|
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnPollAnswer>> {
|
||||||
|
late final String id;
|
||||||
|
|
||||||
|
FutureOr<CursorPagingData<SnPollAnswer>> build(String id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [PollFeedbackNotifier].
|
||||||
|
@ProviderFor(PollFeedbackNotifier)
|
||||||
|
const pollFeedbackNotifierProvider = PollFeedbackNotifierFamily();
|
||||||
|
|
||||||
|
/// See also [PollFeedbackNotifier].
|
||||||
|
class PollFeedbackNotifierFamily
|
||||||
|
extends Family<AsyncValue<CursorPagingData<SnPollAnswer>>> {
|
||||||
|
/// See also [PollFeedbackNotifier].
|
||||||
|
const PollFeedbackNotifierFamily();
|
||||||
|
|
||||||
|
/// See also [PollFeedbackNotifier].
|
||||||
|
PollFeedbackNotifierProvider call(String id) {
|
||||||
|
return PollFeedbackNotifierProvider(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
PollFeedbackNotifierProvider getProviderOverride(
|
||||||
|
covariant PollFeedbackNotifierProvider provider,
|
||||||
|
) {
|
||||||
|
return call(provider.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||||
|
|
||||||
|
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||||
|
_allTransitiveDependencies;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get name => r'pollFeedbackNotifierProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [PollFeedbackNotifier].
|
||||||
|
class PollFeedbackNotifierProvider
|
||||||
|
extends
|
||||||
|
AutoDisposeAsyncNotifierProviderImpl<
|
||||||
|
PollFeedbackNotifier,
|
||||||
|
CursorPagingData<SnPollAnswer>
|
||||||
|
> {
|
||||||
|
/// See also [PollFeedbackNotifier].
|
||||||
|
PollFeedbackNotifierProvider(String id)
|
||||||
|
: this._internal(
|
||||||
|
() => PollFeedbackNotifier()..id = id,
|
||||||
|
from: pollFeedbackNotifierProvider,
|
||||||
|
name: r'pollFeedbackNotifierProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$pollFeedbackNotifierHash,
|
||||||
|
dependencies: PollFeedbackNotifierFamily._dependencies,
|
||||||
|
allTransitiveDependencies:
|
||||||
|
PollFeedbackNotifierFamily._allTransitiveDependencies,
|
||||||
|
id: id,
|
||||||
|
);
|
||||||
|
|
||||||
|
PollFeedbackNotifierProvider._internal(
|
||||||
|
super._createNotifier, {
|
||||||
|
required super.name,
|
||||||
|
required super.dependencies,
|
||||||
|
required super.allTransitiveDependencies,
|
||||||
|
required super.debugGetCreateSourceHash,
|
||||||
|
required super.from,
|
||||||
|
required this.id,
|
||||||
|
}) : super.internal();
|
||||||
|
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<CursorPagingData<SnPollAnswer>> runNotifierBuild(
|
||||||
|
covariant PollFeedbackNotifier notifier,
|
||||||
|
) {
|
||||||
|
return notifier.build(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Override overrideWith(PollFeedbackNotifier Function() create) {
|
||||||
|
return ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
override: PollFeedbackNotifierProvider._internal(
|
||||||
|
() => create()..id = id,
|
||||||
|
from: from,
|
||||||
|
name: null,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
debugGetCreateSourceHash: null,
|
||||||
|
id: id,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AutoDisposeAsyncNotifierProviderElement<
|
||||||
|
PollFeedbackNotifier,
|
||||||
|
CursorPagingData<SnPollAnswer>
|
||||||
|
>
|
||||||
|
createElement() {
|
||||||
|
return _PollFeedbackNotifierProviderElement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is PollFeedbackNotifierProvider && other.id == id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, id.hashCode);
|
||||||
|
|
||||||
|
return _SystemHash.finish(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
mixin PollFeedbackNotifierRef
|
||||||
|
on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnPollAnswer>> {
|
||||||
|
/// The parameter `id` of this provider.
|
||||||
|
String get id;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PollFeedbackNotifierProviderElement
|
||||||
|
extends
|
||||||
|
AutoDisposeAsyncNotifierProviderElement<
|
||||||
|
PollFeedbackNotifier,
|
||||||
|
CursorPagingData<SnPollAnswer>
|
||||||
|
>
|
||||||
|
with PollFeedbackNotifierRef {
|
||||||
|
_PollFeedbackNotifierProviderElement(super.provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get id => (origin as PollFeedbackNotifierProvider).id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
@@ -1,23 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:island/models/poll.dart';
|
import 'package:island/models/poll.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:island/widgets/alert.dart';
|
||||||
|
|
||||||
/// A poll answering widget that shows one question at a time and collects answers.
|
|
||||||
///
|
|
||||||
/// Usage:
|
|
||||||
/// PollSubmit(
|
|
||||||
/// poll: poll,
|
|
||||||
/// onSubmit: (answers) {
|
|
||||||
/// // answers is Map<String, dynamic>: questionId -> answer
|
|
||||||
/// // answer types by question:
|
|
||||||
/// // - singleChoice: String optionId
|
|
||||||
/// // - multipleChoice: List<String> optionIds
|
|
||||||
/// // - yesNo: bool
|
|
||||||
/// // - rating: int (1..5)
|
|
||||||
/// // - freeText: String
|
|
||||||
/// },
|
|
||||||
/// )
|
|
||||||
class PollSubmit extends ConsumerStatefulWidget {
|
class PollSubmit extends ConsumerStatefulWidget {
|
||||||
const PollSubmit({
|
const PollSubmit({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -208,12 +195,11 @@ class _PollSubmitState extends ConsumerState<PollSubmit> {
|
|||||||
|
|
||||||
// Only call onSubmit after server accepts
|
// Only call onSubmit after server accepts
|
||||||
widget.onSubmit(Map<String, dynamic>.unmodifiable(_answers));
|
widget.onSubmit(Map<String, dynamic>.unmodifiable(_answers));
|
||||||
|
|
||||||
|
showSnackBar('Poll answer has been submitted.');
|
||||||
|
HapticFeedback.heavyImpact();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
showErrorAlert(e);
|
||||||
ScaffoldMessenger.of(
|
|
||||||
context,
|
|
||||||
).showSnackBar(SnackBar(content: Text('Failed to submit poll: $e')));
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|||||||
@@ -189,8 +189,8 @@ class ComposePollSheet extends HookConsumerWidget {
|
|||||||
Widget? _buildPollSubtitle(SnPoll poll) {
|
Widget? _buildPollSubtitle(SnPoll poll) {
|
||||||
try {
|
try {
|
||||||
final SnPoll dyn = poll;
|
final SnPoll dyn = poll;
|
||||||
final List<SnPollQuestion>? options = dyn.questions;
|
final List<SnPollQuestion> options = dyn.questions;
|
||||||
if (options == null || options.isEmpty) return null;
|
if (options.isEmpty) return null;
|
||||||
final preview = options.take(3).map((e) => e.title).join(' · ');
|
final preview = options.take(3).map((e) => e.title).join(' · ');
|
||||||
if (preview.trim().isEmpty) return null;
|
if (preview.trim().isEmpty) return null;
|
||||||
return Text(preview);
|
return Text(preview);
|
||||||
|
|||||||
@@ -1,11 +1,29 @@
|
|||||||
|
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/models/post_category.dart';
|
||||||
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/widgets/content/sheet.dart';
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
|
import 'package:island/widgets/post/compose_shared.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:textfield_tags/textfield_tags.dart';
|
import 'package:textfield_tags/textfield_tags.dart';
|
||||||
|
|
||||||
|
part 'compose_settings_sheet.g.dart';
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
Future<List<SnPostCategory>> postCategories(Ref ref) async {
|
||||||
|
final apiClient = ref.watch(apiClientProvider);
|
||||||
|
final resp = await apiClient.get('/sphere/posts/categories');
|
||||||
|
return resp.data
|
||||||
|
.map((e) => SnPostCategory.fromJson(e))
|
||||||
|
.cast<SnPostCategory>()
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
/// A reusable widget for tag input fields with chip display
|
/// A reusable widget for tag input fields with chip display
|
||||||
class ChipTagInputField extends StatelessWidget {
|
class ChipTagInputField extends StatelessWidget {
|
||||||
final InputFieldValues inputFieldValues;
|
final InputFieldValues inputFieldValues;
|
||||||
@@ -98,31 +116,20 @@ class ChipTagInputField extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ComposeSettingsSheet extends HookWidget {
|
class ComposeSettingsSheet extends HookConsumerWidget {
|
||||||
final TextEditingController titleController;
|
final ComposeState state;
|
||||||
final TextEditingController descriptionController;
|
|
||||||
final ValueNotifier<int> visibility;
|
|
||||||
final VoidCallback? onVisibilityChanged;
|
|
||||||
final StringTagController tagsController;
|
|
||||||
final StringTagController categoriesController;
|
|
||||||
|
|
||||||
const ComposeSettingsSheet({
|
const ComposeSettingsSheet({super.key, required this.state});
|
||||||
super.key,
|
|
||||||
required this.titleController,
|
|
||||||
required this.descriptionController,
|
|
||||||
required this.visibility,
|
|
||||||
this.onVisibilityChanged,
|
|
||||||
required this.tagsController,
|
|
||||||
required this.categoriesController,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final colorScheme = theme.colorScheme;
|
final colorScheme = theme.colorScheme;
|
||||||
|
|
||||||
// Listen to visibility changes to trigger rebuilds
|
// Listen to visibility changes to trigger rebuilds
|
||||||
final currentVisibility = useValueListenable(visibility);
|
final currentVisibility = useValueListenable(state.visibility);
|
||||||
|
final currentCategories = useValueListenable(state.categories);
|
||||||
|
final postCategories = ref.watch(postCategoriesProvider);
|
||||||
|
|
||||||
IconData getVisibilityIcon(int visibilityValue) {
|
IconData getVisibilityIcon(int visibilityValue) {
|
||||||
switch (visibilityValue) {
|
switch (visibilityValue) {
|
||||||
@@ -160,11 +167,10 @@ class ComposeSettingsSheet extends HookWidget {
|
|||||||
leading: Icon(icon),
|
leading: Icon(icon),
|
||||||
title: Text(textKey.tr()),
|
title: Text(textKey.tr()),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
visibility.value = value;
|
state.visibility.value = value;
|
||||||
onVisibilityChanged?.call();
|
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
selected: visibility.value == value,
|
selected: state.visibility.value == value,
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -210,48 +216,16 @@ class ComposeSettingsSheet extends HookWidget {
|
|||||||
|
|
||||||
return SheetScaffold(
|
return SheetScaffold(
|
||||||
titleText: 'postSettings'.tr(),
|
titleText: 'postSettings'.tr(),
|
||||||
|
heightFactor: 0.6,
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
spacing: 16,
|
spacing: 16,
|
||||||
children: [
|
children: [
|
||||||
// Title field
|
|
||||||
TextField(
|
|
||||||
controller: titleController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'postTitle'.tr(),
|
|
||||||
hintText: 'postTitle'.tr(),
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
contentPadding: const EdgeInsets.all(16),
|
|
||||||
),
|
|
||||||
style: theme.textTheme.titleMedium,
|
|
||||||
onTapOutside:
|
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Description field
|
|
||||||
TextField(
|
|
||||||
controller: descriptionController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'postDescription'.tr(),
|
|
||||||
hintText: 'postDescription'.tr(),
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
contentPadding: const EdgeInsets.all(16),
|
|
||||||
),
|
|
||||||
style: theme.textTheme.bodyMedium,
|
|
||||||
maxLines: 3,
|
|
||||||
onTapOutside:
|
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Tags field
|
// Tags field
|
||||||
TextFieldTags(
|
TextFieldTags(
|
||||||
textfieldTagsController: tagsController,
|
textfieldTagsController: state.tagsController,
|
||||||
textSeparators: const [' ', ','],
|
textSeparators: const [' ', ','],
|
||||||
letterCase: LetterCase.normal,
|
letterCase: LetterCase.normal,
|
||||||
validator: (String tag) {
|
validator: (String tag) {
|
||||||
@@ -270,23 +244,106 @@ class ComposeSettingsSheet extends HookWidget {
|
|||||||
),
|
),
|
||||||
|
|
||||||
// Categories field
|
// Categories field
|
||||||
TextFieldTags(
|
// FIXME: Sometimes the entire dropdown crashes: 'package:flutter/src/rendering/stack.dart': Failed assertion: line 799 pos 12: 'firstChild == null || child != null': is not true.
|
||||||
textfieldTagsController: categoriesController,
|
DropdownButtonFormField2<SnPostCategory>(
|
||||||
textSeparators: const [' ', ','],
|
isExpanded: true,
|
||||||
letterCase: LetterCase.small,
|
decoration: InputDecoration(
|
||||||
validator: (String tag) {
|
contentPadding: const EdgeInsets.symmetric(vertical: 9),
|
||||||
if (tag.isEmpty) return 'No, cannot be empty';
|
border: OutlineInputBorder(
|
||||||
if (tag.contains(' ')) return 'Tags should be URL-safe';
|
borderRadius: BorderRadius.circular(12),
|
||||||
return null;
|
),
|
||||||
|
),
|
||||||
|
hint: Text('categories'.tr(), style: TextStyle(fontSize: 15)),
|
||||||
|
items:
|
||||||
|
(postCategories.value ?? <SnPostCategory>[]).map((item) {
|
||||||
|
return DropdownMenuItem(
|
||||||
|
value: item,
|
||||||
|
enabled: false,
|
||||||
|
child: StatefulBuilder(
|
||||||
|
builder: (context, menuSetState) {
|
||||||
|
final isSelected = state.categories.value.contains(
|
||||||
|
item,
|
||||||
|
);
|
||||||
|
return InkWell(
|
||||||
|
onTap: () {
|
||||||
|
isSelected
|
||||||
|
? state.categories.value =
|
||||||
|
state.categories.value
|
||||||
|
.where((e) => e != item)
|
||||||
|
.toList()
|
||||||
|
: state.categories.value = [
|
||||||
|
...state.categories.value,
|
||||||
|
item,
|
||||||
|
];
|
||||||
|
menuSetState(() {});
|
||||||
},
|
},
|
||||||
inputFieldBuilder: (context, inputFieldValues) {
|
child: Container(
|
||||||
return ChipTagInputField(
|
height: double.infinity,
|
||||||
inputFieldValues: inputFieldValues,
|
padding: const EdgeInsets.symmetric(
|
||||||
labelText: 'categories',
|
horizontal: 16.0,
|
||||||
hintText: 'categoriesHint',
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
if (isSelected)
|
||||||
|
const Icon(Icons.check_box_outlined)
|
||||||
|
else
|
||||||
|
const Icon(Icons.check_box_outline_blank),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
item.categoryDisplayTitle,
|
||||||
|
style: const TextStyle(fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
value: currentCategories.isEmpty ? null : currentCategories.last,
|
||||||
|
onChanged: (_) {},
|
||||||
|
selectedItemBuilder: (context) {
|
||||||
|
return currentCategories.map((item) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
for (final category in currentCategories)
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 4,
|
||||||
|
),
|
||||||
|
margin: const EdgeInsets.only(right: 4),
|
||||||
|
child: Text(
|
||||||
|
category.categoryDisplayTitle,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.onPrimary,
|
||||||
|
fontSize: 13,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
},
|
||||||
|
buttonStyleData: const ButtonStyleData(
|
||||||
|
padding: EdgeInsets.only(left: 16, right: 8),
|
||||||
|
height: 40,
|
||||||
|
),
|
||||||
|
menuItemStyleData: const MenuItemStyleData(
|
||||||
|
height: 40,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
// Visibility setting
|
// Visibility setting
|
||||||
Container(
|
Container(
|
||||||
|
|||||||
29
lib/widgets/post/compose_settings_sheet.g.dart
Normal file
29
lib/widgets/post/compose_settings_sheet.g.dart
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'compose_settings_sheet.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$postCategoriesHash() => r'24337fe806d088b6468a350f62d5a5d40232a73c';
|
||||||
|
|
||||||
|
/// See also [postCategories].
|
||||||
|
@ProviderFor(postCategories)
|
||||||
|
final postCategoriesProvider =
|
||||||
|
AutoDisposeFutureProvider<List<SnPostCategory>>.internal(
|
||||||
|
postCategories,
|
||||||
|
name: r'postCategoriesProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$postCategoriesHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
typedef PostCategoriesRef = AutoDisposeFutureProviderRef<List<SnPostCategory>>;
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
@@ -7,6 +7,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:island/models/file.dart';
|
import 'package:island/models/file.dart';
|
||||||
import 'package:island/models/post.dart';
|
import 'package:island/models/post.dart';
|
||||||
|
import 'package:island/models/post_category.dart';
|
||||||
import 'package:island/models/publisher.dart';
|
import 'package:island/models/publisher.dart';
|
||||||
import 'package:island/pods/config.dart';
|
import 'package:island/pods/config.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
@@ -30,8 +31,8 @@ class ComposeState {
|
|||||||
final ValueNotifier<Map<int, double>> attachmentProgress;
|
final ValueNotifier<Map<int, double>> attachmentProgress;
|
||||||
final ValueNotifier<SnPublisher?> currentPublisher;
|
final ValueNotifier<SnPublisher?> currentPublisher;
|
||||||
final ValueNotifier<bool> submitting;
|
final ValueNotifier<bool> submitting;
|
||||||
|
final ValueNotifier<List<SnPostCategory>> categories;
|
||||||
StringTagController tagsController;
|
StringTagController tagsController;
|
||||||
StringTagController categoriesController;
|
|
||||||
final String draftId;
|
final String draftId;
|
||||||
int postType;
|
int postType;
|
||||||
// Linked poll id for this compose session (nullable)
|
// Linked poll id for this compose session (nullable)
|
||||||
@@ -48,7 +49,7 @@ class ComposeState {
|
|||||||
required this.currentPublisher,
|
required this.currentPublisher,
|
||||||
required this.submitting,
|
required this.submitting,
|
||||||
required this.tagsController,
|
required this.tagsController,
|
||||||
required this.categoriesController,
|
required this.categories,
|
||||||
required this.draftId,
|
required this.draftId,
|
||||||
this.postType = 0,
|
this.postType = 0,
|
||||||
String? pollId,
|
String? pollId,
|
||||||
@@ -80,11 +81,7 @@ class ComposeLogic {
|
|||||||
}) {
|
}) {
|
||||||
final id = draftId ?? DateTime.now().millisecondsSinceEpoch.toString();
|
final id = draftId ?? DateTime.now().millisecondsSinceEpoch.toString();
|
||||||
final tagsController = StringTagController();
|
final tagsController = StringTagController();
|
||||||
final categoriesController = StringTagController();
|
|
||||||
originalPost?.tags.forEach((x) => tagsController.addTag(x.slug));
|
originalPost?.tags.forEach((x) => tagsController.addTag(x.slug));
|
||||||
originalPost?.categories.forEach(
|
|
||||||
(x) => categoriesController.addTag(x.slug),
|
|
||||||
);
|
|
||||||
return ComposeState(
|
return ComposeState(
|
||||||
attachments: ValueNotifier<List<UniversalFile>>(
|
attachments: ValueNotifier<List<UniversalFile>>(
|
||||||
originalPost?.attachments
|
originalPost?.attachments
|
||||||
@@ -112,7 +109,9 @@ class ComposeLogic {
|
|||||||
attachmentProgress: ValueNotifier<Map<int, double>>({}),
|
attachmentProgress: ValueNotifier<Map<int, double>>({}),
|
||||||
currentPublisher: ValueNotifier<SnPublisher?>(originalPost?.publisher),
|
currentPublisher: ValueNotifier<SnPublisher?>(originalPost?.publisher),
|
||||||
tagsController: tagsController,
|
tagsController: tagsController,
|
||||||
categoriesController: categoriesController,
|
categories: ValueNotifier<List<SnPostCategory>>(
|
||||||
|
originalPost?.categories ?? [],
|
||||||
|
),
|
||||||
draftId: id,
|
draftId: id,
|
||||||
postType: postType,
|
postType: postType,
|
||||||
// initialize without poll by default
|
// initialize without poll by default
|
||||||
@@ -141,7 +140,7 @@ class ComposeLogic {
|
|||||||
attachmentProgress: ValueNotifier<Map<int, double>>({}),
|
attachmentProgress: ValueNotifier<Map<int, double>>({}),
|
||||||
currentPublisher: ValueNotifier<SnPublisher?>(null),
|
currentPublisher: ValueNotifier<SnPublisher?>(null),
|
||||||
tagsController: tagsController,
|
tagsController: tagsController,
|
||||||
categoriesController: categoriesController,
|
categories: ValueNotifier<List<SnPostCategory>>([]),
|
||||||
draftId: draft.id,
|
draftId: draft.id,
|
||||||
postType: postType,
|
postType: postType,
|
||||||
pollId: null,
|
pollId: null,
|
||||||
@@ -640,7 +639,7 @@ class ComposeLogic {
|
|||||||
if (repliedPost != null) 'replied_post_id': repliedPost.id,
|
if (repliedPost != null) 'replied_post_id': repliedPost.id,
|
||||||
if (forwardedPost != null) 'forwarded_post_id': forwardedPost.id,
|
if (forwardedPost != null) 'forwarded_post_id': forwardedPost.id,
|
||||||
'tags': state.tagsController.getTags,
|
'tags': state.tagsController.getTags,
|
||||||
'categories': state.categoriesController.getTags,
|
'categories': state.categories.value.map((e) => e.slug).toList(),
|
||||||
if (state.pollId.value != null) 'poll_id': state.pollId.value,
|
if (state.pollId.value != null) 'poll_id': state.pollId.value,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -689,7 +688,7 @@ class ComposeLogic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void handleKeyPress(
|
static void handleKeyPress(
|
||||||
RawKeyEvent event,
|
KeyEvent event,
|
||||||
ComposeState state,
|
ComposeState state,
|
||||||
WidgetRef ref,
|
WidgetRef ref,
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
@@ -697,11 +696,13 @@ class ComposeLogic {
|
|||||||
SnPost? repliedPost,
|
SnPost? repliedPost,
|
||||||
SnPost? forwardedPost,
|
SnPost? forwardedPost,
|
||||||
}) {
|
}) {
|
||||||
if (event is! RawKeyDownEvent) return;
|
if (event is! KeyDownEvent) return;
|
||||||
|
|
||||||
final isPaste = event.logicalKey == LogicalKeyboardKey.keyV;
|
final isPaste = event.logicalKey == LogicalKeyboardKey.keyV;
|
||||||
final isSave = event.logicalKey == LogicalKeyboardKey.keyS;
|
final isSave = event.logicalKey == LogicalKeyboardKey.keyS;
|
||||||
final isModifierPressed = event.isMetaPressed || event.isControlPressed;
|
final isModifierPressed =
|
||||||
|
HardwareKeyboard.instance.isMetaPressed ||
|
||||||
|
HardwareKeyboard.instance.isControlPressed;
|
||||||
final isSubmit = event.logicalKey == LogicalKeyboardKey.enter;
|
final isSubmit = event.logicalKey == LogicalKeyboardKey.enter;
|
||||||
|
|
||||||
if (isPaste && isModifierPressed) {
|
if (isPaste && isModifierPressed) {
|
||||||
@@ -731,7 +732,7 @@ class ComposeLogic {
|
|||||||
state.attachmentProgress.dispose();
|
state.attachmentProgress.dispose();
|
||||||
state.currentPublisher.dispose();
|
state.currentPublisher.dispose();
|
||||||
state.tagsController.dispose();
|
state.tagsController.dispose();
|
||||||
state.categoriesController.dispose();
|
state.categories.dispose();
|
||||||
state.pollId.dispose();
|
state.pollId.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ class ComposeToolbar extends HookConsumerWidget {
|
|||||||
|
|
||||||
return Material(
|
return Material(
|
||||||
elevation: 4,
|
elevation: 4,
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainerLow,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 560),
|
constraints: const BoxConstraints(maxWidth: 560),
|
||||||
|
|||||||
@@ -279,18 +279,14 @@ class _DraftItem extends StatelessWidget {
|
|||||||
|
|
||||||
String _parseVisibility(int visibility) {
|
String _parseVisibility(int visibility) {
|
||||||
switch (visibility) {
|
switch (visibility) {
|
||||||
case 0:
|
|
||||||
return 'public'.tr();
|
|
||||||
case 1:
|
case 1:
|
||||||
return 'unlisted'.tr();
|
return 'postVisibilityFriends';
|
||||||
case 2:
|
case 2:
|
||||||
return 'friends'.tr();
|
return 'postVisibilityUnlisted';
|
||||||
case 3:
|
case 3:
|
||||||
return 'selected'.tr();
|
return 'postVisibilityPrivate';
|
||||||
case 4:
|
|
||||||
return 'private'.tr();
|
|
||||||
default:
|
default:
|
||||||
return 'unknown'.tr();
|
return 'postVisibilityPublic';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
105
lib/widgets/post/post_featured.dart
Normal file
105
lib/widgets/post/post_featured.dart
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/models/post.dart';
|
||||||
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
import 'package:island/widgets/post/post_item.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
|
part 'post_featured.g.dart';
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
Future<List<SnPost>> featuredPosts(Ref ref) async {
|
||||||
|
final apiClient = ref.watch(apiClientProvider);
|
||||||
|
final resp = await apiClient.get('/sphere/posts/featured');
|
||||||
|
return resp.data.map((e) => SnPost.fromJson(e)).cast<SnPost>().toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
class PostFeaturedList extends HookConsumerWidget {
|
||||||
|
const PostFeaturedList({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final featuredPostsAsync = ref.watch(featuredPostsProvider);
|
||||||
|
|
||||||
|
final pageViewController = usePageController();
|
||||||
|
final pageViewCurrent = useState(0);
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
pageViewController.addListener(() {
|
||||||
|
pageViewCurrent.value = pageViewController.page?.round() ?? 0;
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}, [pageViewController]);
|
||||||
|
|
||||||
|
return ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
child: Card(
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.highlight),
|
||||||
|
Text('Highlight Posts'),
|
||||||
|
Spacer(),
|
||||||
|
IconButton(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
onPressed: () {
|
||||||
|
pageViewController.animateToPage(
|
||||||
|
pageViewCurrent.value - 1,
|
||||||
|
duration: const Duration(milliseconds: 250),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: const Icon(Symbols.arrow_left),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
onPressed: () {
|
||||||
|
pageViewController.animateToPage(
|
||||||
|
pageViewCurrent.value + 1,
|
||||||
|
duration: const Duration(milliseconds: 250),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: const Icon(Symbols.arrow_right),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 16, vertical: 8),
|
||||||
|
featuredPostsAsync.when(
|
||||||
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
|
error: (error, stack) => Center(child: Text('Error: $error')),
|
||||||
|
data: (posts) {
|
||||||
|
return SizedBox(
|
||||||
|
height: 320,
|
||||||
|
child: PageView.builder(
|
||||||
|
controller: pageViewController,
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
itemCount: posts.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: PostActionableItem(
|
||||||
|
item: posts[index],
|
||||||
|
borderRadius: 8,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
lib/widgets/post/post_featured.g.dart
Normal file
28
lib/widgets/post/post_featured.g.dart
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'post_featured.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$featuredPostsHash() => r'4b7fffb02eac72f5861b02af1b1e5da36b571698';
|
||||||
|
|
||||||
|
/// See also [featuredPosts].
|
||||||
|
@ProviderFor(featuredPosts)
|
||||||
|
final featuredPostsProvider = AutoDisposeFutureProvider<List<SnPost>>.internal(
|
||||||
|
featuredPosts,
|
||||||
|
name: r'featuredPostsProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$featuredPostsHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
typedef FeaturedPostsRef = AutoDisposeFutureProviderRef<List<SnPost>>;
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
@@ -16,6 +16,7 @@ import 'package:island/pods/userinfo.dart';
|
|||||||
import 'package:island/screens/posts/compose.dart';
|
import 'package:island/screens/posts/compose.dart';
|
||||||
import 'package:island/services/responsive.dart';
|
import 'package:island/services/responsive.dart';
|
||||||
import 'package:island/services/time.dart';
|
import 'package:island/services/time.dart';
|
||||||
|
import 'package:island/utils/mapping.dart';
|
||||||
import 'package:island/widgets/account/account_name.dart';
|
import 'package:island/widgets/account/account_name.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/content/cloud_file_collection.dart';
|
import 'package:island/widgets/content/cloud_file_collection.dart';
|
||||||
@@ -314,6 +315,19 @@ class PostItem extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String parseVisibility(int visibility) {
|
||||||
|
switch (visibility) {
|
||||||
|
case 1:
|
||||||
|
return 'postVisibilityFriends';
|
||||||
|
case 2:
|
||||||
|
return 'postVisibilityUnlisted';
|
||||||
|
case 3:
|
||||||
|
return 'postVisibilityPrivate';
|
||||||
|
default:
|
||||||
|
return 'postVisibilityPublic';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -349,13 +363,27 @@ class PostItem extends HookConsumerWidget {
|
|||||||
Text('@${item.publisher.name}').fontSize(11),
|
Text('@${item.publisher.name}').fontSize(11),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
Row(
|
||||||
|
spacing: 6,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
Text(
|
Text(
|
||||||
isFullPost
|
isFullPost
|
||||||
? (item.publishedAt ?? item.createdAt)!.formatSystem()
|
? (item.publishedAt ?? item.createdAt)!
|
||||||
: (item.publishedAt ?? item.createdAt)!.formatRelative(
|
.formatSystem()
|
||||||
context,
|
: (item.publishedAt ?? item.createdAt)!
|
||||||
),
|
.formatRelative(context),
|
||||||
).fontSize(10),
|
).fontSize(10),
|
||||||
|
if (item.editedAt != null)
|
||||||
|
Text(
|
||||||
|
'editedAt'.tr(args: [item.editedAt!.formatSystem()]),
|
||||||
|
).fontSize(10),
|
||||||
|
if (item.visibility != 0)
|
||||||
|
Text(
|
||||||
|
parseVisibility(item.visibility).tr(),
|
||||||
|
).fontSize(10),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -535,7 +563,7 @@ class PostItem extends HookConsumerWidget {
|
|||||||
right: renderingPadding.horizontal,
|
right: renderingPadding.horizontal,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (item.attachments.isNotEmpty)
|
if (item.attachments.isNotEmpty && item.type != 1)
|
||||||
CloudFileList(
|
CloudFileList(
|
||||||
files: item.attachments,
|
files: item.attachments,
|
||||||
padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.symmetric(
|
||||||
@@ -543,11 +571,65 @@ class PostItem extends HookConsumerWidget {
|
|||||||
vertical: 4,
|
vertical: 4,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (item.tags.isNotEmpty)
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
spacing: 2,
|
||||||
|
children: [
|
||||||
|
if (item.tags.isNotEmpty)
|
||||||
|
Wrap(
|
||||||
|
runAlignment: WrapAlignment.center,
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.label, size: 16).padding(top: 2),
|
||||||
|
for (final tag
|
||||||
|
in isFullPost ? item.tags : item.tags.take(3))
|
||||||
|
InkWell(
|
||||||
|
child: Text('#${tag.name ?? tag.slug}'),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed(
|
||||||
|
'postTagDetail',
|
||||||
|
pathParameters: {'slug': tag.slug},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (!isFullPost && item.tags.length > 3)
|
||||||
|
Text('+${item.tags.length - 3}').opacity(0.6),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (item.categories.isNotEmpty)
|
||||||
|
Wrap(
|
||||||
|
runAlignment: WrapAlignment.center,
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.category, size: 16).padding(top: 2),
|
||||||
|
for (final category
|
||||||
|
in isFullPost
|
||||||
|
? item.categories
|
||||||
|
: item.categories.take(2))
|
||||||
|
InkWell(
|
||||||
|
child: Text(category.categoryDisplayTitle),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed(
|
||||||
|
'postCategoryDetail',
|
||||||
|
pathParameters: {'slug': category.slug},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (!isFullPost && item.categories.length > 2)
|
||||||
|
Text('+${item.categories.length - 2}').opacity(0.6),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: renderingPadding.horizontal + 4, top: 4),
|
||||||
if (item.meta?['embeds'] != null)
|
if (item.meta?['embeds'] != null)
|
||||||
...((item.meta!['embeds'] as List<dynamic>).map(
|
...((item.meta!['embeds'] as List<dynamic>)
|
||||||
|
.map((embedData) => convertMapKeysToSnakeCase(embedData))
|
||||||
|
.map(
|
||||||
(embedData) => switch (embedData['type']) {
|
(embedData) => switch (embedData['type']) {
|
||||||
'link' => EmbedLinkWidget(
|
'link' => EmbedLinkWidget(
|
||||||
link: SnEmbedLink.fromJson(embedData as Map<String, dynamic>),
|
link: SnScrappedLink.fromJson(embedData),
|
||||||
maxWidth: math.min(
|
maxWidth: math.min(
|
||||||
MediaQuery.of(context).size.width,
|
MediaQuery.of(context).size.width,
|
||||||
kWideScreenWidth,
|
kWideScreenWidth,
|
||||||
@@ -564,8 +646,12 @@ class PostItem extends HookConsumerWidget {
|
|||||||
horizontal: renderingPadding.horizontal,
|
horizontal: renderingPadding.horizontal,
|
||||||
vertical: 8,
|
vertical: 8,
|
||||||
),
|
),
|
||||||
child: PollSubmit(
|
child:
|
||||||
initialAnswers: embedData['poll']?['user_answer']?['answer'],
|
embedData['poll'] == null
|
||||||
|
? Text('Poll was not loaded...')
|
||||||
|
: PollSubmit(
|
||||||
|
initialAnswers:
|
||||||
|
embedData['poll']?['user_answer']?['answer'],
|
||||||
stats: embedData['poll']?['stats'],
|
stats: embedData['poll']?['stats'],
|
||||||
poll: SnPollWithStats.fromJson(embedData['poll']),
|
poll: SnPollWithStats.fromJson(embedData['poll']),
|
||||||
onSubmit: (_) {},
|
onSubmit: (_) {},
|
||||||
@@ -770,7 +856,7 @@ class PostReplyPreview extends HookConsumerWidget {
|
|||||||
final posts = useState<List<SnPost>>([]);
|
final posts = useState<List<SnPost>>([]);
|
||||||
final loading = useState(false);
|
final loading = useState(false);
|
||||||
|
|
||||||
Future<void> fetchMoreReplies({int pageSize = 1}) async {
|
Future<void> fetchMoreReplies({int pageSize = 3}) async {
|
||||||
final client = ref.read(apiClientProvider);
|
final client = ref.read(apiClientProvider);
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
|
||||||
@@ -779,10 +865,14 @@ class PostReplyPreview extends HookConsumerWidget {
|
|||||||
'/sphere/posts/${parent.id}/replies',
|
'/sphere/posts/${parent.id}/replies',
|
||||||
queryParameters: {'offset': posts.value.length, 'take': pageSize},
|
queryParameters: {'offset': posts.value.length, 'take': pageSize},
|
||||||
);
|
);
|
||||||
|
try {
|
||||||
posts.value = [
|
posts.value = [
|
||||||
...posts.value,
|
...posts.value,
|
||||||
...response.data.map((e) => SnPost.fromJson(e)),
|
...response.data.map((e) => SnPost.fromJson(e)),
|
||||||
];
|
];
|
||||||
|
} catch (_) {
|
||||||
|
// ignore disposed
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showErrorAlert(err);
|
showErrorAlert(err);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -877,38 +967,40 @@ class PostReplyPreview extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
: featuredReply!.when(
|
: (featuredReply!).map(
|
||||||
data:
|
data:
|
||||||
(value) => Row(
|
(data) => Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
children: [
|
children: [
|
||||||
ProfilePictureWidget(
|
ProfilePictureWidget(
|
||||||
file: value?.publisher.picture,
|
file: data.value?.publisher.picture,
|
||||||
radius: 12,
|
radius: 12,
|
||||||
).padding(top: 4),
|
).padding(top: 4),
|
||||||
if (value?.content?.isNotEmpty ?? false)
|
if (data.value?.content?.isNotEmpty ?? false)
|
||||||
Expanded(
|
Expanded(
|
||||||
child: MarkdownTextContent(content: value!.content!),
|
child: MarkdownTextContent(
|
||||||
|
content: data.value!.content!,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
'postHasAttachments',
|
'postHasAttachments',
|
||||||
).plural(value?.attachments.length ?? 0),
|
).plural(data.value?.attachments.length ?? 0),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
error:
|
error:
|
||||||
(error, _) => Row(
|
(e) => Row(
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.close, size: 18),
|
const Icon(Symbols.close, size: 18),
|
||||||
Text(error.toString()),
|
Text(e.error.toString()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
loading:
|
loading:
|
||||||
() => Row(
|
(_) => Row(
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
@@ -939,7 +1031,6 @@ class PostReplyPreview extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
Text('repliesCount')
|
Text('repliesCount')
|
||||||
.plural(parent.repliesCount)
|
.plural(parent.repliesCount)
|
||||||
.tr()
|
|
||||||
.fontSize(15)
|
.fontSize(15)
|
||||||
.bold()
|
.bold()
|
||||||
.padding(horizontal: 5),
|
.padding(horizontal: 5),
|
||||||
|
|||||||
@@ -15,7 +15,12 @@ class PostListNotifier extends _$PostListNotifier
|
|||||||
static const int _pageSize = 20;
|
static const int _pageSize = 20;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<CursorPagingData<SnPost>> build(String? pubName) {
|
Future<CursorPagingData<SnPost>> build(
|
||||||
|
String? pubName, {
|
||||||
|
int? type,
|
||||||
|
List<String>? categories,
|
||||||
|
List<String>? tags,
|
||||||
|
}) {
|
||||||
return fetch(cursor: null);
|
return fetch(cursor: null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,6 +33,9 @@ class PostListNotifier extends _$PostListNotifier
|
|||||||
'offset': offset,
|
'offset': offset,
|
||||||
'take': _pageSize,
|
'take': _pageSize,
|
||||||
if (pubName != null) 'pub': pubName,
|
if (pubName != null) 'pub': pubName,
|
||||||
|
if (type != null) 'type': type,
|
||||||
|
if (tags != null) 'tags': tags,
|
||||||
|
if (categories != null) 'categories': categories,
|
||||||
};
|
};
|
||||||
|
|
||||||
final response = await client.get(
|
final response = await client.get(
|
||||||
@@ -60,6 +68,9 @@ enum PostItemType {
|
|||||||
|
|
||||||
class SliverPostList extends HookConsumerWidget {
|
class SliverPostList extends HookConsumerWidget {
|
||||||
final String? pubName;
|
final String? pubName;
|
||||||
|
final int? type;
|
||||||
|
final List<String>? categories;
|
||||||
|
final List<String>? tags;
|
||||||
final PostItemType itemType;
|
final PostItemType itemType;
|
||||||
final Color? backgroundColor;
|
final Color? backgroundColor;
|
||||||
final EdgeInsets? padding;
|
final EdgeInsets? padding;
|
||||||
@@ -70,6 +81,9 @@ class SliverPostList extends HookConsumerWidget {
|
|||||||
const SliverPostList({
|
const SliverPostList({
|
||||||
super.key,
|
super.key,
|
||||||
this.pubName,
|
this.pubName,
|
||||||
|
this.type,
|
||||||
|
this.categories,
|
||||||
|
this.tags,
|
||||||
this.itemType = PostItemType.regular,
|
this.itemType = PostItemType.regular,
|
||||||
this.backgroundColor,
|
this.backgroundColor,
|
||||||
this.padding,
|
this.padding,
|
||||||
@@ -81,9 +95,26 @@ class SliverPostList extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
return PagingHelperSliverView(
|
return PagingHelperSliverView(
|
||||||
provider: postListNotifierProvider(pubName),
|
provider: postListNotifierProvider(
|
||||||
futureRefreshable: postListNotifierProvider(pubName).future,
|
pubName,
|
||||||
notifierRefreshable: postListNotifierProvider(pubName).notifier,
|
type: type,
|
||||||
|
categories: categories,
|
||||||
|
tags: tags,
|
||||||
|
),
|
||||||
|
futureRefreshable:
|
||||||
|
postListNotifierProvider(
|
||||||
|
pubName,
|
||||||
|
type: type,
|
||||||
|
categories: categories,
|
||||||
|
tags: tags,
|
||||||
|
).future,
|
||||||
|
notifierRefreshable:
|
||||||
|
postListNotifierProvider(
|
||||||
|
pubName,
|
||||||
|
type: type,
|
||||||
|
categories: categories,
|
||||||
|
tags: tags,
|
||||||
|
).notifier,
|
||||||
contentBuilder:
|
contentBuilder:
|
||||||
(data, widgetCount, endItemView) => SliverList.builder(
|
(data, widgetCount, endItemView) => SliverList.builder(
|
||||||
itemCount: widgetCount,
|
itemCount: widgetCount,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ part of 'post_list.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$postListNotifierHash() => r'2e4fb36123d3f97ac1edf9945043251d4eb519a2';
|
String _$postListNotifierHash() => r'dc57fc6aaff6bfb4e9b4d1185984162b099b8773';
|
||||||
|
|
||||||
/// Copied from Dart SDK
|
/// Copied from Dart SDK
|
||||||
class _SystemHash {
|
class _SystemHash {
|
||||||
@@ -32,8 +32,16 @@ class _SystemHash {
|
|||||||
abstract class _$PostListNotifier
|
abstract class _$PostListNotifier
|
||||||
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnPost>> {
|
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnPost>> {
|
||||||
late final String? pubName;
|
late final String? pubName;
|
||||||
|
late final int? type;
|
||||||
|
late final List<String>? categories;
|
||||||
|
late final List<String>? tags;
|
||||||
|
|
||||||
FutureOr<CursorPagingData<SnPost>> build(String? pubName);
|
FutureOr<CursorPagingData<SnPost>> build(
|
||||||
|
String? pubName, {
|
||||||
|
int? type,
|
||||||
|
List<String>? categories,
|
||||||
|
List<String>? tags,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See also [PostListNotifier].
|
/// See also [PostListNotifier].
|
||||||
@@ -47,15 +55,30 @@ class PostListNotifierFamily
|
|||||||
const PostListNotifierFamily();
|
const PostListNotifierFamily();
|
||||||
|
|
||||||
/// See also [PostListNotifier].
|
/// See also [PostListNotifier].
|
||||||
PostListNotifierProvider call(String? pubName) {
|
PostListNotifierProvider call(
|
||||||
return PostListNotifierProvider(pubName);
|
String? pubName, {
|
||||||
|
int? type,
|
||||||
|
List<String>? categories,
|
||||||
|
List<String>? tags,
|
||||||
|
}) {
|
||||||
|
return PostListNotifierProvider(
|
||||||
|
pubName,
|
||||||
|
type: type,
|
||||||
|
categories: categories,
|
||||||
|
tags: tags,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
PostListNotifierProvider getProviderOverride(
|
PostListNotifierProvider getProviderOverride(
|
||||||
covariant PostListNotifierProvider provider,
|
covariant PostListNotifierProvider provider,
|
||||||
) {
|
) {
|
||||||
return call(provider.pubName);
|
return call(
|
||||||
|
provider.pubName,
|
||||||
|
type: provider.type,
|
||||||
|
categories: provider.categories,
|
||||||
|
tags: provider.tags,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||||
@@ -81,9 +104,18 @@ class PostListNotifierProvider
|
|||||||
CursorPagingData<SnPost>
|
CursorPagingData<SnPost>
|
||||||
> {
|
> {
|
||||||
/// See also [PostListNotifier].
|
/// See also [PostListNotifier].
|
||||||
PostListNotifierProvider(String? pubName)
|
PostListNotifierProvider(
|
||||||
: this._internal(
|
String? pubName, {
|
||||||
() => PostListNotifier()..pubName = pubName,
|
int? type,
|
||||||
|
List<String>? categories,
|
||||||
|
List<String>? tags,
|
||||||
|
}) : this._internal(
|
||||||
|
() =>
|
||||||
|
PostListNotifier()
|
||||||
|
..pubName = pubName
|
||||||
|
..type = type
|
||||||
|
..categories = categories
|
||||||
|
..tags = tags,
|
||||||
from: postListNotifierProvider,
|
from: postListNotifierProvider,
|
||||||
name: r'postListNotifierProvider',
|
name: r'postListNotifierProvider',
|
||||||
debugGetCreateSourceHash:
|
debugGetCreateSourceHash:
|
||||||
@@ -94,6 +126,9 @@ class PostListNotifierProvider
|
|||||||
allTransitiveDependencies:
|
allTransitiveDependencies:
|
||||||
PostListNotifierFamily._allTransitiveDependencies,
|
PostListNotifierFamily._allTransitiveDependencies,
|
||||||
pubName: pubName,
|
pubName: pubName,
|
||||||
|
type: type,
|
||||||
|
categories: categories,
|
||||||
|
tags: tags,
|
||||||
);
|
);
|
||||||
|
|
||||||
PostListNotifierProvider._internal(
|
PostListNotifierProvider._internal(
|
||||||
@@ -104,15 +139,26 @@ class PostListNotifierProvider
|
|||||||
required super.debugGetCreateSourceHash,
|
required super.debugGetCreateSourceHash,
|
||||||
required super.from,
|
required super.from,
|
||||||
required this.pubName,
|
required this.pubName,
|
||||||
|
required this.type,
|
||||||
|
required this.categories,
|
||||||
|
required this.tags,
|
||||||
}) : super.internal();
|
}) : super.internal();
|
||||||
|
|
||||||
final String? pubName;
|
final String? pubName;
|
||||||
|
final int? type;
|
||||||
|
final List<String>? categories;
|
||||||
|
final List<String>? tags;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<CursorPagingData<SnPost>> runNotifierBuild(
|
FutureOr<CursorPagingData<SnPost>> runNotifierBuild(
|
||||||
covariant PostListNotifier notifier,
|
covariant PostListNotifier notifier,
|
||||||
) {
|
) {
|
||||||
return notifier.build(pubName);
|
return notifier.build(
|
||||||
|
pubName,
|
||||||
|
type: type,
|
||||||
|
categories: categories,
|
||||||
|
tags: tags,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -120,13 +166,21 @@ class PostListNotifierProvider
|
|||||||
return ProviderOverride(
|
return ProviderOverride(
|
||||||
origin: this,
|
origin: this,
|
||||||
override: PostListNotifierProvider._internal(
|
override: PostListNotifierProvider._internal(
|
||||||
() => create()..pubName = pubName,
|
() =>
|
||||||
|
create()
|
||||||
|
..pubName = pubName
|
||||||
|
..type = type
|
||||||
|
..categories = categories
|
||||||
|
..tags = tags,
|
||||||
from: from,
|
from: from,
|
||||||
name: null,
|
name: null,
|
||||||
dependencies: null,
|
dependencies: null,
|
||||||
allTransitiveDependencies: null,
|
allTransitiveDependencies: null,
|
||||||
debugGetCreateSourceHash: null,
|
debugGetCreateSourceHash: null,
|
||||||
pubName: pubName,
|
pubName: pubName,
|
||||||
|
type: type,
|
||||||
|
categories: categories,
|
||||||
|
tags: tags,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -142,13 +196,20 @@ class PostListNotifierProvider
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return other is PostListNotifierProvider && other.pubName == pubName;
|
return other is PostListNotifierProvider &&
|
||||||
|
other.pubName == pubName &&
|
||||||
|
other.type == type &&
|
||||||
|
other.categories == categories &&
|
||||||
|
other.tags == tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode {
|
int get hashCode {
|
||||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||||
hash = _SystemHash.combine(hash, pubName.hashCode);
|
hash = _SystemHash.combine(hash, pubName.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, type.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, categories.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, tags.hashCode);
|
||||||
|
|
||||||
return _SystemHash.finish(hash);
|
return _SystemHash.finish(hash);
|
||||||
}
|
}
|
||||||
@@ -160,6 +221,15 @@ mixin PostListNotifierRef
|
|||||||
on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnPost>> {
|
on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnPost>> {
|
||||||
/// The parameter `pubName` of this provider.
|
/// The parameter `pubName` of this provider.
|
||||||
String? get pubName;
|
String? get pubName;
|
||||||
|
|
||||||
|
/// The parameter `type` of this provider.
|
||||||
|
int? get type;
|
||||||
|
|
||||||
|
/// The parameter `categories` of this provider.
|
||||||
|
List<String>? get categories;
|
||||||
|
|
||||||
|
/// The parameter `tags` of this provider.
|
||||||
|
List<String>? get tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PostListNotifierProviderElement
|
class _PostListNotifierProviderElement
|
||||||
@@ -173,6 +243,13 @@ class _PostListNotifierProviderElement
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String? get pubName => (origin as PostListNotifierProvider).pubName;
|
String? get pubName => (origin as PostListNotifierProvider).pubName;
|
||||||
|
@override
|
||||||
|
int? get type => (origin as PostListNotifierProvider).type;
|
||||||
|
@override
|
||||||
|
List<String>? get categories =>
|
||||||
|
(origin as PostListNotifierProvider).categories;
|
||||||
|
@override
|
||||||
|
List<String>? get tags => (origin as PostListNotifierProvider).tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
|
|||||||
307
lib/widgets/stickers/picker.dart
Normal file
307
lib/widgets/stickers/picker.dart
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/models/sticker.dart';
|
||||||
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:flutter_popup_card/flutter_popup_card.dart';
|
||||||
|
|
||||||
|
part 'picker.g.dart';
|
||||||
|
|
||||||
|
/// Fetch user-added sticker packs (with stickers) from API:
|
||||||
|
/// GET /sphere/stickers/me
|
||||||
|
@riverpod
|
||||||
|
Future<List<SnStickerPack>> myStickerPacks(Ref ref) async {
|
||||||
|
final api = ref.watch(apiClientProvider);
|
||||||
|
final resp = await api.get('/sphere/stickers/me');
|
||||||
|
final data = resp.data;
|
||||||
|
if (data is List) {
|
||||||
|
return data
|
||||||
|
.map((e) => SnStickerPack.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
return const <SnStickerPack>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sticker Picker popover dialog
|
||||||
|
/// - Displays user-owned sticker packs as tabs (chips)
|
||||||
|
/// - Shows grid of stickers in selected pack
|
||||||
|
/// - On tap, returns placeholder string :{prefix}{slug}: via onPick callback
|
||||||
|
class StickerPicker extends HookConsumerWidget {
|
||||||
|
final void Function(String placeholder) onPick;
|
||||||
|
|
||||||
|
const StickerPicker({super.key, required this.onPick});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final packsAsync = ref.watch(myStickerPacksProvider);
|
||||||
|
|
||||||
|
return PopupCard(
|
||||||
|
elevation: 8,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)),
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 520, maxHeight: 520),
|
||||||
|
child: packsAsync.when(
|
||||||
|
data: (packs) {
|
||||||
|
if (packs.isEmpty) {
|
||||||
|
return _EmptyState(
|
||||||
|
onRefresh: () async {
|
||||||
|
ref.invalidate(myStickerPacksProvider);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maintain selected index locally with a ValueNotifier to avoid hooks dependency
|
||||||
|
return _PackSwitcher(
|
||||||
|
packs: packs,
|
||||||
|
onPick: (pack, sticker) {
|
||||||
|
final placeholder = ':${pack.prefix}${sticker.slug}:';
|
||||||
|
HapticFeedback.selectionClick();
|
||||||
|
onPick(placeholder);
|
||||||
|
if (Navigator.of(context).canPop()) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRefresh: () async {
|
||||||
|
ref.invalidate(myStickerPacksProvider);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
loading:
|
||||||
|
() => const SizedBox(
|
||||||
|
width: 320,
|
||||||
|
height: 320,
|
||||||
|
child: Center(child: CircularProgressIndicator()),
|
||||||
|
),
|
||||||
|
error:
|
||||||
|
(err, _) => SizedBox(
|
||||||
|
width: 360,
|
||||||
|
height: 200,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.error, size: 28),
|
||||||
|
const Gap(8),
|
||||||
|
Text('Error: $err', textAlign: TextAlign.center),
|
||||||
|
const Gap(12),
|
||||||
|
FilledButton.icon(
|
||||||
|
onPressed: () => ref.invalidate(myStickerPacksProvider),
|
||||||
|
icon: const Icon(Symbols.refresh),
|
||||||
|
label: Text('retry').tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(all: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EmptyState extends StatelessWidget {
|
||||||
|
final Future<void> Function() onRefresh;
|
||||||
|
const _EmptyState({required this.onRefresh});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
width: 360,
|
||||||
|
height: 220,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.emoji_symbols, size: 28),
|
||||||
|
const Gap(8),
|
||||||
|
Text('noStickerPacks'.tr(), textAlign: TextAlign.center),
|
||||||
|
const Gap(12),
|
||||||
|
OutlinedButton.icon(
|
||||||
|
onPressed: onRefresh,
|
||||||
|
icon: const Icon(Symbols.refresh),
|
||||||
|
label: Text('refresh').tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(all: 16),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PackSwitcher extends StatefulWidget {
|
||||||
|
final List<SnStickerPack> packs;
|
||||||
|
final void Function(SnStickerPack pack, SnSticker sticker) onPick;
|
||||||
|
final Future<void> Function() onRefresh;
|
||||||
|
|
||||||
|
const _PackSwitcher({
|
||||||
|
required this.packs,
|
||||||
|
required this.onPick,
|
||||||
|
required this.onRefresh,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_PackSwitcher> createState() => _PackSwitcherState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PackSwitcherState extends State<_PackSwitcher> {
|
||||||
|
int _index = 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final packs = widget.packs;
|
||||||
|
_index = _index.clamp(0, packs.length - 1);
|
||||||
|
|
||||||
|
final selectedPack = packs[_index];
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
// Header
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.sticky_note_2, size: 20),
|
||||||
|
const Gap(8),
|
||||||
|
Text(
|
||||||
|
'stickers'.tr(),
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
IconButton(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
tooltip: 'close'.tr(),
|
||||||
|
onPressed: () => Navigator.of(context).maybePop(),
|
||||||
|
icon: const Icon(Symbols.close),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 12, top: 8),
|
||||||
|
|
||||||
|
// Vertical, scrollable packs rail like common emoji pickers
|
||||||
|
SizedBox(
|
||||||
|
height: 48,
|
||||||
|
child: ListView.separated(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
itemCount: packs.length,
|
||||||
|
separatorBuilder: (_, _) => const Gap(4),
|
||||||
|
itemBuilder: (context, i) {
|
||||||
|
final selected = _index == i;
|
||||||
|
return Tooltip(
|
||||||
|
message: packs[i].name,
|
||||||
|
child: FilterChip(
|
||||||
|
label: Text(packs[i].name, overflow: TextOverflow.ellipsis),
|
||||||
|
selected: selected,
|
||||||
|
onSelected: (_) {
|
||||||
|
setState(() => _index = i);
|
||||||
|
HapticFeedback.selectionClick();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
).padding(bottom: 8),
|
||||||
|
const Divider(height: 1),
|
||||||
|
|
||||||
|
// Content
|
||||||
|
Expanded(
|
||||||
|
child: RefreshIndicator(
|
||||||
|
onRefresh: widget.onRefresh,
|
||||||
|
child: _StickersGrid(
|
||||||
|
pack: selectedPack,
|
||||||
|
onPick: (sticker) => widget.onPick(selectedPack, sticker),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _StickersGrid extends StatelessWidget {
|
||||||
|
final SnStickerPack pack;
|
||||||
|
final void Function(SnSticker sticker) onPick;
|
||||||
|
|
||||||
|
const _StickersGrid({required this.pack, required this.onPick});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final stickers = pack.stickers;
|
||||||
|
|
||||||
|
if (stickers.isEmpty) {
|
||||||
|
return Center(child: Text('noStickersInPack'.tr()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return GridView.builder(
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
|
||||||
|
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||||
|
maxCrossAxisExtent: 96,
|
||||||
|
mainAxisSpacing: 12,
|
||||||
|
crossAxisSpacing: 12,
|
||||||
|
),
|
||||||
|
itemCount: stickers.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final sticker = stickers[index];
|
||||||
|
final placeholder = ':${pack.prefix}${sticker.slug}:';
|
||||||
|
return Tooltip(
|
||||||
|
message: placeholder,
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
onTap: () => onPick(sticker),
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
child: DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
),
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: 1,
|
||||||
|
child: CloudImageWidget(
|
||||||
|
fileId: sticker.imageId,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper to show sticker picker as an anchored popover near the trigger.
|
||||||
|
/// Provide the button's BuildContext (typically from the onPressed closure).
|
||||||
|
/// Fallbacks to dialog if overlay cannot be found (e.g., during tests).
|
||||||
|
Future<void> showStickerPickerPopover(
|
||||||
|
BuildContext context,
|
||||||
|
Offset offset, {
|
||||||
|
required void Function(String placeholder) onPick,
|
||||||
|
}) async {
|
||||||
|
// Use flutter_popup_card to present the anchored popup near trigger.
|
||||||
|
await showPopupCard<void>(
|
||||||
|
context: context,
|
||||||
|
offset: offset,
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
dimBackground: true,
|
||||||
|
builder:
|
||||||
|
(ctx) => SizedBox(
|
||||||
|
width: math.min(480, MediaQuery.of(context).size.width * 0.9),
|
||||||
|
height: 480,
|
||||||
|
child: ProviderScope(
|
||||||
|
parent: ProviderScope.containerOf(context),
|
||||||
|
child: StickerPicker(
|
||||||
|
onPick: (ph) {
|
||||||
|
onPick(ph);
|
||||||
|
Navigator.of(ctx).maybePop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
32
lib/widgets/stickers/picker.g.dart
Normal file
32
lib/widgets/stickers/picker.g.dart
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'picker.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$myStickerPacksHash() => r'1e19832e8ab1cb139ad18aebfa5aebdf4fdea499';
|
||||||
|
|
||||||
|
/// Fetch user-added sticker packs (with stickers) from API:
|
||||||
|
/// GET /sphere/stickers/me
|
||||||
|
///
|
||||||
|
/// Copied from [myStickerPacks].
|
||||||
|
@ProviderFor(myStickerPacks)
|
||||||
|
final myStickerPacksProvider =
|
||||||
|
AutoDisposeFutureProvider<List<SnStickerPack>>.internal(
|
||||||
|
myStickerPacks,
|
||||||
|
name: r'myStickerPacksProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$myStickerPacksHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
typedef MyStickerPacksRef = AutoDisposeFutureProviderRef<List<SnStickerPack>>;
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
@@ -2,6 +2,12 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>CLIENT_ID</key>
|
||||||
|
<string>961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com</string>
|
||||||
|
<key>REVERSED_CLIENT_ID</key>
|
||||||
|
<string>com.googleusercontent.apps.961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig</string>
|
||||||
|
<key>ANDROID_CLIENT_ID</key>
|
||||||
|
<string>961776991058-r4iv9qoio57ul7utbfpgfrda2etvtch8.apps.googleusercontent.com</string>
|
||||||
<key>API_KEY</key>
|
<key>API_KEY</key>
|
||||||
<string>AIzaSyCzQIyiYKoYHTpGXhN-IjgMML8z797WVD8</string>
|
<string>AIzaSyCzQIyiYKoYHTpGXhN-IjgMML8z797WVD8</string>
|
||||||
<key>GCM_SENDER_ID</key>
|
<key>GCM_SENDER_ID</key>
|
||||||
|
|||||||
Reference in New Issue
Block a user