Compare commits

...

7 Commits

Author SHA1 Message Date
4c9f3e799b 🐛 Fix attachment download the compressed version 2025-04-02 00:59:14 +08:00
e645db1630 🚀 Launch 2.4.2+89 Spring Boot Patch 2025-04-02 00:56:58 +08:00
d5cf2478d8 💄 Use bottom modal sheet instead of popover
 Show strike on user profile page
2025-04-02 00:52:03 +08:00
cf34a285b4 🐛 Fix attachments can't be zoom 2025-04-02 00:31:00 +08:00
a75083d916 ♻️ Improve the attachment item gesture 2025-04-01 23:48:45 +08:00
919ff5e464 💄 Optimized unread indicator 2025-04-01 22:40:43 +08:00
00863b94e8 🐛 Fix remain bugs 2025-04-01 22:36:06 +08:00
20 changed files with 611 additions and 536 deletions

View File

@ -255,8 +255,9 @@ class PostWriteController extends ChangeNotifier {
List.from(post.categories.map((ele) => ele.alias), growable: true); List.from(post.categories.map((ele) => ele.alias), growable: true);
attachments.addAll( attachments.addAll(
post.body['attachments'] post.body['attachments']
?.where((ele) => SnAttachment.fromJson(ele)) ?.map((ele) => SnAttachment.fromJson(ele))
?.map(PostWriteMedia) ?? ?.map((ele) => PostWriteMedia(ele))
?.cast<PostWriteMedia>() ??
[], [],
); );
poll = post.poll; poll = post.poll;

View File

@ -249,8 +249,11 @@ class SnNetworkProvider {
return null; return null;
} }
String getAttachmentUrl(String ky) { String getAttachmentUrl(String ky, {bool preview = true}) {
if (ky.startsWith("http")) return ky; if (ky.startsWith("http")) return ky;
if (!preview) {
return '${client.options.baseUrl}/cgi/uc/attachments/$ky?preview=false';
}
return '${client.options.baseUrl}/cgi/uc/attachments/$ky'; return '${client.options.baseUrl}/cgi/uc/attachments/$ky';
} }

View File

@ -15,6 +15,7 @@ import 'package:surface/providers/experience.dart';
import 'package:surface/providers/relationship.dart'; import 'package:surface/providers/relationship.dart';
import 'package:surface/providers/sn_network.dart'; import 'package:surface/providers/sn_network.dart';
import 'package:surface/screens/abuse_report.dart'; import 'package:surface/screens/abuse_report.dart';
import 'package:surface/screens/account/punishments.dart';
import 'package:surface/types/account.dart'; import 'package:surface/types/account.dart';
import 'package:surface/types/check_in.dart'; import 'package:surface/types/check_in.dart';
import 'package:surface/types/post.dart'; import 'package:surface/types/post.dart';
@ -457,7 +458,7 @@ class _UserScreenState extends State<UserScreen>
], ],
).padding(right: 8), ).padding(right: 8),
if (_account!.profile!.description.isNotEmpty) if (_account!.profile!.description.isNotEmpty)
const Gap(12) const Gap(4)
else else
const Gap(8), const Gap(8),
if (_account!.profile!.description.isNotEmpty) if (_account!.profile!.description.isNotEmpty)
@ -503,14 +504,15 @@ class _UserScreenState extends State<UserScreen>
], ],
).padding(vertical: 8, horizontal: 12), ).padding(vertical: 8, horizontal: 12),
), ),
const Gap(8), if (_account!.badges.isNotEmpty) const Gap(8),
Wrap( if (_account!.badges.isNotEmpty)
spacing: 4, Wrap(
runSpacing: 4, spacing: 4,
children: _account!.badges runSpacing: 4,
.map((ele) => AccountBadge(badge: ele)) children: _account!.badges
.toList(), .map((ele) => AccountBadge(badge: ele))
).padding(horizontal: 8), .toList(),
).padding(horizontal: 8),
const Gap(8), const Gap(8),
Column( Column(
children: [ children: [
@ -619,6 +621,17 @@ class _UserScreenState extends State<UserScreen>
], ],
).padding(all: 16), ).padding(all: 16),
), ),
if (_account?.punishments.isNotEmpty ?? false)
SliverToBoxAdapter(child: const Divider()),
if (_account?.punishments.isNotEmpty ?? false)
SliverToBoxAdapter(
child: Column(
children: [
for (final ele in _account!.punishments)
PunishmentInfoCard(ele: ele),
],
),
),
if (_account?.profile?.links.isNotEmpty ?? false) if (_account?.profile?.links.isNotEmpty ?? false)
SliverToBoxAdapter(child: const Divider()), SliverToBoxAdapter(child: const Divider()),
if (_account?.profile?.links.isNotEmpty ?? false) if (_account?.profile?.links.isNotEmpty ?? false)

View File

@ -107,74 +107,7 @@ class _PunishmentsScreenState extends State<PunishmentsScreen> {
itemCount: _punishments?.length ?? 0, itemCount: _punishments?.length ?? 0,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final ele = _punishments![index]; final ele = _punishments![index];
return Card( return PunishmentInfoCard(ele: ele);
margin: EdgeInsets.symmetric(horizontal: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(kPunishmentIcons[ele.type], size: 20),
const Gap(6),
Expanded(
child: Text('punishmentType${ele.type}')
.tr()
.fontSize(16)
.bold(),
),
],
),
Text(ele.reason),
const Gap(4),
Text(
'punishmentCreatedAt'.tr(args: [
DateFormat().format(
ele.createdAt.toLocal(),
)
]),
).opacity(0.8),
Text(
ele.expiredAt == null
? 'punishmentExpiredNever'.tr()
: 'punishmentExpiredAt'.tr(args: [
DateFormat().format(
ele.expiredAt!.toLocal(),
)
]),
).opacity(0.8),
const Gap(8),
if (ele.moderator != null)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('punishmentModerator').tr().opacity(0.75),
InkWell(
child: Row(
children: [
AccountImage(
content: ele.moderator!.avatar,
radius: 8,
),
const Gap(4),
Text(ele.moderator?.nick ?? 'unknown'),
],
),
onTap: () {
GoRouter.of(context).pushNamed(
'accountProfilePage',
pathParameters: {
'name': ele.moderator!.name,
},
);
},
),
],
)
else
Text('punishmentMadeBySystem').tr().opacity(0.75),
],
).padding(horizontal: 24, vertical: 16),
);
}, },
separatorBuilder: (_, __) => const Gap(8), separatorBuilder: (_, __) => const Gap(8),
), ),
@ -185,3 +118,82 @@ class _PunishmentsScreenState extends State<PunishmentsScreen> {
); );
} }
} }
class PunishmentInfoCard extends StatelessWidget {
const PunishmentInfoCard({
super.key,
required this.ele,
});
final SnPunishment ele;
@override
Widget build(BuildContext context) {
return Card(
margin: EdgeInsets.symmetric(horizontal: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(kPunishmentIcons[ele.type], size: 20),
const Gap(6),
Expanded(
child:
Text('punishmentType${ele.type}').tr().fontSize(16).bold(),
),
],
),
Text(ele.reason),
const Gap(4),
Text(
'punishmentCreatedAt'.tr(args: [
DateFormat().format(
ele.createdAt.toLocal(),
)
]),
).opacity(0.8),
Text(
ele.expiredAt == null
? 'punishmentExpiredNever'.tr()
: 'punishmentExpiredAt'.tr(args: [
DateFormat().format(
ele.expiredAt!.toLocal(),
)
]),
).opacity(0.8),
const Gap(8),
if (ele.moderator != null)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('punishmentModerator').tr().opacity(0.75),
InkWell(
child: Row(
children: [
AccountImage(
content: ele.moderator!.avatar,
radius: 8,
),
const Gap(4),
Text(ele.moderator?.nick ?? 'unknown'),
],
),
onTap: () {
GoRouter.of(context).pushNamed(
'accountProfilePage',
pathParameters: {
'name': ele.moderator!.name,
},
);
},
),
],
)
else
Text('punishmentMadeBySystem').tr().opacity(0.75),
],
).padding(horizontal: 24, vertical: 16),
);
}
}

