Compare commits

..

16 Commits

Author SHA1 Message Date
001549b190 💄 Fix notification bottom sheet covered with sheet 2025-10-15 22:55:37 +08:00
4595865ad3 🐛 Bug fixes on windows 2025-10-15 22:49:43 +08:00
LittleSheep
1834643167 🔀 Merge pull request #184 from Texas0295/v3
[FIX] linux: restrict setAsFrameless to Wayland only
2025-10-15 20:05:40 +08:00
Texas0295
0e816eaa3e [FIX] wayland: restrict setAsFrameless to Wayland only
Prevent unconditional frameless calls on non-Wayland platforms.

Signed-off-by: Texas0295 <kimura@texas0295.top>
2025-10-15 02:24:40 +08:00
LittleSheep
7c1f24b824 🔀 Merge pull request #183 from Texas0295/v3
[FIX] linux: correct Wayland window buffer mismatch by setAsFreameless()
2025-10-15 00:47:58 +08:00
c6594ea2ce 🚀 Launch 3.3.0+136 2025-10-15 00:46:34 +08:00
3bec6e683e 🐛 Fix chat input not enter to send 2025-10-15 00:42:26 +08:00
83e92e2eed Show heatmap on pub profile 2025-10-15 00:27:32 +08:00
Texas0295
b7d44d96ba [FIX] linux: correct Wayland window buffer mismatch by setAsFreameless()
Wayland (Hyprland, Sway, etc.) compositors misreport window buffer geometry
when using transparent + hidden titlebar settings.

This causes Flutter to render outside the actual visible region
and the debug banner to be cropped offscreen.

Calling windowManager.setAsFrameless() once after window creation
forces compositor to reconfigure the surface and align buffer size
with the visible client area.

No effect on X11, Windows, or macOS.

Signed-off-by: Texas0295 <kimura@texas0295.top>
2025-10-15 00:06:03 +08:00
a83b929d42 ♻️ Convert the notification to sheet 2025-10-14 23:44:07 +08:00
9423affa75 🐛 Fix bugs 2025-10-14 23:01:40 +08:00
cda23db609 🐛 Make the background image not responsive for virtual keyboard to provide better visual experience on iOS 26 2025-10-14 22:53:59 +08:00
61074bc5a3 🐛 Fix compose dialog 2025-10-14 22:53:38 +08:00
5feafa9255 💄 Optimize quick reply input again... 2025-10-14 22:48:34 +08:00
e604577c1f 💄 Optimize quick reply input 2025-10-14 22:27:51 +08:00
af0ddd1273 💄 Optimize and bug fixes in auto complete 2025-10-14 22:26:06 +08:00
20 changed files with 322 additions and 105 deletions

View File

@@ -120,13 +120,24 @@ void main() async {
windowButtonVisibility: true, windowButtonVisibility: true,
); );
windowManager.waitUntilReadyToShow(windowOptions, () async { windowManager.waitUntilReadyToShow(windowOptions, () async {
final env = Platform.environment;
final isWayland = env.containsKey('WAYLAND_DISPLAY');
if (isWayland) {
try {
await windowManager.setAsFrameless();
} catch (e) {
debugPrint('[Wayland] setAsFrameless failed: $e');
}
}
await windowManager.setMinimumSize(defaultSize); await windowManager.setMinimumSize(defaultSize);
await windowManager.show(); await windowManager.show();
await windowManager.focus(); await windowManager.focus();
final opacity = prefs.getDouble(kAppWindowOpacity) ?? 1.0; final opacity = prefs.getDouble(kAppWindowOpacity) ?? 1.0;
await windowManager.setOpacity(opacity); await windowManager.setOpacity(opacity);
talker.info( talker.info(
"[SplashScreen] Desktop window is ready with size: ${initialSize.width}x${initialSize.height}", "[SplashScreen] Desktop window is ready with size: ${initialSize.width}x${initialSize.height}"
"${isWayland ? " (Wayland frameless fix applied)" : ""}",
); );
}); });
} }

View File

