Compare commits

..

3 Commits

Author SHA1 Message Date
fec28f6223 💄 Optimize publisher page 2025-11-02 01:30:47 +08:00
85005ff9c3 💄 Optimize profile page 2025-11-02 01:20:14 +08:00
e3c92a3c55 💄 Optimize profile page styling 2025-11-02 01:05:40 +08:00
4 changed files with 322 additions and 225 deletions

View File

@@ -344,7 +344,7 @@
"accountSettingsHelpContent": "此页面允许您管理您的帐户安全性、隐私和其他设置。如果您需要帮助,请联系管理员。",
"unauthorized": "未授权",
"unauthorizedHint": "您未登录或会话已过期,请重新登录。",
"publisherBelongsTo": "属于",
"publisherBelongsTo": "属于 {}",
"postContent": "内容",
"postSettings": "设置",
"postPublisherUnselected": "未指定发布者",

View File

@@ -54,56 +54,134 @@ class _AccountBasicInfo extends StatelessWidget {
@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(
return Card(
child: Builder(
builder: (context) {
final hasBackground = data.profile.background?.id != null;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (isWideScreen(context) && hasBackground)
Stack(
clipBehavior: Clip.none,
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),
ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(8),
topRight: Radius.circular(8),
),
child: AspectRatio(
aspectRatio: 16 / 7,
child: CloudImageWidget(
file: data.profile.background,
fit: BoxFit.cover,
),
),
),
Positioned(
bottom: -24,
left: 16,
child: ProfilePictureWidget(
file: data.profile.picture,
radius: 32,
),
),
],
),
if (accountDeveloper.value != null)
Row(
spacing: 7,
Builder(
builder: (context) {
final showBackground = isWideScreen(context) && hasBackground;
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: showBackground ? 0 : 20,
children: [
const Icon(Symbols.smart_toy, size: 18),
Text(
'botAutomatedBy'.tr(
args: [accountDeveloper.value!.publisher!.nick],
if (!showBackground)
ProfilePictureWidget(
file: data.profile.picture,
radius: 32,
),
).fontSize(13),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
spacing: 4,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Flexible(
child: AccountName(
account: data,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
if (isWideScreen(context))
Flexible(
child: Text(
'@${data.name}',
).fontSize(11).padding(bottom: 2.5),
),
],
),
if (!isWideScreen(context))
Text(
'@${data.name}',
).fontSize(11).padding(bottom: 2.5),
Text(
(data.profile.bio.isNotEmpty)
? data.profile.bio
: 'descriptionNone'.tr(),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
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).padding(top: 4),
const Gap(4),
AccountStatusWidget(
uname: uname,
padding: EdgeInsets.zero,
),
const Gap(8),
],
),
),
IconButton(
onPressed: () {
SharePlus.instance.share(
ShareParams(
uri: Uri.parse(
'https://solian.app/@${data.name}',
),
),
);
},
icon: const Icon(Symbols.share),
),
],
).opacity(0.75),
const Gap(4),
AccountStatusWidget(uname: uname, padding: EdgeInsets.zero),
],
),
),
IconButton(
onPressed: () {
SharePlus.instance.share(
ShareParams(uri: Uri.parse('https://solian.app/@${data.name}')),
);
},
icon: const Icon(Symbols.share),
),
],
).padding(
left: 16,
right: 16,
top: 16 + (showBackground ? 16 : 0),
);
},
),
],
);
},
),
);
}
@@ -764,33 +842,14 @@ class AccountProfileScreen extends HookConsumerWidget {
color: appbarColor.value,
shadows: [appbarShadow],
),
flexibleSpace: Stack(
children: [
Positioned.fill(
child:
data.profile.background?.id != null
? CloudImageWidget(
file: data.profile.background,
)
: Container(
color:
Theme.of(
context,
).appBarTheme.backgroundColor,
),
),
FlexibleSpaceBar(
title: Text(
data.nick,
style: TextStyle(
color:
appbarColor.value ??
Theme.of(context).appBarTheme.foregroundColor,
shadows: [appbarShadow],
),
),
),
],
title: Text(
data.nick,
style: TextStyle(
color:
appbarColor.value ??
Theme.of(context).appBarTheme.foregroundColor,
shadows: [appbarShadow],
),
),
)
: null,
@@ -806,7 +865,7 @@ class AccountProfileScreen extends HookConsumerWidget {
data: data,
uname: name,
accountDeveloper: accountDeveloper,
),
).padding(horizontal: 4, top: 20),
),
if (data.badges.isNotEmpty)
SliverToBoxAdapter(
@@ -857,7 +916,12 @@ class AccountProfileScreen extends HookConsumerWidget {
Flexible(
child: CustomScrollView(
slivers: [
SliverGap(24),
SliverGap(18),
SliverToBoxAdapter(
child: ActivityPresenceWidget(
uname: name,
).padding(horizontal: 4, top: 4, bottom: 4),
),
SliverToBoxAdapter(
child: _AccountPublisherList(
publishers: accountPublishers.value ?? [],
@@ -883,9 +947,6 @@ class AccountProfileScreen extends HookConsumerWidget {
),
),
),
SliverToBoxAdapter(
child: ActivityPresenceWidget(uname: name),
),
],
),
),
@@ -937,7 +998,7 @@ class AccountProfileScreen extends HookConsumerWidget {
data: data,
uname: name,
accountDeveloper: accountDeveloper,
),
).padding(horizontal: 4, top: 8),
),
if (data.badges.isNotEmpty)
SliverToBoxAdapter(
@@ -981,6 +1042,11 @@ class AccountProfileScreen extends HookConsumerWidget {
data: data,
).padding(horizontal: 4),
),
SliverToBoxAdapter(
child: ActivityPresenceWidget(
uname: name,
).padding(horizontal: 8, top: 4, bottom: 4),
),
SliverToBoxAdapter(
child: _AccountPublisherList(
publishers: accountPublishers.value ?? [],
@@ -1010,11 +1076,6 @@ class AccountProfileScreen extends HookConsumerWidget {
),
).padding(horizontal: 4),
),
SliverToBoxAdapter(
child: ActivityPresenceWidget(
uname: name,
).padding(horizontal: 8, top: 4, bottom: 8),
),
],
),
);