View File

@ -171,7 +171,18 @@ class _ChatScreenState extends State<ChatScreen> {
} }
void _onTapChannel(SnChannel channel) { void _onTapChannel(SnChannel channel) {
setState(() => _unreadCounts?[channel.id] = 0); setState(() {
_unreadCounts?[channel.id] = 0;
if (channel.realmId != null) {
_unreadCountsGrouped?[channel.realmId!] =
(_unreadCountsGrouped?[channel.realmId!] ?? 0) -
(_unreadCounts?[channel.id] ?? 0);
}
if (channel.type == 1) {
_unreadCountsGrouped?[0] =
(_unreadCountsGrouped?[0] ?? 0) - (_unreadCounts?[channel.id] ?? 0);
}
});
if (ResponsiveScaffold.getIsExpand(context)) { if (ResponsiveScaffold.getIsExpand(context)) {
GoRouter.of(context).pushReplacementNamed( GoRouter.of(context).pushReplacementNamed(
'chatRoom', 'chatRoom',
@ -180,9 +191,8 @@ class _ChatScreenState extends State<ChatScreen> {
'alias': channel.alias, 'alias': channel.alias,
}, },
).then((value) { ).then((value) {
if (mounted) { if (mounted && value == true) {
setState(() => _unreadCounts?[channel.id] = 0); _refreshChannels();
_refreshChannels(noRemote: true);
} }
}); });
} else { } else {
@ -193,9 +203,8 @@ class _ChatScreenState extends State<ChatScreen> {
'alias': channel.alias, 'alias': channel.alias,
}, },
).then((value) { ).then((value) {
if (mounted) { if (mounted && value == true) {
setState(() => _unreadCounts?[channel.id] = 0); _refreshChannels();
_refreshChannels(noRemote: true);
} }
}); });
} }

View File

