Compare commits

..

2 Commits

Author SHA1 Message Date
a20c2598fc 🐛 Fixes render errors of unauthorized 2025-07-03 00:49:11 +08:00
2eba871a6d 💄 Web article has max width 2025-07-03 00:39:45 +08:00
12 changed files with 153 additions and 117 deletions

View File

@ -532,7 +532,7 @@
"aboutScreenContactUsTitle": "联系我们", "aboutScreenContactUsTitle": "联系我们",
"aboutScreenLicenseTitle": "许可证", "aboutScreenLicenseTitle": "许可证",
"aboutScreenLicenseContent": "GNU Affero General Public License v3.0", "aboutScreenLicenseContent": "GNU Affero General Public License v3.0",
"aboutScreenCopyright": "版权所有 © Solsynth {}", "aboutScreenCopyright": "版权所有 © 索尔辛茨 {}",
"aboutScreenMadeWith": "由 Solar Network Team 用 ❤︎️ 制作", "aboutScreenMadeWith": "由 Solar Network Team 用 ❤︎️ 制作",
"aboutScreenFailedToLoadPackageInfo": "加载包信息失败:{error}", "aboutScreenFailedToLoadPackageInfo": "加载包信息失败:{error}",
"copiedToClipboard": "已复制到剪贴板", "copiedToClipboard": "已复制到剪贴板",

View File

@ -221,7 +221,7 @@ class IslandApp extends HookConsumerWidget {
Future(() { Future(() {
userNotifier.fetchUser().then((_) { userNotifier.fetchUser().then((_) {
final user = ref.watch(userInfoProvider); final user = ref.watch(userInfoProvider);
if (user.hasValue) { if (user.value != null) {
final apiClient = ref.read(apiClientProvider); final apiClient = ref.read(apiClientProvider);
subscribePushNotification(apiClient); subscribePushNotification(apiClient);
final wsNotifier = ref.read(websocketStateProvider.notifier); final wsNotifier = ref.read(websocketStateProvider.notifier);

View File

@ -18,8 +18,13 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> {
final user = SnAccount.fromJson(response.data); final user = SnAccount.fromJson(response.data);
state = AsyncValue.data(user); state = AsyncValue.data(user);
} catch (error, stackTrace) { } catch (error, stackTrace) {
log("[UserInfo] Failed to fetch user info: $error"); log(
state = AsyncValue.error(error, stackTrace); "[UserInfo] Failed to fetch user info...",
name: 'UserInfoNotifier',
error: error,
stackTrace: stackTrace,
);
state = AsyncValue.data(null);
} }
} }

View File

@ -59,7 +59,7 @@ class AccountScreen extends HookConsumerWidget {
notificationUnreadCountNotifierProvider, notificationUnreadCountNotifierProvider,
); );
if (!user.hasValue || user.value == null) { if (user.value == null || user.value == null) {
return _UnauthorizedAccountScreen(); return _UnauthorizedAccountScreen();
} }
@ -367,12 +367,23 @@ class _UnauthorizedAccountScreen extends StatelessWidget {
), ),
), ),
const Gap(8), const Gap(8),
TextButton( Row(
onPressed: () { mainAxisAlignment: MainAxisAlignment.center,
context.push('/settings'); children: [
}, TextButton(
child: Text('appSettings').tr(), onPressed: () {
).center(), context.push('/about');
},
child: Text('about').tr(),
),
TextButton(
onPressed: () {
context.push('/settings');
},
child: Text('appSettings').tr(),
),
],
),
], ],
), ),
).center(), ).center(),

View File

@ -82,7 +82,7 @@ class EventCalanderScreen extends HookConsumerWidget {
), ),
// Show user profile if viewing someone else's calendar // Show user profile if viewing someone else's calendar
if (name != 'me' && user.hasValue) if (name != 'me' && user.value != null)
AccountNameplate(name: name), AccountNameplate(name: name),
], ],
), ),
@ -106,7 +106,7 @@ class EventCalanderScreen extends HookConsumerWidget {
).padding(horizontal: 8, vertical: 4), ).padding(horizontal: 8, vertical: 4),
// Show user profile if viewing someone else's calendar // Show user profile if viewing someone else's calendar
if (name != 'me' && user.hasValue) if (name != 'me' && user.value != null)
AccountNameplate(name: name), AccountNameplate(name: name),
Gap(MediaQuery.of(context).padding.bottom + 16), Gap(MediaQuery.of(context).padding.bottom + 16),
], ],

View File

