Compare commits
5 Commits
3.3.0+135
...
f4e2c0440d
| Author | SHA1 | Date | |
|---|---|---|---|
|
f4e2c0440d
|
|||
|
c9c0aa9fb5
|
|||
| baed28bef7 | |||
| 7a6eecf628 | |||
| 6a63bc235b |
@@ -47,6 +47,7 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
private func processNotification(request: UNNotificationRequest, content: UNMutableNotificationContent) throws {
|
||||
switch content.userInfo["type"] as? String {
|
||||
case "messages.new":
|
||||
content.categoryIdentifier = "REPLYABLE_MESSAGE"
|
||||
try handleMessagingNotification(request: request, content: content)
|
||||
default:
|
||||
try handleDefaultNotification(content: content)
|
||||
@@ -60,8 +61,6 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
|
||||
let pfpIdentifier = meta["pfp"] as? String
|
||||
|
||||
content.categoryIdentifier = "REPLYABLE_MESSAGE"
|
||||
|
||||
let metaCopy = meta as? [String: Any] ?? [:]
|
||||
let pfpUrl = pfpIdentifier != nil ? getAttachmentUrl(for: pfpIdentifier!) : nil
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/chat.dart';
|
||||
import 'package:island/models/developer.dart';
|
||||
import 'package:island/models/publisher.dart';
|
||||
import 'package:island/models/relationship.dart';
|
||||
import 'package:island/models/account.dart';
|
||||
import 'package:island/pods/config.dart';
|
||||
@@ -39,6 +40,465 @@ import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
part 'profile.g.dart';
|
||||
|
||||
class _AccountBasicInfo extends StatelessWidget {
|
||||
final SnAccount data;
|
||||
final String uname;
|
||||
final AsyncValue<SnDeveloper?> accountDeveloper;
|
||||
|
||||
const _AccountBasicInfo({
|
||||
required this.data,
|
||||
required this.uname,
|
||||
required this.accountDeveloper,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 24, 24, 8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ProfilePictureWidget(file: data.profile.picture, radius: 32),
|
||||
const Gap(20),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
AccountName(account: data, style: TextStyle(fontSize: 20)),
|
||||
const Gap(6),
|
||||
Flexible(
|
||||
child: Text(
|
||||
'@${data.name}',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
).fontSize(14).opacity(0.85),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (accountDeveloper.value != null)
|
||||
Row(
|
||||
spacing: 7,
|
||||
children: [
|
||||
const Icon(Symbols.smart_toy, size: 18),
|
||||
Text(
|
||||
'botAutomatedBy'.tr(
|
||||
args: [accountDeveloper.value!.publisher!.nick],
|
||||
),
|
||||
).fontSize(13),
|
||||
],
|
||||
).opacity(0.75),
|
||||
const Gap(4),
|
||||
AccountStatusWidget(uname: uname, padding: EdgeInsets.zero),
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
SharePlus.instance.share(
|
||||
ShareParams(
|
||||
uri: Uri.parse('https://id.solian.app/@${data.name}'),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Symbols.share),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AccountProfileBio extends StatelessWidget {
|
||||
final SnAccount data;
|
||||
|
||||
const _AccountProfileBio({required this.data});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('bio').tr().bold().fontSize(15).padding(bottom: 8),
|
||||
if (data.profile.bio.isEmpty)
|
||||
Text('descriptionNone').tr().italic()
|
||||
else
|
||||
MarkdownTextContent(
|
||||
content: data.profile.bio,
|
||||
linesMargin: EdgeInsets.zero,
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 24, vertical: 20),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AccountProfileDetail extends StatelessWidget {
|
||||
final SnAccount data;
|
||||
|
||||
const _AccountProfileDetail({required this.data});
|
||||
|
||||
List<Widget> _buildSubcolumn() {
|
||||
return [
|
||||
Row(
|
||||
spacing: 6,
|
||||
children: [
|
||||
const Icon(Symbols.join, size: 17, fill: 1),
|
||||
Text(
|
||||
'joinedAt'.tr(args: [data.createdAt.formatCustom('yyyy-MM-dd')]),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (data.profile.birthday != null)
|
||||
Row(
|
||||
spacing: 6,
|
||||
children: [
|
||||
const Icon(Symbols.cake, size: 17, fill: 1),
|
||||
Text(data.profile.birthday!.formatCustom('yyyy-MM-dd')),
|
||||
Text('·').bold(),
|
||||
Text(
|
||||
'${DateTime.now().difference(data.profile.birthday!).inDays ~/ 365} yrs old',
|
||||
),
|
||||
],
|
||||
),
|
||||
if (data.profile.location.isNotEmpty)
|
||||
Row(
|
||||
spacing: 6,
|
||||
children: [
|
||||
const Icon(Symbols.location_on, size: 17, fill: 1),
|
||||
Text(data.profile.location),
|
||||
],
|
||||
),
|
||||
if (data.profile.pronouns.isNotEmpty || data.profile.gender.isNotEmpty)
|
||||
Row(
|
||||
spacing: 6,
|
||||
children: [
|
||||
const Icon(Symbols.person, size: 17, fill: 1),
|
||||
Text(
|
||||
data.profile.gender.isEmpty
|
||||
? 'unspecified'.tr()
|
||||
: data.profile.gender,
|
||||
),
|
||||
Text('·').bold(),
|
||||
Text(
|
||||
data.profile.pronouns.isEmpty
|
||||
? 'unspecified'.tr()
|
||||
: data.profile.pronouns,
|
||||
),
|
||||
],
|
||||
),
|
||||
if (data.profile.firstName.isNotEmpty ||
|
||||
data.profile.middleName.isNotEmpty ||
|
||||
data.profile.lastName.isNotEmpty)
|
||||
Row(
|
||||
spacing: 6,
|
||||
children: [
|
||||
const Icon(Symbols.id_card, size: 17, fill: 1),
|
||||
if (data.profile.firstName.isNotEmpty) Text(data.profile.firstName),
|
||||
if (data.profile.middleName.isNotEmpty)
|
||||
Text(data.profile.middleName),
|
||||
if (data.profile.lastName.isNotEmpty) Text(data.profile.lastName),
|
||||
],
|
||||
),
|
||||
Tooltip(
|
||||
message: 'creditsStatus'.tr(),
|
||||
child: Row(
|
||||
spacing: 6,
|
||||
children: [
|
||||
Icon(Symbols.star, size: 17, fill: 1).padding(right: 2),
|
||||
Text('${data.profile.socialCredits.toStringAsFixed(2)} pts'),
|
||||
Text('·').bold(),
|
||||
switch (data.profile.socialCreditsLevel) {
|
||||
-1 => Text('socialCreditsLevelPoor').tr(),
|
||||
0 => Text('socialCreditsLevelNormal').tr(),
|
||||
1 => Text('socialCreditsLevelGood').tr(),
|
||||
2 => Text('socialCreditsLevelExcellent').tr(),
|
||||
_ => Text('unknown').tr(),
|
||||
},
|
||||
],
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
child: Row(
|
||||
spacing: 6,
|
||||
children: [
|
||||
Icon(Symbols.fingerprint, size: 17, fill: 1).padding(right: 2),
|
||||
Flexible(
|
||||
child: Text(
|
||||
data.id,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(text: data.id));
|
||||
},
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
spacing: 24,
|
||||
children: [
|
||||
if (_buildSubcolumn().isNotEmpty)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 2,
|
||||
children: _buildSubcolumn(),
|
||||
),
|
||||
if (data.profile.timeZone.isNotEmpty && !kIsWeb)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('timeZone').tr().bold(),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||
textBaseline: TextBaseline.alphabetic,
|
||||
spacing: 6,
|
||||
children: [
|
||||
Text(data.profile.timeZone),
|
||||
Text(
|
||||
getTzInfo(
|
||||
data.profile.timeZone,
|
||||
).$2.formatCustomGlobal('HH:mm'),
|
||||
),
|
||||
Text(
|
||||
getTzInfo(data.profile.timeZone).$1.formatOffsetLocal(),
|
||||
).fontSize(11),
|
||||
Text(
|
||||
'UTC${getTzInfo(data.profile.timeZone).$1.formatOffset()}',
|
||||
).fontSize(11).opacity(0.75),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 24, vertical: 16),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AccountProfileLinks extends StatelessWidget {
|
||||
final SnAccount data;
|
||||
|
||||
const _AccountProfileLinks({required this.data});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('links').tr().bold().padding(horizontal: 24, top: 12, bottom: 4),
|
||||
for (final link in data.profile.links)
|
||||
ListTile(
|
||||
title: Text(link.name.capitalizeEachWord()),
|
||||
subtitle: Text(link.url),
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
),
|
||||
onTap: () {
|
||||
if (!link.url.startsWith('http') && !link.url.contains('://')) {
|
||||
launchUrlString('https://${link.url}');
|
||||
} else {
|
||||
launchUrlString(link.url);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AccountPublisherList extends StatelessWidget {
|
||||
final List<SnPublisher> publishers;
|
||||
|
||||
const _AccountPublisherList({required this.publishers});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (publishers.isEmpty) return const SizedBox.shrink();
|
||||
return Card(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'publishers',
|
||||
).tr().bold().padding(horizontal: 24, top: 12, bottom: 4),
|
||||
for (final publisher in publishers)
|
||||
ListTile(
|
||||
title: Text(publisher.nick),
|
||||
subtitle:
|
||||
publisher.bio.isNotEmpty
|
||||
? Text(
|
||||
publisher.bio,
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)
|
||||
: null,
|
||||
leading: ProfilePictureWidget(
|
||||
file: publisher.picture,
|
||||
borderRadius: publisher.type == 1 ? 8 : null,
|
||||
),
|
||||
isThreeLine: true,
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.pop(context, true);
|
||||
context.pushNamed(
|
||||
'publisherProfile',
|
||||
pathParameters: {'name': publisher.name},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AccountAction extends StatelessWidget {
|
||||
final SnAccount data;
|
||||
final AsyncValue<SnRelationship?> accountRelationship;
|
||||
final AsyncValue<SnChatRoom?> accountChat;
|
||||
final VoidCallback relationshipAction;
|
||||
final VoidCallback blockAction;
|
||||
final VoidCallback directMessageAction;
|
||||
|
||||
const _AccountAction({
|
||||
required this.data,
|
||||
required this.accountRelationship,
|
||||
required this.accountChat,
|
||||
required this.relationshipAction,
|
||||
required this.blockAction,
|
||||
required this.directMessageAction,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
child: Column(
|
||||
spacing: 8,
|
||||
children: [
|
||||
Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
if (accountRelationship.value == null ||
|
||||
accountRelationship.value!.status > -100)
|
||||
Expanded(
|
||||
child: FilledButton.icon(
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStatePropertyAll(
|
||||
accountRelationship.value == null
|
||||
? null
|
||||
: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
foregroundColor: WidgetStatePropertyAll(
|
||||
accountRelationship.value == null
|
||||
? null
|
||||
: Theme.of(context).colorScheme.onSecondary,
|
||||
),
|
||||
),
|
||||
onPressed: relationshipAction,
|
||||
label:
|
||||
Text(
|
||||
accountRelationship.value == null
|
||||
? 'addFriendShort'
|
||||
: 'added',
|
||||
).tr(),
|
||||
icon:
|
||||
accountRelationship.value == null
|
||||
? const Icon(Symbols.person_add)
|
||||
: const Icon(Symbols.person_check),
|
||||
),
|
||||
),
|
||||
if (accountRelationship.value == null ||
|
||||
accountRelationship.value!.status <= -100)
|
||||
Expanded(
|
||||
child: FilledButton.icon(
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStatePropertyAll(
|
||||
accountRelationship.value == null
|
||||
? null
|
||||
: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
foregroundColor: WidgetStatePropertyAll(
|
||||
accountRelationship.value == null
|
||||
? null
|
||||
: Theme.of(context).colorScheme.onSecondary,
|
||||
),
|
||||
),
|
||||
onPressed: blockAction,
|
||||
label:
|
||||
Text(
|
||||
accountRelationship.value == null
|
||||
? 'blockUser'
|
||||
: 'unblockUser',
|
||||
).tr(),
|
||||
icon:
|
||||
accountRelationship.value == null
|
||||
? const Icon(Symbols.block)
|
||||
: const Icon(Symbols.person_cancel),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
Expanded(
|
||||
child: FilledButton.icon(
|
||||
onPressed: directMessageAction,
|
||||
icon: const Icon(Symbols.message),
|
||||
label:
|
||||
Text(
|
||||
accountChat.value == null
|
||||
? 'createDirectMessage'
|
||||
: 'gotoDirectMessage',
|
||||
maxLines: 1,
|
||||
).tr(),
|
||||
),
|
||||
),
|
||||
IconButton.filled(
|
||||
onPressed: () {
|
||||
showAbuseReportSheet(
|
||||
context,
|
||||
resourceIdentifier: 'account/${data.id}',
|
||||
);
|
||||
},
|
||||
icon: Icon(
|
||||
Symbols.flag,
|
||||
color: Theme.of(context).colorScheme.onError,
|
||||
),
|
||||
style: ButtonStyle(
|
||||
visualDensity: VisualDensity.compact,
|
||||
backgroundColor: WidgetStatePropertyAll(
|
||||
Theme.of(context).colorScheme.error,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 16, vertical: 12),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<SnAccount> account(Ref ref, String uname) async {
|
||||
if (uname == 'me') {
|
||||
@@ -132,6 +592,20 @@ Future<SnDeveloper?> accountBotDeveloper(Ref ref, String uname) async {
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<List<SnPublisher>> accountPublishers(Ref ref, String id) async {
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
try {
|
||||
final resp = await apiClient.get('/sphere/publishers/of/$id');
|
||||
return resp.data
|
||||
.map((e) => SnPublisher.fromJson(e))
|
||||
.cast<SnPublisher>()
|
||||
.toList();
|
||||
} catch (err) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
class AccountProfileScreen extends HookConsumerWidget {
|
||||
final String name;
|
||||
const AccountProfileScreen({super.key, required this.name});
|
||||
@@ -217,354 +691,16 @@ class AccountProfileScreen extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
List<Widget> buildSubcolumn(SnAccount data) {
|
||||
return [
|
||||
Row(
|
||||
spacing: 6,
|
||||
children: [
|
||||
const Icon(Symbols.join, size: 17, fill: 1),
|
||||
Text(
|
||||
'joinedAt'.tr(args: [data.createdAt.formatCustom('yyyy-MM-dd')]),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (data.profile.birthday != null)
|
||||
Row(
|
||||
spacing: 6,
|
||||
children: [
|
||||
const Icon(Symbols.cake, size: 17, fill: 1),
|
||||
Text(data.profile.birthday!.formatCustom('yyyy-MM-dd')),
|
||||
Text('·').bold(),
|
||||
Text(
|
||||
'${DateTime.now().difference(data.profile.birthday!).inDays ~/ 365} yrs old',
|
||||
),
|
||||
],
|
||||
),
|
||||
if (data.profile.location.isNotEmpty)
|
||||
Row(
|
||||
spacing: 6,
|
||||
children: [
|
||||
const Icon(Symbols.location_on, size: 17, fill: 1),
|
||||
Text(data.profile.location),
|
||||
],
|
||||
),
|
||||
if (data.profile.pronouns.isNotEmpty || data.profile.gender.isNotEmpty)
|
||||
Row(
|
||||
spacing: 6,
|
||||
children: [
|
||||
const Icon(Symbols.person, size: 17, fill: 1),
|
||||
Text(
|
||||
data.profile.gender.isEmpty
|
||||
? 'unspecified'.tr()
|
||||
: data.profile.gender,
|
||||
),
|
||||
Text('·').bold(),
|
||||
Text(
|
||||
data.profile.pronouns.isEmpty
|
||||
? 'unspecified'.tr()
|
||||
: data.profile.pronouns,
|
||||
),
|
||||
],
|
||||
),
|
||||
if (data.profile.firstName.isNotEmpty ||
|
||||
data.profile.middleName.isNotEmpty ||
|
||||
data.profile.lastName.isNotEmpty)
|
||||
Row(
|
||||
spacing: 6,
|
||||
children: [
|
||||
const Icon(Symbols.id_card, size: 17, fill: 1),
|
||||
if (data.profile.firstName.isNotEmpty)
|
||||
Text(data.profile.firstName),
|
||||
if (data.profile.middleName.isNotEmpty)
|
||||
Text(data.profile.middleName),
|
||||
if (data.profile.lastName.isNotEmpty) Text(data.profile.lastName),
|
||||
],
|
||||
),
|
||||
Tooltip(
|
||||
message: 'creditsStatus'.tr(),
|
||||
child: Row(
|
||||
spacing: 6,
|
||||
children: [
|
||||
Icon(Symbols.star, size: 17, fill: 1).padding(right: 2),
|
||||
Text('${data.profile.socialCredits.toStringAsFixed(2)} pts'),
|
||||
Text('·').bold(),
|
||||
switch (data.profile.socialCreditsLevel) {
|
||||
-1 => Text('socialCreditsLevelPoor').tr(),
|
||||
0 => Text('socialCreditsLevelNormal').tr(),
|
||||
1 => Text('socialCreditsLevelGood').tr(),
|
||||
2 => Text('socialCreditsLevelExcellent').tr(),
|
||||
_ => Text('unknown').tr(),
|
||||
},
|
||||
],
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
child: Row(
|
||||
spacing: 6,
|
||||
children: [
|
||||
Icon(Symbols.fingerprint, size: 17, fill: 1).padding(right: 2),
|
||||
Text(data.id),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(text: data.id));
|
||||
},
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
final user = ref.watch(userInfoProvider);
|
||||
final isCurrentUser = useMemoized(
|
||||
() => user.value?.id == account.value?.id,
|
||||
[user, account],
|
||||
);
|
||||
|
||||
Widget accountBasicInfo(SnAccount data) => Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 24, 24, 8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ProfilePictureWidget(file: data.profile.picture, radius: 32),
|
||||
const Gap(20),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
AccountName(account: data, style: TextStyle(fontSize: 20)),
|
||||
const Gap(6),
|
||||
Flexible(
|
||||
child: Text(
|
||||
'@${data.name}',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
).fontSize(14).opacity(0.85),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (accountDeveloper.value != null)
|
||||
Row(
|
||||
spacing: 7,
|
||||
children: [
|
||||
const Icon(Symbols.smart_toy, size: 18),
|
||||
Text(
|
||||
'botAutomatedBy'.tr(
|
||||
args: [accountDeveloper.value!.publisher!.nick],
|
||||
),
|
||||
).fontSize(13),
|
||||
],
|
||||
).opacity(0.75),
|
||||
const Gap(4),
|
||||
AccountStatusWidget(uname: name, padding: EdgeInsets.zero),
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
SharePlus.instance.share(
|
||||
ShareParams(
|
||||
uri: Uri.parse('https://id.solian.app/@${data.name}'),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Symbols.share),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
Widget accountProfileBio(SnAccount data) => Card(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('bio').tr().bold().fontSize(15).padding(bottom: 8),
|
||||
if (data.profile.bio.isEmpty)
|
||||
Text('descriptionNone').tr().italic()
|
||||
else
|
||||
MarkdownTextContent(
|
||||
content: data.profile.bio,
|
||||
linesMargin: EdgeInsets.zero,
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 24, vertical: 20),
|
||||
);
|
||||
|
||||
Widget accountProfileDetail(SnAccount data) => Card(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
spacing: 24,
|
||||
children: [
|
||||
if (buildSubcolumn(data).isNotEmpty)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 2,
|
||||
children: buildSubcolumn(data),
|
||||
),
|
||||
if (data.profile.timeZone.isNotEmpty && !kIsWeb)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('timeZone').tr().bold(),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||
textBaseline: TextBaseline.alphabetic,
|
||||
spacing: 6,
|
||||
children: [
|
||||
Text(data.profile.timeZone),
|
||||
Text(
|
||||
getTzInfo(
|
||||
data.profile.timeZone,
|
||||
).$2.formatCustomGlobal('HH:mm'),
|
||||
),
|
||||
Text(
|
||||
getTzInfo(data.profile.timeZone).$1.formatOffsetLocal(),
|
||||
).fontSize(11),
|
||||
Text(
|
||||
'UTC${getTzInfo(data.profile.timeZone).$1.formatOffset()}',
|
||||
).fontSize(11).opacity(0.75),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 24, vertical: 16),
|
||||
);
|
||||
|
||||
Widget accountProfileLinks(SnAccount data) => Card(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('links').tr().bold().padding(horizontal: 24, top: 12, bottom: 4),
|
||||
for (final link in data.profile.links)
|
||||
ListTile(
|
||||
title: Text(link.name.capitalizeEachWord()),
|
||||
subtitle: Text(link.url),
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
),
|
||||
onTap: () {
|
||||
if (!link.url.startsWith('http') && !link.url.contains('://')) {
|
||||
launchUrlString('https://${link.url}');
|
||||
} else {
|
||||
launchUrlString(link.url);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
Widget accountAction(SnAccount data) => Card(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
if (accountRelationship.value == null ||
|
||||
accountRelationship.value!.status > -100)
|
||||
Expanded(
|
||||
child: FilledButton.icon(
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStatePropertyAll(
|
||||
accountRelationship.value == null
|
||||
? null
|
||||
: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
foregroundColor: WidgetStatePropertyAll(
|
||||
accountRelationship.value == null
|
||||
? null
|
||||
: Theme.of(context).colorScheme.onSecondary,
|
||||
),
|
||||
),
|
||||
onPressed: relationshipAction,
|
||||
label:
|
||||
Text(
|
||||
accountRelationship.value == null
|
||||
? 'addFriendShort'
|
||||
: 'added',
|
||||
).tr(),
|
||||
icon:
|
||||
accountRelationship.value == null
|
||||
? const Icon(Symbols.person_add)
|
||||
: const Icon(Symbols.person_check),
|
||||
),
|
||||
),
|
||||
if (accountRelationship.value == null ||
|
||||
accountRelationship.value!.status <= -100)
|
||||
Expanded(
|
||||
child: FilledButton.icon(
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStatePropertyAll(
|
||||
accountRelationship.value == null
|
||||
? null
|
||||
: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
foregroundColor: WidgetStatePropertyAll(
|
||||
accountRelationship.value == null
|
||||
? null
|
||||
: Theme.of(context).colorScheme.onSecondary,
|
||||
),
|
||||
),
|
||||
onPressed: blockAction,
|
||||
label:
|
||||
Text(
|
||||
accountRelationship.value == null
|
||||
? 'blockUser'
|
||||
: 'unblockUser',
|
||||
).tr(),
|
||||
icon:
|
||||
accountRelationship.value == null
|
||||
? const Icon(Symbols.block)
|
||||
: const Icon(Symbols.person_cancel),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
Expanded(
|
||||
child: FilledButton.icon(
|
||||
onPressed: directMessageAction,
|
||||
icon: const Icon(Symbols.message),
|
||||
label:
|
||||
Text(
|
||||
accountChat.value == null
|
||||
? 'createDirectMessage'
|
||||
: 'gotoDirectMessage',
|
||||
maxLines: 1,
|
||||
).tr(),
|
||||
),
|
||||
),
|
||||
IconButton.filled(
|
||||
onPressed: () {
|
||||
showAbuseReportSheet(
|
||||
context,
|
||||
resourceIdentifier: 'account/${data.id}',
|
||||
);
|
||||
},
|
||||
icon: Icon(
|
||||
Symbols.flag,
|
||||
color: Theme.of(context).colorScheme.onError,
|
||||
),
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStatePropertyAll(
|
||||
Theme.of(context).colorScheme.error,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 16, vertical: 12),
|
||||
);
|
||||
|
||||
return account.when(
|
||||
data:
|
||||
(data) => AppScaffold(
|
||||
data: (data) {
|
||||
final accountPublishers = ref.watch(accountPublishersProvider(data.id));
|
||||
return AppScaffold(
|
||||
isNoBackground: false,
|
||||
appBar:
|
||||
isWideScreen(context)
|
||||
@@ -595,9 +731,7 @@ class AccountProfileScreen extends HookConsumerWidget {
|
||||
style: TextStyle(
|
||||
color:
|
||||
appbarColor.value ??
|
||||
Theme.of(
|
||||
context,
|
||||
).appBarTheme.foregroundColor,
|
||||
Theme.of(context).appBarTheme.foregroundColor,
|
||||
shadows: [appbarShadow],
|
||||
),
|
||||
),
|
||||
@@ -613,7 +747,13 @@ class AccountProfileScreen extends HookConsumerWidget {
|
||||
Flexible(
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverToBoxAdapter(child: accountBasicInfo(data)),
|
||||
SliverToBoxAdapter(
|
||||
child: _AccountBasicInfo(
|
||||
data: data,
|
||||
uname: name,
|
||||
accountDeveloper: accountDeveloper,
|
||||
),
|
||||
),
|
||||
if (data.badges.isNotEmpty)
|
||||
SliverToBoxAdapter(
|
||||
child: Card(
|
||||
@@ -642,14 +782,16 @@ class AccountProfileScreen extends HookConsumerWidget {
|
||||
).padding(horizontal: 4, top: 8),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: accountProfileBio(data).padding(top: 4),
|
||||
child: _AccountProfileBio(
|
||||
data: data,
|
||||
).padding(top: 4),
|
||||
),
|
||||
if (data.profile.links.isNotEmpty)
|
||||
SliverToBoxAdapter(
|
||||
child: accountProfileLinks(data),
|
||||
child: _AccountProfileLinks(data: data),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: accountProfileDetail(data),
|
||||
child: _AccountProfileDetail(data: data),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -658,8 +800,22 @@ class AccountProfileScreen extends HookConsumerWidget {
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverGap(24),
|
||||
SliverToBoxAdapter(
|
||||
child: _AccountPublisherList(
|
||||
publishers: accountPublishers.value ?? [],
|
||||
),
|
||||
),
|
||||
if (user.value != null && !isCurrentUser)
|
||||
SliverToBoxAdapter(child: accountAction(data)),
|
||||
SliverToBoxAdapter(
|
||||
child: _AccountAction(
|
||||
data: data,
|
||||
accountRelationship: accountRelationship,
|
||||
accountChat: accountChat,
|
||||
relationshipAction: relationshipAction,
|
||||
blockAction: blockAction,
|
||||
directMessageAction: directMessageAction,
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Card(
|
||||
child: FortuneGraphWidget(
|
||||
@@ -715,7 +871,13 @@ class AccountProfileScreen extends HookConsumerWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(child: accountBasicInfo(data)),
|
||||
SliverToBoxAdapter(
|
||||
child: _AccountBasicInfo(
|
||||
data: data,
|
||||
uname: name,
|
||||
accountDeveloper: accountDeveloper,
|
||||
),
|
||||
),
|
||||
if (data.badges.isNotEmpty)
|
||||
SliverToBoxAdapter(
|
||||
child: Card(
|
||||
@@ -742,22 +904,36 @@ class AccountProfileScreen extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: accountProfileBio(data).padding(horizontal: 4),
|
||||
child: _AccountProfileBio(
|
||||
data: data,
|
||||
).padding(horizontal: 4),
|
||||
),
|
||||
if (data.profile.links.isNotEmpty)
|
||||
SliverToBoxAdapter(
|
||||
child: accountProfileLinks(
|
||||
data,
|
||||
child: _AccountProfileLinks(
|
||||
data: data,
|
||||
).padding(horizontal: 4),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: accountProfileDetail(
|
||||
data,
|
||||
child: _AccountPublisherList(
|
||||
publishers: accountPublishers.value ?? [],
|
||||
).padding(horizontal: 4),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: _AccountProfileDetail(
|
||||
data: data,
|
||||
).padding(horizontal: 4),
|
||||
),
|
||||
if (user.value != null && !isCurrentUser)
|
||||
SliverToBoxAdapter(
|
||||
child: accountAction(data).padding(horizontal: 4),
|
||||
child: _AccountAction(
|
||||
data: data,
|
||||
accountRelationship: accountRelationship,
|
||||
accountChat: accountChat,
|
||||
relationshipAction: relationshipAction,
|
||||
blockAction: blockAction,
|
||||
directMessageAction: directMessageAction,
|
||||
).padding(horizontal: 4),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Card(
|
||||
@@ -769,7 +945,8 @@ class AccountProfileScreen extends HookConsumerWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
error:
|
||||
(error, stackTrace) => AppScaffold(
|
||||
appBar: AppBar(leading: const PageBackButton()),
|
||||
|
||||
@@ -762,5 +762,127 @@ class _AccountBotDeveloperProviderElement
|
||||
String get uname => (origin as AccountBotDeveloperProvider).uname;
|
||||
}
|
||||
|
||||
String _$accountPublishersHash() => r'25f5695b4a5154163d77f1769876d826bf736609';
|
||||
|
||||
/// See also [accountPublishers].
|
||||
@ProviderFor(accountPublishers)
|
||||
const accountPublishersProvider = AccountPublishersFamily();
|
||||
|
||||
/// See also [accountPublishers].
|
||||
class AccountPublishersFamily extends Family<AsyncValue<List<SnPublisher>>> {
|
||||
/// See also [accountPublishers].
|
||||
const AccountPublishersFamily();
|
||||
|
||||
/// See also [accountPublishers].
|
||||
AccountPublishersProvider call(String id) {
|
||||
return AccountPublishersProvider(id);
|
||||
}
|
||||
|
||||
@override
|
||||
AccountPublishersProvider getProviderOverride(
|
||||
covariant AccountPublishersProvider provider,
|
||||
) {
|
||||
return call(provider.id);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'accountPublishersProvider';
|
||||
}
|
||||
|
||||
/// See also [accountPublishers].
|
||||
class AccountPublishersProvider
|
||||
extends AutoDisposeFutureProvider<List<SnPublisher>> {
|
||||
/// See also [accountPublishers].
|
||||
AccountPublishersProvider(String id)
|
||||
: this._internal(
|
||||
(ref) => accountPublishers(ref as AccountPublishersRef, id),
|
||||
from: accountPublishersProvider,
|
||||
name: r'accountPublishersProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$accountPublishersHash,
|
||||
dependencies: AccountPublishersFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
AccountPublishersFamily._allTransitiveDependencies,
|
||||
id: id,
|
||||
);
|
||||
|
||||
AccountPublishersProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.id,
|
||||
}) : super.internal();
|
||||
|
||||
final String id;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
FutureOr<List<SnPublisher>> Function(AccountPublishersRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: AccountPublishersProvider._internal(
|
||||
(ref) => create(ref as AccountPublishersRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
id: id,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeFutureProviderElement<List<SnPublisher>> createElement() {
|
||||
return _AccountPublishersProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is AccountPublishersProvider && other.id == id;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, id.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin AccountPublishersRef on AutoDisposeFutureProviderRef<List<SnPublisher>> {
|
||||
/// The parameter `id` of this provider.
|
||||
String get id;
|
||||
}
|
||||
|
||||
class _AccountPublishersProviderElement
|
||||
extends AutoDisposeFutureProviderElement<List<SnPublisher>>
|
||||
with AccountPublishersRef {
|
||||
_AccountPublishersProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String get id => (origin as AccountPublishersProvider).id;
|
||||
}
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
|
||||
@@ -72,7 +72,7 @@ class MarketplaceWebFeedsScreen extends HookConsumerWidget {
|
||||
searchController.clear();
|
||||
}
|
||||
return null;
|
||||
}, [query.value]);
|
||||
}, [query]);
|
||||
|
||||
// Clean up timer on dispose
|
||||
useEffect(() {
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'explore.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$activityListNotifierHash() =>
|
||||
r'a4968856ac34b59d47cfd4a7cbb39289aef2a1b1';
|
||||
r'167021cada54da7c8d8437eef1ffb387a92ea2e3';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
||||
@@ -27,6 +27,224 @@ import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
part 'pub_profile.g.dart';
|
||||
|
||||
class _PublisherBasisWidget extends StatelessWidget {
|
||||
final SnPublisher data;
|
||||
final AsyncValue<SnSubscriptionStatus> subStatus;
|
||||
final ValueNotifier<bool> subscribing;
|
||||
final VoidCallback subscribe;
|
||||
final VoidCallback unsubscribe;
|
||||
|
||||
const _PublisherBasisWidget({
|
||||
required this.data,
|
||||
required this.subStatus,
|
||||
required this.subscribing,
|
||||
required this.subscribe,
|
||||
required this.unsubscribe,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 20,
|
||||
children: [
|
||||
GestureDetector(
|
||||
child: Badge(
|
||||
isLabelVisible: data.type == 0,
|
||||
padding: EdgeInsets.all(4),
|
||||
label: Icon(
|
||||
Symbols.launch,
|
||||
size: 16,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
offset: Offset(0, 48),
|
||||
child: ProfilePictureWidget(
|
||||
file: data.picture,
|
||||
radius: 32,
|
||||
borderRadius: data.type == 0 ? null : 12,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
if (data.account?.name != null) {
|
||||
Navigator.pop(context, true);
|
||||
context.pushNamed(
|
||||
'accountProfile',
|
||||
pathParameters: {'name': data.account!.name},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
spacing: 6,
|
||||
children: [
|
||||
Text(data.nick).fontSize(20),
|
||||
if (data.verification != null)
|
||||
VerificationMark(mark: data.verification!),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'@${data.name}',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
).fontSize(14).opacity(0.85),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (data.type == 0 && data.account != null)
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
spacing: 6,
|
||||
children: [
|
||||
Icon(
|
||||
data.type == 0 ? Symbols.person : Symbols.workspaces,
|
||||
fill: 1,
|
||||
size: 17,
|
||||
),
|
||||
Text(
|
||||
'publisherBelongsTo'.tr(args: ['@${data.account!.name}']),
|
||||
).fontSize(14),
|
||||
],
|
||||
).opacity(0.85),
|
||||
const Gap(4),
|
||||
if (data.type == 0 && data.account != null)
|
||||
AccountStatusWidget(
|
||||
uname: data.account!.name,
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
subStatus
|
||||
.when(
|
||||
data:
|
||||
(status) => FilledButton.icon(
|
||||
onPressed:
|
||||
subscribing.value
|
||||
? null
|
||||
: (status.isSubscribed
|
||||
? unsubscribe
|
||||
: subscribe),
|
||||
icon: Icon(
|
||||
status.isSubscribed
|
||||
? Symbols.remove_circle
|
||||
: Symbols.add_circle,
|
||||
),
|
||||
label:
|
||||
Text(
|
||||
status.isSubscribed
|
||||
? 'unsubscribe'
|
||||
: 'subscribe',
|
||||
).tr(),
|
||||
style: ButtonStyle(
|
||||
visualDensity: VisualDensity(vertical: -2),
|
||||
),
|
||||
),
|
||||
error: (_, _) => const SizedBox(),
|
||||
loading:
|
||||
() => const SizedBox(
|
||||
height: 36,
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.padding(top: 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 24, top: 24);
|
||||
}
|
||||
}
|
||||
|
||||
class _PublisherBadgesWidget extends StatelessWidget {
|
||||
final SnPublisher data;
|
||||
final AsyncValue<List<SnAccountBadge>> badges;
|
||||
|
||||
const _PublisherBadgesWidget({required this.data, required this.badges});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return (badges.value?.isNotEmpty ?? false)
|
||||
? Card(
|
||||
child: BadgeList(
|
||||
badges: badges.value!,
|
||||
).padding(horizontal: 26, vertical: 20),
|
||||
).padding(horizontal: 4)
|
||||
: const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
|
||||
class _PublisherVerificationWidget extends StatelessWidget {
|
||||
final SnPublisher data;
|
||||
|
||||
const _PublisherVerificationWidget({required this.data});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return (data.verification != null)
|
||||
? Card(
|
||||
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: VerificationStatusCard(mark: data.verification!),
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
|
||||
class _PublisherBioWidget extends StatelessWidget {
|
||||
final SnPublisher data;
|
||||
|
||||
const _PublisherBioWidget({required this.data});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text('bio').tr().bold().fontSize(15).padding(bottom: 8),
|
||||
if (data.bio.isEmpty)
|
||||
Text('descriptionNone').tr().italic()
|
||||
else
|
||||
MarkdownTextContent(
|
||||
content: data.bio,
|
||||
linesMargin: EdgeInsets.zero,
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 20, vertical: 16),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PublisherCategoryTabWidget extends StatelessWidget {
|
||||
final TabController categoryTabController;
|
||||
|
||||
const _PublisherCategoryTabWidget({required this.categoryTabController});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: TabBar(
|
||||
controller: categoryTabController,
|
||||
dividerColor: Colors.transparent,
|
||||
splashBorderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
tabs: [
|
||||
Tab(text: 'all'.tr()),
|
||||
Tab(text: 'postTypePost'.tr()),
|
||||
Tab(text: 'postArticle'.tr()),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<SnPublisher> publisher(Ref ref, String uname) async {
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
@@ -132,170 +350,6 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
||||
offset: Offset(1.0, 1.0),
|
||||
);
|
||||
|
||||
Widget publisherBasisWidget(SnPublisher data) => Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 20,
|
||||
children: [
|
||||
GestureDetector(
|
||||
child: Badge(
|
||||
isLabelVisible: data.type == 0,
|
||||
padding: EdgeInsets.all(4),
|
||||
label: Icon(
|
||||
Symbols.launch,
|
||||
size: 16,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
offset: Offset(0, 48),
|
||||
child: ProfilePictureWidget(
|
||||
file: data.picture,
|
||||
radius: 32,
|
||||
borderRadius: data.type == 0 ? null : 12,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.pop(context, true);
|
||||
if (data.account?.name != null) {
|
||||
context.pushNamed(
|
||||
'accountProfile',
|
||||
pathParameters: {'name': data.account!.name},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
spacing: 6,
|
||||
children: [
|
||||
Text(data.nick).fontSize(20),
|
||||
if (data.verification != null)
|
||||
VerificationMark(mark: data.verification!),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'@${data.name}',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
).fontSize(14).opacity(0.85),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (data.type == 0 && data.account != null)
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
spacing: 6,
|
||||
children: [
|
||||
Icon(
|
||||
data.type == 0 ? Symbols.person : Symbols.workspaces,
|
||||
fill: 1,
|
||||
size: 17,
|
||||
),
|
||||
Text(
|
||||
'publisherBelongsTo'.tr(args: ['@${data.account!.name}']),
|
||||
).fontSize(14),
|
||||
],
|
||||
).opacity(0.85),
|
||||
const Gap(4),
|
||||
if (data.type == 0 && data.account != null)
|
||||
AccountStatusWidget(
|
||||
uname: data.account!.name,
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
subStatus
|
||||
.when(
|
||||
data:
|
||||
(status) => FilledButton.icon(
|
||||
onPressed:
|
||||
subscribing.value
|
||||
? null
|
||||
: (status.isSubscribed
|
||||
? unsubscribe
|
||||
: subscribe),
|
||||
icon: Icon(
|
||||
status.isSubscribed
|
||||
? Symbols.remove_circle
|
||||
: Symbols.add_circle,
|
||||
),
|
||||
label:
|
||||
Text(
|
||||
status.isSubscribed
|
||||
? 'unsubscribe'
|
||||
: 'subscribe',
|
||||
).tr(),
|
||||
style: ButtonStyle(
|
||||
visualDensity: VisualDensity(vertical: -2),
|
||||
),
|
||||
),
|
||||
error: (_, _) => const SizedBox(),
|
||||
loading:
|
||||
() => const SizedBox(
|
||||
height: 36,
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.padding(top: 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 24, top: 24);
|
||||
|
||||
Widget publisherBadgesWidget(SnPublisher data) =>
|
||||
(badges.value?.isNotEmpty ?? false)
|
||||
? Card(
|
||||
child: BadgeList(
|
||||
badges: badges.value!,
|
||||
).padding(horizontal: 26, vertical: 20),
|
||||
).padding(horizontal: 4)
|
||||
: const SizedBox.shrink();
|
||||
|
||||
Widget publisherVerificationWidget(SnPublisher data) =>
|
||||
(data.verification != null)
|
||||
? Card(
|
||||
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: VerificationStatusCard(mark: data.verification!),
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
|
||||
Widget publisherBioWidget(SnPublisher data) => Card(
|
||||
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text('bio').tr().bold().fontSize(15).padding(bottom: 8),
|
||||
if (data.bio.isEmpty)
|
||||
Text('descriptionNone').tr().italic()
|
||||
else
|
||||
MarkdownTextContent(
|
||||
content: data.bio,
|
||||
linesMargin: EdgeInsets.zero,
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 20, vertical: 16),
|
||||
);
|
||||
|
||||
Widget publisherCategoryTabWidget() => Card(
|
||||
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: TabBar(
|
||||
controller: categoryTabController,
|
||||
dividerColor: Colors.transparent,
|
||||
splashBorderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
tabs: [
|
||||
Tab(text: 'all'.tr()),
|
||||
Tab(text: 'postTypePost'.tr()),
|
||||
Tab(text: 'postArticle'.tr()),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
return publisher.when(
|
||||
data:
|
||||
(data) => AppScaffold(
|
||||
@@ -351,7 +405,9 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
||||
SliverGap(16),
|
||||
SliverPostList(pubName: name, pinned: true),
|
||||
SliverToBoxAdapter(
|
||||
child: publisherCategoryTabWidget(),
|
||||
child: _PublisherCategoryTabWidget(
|
||||
categoryTabController: categoryTabController,
|
||||
),
|
||||
),
|
||||
SliverPostList(
|
||||
key: ValueKey(categoryTab.value),
|
||||
@@ -377,10 +433,19 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
publisherBasisWidget(data).padding(bottom: 8),
|
||||
publisherBadgesWidget(data),
|
||||
publisherVerificationWidget(data),
|
||||
publisherBioWidget(data),
|
||||
_PublisherBasisWidget(
|
||||
data: data,
|
||||
subStatus: subStatus,
|
||||
subscribing: subscribing,
|
||||
subscribe: subscribe,
|
||||
unsubscribe: unsubscribe,
|
||||
).padding(bottom: 8),
|
||||
_PublisherBadgesWidget(
|
||||
data: data,
|
||||
badges: badges,
|
||||
),
|
||||
_PublisherVerificationWidget(data: data),
|
||||
_PublisherBioWidget(data: data),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -432,15 +497,32 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: publisherBasisWidget(data).padding(bottom: 8),
|
||||
child: _PublisherBasisWidget(
|
||||
data: data,
|
||||
subStatus: subStatus,
|
||||
subscribing: subscribing,
|
||||
subscribe: subscribe,
|
||||
unsubscribe: unsubscribe,
|
||||
).padding(bottom: 8),
|
||||
),
|
||||
SliverToBoxAdapter(child: publisherBadgesWidget(data)),
|
||||
SliverToBoxAdapter(
|
||||
child: publisherVerificationWidget(data),
|
||||
child: _PublisherBadgesWidget(
|
||||
data: data,
|
||||
badges: badges,
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: _PublisherVerificationWidget(data: data),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: _PublisherBioWidget(data: data),
|
||||
),
|
||||
SliverToBoxAdapter(child: publisherBioWidget(data)),
|
||||
SliverPostList(pubName: name, pinned: true),
|
||||
SliverToBoxAdapter(child: publisherCategoryTabWidget()),
|
||||
SliverToBoxAdapter(
|
||||
child: _PublisherCategoryTabWidget(
|
||||
categoryTabController: categoryTabController,
|
||||
),
|
||||
),
|
||||
SliverPostList(
|
||||
key: ValueKey(categoryTab.value),
|
||||
pubName: name,
|
||||
|
||||
@@ -77,7 +77,7 @@ class MarketplaceStickersScreen extends HookConsumerWidget {
|
||||
searchController.clear();
|
||||
}
|
||||
return null;
|
||||
}, [query.value]);
|
||||
}, [query]);
|
||||
|
||||
// Clean up timer on dispose
|
||||
useEffect(() {
|
||||
|
||||
@@ -48,7 +48,7 @@ class PostFeaturedList extends HookConsumerWidget {
|
||||
'PostFeaturedList: isCollapsed changed to ${isCollapsed.value}',
|
||||
);
|
||||
return null;
|
||||
}, [isCollapsed.value]);
|
||||
}, [isCollapsed]);
|
||||
|
||||
useEffect(() {
|
||||
if (featuredPostsAsync.hasValue && featuredPostsAsync.value!.isNotEmpty) {
|
||||
@@ -93,7 +93,7 @@ class PostFeaturedList extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}, [featuredPostsAsync.value]);
|
||||
}, [featuredPostsAsync]);
|
||||
|
||||
return ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
|
||||
@@ -36,9 +36,11 @@ class PostShuffleScreen extends HookConsumerWidget {
|
||||
bottom:
|
||||
kBottomControlHeight + MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
child:
|
||||
(postListState.value?.items.length ?? 0) > 0
|
||||
? CardSwiper(
|
||||
child: Builder(
|
||||
key: ValueKey(postListState.value?.items.length ?? 0),
|
||||
builder: (context) {
|
||||
if ((postListState.value?.items.length ?? 0) > 0) {
|
||||
return CardSwiper(
|
||||
controller: cardSwiperController,
|
||||
cardsCount: postListState.value!.items.length,
|
||||
isLoop: false,
|
||||
@@ -67,15 +69,17 @@ class PostShuffleScreen extends HookConsumerWidget {
|
||||
),
|
||||
);
|
||||
},
|
||||
onEnd: () {
|
||||
onEnd: () async {
|
||||
if (postListState.value?.hasMore ?? true) {
|
||||
postListNotifier.fetch(
|
||||
cursor: postListState.value?.nextCursor,
|
||||
);
|
||||
postListNotifier.forceRefresh();
|
||||
}
|
||||
},
|
||||
)
|
||||
: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
} else {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 0,
|
||||
|
||||
Reference in New Issue
Block a user