@ -11,7 +11,6 @@ import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/notification.dart'; import 'package:surface/providers/notification.dart';
import 'package:surface/providers/sn_network.dart'; import 'package:surface/providers/sn_network.dart';
import 'package:surface/types/notification.dart'; import 'package:surface/types/notification.dart';
import 'package:surface/widgets/app_bar_leading.dart';
import 'package:surface/widgets/dialog.dart'; import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/loading_indicator.dart'; import 'package:surface/widgets/loading_indicator.dart';
import 'package:surface/widgets/markdown_content.dart'; import 'package:surface/widgets/markdown_content.dart';
@ -156,7 +155,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
return AppScaffold( return AppScaffold(
appBar: AppBar( appBar: AppBar(
leading: AutoAppBarLeading(), leading: PageBackButton(),
title: Text('screenNotification').tr(), title: Text('screenNotification').tr(),
actions: [ actions: [
IconButton( IconButton(

View File

@ -22,6 +22,7 @@ abstract class SnAccount with _$SnAccount {
required String language, required String language,
required SnAccountProfile? profile, required SnAccountProfile? profile,
@Default([]) List<SnAccountBadge> badges, @Default([]) List<SnAccountBadge> badges,
@Default([]) List<SnPunishment> punishments,
required DateTime? suspendedAt, required DateTime? suspendedAt,
required int? affiliatedId, required int? affiliatedId,
required int? affiliatedTo, required int? affiliatedTo,

View File

@ -29,6 +29,7 @@ mixin _$SnAccount {
String get language; String get language;
SnAccountProfile? get profile; SnAccountProfile? get profile;
List<SnAccountBadge> get badges; List<SnAccountBadge> get badges;
List<SnPunishment> get punishments;
DateTime? get suspendedAt; DateTime? get suspendedAt;
int? get affiliatedId; int? get affiliatedId;
int? get affiliatedTo; int? get affiliatedTo;
@ -69,6 +70,8 @@ mixin _$SnAccount {
other.language == language) && other.language == language) &&
(identical(other.profile, profile) || other.profile == profile) && (identical(other.profile, profile) || other.profile == profile) &&
const DeepCollectionEquality().equals(other.badges, badges) && const DeepCollectionEquality().equals(other.badges, badges) &&
const DeepCollectionEquality()
.equals(other.punishments, punishments) &&
(identical(other.suspendedAt, suspendedAt) || (identical(other.suspendedAt, suspendedAt) ||
other.suspendedAt == suspendedAt) && other.suspendedAt == suspendedAt) &&
(identical(other.affiliatedId, affiliatedId) || (identical(other.affiliatedId, affiliatedId) ||
@ -99,6 +102,7 @@ mixin _$SnAccount {
language, language,
profile, profile,
const DeepCollectionEquality().hash(badges), const DeepCollectionEquality().hash(badges),
const DeepCollectionEquality().hash(punishments),
suspendedAt, suspendedAt,
affiliatedId, affiliatedId,
affiliatedTo, affiliatedTo,
@ -108,7 +112,7 @@ mixin _$SnAccount {
@override @override
String toString() { String toString() {
return 'SnAccount(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, confirmedAt: $confirmedAt, contacts: $contacts, avatar: $avatar, banner: $banner, name: $name, nick: $nick, permNodes: $permNodes, language: $language, profile: $profile, badges: $badges, suspendedAt: $suspendedAt, affiliatedId: $affiliatedId, affiliatedTo: $affiliatedTo, automatedBy: $automatedBy, automatedId: $automatedId)'; return 'SnAccount(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, confirmedAt: $confirmedAt, contacts: $contacts, avatar: $avatar, banner: $banner, name: $name, nick: $nick, permNodes: $permNodes, language: $language, profile: $profile, badges: $badges, punishments: $punishments, suspendedAt: $suspendedAt, affiliatedId: $affiliatedId, affiliatedTo: $affiliatedTo, automatedBy: $automatedBy, automatedId: $automatedId)';
} }
} }
@ -132,6 +136,7 @@ abstract mixin class $SnAccountCopyWith<$Res> {
String language, String language,
SnAccountProfile? profile, SnAccountProfile? profile,
List<SnAccountBadge> badges, List<SnAccountBadge> badges,
List<SnPunishment> punishments,
DateTime? suspendedAt, DateTime? suspendedAt,
int? affiliatedId, int? affiliatedId,
int? affiliatedTo, int? affiliatedTo,
@ -167,6 +172,7 @@ class _$SnAccountCopyWithImpl<$Res> implements $SnAccountCopyWith<$Res> {
Object? language = null, Object? language = null,
Object? profile = freezed, Object? profile = freezed,
Object? badges = null, Object? badges = null,
Object? punishments = null,
Object? suspendedAt = freezed, Object? suspendedAt = freezed,
Object? affiliatedId = freezed, Object? affiliatedId = freezed,
Object? affiliatedTo = freezed, Object? affiliatedTo = freezed,
@ -230,6 +236,10 @@ class _$SnAccountCopyWithImpl<$Res> implements $SnAccountCopyWith<$Res> {
? _self.badges ? _self.badges
: badges // ignore: cast_nullable_to_non_nullable : badges // ignore: cast_nullable_to_non_nullable
as List<SnAccountBadge>, as List<SnAccountBadge>,
punishments: null == punishments
? _self.punishments
: punishments // ignore: cast_nullable_to_non_nullable
as List<SnPunishment>,
suspendedAt: freezed == suspendedAt suspendedAt: freezed == suspendedAt
? _self.suspendedAt ? _self.suspendedAt
: suspendedAt // ignore: cast_nullable_to_non_nullable : suspendedAt // ignore: cast_nullable_to_non_nullable
@ -286,6 +296,7 @@ class _SnAccount extends SnAccount {
required this.language, required this.language,
required this.profile, required this.profile,
final List<SnAccountBadge> badges = const [], final List<SnAccountBadge> badges = const [],
final List<SnPunishment> punishments = const [],
required this.suspendedAt, required this.suspendedAt,
required this.affiliatedId, required this.affiliatedId,
required this.affiliatedTo, required this.affiliatedTo,
@ -294,6 +305,7 @@ class _SnAccount extends SnAccount {
: _contacts = contacts, : _contacts = contacts,
_permNodes = permNodes, _permNodes = permNodes,
_badges = badges, _badges = badges,
_punishments = punishments,
super._(); super._();
factory _SnAccount.fromJson(Map<String, dynamic> json) => factory _SnAccount.fromJson(Map<String, dynamic> json) =>
_$SnAccountFromJson(json); _$SnAccountFromJson(json);
@ -350,6 +362,15 @@ class _SnAccount extends SnAccount {
return EqualUnmodifiableListView(_badges); return EqualUnmodifiableListView(_badges);
} }
final List<SnPunishment> _punishments;
@override
@JsonKey()
List<SnPunishment> get punishments {
if (_punishments is EqualUnmodifiableListView) return _punishments;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_punishments);
}
@override @override
final DateTime? suspendedAt; final DateTime? suspendedAt;
@override @override
@ -401,6 +422,8 @@ class _SnAccount extends SnAccount {
other.language == language) && other.language == language) &&
(identical(other.profile, profile) || other.profile == profile) && (identical(other.profile, profile) || other.profile == profile) &&
const DeepCollectionEquality().equals(other._badges, _badges) && const DeepCollectionEquality().equals(other._badges, _badges) &&
const DeepCollectionEquality()
.equals(other._punishments, _punishments) &&
(identical(other.suspendedAt, suspendedAt) || (identical(other.suspendedAt, suspendedAt) ||
other.suspendedAt == suspendedAt) && other.suspendedAt == suspendedAt) &&
(identical(other.affiliatedId, affiliatedId) || (identical(other.affiliatedId, affiliatedId) ||
@ -431,6 +454,7 @@ class _SnAccount extends SnAccount {
language, language,
profile, profile,
const DeepCollectionEquality().hash(_badges), const DeepCollectionEquality().hash(_badges),
const DeepCollectionEquality().hash(_punishments),
suspendedAt, suspendedAt,
affiliatedId, affiliatedId,
affiliatedTo, affiliatedTo,
@ -440,7 +464,7 @@ class _SnAccount extends SnAccount {
@override @override
String toString() { String toString() {
return 'SnAccount(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, confirmedAt: $confirmedAt, contacts: $contacts, avatar: $avatar, banner: $banner, name: $name, nick: $nick, permNodes: $permNodes, language: $language, profile: $profile, badges: $badges, suspendedAt: $suspendedAt, affiliatedId: $affiliatedId, affiliatedTo: $affiliatedTo, automatedBy: $automatedBy, automatedId: $automatedId)'; return 'SnAccount(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, confirmedAt: $confirmedAt, contacts: $contacts, avatar: $avatar, banner: $banner, name: $name, nick: $nick, permNodes: $permNodes, language: $language, profile: $profile, badges: $badges, punishments: $punishments, suspendedAt: $suspendedAt, affiliatedId: $affiliatedId, affiliatedTo: $affiliatedTo, automatedBy: $automatedBy, automatedId: $automatedId)';
} }
} }
@ -467,6 +491,7 @@ abstract mixin class _$SnAccountCopyWith<$Res>
String language, String language,
SnAccountProfile? profile, SnAccountProfile? profile,
List<SnAccountBadge> badges, List<SnAccountBadge> badges,
List<SnPunishment> punishments,
DateTime? suspendedAt, DateTime? suspendedAt,
int? affiliatedId, int? affiliatedId,
int? affiliatedTo, int? affiliatedTo,
@ -503,6 +528,7 @@ class __$SnAccountCopyWithImpl<$Res> implements _$SnAccountCopyWith<$Res> {
Object? language = null, Object? language = null,
Object? profile = freezed, Object? profile = freezed,
Object? badges = null, Object? badges = null,
Object? punishments = null,
Object? suspendedAt = freezed, Object? suspendedAt = freezed,
Object? affiliatedId = freezed, Object? affiliatedId = freezed,
Object? affiliatedTo = freezed, Object? affiliatedTo = freezed,
@ -566,6 +592,10 @@ class __$SnAccountCopyWithImpl<$Res> implements _$SnAccountCopyWith<$Res> {
? _self._badges ? _self._badges
: badges // ignore: cast_nullable_to_non_nullable : badges // ignore: cast_nullable_to_non_nullable
as List<SnAccountBadge>, as List<SnAccountBadge>,
punishments: null == punishments
? _self._punishments
: punishments // ignore: cast_nullable_to_non_nullable
as List<SnPunishment>,
suspendedAt: freezed == suspendedAt suspendedAt: freezed == suspendedAt
? _self.suspendedAt ? _self.suspendedAt
: suspendedAt // ignore: cast_nullable_to_non_nullable : suspendedAt // ignore: cast_nullable_to_non_nullable

View File

@ -32,6 +32,10 @@ _SnAccount _$SnAccountFromJson(Map<String, dynamic> json) => _SnAccount(
?.map((e) => SnAccountBadge.fromJson(e as Map<String, dynamic>)) ?.map((e) => SnAccountBadge.fromJson(e as Map<String, dynamic>))
.toList() ?? .toList() ??
const [], const [],
punishments: (json['punishments'] as List<dynamic>?)
?.map((e) => SnPunishment.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
suspendedAt: json['suspended_at'] == null suspendedAt: json['suspended_at'] == null
? null ? null
: DateTime.parse(json['suspended_at'] as String), : DateTime.parse(json['suspended_at'] as String),
@ -57,6 +61,7 @@ Map<String, dynamic> _$SnAccountToJson(_SnAccount instance) =>
'language': instance.language, 'language': instance.language,
'profile': instance.profile?.toJson(), 'profile': instance.profile?.toJson(),
'badges': instance.badges.map((e) => e.toJson()).toList(), 'badges': instance.badges.map((e) => e.toJson()).toList(),
'punishments': instance.punishments.map((e) => e.toJson()).toList(),
'suspended_at': instance.suspendedAt?.toIso8601String(), 'suspended_at': instance.suspendedAt?.toIso8601String(),
'affiliated_id': instance.affiliatedId, 'affiliated_id': instance.affiliatedId,
'affiliated_to': instance.affiliatedTo, 'affiliated_to': instance.affiliatedTo,

View File

@ -22,142 +22,151 @@ class AccountPopoverCard extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final sn = context.read<SnNetworkProvider>(); final sn = context.read<SnNetworkProvider>();
return Column( return SingleChildScrollView(
crossAxisAlignment: CrossAxisAlignment.start, child: Column(
mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (data.banner.isNotEmpty) if (data.banner.isNotEmpty)
Container( ClipRRect(
color: Theme.of(context).colorScheme.surfaceContainer, borderRadius: BorderRadius.circular(16),
child: AspectRatio( child: Container(
aspectRatio: 16 / 7, color: Theme.of(context).colorScheme.surfaceContainer,
child: AutoResizeUniversalImage( child: AspectRatio(
sn.getAttachmentUrl(data.banner), aspectRatio: 16 / 7,
fit: BoxFit.cover, child: AutoResizeUniversalImage(
), sn.getAttachmentUrl(data.banner),
), fit: BoxFit.cover,
), ),
// Top padding
Gap(16),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AccountImage(
content: data.avatar,
radius: 20,
),
Gap(16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(data.nick).bold(),
Text('@${data.name}').fontSize(13).opacity(0.75),
],
),
),
IconButton(
onPressed: () {
Navigator.pop(context);
GoRouter.of(context).pushNamed(
'accountProfilePage',
pathParameters: {'name': data.name},
);
},
icon: const Icon(Symbols.chevron_right),
padding: EdgeInsets.zero,
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
),
const Gap(8)
],
).padding(horizontal: 16),
if (data.badges.isNotEmpty)
Wrap(
spacing: 4,
children: data.badges
.map(
(ele) => AccountBadge(badge: ele),
)
.toList(),
).padding(horizontal: 24, bottom: 12, top: 12),
if (data.profile?.description.isNotEmpty ?? false)
Text(
data.profile?.description ?? '',
maxLines: 2,
overflow: TextOverflow.ellipsis,
).padding(horizontal: 26, bottom: 8),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(Symbols.star),
const Gap(8),
Text('Lv${getLevelFromExp(data.profile?.experience ?? 0)}'),
const Gap(8),
Text(calcLevelUpProgressLevel(data.profile?.experience ?? 0))
.fontSize(11)
.opacity(0.5),
const Gap(8),
Container(
width: double.infinity,
constraints: const BoxConstraints(maxWidth: 160),
child: LinearProgressIndicator(
value: calcLevelUpProgress(data.profile?.experience ?? 0),
borderRadius: BorderRadius.circular(8),
backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
).alignment(Alignment.centerLeft),
),
],
).padding(horizontal: 24),
FutureBuilder(
future: sn.client.get('/cgi/id/users/${data.name}/status'),
builder: (context, snapshot) {
final SnAccountStatusInfo? status = snapshot.hasData
? SnAccountStatusInfo.fromJson(snapshot.data!.data)
: null;
return Row(
children: [
Icon(
(status?.isDisturbable ?? true)
? Symbols.circle
: Symbols.do_not_disturb_on,
fill: (status?.isOnline ?? false) ? 1 : 0,
size: 16,
color: (status?.isOnline ?? false)
? (status?.isDisturbable ?? true)
? Colors.green
: Colors.red
: Colors.grey,
).padding(all: 4),
const Gap(8),
Text(
status != null
? (status.status?.label.isNotEmpty ?? false)
? status.status!.label
: status.isOnline
? 'accountStatusOnline'.tr()
: 'accountStatusOffline'.tr()
: 'loading'.tr(),
), ),
if (status != null && ),
!status.isOnline && ).padding(all: 16)
status.lastSeenAt != null) else
const Gap(16),
// Top padding
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AccountImage(
content: data.avatar,
radius: 20,
),
Gap(16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(data.nick).bold(),
Text('@${data.name}').fontSize(13).opacity(0.75),
],
),
),
IconButton(
onPressed: () {
Navigator.pop(context);
GoRouter.of(context).pushReplacementNamed(
'accountProfilePage',
pathParameters: {'name': data.name},
);
},
icon: const Icon(Symbols.chevron_right),
padding: EdgeInsets.zero,
visualDensity:
const VisualDensity(horizontal: -4, vertical: -4),
),
const Gap(8)
],
).padding(horizontal: 16),
if (data.badges.isNotEmpty)
Wrap(
spacing: 4,
children: data.badges
.map(
(ele) => AccountBadge(badge: ele),
)
.toList(),
).padding(horizontal: 24, bottom: 12, top: 12),
if (data.profile?.description.isNotEmpty ?? false)
Text(
data.profile?.description ?? '',
maxLines: 2,
overflow: TextOverflow.ellipsis,
).padding(horizontal: 26, bottom: 8)
else
const Gap(12),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(Symbols.star),
const Gap(8),
Text('Lv${getLevelFromExp(data.profile?.experience ?? 0)}'),
const Gap(8),
Text(calcLevelUpProgressLevel(data.profile?.experience ?? 0))
.fontSize(11)
.opacity(0.5),
const Gap(8),
Container(
width: double.infinity,
constraints: const BoxConstraints(maxWidth: 160),
child: LinearProgressIndicator(
value: calcLevelUpProgress(data.profile?.experience ?? 0),
borderRadius: BorderRadius.circular(8),
backgroundColor:
Theme.of(context).colorScheme.surfaceContainer,
).alignment(Alignment.centerLeft),
),
],
).padding(horizontal: 24),
FutureBuilder(
future: sn.client.get('/cgi/id/users/${data.name}/status'),
builder: (context, snapshot) {
final SnAccountStatusInfo? status = snapshot.hasData
? SnAccountStatusInfo.fromJson(snapshot.data!.data)
: null;
return Row(
children: [
Icon(
(status?.isDisturbable ?? true)
? Symbols.circle
: Symbols.do_not_disturb_on,
fill: (status?.isOnline ?? false) ? 1 : 0,
size: 16,
color: (status?.isOnline ?? false)
? (status?.isDisturbable ?? true)
? Colors.green
: Colors.red
: Colors.grey,
).padding(all: 4),
const Gap(8),
Text( Text(
'accountStatusLastSeen'.tr(args: [ status != null
status.lastSeenAt != null ? (status.status?.label.isNotEmpty ?? false)
? RelativeTime(context).format( ? status.status!.label
status.lastSeenAt!.toLocal(), : status.isOnline
) ? 'accountStatusOnline'.tr()
: 'unknown', : 'accountStatusOffline'.tr()
]), : 'loading'.tr(),
).padding(left: 6).opacity(0.75), ),
], if (status != null &&
).padding(horizontal: 24); !status.isOnline &&
}, status.lastSeenAt != null)
), Text(
// Bottom padding 'accountStatusLastSeen'.tr(args: [
const Gap(16), status.lastSeenAt != null
], ? RelativeTime(context).format(
status.lastSeenAt!.toLocal(),
)
: 'unknown',
]),
).padding(left: 6).opacity(0.75),
],
).padding(horizontal: 24);
},
),
// Bottom padding
const Gap(64),
],
),
); );
} }
} }