@@ -25,7 +25,6 @@ import 'package:island/screens/tabs.dart';
import 'package:island/screens/explore.dart'; import 'package:island/screens/explore.dart';
import 'package:island/screens/discovery/article_detail.dart'; import 'package:island/screens/discovery/article_detail.dart';
import 'package:island/screens/account.dart'; import 'package:island/screens/account.dart';
import 'package:island/screens/notification.dart';
import 'package:island/screens/wallet.dart'; import 'package:island/screens/wallet.dart';
import 'package:island/screens/account/relationship.dart'; import 'package:island/screens/account/relationship.dart';
import 'package:island/screens/account/profile.dart'; import 'package:island/screens/account/profile.dart';
@@ -392,11 +391,6 @@ final routerProvider = Provider<GoRouter>((ref) {
), ),
], ],
), ),
GoRoute(
name: 'notifications',
path: '/account/notifications',
builder: (context, state) => const NotificationScreen(),
),
GoRoute( GoRoute(
name: 'wallet', name: 'wallet',
path: '/account/wallet', path: '/account/wallet',

View File

@@ -282,7 +282,12 @@ class AccountScreen extends HookConsumerWidget {
], ],
), ),
onTap: () { onTap: () {
context.pushNamed('notifications'); showModalBottomSheet(
context: context,
isScrollControlled: true,
useRootNavigator: true,
builder: (context) => const NotificationSheet(),
);
}, },
), ),
ListTile( ListTile(

View File

@@ -20,6 +20,7 @@ class ArticleDetailScreen extends ConsumerWidget {
final articleAsync = ref.watch(articleDetailProvider(articleId)); final articleAsync = ref.watch(articleDetailProvider(articleId));
return AppScaffold( return AppScaffold(
isNoBackground: false,
body: articleAsync.when( body: articleAsync.when(
data: data:
(article) => AppScaffold( (article) => AppScaffold(

View File

@@ -54,7 +54,12 @@ Widget notificationIndicatorWidget(
trailing: const Icon(Symbols.chevron_right), trailing: const Icon(Symbols.chevron_right),
contentPadding: EdgeInsets.only(left: 16, right: 15), contentPadding: EdgeInsets.only(left: 16, right: 15),
onTap: () { onTap: () {
GoRouter.of(context).pushNamed('notifications'); showModalBottomSheet(
context: context,
isScrollControlled: true,
useRootNavigator: true,
builder: (context) => const NotificationSheet(),
);
}, },
), ),
); );

View File

@@ -3,7 +3,6 @@ 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:gap/gap.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/account.dart'; import 'package:island/models/account.dart';
@@ -11,8 +10,8 @@ import 'package:island/pods/network.dart';
import 'package:island/pods/websocket.dart'; import 'package:island/pods/websocket.dart';
import 'package:island/route.dart'; import 'package:island/route.dart';
import 'package:island/widgets/alert.dart'; import 'package:island/widgets/alert.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/content/markdown.dart'; import 'package:island/widgets/content/markdown.dart';
import 'package:island/widgets/content/sheet.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart';
import 'package:relative_time/relative_time.dart'; import 'package:relative_time/relative_time.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -113,8 +112,8 @@ class NotificationListNotifier extends _$NotificationListNotifier
} }
} }
class NotificationScreen extends HookConsumerWidget { class NotificationSheet extends HookConsumerWidget {
const NotificationScreen({super.key}); const NotificationSheet({super.key});
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
@@ -128,19 +127,15 @@ class NotificationScreen extends HookConsumerWidget {
ref.watch(notificationUnreadCountNotifierProvider.notifier).clear(); ref.watch(notificationUnreadCountNotifierProvider.notifier).clear();
} }
return AppScaffold( return SheetScaffold(
appBar: AppBar( titleText: 'notifications'.tr(),
leading: const PageBackButton(), actions: [
title: const Text('notifications').tr(), IconButton(
actions: [ onPressed: markAllRead,
IconButton( icon: const Icon(Symbols.mark_as_unread),
onPressed: markAllRead, ),
icon: const Icon(Symbols.mark_as_unread), ],
), child: PagingHelperView(
const Gap(8),
],
),
body: PagingHelperView(
provider: notificationListNotifierProvider, provider: notificationListNotifierProvider,
futureRefreshable: notificationListNotifierProvider.future, futureRefreshable: notificationListNotifierProvider.future,
notifierRefreshable: notificationListNotifierProvider.notifier, notifierRefreshable: notificationListNotifierProvider.notifier,

View File

@@ -8,6 +8,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/post.dart'; import 'package:island/models/post.dart';
import 'package:island/models/publisher.dart'; import 'package:island/models/publisher.dart';
import 'package:island/models/account.dart'; import 'package:island/models/account.dart';
import 'package:island/models/heatmap.dart';
import 'package:island/pods/config.dart'; import 'package:island/pods/config.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/services/color.dart'; import 'package:island/services/color.dart';
@@ -20,6 +21,7 @@ import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/content/cloud_files.dart'; import 'package:island/widgets/content/cloud_files.dart';
import 'package:island/widgets/content/markdown.dart'; import 'package:island/widgets/content/markdown.dart';
import 'package:island/widgets/post/post_list.dart'; import 'package:island/widgets/post/post_list.dart';
import 'package:island/widgets/activity_heatmap.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:island/services/color_extraction.dart'; import 'package:island/services/color_extraction.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -222,6 +224,32 @@ class _PublisherBioWidget extends StatelessWidget {
} }
} }
class _PublisherHeatmapWidget extends StatelessWidget {
final AsyncValue<SnHeatmap?> heatmap;
final bool forceDense;
const _PublisherHeatmapWidget({
required this.heatmap,
this.forceDense = false,
});
@override
Widget build(BuildContext context) {
return heatmap.when(
data:
(data) =>
data != null
? ActivityHeatmapWidget(
heatmap: data,
forceDense: forceDense,
).padding(horizontal: 8)
: const SizedBox.shrink(),
loading: () => const SizedBox.shrink(),
error: (_, _) => const SizedBox.shrink(),
);
}
}
class _PublisherCategoryTabWidget extends StatelessWidget { class _PublisherCategoryTabWidget extends StatelessWidget {
final TabController categoryTabController; final TabController categoryTabController;
@@ -292,6 +320,13 @@ Future<Color?> publisherAppbarForcegroundColor(Ref ref, String pubName) async {
} }
} }
@riverpod
Future<SnHeatmap?> publisherHeatmap(Ref ref, String uname) async {
final apiClient = ref.watch(apiClientProvider);
final resp = await apiClient.get('/sphere/publishers/$uname/heatmap');
return SnHeatmap.fromJson(resp.data);
}
class PublisherProfileScreen extends HookConsumerWidget { class PublisherProfileScreen extends HookConsumerWidget {
final String name; final String name;
const PublisherProfileScreen({super.key, required this.name}); const PublisherProfileScreen({super.key, required this.name});
@@ -301,6 +336,7 @@ class PublisherProfileScreen extends HookConsumerWidget {
final publisher = ref.watch(publisherProvider(name)); final publisher = ref.watch(publisherProvider(name));
final badges = ref.watch(publisherBadgesProvider(name)); final badges = ref.watch(publisherBadgesProvider(name));
final subStatus = ref.watch(publisherSubscriptionStatusProvider(name)); final subStatus = ref.watch(publisherSubscriptionStatusProvider(name));
final heatmap = ref.watch(publisherHeatmapProvider(name));
final appbarColor = ref.watch( final appbarColor = ref.watch(
publisherAppbarForcegroundColorProvider(name), publisherAppbarForcegroundColorProvider(name),
); );
@@ -446,6 +482,10 @@ class PublisherProfileScreen extends HookConsumerWidget {
), ),
_PublisherVerificationWidget(data: data), _PublisherVerificationWidget(data: data),
_PublisherBioWidget(data: data), _PublisherBioWidget(data: data),
_PublisherHeatmapWidget(
heatmap: heatmap,
forceDense: true,
),
], ],
), ),
), ),
@@ -517,6 +557,9 @@ class PublisherProfileScreen extends HookConsumerWidget {
SliverToBoxAdapter( SliverToBoxAdapter(
child: _PublisherBioWidget(data: data), child: _PublisherBioWidget(data: data),
), ),
SliverToBoxAdapter(
child: _PublisherHeatmapWidget(heatmap: heatmap),
),
SliverPostList(pubName: name, pinned: true), SliverPostList(pubName: name, pinned: true),
SliverToBoxAdapter( SliverToBoxAdapter(
child: _PublisherCategoryTabWidget( child: _PublisherCategoryTabWidget(

View File

@@ -530,5 +530,126 @@ class _PublisherAppbarForcegroundColorProviderElement
(origin as PublisherAppbarForcegroundColorProvider).pubName; (origin as PublisherAppbarForcegroundColorProvider).pubName;
} }
String _$publisherHeatmapHash() => r'86db275ce3861a2855b5ec35fbfef85fc47b23a6';
/// See also [publisherHeatmap].
@ProviderFor(publisherHeatmap)
const publisherHeatmapProvider = PublisherHeatmapFamily();
/// See also [publisherHeatmap].
class PublisherHeatmapFamily extends Family<AsyncValue<SnHeatmap?>> {
/// See also [publisherHeatmap].
const PublisherHeatmapFamily();
/// See also [publisherHeatmap].
PublisherHeatmapProvider call(String uname) {
return PublisherHeatmapProvider(uname);
}
@override
PublisherHeatmapProvider getProviderOverride(
covariant PublisherHeatmapProvider provider,
) {
return call(provider.uname);
}
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'publisherHeatmapProvider';
}
/// See also [publisherHeatmap].
class PublisherHeatmapProvider extends AutoDisposeFutureProvider<SnHeatmap?> {
/// See also [publisherHeatmap].
PublisherHeatmapProvider(String uname)
: this._internal(
(ref) => publisherHeatmap(ref as PublisherHeatmapRef, uname),
from: publisherHeatmapProvider,
name: r'publisherHeatmapProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$publisherHeatmapHash,
dependencies: PublisherHeatmapFamily._dependencies,
allTransitiveDependencies:
PublisherHeatmapFamily._allTransitiveDependencies,
uname: uname,
);
PublisherHeatmapProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.uname,
}) : super.internal();
final String uname;
@override
Override overrideWith(
FutureOr<SnHeatmap?> Function(PublisherHeatmapRef provider) create,
) {
return ProviderOverride(
origin: this,
override: PublisherHeatmapProvider._internal(
(ref) => create(ref as PublisherHeatmapRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
uname: uname,
),
);
}
@override
AutoDisposeFutureProviderElement<SnHeatmap?> createElement() {
return _PublisherHeatmapProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is PublisherHeatmapProvider && other.uname == uname;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, uname.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin PublisherHeatmapRef on AutoDisposeFutureProviderRef<SnHeatmap?> {
/// The parameter `uname` of this provider.
String get uname;
}
class _PublisherHeatmapProviderElement
extends AutoDisposeFutureProviderElement<SnHeatmap?>
with PublisherHeatmapRef {
_PublisherHeatmapProviderElement(super.provider);
@override
String get uname => (origin as PublisherHeatmapProvider).uname;
}
// ignore_for_file: type=lint // 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 // 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

View File

@@ -66,12 +66,12 @@ class TabsScreen extends HookConsumerWidget {
if (wideScreen) if (wideScreen)
NavigationDestination( NavigationDestination(
label: 'creatorHub'.tr(), label: 'creatorHub'.tr(),
icon: const Icon(Symbols.draw), icon: const Icon(Symbols.ink_pen),
), ),
if (wideScreen) if (wideScreen)
NavigationDestination( NavigationDestination(
label: 'developerHub'.tr(), label: 'developerHub'.tr(),
icon: const Icon(Symbols.code), icon: const Icon(Symbols.data_object),
), ),
]; ];
@@ -126,6 +126,7 @@ class TabsScreen extends HookConsumerWidget {
return Scaffold( return Scaffold(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
extendBody: true, extendBody: true,
resizeToAvoidBottomInset: false,
body: ClipRRect( body: ClipRRect(
borderRadius: const BorderRadius.only( borderRadius: const BorderRadius.only(
topLeft: Radius.circular(16), topLeft: Radius.circular(16),

View File

@@ -11,8 +11,13 @@ import '../services/responsive.dart';
/// Shows exactly 365 days (wide screen) or 90 days (non-wide screen) of data ending at the current date. /// Shows exactly 365 days (wide screen) or 90 days (non-wide screen) of data ending at the current date.
class ActivityHeatmapWidget extends HookConsumerWidget { class ActivityHeatmapWidget extends HookConsumerWidget {
final SnHeatmap heatmap; final SnHeatmap heatmap;
final bool forceDense;
const ActivityHeatmapWidget({super.key, required this.heatmap}); const ActivityHeatmapWidget({
super.key,
required this.heatmap,
this.forceDense = false,
});
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
@@ -21,7 +26,7 @@ class ActivityHeatmapWidget extends HookConsumerWidget {
final now = DateTime.now(); final now = DateTime.now();
final isWide = isWideScreen(context); final isWide = isWideScreen(context);
final days = isWide ? 365 : 90; final days = (isWide && !forceDense) ? 365 : 90;
// Start from exactly the selected days ago // Start from exactly the selected days ago
final startDate = now.subtract(Duration(days: days)); final startDate = now.subtract(Duration(days: days));

View File

@@ -314,28 +314,22 @@ class AppScaffold extends HookConsumerWidget {
final noBackground = isNoBackground ?? isWideScreen(context); final noBackground = isNoBackground ?? isWideScreen(context);
final content = Column( final builtWidget = Focus(
children: [
IgnorePointer(
child: SizedBox(height: appBar != null ? appBarHeight + safeTop : 0),
),
if (body != null) Expanded(child: body!),
],
);
return Focus(
focusNode: focusNode, focusNode: focusNode,
child: Scaffold( child: Scaffold(
extendBody: extendBody ?? true, extendBody: extendBody ?? true,
extendBodyBehindAppBar: true, extendBodyBehindAppBar: true,
backgroundColor: backgroundColor: Colors.transparent,
noBackground body: Column(
? Colors.transparent children: [
: Theme.of(context).scaffoldBackgroundColor, IgnorePointer(
body: child: SizedBox(
noBackground height: appBar != null ? appBarHeight + safeTop : 0,
? content ),
: AppBackground(isRoot: true, child: content), ),
if (body != null) Expanded(child: body!),
],
),
appBar: appBar, appBar: appBar,
bottomNavigationBar: bottomNavigationBar, bottomNavigationBar: bottomNavigationBar,
bottomSheet: bottomSheet, bottomSheet: bottomSheet,
@@ -348,6 +342,10 @@ class AppScaffold extends HookConsumerWidget {
onEndDrawerChanged: onEndDrawerChanged, onEndDrawerChanged: onEndDrawerChanged,
), ),
); );
return noBackground
? builtWidget
: AppBackground(isRoot: true, child: builtWidget);
} }
} }
@@ -376,10 +374,10 @@ class PageBackButton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isDesktop = final hasPageAction =
!kIsWeb && (Platform.isMacOS || Platform.isLinux || Platform.isWindows); !kIsWeb && Platform.isMacOS;
if (isDesktop && isWideScreen(context)) return const SizedBox.shrink(); if (hasPageAction && isWideScreen(context)) return const SizedBox.shrink();
return IconButton( return IconButton(
onPressed: () { onPressed: () {

View File

@@ -73,10 +73,8 @@ class ChatInput extends HookConsumerWidget {
final chatSubscribe = ref.watch(chatSubscribeNotifierProvider(chatRoom.id)); final chatSubscribe = ref.watch(chatSubscribeNotifierProvider(chatRoom.id));
void send() { void send() {
inputFocusNode.requestFocus();
onSend.call(); onSend.call();
WidgetsBinding.instance.addPostFrameCallback((_) {
inputFocusNode.requestFocus();
});
} }
void insertNewLine() { void insertNewLine() {
@@ -539,6 +537,10 @@ class ChatInput extends HookConsumerWidget {
onTapOutside: onTapOutside:
(_) => (_) =>
FocusManager.instance.primaryFocus?.unfocus(), FocusManager.instance.primaryFocus?.unfocus(),
textInputAction:
settings.enterToSend
? TextInputAction.send
: null,
onSubmitted: onSubmitted:
settings.enterToSend ? (_) => send() : null, settings.enterToSend ? (_) => send() : null,
); );
@@ -550,11 +552,13 @@ class ChatInput extends HookConsumerWidget {
final triggerIndex = final triggerIndex =
atIndex > colonIndex ? atIndex : colonIndex; atIndex > colonIndex ? atIndex : colonIndex;
if (triggerIndex == -1) return []; if (triggerIndex == -1) return [];
final chopped = pattern.substring(triggerIndex);
if (chopped.contains(' ')) return [];
final service = ref.read(autocompleteServiceProvider); final service = ref.read(autocompleteServiceProvider);
try { try {
return await service.getSuggestions( return await service.getSuggestions(
chatRoom.id, chatRoom.id,
pattern, chopped,
); );
} catch (e) { } catch (e) {
return []; return [];
@@ -645,7 +649,7 @@ class ChatInput extends HookConsumerWidget {
direction: VerticalDirection.up, direction: VerticalDirection.up,
hideOnEmpty: true, hideOnEmpty: true,
hideOnLoading: true, hideOnLoading: true,
debounceDuration: const Duration(milliseconds: 500), debounceDuration: const Duration(milliseconds: 1000),
), ),
), ),
IconButton( IconButton(

View File

@@ -14,7 +14,6 @@ import 'package:island/pods/chat/messages_notifier.dart';
import 'package:island/pods/translate.dart'; import 'package:island/pods/translate.dart';
import 'package:island/pods/config.dart'; import 'package:island/pods/config.dart';
import 'package:island/widgets/account/account_pfc.dart'; import 'package:island/widgets/account/account_pfc.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/chat/message_content.dart'; import 'package:island/widgets/chat/message_content.dart';
import 'package:island/widgets/chat/message_indicators.dart'; import 'package:island/widgets/chat/message_indicators.dart';
import 'package:island/widgets/chat/message_sender_info.dart'; import 'package:island/widgets/chat/message_sender_info.dart';
@@ -666,17 +665,11 @@ class MessageItemDisplayBubble extends HookConsumerWidget {
? Theme.of(context).colorScheme.primaryContainer.withOpacity(0.5) ? Theme.of(context).colorScheme.primaryContainer.withOpacity(0.5)
: Theme.of(context).colorScheme.surfaceContainer; : Theme.of(context).colorScheme.surfaceContainer;
final hasBackground =
ref.watch(backgroundImageFileProvider).valueOrNull != null;
final remoteMessage = message.toRemoteMessage(); final remoteMessage = message.toRemoteMessage();
final sender = remoteMessage.sender; final sender = remoteMessage.sender;
return Material( return Material(
color: color: Colors.transparent,
hasBackground
? Colors.transparent
: Theme.of(context).colorScheme.surface,
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
child: Column( child: Column(

View File

@@ -23,12 +23,12 @@ class PostComposeDialog extends HookConsumerWidget {
this.isBottomSheet = false, this.isBottomSheet = false,
}); });
static Future<SnPost?> show( static Future<bool?> show(
BuildContext context, { BuildContext context, {
SnPost? originalPost, SnPost? originalPost,
PostComposeInitialState? initialState, PostComposeInitialState? initialState,
}) { }) {
return showDialog<SnPost>( return showDialog<bool>(
context: context, context: context,
useRootNavigator: true, useRootNavigator: true,
builder: builder:

View File

@@ -149,9 +149,11 @@ class ComposeFormFields extends HookConsumerWidget {
final triggerIndex = final triggerIndex =
atIndex > colonIndex ? atIndex : colonIndex; atIndex > colonIndex ? atIndex : colonIndex;
if (triggerIndex == -1) return []; if (triggerIndex == -1) return [];
final chopped = pattern.substring(triggerIndex);
if (chopped.contains(' ')) return [];
final service = ref.read(autocompleteServiceProvider); final service = ref.read(autocompleteServiceProvider);
try { try {
return await service.getGeneralSuggestions(pattern); return await service.getGeneralSuggestions(chopped);
} catch (e) { } catch (e) {
return []; return [];
} }
@@ -235,7 +237,7 @@ class ComposeFormFields extends HookConsumerWidget {
direction: VerticalDirection.down, direction: VerticalDirection.down,
hideOnEmpty: true, hideOnEmpty: true,
hideOnLoading: true, hideOnLoading: true,
debounceDuration: const Duration(milliseconds: 500), debounceDuration: const Duration(milliseconds: 1000),
), ),
], ],
), ),

View File

@@ -23,6 +23,7 @@ class PostListNotifier extends _$PostListNotifier
List<String>? tags, List<String>? tags,
bool? pinned, bool? pinned,
bool shuffle = false, bool shuffle = false,
bool? includeReplies,
}) { }) {
return fetch(cursor: null); return fetch(cursor: null);
} }
@@ -42,6 +43,7 @@ class PostListNotifier extends _$PostListNotifier
if (categories != null) 'categories': categories, if (categories != null) 'categories': categories,
if (shuffle) 'shuffle': true, if (shuffle) 'shuffle': true,
if (pinned != null) 'pinned': pinned, if (pinned != null) 'pinned': pinned,
if (includeReplies != null) 'includeReplies': includeReplies,
}; };
final response = await client.get( final response = await client.get(

View File

@@ -6,7 +6,7 @@ part of 'post_list.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$postListNotifierHash() => r'3c0a8154ded4bcd8f5456f7a4ea2e542f57efa85'; String _$postListNotifierHash() => r'fc139ad4df0deb67bcbb949560319f2f7fbfb503';
/// Copied from Dart SDK /// Copied from Dart SDK
class _SystemHash { class _SystemHash {
@@ -38,6 +38,7 @@ abstract class _$PostListNotifier
late final List<String>? tags; late final List<String>? tags;
late final bool? pinned; late final bool? pinned;
late final bool shuffle; late final bool shuffle;
late final bool? includeReplies;
FutureOr<CursorPagingData<SnPost>> build({ FutureOr<CursorPagingData<SnPost>> build({
String? pubName, String? pubName,
@@ -47,6 +48,7 @@ abstract class _$PostListNotifier
List<String>? tags, List<String>? tags,
bool? pinned, bool? pinned,
bool shuffle = false, bool shuffle = false,
bool? includeReplies,
}); });
} }
@@ -69,6 +71,7 @@ class PostListNotifierFamily
List<String>? tags, List<String>? tags,
bool? pinned, bool? pinned,
bool shuffle = false, bool shuffle = false,
bool? includeReplies,
}) { }) {
return PostListNotifierProvider( return PostListNotifierProvider(
pubName: pubName, pubName: pubName,
@@ -78,6 +81,7 @@ class PostListNotifierFamily
tags: tags, tags: tags,
pinned: pinned, pinned: pinned,
shuffle: shuffle, shuffle: shuffle,
includeReplies: includeReplies,
); );
} }
@@ -93,6 +97,7 @@ class PostListNotifierFamily
tags: provider.tags, tags: provider.tags,
pinned: provider.pinned, pinned: provider.pinned,
shuffle: provider.shuffle, shuffle: provider.shuffle,
includeReplies: provider.includeReplies,
); );
} }
@@ -127,6 +132,7 @@ class PostListNotifierProvider
List<String>? tags, List<String>? tags,
bool? pinned, bool? pinned,
bool shuffle = false, bool shuffle = false,
bool? includeReplies,
}) : this._internal( }) : this._internal(
() => () =>
PostListNotifier() PostListNotifier()
@@ -136,7 +142,8 @@ class PostListNotifierProvider
..categories = categories ..categories = categories
..tags = tags ..tags = tags
..pinned = pinned ..pinned = pinned
..shuffle = shuffle, ..shuffle = shuffle
..includeReplies = includeReplies,
from: postListNotifierProvider, from: postListNotifierProvider,
name: r'postListNotifierProvider', name: r'postListNotifierProvider',
debugGetCreateSourceHash: debugGetCreateSourceHash:
@@ -153,6 +160,7 @@ class PostListNotifierProvider
tags: tags, tags: tags,
pinned: pinned, pinned: pinned,
shuffle: shuffle, shuffle: shuffle,
includeReplies: includeReplies,
); );
PostListNotifierProvider._internal( PostListNotifierProvider._internal(
@@ -169,6 +177,7 @@ class PostListNotifierProvider
required this.tags, required this.tags,
required this.pinned, required this.pinned,
required this.shuffle, required this.shuffle,
required this.includeReplies,
}) : super.internal(); }) : super.internal();
final String? pubName; final String? pubName;
@@ -178,6 +187,7 @@ class PostListNotifierProvider
final List<String>? tags; final List<String>? tags;
final bool? pinned; final bool? pinned;
final bool shuffle; final bool shuffle;
final bool? includeReplies;
@override @override
FutureOr<CursorPagingData<SnPost>> runNotifierBuild( FutureOr<CursorPagingData<SnPost>> runNotifierBuild(
@@ -191,6 +201,7 @@ class PostListNotifierProvider
tags: tags, tags: tags,
pinned: pinned, pinned: pinned,
shuffle: shuffle, shuffle: shuffle,
includeReplies: includeReplies,
); );
} }
@@ -207,7 +218,8 @@ class PostListNotifierProvider
..categories = categories ..categories = categories
..tags = tags ..tags = tags
..pinned = pinned ..pinned = pinned
..shuffle = shuffle, ..shuffle = shuffle
..includeReplies = includeReplies,
from: from, from: from,
name: null, name: null,
dependencies: null, dependencies: null,
@@ -220,6 +232,7 @@ class PostListNotifierProvider
tags: tags, tags: tags,
pinned: pinned, pinned: pinned,
shuffle: shuffle, shuffle: shuffle,
includeReplies: includeReplies,
), ),
); );
} }
@@ -242,7 +255,8 @@ class PostListNotifierProvider
other.categories == categories && other.categories == categories &&
other.tags == tags && other.tags == tags &&
other.pinned == pinned && other.pinned == pinned &&
other.shuffle == shuffle; other.shuffle == shuffle &&
other.includeReplies == includeReplies;
} }
@override @override
@@ -255,6 +269,7 @@ class PostListNotifierProvider
hash = _SystemHash.combine(hash, tags.hashCode); hash = _SystemHash.combine(hash, tags.hashCode);
hash = _SystemHash.combine(hash, pinned.hashCode); hash = _SystemHash.combine(hash, pinned.hashCode);
hash = _SystemHash.combine(hash, shuffle.hashCode); hash = _SystemHash.combine(hash, shuffle.hashCode);
hash = _SystemHash.combine(hash, includeReplies.hashCode);
return _SystemHash.finish(hash); return _SystemHash.finish(hash);
} }
@@ -284,6 +299,9 @@ mixin PostListNotifierRef
/// The parameter `shuffle` of this provider. /// The parameter `shuffle` of this provider.
bool get shuffle; bool get shuffle;
/// The parameter `includeReplies` of this provider.
bool? get includeReplies;
} }
class _PostListNotifierProviderElement class _PostListNotifierProviderElement
@@ -310,6 +328,9 @@ class _PostListNotifierProviderElement
bool? get pinned => (origin as PostListNotifierProvider).pinned; bool? get pinned => (origin as PostListNotifierProvider).pinned;
@override @override
bool get shuffle => (origin as PostListNotifierProvider).shuffle; bool get shuffle => (origin as PostListNotifierProvider).shuffle;
@override
bool? get includeReplies =>
(origin as PostListNotifierProvider).includeReplies;
} }
// ignore_for_file: type=lint // ignore_for_file: type=lint