View File

@@ -46,121 +46,178 @@ class _PublisherBasisWidget extends StatelessWidget {
@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,
return Card(
child: Builder(
builder: (context) {
final hasBackground = data.background?.id != null;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
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,
if (isWideScreen(context) && hasBackground)
Stack(
clipBehavior: Clip.none,
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),
),
ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(8),
topRight: Radius.circular(8),
),
child: AspectRatio(
aspectRatio: 16 / 7,
child: CloudImageWidget(
file: data.background,
fit: BoxFit.cover,
),
error: (_, _) => const SizedBox(),
loading:
() => const SizedBox(
height: 36,
child: Center(
child: SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
),
),
Positioned(
bottom: -24,
left: 16,
child: ProfilePictureWidget(
file: data.picture,
radius: 32,
borderRadius: data.type == 0 ? null : 12,
),
),
],
),
Builder(
builder: (context) {
final showBackground = isWideScreen(context) && hasBackground;
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: showBackground ? 0 : 20,
children: [
if (!showBackground)
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},
);
}
},
),
)
.padding(top: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
spacing: 6,
children: [
Text(data.nick).fontSize(20),
if (data.verification != null)
VerificationMark(mark: data.verification!),
if (isWideScreen(context))
Expanded(
child: Text(
'@${data.name}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
).fontSize(14).opacity(0.85),
),
],
),
if (!isWideScreen(context))
Text(
'@${data.name}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
).fontSize(14).opacity(0.85).padding(top: 4),
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(vertical: 8),
],
),
),
],
).padding(
left: 16,
right: 16,
top: 16 + (showBackground ? 16 : 0),
);
},
),
],
),
),
],
).padding(horizontal: 24, top: 24);
);
},
),
);
}
}
@@ -400,35 +457,14 @@ class PublisherProfileScreen extends HookConsumerWidget {
color: appbarColor.value,
shadows: [appbarShadow],
),
flexibleSpace: Stack(
children: [
Positioned.fill(
child:
data.background?.id != null
? CloudImageWidget(file: data.background)
: Container(
color:
Theme.of(
context,
).appBarTheme.backgroundColor,
),
),
FlexibleSpaceBar(
title: Text(
data.nick,
style: TextStyle(
color:
appbarColor.value ??
Theme.of(
context,
).appBarTheme.foregroundColor,
shadows: [appbarShadow],
),
),
background:
Container(), // Empty container since background is handled by Stack
),
],
title: Text(
data.nick,
style: TextStyle(
color:
appbarColor.value ??
Theme.of(context).appBarTheme.foregroundColor,
shadows: [appbarShadow],
),
),
)
: null,
@@ -477,7 +513,7 @@ class PublisherProfileScreen extends HookConsumerWidget {
subscribing: subscribing,
subscribe: subscribe,
unsubscribe: unsubscribe,
).padding(bottom: 8),
).padding(horizontal: 4, top: 20),
_PublisherBadgesWidget(
data: data,
badges: badges,
@@ -487,7 +523,7 @@ class PublisherProfileScreen extends HookConsumerWidget {
_PublisherHeatmapWidget(
heatmap: heatmap,
forceDense: true,
),
).padding(vertical: 4),
],
),
),
@@ -545,7 +581,7 @@ class PublisherProfileScreen extends HookConsumerWidget {
subscribing: subscribing,
subscribe: subscribe,
unsubscribe: unsubscribe,
).padding(bottom: 8),
).padding(horizontal: 4, top: 8),
),
SliverToBoxAdapter(
child: _PublisherBadgesWidget(
@@ -560,7 +596,7 @@ class PublisherProfileScreen extends HookConsumerWidget {
child: _PublisherBioWidget(data: data),
),
SliverToBoxAdapter(
child: _PublisherHeatmapWidget(heatmap: heatmap),
child: _PublisherHeatmapWidget(heatmap: heatmap).padding(vertical: 4),
),
SliverPostList(pubName: name, pinned: true),
SliverToBoxAdapter(

View File

@@ -256,7 +256,7 @@ class ActivityPresenceWidget extends ConsumerWidget {
children: [
Text(
'activities',
).tr().bold().padding(horizontal: 8, vertical: 4),
).tr().bold().padding(horizontal: 16, vertical: 4),
if (activities.isEmpty)
Row(
spacing: 4,
@@ -264,7 +264,7 @@ class ActivityPresenceWidget extends ConsumerWidget {
const Icon(Symbols.inbox, size: 16),
Text('dataEmpty').tr().fontSize(13),
],
).opacity(0.75).padding(horizontal: 8),
).opacity(0.75).padding(horizontal: 16, bottom: 8),
...activities.map((activity) {
final dcImages = _buildDiscordImages(ref, activity);