View File

@ -25,6 +25,7 @@ class AttachmentItem extends StatelessWidget {
final String? heroTag; final String? heroTag;
final BoxFit fit; final BoxFit fit;
final FilterQuality? filterQuality; final FilterQuality? filterQuality;
final Function? onZoom;
const AttachmentItem({ const AttachmentItem({
super.key, super.key,
@ -32,6 +33,7 @@ class AttachmentItem extends StatelessWidget {
required this.data, required this.data,
required this.heroTag, required this.heroTag,
this.filterQuality, this.filterQuality,
this.onZoom,
}); });
Widget _buildContent(BuildContext context) { Widget _buildContent(BuildContext context) {
@ -94,7 +96,14 @@ class AttachmentItem extends StatelessWidget {
}); });
} }
return _buildContent(context); return GestureDetector(
child: _buildContent(context),
onTap: () {
if (data?.mimetype.startsWith('image') ?? false) {
onZoom?.call();
}
},
);
} }
} }

View File

@ -74,40 +74,35 @@ class _AttachmentListState extends State<AttachmentList> {
return Container( return Container(
padding: widget.padding ?? EdgeInsets.zero, padding: widget.padding ?? EdgeInsets.zero,
constraints: constraints, constraints: constraints,
child: GestureDetector( child: AspectRatio(
child: AspectRatio( aspectRatio: singleAspectRatio,
aspectRatio: singleAspectRatio, child: Container(
child: Container( decoration: BoxDecoration(
decoration: BoxDecoration( color: backgroundColor,
color: backgroundColor, border: Border.fromBorderSide(borderSide),
border: Border.fromBorderSide(borderSide), borderRadius: AttachmentList.kDefaultRadius,
borderRadius: AttachmentList.kDefaultRadius, ),
), child: ClipRRect(
child: ClipRRect( borderRadius: AttachmentList.kDefaultRadius,
borderRadius: AttachmentList.kDefaultRadius, child: AttachmentItem(
child: AttachmentItem( data: widget.data[0],
data: widget.data[0], heroTag: heroTags[0],
heroTag: heroTags[0], fit: widget.fit,
fit: widget.fit, filterQuality: widget.filterQuality,
filterQuality: widget.filterQuality, onZoom: () {
), context.pushTransparentRoute(
AttachmentZoomView(
data: widget.data.where((ele) => ele != null).cast(),
initialIndex: 0,
heroTags: heroTags,
),
backgroundColor: Colors.black.withOpacity(0.7),
rootNavigator: true,
);
},
), ),
), ),
), ),
onTap: () {
if (widget.data.firstOrNull?.mediaType != SnMediaType.image) {
return;
}
context.pushTransparentRoute(
AttachmentZoomView(
data: widget.data.where((ele) => ele != null).cast(),
initialIndex: 0,
heroTags: heroTags,
),
backgroundColor: Colors.black.withOpacity(0.7),
rootNavigator: true,
);
},
), ),
); );
} }
@ -133,33 +128,27 @@ class _AttachmentListState extends State<AttachmentList> {
mainAxisSpacing: 4, mainAxisSpacing: 4,
children: widget.data children: widget.data
.mapIndexed( .mapIndexed(
(idx, ele) => GestureDetector( (idx, ele) => Container(
child: Container( constraints: constraints,
constraints: constraints, child: AttachmentItem(
child: AttachmentItem( data: ele,
data: ele, heroTag: heroTags[idx],
heroTag: heroTags[idx], fit: BoxFit.cover,
fit: BoxFit.cover, filterQuality: widget.filterQuality,
filterQuality: widget.filterQuality, onZoom: () {
), context.pushTransparentRoute(
AttachmentZoomView(
data: widget.data
.where((ele) => ele != null)
.cast(),
initialIndex: idx,
heroTags: heroTags,
),
backgroundColor: Colors.black.withOpacity(0.7),
rootNavigator: true,
);
},
), ),
onTap: () {
if (widget.data[idx]!.mediaType !=
SnMediaType.image) {
return;
}
context.pushTransparentRoute(
AttachmentZoomView(
data: widget.data
.where((ele) => ele != null)
.cast(),
initialIndex: idx,
heroTags: heroTags,
),
backgroundColor: Colors.black.withOpacity(0.7),
rootNavigator: true,
);
},
), ),
) )
.toList(), .toList(),
@ -181,17 +170,30 @@ class _AttachmentListState extends State<AttachmentList> {
child: Column( child: Column(
children: widget.data children: widget.data
.mapIndexed( .mapIndexed(
(idx, ele) => GestureDetector( (idx, ele) => AspectRatio(
child: AspectRatio( aspectRatio: ele?.data['ratio']?.toDouble() ?? 1,
aspectRatio: ele?.data['ratio']?.toDouble() ?? 1, child: Container(
child: Container( constraints: constraints,
constraints: constraints, child: AttachmentItem(
child: AttachmentItem( data: ele,
data: ele, heroTag: heroTags[idx],
heroTag: heroTags[idx], fit: BoxFit.cover,
fit: BoxFit.cover, filterQuality: widget.filterQuality,
filterQuality: widget.filterQuality, onZoom: () {
), context.pushTransparentRoute(
AttachmentZoomView(
data: widget.data
.where((ele) =>
ele != null &&
ele.mediaType == SnMediaType.image)
.cast(),
initialIndex: idx,
heroTags: heroTags,
),
backgroundColor: Colors.black.withOpacity(0.7),
rootNavigator: true,
);
},
), ),
), ),
), ),
@ -222,56 +224,52 @@ class _AttachmentListState extends State<AttachmentList> {
child: AspectRatio( child: AspectRatio(
aspectRatio: aspectRatio:
(widget.data[idx]?.data['ratio'] ?? 1).toDouble(), (widget.data[idx]?.data['ratio'] ?? 1).toDouble(),
child: GestureDetector( child: Stack(
onTap: () { fit: StackFit.expand,
if (widget.data[idx]?.mediaType != children: [
SnMediaType.image) { Container(
return; decoration: BoxDecoration(
} color: backgroundColor,
context.pushTransparentRoute( border: Border.all(
AttachmentZoomView( width: 1,
data: widget.data color: Theme.of(context).dividerColor,
.where((ele) =>
ele != null &&
ele.mediaType == SnMediaType.image)
.cast(),
initialIndex: idx,
heroTags: heroTags,
),
backgroundColor: Colors.black.withOpacity(0.7),
rootNavigator: true,
);
},
child: Stack(
fit: StackFit.expand,
children: [
Container(
decoration: BoxDecoration(
color: backgroundColor,
border: Border.all(
width: 1,
color: Theme.of(context).dividerColor,
),
borderRadius: AttachmentList.kDefaultRadius,
), ),
child: ClipRRect( borderRadius: AttachmentList.kDefaultRadius,
borderRadius: AttachmentList.kDefaultRadius, ),
child: AttachmentItem( child: ClipRRect(
data: widget.data[idx], borderRadius: AttachmentList.kDefaultRadius,
heroTag: heroTags[idx], child: AttachmentItem(
filterQuality: widget.filterQuality, data: widget.data[idx],
), heroTag: heroTags[idx],
filterQuality: widget.filterQuality,
onZoom: () {
context.pushTransparentRoute(
AttachmentZoomView(
data: widget.data
.where((ele) =>
ele != null &&
ele.mediaType ==
SnMediaType.image)
.cast(),
initialIndex: idx,
heroTags: heroTags,
),
backgroundColor:
Colors.black.withOpacity(0.7),
rootNavigator: true,
);
},
), ),
), ),
Positioned( ),
right: 8, Positioned(
bottom: 8, right: 8,
child: Chip( bottom: 8,
label: Text('${idx + 1}/${widget.data.length}'), child: Chip(
), label: Text('${idx + 1}/${widget.data.length}'),
), ),
], ),
), ],
), ),
), ),
); );

View File

@ -64,7 +64,7 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
Future<void> _saveToAlbum(int idx) async { Future<void> _saveToAlbum(int idx) async {
final sn = context.read<SnNetworkProvider>(); final sn = context.read<SnNetworkProvider>();
final item = widget.data.elementAt(idx); final item = widget.data.elementAt(idx);
final url = sn.getAttachmentUrl(item.rid); final url = sn.getAttachmentUrl(item.rid, preview: false);
if (kIsWeb || Platform.isLinux) { if (kIsWeb || Platform.isLinux) {
await launchUrlString(url); await launchUrlString(url);
@ -181,7 +181,10 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
scaleState == PhotoViewScaleState.initial); scaleState == PhotoViewScaleState.initial);
}, },
imageProvider: UniversalImage.provider( imageProvider: UniversalImage.provider(
sn.getAttachmentUrl(widget.data.first.rid), sn.getAttachmentUrl(
widget.data.first.rid,
preview: false,
),
), ),
), ),
); );
@ -199,7 +202,10 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
widget.heroTags?.elementAt(idx) ?? uuid.v4(); widget.heroTags?.elementAt(idx) ?? uuid.v4();
return PhotoViewGalleryPageOptions( return PhotoViewGalleryPageOptions(
imageProvider: UniversalImage.provider( imageProvider: UniversalImage.provider(
sn.getAttachmentUrl(widget.data.elementAt(idx).rid), sn.getAttachmentUrl(
widget.data.elementAt(idx).rid,
preview: false,
),
), ),
heroAttributes: PhotoViewHeroAttributes( heroAttributes: PhotoViewHeroAttributes(
tag: 'attachment-${widget.data.first.rid}-$heroTag', tag: 'attachment-${widget.data.first.rid}-$heroTag',

View File

@ -1,11 +1,8 @@
import 'dart:math' as math;
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_context_menu/flutter_context_menu.dart'; import 'package:flutter_context_menu/flutter_context_menu.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:popover/popover.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/config.dart'; import 'package:surface/providers/config.dart';
@ -120,20 +117,9 @@ class ChatMessage extends StatelessWidget {
), ),
onTap: () { onTap: () {
if (user == null) return; if (user == null) return;
showPopover( showModalBottomSheet(
backgroundColor:
Theme.of(context).colorScheme.surface,
context: context, context: context,
transition: PopoverTransition.other, builder: (context) => AccountPopoverCard(data: user),
bodyBuilder: (context) => SizedBox(
width: math.min(
400, MediaQuery.of(context).size.width - 10),
child: AccountPopoverCard(data: user),
),
direction: PopoverDirection.bottom,
arrowHeight: 5,
arrowWidth: 15,
arrowDxOffset: -190,
); );
}, },
) )

View File

@ -100,6 +100,7 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
contentPadding: EdgeInsets.symmetric(horizontal: 24), contentPadding: EdgeInsets.symmetric(horizontal: 24),
leading: AccountImage( leading: AccountImage(
content: ua.user?.avatar, content: ua.user?.avatar,
backgroundColor: Colors.transparent,
fallbackWidget: fallbackWidget:
ua.isAuthorized ? null : const Icon(Symbols.login), ua.isAuthorized ? null : const Icon(Symbols.login),
), ),
@ -122,15 +123,6 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
Scaffold.of(context).closeDrawer(); Scaffold.of(context).closeDrawer();
}, },
), ),
IconButton(
icon: const Icon(Symbols.settings, fill: 1),
padding: EdgeInsets.zero,
visualDensity: VisualDensity.compact,
onPressed: () {
GoRouter.of(context).pushNamed('settings');
Scaffold.of(context).closeDrawer();
},
),
], ],
), ),
onTap: () { onTap: () {

View File

@ -12,7 +12,6 @@ import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:popover/popover.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:qr_flutter/qr_flutter.dart'; import 'package:qr_flutter/qr_flutter.dart';
import 'package:relative_time/relative_time.dart'; import 'package:relative_time/relative_time.dart';
@ -1274,20 +1273,11 @@ class _PostAvatar extends StatelessWidget {
), ),
), ),
onTap: () { onTap: () {
showPopover( showModalBottomSheet(
backgroundColor: Theme.of(context).colorScheme.surface,
context: context, context: context,
transition: PopoverTransition.other, builder: (context) => PublisherPopoverCard(
bodyBuilder: (context) => SizedBox( data: data.publisher,
width: math.min(400, MediaQuery.of(context).size.width - 10),
child: PublisherPopoverCard(
data: data.publisher,
),
), ),
direction: PopoverDirection.bottom,
arrowHeight: 5,
arrowWidth: 15,
arrowDxOffset: -190,
); );
}, },
); );

