Compare commits

..

12 Commits

Author SHA1 Message Date
69d5e95565 🚀 Launch 2.4.2+86 March Update 2025-03-31 23:05:36 +08:00
3e3442fc89 💄 Enable new sfx on special days 2025-03-31 23:00:13 +08:00
8181010b0b 💄 New desktop loading animation 2025-03-31 22:50:08 +08:00
269caf7555 💄 Some improvements
🐛 Bug fixes
 The heart reaction
2025-03-31 01:27:45 +08:00
ae0809ad35 💄 Optimize background color 2025-03-31 00:51:37 +08:00
4005f03cf8 🐛 Fix notification 2025-03-30 23:25:38 +08:00
4bd8ec54f1 Optimize initialization 2025-03-30 20:43:47 +08:00
51a387851f 🐛 Fix infinite starting up 2025-03-30 20:37:04 +08:00
8ed847d870 ♻️ Use API Version 2 to load post 2025-03-30 15:31:02 +08:00
dfe13de220 Program Badges 2025-03-29 17:00:17 +08:00
b02a54c1e9 🐛 Fix sound mode 2025-03-29 16:41:23 +08:00
55a7e7d900 🐛 Fix app drawer did not close after selected 2025-03-29 01:04:37 +08:00
51 changed files with 824 additions and 486 deletions

View File

@ -941,5 +941,12 @@
"settingsResetMemorizedWindowSize": "Reset Window Size",
"settingsResetMemorizedWindowSizeDescription": "Reset the memorized window size, and set it to the default size.",
"chatDirect": "Direct Messages",
"back": "返回"
"back": "Back",
"badgeProgramDeveloper": "Developer Program Member",
"badgeProgramStellar": "A Stellar",
"badgeProgramModerator": "Community Moderator",
"postEditedHint": "edited",
"splashScreenServer": "Server",
"splashScreenServerName": "Potato",
"splashScreenCaption": "Trying to establishing connection with HyperNet™"
}

View File

@ -938,5 +938,12 @@
"settingsResetMemorizedWindowSize": "重置窗口大小",
"settingsResetMemorizedWindowSizeDescription": "重置记忆的窗口大小,以重新设置为默认大小。",
"chatDirect": "私信",
"back": "返回"
"back": "返回",
"badgeProgramDeveloper": "开发者计划成员",
"badgeProgramStellar": "一颗恒星",
"badgeProgramModerator": "社区管理员",
"postEditedHint": "已编辑",
"splashScreenServer": "服务器",
"splashScreenServerName": "土豆",
"splashScreenCaption": "正在尝试与 HyperNet™ 取得太阳链连接"
}

View File

