Compare commits
	
		
			2 Commits
		
	
	
		
			a7cb7170b8
			...
			217a0c0a54
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 217a0c0a54 | |||
| 5c0f7225e6 | 
@@ -104,7 +104,7 @@ void main() async {
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
final _appRouter = AppRouter();
 | 
			
		||||
final appRouter = AppRouter();
 | 
			
		||||
 | 
			
		||||
class IslandApp extends HookConsumerWidget {
 | 
			
		||||
  const IslandApp({super.key});
 | 
			
		||||
@@ -118,7 +118,7 @@ class IslandApp extends HookConsumerWidget {
 | 
			
		||||
        var uri = notification.data['action_uri'] as String;
 | 
			
		||||
        if (uri.startsWith('/')) {
 | 
			
		||||
          // In-app routes
 | 
			
		||||
          _appRouter.pushPath(notification.data['action_uri']);
 | 
			
		||||
          appRouter.pushPath(notification.data['action_uri']);
 | 
			
		||||
        } else {
 | 
			
		||||
          // External links
 | 
			
		||||
          launchUrlString(uri);
 | 
			
		||||
@@ -164,7 +164,7 @@ class IslandApp extends HookConsumerWidget {
 | 
			
		||||
      theme: theme?.light,
 | 
			
		||||
      darkTheme: theme?.dark,
 | 
			
		||||
      themeMode: ThemeMode.system,
 | 
			
		||||
      routerConfig: _appRouter.config(
 | 
			
		||||
      routerConfig: appRouter.config(
 | 
			
		||||
        navigatorObservers:
 | 
			
		||||
            () => [
 | 
			
		||||
              TabNavigationObserver(
 | 
			
		||||
@@ -187,9 +187,9 @@ class IslandApp extends HookConsumerWidget {
 | 
			
		||||
            OverlayEntry(
 | 
			
		||||
              builder:
 | 
			
		||||
                  (_) => WindowScaffold(
 | 
			
		||||
                    router: _appRouter,
 | 
			
		||||
                    router: appRouter,
 | 
			
		||||
                    child: TabsNavigationWidget(
 | 
			
		||||
                      router: _appRouter,
 | 
			
		||||
                      router: appRouter,
 | 
			
		||||
                      child: child ?? const SizedBox.shrink(),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
 
 | 
			
		||||
@@ -1,16 +1,16 @@
 | 
			
		||||
import 'dart:async';
 | 
			
		||||
import 'dart:developer';
 | 
			
		||||
import 'dart:io';
 | 
			
		||||
import 'package:flutter/foundation.dart';
 | 
			
		||||
import 'package:flutter_hooks/flutter_hooks.dart';
 | 
			
		||||
import 'package:collection/collection.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:auto_route/auto_route.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:freezed_annotation/freezed_annotation.dart';
 | 
			
		||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
			
		||||
import 'package:island/main.dart';
 | 
			
		||||
import 'package:island/models/user.dart';
 | 
			
		||||
import 'package:island/pods/websocket.dart';
 | 
			
		||||
import 'package:island/widgets/content/cloud_files.dart';
 | 
			
		||||
import 'package:material_symbols_icons/material_symbols_icons.dart';
 | 
			
		||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
 | 
			
		||||
import 'package:styled_widget/styled_widget.dart';
 | 
			
		||||
@@ -25,16 +25,16 @@ class AppNotificationToast extends HookConsumerWidget {
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context, WidgetRef ref) {
 | 
			
		||||
    final notifications = ref.watch(appNotificationsProvider);
 | 
			
		||||
    final isDesktop =
 | 
			
		||||
        !kIsWeb && (Platform.isMacOS || Platform.isWindows || Platform.isLinux);
 | 
			
		||||
 | 
			
		||||
    // Calculate position based on device type
 | 
			
		||||
    final safeAreaTop = MediaQuery.of(context).padding.top;
 | 
			
		||||
    final notificationTop = safeAreaTop + (isDesktop ? 30 : 10);
 | 
			
		||||
 | 
			
		||||
    // Create a global key for AnimatedList
 | 
			
		||||
    final listKey = useMemoized(() => GlobalKey<AnimatedListState>());
 | 
			
		||||
 | 
			
		||||
    // Track visual notification count (including those being animated out)
 | 
			
		||||
    final visualCount = useState(notifications.length);
 | 
			
		||||
 | 
			
		||||
    // Track notifications being removed to manage visual count
 | 
			
		||||
    final animatingOutIds = useState<Set<String>>({});
 | 
			
		||||
 | 
			
		||||
    // Track previous notifications to detect changes
 | 
			
		||||
    final previousNotifications = usePrevious(notifications) ?? [];
 | 
			
		||||
 | 
			
		||||
@@ -46,6 +46,11 @@ class AppNotificationToast extends HookConsumerWidget {
 | 
			
		||||
      // Find new notifications (added)
 | 
			
		||||
      final newIds = currentIds.difference(previousIds);
 | 
			
		||||
 | 
			
		||||
      // Update visual count for new notifications
 | 
			
		||||
      if (newIds.isNotEmpty) {
 | 
			
		||||
        visualCount.value += newIds.length;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Insert new notifications with animation
 | 
			
		||||
      for (final id in newIds) {
 | 
			
		||||
        final index = notifications.indexWhere((n) => n.data.id == id);
 | 
			
		||||
@@ -69,43 +74,12 @@ class AppNotificationToast extends HookConsumerWidget {
 | 
			
		||||
    }, [notifications]);
 | 
			
		||||
 | 
			
		||||
    return Positioned(
 | 
			
		||||
      top: notificationTop,
 | 
			
		||||
      top: MediaQuery.of(context).padding.top + 50,
 | 
			
		||||
      left: 16,
 | 
			
		||||
      right: 16,
 | 
			
		||||
      bottom: 16, // Add bottom constraint to make it take full height
 | 
			
		||||
      child: Column(
 | 
			
		||||
        children: [
 | 
			
		||||
          // Test button for development
 | 
			
		||||
          if (kDebugMode)
 | 
			
		||||
            ElevatedButton(
 | 
			
		||||
              onPressed: () {
 | 
			
		||||
                ref
 | 
			
		||||
                    .read(appNotificationsProvider.notifier)
 | 
			
		||||
                    .addNotification(
 | 
			
		||||
                      AppNotification(
 | 
			
		||||
                        data: SnNotification(
 | 
			
		||||
                          createdAt: DateTime.now(),
 | 
			
		||||
                          updatedAt: DateTime.now(),
 | 
			
		||||
                          deletedAt: null,
 | 
			
		||||
                          id:
 | 
			
		||||
                              'test-notification-${DateTime.now().millisecondsSinceEpoch}',
 | 
			
		||||
                          topic: 'test',
 | 
			
		||||
                          title: 'Test Notification',
 | 
			
		||||
                          content: 'This is a test notification content',
 | 
			
		||||
                          priority: 1,
 | 
			
		||||
                          viewedAt: null,
 | 
			
		||||
                          accountId: 'test-account-123',
 | 
			
		||||
                        ),
 | 
			
		||||
                        icon: Symbols.info,
 | 
			
		||||
                        createdAt: DateTime.now(),
 | 
			
		||||
                        duration: const Duration(seconds: 5),
 | 
			
		||||
                      ),
 | 
			
		||||
                    );
 | 
			
		||||
              },
 | 
			
		||||
              child: Text('test notification'),
 | 
			
		||||
            ),
 | 
			
		||||
          // Use AnimatedList for notifications with Expanded to take full height
 | 
			
		||||
          Expanded(
 | 
			
		||||
      child: SizedBox(
 | 
			
		||||
        // Use visualCount instead of notifications.length for height calculation
 | 
			
		||||
        height: visualCount.value * 80,
 | 
			
		||||
        child: AnimatedList(
 | 
			
		||||
          physics: NeverScrollableScrollPhysics(),
 | 
			
		||||
          padding: EdgeInsets.zero,
 | 
			
		||||
@@ -126,8 +100,10 @@ class AppNotificationToast extends HookConsumerWidget {
 | 
			
		||||
            final remainingTime = duration - elapsedTime;
 | 
			
		||||
            final progress =
 | 
			
		||||
                1.0 -
 | 
			
		||||
                    (remainingTime.inMilliseconds / duration.inMilliseconds)
 | 
			
		||||
                        .clamp(0.0, 1.0); // Ensure progress is clamped
 | 
			
		||||
                (remainingTime.inMilliseconds / duration.inMilliseconds).clamp(
 | 
			
		||||
                  0.0,
 | 
			
		||||
                  1.0,
 | 
			
		||||
                ); // Ensure progress is clamped
 | 
			
		||||
 | 
			
		||||
            return SizeTransition(
 | 
			
		||||
              sizeFactor: animation.drive(
 | 
			
		||||
@@ -142,6 +118,15 @@ class AppNotificationToast extends HookConsumerWidget {
 | 
			
		||||
                    (n) => n.data.id == notification.data.id,
 | 
			
		||||
                  );
 | 
			
		||||
 | 
			
		||||
                  // Add to animating out set
 | 
			
		||||
                  final notificationId = notification.data.id;
 | 
			
		||||
                  if (!animatingOutIds.value.contains(notificationId)) {
 | 
			
		||||
                    animatingOutIds.value = {
 | 
			
		||||
                      ...animatingOutIds.value,
 | 
			
		||||
                      notificationId,
 | 
			
		||||
                    };
 | 
			
		||||
                  }
 | 
			
		||||
 | 
			
		||||
                  if (currentIndex != -1 &&
 | 
			
		||||
                      listKey.currentState != null &&
 | 
			
		||||
                      currentIndex >= 0 &&
 | 
			
		||||
@@ -152,9 +137,7 @@ class AppNotificationToast extends HookConsumerWidget {
 | 
			
		||||
                        currentIndex,
 | 
			
		||||
                        (context, animation) => SizeTransition(
 | 
			
		||||
                          sizeFactor: animation.drive(
 | 
			
		||||
                                CurveTween(
 | 
			
		||||
                                  curve: Curves.fastLinearToSlowEaseIn,
 | 
			
		||||
                                ),
 | 
			
		||||
                            CurveTween(curve: Curves.fastLinearToSlowEaseIn),
 | 
			
		||||
                          ),
 | 
			
		||||
                          child: _NotificationCard(
 | 
			
		||||
                            notification: notification,
 | 
			
		||||
@@ -163,13 +146,31 @@ class AppNotificationToast extends HookConsumerWidget {
 | 
			
		||||
                                () {}, // Empty because it's being removed
 | 
			
		||||
                          ),
 | 
			
		||||
                        ),
 | 
			
		||||
                            duration: const Duration(
 | 
			
		||||
                              milliseconds: 150,
 | 
			
		||||
                            ), // Make removal animation faster
 | 
			
		||||
                        duration: const Duration(milliseconds: 150),
 | 
			
		||||
                        // When animation completes, update the visual count
 | 
			
		||||
                      );
 | 
			
		||||
 | 
			
		||||
                      // Schedule decrementing the visual count after animation completes
 | 
			
		||||
                      Future.delayed(const Duration(milliseconds: 150), () {
 | 
			
		||||
                        if (animatingOutIds.value.contains(notificationId)) {
 | 
			
		||||
                          visualCount.value =
 | 
			
		||||
                              visualCount.value > 0 ? visualCount.value - 1 : 0;
 | 
			
		||||
                          animatingOutIds.value =
 | 
			
		||||
                              animatingOutIds.value
 | 
			
		||||
                                  .where((id) => id != notificationId)
 | 
			
		||||
                                  .toSet();
 | 
			
		||||
                        }
 | 
			
		||||
                      });
 | 
			
		||||
                    } catch (e) {
 | 
			
		||||
                      // Log error but don't crash the app
 | 
			
		||||
                          debugPrint('Error removing notification: $e');
 | 
			
		||||
                      log('[Notification] Error removing notification: $e');
 | 
			
		||||
                      // Still update visual count in case of error
 | 
			
		||||
                      visualCount.value =
 | 
			
		||||
                          visualCount.value > 0 ? visualCount.value - 1 : 0;
 | 
			
		||||
                      animatingOutIds.value =
 | 
			
		||||
                          animatingOutIds.value
 | 
			
		||||
                              .where((id) => id != notificationId)
 | 
			
		||||
                              .toSet();
 | 
			
		||||
                    }
 | 
			
		||||
                  }
 | 
			
		||||
 | 
			
		||||
@@ -183,8 +184,6 @@ class AppNotificationToast extends HookConsumerWidget {
 | 
			
		||||
          },
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -235,7 +234,9 @@ class _NotificationCard extends HookConsumerWidget {
 | 
			
		||||
    return Card(
 | 
			
		||||
      elevation: 4,
 | 
			
		||||
      margin: const EdgeInsets.only(bottom: 8),
 | 
			
		||||
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
 | 
			
		||||
      shape: RoundedRectangleBorder(
 | 
			
		||||
        borderRadius: BorderRadius.vertical(bottom: Radius.circular(8)),
 | 
			
		||||
      ),
 | 
			
		||||
      child: InkWell(
 | 
			
		||||
        borderRadius: const BorderRadius.all(Radius.circular(8)),
 | 
			
		||||
        onTap: () {
 | 
			
		||||
@@ -243,11 +244,12 @@ class _NotificationCard extends HookConsumerWidget {
 | 
			
		||||
            var uri = notification.data.meta['action_uri'] as String;
 | 
			
		||||
            if (uri.startsWith('/')) {
 | 
			
		||||
              // In-app routes
 | 
			
		||||
              context.router.pushPath(notification.data.meta['action_uri']);
 | 
			
		||||
              appRouter.pushPath(notification.data.meta['action_uri']);
 | 
			
		||||
            } else {
 | 
			
		||||
              // External URLs
 | 
			
		||||
              launchUrlString(uri);
 | 
			
		||||
            }
 | 
			
		||||
            onDismiss();
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        child: Column(
 | 
			
		||||
@@ -265,9 +267,7 @@ class _NotificationCard extends HookConsumerWidget {
 | 
			
		||||
                    ),
 | 
			
		||||
                    value: 1.0 - progressState.value,
 | 
			
		||||
                    backgroundColor: Colors.transparent,
 | 
			
		||||
                    color: Theme.of(
 | 
			
		||||
                      context,
 | 
			
		||||
                    ).colorScheme.primary.withOpacity(0.5),
 | 
			
		||||
                    color: Theme.of(context).colorScheme.tertiary,
 | 
			
		||||
                    minHeight: 3,
 | 
			
		||||
                    stopIndicatorColor: Colors.transparent,
 | 
			
		||||
                    stopIndicatorRadius: 0,
 | 
			
		||||
@@ -279,7 +279,12 @@ class _NotificationCard extends HookConsumerWidget {
 | 
			
		||||
              child: Row(
 | 
			
		||||
                crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
                children: [
 | 
			
		||||
                  if (notification.icon != null)
 | 
			
		||||
                  if (notification.data.meta['avatar'] != null)
 | 
			
		||||
                    ProfilePictureWidget(
 | 
			
		||||
                      fileId: notification.data.meta['avatar'],
 | 
			
		||||
                      radius: 12,
 | 
			
		||||
                    ).padding(right: 12, top: 2)
 | 
			
		||||
                  else if (notification.icon != null)
 | 
			
		||||
                    Icon(
 | 
			
		||||
                      notification.icon,
 | 
			
		||||
                      color: Theme.of(context).colorScheme.primary,
 | 
			
		||||
@@ -298,12 +303,12 @@ class _NotificationCard extends HookConsumerWidget {
 | 
			
		||||
                          Text(
 | 
			
		||||
                            notification.data.content,
 | 
			
		||||
                            style: Theme.of(context).textTheme.bodyMedium,
 | 
			
		||||
                          ).padding(top: 4),
 | 
			
		||||
                          ),
 | 
			
		||||
                        if (notification.data.subtitle.isNotEmpty)
 | 
			
		||||
                          Text(
 | 
			
		||||
                            notification.data.subtitle,
 | 
			
		||||
                            style: Theme.of(context).textTheme.bodySmall,
 | 
			
		||||
                          ).padding(top: 2),
 | 
			
		||||
                          ),
 | 
			
		||||
                      ],
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user