View File

@@ -68,21 +68,24 @@ class PostQuickReply extends HookConsumerWidget {
} }
} }
const kInputChipHeight = 54.0;
return publishers.when( return publishers.when(
data: data:
(data) => Material( (data) => Material(
elevation: 2, elevation: 2,
color: Theme.of(context).colorScheme.surfaceContainerHighest, color: Theme.of(context).colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(28),
child: Padding( child: Container(
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 8), constraints: BoxConstraints(minHeight: kInputChipHeight),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
GestureDetector( GestureDetector(
child: ProfilePictureWidget( child: ProfilePictureWidget(
fileId: currentPublisher.value?.picture?.id, fileId: currentPublisher.value?.picture?.id,
radius: 16, radius: (kInputChipHeight * 0.5) - 6,
), ),
onTap: () { onTap: () {
showModalBottomSheet( showModalBottomSheet(
@@ -106,11 +109,13 @@ class PostQuickReply extends HookConsumerWidget {
isCollapsed: true, isCollapsed: true,
contentPadding: EdgeInsets.symmetric( contentPadding: EdgeInsets.symmetric(
horizontal: 12, horizontal: 12,
vertical: 9, vertical: 14,
), ),
visualDensity: VisualDensity.compact,
), ),
style: TextStyle(fontSize: 14), style: TextStyle(fontSize: 14),
maxLines: null, minLines: 1,
maxLines: 5,
onTapOutside: onTapOutside:
(_) => FocusManager.instance.primaryFocus?.unfocus(), (_) => FocusManager.instance.primaryFocus?.unfocus(),
), ),
@@ -130,6 +135,10 @@ class PostQuickReply extends HookConsumerWidget {
}, },
icon: const Icon(Symbols.launch, size: 20), icon: const Icon(Symbols.launch, size: 20),
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
constraints: BoxConstraints(
maxHeight: kInputChipHeight - 6,
minHeight: kInputChipHeight - 6,
),
), ),
IconButton( IconButton(
icon: icon:
@@ -143,6 +152,10 @@ class PostQuickReply extends HookConsumerWidget {
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
onPressed: submitting.value ? null : performAction, onPressed: submitting.value ? null : performAction,
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
constraints: BoxConstraints(
maxHeight: kInputChipHeight - 6,
minHeight: kInputChipHeight - 6,
),
), ),
], ],
), ),

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/post.dart'; import 'package:island/models/post.dart';
import 'package:island/pods/userinfo.dart'; import 'package:island/pods/userinfo.dart';
@@ -19,35 +20,37 @@ class PostRepliesSheet extends HookConsumerWidget {
return SheetScaffold( return SheetScaffold(
titleText: 'repliesCount'.plural(post.repliesCount), titleText: 'repliesCount'.plural(post.repliesCount),
child: Column( child: Stack(
children: [ children: [
// Replies list CustomScrollView(
Expanded( slivers: [
child: CustomScrollView( PostRepliesList(
slivers: [ postId: post.id.toString(),
PostRepliesList( onOpen: () {
postId: post.id.toString(), Navigator.pop(context);
onOpen: () { },
Navigator.pop(context); ),
}, SliverGap(80),
), ],
],
),
), ),
// Quick reply section
if (user.value != null) if (user.value != null)
PostQuickReply( Positioned(
parent: post, bottom: 0,
onPosted: () { left: 0,
ref.invalidate(postRepliesNotifierProvider(post.id)); right: 0,
}, child: PostQuickReply(
onLaunch: () { parent: post,
Navigator.of(context).pop(); onPosted: () {
}, ref.invalidate(postRepliesNotifierProvider(post.id));
).padding( },
bottom: MediaQuery.of(context).padding.bottom + 16, onLaunch: () {
top: 8, Navigator.of(context).pop();
horizontal: 16, },
).padding(
bottom: MediaQuery.of(context).padding.bottom + 16,
top: 8,
horizontal: 16,
),
), ),
], ],
), ),

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: 3.3.0+135 version: 3.3.0+136
environment: environment:
sdk: ^3.7.2 sdk: ^3.7.2