@ -189,6 +189,11 @@ PODS:
- Flutter
- flutter_webrtc
- WebRTC-SDK (= 125.6422.06)
- livekit_noise_filter (0.0.1):
- Flutter
- flutter_webrtc
- LiveKitKrispNoiseFilter (= 0.0.7)
- LiveKitKrispNoiseFilter (0.0.7)
- media_kit_libs_ios_video (1.0.4):
- Flutter
- media_kit_video (0.0.1):
@ -283,6 +288,7 @@ DEPENDENCIES:
- in_app_review (from `.symlinks/plugins/in_app_review/ios`)
- Kingfisher (~> 8.0)
- livekit_client (from `.symlinks/plugins/livekit_client/ios`)
- livekit_noise_filter (from `.symlinks/plugins/livekit_noise_filter/ios`)
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
- media_kit_video (from `.symlinks/plugins/media_kit_video/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
@ -315,6 +321,7 @@ SPEC REPOS:
- GoogleDataTransport
- GoogleUtilities
- Kingfisher
- LiveKitKrispNoiseFilter
- nanopb
- OrderedSet
- PromisesObjC
@ -369,6 +376,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/in_app_review/ios"
livekit_client:
:path: ".symlinks/plugins/livekit_client/ios"
livekit_noise_filter:
:path: ".symlinks/plugins/livekit_noise_filter/ios"
media_kit_libs_ios_video:
:path: ".symlinks/plugins/media_kit_libs_ios_video/ios"
media_kit_video:
@ -438,6 +447,8 @@ SPEC CHECKSUMS:
in_app_review: 5596fe56fab799e8edb3561c03d053363ab13457
Kingfisher: 323e5c4ec7983aaace12af655a7b51a7f88a599d
livekit_client: 08755cabfa4da4ed455642f460cfbb39bc518070
livekit_noise_filter: a26aeb1c1eae6db0a023fd2f6ea3ff108c3ecbb0
LiveKitKrispNoiseFilter: efe418ceca28163ace0ff222bd2cc02384645d84
media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854
media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275

View File

@ -241,7 +241,7 @@ class PostWriteController extends ChangeNotifier {
contentController.text = post.body['content'] ?? '';
aliasController.text = post.alias ?? '';
rewardController.text = post.body['reward']?.toString() ?? '';
videoAttachment = post.preload?.video;
videoAttachment = SnAttachment.fromJson(post.body['video']);
publishedAt = post.publishedAt;
publishedUntil = post.publishedUntil;
visibleUsers = List.from(post.visibleUsersList ?? [], growable: true);
@ -252,17 +252,21 @@ class PostWriteController extends ChangeNotifier {
categories =
List.from(post.categories.map((ele) => ele.alias), growable: true);
attachments.addAll(
post.preload?.attachments?.map((ele) => PostWriteMedia(ele)) ?? []);
poll = post.preload?.poll;
post.body['attachments']
.where(SnAttachment.fromJson)
?.map(PostWriteMedia) ??
[],
);
poll = post.poll;
editingDraft = post.isDraft;
if (post.preload?.thumbnail != null &&
(post.preload?.thumbnail?.rid.isNotEmpty ?? false)) {
thumbnail = PostWriteMedia(post.preload!.thumbnail);
if (post.body['thumbnail'] != null) {
thumbnail =
PostWriteMedia(SnAttachment.fromJson(post.body['thumbnail']));
}
if (post.preload?.realm != null) {
realm = post.preload!.realm!;
if (post.realm != null) {
realm = post.realm!;
}
editingPost = post;

View File

@ -15,6 +15,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:gap/gap.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:hotkey_manager/hotkey_manager.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:provider/provider.dart';
@ -49,6 +50,7 @@ import 'package:surface/router.dart';
import 'package:flutter_web_plugins/url_strategy.dart' show usePathUrlStrategy;
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/menu_bar.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:surface/widgets/version_label.dart';
import 'package:tray_manager/tray_manager.dart';
import 'package:version/version.dart';
@ -57,6 +59,7 @@ import 'package:in_app_review/in_app_review.dart';
import 'package:image_picker_android/image_picker_android.dart';
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
import 'package:local_notifier/local_notifier.dart';
import 'package:flutter_animate/flutter_animate.dart';
@pragma('vm:entry-point')
void appBackgroundDispatcher() {
@ -257,6 +260,7 @@ class _AppSplashScreen extends StatefulWidget {
class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
bool _isBusy = false;
double _initPercentage = 0;
String _phaseText = 'appInitStarting';
void _tryRequestRating() async {
@ -331,20 +335,24 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
// The Network initialization must be done after the HomeWidget initialization
// The Network initialization will save the server url to the HomeWidget
// The Network initialization will also save initialize the Config, so it not need to be initialized again
_initPercentage = 0.1;
_setPhaseText('network');
final sn = context.read<SnNetworkProvider>();
await sn.initializeUserAgent();
await sn.setConfigWithNative();
if (!mounted) return;
_initPercentage = 0.2;
_setPhaseText('userdata');
final ua = context.read<UserProvider>();
await ua.initialize();
if (!mounted) return;
_initPercentage = 0.3;
_setPhaseText('websocket');
final ws = context.read<WebSocketProvider>();
await ws.tryConnect();
try {
if (!mounted) return;
_initPercentage = 0.9;
_setPhaseText('keyPair');
final kp = context.read<KeyPairProvider>();
kp.reloadActive();
@ -357,24 +365,25 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
notify.listen();
try {
notify.registerPushNotifications();
if (!mounted) return;
_setPhaseText('stickers');
final sticker = context.read<SnStickerProvider>();
await sticker.listSticker();
if (!mounted) return;
_setPhaseText('userDirectory');
final ud = context.read<UserDirectoryProvider>();
await ud.loadAccountCache();
if (!mounted) return;
_setPhaseText('realm');
final rm = context.read<SnRealmProvider>();
await rm.refreshAvailableRealms();
if (!mounted) return;
_setPhaseText('chat');
final ct = context.read<ChatChannelProvider>();
await ct.refreshAvailableChannels();
_initPercentage = 1;
_setPhaseText('done');
} catch (_) {}
if (!mounted) return;
_setPhaseText('stickers');
final sticker = context.read<SnStickerProvider>();
await sticker.listSticker();
if (!mounted) return;
_setPhaseText('userDirectory');
final ud = context.read<UserDirectoryProvider>();
await ud.loadAccountCache();
if (!mounted) return;
_setPhaseText('realm');
final rm = context.read<SnRealmProvider>();
await rm.refreshAvailableRealms();
if (!mounted) return;
_setPhaseText('chat');
final ct = context.read<ChatChannelProvider>();
await ct.refreshAvailableChannels();
_setPhaseText('done');
_playIntro();
}
} catch (err) {
@ -396,8 +405,22 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
final cfg = context.read<ConfigProvider>();
if (!cfg.soundEffects) return;
final date = DateTime.now();
final player = AudioPlayer(playerId: 'launch-done-player');
await player.play(AssetSource('audio/sfx/launch-done.mp3'), volume: 0.8);
await player.play(
(cfg.aprilFoolFeatures && date.month == 4 && date.day == 1)
? AssetSource('audio/sfx/launch-intro.mp3')
: AssetSource('audio/sfx/launch-done.mp3'),
volume: 0.8,
ctx: AudioContext(
android: AudioContextAndroid(
contentType: AndroidContentType.sonification,
usageType: AndroidUsageType.notificationEvent,
),
iOS: AudioContextIOS(category: AVAudioSessionCategory.ambient),
),
mode: PlayerMode.lowLatency,
);
player.onPlayerComplete.listen((_) {
player.dispose();
});
@ -456,15 +479,22 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
AppLifecycleListener(onExitRequested: _onExitRequested);
}
_trayInitialization();
_hotkeyInitialization();
_notifyInitialization();
_initialize().then((_) {
_postInitialization();
_tryRequestRating();
_checkForUpdate();
setState(() => _isBusy = false);
});
try {
_trayInitialization();
_hotkeyInitialization();
_notifyInitialization();
_initialize().then((_) {
_postInitialization();
_tryRequestRating();
_checkForUpdate();
setState(() => _isBusy = false);
}).catchError((err) {
logging.error('[Bootstrap] Unable to initialize app', err);
setState(() => _isBusy = false);
});
} catch (err) {
logging.error('[Bootstrap] Unable to initialize (pre-stage) app', err);
}
}
Future<AppExitResponse> _onExitRequested() async {
@ -555,46 +585,10 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
});
return SizeChangedLayoutNotifier(
child: _isBusy
? Material(
key: Key('app-splash-screen-$_isBusy'),
child: Stack(
children: [
Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/icon/kanban-1st.jpg'),
fit: BoxFit.cover,
opacity: 0.1,
),
color: Theme.of(context).colorScheme.surface,
backgroundBlendMode: BlendMode.darken,
),
),
Center(
child: Container(
constraints: const BoxConstraints(maxWidth: 240),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset(
'assets/icon/icon.png',
width: 64,
height: 64,
color:
Theme.of(context).colorScheme.onSurface,
),
Text('Solar Network').bold(),
AppVersionLabel(),
Gap(8),
Text(_phaseText, textAlign: TextAlign.center),
Gap(16),
const LinearProgressIndicator(),
],
),
),
),
],
),
? _AppLoadingScreen(
isBusy: _isBusy,
initPercentage: _initPercentage,
phaseText: _phaseText,
)
: widget.child,
);
@ -604,3 +598,234 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
);
}
}
class _AppLoadingScreen extends StatelessWidget {
const _AppLoadingScreen({
required this.isBusy,
required this.initPercentage,
required this.phaseText,
});
final bool isBusy;
final double initPercentage;
final String phaseText;
@override
Widget build(BuildContext context) {
if (ResponsiveScaffold.getIsExpand(context)) {
return Material(
key: Key('app-splash-screen-$isBusy'),
child: Stack(
children: [
Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/icon/kanban-1st.jpg'),
fit: BoxFit.cover,
opacity: 0.1,
),
color: Theme.of(context).colorScheme.surface,
backgroundBlendMode: BlendMode.darken,
),
),
Center(
child: Row(
children: [
Expanded(
child: TweenAnimationBuilder<double>(
tween: Tween(begin: 0, end: initPercentage),
duration: Duration(milliseconds: 300),
builder: (context, value, _) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('${(value * 100).toStringAsFixed(0)}%')
.padding(left: 32, bottom: 4),
LinearProgressIndicator(
value: value,
borderRadius: const BorderRadius.all(
Radius.circular(0),
),
stopIndicatorColor: Colors.transparent,
backgroundColor: Colors.transparent,
),
const Gap(24),
],
),
),
),
Expanded(
child: TweenAnimationBuilder<double>(
tween: Tween(begin: 0, end: initPercentage),
duration: Duration(milliseconds: 300),
builder: (context, value, _) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text('${(value * 100).toStringAsFixed(0)}%')
.padding(right: 32, bottom: 4),
Transform.flip(
flipX: true,
child: LinearProgressIndicator(
value: value,
borderRadius: const BorderRadius.all(
Radius.circular(0),
),
stopIndicatorColor: Colors.transparent,
backgroundColor: Colors.transparent,
),
),
const Gap(24),
],
),
),
),
],
),
),
Center(
child: Container(
constraints: const BoxConstraints(maxWidth: 240, minWidth: 160),
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 24),
decoration: BoxDecoration(
color:
Theme.of(context).colorScheme.surface.withOpacity(0.85),
border: Border.all(
color: Theme.of(context).dividerColor,
width: 3,
),
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'splashScreenServer',
style: GoogleFonts.notoSerifHk(height: 1, fontSize: 11),
textAlign: TextAlign.center,
).tr().opacity(0.85),
Text(
'splashScreenServerName',
style: GoogleFonts.notoSerifHk(
fontSize: 24,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
).tr().opacity(0.85),
Text.rich(
TextSpan(
text: '#',
style: GoogleFonts.notoSerifHk(),
children: [
TextSpan(
text: '0',
style: GoogleFonts.notoSerifHk(
fontSize: 80,
fontWeight: FontWeight.bold,
),
),
],
),
textAlign: TextAlign.center,
).padding(vertical: 16),
],
),
),
),
Positioned(
left: 0,
right: 0,
bottom: MediaQuery.of(context).size.height * 0.2,
child: Column(
children: [
Text(
phaseText,
textAlign: TextAlign.center,
),
AnimateWidgetExtensions(Text(
'splashScreenCaption',
textAlign: TextAlign.center,
).tr())
.animate(onPlay: (e) => e.repeat())
.fadeIn(duration: 500.ms, curve: Curves.easeOut)
.then()
.fadeOut(
duration: 500.ms,
delay: 1000.ms,
curve: Curves.easeIn,
),
],
),
),
Positioned(
bottom: 8,
left: 16,
right: 16,
child: Row(
children: [
Image.asset(
'assets/icon/icon.png',
width: 40,
height: 40,
color: Theme.of(context).colorScheme.onSurface,
).padding(all: 4),
const Gap(4),
Text('Solar Network').bold(),
Expanded(child: const SizedBox()),
AppVersionLabel(),
const Gap(12),
],
),
),
],
),
);
}
return Material(
key: Key('app-splash-screen-$isBusy'),
child: Stack(
children: [
Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/icon/kanban-1st.jpg'),
fit: BoxFit.cover,
opacity: 0.1,
),
color: Theme.of(context).colorScheme.surface,
backgroundBlendMode: BlendMode.darken,
),
),
Center(
child: Container(
constraints: const BoxConstraints(maxWidth: 240),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset(
'assets/icon/icon.png',
width: 64,
height: 64,
color: Theme.of(context).colorScheme.onSurface,
),
Text('Solar Network').bold(),
AppVersionLabel(),
Gap(8),
Text(phaseText, textAlign: TextAlign.center),
Gap(16),
TweenAnimationBuilder<double>(
tween: Tween(begin: 0, end: initPercentage),
duration: Duration(milliseconds: 300),
builder: (context, value, _) =>
LinearProgressIndicator(value: value),
),
],
),
),
),
],
),
);
}
}

View File

@ -106,6 +106,14 @@ class NotificationProvider extends ChangeNotifier {
_notifySoundPlayer.play(
AssetSource('audio/notify/metal-pipe.mp3'),
volume: 0.6,
ctx: AudioContext(
android: AudioContextAndroid(
contentType: AndroidContentType.sonification,
usageType: AndroidUsageType.notificationEvent,
),
iOS: AudioContextIOS(category: AVAudioSessionCategory.ambient),
),
mode: PlayerMode.lowLatency,
);
}
}

View File

@ -1,144 +1,31 @@
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:surface/providers/sn_attachment.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/sn_realm.dart';
import 'package:surface/providers/user_directory.dart';
import 'package:surface/types/poll.dart';
import 'package:surface/types/post.dart';
import 'package:surface/types/realm.dart';
class SnPostContentProvider {
late final SnNetworkProvider _sn;
late final UserDirectoryProvider _ud;
late final SnAttachmentProvider _attach;
late final SnRealmProvider _realm;
SnPostContentProvider(BuildContext context) {
_sn = context.read<SnNetworkProvider>();
_ud = context.read<UserDirectoryProvider>();
_attach = context.read<SnAttachmentProvider>();
_realm = context.read<SnRealmProvider>();
}
Future<SnPoll> _fetchPoll(int id) async {
final resp = await _sn.client.get('/cgi/co/polls/$id');
return SnPoll.fromJson(resp.data);
}
Future<List<SnPost>> _preloadRelatedDataInBatch(List<SnPost> out) async {
Set<String> rids = {};
Set<int> uids = {};
for (var i = 0; i < out.length; i++) {
rids.addAll(out[i].body['attachments']?.cast<String>() ?? []);
if (out[i].body['thumbnail'] != null) {
rids.add(out[i].body['thumbnail']);
}
if (out[i].body['video'] != null) {
rids.add(out[i].body['video']);
}
if (out[i].repostTo != null) {
out[i] = out[i].copyWith(
repostTo: await _preloadRelatedDataSingle(out[i].repostTo!),
);
}
if (out[i].publisher.type == 0) {
uids.add(out[i].publisher.accountId);
}
}
final attachments = await _attach.getMultiple(rids.toList());
for (var i = 0; i < out.length; i++) {
SnPoll? poll;
SnRealm? realm;
if (out[i].pollId != null) {
poll = await _fetchPoll(out[i].pollId!);
}
if (out[i].realmId != null) {
realm = await _realm.getRealm(out[i].realmId!);
}
out[i] = out[i].copyWith(
preload: SnPostPreload(
thumbnail: attachments
.where((ele) => ele?.rid == out[i].body['thumbnail'])
.firstOrNull,
attachments: attachments
.where((ele) =>
out[i].body['attachments']?.contains(ele?.rid) ?? false)
.toList(),
video: attachments
.where((ele) => ele?.rid == out[i].body['video'])
.firstOrNull,
poll: poll,
realm: realm,
),
);
}
uids.addAll(
attachments.where((ele) => ele != null).map((ele) => ele!.accountId));
await _ud.listAccount(uids);
return out;
}
Future<SnPost> _preloadRelatedDataSingle(SnPost out) async {
Set<String> rids = {};
Set<int> uids = {};
rids.addAll(out.body['attachments']?.cast<String>() ?? []);
if (out.body['thumbnail'] != null) {
rids.add(out.body['thumbnail']);
}
if (out.body['video'] != null) {
rids.add(out.body['video']);
}
if (out.repostTo != null) {
out = out.copyWith(
repostTo: await _preloadRelatedDataSingle(out.repostTo!),
);
}
if (out.publisher.type == 0) {
uids.add(out.publisher.accountId);
}
final attachments = await _attach.getMultiple(rids.toList());
SnPoll? poll;
SnRealm? realm;
if (out.pollId != null) {
poll = await _fetchPoll(out.pollId!);
}
if (out.realmId != null) {
realm = await _realm.getRealm(out.realmId!);
}
out = out.copyWith(
preload: SnPostPreload(
thumbnail: attachments
.where((ele) => ele?.rid == out.body['thumbnail'])
.firstOrNull,
attachments: attachments
.where(
(ele) => out.body['attachments']?.contains(ele?.rid) ?? false)
.toList(),
video: attachments
.where((ele) => ele?.rid == out.body['video'])
.firstOrNull,
poll: poll,
realm: realm,
),
);
uids.addAll(
attachments.where((ele) => ele != null).map((ele) => ele!.accountId));
await _ud.listAccount(uids);
return out;
}
Future<List<SnPost>> listRecommendations() async {
final resp = await _sn.client.get('/cgi/co/recommendations');
final resp = await _sn.client.get(
'/cgi/co/recommendations',
options: Options(headers: {
'X-API-Version': '2',
}),
);
final out = _preloadRelatedDataInBatch(
List.from(resp.data.map((ele) => SnPost.fromJson(ele))),
);
@ -202,6 +89,9 @@ class SnPostContentProvider {
if (realm != null) 'realm': realm,
if (channel != null) 'channel': channel,
},
options: Options(headers: {
'X-API-Version': '2',
}),
);
final List<SnPost> out = await _preloadRelatedDataInBatch(
List.from(resp.data['data']?.map((e) => SnPost.fromJson(e)) ?? []),
@ -215,11 +105,16 @@ class SnPostContentProvider {
int take = 10,
int offset = 0,
}) async {
final resp = await _sn.client
.get('/cgi/co/posts/$parentId/replies', queryParameters: {
'take': take,
'offset': offset,
});
final resp = await _sn.client.get(
'/cgi/co/posts/$parentId/replies',
queryParameters: {
'take': take,
'offset': offset,
},
options: Options(headers: {
'X-API-Version': '2',
}),
);
final List<SnPost> out = await _preloadRelatedDataInBatch(
List.from(resp.data['data']?.map((e) => SnPost.fromJson(e)) ?? []),
);
@ -234,13 +129,20 @@ class SnPostContentProvider {
Iterable<String>? tags,
Iterable<String>? categories,
}) async {
final resp = await _sn.client.get('/cgi/co/posts/search', queryParameters: {
'take': take,
'offset': offset,
'probe': searchTerm,
if (tags?.isNotEmpty ?? false) 'tags': tags!.join(','),
if (categories?.isNotEmpty ?? false) 'categories': categories!.join(','),
});
final resp = await _sn.client.get(
'/cgi/co/posts/search',
queryParameters: {
'take': take,
'offset': offset,
'probe': searchTerm,
if (tags?.isNotEmpty ?? false) 'tags': tags!.join(','),
if (categories?.isNotEmpty ?? false)
'categories': categories!.join(','),
},
options: Options(headers: {
'X-API-Version': '2',
}),
);
final List<SnPost> out = await _preloadRelatedDataInBatch(
List.from(resp.data['data']?.map((e) => SnPost.fromJson(e)) ?? []),
);
@ -249,7 +151,12 @@ class SnPostContentProvider {
}
Future<SnPost> getPost(dynamic id) async {
final resp = await _sn.client.get('/cgi/co/posts/$id');
final resp = await _sn.client.get(
'/cgi/co/posts/$id',
options: Options(headers: {
'X-API-Version': '2',
}),
);
final out = _preloadRelatedDataSingle(
SnPost.fromJson(resp.data),
);

View File

@ -1,4 +1,3 @@
import 'package:animations/animations.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:surface/screens/abuse_report.dart';
@ -54,16 +53,6 @@ import 'package:surface/types/post.dart';
import 'package:surface/widgets/about.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
Widget _fadeThroughTransition(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return FadeThroughTransition(
animation: animation,
secondaryAnimation: secondaryAnimation,
fillColor: Colors.transparent,
child: child,
);
}
final _appRoutes = [
GoRoute(
path: '/',
@ -305,10 +294,7 @@ final _appRoutes = [
GoRoute(
path: '/realm',
name: 'realm',
pageBuilder: (context, state) => CustomTransitionPage(
transitionsBuilder: _fadeThroughTransition,
child: const RealmScreen(),
),
builder: (context, state) => const RealmScreen(),
routes: [
GoRoute(
path: '/:alias/community',

View File

@ -110,7 +110,7 @@ class AccountScreen extends StatelessWidget {
final sn = context.read<SnNetworkProvider>();
return AppScaffold(
noBackground: true,
noBackground: ResponsiveScaffold.getIsExpand(context),
appBar: AppBar(
leading: AutoAppBarLeading(),
title: Text("screenAccount").tr(),

View File

@ -59,7 +59,7 @@ class _ActionEventScreenState extends State<ActionEventScreen> {
@override
Widget build(BuildContext context) {
return AppScaffold(
noBackground: true,
noBackground: ResponsiveScaffold.getIsExpand(context),
appBar: AppBar(
leading: const PageBackButton(),
title: Text('accountActionEvent').tr(),

View File

@ -91,7 +91,7 @@ class _AccountAuthTicketState extends State<AccountAuthTicket> {
@override
Widget build(BuildContext context) {
return AppScaffold(
noBackground: true,
noBackground: ResponsiveScaffold.getIsExpand(context),
appBar: AppBar(
leading: const PageBackButton(),
title: Text('accountAuthTickets').tr(),

View File

@ -70,7 +70,7 @@ class _AccountBadgesScreenState extends State<AccountBadgesScreen> {
@override
Widget build(BuildContext context) {
return AppScaffold(
noBackground: true,
noBackground: ResponsiveScaffold.getIsExpand(context),
appBar: AppBar(
title: Text('screenAccountBadges').tr(),
),

View File

@ -69,7 +69,7 @@ class _AccountContactMethodState extends State<AccountContactMethod> {
@override
Widget build(BuildContext context) {
return AppScaffold(
noBackground: true,
noBackground: ResponsiveScaffold.getIsExpand(context),
appBar: AppBar(
leading: const PageBackButton(),
title: Text('accountContactMethods').tr(),

View File

@ -62,7 +62,7 @@ class _FactorSettingsScreenState extends State<FactorSettingsScreen> {
@override
Widget build(BuildContext context) {
return AppScaffold(
noBackground: true,
noBackground: ResponsiveScaffold.getIsExpand(context),
appBar: AppBar(
leading: PageBackButton(),
title: Text('screenFactorSettings').tr(),

View File

@ -37,7 +37,7 @@ class _KeyPairScreenState extends State<KeyPairScreen> {
@override
Widget build(BuildContext context) {
return AppScaffold(
noBackground: true,
noBackground: ResponsiveScaffold.getIsExpand(context),
appBar: AppBar(
title: Text('screenKeyPairs').tr(),
),

View File

@ -75,7 +75,7 @@ class _AccountNotifyPrefsScreenState extends State<AccountNotifyPrefsScreen> {
@override
Widget build(BuildContext context) {
return AppScaffold(
noBackground: true,
noBackground: ResponsiveScaffold.getIsExpand(context),
appBar: AppBar(
leading: const PageBackButton(),
title: Text('accountSettingsNotify').tr(),

View File

@ -70,7 +70,7 @@ class _AccountSecurityPrefsScreenState
@override
Widget build(BuildContext context) {
return AppScaffold(
noBackground: true,
noBackground: ResponsiveScaffold.getIsExpand(context),
appBar: AppBar(
leading: const PageBackButton(),
title: Text('accountSettingsSecurity').tr(),

View File

@ -244,7 +244,7 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
final sn = context.read<SnNetworkProvider>();
return AppScaffold(
noBackground: true,
noBackground: ResponsiveScaffold.getIsExpand(context),
appBar: AppBar(
leading: const PageBackButton(),
title: Text('screenAccountProfileEdit').tr()),
@ -263,7 +263,7 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: AspectRatio(
aspectRatio: 16 / 9,
aspectRatio: 16 / 7,
child: Container(
color: Theme.of(context)
.colorScheme

View File

@ -61,6 +61,21 @@ final Map<String, (String, IconData, Color)> kBadgesMeta = {
Symbols.thumb_up,
Colors.lightGreen,
),
'programs.developers': (
'badgeProgramDeveloper',
Symbols.code,
Colors.blue,
),
'programs.stellar': (
'badgeProgramStellar',
Symbols.family_star,
Colors.orange,
),
'programs.moderator': (
'badgeProgramModerator',
Symbols.sword_rose,
Colors.blue,
),
};
class UserScreen extends StatefulWidget {

View File

@ -70,7 +70,7 @@ class _AccountProgramScreenState extends State<AccountProgramScreen> {
@override
Widget build(BuildContext context) {
return AppScaffold(
noBackground: true,
noBackground: ResponsiveScaffold.getIsExpand(context),
appBar: AppBar(
title: Text('accountProgram').tr(),
),

View File

@ -196,7 +196,7 @@ class _AccountPublisherEditScreenState
final sn = context.read<SnNetworkProvider>();
return AppScaffold(
noBackground: true,
noBackground: ResponsiveScaffold.getIsExpand(context),
appBar: AppBar(
leading: PageBackButton(),
title: Text('screenAccountPublisherEdit').tr()),
@ -214,7 +214,7 @@ class _AccountPublisherEditScreenState
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: AspectRatio(
aspectRatio: 16 / 9,
aspectRatio: 16 / 7,
child: Container(
color: Theme.of(context)
.colorScheme

View File

@ -26,7 +26,7 @@ class _AccountPublisherNewScreenState extends State<AccountPublisherNewScreen> {
@override
Widget build(BuildContext context) {
return AppScaffold(
noBackground: true,
noBackground: ResponsiveScaffold.getIsExpand(context),
appBar: AppBar(
leading: const PageBackButton(),
title: Text('screenAccountPublisherNew').tr(),

View File

@ -82,7 +82,7 @@ class _PublisherScreenState extends State<PublisherScreen> {
@override
Widget build(BuildContext context) {
return AppScaffold(
noBackground: true,
noBackground: ResponsiveScaffold.getIsExpand(context),
appBar: AppBar(
leading: const PageBackButton(),
title: Text('screenAccountPublishers').tr(),

View File

@ -55,7 +55,7 @@ class _PunishmentsScreenState extends State<PunishmentsScreen> {
@override
Widget build(BuildContext context) {
return AppScaffold(
noBackground: true,
noBackground: ResponsiveScaffold.getIsExpand(context),
appBar: AppBar(
title: Text('accountPunishments').tr(),
leading: PageBackButton(),

View File

@ -37,7 +37,7 @@ class AccountSettingsScreen extends StatelessWidget {
final ua = context.watch<UserProvider>();
return AppScaffold(
noBackground: true,
noBackground: ResponsiveScaffold.getIsExpand(context),
appBar: AppBar(
leading: PageBackButton(),
title: Text('screenAccountSettings').tr(),

View File

@ -8,7 +8,6 @@ import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/user_directory.dart';
import 'package:surface/types/attachment.dart';
import 'package:surface/widgets/attachment/attachment_zoom.dart';
import 'package:surface/widgets/attachment/attachment_item.dart';
@ -53,7 +52,6 @@ class _AlbumScreenState extends State<AlbumScreen> {
try {
final sn = context.read<SnNetworkProvider>();
final ud = context.read<UserDirectoryProvider>();
final resp = await sn.client.get('/cgi/uc/attachments', queryParameters: {
'take': 10,
'offset': _attachments.length,
@ -64,8 +62,6 @@ class _AlbumScreenState extends State<AlbumScreen> {
_attachments.addAll(attachments);
_heroTags.addAll(_attachments.map((_) => uuid.v4()));
await ud.listAccount(attachments.map((e) => e.accountId).toSet());
_totalCount = resp.data['count'] as int?;
} catch (err) {
if (!mounted) return;

View File

@ -223,7 +223,7 @@ class _ChatScreenState extends State<ChatScreen> {
}
return AppScaffold(
noBackground: true,
noBackground: ResponsiveScaffold.getIsExpand(context),
appBar: AppBar(
leading: AutoAppBarLeading(),
title: Text('screenChat').tr(),
@ -389,7 +389,7 @@ class _ChatScreenState extends State<ChatScreen> {
children: [
if (_focusedRealm!.banner != null)
AspectRatio(
aspectRatio: 16 / 9,
aspectRatio: 16 / 7,
child: AutoResizeUniversalImage(
sn.getAttachmentUrl(
_focusedRealm!.banner!,

View File

@ -124,7 +124,7 @@ class _CallRoomScreenState extends State<CallRoomScreen> {
listenable: call,
builder: (context, _) {
return AppScaffold(
noBackground: true,
noBackground: ResponsiveScaffold.getIsExpand(context),
appBar: AppBar(
title: RichText(
textAlign: TextAlign.center,

View File

@ -220,7 +220,7 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
final isOwned = ua.isAuthorized && _channel?.accountId == ua.user?.id;
return AppScaffold(
noBackground: true,
noBackground: ResponsiveScaffold.getIsExpand(context),
appBar: AppBar(
title: _channel != null ? Text(_channel!.name) : Text('loading').tr(),
),

View File

@ -141,7 +141,7 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
@override
Widget build(BuildContext context) {
return AppScaffold(
noBackground: true,
noBackground: ResponsiveScaffold.getIsExpand(context),
appBar: AppBar(
title: widget.editingChannelAlias != null
? Text('screenChatManage').tr()

View File

@ -304,7 +304,7 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
final ud = context.read<UserDirectoryProvider>();
return AppScaffold(
noBackground: true,
noBackground: ResponsiveScaffold.getIsExpand(context),
appBar: AppBar(
title: Text(
_channel?.type == 1

View File

@ -157,7 +157,7 @@ class _ExploreScreenState extends State<ExploreScreen>
Widget build(BuildContext context) {
final cfg = context.watch<ConfigProvider>();
return AppScaffold(
noBackground: true,
noBackground: ResponsiveScaffold.getIsExpand(context),
floatingActionButtonLocation: ExpandableFab.location,
floatingActionButton: ExpandableFab(
key: _fabKey,

View File

@ -11,13 +11,11 @@ import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/notification.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/types/notification.dart';
import 'package:surface/types/post.dart';
import 'package:surface/widgets/app_bar_leading.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/loading_indicator.dart';
import 'package:surface/widgets/markdown_content.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:surface/widgets/post/post_item.dart';
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
import '../providers/userinfo.dart';
@ -219,34 +217,24 @@ class _NotificationScreenState extends State<NotificationScreen> {
'interactive.subscription',
].contains(nty.topic) &&
nty.metadata['related_post'] != null)
GestureDetector(
child: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(
Radius.circular(8)),
border: Border.all(
color: Theme.of(context).dividerColor,
width: 1),
TextButton(
style: ButtonStyle(
padding: WidgetStatePropertyAll(
EdgeInsets.zero,
),
child: PostItem(
data: SnPost.fromJson(
nty.metadata['related_post']!),
showComments: false,
showReactions: false,
showMenu: false,
).padding(vertical: 4),
visualDensity: VisualDensity.compact,
),
onTap: () {
child: Text('postReadMore').tr(),
onPressed: () {
GoRouter.of(context).pushNamed(
'postDetail',
pathParameters: {
'slug': nty
.metadata['related_post']!['id']
.toString()
'slug': nty.metadata['related_post']['id']
.toString(),
},
);
},
).padding(top: 8),
),
const Gap(8),
Row(
children: [

View File

@ -66,7 +66,7 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
final double maxWidth = _data?.type == 'video' ? double.infinity : 640;
return AppScaffold(
noBackground: true,
noBackground: ResponsiveScaffold.getIsExpand(context),
appBar: AppBar(
leading: BackButton(
onPressed: () {

View File

@ -79,6 +79,7 @@ class _PostShuffleScreenState extends State<PostShuffleScreen> {
key: ValueKey(ele),
data: ele,
maxWidth: 640,
useReplace: true,
onChanged: (ele) {
_posts[idx] = ele;
setState(() {});

View File

@ -286,7 +286,7 @@ class _PostPublisherScreenState extends State<PostPublisherScreen>
final sn = context.read<SnNetworkProvider>();
return AppScaffold(
noBackground: true,
noBackground: ResponsiveScaffold.getIsExpand(context),
body: NestedScrollView(
controller: _scrollController,
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {

View File

@ -45,7 +45,7 @@ class _WalletScreenState extends State<WalletScreen> {
@override
Widget build(BuildContext context) {
return AppScaffold(
noBackground: true,
noBackground: ResponsiveScaffold.getIsExpand(context),
appBar: AppBar(
leading: PageBackButton(), title: Text('screenAccountWallet').tr()),
body: Column(

View File

@ -1,4 +1,5 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:surface/types/account.dart';
part 'attachment.freezed.dart';
@ -39,6 +40,7 @@ abstract class SnAttachment with _$SnAttachment {
required int? refId,
required SnAttachmentPool? pool,
required int? poolId,
required SnAccount? account,
required int accountId,
int? thumbnailId,
SnAttachment? thumbnail,
@ -49,7 +51,8 @@ abstract class SnAttachment with _$SnAttachment {
@Default({}) Map<String, dynamic> metadata,
}) = _SnAttachment;
factory SnAttachment.fromJson(Map<String, Object?> json) => _$SnAttachmentFromJson(json);
factory SnAttachment.fromJson(Map<String, Object?> json) =>
_$SnAttachmentFromJson(json);
Map<String, dynamic> get data => {
...metadata,
@ -85,7 +88,8 @@ abstract class SnAttachmentFragment with _$SnAttachmentFragment {
@Default([]) List<String> fileChunksMissing,
}) = _SnAttachmentFragment;
factory SnAttachmentFragment.fromJson(Map<String, Object?> json) => _$SnAttachmentFragmentFromJson(json);
factory SnAttachmentFragment.fromJson(Map<String, Object?> json) =>
_$SnAttachmentFragmentFromJson(json);
SnMediaType get mediaType => switch (mimetype.split('/').firstOrNull) {
'image' => SnMediaType.image,
@ -109,7 +113,8 @@ abstract class SnAttachmentPool with _$SnAttachmentPool {
required int? accountId,
}) = _SnAttachmentPool;
factory SnAttachmentPool.fromJson(Map<String, Object?> json) => _$SnAttachmentPoolFromJson(json);
factory SnAttachmentPool.fromJson(Map<String, Object?> json) =>
_$SnAttachmentPoolFromJson(json);
}
@freezed
@ -122,7 +127,8 @@ abstract class SnAttachmentDestination with _$SnAttachmentDestination {
required bool isBoost,
}) = _SnAttachmentDestination;
factory SnAttachmentDestination.fromJson(Map<String, Object?> json) => _$SnAttachmentDestinationFromJson(json);
factory SnAttachmentDestination.fromJson(Map<String, Object?> json) =>
_$SnAttachmentDestinationFromJson(json);
}
@freezed
@ -139,7 +145,8 @@ abstract class SnAttachmentBoost with _$SnAttachmentBoost {
required int account,
}) = _SnAttachmentBoost;
factory SnAttachmentBoost.fromJson(Map<String, Object?> json) => _$SnAttachmentBoostFromJson(json);
factory SnAttachmentBoost.fromJson(Map<String, Object?> json) =>
_$SnAttachmentBoostFromJson(json);
}
@freezed
@ -158,7 +165,8 @@ abstract class SnSticker with _$SnSticker {
required int accountId,
}) = _SnSticker;
factory SnSticker.fromJson(Map<String, Object?> json) => _$SnStickerFromJson(json);
factory SnSticker.fromJson(Map<String, Object?> json) =>
_$SnStickerFromJson(json);
}
@freezed
@ -175,7 +183,8 @@ abstract class SnStickerPack with _$SnStickerPack {
required int accountId,
}) = _SnStickerPack;
factory SnStickerPack.fromJson(Map<String, Object?> json) => _$SnStickerPackFromJson(json);
factory SnStickerPack.fromJson(Map<String, Object?> json) =>
_$SnStickerPackFromJson(json);
}
@freezed
@ -186,5 +195,6 @@ abstract class SnAttachmentBilling with _$SnAttachmentBilling {
required double includedRatio,
}) = _SnAttachmentBilling;
factory SnAttachmentBilling.fromJson(Map<String, Object?> json) => _$SnAttachmentBillingFromJson(json);
factory SnAttachmentBilling.fromJson(Map<String, Object?> json) =>
_$SnAttachmentBillingFromJson(json);
}

View File

@ -38,6 +38,7 @@ mixin _$SnAttachment {
int? get refId;
SnAttachmentPool? get pool;
int? get poolId;
SnAccount? get account;
int get accountId;
int? get thumbnailId;
SnAttachment? get thumbnail;
@ -98,6 +99,7 @@ mixin _$SnAttachment {
(identical(other.refId, refId) || other.refId == refId) &&
(identical(other.pool, pool) || other.pool == pool) &&
(identical(other.poolId, poolId) || other.poolId == poolId) &&
(identical(other.account, account) || other.account == account) &&
(identical(other.accountId, accountId) ||
other.accountId == accountId) &&
(identical(other.thumbnailId, thumbnailId) ||
@ -140,6 +142,7 @@ mixin _$SnAttachment {
refId,
pool,
poolId,
account,
accountId,
thumbnailId,
thumbnail,
@ -152,7 +155,7 @@ mixin _$SnAttachment {
@override
String toString() {
return 'SnAttachment(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, rid: $rid, uuid: $uuid, size: $size, name: $name, alt: $alt, mimetype: $mimetype, hash: $hash, destination: $destination, refCount: $refCount, contentRating: $contentRating, qualityRating: $qualityRating, cleanedAt: $cleanedAt, isAnalyzed: $isAnalyzed, isSelfRef: $isSelfRef, isIndexable: $isIndexable, ref: $ref, refId: $refId, pool: $pool, poolId: $poolId, accountId: $accountId, thumbnailId: $thumbnailId, thumbnail: $thumbnail, compressedId: $compressedId, compressed: $compressed, boosts: $boosts, usermeta: $usermeta, metadata: $metadata)';
return 'SnAttachment(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, rid: $rid, uuid: $uuid, size: $size, name: $name, alt: $alt, mimetype: $mimetype, hash: $hash, destination: $destination, refCount: $refCount, contentRating: $contentRating, qualityRating: $qualityRating, cleanedAt: $cleanedAt, isAnalyzed: $isAnalyzed, isSelfRef: $isSelfRef, isIndexable: $isIndexable, ref: $ref, refId: $refId, pool: $pool, poolId: $poolId, account: $account, accountId: $accountId, thumbnailId: $thumbnailId, thumbnail: $thumbnail, compressedId: $compressedId, compressed: $compressed, boosts: $boosts, usermeta: $usermeta, metadata: $metadata)';
}
}
@ -186,6 +189,7 @@ abstract mixin class $SnAttachmentCopyWith<$Res> {
int? refId,
SnAttachmentPool? pool,
int? poolId,
SnAccount? account,
int accountId,
int? thumbnailId,
SnAttachment? thumbnail,
@ -197,6 +201,7 @@ abstract mixin class $SnAttachmentCopyWith<$Res> {
$SnAttachmentCopyWith<$Res>? get ref;
$SnAttachmentPoolCopyWith<$Res>? get pool;
$SnAccountCopyWith<$Res>? get account;
$SnAttachmentCopyWith<$Res>? get thumbnail;
$SnAttachmentCopyWith<$Res>? get compressed;
}
@ -236,6 +241,7 @@ class _$SnAttachmentCopyWithImpl<$Res> implements $SnAttachmentCopyWith<$Res> {
Object? refId = freezed,
Object? pool = freezed,
Object? poolId = freezed,
Object? account = freezed,
Object? accountId = null,
Object? thumbnailId = freezed,
Object? thumbnail = freezed,
@ -338,6 +344,10 @@ class _$SnAttachmentCopyWithImpl<$Res> implements $SnAttachmentCopyWith<$Res> {
? _self.poolId
: poolId // ignore: cast_nullable_to_non_nullable
as int?,
account: freezed == account
? _self.account
: account // ignore: cast_nullable_to_non_nullable
as SnAccount?,
accountId: null == accountId
? _self.accountId
: accountId // ignore: cast_nullable_to_non_nullable
@ -401,6 +411,20 @@ class _$SnAttachmentCopyWithImpl<$Res> implements $SnAttachmentCopyWith<$Res> {
});
}
/// Create a copy of SnAttachment
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnAccountCopyWith<$Res>? get account {
if (_self.account == null) {
return null;
}
return $SnAccountCopyWith<$Res>(_self.account!, (value) {
return _then(_self.copyWith(account: value));
});
}
/// Create a copy of SnAttachment
/// with the given fields replaced by the non-null parameter values.
@override
@ -457,6 +481,7 @@ class _SnAttachment extends SnAttachment {
required this.refId,
required this.pool,
required this.poolId,
required this.account,
required this.accountId,
this.thumbnailId,
this.thumbnail,
@ -521,6 +546,8 @@ class _SnAttachment extends SnAttachment {
@override
final int? poolId;
@override
final SnAccount? account;
@override
final int accountId;
@override
final int? thumbnailId;
@ -612,6 +639,7 @@ class _SnAttachment extends SnAttachment {
(identical(other.refId, refId) || other.refId == refId) &&
(identical(other.pool, pool) || other.pool == pool) &&
(identical(other.poolId, poolId) || other.poolId == poolId) &&
(identical(other.account, account) || other.account == account) &&
(identical(other.accountId, accountId) ||
other.accountId == accountId) &&
(identical(other.thumbnailId, thumbnailId) ||
@ -654,6 +682,7 @@ class _SnAttachment extends SnAttachment {
refId,
pool,
poolId,
account,
accountId,
thumbnailId,
thumbnail,
@ -666,7 +695,7 @@ class _SnAttachment extends SnAttachment {
@override
String toString() {
return 'SnAttachment(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, rid: $rid, uuid: $uuid, size: $size, name: $name, alt: $alt, mimetype: $mimetype, hash: $hash, destination: $destination, refCount: $refCount, contentRating: $contentRating, qualityRating: $qualityRating, cleanedAt: $cleanedAt, isAnalyzed: $isAnalyzed, isSelfRef: $isSelfRef, isIndexable: $isIndexable, ref: $ref, refId: $refId, pool: $pool, poolId: $poolId, accountId: $accountId, thumbnailId: $thumbnailId, thumbnail: $thumbnail, compressedId: $compressedId, compressed: $compressed, boosts: $boosts, usermeta: $usermeta, metadata: $metadata)';
return 'SnAttachment(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, rid: $rid, uuid: $uuid, size: $size, name: $name, alt: $alt, mimetype: $mimetype, hash: $hash, destination: $destination, refCount: $refCount, contentRating: $contentRating, qualityRating: $qualityRating, cleanedAt: $cleanedAt, isAnalyzed: $isAnalyzed, isSelfRef: $isSelfRef, isIndexable: $isIndexable, ref: $ref, refId: $refId, pool: $pool, poolId: $poolId, account: $account, accountId: $accountId, thumbnailId: $thumbnailId, thumbnail: $thumbnail, compressedId: $compressedId, compressed: $compressed, boosts: $boosts, usermeta: $usermeta, metadata: $metadata)';
}
}
@ -702,6 +731,7 @@ abstract mixin class _$SnAttachmentCopyWith<$Res>
int? refId,
SnAttachmentPool? pool,
int? poolId,
SnAccount? account,
int accountId,
int? thumbnailId,
SnAttachment? thumbnail,
@ -716,6 +746,8 @@ abstract mixin class _$SnAttachmentCopyWith<$Res>
@override
$SnAttachmentPoolCopyWith<$Res>? get pool;
@override
$SnAccountCopyWith<$Res>? get account;
@override
$SnAttachmentCopyWith<$Res>? get thumbnail;
@override
$SnAttachmentCopyWith<$Res>? get compressed;
@ -757,6 +789,7 @@ class __$SnAttachmentCopyWithImpl<$Res>
Object? refId = freezed,
Object? pool = freezed,
Object? poolId = freezed,
Object? account = freezed,
Object? accountId = null,
Object? thumbnailId = freezed,
Object? thumbnail = freezed,
@ -859,6 +892,10 @@ class __$SnAttachmentCopyWithImpl<$Res>
? _self.poolId
: poolId // ignore: cast_nullable_to_non_nullable
as int?,
account: freezed == account
? _self.account
: account // ignore: cast_nullable_to_non_nullable
as SnAccount?,
accountId: null == accountId
? _self.accountId
: accountId // ignore: cast_nullable_to_non_nullable
@ -922,6 +959,20 @@ class __$SnAttachmentCopyWithImpl<$Res>
});
}
/// Create a copy of SnAttachment
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnAccountCopyWith<$Res>? get account {
if (_self.account == null) {
return null;
}
return $SnAccountCopyWith<$Res>(_self.account!, (value) {
return _then(_self.copyWith(account: value));
});
}
/// Create a copy of SnAttachment
/// with the given fields replaced by the non-null parameter values.
@override

View File

@ -39,6 +39,9 @@ _SnAttachment _$SnAttachmentFromJson(Map<String, dynamic> json) =>
? null
: SnAttachmentPool.fromJson(json['pool'] as Map<String, dynamic>),
poolId: (json['pool_id'] as num?)?.toInt(),
account: json['account'] == null
? null
: SnAccount.fromJson(json['account'] as Map<String, dynamic>),
accountId: (json['account_id'] as num).toInt(),
thumbnailId: (json['thumbnail_id'] as num?)?.toInt(),
thumbnail: json['thumbnail'] == null
@ -82,6 +85,7 @@ Map<String, dynamic> _$SnAttachmentToJson(_SnAttachment instance) =>
'ref_id': instance.refId,
'pool': instance.pool?.toJson(),
'pool_id': instance.poolId,
'account': instance.account?.toJson(),
'account_id': instance.accountId,
'thumbnail_id': instance.thumbnailId,
'thumbnail': instance.thumbnail?.toJson(),

View File

@ -1,4 +1,5 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:surface/types/account.dart';
import 'package:surface/types/attachment.dart';
import 'package:surface/types/poll.dart';
import 'package:surface/types/realm.dart';
@ -26,6 +27,7 @@ abstract class SnPost with _$SnPost {
required int? replyId,
required int? repostId,
required int? realmId,
required SnRealm? realm,
required SnPost? replyTo,
required SnPost? repostTo,
required List<int>? visibleUsersList,
@ -43,9 +45,9 @@ abstract class SnPost with _$SnPost {
@Default(0) int totalAggregatedViews,
required int publisherId,
required int? pollId,
required SnPoll? poll,
required SnPublisher publisher,
required SnMetric metric,
SnPostPreload? preload,
}) = _SnPost;
factory SnPost.fromJson(Map<String, Object?> json) => _$SnPostFromJson(json);
@ -146,6 +148,7 @@ abstract class SnPublisher with _$SnPublisher {
required int totalDownvote,
required int? realmId,
required int accountId,
required SnAccount? account,
}) = _SnPublisher;
factory SnPublisher.fromJson(Map<String, Object?> json) =>

View File

@ -30,6 +30,7 @@ mixin _$SnPost {
int? get replyId;
int? get repostId;
int? get realmId;
SnRealm? get realm;
SnPost? get replyTo;
SnPost? get repostTo;
List<int>? get visibleUsersList;
@ -47,9 +48,9 @@ mixin _$SnPost {
int get totalAggregatedViews;
int get publisherId;
int? get pollId;
SnPoll? get poll;
SnPublisher get publisher;
SnMetric get metric;
SnPostPreload? get preload;
/// Create a copy of SnPost
/// with the given fields replaced by the non-null parameter values.
@ -88,6 +89,7 @@ mixin _$SnPost {
(identical(other.repostId, repostId) ||
other.repostId == repostId) &&
(identical(other.realmId, realmId) || other.realmId == realmId) &&
(identical(other.realm, realm) || other.realm == realm) &&
(identical(other.replyTo, replyTo) || other.replyTo == replyTo) &&
(identical(other.repostTo, repostTo) ||
other.repostTo == repostTo) &&
@ -119,10 +121,10 @@ mixin _$SnPost {
(identical(other.publisherId, publisherId) ||
other.publisherId == publisherId) &&
(identical(other.pollId, pollId) || other.pollId == pollId) &&
(identical(other.poll, poll) || other.poll == poll) &&
(identical(other.publisher, publisher) ||
other.publisher == publisher) &&
(identical(other.metric, metric) || other.metric == metric) &&
(identical(other.preload, preload) || other.preload == preload));
(identical(other.metric, metric) || other.metric == metric));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@ -144,6 +146,7 @@ mixin _$SnPost {
replyId,
repostId,
realmId,
realm,
replyTo,
repostTo,
const DeepCollectionEquality().hash(visibleUsersList),
@ -161,14 +164,14 @@ mixin _$SnPost {
totalAggregatedViews,
publisherId,
pollId,
poll,
publisher,
metric,
preload
metric
]);
@override
String toString() {
return 'SnPost(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, type: $type, body: $body, language: $language, alias: $alias, aliasPrefix: $aliasPrefix, tags: $tags, categories: $categories, replies: $replies, replyId: $replyId, repostId: $repostId, realmId: $realmId, replyTo: $replyTo, repostTo: $repostTo, visibleUsersList: $visibleUsersList, invisibleUsersList: $invisibleUsersList, visibility: $visibility, editedAt: $editedAt, pinnedAt: $pinnedAt, lockedAt: $lockedAt, isDraft: $isDraft, publishedAt: $publishedAt, publishedUntil: $publishedUntil, totalUpvote: $totalUpvote, totalDownvote: $totalDownvote, totalViews: $totalViews, totalAggregatedViews: $totalAggregatedViews, publisherId: $publisherId, pollId: $pollId, publisher: $publisher, metric: $metric, preload: $preload)';
return 'SnPost(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, type: $type, body: $body, language: $language, alias: $alias, aliasPrefix: $aliasPrefix, tags: $tags, categories: $categories, replies: $replies, replyId: $replyId, repostId: $repostId, realmId: $realmId, realm: $realm, replyTo: $replyTo, repostTo: $repostTo, visibleUsersList: $visibleUsersList, invisibleUsersList: $invisibleUsersList, visibility: $visibility, editedAt: $editedAt, pinnedAt: $pinnedAt, lockedAt: $lockedAt, isDraft: $isDraft, publishedAt: $publishedAt, publishedUntil: $publishedUntil, totalUpvote: $totalUpvote, totalDownvote: $totalDownvote, totalViews: $totalViews, totalAggregatedViews: $totalAggregatedViews, publisherId: $publisherId, pollId: $pollId, poll: $poll, publisher: $publisher, metric: $metric)';
}
}
@ -193,6 +196,7 @@ abstract mixin class $SnPostCopyWith<$Res> {
int? replyId,
int? repostId,
int? realmId,
SnRealm? realm,
SnPost? replyTo,
SnPost? repostTo,
List<int>? visibleUsersList,
@ -210,15 +214,16 @@ abstract mixin class $SnPostCopyWith<$Res> {
int totalAggregatedViews,
int publisherId,
int? pollId,
SnPoll? poll,
SnPublisher publisher,
SnMetric metric,
SnPostPreload? preload});
SnMetric metric});
$SnRealmCopyWith<$Res>? get realm;
$SnPostCopyWith<$Res>? get replyTo;
$SnPostCopyWith<$Res>? get repostTo;
$SnPollCopyWith<$Res>? get poll;
$SnPublisherCopyWith<$Res> get publisher;
$SnMetricCopyWith<$Res> get metric;
$SnPostPreloadCopyWith<$Res>? get preload;
}
/// @nodoc
@ -248,6 +253,7 @@ class _$SnPostCopyWithImpl<$Res> implements $SnPostCopyWith<$Res> {
Object? replyId = freezed,
Object? repostId = freezed,
Object? realmId = freezed,
Object? realm = freezed,
Object? replyTo = freezed,
Object? repostTo = freezed,
Object? visibleUsersList = freezed,
@ -265,9 +271,9 @@ class _$SnPostCopyWithImpl<$Res> implements $SnPostCopyWith<$Res> {
Object? totalAggregatedViews = null,
Object? publisherId = null,
Object? pollId = freezed,
Object? poll = freezed,
Object? publisher = null,
Object? metric = null,
Object? preload = freezed,
}) {
return _then(_self.copyWith(
id: null == id
@ -330,6 +336,10 @@ class _$SnPostCopyWithImpl<$Res> implements $SnPostCopyWith<$Res> {
? _self.realmId
: realmId // ignore: cast_nullable_to_non_nullable
as int?,
realm: freezed == realm
? _self.realm
: realm // ignore: cast_nullable_to_non_nullable
as SnRealm?,
replyTo: freezed == replyTo
? _self.replyTo
: replyTo // ignore: cast_nullable_to_non_nullable
@ -398,6 +408,10 @@ class _$SnPostCopyWithImpl<$Res> implements $SnPostCopyWith<$Res> {
? _self.pollId
: pollId // ignore: cast_nullable_to_non_nullable
as int?,
poll: freezed == poll
? _self.poll
: poll // ignore: cast_nullable_to_non_nullable
as SnPoll?,
publisher: null == publisher
? _self.publisher
: publisher // ignore: cast_nullable_to_non_nullable
@ -406,13 +420,23 @@ class _$SnPostCopyWithImpl<$Res> implements $SnPostCopyWith<$Res> {
? _self.metric
: metric // ignore: cast_nullable_to_non_nullable
as SnMetric,
preload: freezed == preload
? _self.preload
: preload // ignore: cast_nullable_to_non_nullable
as SnPostPreload?,
));
}
/// Create a copy of SnPost
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnRealmCopyWith<$Res>? get realm {
if (_self.realm == null) {
return null;
}
return $SnRealmCopyWith<$Res>(_self.realm!, (value) {
return _then(_self.copyWith(realm: value));
});
}
/// Create a copy of SnPost
/// with the given fields replaced by the non-null parameter values.
@override
@ -441,6 +465,20 @@ class _$SnPostCopyWithImpl<$Res> implements $SnPostCopyWith<$Res> {
});
}
/// Create a copy of SnPost
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnPollCopyWith<$Res>? get poll {
if (_self.poll == null) {
return null;
}
return $SnPollCopyWith<$Res>(_self.poll!, (value) {
return _then(_self.copyWith(poll: value));
});
}
/// Create a copy of SnPost
/// with the given fields replaced by the non-null parameter values.
@override
@ -460,20 +498,6 @@ class _$SnPostCopyWithImpl<$Res> implements $SnPostCopyWith<$Res> {
return _then(_self.copyWith(metric: value));
});
}
/// Create a copy of SnPost
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnPostPreloadCopyWith<$Res>? get preload {
if (_self.preload == null) {
return null;
}
return $SnPostPreloadCopyWith<$Res>(_self.preload!, (value) {
return _then(_self.copyWith(preload: value));
});
}
}
/// @nodoc
@ -495,6 +519,7 @@ class _SnPost extends SnPost {
required this.replyId,
required this.repostId,
required this.realmId,
required this.realm,
required this.replyTo,
required this.repostTo,
required final List<int>? visibleUsersList,
@ -512,9 +537,9 @@ class _SnPost extends SnPost {
this.totalAggregatedViews = 0,
required this.publisherId,
required this.pollId,
required this.poll,
required this.publisher,
required this.metric,
this.preload})
required this.metric})
: _body = body,
_tags = tags,
_categories = categories,
@ -583,6 +608,8 @@ class _SnPost extends SnPost {
@override
final int? realmId;
@override
final SnRealm? realm;
@override
final SnPost? replyTo;
@override
final SnPost? repostTo;
@ -637,11 +664,11 @@ class _SnPost extends SnPost {
@override
final int? pollId;
@override
final SnPoll? poll;
@override
final SnPublisher publisher;
@override
final SnMetric metric;
@override
final SnPostPreload? preload;
/// Create a copy of SnPost
/// with the given fields replaced by the non-null parameter values.
@ -685,6 +712,7 @@ class _SnPost extends SnPost {
(identical(other.repostId, repostId) ||
other.repostId == repostId) &&
(identical(other.realmId, realmId) || other.realmId == realmId) &&
(identical(other.realm, realm) || other.realm == realm) &&
(identical(other.replyTo, replyTo) || other.replyTo == replyTo) &&
(identical(other.repostTo, repostTo) ||
other.repostTo == repostTo) &&
@ -716,10 +744,10 @@ class _SnPost extends SnPost {
(identical(other.publisherId, publisherId) ||
other.publisherId == publisherId) &&
(identical(other.pollId, pollId) || other.pollId == pollId) &&
(identical(other.poll, poll) || other.poll == poll) &&
(identical(other.publisher, publisher) ||
other.publisher == publisher) &&
(identical(other.metric, metric) || other.metric == metric) &&
(identical(other.preload, preload) || other.preload == preload));
(identical(other.metric, metric) || other.metric == metric));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@ -741,6 +769,7 @@ class _SnPost extends SnPost {
replyId,
repostId,
realmId,
realm,
replyTo,
repostTo,
const DeepCollectionEquality().hash(_visibleUsersList),
@ -758,14 +787,14 @@ class _SnPost extends SnPost {
totalAggregatedViews,
publisherId,
pollId,
poll,
publisher,
metric,
preload
metric
]);
@override
String toString() {
return 'SnPost(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, type: $type, body: $body, language: $language, alias: $alias, aliasPrefix: $aliasPrefix, tags: $tags, categories: $categories, replies: $replies, replyId: $replyId, repostId: $repostId, realmId: $realmId, replyTo: $replyTo, repostTo: $repostTo, visibleUsersList: $visibleUsersList, invisibleUsersList: $invisibleUsersList, visibility: $visibility, editedAt: $editedAt, pinnedAt: $pinnedAt, lockedAt: $lockedAt, isDraft: $isDraft, publishedAt: $publishedAt, publishedUntil: $publishedUntil, totalUpvote: $totalUpvote, totalDownvote: $totalDownvote, totalViews: $totalViews, totalAggregatedViews: $totalAggregatedViews, publisherId: $publisherId, pollId: $pollId, publisher: $publisher, metric: $metric, preload: $preload)';
return 'SnPost(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, type: $type, body: $body, language: $language, alias: $alias, aliasPrefix: $aliasPrefix, tags: $tags, categories: $categories, replies: $replies, replyId: $replyId, repostId: $repostId, realmId: $realmId, realm: $realm, replyTo: $replyTo, repostTo: $repostTo, visibleUsersList: $visibleUsersList, invisibleUsersList: $invisibleUsersList, visibility: $visibility, editedAt: $editedAt, pinnedAt: $pinnedAt, lockedAt: $lockedAt, isDraft: $isDraft, publishedAt: $publishedAt, publishedUntil: $publishedUntil, totalUpvote: $totalUpvote, totalDownvote: $totalDownvote, totalViews: $totalViews, totalAggregatedViews: $totalAggregatedViews, publisherId: $publisherId, pollId: $pollId, poll: $poll, publisher: $publisher, metric: $metric)';
}
}
@ -791,6 +820,7 @@ abstract mixin class _$SnPostCopyWith<$Res> implements $SnPostCopyWith<$Res> {
int? replyId,
int? repostId,
int? realmId,
SnRealm? realm,
SnPost? replyTo,
SnPost? repostTo,
List<int>? visibleUsersList,
@ -808,20 +838,22 @@ abstract mixin class _$SnPostCopyWith<$Res> implements $SnPostCopyWith<$Res> {
int totalAggregatedViews,
int publisherId,
int? pollId,
SnPoll? poll,
SnPublisher publisher,
SnMetric metric,
SnPostPreload? preload});
SnMetric metric});
@override
$SnRealmCopyWith<$Res>? get realm;
@override
$SnPostCopyWith<$Res>? get replyTo;
@override
$SnPostCopyWith<$Res>? get repostTo;
@override
$SnPollCopyWith<$Res>? get poll;
@override
$SnPublisherCopyWith<$Res> get publisher;
@override
$SnMetricCopyWith<$Res> get metric;
@override
$SnPostPreloadCopyWith<$Res>? get preload;
}
/// @nodoc
@ -851,6 +883,7 @@ class __$SnPostCopyWithImpl<$Res> implements _$SnPostCopyWith<$Res> {
Object? replyId = freezed,
Object? repostId = freezed,
Object? realmId = freezed,
Object? realm = freezed,
Object? replyTo = freezed,
Object? repostTo = freezed,
Object? visibleUsersList = freezed,
@ -868,9 +901,9 @@ class __$SnPostCopyWithImpl<$Res> implements _$SnPostCopyWith<$Res> {
Object? totalAggregatedViews = null,
Object? publisherId = null,
Object? pollId = freezed,
Object? poll = freezed,
Object? publisher = null,
Object? metric = null,
Object? preload = freezed,
}) {
return _then(_SnPost(
id: null == id
@ -933,6 +966,10 @@ class __$SnPostCopyWithImpl<$Res> implements _$SnPostCopyWith<$Res> {
? _self.realmId
: realmId // ignore: cast_nullable_to_non_nullable
as int?,
realm: freezed == realm
? _self.realm
: realm // ignore: cast_nullable_to_non_nullable
as SnRealm?,
replyTo: freezed == replyTo
? _self.replyTo
: replyTo // ignore: cast_nullable_to_non_nullable
@ -1001,6 +1038,10 @@ class __$SnPostCopyWithImpl<$Res> implements _$SnPostCopyWith<$Res> {
? _self.pollId
: pollId // ignore: cast_nullable_to_non_nullable
as int?,
poll: freezed == poll
? _self.poll
: poll // ignore: cast_nullable_to_non_nullable
as SnPoll?,
publisher: null == publisher
? _self.publisher
: publisher // ignore: cast_nullable_to_non_nullable
@ -1009,13 +1050,23 @@ class __$SnPostCopyWithImpl<$Res> implements _$SnPostCopyWith<$Res> {
? _self.metric
: metric // ignore: cast_nullable_to_non_nullable
as SnMetric,
preload: freezed == preload
? _self.preload
: preload // ignore: cast_nullable_to_non_nullable
as SnPostPreload?,
));
}
/// Create a copy of SnPost
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnRealmCopyWith<$Res>? get realm {
if (_self.realm == null) {
return null;
}
return $SnRealmCopyWith<$Res>(_self.realm!, (value) {
return _then(_self.copyWith(realm: value));
});
}
/// Create a copy of SnPost
/// with the given fields replaced by the non-null parameter values.
@override
@ -1044,6 +1095,20 @@ class __$SnPostCopyWithImpl<$Res> implements _$SnPostCopyWith<$Res> {
});
}
/// Create a copy of SnPost
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnPollCopyWith<$Res>? get poll {
if (_self.poll == null) {
return null;
}
return $SnPollCopyWith<$Res>(_self.poll!, (value) {
return _then(_self.copyWith(poll: value));
});
}
/// Create a copy of SnPost
/// with the given fields replaced by the non-null parameter values.
@override
@ -1063,20 +1128,6 @@ class __$SnPostCopyWithImpl<$Res> implements _$SnPostCopyWith<$Res> {
return _then(_self.copyWith(metric: value));
});
}
/// Create a copy of SnPost
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnPostPreloadCopyWith<$Res>? get preload {
if (_self.preload == null) {
return null;
}
return $SnPostPreloadCopyWith<$Res>(_self.preload!, (value) {
return _then(_self.copyWith(preload: value));
});
}
}
/// @nodoc
@ -2465,6 +2516,7 @@ mixin _$SnPublisher {
int get totalDownvote;
int? get realmId;
int get accountId;
SnAccount? get account;
/// Create a copy of SnPublisher
/// with the given fields replaced by the non-null parameter values.
@ -2501,7 +2553,8 @@ mixin _$SnPublisher {
other.totalDownvote == totalDownvote) &&
(identical(other.realmId, realmId) || other.realmId == realmId) &&
(identical(other.accountId, accountId) ||
other.accountId == accountId));
other.accountId == accountId) &&
(identical(other.account, account) || other.account == account));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@ -2521,11 +2574,12 @@ mixin _$SnPublisher {
totalUpvote,
totalDownvote,
realmId,
accountId);
accountId,
account);
@override
String toString() {
return 'SnPublisher(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, type: $type, name: $name, nick: $nick, description: $description, avatar: $avatar, banner: $banner, totalUpvote: $totalUpvote, totalDownvote: $totalDownvote, realmId: $realmId, accountId: $accountId)';
return 'SnPublisher(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, type: $type, name: $name, nick: $nick, description: $description, avatar: $avatar, banner: $banner, totalUpvote: $totalUpvote, totalDownvote: $totalDownvote, realmId: $realmId, accountId: $accountId, account: $account)';
}
}
@ -2549,7 +2603,10 @@ abstract mixin class $SnPublisherCopyWith<$Res> {
int totalUpvote,
int totalDownvote,
int? realmId,
int accountId});
int accountId,
SnAccount? account});
$SnAccountCopyWith<$Res>? get account;
}
/// @nodoc
@ -2578,6 +2635,7 @@ class _$SnPublisherCopyWithImpl<$Res> implements $SnPublisherCopyWith<$Res> {
Object? totalDownvote = null,
Object? realmId = freezed,
Object? accountId = null,
Object? account = freezed,
}) {
return _then(_self.copyWith(
id: null == id
@ -2636,8 +2694,26 @@ class _$SnPublisherCopyWithImpl<$Res> implements $SnPublisherCopyWith<$Res> {
? _self.accountId
: accountId // ignore: cast_nullable_to_non_nullable
as int,
account: freezed == account
? _self.account
: account // ignore: cast_nullable_to_non_nullable
as SnAccount?,
));
}
/// Create a copy of SnPublisher
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnAccountCopyWith<$Res>? get account {
if (_self.account == null) {
return null;
}
return $SnAccountCopyWith<$Res>(_self.account!, (value) {
return _then(_self.copyWith(account: value));
});
}
}
/// @nodoc
@ -2657,7 +2733,8 @@ class _SnPublisher implements SnPublisher {
required this.totalUpvote,
required this.totalDownvote,
required this.realmId,
required this.accountId});
required this.accountId,
required this.account});
factory _SnPublisher.fromJson(Map<String, dynamic> json) =>
_$SnPublisherFromJson(json);
@ -2689,6 +2766,8 @@ class _SnPublisher implements SnPublisher {
final int? realmId;
@override
final int accountId;
@override
final SnAccount? account;
/// Create a copy of SnPublisher
/// with the given fields replaced by the non-null parameter values.
@ -2730,7 +2809,8 @@ class _SnPublisher implements SnPublisher {
other.totalDownvote == totalDownvote) &&
(identical(other.realmId, realmId) || other.realmId == realmId) &&
(identical(other.accountId, accountId) ||
other.accountId == accountId));
other.accountId == accountId) &&
(identical(other.account, account) || other.account == account));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@ -2750,11 +2830,12 @@ class _SnPublisher implements SnPublisher {
totalUpvote,
totalDownvote,
realmId,
accountId);
accountId,
account);
@override
String toString() {
return 'SnPublisher(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, type: $type, name: $name, nick: $nick, description: $description, avatar: $avatar, banner: $banner, totalUpvote: $totalUpvote, totalDownvote: $totalDownvote, realmId: $realmId, accountId: $accountId)';
return 'SnPublisher(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, type: $type, name: $name, nick: $nick, description: $description, avatar: $avatar, banner: $banner, totalUpvote: $totalUpvote, totalDownvote: $totalDownvote, realmId: $realmId, accountId: $accountId, account: $account)';
}
}
@ -2780,7 +2861,11 @@ abstract mixin class _$SnPublisherCopyWith<$Res>
int totalUpvote,
int totalDownvote,
int? realmId,
int accountId});
int accountId,
SnAccount? account});
@override
$SnAccountCopyWith<$Res>? get account;
}
/// @nodoc
@ -2809,6 +2894,7 @@ class __$SnPublisherCopyWithImpl<$Res> implements _$SnPublisherCopyWith<$Res> {
Object? totalDownvote = null,
Object? realmId = freezed,
Object? accountId = null,
Object? account = freezed,
}) {
return _then(_SnPublisher(
id: null == id
@ -2867,8 +2953,26 @@ class __$SnPublisherCopyWithImpl<$Res> implements _$SnPublisherCopyWith<$Res> {
? _self.accountId
: accountId // ignore: cast_nullable_to_non_nullable
as int,
account: freezed == account
? _self.account
: account // ignore: cast_nullable_to_non_nullable
as SnAccount?,
));
}
/// Create a copy of SnPublisher
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnAccountCopyWith<$Res>? get account {
if (_self.account == null) {
return null;
}
return $SnAccountCopyWith<$Res>(_self.account!, (value) {
return _then(_self.copyWith(account: value));
});
}
}
/// @nodoc

View File

@ -32,6 +32,9 @@ _SnPost _$SnPostFromJson(Map<String, dynamic> json) => _SnPost(
replyId: (json['reply_id'] as num?)?.toInt(),
repostId: (json['repost_id'] as num?)?.toInt(),
realmId: (json['realm_id'] as num?)?.toInt(),
realm: json['realm'] == null
? null
: SnRealm.fromJson(json['realm'] as Map<String, dynamic>),
replyTo: json['reply_to'] == null
? null
: SnPost.fromJson(json['reply_to'] as Map<String, dynamic>),
@ -68,12 +71,12 @@ _SnPost _$SnPostFromJson(Map<String, dynamic> json) => _SnPost(
(json['total_aggregated_views'] as num?)?.toInt() ?? 0,
publisherId: (json['publisher_id'] as num).toInt(),
pollId: (json['poll_id'] as num?)?.toInt(),
poll: json['poll'] == null
? null
: SnPoll.fromJson(json['poll'] as Map<String, dynamic>),
publisher:
SnPublisher.fromJson(json['publisher'] as Map<String, dynamic>),
metric: SnMetric.fromJson(json['metric'] as Map<String, dynamic>),
preload: json['preload'] == null
? null
: SnPostPreload.fromJson(json['preload'] as Map<String, dynamic>),
);
Map<String, dynamic> _$SnPostToJson(_SnPost instance) => <String, dynamic>{
@ -92,6 +95,7 @@ Map<String, dynamic> _$SnPostToJson(_SnPost instance) => <String, dynamic>{
'reply_id': instance.replyId,
'repost_id': instance.repostId,
'realm_id': instance.realmId,
'realm': instance.realm?.toJson(),
'reply_to': instance.replyTo?.toJson(),
'repost_to': instance.repostTo?.toJson(),
'visible_users_list': instance.visibleUsersList,
@ -109,9 +113,9 @@ Map<String, dynamic> _$SnPostToJson(_SnPost instance) => <String, dynamic>{
'total_aggregated_views': instance.totalAggregatedViews,
'publisher_id': instance.publisherId,
'poll_id': instance.pollId,
'poll': instance.poll?.toJson(),
'publisher': instance.publisher.toJson(),
'metric': instance.metric.toJson(),
'preload': instance.preload?.toJson(),
};
_SnPostTag _$SnPostTagFromJson(Map<String, dynamic> json) => _SnPostTag(
@ -241,6 +245,9 @@ _SnPublisher _$SnPublisherFromJson(Map<String, dynamic> json) => _SnPublisher(
totalDownvote: (json['total_downvote'] as num).toInt(),
realmId: (json['realm_id'] as num?)?.toInt(),
accountId: (json['account_id'] as num).toInt(),
account: json['account'] == null
? null
: SnAccount.fromJson(json['account'] as Map<String, dynamic>),
);
Map<String, dynamic> _$SnPublisherToJson(_SnPublisher instance) =>
@ -259,6 +266,7 @@ Map<String, dynamic> _$SnPublisherToJson(_SnPublisher instance) =>
'total_downvote': instance.totalDownvote,
'realm_id': instance.realmId,
'account_id': instance.accountId,
'account': instance.account?.toJson(),
};
_SnSubscription _$SnSubscriptionFromJson(Map<String, dynamic> json) =>

View File

@ -17,4 +17,5 @@ const Map<String, ReactInfo> kTemplateReactions = {
'party': ReactInfo(icon: '🎉', attitude: 1),
'joy': ReactInfo(icon: '🤣', attitude: 1),
'pray': ReactInfo(icon: '🙏', attitude: 1),
'heart': ReactInfo(icon: '❤️', attitude: 1),
};

View File

@ -16,7 +16,6 @@ import 'package:photo_view/photo_view_gallery.dart';
import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/user_directory.dart';
import 'package:surface/types/attachment.dart';
import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/dialog.dart';
@ -418,8 +417,7 @@ class _AttachmentZoomDetailPopup extends StatelessWidget {
@override
Widget build(BuildContext context) {
final ud = context.read<UserDirectoryProvider>();
final account = ud.getFromCache(data.accountId);
final account = data.account!;
const tableGap = TableRow(
children: [
@ -461,12 +459,12 @@ class _AttachmentZoomDetailPopup extends StatelessWidget {
children: [
if (data.accountId > 0)
AccountImage(
content: account?.avatar,
content: account.avatar,
radius: 8,
),
const Gap(8),
Text(data.accountId > 0
? account?.nick ?? 'unknown'.tr()
? account.nick
: 'unknown'.tr()),
const Gap(8),
Text('#${data.accountId}',

View File

@ -26,11 +26,13 @@ class _LinkPreviewWidgetState extends State<LinkPreviewWidget> {
Future<void> _getLinkMeta() async {
final linkRegex = RegExp(r'https?:\/\/[^\s/$.?#].[^\s]*');
final links = linkRegex.allMatches(widget.text).map((e) => e.group(0)).toSet();
final links =
linkRegex.allMatches(widget.text).map((e) => e.group(0)).toSet();
final lp = context.read<SnLinkPreviewProvider>();
final List<Future<SnLinkMeta?>> futures = links.where((e) => e != null).map((e) => lp.getLinkMeta(e!)).toList();
final List<Future<SnLinkMeta?>> futures =
links.where((e) => e != null).map((e) => lp.getLinkMeta(e!)).toList();
final results = await Future.wait(futures);
_links.addAll(results.where((e) => e != null).map((e) => e!).toList());
@ -66,7 +68,9 @@ class _LinkPreviewEntry extends StatelessWidget {
Widget build(BuildContext context) {
return Container(
constraints: BoxConstraints(
maxWidth: ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE) ? double.infinity : 480,
maxWidth: ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE)
? double.infinity
: 480,
),
child: GestureDetector(
child: Card(
@ -74,16 +78,25 @@ class _LinkPreviewEntry extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (meta.image != null)
Container(
margin: const EdgeInsets.only(bottom: 4),
color: Theme.of(context).colorScheme.surfaceContainer,
child: AspectRatio(
aspectRatio: 16 / 9,
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: AutoResizeUniversalImage(
meta.image!.startsWith('//') ? 'https:${meta.image}' : meta.image!,
fit: BoxFit.contain,
ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(8),
topRight: Radius.circular(8),
),
child: Container(
margin: const EdgeInsets.only(bottom: 4),
color: Theme.of(context).colorScheme.surfaceContainer,
child: AspectRatio(
aspectRatio: 16 / 9,
child: ClipRRect(
borderRadius:
const BorderRadius.all(Radius.circular(8)),
child: AutoResizeUniversalImage(
meta.image!.startsWith('//')
? 'https:${meta.image}'
: meta.image!,
fit: BoxFit.contain,
),
),
),
),
@ -98,7 +111,8 @@ class _LinkPreviewEntry extends StatelessWidget {
width: 36,
height: 36,
child: meta.icon!.endsWith('.svg')
? SvgPicture.network(meta.icon!, width: 36, height: 36)
? SvgPicture.network(meta.icon!,
width: 36, height: 36)
: UniversalImage(
meta.icon!,
noErrorWidget: true,

View File

@ -87,6 +87,7 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
onTap: () {
GoRouter.of(context).pushNamed(ele.screen);
nav.setIndex(idx);
Scaffold.of(context).closeDrawer();
},
);
})

View File

@ -282,7 +282,7 @@ class ResponsiveScaffoldLanding extends StatelessWidget {
Widget build(BuildContext context) {
if (ResponsiveScaffold.getIsExpand(context) || child == null) {
return AppScaffold(
noBackground: true,
noBackground: ResponsiveScaffold.getIsExpand(context),
appBar: AppBar(),
body: const SizedBox.shrink(),
);

View File

@ -179,57 +179,54 @@ class _PostCommentListPopupState extends State<PostCommentListPopup> {
final ua = context.watch<UserProvider>();
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
return SizedBox(
height: MediaQuery.of(context).size.height * 0.85,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(Symbols.comment, size: 24),
const Gap(16),
Text('postCommentsDetailed')
.plural(widget.commentCount)
.textStyle(Theme.of(context).textTheme.titleLarge!),
],
).padding(horizontal: 20, top: 16, bottom: 12),
Expanded(
child: CustomScrollView(
slivers: [
if (ua.isAuthorized)
SliverToBoxAdapter(
child: Container(
margin: const EdgeInsets.only(bottom: 8),
height: 240,
decoration: BoxDecoration(
border: Border.symmetric(
horizontal: BorderSide(
color: Theme.of(context).dividerColor,
width: 1 / devicePixelRatio,
),
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(Symbols.comment, size: 24),
const Gap(16),
Text('postCommentsDetailed')
.plural(widget.commentCount)
.textStyle(Theme.of(context).textTheme.titleLarge!),
],
).padding(horizontal: 20, top: 16, bottom: 12),
Expanded(
child: CustomScrollView(
slivers: [
if (ua.isAuthorized)
SliverToBoxAdapter(
child: Container(
margin: const EdgeInsets.only(bottom: 8),
height: 240,
decoration: BoxDecoration(
border: Border.symmetric(
horizontal: BorderSide(
color: Theme.of(context).dividerColor,
width: 1 / devicePixelRatio,
),
),
child: PostMiniEditor(
postReplyId: widget.post.id,
onPost: () {
_childListKey.currentState!.refresh();
},
onExpand: () {
Navigator.pop(context);
},
),
),
child: PostMiniEditor(
postReplyId: widget.post.id,
onPost: () {
_childListKey.currentState!.refresh();
},
onExpand: () {
Navigator.pop(context);
},
),
),
PostCommentSliverList(
parentPost: widget.post,
key: _childListKey,
),
],
),
PostCommentSliverList(
parentPost: widget.post,
key: _childListKey,
),
],
),
],
),
),
],
);
}
}

View File

@ -274,8 +274,10 @@ class _PostItemState extends State<PostItem> {
final isParentAuthor = ua.isAuthorized &&
widget.data.replyTo?.publisher.accountId == ua.user?.id;
final displayableAttachments = widget.data.preload?.attachments
?.where((ele) =>
final displayableAttachments = widget.data.body['attachments']
?.map((e) => SnAttachment.fromJson(e))
.cast<SnAttachment>()
.where((ele) =>
ele?.mediaType != SnMediaType.image ||
widget.data.type != 'article')
.toList();
@ -284,7 +286,7 @@ class _PostItemState extends State<PostItem> {
var attachmentSize = math.min(
MediaQuery.of(context).size.width, widget.maxWidth ?? double.infinity);
if ((widget.data.preload?.attachments?.length ?? 0) > 1) {
if ((widget.data.body['attachments']?.length ?? 0) > 1) {
attachmentSize -= 80;
}
@ -341,7 +343,7 @@ class _PostItemState extends State<PostItem> {
],
),
const Gap(8),
if (widget.data.preload?.thumbnail != null)
if (widget.data.body['thumbnail'] != null)
Container(
margin: const EdgeInsets.only(bottom: 8),
decoration: BoxDecoration(
@ -361,14 +363,14 @@ class _PostItemState extends State<PostItem> {
),
child: AutoResizeUniversalImage(
sn.getAttachmentUrl(
widget.data.preload!.thumbnail!.rid,
widget.data.body['thumbnail']['rid'],
),
fit: BoxFit.cover,
),
),
),
),
if (widget.data.preload?.video != null)
if (widget.data.body['video'] != null)
_PostVideoPlayer(data: widget.data).padding(bottom: 8),
if (widget.data.type == 'question')
_PostQuestionHint(data: widget.data).padding(bottom: 8),
@ -455,10 +457,10 @@ class _PostItemState extends State<PostItem> {
if (widget.data.repostTo != null)
_PostQuoteContent(child: widget.data.repostTo!).padding(
top: 4,
bottom: widget.data.preload?.attachments?.isNotEmpty ??
false
? 12
: 0,
bottom:
widget.data.body['attachments'].isNotEmpty ?? false
? 12
: 0,
),
],
).padding(
@ -479,11 +481,11 @@ class _PostItemState extends State<PostItem> {
fit: widget.showFullPost ? BoxFit.cover : BoxFit.contain,
padding: EdgeInsets.only(left: 12, right: 12),
),
if (widget.data.preload?.poll != null)
if (widget.data.poll != null)
StyledWidget(Container(
constraints:
BoxConstraints(maxWidth: widget.maxWidth ?? double.infinity),
child: PostPoll(poll: widget.data.preload!.poll!),
child: PostPoll(poll: widget.data.poll!),
))
.padding(
left: 12,
@ -585,7 +587,7 @@ class _PostItemState extends State<PostItem> {
),
],
).padding(bottom: widget.showCompactAvatar ? 4 : 0),
if (widget.data.preload?.thumbnail != null)
if (widget.data.body['thumbnail'] != null)
Container(
margin: const EdgeInsets.only(bottom: 8),
decoration: BoxDecoration(
@ -605,14 +607,14 @@ class _PostItemState extends State<PostItem> {
),
child: AutoResizeUniversalImage(
sn.getAttachmentUrl(
widget.data.preload!.thumbnail!.rid,
widget.data.body['thumbnail']['rid'],
),
fit: BoxFit.cover,
),
),
),
),
if (widget.data.preload?.video != null)
if (widget.data.body['video'] != null)
_PostVideoPlayer(data: widget.data)
.padding(bottom: 8),
if (widget.data.type == 'question')
@ -712,7 +714,7 @@ class _PostItemState extends State<PostItem> {
_isTranslated ||
_isTranslating) &&
(widget.data.repostTo != null ||
(widget.data.preload?.attachments
(widget.data.body['attachments']
?.isNotEmpty ??
false))
? 8
@ -722,7 +724,7 @@ class _PostItemState extends State<PostItem> {
_PostQuoteContent(child: widget.data.repostTo!)
.padding(
bottom:
(widget.data.preload?.attachments?.isNotEmpty ??
(widget.data.body['attachments']?.isNotEmpty ??
false)
? 8
: 0,
@ -746,8 +748,8 @@ class _PostItemState extends State<PostItem> {
padding:
EdgeInsets.only(left: widget.showAvatar ? 60 : 12, right: 12),
),
if (widget.data.preload?.poll != null)
PostPoll(poll: widget.data.preload!.poll!).padding(
if (widget.data.poll != null)
PostPoll(poll: widget.data.poll!).padding(
left: widget.showAvatar ? 60 : 12,
right: 12,
top: 12,
@ -808,7 +810,7 @@ class PostShareImageWidget extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
if (data.preload?.thumbnail != null)
if (data.body['thumbnail'] != null)
AspectRatio(
aspectRatio: 16 / 9,
child: ClipRRect(
@ -817,7 +819,7 @@ class PostShareImageWidget extends StatelessWidget {
topRight: Radius.circular(8),
),
child: AutoResizeUniversalImage(
sn.getAttachmentUrl(data.preload!.thumbnail!.rid),
sn.getAttachmentUrl(data.body['thumbnail']['rid']),
fit: BoxFit.cover,
filterQuality: FilterQuality.high,
),
@ -855,9 +857,13 @@ class PostShareImageWidget extends StatelessWidget {
isRelativeDate: false,
).padding(horizontal: 16, bottom: 8),
if (data.type != 'article' &&
(data.preload?.attachments?.isNotEmpty ?? false))
(data.body['attachments']?.isNotEmpty ?? false))
StyledWidget(AttachmentList(
data: data.preload!.attachments!,
data: data.body['attachments']
?.map((e) => SnAttachment.fromJson(e))
.cast<SnAttachment>()
.toList() ??
[],
columned: true,
fit: BoxFit.contain,
filterQuality: FilterQuality.high,
@ -1146,31 +1152,9 @@ class _PostHeadline extends StatelessWidget {
@override
Widget build(BuildContext context) {
if (isEnlarge) {
final sn = context.read<SnNetworkProvider>();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (data.preload?.thumbnail != null)
Container(
margin: const EdgeInsets.only(bottom: 8),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(8)),
border: Border.all(
color: Theme.of(context).dividerColor,
width: 1,
),
),
child: AspectRatio(
aspectRatio: 16 / 9,
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: AutoResizeUniversalImage(
sn.getAttachmentUrl(data.preload!.thumbnail!.rid),
fit: BoxFit.cover,
),
),
),
),
if (data.body['title'] != null || (title?.isNotEmpty ?? false))
Text(
title ?? data.body['title'],
@ -1255,7 +1239,7 @@ class _PostAvatar extends StatelessWidget {
: null;
return GestureDetector(
child: data.preload?.realm == null
child: data.realm == null
? AccountImage(
filterQuality: filterQuality,
content: data.publisher.avatar,
@ -1271,7 +1255,7 @@ class _PostAvatar extends StatelessWidget {
)
: AccountImage(
filterQuality: filterQuality,
content: data.preload!.realm!.avatar,
content: data.realm!.avatar,
radius: isCompact ? 12 : 20,
borderRadius: isCompact ? 4 : 8,
badgeOffset: Offset(-6, -4),
@ -1568,6 +1552,7 @@ class _PostContentHeader extends StatelessWidget {
Widget build(BuildContext context) {
if (isCompact) {
return Row(
spacing: 4,
children: [
Flexible(
child: Text(
@ -1575,7 +1560,6 @@ class _PostContentHeader extends StatelessWidget {
maxLines: 1,
).bold(),
),
const Gap(4),
Flexible(
child: Text(
isRelativeDate
@ -1587,6 +1571,10 @@ class _PostContentHeader extends StatelessWidget {
overflow: TextOverflow.fade,
).fontSize(13).opacity(0.8),
),
if (data.editedAt != null)
Flexible(
child: Text('postEditedHint').tr().fontSize(13).opacity(0.8),
)
],
);
} else {
@ -1596,20 +1584,20 @@ class _PostContentHeader extends StatelessWidget {
Row(
children: [
Text(data.publisher.nick).bold(),
if (data.preload?.realm != null)
if (data.realm != null)
const Icon(Symbols.arrow_right, size: 16)
.padding(horizontal: 2)
.opacity(0.5),
if (data.preload?.realm != null) Text(data.preload!.realm!.name),
if (data.realm != null) Text(data.realm!.name),
],
),
Row(
spacing: 4,
children: [
Text(
'@${data.publisher.name}',
maxLines: 1,
).fontSize(13),
const Gap(4),
Text(
isRelativeDate
? RelativeTime(context)
@ -1619,6 +1607,8 @@ class _PostContentHeader extends StatelessWidget {
maxLines: 1,
overflow: TextOverflow.fade,
).fontSize(13),
if (data.editedAt != null)
Text('postEditedHint').tr().fontSize(13),
],
).opacity(0.8),
],
@ -1648,7 +1638,11 @@ class _PostContentBody extends StatelessWidget {
RegExp(r"^:([-\w]+):$").hasMatch(data.body['content'] ?? ''),
textScaler: isEnlarge ? TextScaler.linear(1.1) : null,
content: text,
attachments: data.preload?.attachments,
attachments: data.body['attachments']
?.map((e) => SnAttachment.fromJson(e))
.cast<SnAttachment>()
.toList() ??
[],
);
if (isSelectable) {
@ -1706,14 +1700,14 @@ class _PostQuoteContent extends StatelessWidget {
],
).padding(horizontal: 16),
if (child.type != 'article' &&
(child.preload?.attachments?.isNotEmpty ?? false))
(child.body['attachments']?.isNotEmpty ?? false))
ClipRRect(
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(8),
bottomRight: Radius.circular(8),
),
child: AttachmentList(
data: child.preload!.attachments!,
data: child.body['attachments']!,
maxHeight: 360,
minWidth: 640,
fit: BoxFit.contain,
@ -2062,8 +2056,6 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
onTap: () {
showModalBottomSheet(
context: context,
useRootNavigator: true,
isScrollControlled: true,
builder: (context) => PostCommentListPopup(
post: widget.data,
commentCount: widget.data.metric.replyCount,
@ -2352,7 +2344,7 @@ class _PostVideoPlayer extends StatelessWidget {
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: AttachmentItem(
data: data.preload!.video!,
data: data.body['video'],
heroTag: 'post-video-${data.id}',
),
),

View File

@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 2.4.2+85
version: 2.4.2+86
environment:
sdk: ^3.5.4