Compare commits
	
		
			3 Commits
		
	
	
		
			2.4.2+78
			...
			e88dea8858
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| e88dea8858 | |||
| 813679b161 | |||
| 9d4ce6ca8c | 
@@ -15,12 +15,10 @@ body:json {
 | 
			
		||||
    "client_id": "{{third_client_id}}",
 | 
			
		||||
    "client_secret":"{{third_client_tk}}",
 | 
			
		||||
    "type": "general",
 | 
			
		||||
    "subject": "新年快乐!",
 | 
			
		||||
    "subtitle": "一条来自 Solar Network 团队的信息",
 | 
			
		||||
    "content": "今天是农历正月初一,小羊祝您新年快乐 🎉",
 | 
			
		||||
    "metadata": {
 | 
			
		||||
      "image": "D2EDbcrsTugs3xk5"
 | 
			
		||||
    },
 | 
			
		||||
    "subject": "关于迁移服务器完成的提示",
 | 
			
		||||
    "subtitle": "一条来自 Solar Network 团队的运营信息",
 | 
			
		||||
    "content": "我们已经将所有用户数据迁移到新版服务器,刚刚发布新的 DNS,因为部分 DNS 缓存的影响。可能更改不会生效,可以使用 nslookup / ping 检查解析地址是否未 8. 开头,您可以主动刷新 DNS。谢谢!",
 | 
			
		||||
    "metadata": {},
 | 
			
		||||
    "priority": 10
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -44,6 +44,7 @@ import 'package:surface/providers/widget.dart';
 | 
			
		||||
import 'package:surface/router.dart';
 | 
			
		||||
import 'package:flutter_web_plugins/url_strategy.dart' show usePathUrlStrategy;
 | 
			
		||||
import 'package:surface/widgets/dialog.dart';
 | 
			
		||||
import 'package:surface/widgets/menu_bar.dart';
 | 
			
		||||
import 'package:tray_manager/tray_manager.dart';
 | 
			
		||||
import 'package:version/version.dart';
 | 
			
		||||
import 'package:workmanager/workmanager.dart';
 | 
			
		||||
@@ -331,18 +332,7 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
 | 
			
		||||
 | 
			
		||||
  Future<void> _hotkeyInitialization() async {
 | 
			
		||||
    if (kIsWeb) return;
 | 
			
		||||
 | 
			
		||||
    if (Platform.isMacOS) {
 | 
			
		||||
      HotKey quitHotKey = HotKey(
 | 
			
		||||
        key: PhysicalKeyboardKey.keyQ,
 | 
			
		||||
        modifiers: [HotKeyModifier.meta],
 | 
			
		||||
        scope: HotKeyScope.inapp,
 | 
			
		||||
      );
 | 
			
		||||
      await hotKeyManager.register(quitHotKey, keyUpHandler: (_) {
 | 
			
		||||
        _appLifecycleListener?.dispose();
 | 
			
		||||
        SystemChannels.platform.invokeMethod('SystemNavigator.pop');
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    // The quit key has been removed, and the logic of the quit key is moved to system menu bar activator.
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  final Menu _appTrayMenu = Menu(
 | 
			
		||||
@@ -426,6 +416,15 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
 | 
			
		||||
    return AppExitResponse.cancel;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _quitApp() {
 | 
			
		||||
    _appLifecycleListener?.dispose();
 | 
			
		||||
    if (Platform.isWindows) {
 | 
			
		||||
      appWindow.close();
 | 
			
		||||
    } else {
 | 
			
		||||
      SystemChannels.platform.invokeMethod('SystemNavigator.pop');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void onTrayIconMouseDown() {
 | 
			
		||||
    if (Platform.isWindows) {
 | 
			
		||||
@@ -460,12 +459,7 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
 | 
			
		||||
        Timer(const Duration(milliseconds: 100), () => appWindow.show());
 | 
			
		||||
        break;
 | 
			
		||||
      case 'exit':
 | 
			
		||||
        _appLifecycleListener?.dispose();
 | 
			
		||||
        if (Platform.isWindows) {
 | 
			
		||||
          appWindow.close();
 | 
			
		||||
        } else {
 | 
			
		||||
          SystemChannels.platform.invokeMethod('SystemNavigator.pop');
 | 
			
		||||
        }
 | 
			
		||||
        _quitApp();
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@@ -482,28 +476,31 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final cfg = context.read<ConfigProvider>();
 | 
			
		||||
    return NotificationListener<SizeChangedLayoutNotification>(
 | 
			
		||||
      onNotification: (notification) {
 | 
			
		||||
        WidgetsBinding.instance.addPostFrameCallback((_) {
 | 
			
		||||
          cfg.calcDrawerSize(context);
 | 
			
		||||
        });
 | 
			
		||||
        return false;
 | 
			
		||||
      },
 | 
			
		||||
      child: OrientationBuilder(
 | 
			
		||||
        builder: (context, orientation) {
 | 
			
		||||
          final cfg = context.read<ConfigProvider>();
 | 
			
		||||
    return AppSystemMenuBar(
 | 
			
		||||
      onQuit: _quitApp,
 | 
			
		||||
      child: NotificationListener<SizeChangedLayoutNotification>(
 | 
			
		||||
        onNotification: (notification) {
 | 
			
		||||
          WidgetsBinding.instance.addPostFrameCallback((_) {
 | 
			
		||||
            cfg.calcDrawerSize(context);
 | 
			
		||||
          });
 | 
			
		||||
          Future.delayed(const Duration(milliseconds: 300), () {
 | 
			
		||||
            if (context.mounted) {
 | 
			
		||||
              cfg.calcDrawerSize(context);
 | 
			
		||||
            }
 | 
			
		||||
          });
 | 
			
		||||
          return SizeChangedLayoutNotifier(
 | 
			
		||||
            child: widget.child,
 | 
			
		||||
          );
 | 
			
		||||
          return false;
 | 
			
		||||
        },
 | 
			
		||||
        child: OrientationBuilder(
 | 
			
		||||
          builder: (context, orientation) {
 | 
			
		||||
            final cfg = context.read<ConfigProvider>();
 | 
			
		||||
            WidgetsBinding.instance.addPostFrameCallback((_) {
 | 
			
		||||
              cfg.calcDrawerSize(context);
 | 
			
		||||
            });
 | 
			
		||||
            Future.delayed(const Duration(milliseconds: 300), () {
 | 
			
		||||
              if (context.mounted) {
 | 
			
		||||
                cfg.calcDrawerSize(context);
 | 
			
		||||
              }
 | 
			
		||||
            });
 | 
			
		||||
            return SizeChangedLayoutNotifier(
 | 
			
		||||
              child: widget.child,
 | 
			
		||||
            );
 | 
			
		||||
          },
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,7 @@ class UserDirectoryProvider {
 | 
			
		||||
 | 
			
		||||
  Future<List<SnAccount?>> listAccount(Iterable<dynamic> id) async {
 | 
			
		||||
    // In-memory cache
 | 
			
		||||
    if (_cacheExpiredAt != null && _cacheExpiredAt!.isAfter(DateTime.now())) {
 | 
			
		||||
    if (_cacheExpiredAt != null && _cacheExpiredAt!.isBefore(DateTime.now())) {
 | 
			
		||||
      _cache.clear();
 | 
			
		||||
      _cacheExpiredAt = DateTime.now().add(const Duration(hours: 1));
 | 
			
		||||
    } else {
 | 
			
		||||
 
 | 
			
		||||
@@ -5,10 +5,12 @@ import 'package:gap/gap.dart';
 | 
			
		||||
import 'package:go_router/go_router.dart';
 | 
			
		||||
import 'package:material_symbols_icons/symbols.dart';
 | 
			
		||||
import 'package:provider/provider.dart';
 | 
			
		||||
import 'package:responsive_framework/responsive_framework.dart';
 | 
			
		||||
import 'package:styled_widget/styled_widget.dart';
 | 
			
		||||
import 'package:surface/providers/post.dart';
 | 
			
		||||
import 'package:surface/providers/sn_network.dart';
 | 
			
		||||
import 'package:surface/providers/sn_realm.dart';
 | 
			
		||||
import 'package:surface/providers/userinfo.dart';
 | 
			
		||||
import 'package:surface/types/post.dart';
 | 
			
		||||
import 'package:surface/types/realm.dart';
 | 
			
		||||
import 'package:surface/widgets/account/account_image.dart';
 | 
			
		||||
@@ -75,6 +77,8 @@ class _ExploreScreenState extends State<ExploreScreen>
 | 
			
		||||
 | 
			
		||||
  Future<void> _fetchRealms() async {
 | 
			
		||||
    try {
 | 
			
		||||
      final ua = context.read<UserProvider>();
 | 
			
		||||
      if (!ua.isAuthorized) return;
 | 
			
		||||
      final rels = context.read<SnRealmProvider>();
 | 
			
		||||
      final out = await rels.listAvailableRealms();
 | 
			
		||||
      setState(() {
 | 
			
		||||
@@ -219,10 +223,15 @@ class _ExploreScreenState extends State<ExploreScreen>
 | 
			
		||||
            SliverOverlapAbsorber(
 | 
			
		||||
              handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
 | 
			
		||||
              sliver: SliverAppBar(
 | 
			
		||||
                leading: AutoAppBarLeading(),
 | 
			
		||||
                leading:
 | 
			
		||||
                    ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE)
 | 
			
		||||
                        ? AutoAppBarLeading()
 | 
			
		||||
                        : null,
 | 
			
		||||
                titleSpacing: 0,
 | 
			
		||||
                title: Row(
 | 
			
		||||
                  children: [
 | 
			
		||||
                    if (ResponsiveBreakpoints.of(context).largerThan(MOBILE))
 | 
			
		||||
                      const Gap(8),
 | 
			
		||||
                    IconButton(
 | 
			
		||||
                      icon: const Icon(Symbols.shuffle),
 | 
			
		||||
                      onPressed: () {
 | 
			
		||||
 
 | 
			
		||||
@@ -164,7 +164,7 @@ class _PostEditorScreenState extends State<PostEditorScreen>
 | 
			
		||||
    });
 | 
			
		||||
    hotKeyManager.register(_saveDraftHotKey, keyDownHandler: (_) async {
 | 
			
		||||
      if (mounted) {
 | 
			
		||||
        _writeController.sendPost(context);
 | 
			
		||||
        _writeController.sendPost(context, saveAsDraft: true);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										103
									
								
								lib/widgets/menu_bar.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								lib/widgets/menu_bar.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,103 @@
 | 
			
		||||
import 'dart:io';
 | 
			
		||||
 | 
			
		||||
import 'package:easy_localization/easy_localization.dart';
 | 
			
		||||
import 'package:flutter/foundation.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter/services.dart';
 | 
			
		||||
import 'package:provider/provider.dart';
 | 
			
		||||
import 'package:surface/providers/navigation.dart';
 | 
			
		||||
import 'package:surface/router.dart';
 | 
			
		||||
 | 
			
		||||
// https://api.flutter.dev/flutter/widgets/PlatformMenuBar-class.html
 | 
			
		||||
// All the code following is only works on macOS
 | 
			
		||||
class AppSystemMenuBar extends StatelessWidget {
 | 
			
		||||
  final Function? onQuit;
 | 
			
		||||
  final Widget child;
 | 
			
		||||
  const AppSystemMenuBar({super.key, this.onQuit, required this.child});
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    if (kIsWeb || !Platform.isMacOS) return child;
 | 
			
		||||
 | 
			
		||||
    final nav = context.watch<NavigationProvider>();
 | 
			
		||||
 | 
			
		||||
    return PlatformMenuBar(
 | 
			
		||||
      menus: <PlatformMenuItem>[
 | 
			
		||||
        PlatformMenu(
 | 
			
		||||
          label: 'Solian',
 | 
			
		||||
          menus: <PlatformMenuItem>[
 | 
			
		||||
            PlatformMenuItemGroup(
 | 
			
		||||
              members: <PlatformMenuItem>[
 | 
			
		||||
                PlatformMenuItem(
 | 
			
		||||
                  label: 'screenAbout'.tr(),
 | 
			
		||||
                  onSelected: () {
 | 
			
		||||
                    appRouter.goNamed('about');
 | 
			
		||||
                    nav.autoDetectIndex(appRouter);
 | 
			
		||||
                  },
 | 
			
		||||
                ),
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
            PlatformMenuItemGroup(
 | 
			
		||||
              members: [
 | 
			
		||||
                PlatformMenuItem(
 | 
			
		||||
                  label: 'screenHome'.tr(),
 | 
			
		||||
                  shortcut: const SingleActivator(
 | 
			
		||||
                    LogicalKeyboardKey.digit1,
 | 
			
		||||
                    meta: true,
 | 
			
		||||
                  ),
 | 
			
		||||
                  onSelected: () {
 | 
			
		||||
                    appRouter.goNamed('home');
 | 
			
		||||
                    nav.autoDetectIndex(appRouter);
 | 
			
		||||
                  },
 | 
			
		||||
                ),
 | 
			
		||||
                PlatformMenuItem(
 | 
			
		||||
                  label: 'screenExplore'.tr(),
 | 
			
		||||
                  shortcut: const SingleActivator(
 | 
			
		||||
                    LogicalKeyboardKey.digit2,
 | 
			
		||||
                    meta: true,
 | 
			
		||||
                  ),
 | 
			
		||||
                  onSelected: () {
 | 
			
		||||
                    appRouter.goNamed('explore');
 | 
			
		||||
                    nav.autoDetectIndex(appRouter);
 | 
			
		||||
                  },
 | 
			
		||||
                ),
 | 
			
		||||
                PlatformMenuItem(
 | 
			
		||||
                  label: 'screenChat'.tr(),
 | 
			
		||||
                  shortcut: const SingleActivator(
 | 
			
		||||
                    LogicalKeyboardKey.digit3,
 | 
			
		||||
                    meta: true,
 | 
			
		||||
                  ),
 | 
			
		||||
                  onSelected: () {
 | 
			
		||||
                    appRouter.goNamed('chat');
 | 
			
		||||
                  },
 | 
			
		||||
                ),
 | 
			
		||||
                PlatformMenuItem(
 | 
			
		||||
                  label: 'screenAccount'.tr(),
 | 
			
		||||
                  shortcut: const SingleActivator(
 | 
			
		||||
                    LogicalKeyboardKey.digit4,
 | 
			
		||||
                    meta: true,
 | 
			
		||||
                  ),
 | 
			
		||||
                  onSelected: () {
 | 
			
		||||
                    appRouter.goNamed('account');
 | 
			
		||||
                  },
 | 
			
		||||
                ),
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
            if (onQuit != null)
 | 
			
		||||
              PlatformMenuItem(
 | 
			
		||||
                shortcut: const SingleActivator(
 | 
			
		||||
                  LogicalKeyboardKey.keyQ,
 | 
			
		||||
                  meta: true,
 | 
			
		||||
                ),
 | 
			
		||||
                label: 'trayMenuExit'.tr(),
 | 
			
		||||
                onSelected: () {
 | 
			
		||||
                  onQuit?.call();
 | 
			
		||||
                },
 | 
			
		||||
              ),
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
      ],
 | 
			
		||||
      child: child,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -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
 | 
			
		||||
# 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.
 | 
			
		||||
version: 2.4.2+78
 | 
			
		||||
version: 2.4.2+79
 | 
			
		||||
 | 
			
		||||
environment:
 | 
			
		||||
  sdk: ^3.5.4
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user