View File

@ -24,125 +24,137 @@ class PublisherPopoverCard extends StatelessWidget {
final user = data.type == 0 ? ud.getFromCache(data.accountId) : null; final user = data.type == 0 ? ud.getFromCache(data.accountId) : null;
return Column( return SingleChildScrollView(
crossAxisAlignment: CrossAxisAlignment.start, child: Column(
mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (data.banner.isNotEmpty) if (data.banner.isNotEmpty)
Container( ClipRRect(
color: Theme.of(context).colorScheme.surfaceContainer, borderRadius: BorderRadius.circular(16),
child: AspectRatio( child: Container(
aspectRatio: 16 / 7, color: Theme.of(context).colorScheme.surfaceContainer,
child: AutoResizeUniversalImage( child: AspectRatio(
sn.getAttachmentUrl(data.banner), aspectRatio: 16 / 7,
fit: BoxFit.cover, child: AutoResizeUniversalImage(
sn.getAttachmentUrl(data.banner),
fit: BoxFit.cover,
),
),
), ),
), ).padding(all: 16)
), else
// Top padding // Top padding
Gap(16),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AccountImage(
content: data.avatar,
radius: 20,
borderRadius: data.type == 1 ? 8 : 20,
),
Gap(16), Gap(16),
Expanded( Row(
child: Column( crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, children: [
mainAxisSize: MainAxisSize.min, AccountImage(
children: [ content: data.avatar,
Text(data.nick).bold(), radius: 20,
Text('@${data.name}').fontSize(13).opacity(0.75), borderRadius: data.type == 1 ? 8 : 20,
],
), ),
), Gap(16),
IconButton( Expanded(
onPressed: () { child: Column(
Navigator.pop(context); crossAxisAlignment: CrossAxisAlignment.start,
GoRouter.of(context).pushNamed( mainAxisSize: MainAxisSize.min,
'postPublisher', children: [
pathParameters: {'name': data.name}, Text(data.nick).bold(),
); Text('@${data.name}').fontSize(13).opacity(0.75),
}, ],
icon: const Icon(Symbols.chevron_right), ),
padding: EdgeInsets.zero,
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
),
const Gap(8)
],
).padding(horizontal: 16),
if (user != null && user.badges.isNotEmpty)
Wrap(
spacing: 4,
children: user.badges
.map(
(ele) => AccountBadge(badge: ele),
)
.toList(),
).padding(horizontal: 24, top: 16),
const Gap(16),
if (data.description.isNotEmpty)
Text(
data.description,
maxLines: 2,
overflow: TextOverflow.ellipsis,
).padding(horizontal: 26, bottom: 20),
Row(
children: [
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text('publisherSocialPoint').tr().fontSize(13).opacity(0.75),
Text((data.totalUpvote - data.totalDownvote).toString()),
],
), ),
), IconButton(
SizedBox( onPressed: () {
height: 20, Navigator.pop(context);
child: const VerticalDivider( GoRouter.of(context).pushNamed(
thickness: 1, 'postPublisher',
pathParameters: {'name': data.name},
);
},
icon: const Icon(Symbols.chevron_right),
padding: EdgeInsets.zero,
visualDensity:
const VisualDensity(horizontal: -4, vertical: -4),
), ),
).padding(horizontal: 8), const Gap(8)
Expanded( ],
child: Column( ).padding(horizontal: 16),
mainAxisSize: MainAxisSize.min, if (user != null && user.badges.isNotEmpty)
crossAxisAlignment: CrossAxisAlignment.center, Wrap(
children: [ spacing: 4,
Text('publisherTotalUpvote').tr().fontSize(13).opacity(0.75), children: user.badges
Text(data.totalUpvote.toString()), .map(
], (ele) => AccountBadge(badge: ele),
)
.toList(),
).padding(horizontal: 24, top: 16),
const Gap(16),
if (data.description.isNotEmpty)
Text(
data.description,
maxLines: 2,
overflow: TextOverflow.ellipsis,
).padding(horizontal: 26, bottom: 20),
Row(
children: [
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text('publisherSocialPoint')
.tr()
.fontSize(13)
.opacity(0.75),
Text((data.totalUpvote - data.totalDownvote).toString()),
],
),
), ),
), SizedBox(
SizedBox( height: 20,
height: 20, child: const VerticalDivider(
child: const VerticalDivider( thickness: 1,
thickness: 1, ),
).padding(horizontal: 8),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text('publisherTotalUpvote')
.tr()
.fontSize(13)
.opacity(0.75),
Text(data.totalUpvote.toString()),
],
),
), ),
).padding(horizontal: 8), SizedBox(
Expanded( height: 20,
child: Column( child: const VerticalDivider(
mainAxisSize: MainAxisSize.min, thickness: 1,
crossAxisAlignment: CrossAxisAlignment.center, ),
children: [ ).padding(horizontal: 8),
Text('publisherTotalDownvote') Expanded(
.tr() child: Column(
.fontSize(13) mainAxisSize: MainAxisSize.min,
.opacity(0.75), crossAxisAlignment: CrossAxisAlignment.center,
Text(data.totalDownvote.toString()), children: [
], Text('publisherTotalDownvote')
.tr()
.fontSize(13)
.opacity(0.75),
Text(data.totalDownvote.toString()),
],
),
), ),
), ],
], ).padding(horizontal: 16),
).padding(horizontal: 16), // Bottom padding
// Bottom padding const Gap(64),
const Gap(16), ],
], ),
); );
} }
} }