@ -72,6 +72,8 @@ Future<Color?> accountAppbarForcegroundColor(Ref ref, String uname) async {
@riverpod @riverpod
Future<SnChatRoom?> accountDirectChat(Ref ref, String uname) async { Future<SnChatRoom?> accountDirectChat(Ref ref, String uname) async {
final userInfo = ref.watch(userInfoProvider);
if (userInfo.value == null) return null;
final account = await ref.watch(accountProvider(uname).future); final account = await ref.watch(accountProvider(uname).future);
final apiClient = ref.watch(apiClientProvider); final apiClient = ref.watch(apiClientProvider);
try { try {
@ -87,6 +89,8 @@ Future<SnChatRoom?> accountDirectChat(Ref ref, String uname) async {
@riverpod @riverpod
Future<SnRelationship?> accountRelationship(Ref ref, String uname) async { Future<SnRelationship?> accountRelationship(Ref ref, String uname) async {
final userInfo = ref.watch(userInfoProvider);
if (userInfo.value == null) return null;
final account = await ref.watch(accountProvider(uname).future); final account = await ref.watch(accountProvider(uname).future);
final apiClient = ref.watch(apiClientProvider); final apiClient = ref.watch(apiClientProvider);
try { try {
@ -219,6 +223,8 @@ class AccountProfileScreen extends HookConsumerWidget {
]; ];
} }
final user = ref.watch(userInfoProvider);
return account.when( return account.when(
data: data:
(data) => AppScaffold( (data) => AppScaffold(
@ -379,56 +385,60 @@ class AccountProfileScreen extends HookConsumerWidget {
).padding(horizontal: 24), ).padding(horizontal: 24),
), ),
SliverToBoxAdapter( if (user.value != null)
child: const Divider(height: 1).padding(top: 24, bottom: 12), SliverToBoxAdapter(
), child: const Divider(
SliverToBoxAdapter( height: 1,
child: Row( ).padding(top: 24, bottom: 12),
spacing: 8, ),
children: [ if (user.value != null)
Expanded( SliverToBoxAdapter(
child: FilledButton.icon( child: Row(
style: ButtonStyle( spacing: 8,
backgroundColor: WidgetStatePropertyAll( children: [
accountRelationship.value == null Expanded(
? null child: FilledButton.icon(
: Theme.of(context).colorScheme.secondary, style: ButtonStyle(
), backgroundColor: WidgetStatePropertyAll(
foregroundColor: WidgetStatePropertyAll(
accountRelationship.value == null
? null
: Theme.of(context).colorScheme.onSecondary,
),
),
onPressed: relationshipAction,
label:
Text(
accountRelationship.value == null accountRelationship.value == null
? 'addFriendShort' ? null
: 'added', : Theme.of(context).colorScheme.secondary,
).tr(), ),
icon: foregroundColor: WidgetStatePropertyAll(
accountRelationship.value == null accountRelationship.value == null
? const Icon(Symbols.person_add) ? null
: const Icon(Symbols.person_check), : 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),
),
), ),
), Expanded(
Expanded( child: FilledButton.icon(
child: FilledButton.icon( onPressed: directMessageAction,
onPressed: directMessageAction, icon: const Icon(Symbols.message),
icon: const Icon(Symbols.message), label:
label: Text(
Text( accountChat.value == null
accountChat.value == null ? 'createDirectMessage'
? 'createDirectMessage' : 'gotoDirectMessage',
: 'gotoDirectMessage', maxLines: 1,
maxLines: 1, ).tr(),
).tr(), ),
), ),
), ],
], ).padding(horizontal: 16),
).padding(horizontal: 16), ),
),
SliverToBoxAdapter( SliverToBoxAdapter(
child: const Divider(height: 1).padding(top: 12), child: const Divider(height: 1).padding(top: 12),
), ),

View File

@ -51,54 +51,59 @@ class _ArticleDetailContent extends HookConsumerWidget {
); );
return SingleChildScrollView( return SingleChildScrollView(
child: Column( child: Center(
crossAxisAlignment: CrossAxisAlignment.stretch, child: ConstrainedBox(
children: [ constraints: const BoxConstraints(maxWidth: 560),
if (article.preview?.imageUrl != null) child: Column(
Image.network( crossAxisAlignment: CrossAxisAlignment.stretch,
article.preview!.imageUrl!, children: [
width: double.infinity, if (article.preview?.imageUrl != null)
height: 200, Image.network(
fit: BoxFit.cover, article.preview!.imageUrl!,
), width: double.infinity,
Padding( height: 200,
padding: const EdgeInsets.all(16.0), fit: BoxFit.cover,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
article.title,
style: Theme.of(context).textTheme.headlineSmall,
), ),
const SizedBox(height: 8), Padding(
if (article.feed?.title != null) padding: const EdgeInsets.all(16.0),
Text( child: Column(
article.feed!.title, crossAxisAlignment: CrossAxisAlignment.start,
style: Theme.of(context).textTheme.bodyMedium?.copyWith( children: [
color: Theme.of(context).colorScheme.onSurfaceVariant, Text(
article.title,
style: Theme.of(context).textTheme.headlineSmall,
), ),
), const SizedBox(height: 8),
const Divider(height: 32), if (article.feed?.title != null)
if (article.content != null) Text(
...MarkdownTextContent.buildGenerator( article.feed!.title,
isDark: Theme.of(context).brightness == Brightness.dark, style: Theme.of(context).textTheme.bodyMedium?.copyWith(
).buildWidgets(markdownContent) color: Theme.of(context).colorScheme.onSurfaceVariant,
else if (article.preview?.description != null) ),
Text(article.preview!.description!),
const Gap(24),
FilledButton(
onPressed:
() => launchUrlString(
article.url,
mode: LaunchMode.externalApplication,
), ),
child: const Text('Read Full Article'), const Divider(height: 32),
if (article.content != null)
...MarkdownTextContent.buildGenerator(
isDark: Theme.of(context).brightness == Brightness.dark,
).buildWidgets(markdownContent)
else if (article.preview?.description != null)
Text(article.preview!.description!),
const Gap(24),
FilledButton(
onPressed:
() => launchUrlString(
article.url,
mode: LaunchMode.externalApplication,
),
child: const Text('Read Full Article'),
),
Gap(MediaQuery.of(context).padding.bottom),
],
), ),
Gap(MediaQuery.of(context).padding.bottom), ),
], ],
),
), ),
], ),
), ),
); );
} }

View File

@ -126,16 +126,21 @@ class ArticlesScreen extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return Scaffold( return Scaffold(
appBar: AppBar(title: Text(title ?? 'Articles')), appBar: AppBar(title: Text(title ?? 'Articles')),
body: CustomScrollView( body: Center(
slivers: [ child: ConstrainedBox(
SliverPadding( constraints: const BoxConstraints(maxWidth: 560),
padding: const EdgeInsets.only(top: 8, left: 8, right: 8), child: CustomScrollView(
sliver: SliverArticlesList( slivers: [
feedId: feedId, SliverPadding(
publisherId: publisherId, padding: const EdgeInsets.only(top: 8, left: 8, right: 8),
), sliver: SliverArticlesList(
feedId: feedId,
publisherId: publisherId,
),
),
],
), ),
], ),
), ),
); );
} }

View File

@ -307,7 +307,7 @@ class _ActivityListView extends HookConsumerWidget {
return CustomScrollView( return CustomScrollView(
slivers: [ slivers: [
if (user.hasValue && !contentOnly) if (user.value != null && !contentOnly)
SliverToBoxAdapter(child: CheckInWidget()), SliverToBoxAdapter(child: CheckInWidget()),
SliverList.builder( SliverList.builder(
itemCount: widgetCount, itemCount: widgetCount,

View File

@ -59,7 +59,7 @@ class AccountStatusCreationSheet extends HookConsumerWidget {
}, },
options: Options(method: initialStatus == null ? 'POST' : 'PATCH'), options: Options(method: initialStatus == null ? 'POST' : 'PATCH'),
); );
if (user.hasValue) { if (user.value != null) {
ref.invalidate(accountStatusProvider(user.value!.name)); ref.invalidate(accountStatusProvider(user.value!.name));
} }
if (!context.mounted) return; if (!context.mounted) return;

View File

@ -350,7 +350,7 @@ class _WebSocketIndicator extends HookConsumerWidget {
return AnimatedPositioned( return AnimatedPositioned(
duration: Duration(milliseconds: 1850), duration: Duration(milliseconds: 1850),
top: top:
!user.hasValue || user.value == null ||
user.value == null || user.value == null ||
websocketState == WebSocketState.connected() websocketState == WebSocketState.connected()
? -indicatorHeight ? -indicatorHeight
@ -362,7 +362,7 @@ class _WebSocketIndicator extends HookConsumerWidget {
child: IgnorePointer( child: IgnorePointer(
child: Material( child: Material(
elevation: elevation:
!user.hasValue || websocketState == WebSocketState.connected() user.value == null || websocketState == WebSocketState.connected()
? 0 ? 0
: 4, : 4,
child: AnimatedContainer( child: AnimatedContainer(

View File

@ -56,7 +56,7 @@ class PostItem extends HookConsumerWidget {
final user = ref.watch(userInfoProvider); final user = ref.watch(userInfoProvider);
final isAuthor = useMemoized( final isAuthor = useMemoized(
() => user.hasValue && user.value?.id == item.publisher.accountId, () => user.value != null && user.value?.id == item.publisher.accountId,
[user], [user],
); );