Compare commits
	
		
			5 Commits
		
	
	
		
			2.2.2+55
			...
			cb4a2598c8
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| cb4a2598c8 | |||
| 950612dc07 | |||
| cbd1eaf1af | |||
| ac41cbd99f | |||
| 9f9c90abc4 | 
| @@ -196,6 +196,10 @@ | ||||
|   "settingsFeatures": "Features", | ||||
|   "settingsNotifyWithHaptic": "Haptic when Notified", | ||||
|   "settingsNotifyWithHapticDescription": "Vibrate lightly when a new notification appears in the foreground.", | ||||
|   "settingsExpandPostLink": "Expand Post Link", | ||||
|   "settingsExpandPostLinkDescription": "Expand the post link in the post list.", | ||||
|   "settingsExpandChatLink": "Expand Chat Link", | ||||
|   "settingsExpandChatLinkDescription": "Expand the chat link in the chat list.", | ||||
|   "settingsNetwork": "Network", | ||||
|   "settingsNetworkServer": "HyperNet Server", | ||||
|   "settingsNetworkServerDescription": "Set the HyperNet server address, choose ours or build your own.", | ||||
|   | ||||
| @@ -194,6 +194,10 @@ | ||||
|   "settingsFeatures": "功能", | ||||
|   "settingsNotifyWithHaptic": "新通知时振动", | ||||
|   "settingsNotifyWithHapticDescription": "在应用在前台时收到新通知出现时出发轻量的振动。", | ||||
|   "settingsExpandPostLink": "展开帖子链接", | ||||
|   "settingsExpandPostLinkDescription": "在帖子列表中展开显示帖子中的链接。", | ||||
|   "settingsExpandChatLink": "展开聊天链接", | ||||
|   "settingsExpandChatLinkDescription": "在聊天信息中展开显示内容中的链接。", | ||||
|   "settingsNetwork": "网络", | ||||
|   "settingsNetworkServer": "HyperNet 服务器", | ||||
|   "settingsNetworkServerDescription": "设置 HyperNet 服务器地址,选择我们提供的,或者自己搭建。", | ||||
|   | ||||
| @@ -260,7 +260,7 @@ class _AppSplashScreenState extends State<_AppSplashScreen> { | ||||
|     try { | ||||
|       final cfg = context.read<ConfigProvider>(); | ||||
|       WidgetsBinding.instance.addPostFrameCallback((_) { | ||||
|         cfg.calcDrawerSize(context); | ||||
|         cfg.calcDrawerSize(context, withMediaQuery: true); | ||||
|       }); | ||||
|       final home = context.read<HomeWidgetProvider>(); | ||||
|       await home.initialize(); | ||||
|   | ||||
| @@ -15,6 +15,8 @@ const kAppBackgroundStoreKey = 'app_has_background'; | ||||
| const kAppColorSchemeStoreKey = 'app_color_scheme'; | ||||
| const kAppDrawerPreferCollapse = 'app_drawer_prefer_collapse'; | ||||
| const kAppNotifyWithHaptic = 'app_notify_with_haptic'; | ||||
| const kAppExpandPostLink = 'app_expand_post_link'; | ||||
| const kAppExpandChatLink = 'app_expand_chat_link'; | ||||
|  | ||||
| const Map<String, FilterQuality> kImageQualityLevel = { | ||||
|   'settingsImageQualityLowest': FilterQuality.none, | ||||
| @@ -39,14 +41,22 @@ class ConfigProvider extends ChangeNotifier { | ||||
|   bool drawerIsCollapsed = false; | ||||
|   bool drawerIsExpanded = false; | ||||
|  | ||||
|   void calcDrawerSize(BuildContext context) { | ||||
|     final rpb = ResponsiveBreakpoints.of(context); | ||||
|     final newDrawerIsCollapsed = rpb.smallerOrEqualTo(MOBILE); | ||||
|     final newDrawerIsExpanded = rpb.largerThan(TABLET) | ||||
|         ? (prefs.getBool(kAppDrawerPreferCollapse) ?? false) | ||||
|             ? false | ||||
|             : true | ||||
|         : false; | ||||
|   void calcDrawerSize(BuildContext context, {bool withMediaQuery = false}) { | ||||
|     bool newDrawerIsCollapsed = false; | ||||
|     bool newDrawerIsExpanded = false; | ||||
|     if (withMediaQuery) { | ||||
|       newDrawerIsCollapsed = MediaQuery.of(context).size.width < 450; | ||||
|       newDrawerIsExpanded = MediaQuery.of(context).size.width >= 451; | ||||
|     } else { | ||||
|       final rpb = ResponsiveBreakpoints.of(context); | ||||
|       newDrawerIsCollapsed = rpb.smallerOrEqualTo(MOBILE); | ||||
|       newDrawerIsCollapsed = rpb.largerThan(TABLET) | ||||
|           ? (prefs.getBool(kAppDrawerPreferCollapse) ?? false) | ||||
|               ? false | ||||
|               : true | ||||
|           : false; | ||||
|     } | ||||
|  | ||||
|     if (newDrawerIsExpanded != drawerIsExpanded || newDrawerIsCollapsed != drawerIsCollapsed) { | ||||
|       drawerIsExpanded = newDrawerIsExpanded; | ||||
|       drawerIsCollapsed = newDrawerIsCollapsed; | ||||
|   | ||||
							
								
								
									
										428
									
								
								lib/router.dart
									
									
									
									
									
								
							
							
						
						
									
										428
									
								
								lib/router.dart
									
									
									
									
									
								
							| @@ -34,246 +34,222 @@ import 'package:surface/widgets/about.dart'; | ||||
| import 'package:surface/widgets/navigation/app_background.dart'; | ||||
| import 'package:surface/widgets/navigation/app_scaffold.dart'; | ||||
|  | ||||
| Widget _fadeThroughTransition( | ||||
|     BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) { | ||||
|   return FadeThroughTransition( | ||||
|     animation: animation, | ||||
|     secondaryAnimation: secondaryAnimation, | ||||
|     fillColor: Colors.transparent, | ||||
|     child: child, | ||||
|   ); | ||||
| } | ||||
|  | ||||
| final _appRoutes = [ | ||||
|   ShellRoute( | ||||
|     builder: (context, state, child) => child, | ||||
|   GoRoute( | ||||
|     path: '/', | ||||
|     name: 'home', | ||||
|     pageBuilder: (context, state) => CustomTransitionPage( | ||||
|       transitionsBuilder: _fadeThroughTransition, | ||||
|       child: const HomeScreen(), | ||||
|     ), | ||||
|   ), | ||||
|   GoRoute( | ||||
|     path: '/posts', | ||||
|     name: 'explore', | ||||
|     pageBuilder: (context, state) => CustomTransitionPage( | ||||
|       transitionsBuilder: _fadeThroughTransition, | ||||
|       child: const ExploreScreen(), | ||||
|     ), | ||||
|     routes: [ | ||||
|       GoRoute( | ||||
|         path: '/', | ||||
|         name: 'home', | ||||
|         pageBuilder: (context, state) => NoTransitionPage( | ||||
|           child: const HomeScreen(), | ||||
|         path: '/write/:mode', | ||||
|         name: 'postEditor', | ||||
|         builder: (context, state) => PostEditorScreen( | ||||
|           mode: state.pathParameters['mode']!, | ||||
|           postEditId: int.tryParse( | ||||
|             state.uri.queryParameters['editing'] ?? '', | ||||
|           ), | ||||
|           postReplyId: int.tryParse( | ||||
|             state.uri.queryParameters['replying'] ?? '', | ||||
|           ), | ||||
|           postRepostId: int.tryParse( | ||||
|             state.uri.queryParameters['reposting'] ?? '', | ||||
|           ), | ||||
|           extraProps: state.extra as PostEditorExtraProps?, | ||||
|         ), | ||||
|       ), | ||||
|       GoRoute( | ||||
|         path: '/posts', | ||||
|         name: 'explore', | ||||
|         pageBuilder: (context, state) => NoTransitionPage( | ||||
|           child: const ExploreScreen(), | ||||
|         ), | ||||
|         routes: [ | ||||
|           GoRoute( | ||||
|             path: '/write/:mode', | ||||
|             name: 'postEditor', | ||||
|             builder: (context, state) => PostEditorScreen( | ||||
|               mode: state.pathParameters['mode']!, | ||||
|               postEditId: int.tryParse( | ||||
|                 state.uri.queryParameters['editing'] ?? '', | ||||
|               ), | ||||
|               postReplyId: int.tryParse( | ||||
|                 state.uri.queryParameters['replying'] ?? '', | ||||
|               ), | ||||
|               postRepostId: int.tryParse( | ||||
|                 state.uri.queryParameters['reposting'] ?? '', | ||||
|               ), | ||||
|               extraProps: state.extra as PostEditorExtraProps?, | ||||
|             ), | ||||
|           ), | ||||
|           GoRoute( | ||||
|             path: '/search', | ||||
|             name: 'postSearch', | ||||
|             builder: (context, state) => PostSearchScreen( | ||||
|               initialTags: state.uri.queryParameters['tags']?.split(','), | ||||
|               initialCategories: state.uri.queryParameters['categories']?.split(','), | ||||
|             ), | ||||
|           ), | ||||
|           GoRoute( | ||||
|             path: '/publishers/:name', | ||||
|             name: 'postPublisher', | ||||
|             builder: (context, state) => PostPublisherScreen(name: state.pathParameters['name']!), | ||||
|           ), | ||||
|           GoRoute( | ||||
|             path: '/:slug', | ||||
|             name: 'postDetail', | ||||
|             builder: (context, state) => PostDetailScreen( | ||||
|               slug: state.pathParameters['slug']!, | ||||
|               preload: state.extra as SnPost?, | ||||
|             ), | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|       GoRoute( | ||||
|         path: '/account', | ||||
|         name: 'account', | ||||
|         pageBuilder: (context, state) => CustomTransitionPage( | ||||
|           transitionsBuilder: (context, animation, secondaryAnimation, child) { | ||||
|             return FadeThroughTransition( | ||||
|               animation: animation, | ||||
|               secondaryAnimation: secondaryAnimation, | ||||
|               fillColor: Colors.transparent, | ||||
|               child: child, | ||||
|             ); | ||||
|           }, | ||||
|           child: const AccountScreen(), | ||||
|         ), | ||||
|         routes: [], | ||||
|       ), | ||||
|       GoRoute( | ||||
|         path: '/chat', | ||||
|         name: 'chat', | ||||
|         pageBuilder: (context, state) => CustomTransitionPage( | ||||
|           transitionsBuilder: (context, animation, secondaryAnimation, child) { | ||||
|             return FadeThroughTransition( | ||||
|               animation: animation, | ||||
|               secondaryAnimation: secondaryAnimation, | ||||
|               fillColor: Colors.transparent, | ||||
|               child: child, | ||||
|             ); | ||||
|           }, | ||||
|           child: const ChatScreen(), | ||||
|         ), | ||||
|         routes: [ | ||||
|           GoRoute( | ||||
|             path: '/:scope/:alias', | ||||
|             name: 'chatRoom', | ||||
|             builder: (context, state) => AppBackground( | ||||
|               child: ChatRoomScreen( | ||||
|                 scope: state.pathParameters['scope']!, | ||||
|                 alias: state.pathParameters['alias']!, | ||||
|               ), | ||||
|             ), | ||||
|           ), | ||||
|           GoRoute( | ||||
|             path: '/:scope/:alias/call', | ||||
|             name: 'chatCallRoom', | ||||
|             builder: (context, state) => AppBackground( | ||||
|               child: CallRoomScreen( | ||||
|                 scope: state.pathParameters['scope']!, | ||||
|                 alias: state.pathParameters['alias']!, | ||||
|               ), | ||||
|             ), | ||||
|           ), | ||||
|           GoRoute( | ||||
|             path: '/:scope/:alias/detail', | ||||
|             name: 'channelDetail', | ||||
|             builder: (context, state) => AppBackground( | ||||
|               child: ChannelDetailScreen( | ||||
|                 scope: state.pathParameters['scope']!, | ||||
|                 alias: state.pathParameters['alias']!, | ||||
|               ), | ||||
|             ), | ||||
|           ), | ||||
|           GoRoute( | ||||
|             path: '/manage', | ||||
|             name: 'chatManage', | ||||
|             pageBuilder: (context, state) => CustomTransitionPage( | ||||
|               child: ChatManageScreen( | ||||
|                 editingChannelAlias: state.uri.queryParameters['editing'], | ||||
|               ), | ||||
|               transitionsBuilder: (context, animation, secondaryAnimation, child) { | ||||
|                 return FadeThroughTransition( | ||||
|                   animation: animation, | ||||
|                   secondaryAnimation: secondaryAnimation, | ||||
|                   fillColor: Colors.transparent, | ||||
|                   child: AppBackground( | ||||
|                     child: child, | ||||
|                   ), | ||||
|                 ); | ||||
|               }, | ||||
|             ), | ||||
|           ), | ||||
|           GoRoute( | ||||
|             path: '/:alias', | ||||
|             name: 'realmDetail', | ||||
|             builder: (context, state) => AppBackground( | ||||
|               child: RealmDetailScreen(alias: state.pathParameters['alias']!), | ||||
|             ), | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|       GoRoute( | ||||
|         path: '/realm', | ||||
|         name: 'realm', | ||||
|         pageBuilder: (context, state) => NoTransitionPage( | ||||
|           child: const RealmScreen(), | ||||
|         ), | ||||
|         routes: [ | ||||
|           GoRoute( | ||||
|             path: '/manage', | ||||
|             name: 'realmManage', | ||||
|             pageBuilder: (context, state) => CustomTransitionPage( | ||||
|               child: RealmManageScreen( | ||||
|                 editingRealmAlias: state.uri.queryParameters['editing'], | ||||
|               ), | ||||
|               transitionsBuilder: (context, animation, secondaryAnimation, child) { | ||||
|                 return FadeThroughTransition( | ||||
|                   animation: animation, | ||||
|                   secondaryAnimation: secondaryAnimation, | ||||
|                   fillColor: Colors.transparent, | ||||
|                   child: AppBackground( | ||||
|                     child: child, | ||||
|                   ), | ||||
|                 ); | ||||
|               }, | ||||
|             ), | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|       GoRoute( | ||||
|         path: '/album', | ||||
|         name: 'album', | ||||
|         pageBuilder: (context, state) => NoTransitionPage( | ||||
|           child: const AlbumScreen(), | ||||
|         path: '/search', | ||||
|         name: 'postSearch', | ||||
|         builder: (context, state) => PostSearchScreen( | ||||
|           initialTags: state.uri.queryParameters['tags']?.split(','), | ||||
|           initialCategories: state.uri.queryParameters['categories']?.split(','), | ||||
|         ), | ||||
|       ), | ||||
|       GoRoute( | ||||
|         path: '/friend', | ||||
|         name: 'friend', | ||||
|         pageBuilder: (context, state) => NoTransitionPage( | ||||
|           child: const FriendScreen(), | ||||
|         ), | ||||
|         path: '/publishers/:name', | ||||
|         name: 'postPublisher', | ||||
|         builder: (context, state) => PostPublisherScreen(name: state.pathParameters['name']!), | ||||
|       ), | ||||
|       GoRoute( | ||||
|         path: '/notification', | ||||
|         name: 'notification', | ||||
|         pageBuilder: (context, state) => NoTransitionPage( | ||||
|           child: const NotificationScreen(), | ||||
|         path: '/:slug', | ||||
|         name: 'postDetail', | ||||
|         builder: (context, state) => PostDetailScreen( | ||||
|           slug: state.pathParameters['slug']!, | ||||
|           preload: state.extra as SnPost?, | ||||
|         ), | ||||
|       ), | ||||
|     ], | ||||
|   ), | ||||
|   ShellRoute( | ||||
|     builder: (context, state, child) => child, | ||||
|   GoRoute( | ||||
|     path: '/account', | ||||
|     name: 'account', | ||||
|     pageBuilder: (context, state) => CustomTransitionPage( | ||||
|       transitionsBuilder: _fadeThroughTransition, | ||||
|       child: const AccountScreen(), | ||||
|     ), | ||||
|   ), | ||||
|   GoRoute( | ||||
|     path: '/chat', | ||||
|     name: 'chat', | ||||
|     pageBuilder: (context, state) => CustomTransitionPage( | ||||
|       transitionsBuilder: _fadeThroughTransition, | ||||
|       child: const ChatScreen(), | ||||
|     ), | ||||
|     routes: [ | ||||
|       GoRoute( | ||||
|         path: '/auth/login', | ||||
|         name: 'authLogin', | ||||
|         builder: (context, state) => LoginScreen(), | ||||
|         path: '/:scope/:alias', | ||||
|         name: 'chatRoom', | ||||
|         builder: (context, state) => AppBackground( | ||||
|           child: ChatRoomScreen( | ||||
|             scope: state.pathParameters['scope']!, | ||||
|             alias: state.pathParameters['alias']!, | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|       GoRoute( | ||||
|         path: '/auth/register', | ||||
|         name: 'authRegister', | ||||
|         builder: (context, state) => RegisterScreen(), | ||||
|         path: '/:scope/:alias/call', | ||||
|         name: 'chatCallRoom', | ||||
|         builder: (context, state) => AppBackground( | ||||
|           child: CallRoomScreen( | ||||
|             scope: state.pathParameters['scope']!, | ||||
|             alias: state.pathParameters['alias']!, | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|       GoRoute( | ||||
|         path: '/reports', | ||||
|         name: 'abuseReport', | ||||
|         builder: (context, state) => AbuseReportScreen(), | ||||
|         path: '/:scope/:alias/detail', | ||||
|         name: 'channelDetail', | ||||
|         builder: (context, state) => AppBackground( | ||||
|           child: ChannelDetailScreen( | ||||
|             scope: state.pathParameters['scope']!, | ||||
|             alias: state.pathParameters['alias']!, | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|       GoRoute( | ||||
|         path: '/account/profile/edit', | ||||
|         name: 'accountProfileEdit', | ||||
|         builder: (context, state) => ProfileEditScreen(), | ||||
|       ), | ||||
|       GoRoute( | ||||
|         path: '/account/publishers', | ||||
|         name: 'accountPublishers', | ||||
|         builder: (context, state) => PublisherScreen(), | ||||
|       ), | ||||
|       GoRoute( | ||||
|         path: '/account/publishers/new', | ||||
|         name: 'accountPublisherNew', | ||||
|         builder: (context, state) => AccountPublisherNewScreen(), | ||||
|       ), | ||||
|       GoRoute( | ||||
|         path: '/account/publishers/edit/:name', | ||||
|         name: 'accountPublisherEdit', | ||||
|         builder: (context, state) => AccountPublisherEditScreen( | ||||
|           name: state.pathParameters['name']!, | ||||
|         path: '/manage', | ||||
|         name: 'chatManage', | ||||
|         pageBuilder: (context, state) => CustomTransitionPage( | ||||
|           child: ChatManageScreen( | ||||
|             editingChannelAlias: state.uri.queryParameters['editing'], | ||||
|           ), | ||||
|           transitionsBuilder: (context, animation, secondaryAnimation, child) { | ||||
|             return FadeThroughTransition( | ||||
|               animation: animation, | ||||
|               secondaryAnimation: secondaryAnimation, | ||||
|               fillColor: Colors.transparent, | ||||
|               child: child, | ||||
|             ); | ||||
|           }, | ||||
|         ), | ||||
|       ), | ||||
|     ], | ||||
|   ), | ||||
|   GoRoute( | ||||
|     path: '/realm', | ||||
|     name: 'realm', | ||||
|     pageBuilder: (context, state) => CustomTransitionPage( | ||||
|       transitionsBuilder: _fadeThroughTransition, | ||||
|       child: const RealmScreen(), | ||||
|     ), | ||||
|     routes: [ | ||||
|       GoRoute( | ||||
|         path: '/:alias', | ||||
|         name: 'realmDetail', | ||||
|         builder: (context, state) => RealmDetailScreen(alias: state.pathParameters['alias']!), | ||||
|       ), | ||||
|       GoRoute( | ||||
|         path: '/manage', | ||||
|         name: 'realmManage', | ||||
|         pageBuilder: (context, state) => CustomTransitionPage( | ||||
|           transitionsBuilder: _fadeThroughTransition, | ||||
|           child: RealmManageScreen( | ||||
|             editingRealmAlias: state.uri.queryParameters['editing'], | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|     ], | ||||
|   ), | ||||
|   GoRoute( | ||||
|     path: '/album', | ||||
|     name: 'album', | ||||
|     pageBuilder: (context, state) => CustomTransitionPage( | ||||
|       transitionsBuilder: _fadeThroughTransition, | ||||
|       child: const AlbumScreen(), | ||||
|     ), | ||||
|   ), | ||||
|   GoRoute( | ||||
|     path: '/friend', | ||||
|     name: 'friend', | ||||
|     pageBuilder: (context, state) => NoTransitionPage( | ||||
|       child: const FriendScreen(), | ||||
|     ), | ||||
|   ), | ||||
|   GoRoute( | ||||
|     path: '/notification', | ||||
|     name: 'notification', | ||||
|     pageBuilder: (context, state) => NoTransitionPage( | ||||
|       child: const NotificationScreen(), | ||||
|     ), | ||||
|   ), | ||||
|   GoRoute( | ||||
|     path: '/auth/login', | ||||
|     name: 'authLogin', | ||||
|     builder: (context, state) => LoginScreen(), | ||||
|   ), | ||||
|   GoRoute( | ||||
|     path: '/auth/register', | ||||
|     name: 'authRegister', | ||||
|     builder: (context, state) => RegisterScreen(), | ||||
|   ), | ||||
|   GoRoute( | ||||
|     path: '/reports', | ||||
|     name: 'abuseReport', | ||||
|     builder: (context, state) => AbuseReportScreen(), | ||||
|   ), | ||||
|   GoRoute( | ||||
|     path: '/account/profile/edit', | ||||
|     name: 'accountProfileEdit', | ||||
|     builder: (context, state) => ProfileEditScreen(), | ||||
|   ), | ||||
|   GoRoute( | ||||
|     path: '/account/publishers', | ||||
|     name: 'accountPublishers', | ||||
|     builder: (context, state) => PublisherScreen(), | ||||
|   ), | ||||
|   GoRoute( | ||||
|     path: '/account/publishers/new', | ||||
|     name: 'accountPublisherNew', | ||||
|     builder: (context, state) => AccountPublisherNewScreen(), | ||||
|   ), | ||||
|   GoRoute( | ||||
|     path: '/account/publishers/edit/:name', | ||||
|     name: 'accountPublisherEdit', | ||||
|     builder: (context, state) => AccountPublisherEditScreen( | ||||
|       name: state.pathParameters['name']!, | ||||
|     ), | ||||
|   ), | ||||
|   GoRoute( | ||||
|     path: '/account/:name', | ||||
|     name: 'accountProfilePage', | ||||
| @@ -281,25 +257,15 @@ final _appRoutes = [ | ||||
|       child: UserScreen(name: state.pathParameters['name']!), | ||||
|     ), | ||||
|   ), | ||||
|   ShellRoute( | ||||
|     builder: (context, state, child) => child, | ||||
|     routes: [ | ||||
|       GoRoute( | ||||
|         path: '/settings', | ||||
|         name: 'settings', | ||||
|         builder: (context, state) => SettingsScreen(), | ||||
|       ), | ||||
|     ], | ||||
|   GoRoute( | ||||
|     path: '/settings', | ||||
|     name: 'settings', | ||||
|     builder: (context, state) => SettingsScreen(), | ||||
|   ), | ||||
|   ShellRoute( | ||||
|     builder: (context, state, child) => child, | ||||
|     routes: [ | ||||
|       GoRoute( | ||||
|         path: '/about', | ||||
|         name: 'about', | ||||
|         builder: (context, state) => AboutScreen(), | ||||
|       ), | ||||
|     ], | ||||
|   GoRoute( | ||||
|     path: '/about', | ||||
|     name: 'about', | ||||
|     builder: (context, state) => AboutScreen(), | ||||
|   ), | ||||
| ]; | ||||
|  | ||||
|   | ||||
| @@ -19,7 +19,6 @@ import 'package:surface/types/check_in.dart'; | ||||
| import 'package:surface/types/post.dart'; | ||||
| import 'package:surface/widgets/account/account_image.dart'; | ||||
| import 'package:surface/widgets/dialog.dart'; | ||||
| import 'package:surface/widgets/navigation/app_scaffold.dart'; | ||||
| import 'package:surface/widgets/universal_image.dart'; | ||||
|  | ||||
| const Map<String, (String, IconData, Color)> kBadgesMeta = { | ||||
| @@ -596,7 +595,7 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM | ||||
|                 subtitle: Text('@${ele.name}'), | ||||
|                 trailing: const Icon(Symbols.chevron_right), | ||||
|                 onTap: () { | ||||
|                   GoRouter.of(context).pushNamed( | ||||
|                   GoRouter.of(context).goNamed( | ||||
|                     'postPublisher', | ||||
|                     pathParameters: {'name': ele.name}, | ||||
|                   ); | ||||
|   | ||||
| @@ -14,6 +14,7 @@ import 'package:surface/widgets/navigation/app_scaffold.dart'; | ||||
| class CallRoomScreen extends StatefulWidget { | ||||
|   final String scope; | ||||
|   final String alias; | ||||
|  | ||||
|   const CallRoomScreen({super.key, required this.scope, required this.alias}); | ||||
|  | ||||
|   @override | ||||
| @@ -36,8 +37,7 @@ class _CallRoomScreenState extends State<CallRoomScreen> { | ||||
|     return Stack( | ||||
|       children: [ | ||||
|         Container( | ||||
|           color: | ||||
|               Theme.of(context).colorScheme.surfaceContainer.withOpacity(0.75), | ||||
|           color: Theme.of(context).colorScheme.surfaceContainer.withOpacity(0.75), | ||||
|           child: call.focusTrack != null | ||||
|               ? InteractiveParticipantWidget( | ||||
|                   isFixedAvatar: false, | ||||
| @@ -72,8 +72,7 @@ class _CallRoomScreenState extends State<CallRoomScreen> { | ||||
|                       color: Theme.of(context).cardColor, | ||||
|                       participant: track, | ||||
|                       onTap: () { | ||||
|                         if (track.participant.sid != | ||||
|                             call.focusTrack?.participant.sid) { | ||||
|                         if (track.participant.sid != call.focusTrack?.participant.sid) { | ||||
|                           call.setFocusTrack(track); | ||||
|                         } | ||||
|                       }, | ||||
| @@ -115,14 +114,10 @@ class _CallRoomScreenState extends State<CallRoomScreen> { | ||||
|             child: ClipRRect( | ||||
|               borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||
|               child: InteractiveParticipantWidget( | ||||
|                 color: Theme.of(context) | ||||
|                     .colorScheme | ||||
|                     .surfaceContainerHigh | ||||
|                     .withOpacity(0.75), | ||||
|                 color: Theme.of(context).colorScheme.surfaceContainerHigh.withOpacity(0.75), | ||||
|                 participant: track, | ||||
|                 onTap: () { | ||||
|                   if (track.participant.sid != | ||||
|                       call.focusTrack?.participant.sid) { | ||||
|                   if (track.participant.sid != call.focusTrack?.participant.sid) { | ||||
|                     call.setFocusTrack(track); | ||||
|                   } | ||||
|                 }, | ||||
| @@ -160,150 +155,127 @@ class _CallRoomScreenState extends State<CallRoomScreen> { | ||||
|                 text: TextSpan(children: [ | ||||
|                   TextSpan( | ||||
|                     text: 'call'.tr(), | ||||
|                     style: Theme.of(context) | ||||
|                         .textTheme | ||||
|                         .titleLarge! | ||||
|                         .copyWith(color: Colors.white), | ||||
|                     style: Theme.of(context).textTheme.titleLarge!.copyWith(color: Colors.white), | ||||
|                   ), | ||||
|                   const TextSpan(text: '\n'), | ||||
|                   TextSpan( | ||||
|                     text: call.lastDuration.toString(), | ||||
|                     style: Theme.of(context) | ||||
|                         .textTheme | ||||
|                         .bodySmall! | ||||
|                         .copyWith(color: Colors.white), | ||||
|                     style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.white), | ||||
|                   ), | ||||
|                 ]), | ||||
|               ), | ||||
|             ), | ||||
|             body: SafeArea( | ||||
|               child: GestureDetector( | ||||
|                 behavior: HitTestBehavior.translucent, | ||||
|                 child: Column( | ||||
|                   children: [ | ||||
|             body: GestureDetector( | ||||
|               behavior: HitTestBehavior.translucent, | ||||
|               child: Column( | ||||
|                 children: [ | ||||
|                   SizedBox( | ||||
|                     width: MediaQuery.of(context).size.width, | ||||
|                     height: 64, | ||||
|                     child: Row( | ||||
|                       mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||||
|                       crossAxisAlignment: CrossAxisAlignment.center, | ||||
|                       children: [ | ||||
|                         Builder(builder: (context) { | ||||
|                           final call = context.read<ChatCallProvider>(); | ||||
|                           final connectionQuality = | ||||
|                               call.room.localParticipant?.connectionQuality ?? livekit.ConnectionQuality.unknown; | ||||
|                           return Expanded( | ||||
|                             child: Column( | ||||
|                               mainAxisSize: MainAxisSize.min, | ||||
|                               crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                               children: [ | ||||
|                                 Row( | ||||
|                                   children: [ | ||||
|                                     Text( | ||||
|                                       call.channel?.name ?? 'unknown'.tr(), | ||||
|                                       style: const TextStyle( | ||||
|                                         fontWeight: FontWeight.bold, | ||||
|                                       ), | ||||
|                                     ), | ||||
|                                     const Gap(6), | ||||
|                                     Text(call.lastDuration.toString()) | ||||
|                                   ], | ||||
|                                 ), | ||||
|                                 Row( | ||||
|                                   children: [ | ||||
|                                     Text( | ||||
|                                       { | ||||
|                                         livekit.ConnectionState.disconnected: 'callStatusDisconnected'.tr(), | ||||
|                                         livekit.ConnectionState.connected: 'callStatusConnected'.tr(), | ||||
|                                         livekit.ConnectionState.connecting: 'callStatusConnecting'.tr(), | ||||
|                                         livekit.ConnectionState.reconnecting: 'callStatusReconnecting'.tr(), | ||||
|                                       }[call.room.connectionState]!, | ||||
|                                     ), | ||||
|                                     const Gap(6), | ||||
|                                     if (connectionQuality != livekit.ConnectionQuality.unknown) | ||||
|                                       Icon( | ||||
|                                         { | ||||
|                                           livekit.ConnectionQuality.excellent: Icons.signal_cellular_alt, | ||||
|                                           livekit.ConnectionQuality.good: Icons.signal_cellular_alt_2_bar, | ||||
|                                           livekit.ConnectionQuality.poor: Icons.signal_cellular_alt_1_bar, | ||||
|                                         }[connectionQuality], | ||||
|                                         color: { | ||||
|                                           livekit.ConnectionQuality.excellent: Colors.green, | ||||
|                                           livekit.ConnectionQuality.good: Colors.orange, | ||||
|                                           livekit.ConnectionQuality.poor: Colors.red, | ||||
|                                         }[connectionQuality], | ||||
|                                         size: 16, | ||||
|                                       ) | ||||
|                                     else | ||||
|                                       const SizedBox( | ||||
|                                         width: 12, | ||||
|                                         height: 12, | ||||
|                                         child: CircularProgressIndicator( | ||||
|                                           color: Colors.white, | ||||
|                                           strokeWidth: 2, | ||||
|                                         ), | ||||
|                                       ).padding(all: 3), | ||||
|                                   ], | ||||
|                                 ), | ||||
|                               ], | ||||
|                             ), | ||||
|                           ); | ||||
|                         }), | ||||
|                         Row( | ||||
|                           children: [ | ||||
|                             IconButton( | ||||
|                               icon: _layoutMode == 0 ? const Icon(Icons.view_list) : const Icon(Icons.grid_view), | ||||
|                               onPressed: () { | ||||
|                                 _switchLayout(); | ||||
|                               }, | ||||
|                             ), | ||||
|                           ], | ||||
|                         ), | ||||
|                       ], | ||||
|                     ).padding(left: 20, right: 16), | ||||
|                   ), | ||||
|                   Expanded( | ||||
|                     child: Material( | ||||
|                       color: Theme.of(context).colorScheme.surfaceContainerLow, | ||||
|                       child: Builder( | ||||
|                         builder: (context) { | ||||
|                           switch (_layoutMode) { | ||||
|                             case 1: | ||||
|                               return _buildGridLayout(); | ||||
|                             default: | ||||
|                               return _buildListLayout(); | ||||
|                           } | ||||
|                         }, | ||||
|                       ), | ||||
|                     ), | ||||
|                   ), | ||||
|                   if (call.room.localParticipant != null) | ||||
|                     SizedBox( | ||||
|                       width: MediaQuery.of(context).size.width, | ||||
|                       height: 64, | ||||
|                       child: Row( | ||||
|                         mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||||
|                         crossAxisAlignment: CrossAxisAlignment.center, | ||||
|                         children: [ | ||||
|                           Builder(builder: (context) { | ||||
|                             final call = context.read<ChatCallProvider>(); | ||||
|                             final connectionQuality = | ||||
|                                 call.room.localParticipant?.connectionQuality ?? | ||||
|                                     livekit.ConnectionQuality.unknown; | ||||
|                             return Expanded( | ||||
|                               child: Column( | ||||
|                                 mainAxisSize: MainAxisSize.min, | ||||
|                                 crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                                 children: [ | ||||
|                                   Row( | ||||
|                                     children: [ | ||||
|                                       Text( | ||||
|                                         call.channel?.name ?? 'unknown'.tr(), | ||||
|                                         style: const TextStyle( | ||||
|                                           fontWeight: FontWeight.bold, | ||||
|                                         ), | ||||
|                                       ), | ||||
|                                       const Gap(6), | ||||
|                                       Text(call.lastDuration.toString()) | ||||
|                                     ], | ||||
|                                   ), | ||||
|                                   Row( | ||||
|                                     children: [ | ||||
|                                       Text( | ||||
|                                         { | ||||
|                                           livekit.ConnectionState.disconnected: | ||||
|                                               'callStatusDisconnected'.tr(), | ||||
|                                           livekit.ConnectionState.connected: | ||||
|                                               'callStatusConnected'.tr(), | ||||
|                                           livekit.ConnectionState.connecting: | ||||
|                                               'callStatusConnecting'.tr(), | ||||
|                                           livekit.ConnectionState.reconnecting: | ||||
|                                               'callStatusReconnecting'.tr(), | ||||
|                                         }[call.room.connectionState]!, | ||||
|                                       ), | ||||
|                                       const Gap(6), | ||||
|                                       if (connectionQuality != | ||||
|                                           livekit.ConnectionQuality.unknown) | ||||
|                                         Icon( | ||||
|                                           { | ||||
|                                             livekit.ConnectionQuality.excellent: | ||||
|                                                 Icons.signal_cellular_alt, | ||||
|                                             livekit.ConnectionQuality.good: | ||||
|                                                 Icons.signal_cellular_alt_2_bar, | ||||
|                                             livekit.ConnectionQuality.poor: | ||||
|                                                 Icons.signal_cellular_alt_1_bar, | ||||
|                                           }[connectionQuality], | ||||
|                                           color: { | ||||
|                                             livekit.ConnectionQuality.excellent: | ||||
|                                                 Colors.green, | ||||
|                                             livekit.ConnectionQuality.good: | ||||
|                                                 Colors.orange, | ||||
|                                             livekit.ConnectionQuality.poor: | ||||
|                                                 Colors.red, | ||||
|                                           }[connectionQuality], | ||||
|                                           size: 16, | ||||
|                                         ) | ||||
|                                       else | ||||
|                                         const SizedBox( | ||||
|                                           width: 12, | ||||
|                                           height: 12, | ||||
|                                           child: CircularProgressIndicator( | ||||
|                                             color: Colors.white, | ||||
|                                             strokeWidth: 2, | ||||
|                                           ), | ||||
|                                         ).padding(all: 3), | ||||
|                                     ], | ||||
|                                   ), | ||||
|                                 ], | ||||
|                               ), | ||||
|                             ); | ||||
|                           }), | ||||
|                           Row( | ||||
|                             children: [ | ||||
|                               IconButton( | ||||
|                                 icon: _layoutMode == 0 | ||||
|                                     ? const Icon(Icons.view_list) | ||||
|                                     : const Icon(Icons.grid_view), | ||||
|                                 onPressed: () { | ||||
|                                   _switchLayout(); | ||||
|                                 }, | ||||
|                               ), | ||||
|                             ], | ||||
|                           ), | ||||
|                         ], | ||||
|                       ).padding(left: 20, right: 16), | ||||
|                     ), | ||||
|                     Expanded( | ||||
|                       child: Material( | ||||
|                         color: | ||||
|                             Theme.of(context).colorScheme.surfaceContainerLow, | ||||
|                         child: Builder( | ||||
|                           builder: (context) { | ||||
|                             switch (_layoutMode) { | ||||
|                               case 1: | ||||
|                                 return _buildGridLayout(); | ||||
|                               default: | ||||
|                                 return _buildListLayout(); | ||||
|                             } | ||||
|                           }, | ||||
|                         ), | ||||
|                       child: ControlsWidget( | ||||
|                         call.room, | ||||
|                         call.room.localParticipant!, | ||||
|                       ), | ||||
|                     ), | ||||
|                     if (call.room.localParticipant != null) | ||||
|                       SizedBox( | ||||
|                         width: MediaQuery.of(context).size.width, | ||||
|                         child: ControlsWidget( | ||||
|                           call.room, | ||||
|                           call.room.localParticipant!, | ||||
|                         ), | ||||
|                       ), | ||||
|                   ], | ||||
|                 ), | ||||
|                 onTap: () {}, | ||||
|                 ], | ||||
|               ), | ||||
|               onTap: () {}, | ||||
|             ), | ||||
|           ); | ||||
|         }); | ||||
|   | ||||
| @@ -276,6 +276,30 @@ class _SettingsScreenState extends State<SettingsScreen> { | ||||
|                     }); | ||||
|                   }, | ||||
|                 ), | ||||
|                 CheckboxListTile( | ||||
|                   secondary: const Icon(Symbols.link), | ||||
|                   title: Text('settingsExpandPostLink').tr(), | ||||
|                   subtitle: Text('settingsExpandPostLinkDescription').tr(), | ||||
|                   contentPadding: const EdgeInsets.only(left: 24, right: 17), | ||||
|                   value: _prefs.getBool(kAppExpandPostLink) ?? true, | ||||
|                   onChanged: (value) { | ||||
|                     setState(() { | ||||
|                       _prefs.setBool(kAppExpandPostLink, value ?? false); | ||||
|                     }); | ||||
|                   }, | ||||
|                 ), | ||||
|                 CheckboxListTile( | ||||
|                   secondary: const Icon(Symbols.chat), | ||||
|                   title: Text('settingsExpandChatLink').tr(), | ||||
|                   subtitle: Text('settingsExpandChatLinkDescription').tr(), | ||||
|                   contentPadding: const EdgeInsets.only(left: 24, right: 17), | ||||
|                   value: _prefs.getBool(kAppExpandChatLink) ?? true, | ||||
|                   onChanged: (value) { | ||||
|                     setState(() { | ||||
|                       _prefs.setBool(kAppExpandChatLink, value ?? false); | ||||
|                     }); | ||||
|                   }, | ||||
|                 ), | ||||
|               ], | ||||
|             ), | ||||
|             Column( | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import 'package:material_symbols_icons/symbols.dart'; | ||||
| import 'package:popover/popover.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
| import 'package:surface/providers/config.dart'; | ||||
| import 'package:surface/providers/user_directory.dart'; | ||||
| import 'package:surface/providers/userinfo.dart'; | ||||
| import 'package:surface/types/chat.dart'; | ||||
| @@ -53,6 +54,8 @@ class ChatMessage extends StatelessWidget { | ||||
|  | ||||
|     final dateFormatter = DateFormat('MM/dd HH:mm'); | ||||
|  | ||||
|     final cfg = context.read<ConfigProvider>(); | ||||
|  | ||||
|     return SwipeTo( | ||||
|       key: Key('chat-message-${data.id}'), | ||||
|       iconOnLeftSwipe: Symbols.reply, | ||||
| @@ -192,7 +195,10 @@ class ChatMessage extends StatelessWidget { | ||||
|                 ], | ||||
|               ).opacity(isPending ? 0.5 : 1), | ||||
|             ), | ||||
|             if (data.body['text'] != null && data.type == 'messages.new' && (data.body['text']?.isNotEmpty ?? false)) | ||||
|             if (data.body['text'] != null && | ||||
|                 data.type == 'messages.new' && | ||||
|                 (data.body['text']?.isNotEmpty ?? false) && | ||||
|                 (cfg.prefs.getBool(kAppExpandChatLink) ?? true)) | ||||
|               LinkPreviewWidget(text: data.body['text']!), | ||||
|             if (data.preload?.attachments?.isNotEmpty ?? false) | ||||
|               AttachmentList( | ||||
|   | ||||
| @@ -7,12 +7,11 @@ import 'package:marquee/marquee.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:responsive_framework/responsive_framework.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
| import 'package:surface/providers/link_preview.dart'; | ||||
| import 'package:surface/types/link.dart'; | ||||
| import 'package:surface/widgets/universal_image.dart'; | ||||
| import 'package:url_launcher/url_launcher_string.dart'; | ||||
|  | ||||
| import '../providers/link_preview.dart'; | ||||
|  | ||||
| class LinkPreviewWidget extends StatefulWidget { | ||||
|   final String text; | ||||
|  | ||||
| @@ -81,8 +80,9 @@ class _LinkPreviewEntry extends StatelessWidget { | ||||
|                   child: AspectRatio( | ||||
|                     aspectRatio: 16 / 9, | ||||
|                     child: ClipRRect( | ||||
|                       borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||
|                       child: AutoResizeUniversalImage( | ||||
|                         meta.image!, | ||||
|                         meta.image!.startsWith('//') ? 'https:${meta.image}' : meta.image!, | ||||
|                         fit: BoxFit.contain, | ||||
|                       ), | ||||
|                     ), | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| import 'dart:io'; | ||||
|  | ||||
| import 'package:bitsdojo_window/bitsdojo_window.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/foundation.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:go_router/go_router.dart'; | ||||
| @@ -12,7 +11,6 @@ import 'package:styled_widget/styled_widget.dart'; | ||||
| import 'package:surface/providers/config.dart'; | ||||
| import 'package:surface/providers/navigation.dart'; | ||||
| import 'package:surface/widgets/connection_indicator.dart'; | ||||
| import 'package:surface/widgets/dialog.dart'; | ||||
| import 'package:surface/widgets/navigation/app_background.dart'; | ||||
| import 'package:surface/widgets/navigation/app_bottom_navigation.dart'; | ||||
| import 'package:surface/widgets/navigation/app_drawer_navigation.dart'; | ||||
|   | ||||
| @@ -203,6 +203,8 @@ class PostItem extends StatelessWidget { | ||||
|         ?.where((ele) => ele?.mediaType != SnMediaType.image || data.type != 'article') | ||||
|         .toList(); | ||||
|  | ||||
|     final cfg = context.read<ConfigProvider>(); | ||||
|  | ||||
|     return Column( | ||||
|       crossAxisAlignment: CrossAxisAlignment.center, | ||||
|       children: [ | ||||
| @@ -261,7 +263,7 @@ class PostItem extends StatelessWidget { | ||||
|             fit: showFullPost ? BoxFit.cover : BoxFit.contain, | ||||
|             padding: const EdgeInsets.symmetric(horizontal: 12), | ||||
|           ), | ||||
|         if (data.body['content'] != null) | ||||
|         if (data.body['content'] != null && (cfg.prefs.getBool(kAppExpandPostLink) ?? true)) | ||||
|           LinkPreviewWidget( | ||||
|             text: data.body['content'], | ||||
|           ).padding(horizontal: 4), | ||||
|   | ||||
		Reference in New Issue
	
	Block a user