View File

@ -25,7 +25,7 @@ class VersionUpdatePopup extends StatelessWidget {
if (Platform.isAndroid) { if (Platform.isAndroid) {
final model = UpdateModel( final model = UpdateModel(
'https://files.solsynth.dev/d/production01/solian/app-arm64-v8a-release.apk', 'https://files.solsynth.dev/d/c1/solian/app-arm64-v8a-release.apk',
'solian-app-release-${config.updatableVersion!}.apk', 'solian-app-release-${config.updatableVersion!}.apk',
'ic_launcher', 'ic_launcher',
'https://apps.apple.com/us/app/solian/id6499032345', 'https://apps.apple.com/us/app/solian/id6499032345',

View File

@ -17,59 +17,59 @@ PODS:
- FlutterMacOS - FlutterMacOS
- file_selector_macos (0.0.1): - file_selector_macos (0.0.1):
- FlutterMacOS - FlutterMacOS
- Firebase/Analytics (11.8.0): - Firebase/Analytics (11.10.0):
- Firebase/Core - Firebase/Core
- Firebase/Core (11.8.0): - Firebase/Core (11.10.0):
- Firebase/CoreOnly - Firebase/CoreOnly
- FirebaseAnalytics (~> 11.8.0) - FirebaseAnalytics (~> 11.10.0)
- Firebase/CoreOnly (11.8.0): - Firebase/CoreOnly (11.10.0):
- FirebaseCore (~> 11.8.0) - FirebaseCore (~> 11.10.0)
- Firebase/Messaging (11.8.0): - Firebase/Messaging (11.10.0):
- Firebase/CoreOnly - Firebase/CoreOnly
- FirebaseMessaging (~> 11.8.0) - FirebaseMessaging (~> 11.10.0)
- firebase_analytics (11.4.4): - firebase_analytics (11.4.5):
- Firebase/Analytics (= 11.8.0) - Firebase/Analytics (= 11.10.0)
- firebase_core - firebase_core
- FlutterMacOS - FlutterMacOS
- firebase_core (3.12.1): - firebase_core (3.13.0):
- Firebase/CoreOnly (~> 11.8.0) - Firebase/CoreOnly (~> 11.10.0)
- FlutterMacOS - FlutterMacOS
- firebase_messaging (15.2.4): - firebase_messaging (15.2.5):
- Firebase/CoreOnly (~> 11.8.0) - Firebase/CoreOnly (~> 11.10.0)
- Firebase/Messaging (~> 11.8.0) - Firebase/Messaging (~> 11.10.0)
- firebase_core - firebase_core
- FlutterMacOS - FlutterMacOS
- FirebaseAnalytics (11.8.0): - FirebaseAnalytics (11.10.0):
- FirebaseAnalytics/AdIdSupport (= 11.8.0) - FirebaseAnalytics/AdIdSupport (= 11.10.0)
- FirebaseCore (~> 11.8.0) - FirebaseCore (~> 11.10.0)
- FirebaseInstallations (~> 11.0) - FirebaseInstallations (~> 11.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0) - GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0) - GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0) - GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)" - "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0) - nanopb (~> 3.30910.0)
- FirebaseAnalytics/AdIdSupport (11.8.0): - FirebaseAnalytics/AdIdSupport (11.10.0):
- FirebaseCore (~> 11.8.0) - FirebaseCore (~> 11.10.0)
- FirebaseInstallations (~> 11.0) - FirebaseInstallations (~> 11.0)
- GoogleAppMeasurement (= 11.8.0) - GoogleAppMeasurement (= 11.10.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0) - GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0) - GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0) - GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)" - "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0) - nanopb (~> 3.30910.0)
- FirebaseCore (11.8.1): - FirebaseCore (11.10.0):
- FirebaseCoreInternal (~> 11.8.0) - FirebaseCoreInternal (~> 11.10.0)
- GoogleUtilities/Environment (~> 8.0) - GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/Logger (~> 8.0) - GoogleUtilities/Logger (~> 8.0)
- FirebaseCoreInternal (11.8.0): - FirebaseCoreInternal (11.10.0):
- "GoogleUtilities/NSData+zlib (~> 8.0)" - "GoogleUtilities/NSData+zlib (~> 8.0)"
- FirebaseInstallations (11.8.0): - FirebaseInstallations (11.10.0):
- FirebaseCore (~> 11.8.0) - FirebaseCore (~> 11.10.0)
- GoogleUtilities/Environment (~> 8.0) - GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0) - GoogleUtilities/UserDefaults (~> 8.0)
- PromisesObjC (~> 2.4) - PromisesObjC (~> 2.4)
- FirebaseMessaging (11.8.0): - FirebaseMessaging (11.10.0):
- FirebaseCore (~> 11.8.0) - FirebaseCore (~> 11.10.0)
- FirebaseInstallations (~> 11.0) - FirebaseInstallations (~> 11.0)
- GoogleDataTransport (~> 10.0) - GoogleDataTransport (~> 10.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0) - GoogleUtilities/AppDelegateSwizzler (~> 8.0)
@ -92,21 +92,21 @@ PODS:
- gal (1.0.0): - gal (1.0.0):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- GoogleAppMeasurement (11.8.0): - GoogleAppMeasurement (11.10.0):
- GoogleAppMeasurement/AdIdSupport (= 11.8.0) - GoogleAppMeasurement/AdIdSupport (= 11.10.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0) - GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0) - GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0) - GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)" - "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0) - nanopb (~> 3.30910.0)
- GoogleAppMeasurement/AdIdSupport (11.8.0): - GoogleAppMeasurement/AdIdSupport (11.10.0):
- GoogleAppMeasurement/WithoutAdIdSupport (= 11.8.0) - GoogleAppMeasurement/WithoutAdIdSupport (= 11.10.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0) - GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0) - GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0) - GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)" - "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0) - nanopb (~> 3.30910.0)
- GoogleAppMeasurement/WithoutAdIdSupport (11.8.0): - GoogleAppMeasurement/WithoutAdIdSupport (11.10.0):
- GoogleUtilities/AppDelegateSwizzler (~> 8.0) - GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0) - GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0) - GoogleUtilities/Network (~> 8.0)
@ -365,22 +365,22 @@ SPEC CHECKSUMS:
file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a
file_saver: e35bd97de451dde55ff8c38862ed7ad0f3418d0f file_saver: e35bd97de451dde55ff8c38862ed7ad0f3418d0f
file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31 file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31
Firebase: d80354ed7f6df5f9aca55e9eb47cc4b634735eaf Firebase: 1fe1c0a7d9aaea32efe01fbea5f0ebd8d70e53a2
firebase_analytics: 2c7864ab677e8a178a6dd4126de1d19e9d9a7bf3 firebase_analytics: 5f4b20b5f700bcae2f800c69a63e79d937d0daa9
firebase_core: 3dcdf8453dfb144a023ee70f49e0463b97177f71 firebase_core: efd50ad8177dc489af1b9163a560359cf1b30597
firebase_messaging: 96fe41b2f8b5bee4e0f21df8d716cb8c9293448c firebase_messaging: acf2566068a55d7eb8cddfee5b094754070a5b88
FirebaseAnalytics: 4fd42def128146e24e480e89f310e3d8534ea42b FirebaseAnalytics: 4e42333f02cf78ed93703a5c36f36dd518aebdef
FirebaseCore: 99fe0c4b44a39f37d99e6404e02009d2db5d718d FirebaseCore: 8344daef5e2661eb004b177488d6f9f0f24251b7
FirebaseCoreInternal: df24ce5af28864660ecbd13596fc8dd3a8c34629 FirebaseCoreInternal: ef4505d2afb1d0ebbc33162cb3795382904b5679
FirebaseInstallations: 6c963bd2a86aca0481eef4f48f5a4df783ae5917 FirebaseInstallations: 9980995bdd06ec8081dfb6ab364162bdd64245c3
FirebaseMessaging: 487b634ccdf6f7b7ff180fdcb2a9935490f764e8 FirebaseMessaging: 2b9f56aa4ed286e1f0ce2ee1d413aabb8f9f5cb9
flutter_inappwebview_macos: c2d68649f9f8f1831bfcd98d73fd6256366d9d1d flutter_inappwebview_macos: c2d68649f9f8f1831bfcd98d73fd6256366d9d1d
flutter_timezone: d59eea86178cbd7943cd2431cc2eaa9850f935d8 flutter_timezone: d59eea86178cbd7943cd2431cc2eaa9850f935d8
flutter_udid: d26e455e8c06174e6aff476e147defc6cae38495 flutter_udid: d26e455e8c06174e6aff476e147defc6cae38495
flutter_webrtc: 377dbcebdde6fed0fc40de87bcaaa2bffcec9a88 flutter_webrtc: 377dbcebdde6fed0fc40de87bcaaa2bffcec9a88
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
gal: baecd024ebfd13c441269ca7404792a7152fde89 gal: baecd024ebfd13c441269ca7404792a7152fde89
GoogleAppMeasurement: fc0817122bd4d4189164f85374e06773b9561896 GoogleAppMeasurement: 36684bfb3ee034e2b42b4321eb19da3a1b81e65d
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
HotKey: 400beb7caa29054ea8d864c96f5ba7e5b4852277 HotKey: 400beb7caa29054ea8d864c96f5ba7e5b4852277

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 # 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 # 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. # of the product and file versions while build-number is used as the build suffix.
version: 2.4.2+88 version: 2.4.2+89
environment: environment:
sdk: ^3.5.4 sdk: ^3.5.4