Compare commits

...

6 Commits

Author SHA1 Message Date
a355e3bf90 🚀 Launch 2.2.2+56 2025-01-25 14:52:40 +08:00
cb4a2598c8 ♻️ Refactored router 2025-01-25 14:35:04 +08:00
950612dc07 🐛 Fix error when went to publisher page from account page 2025-01-25 14:27:09 +08:00
cbd1eaf1af 🐛 Fix some link preview compatibility issue 2025-01-25 01:40:54 +08:00
ac41cbd99f Toggleable preview link 2025-01-25 01:36:11 +08:00
9f9c90abc4 🐛 Fix call room meaningless safe area 2025-01-23 19:22:55 +08:00
13 changed files with 376 additions and 391 deletions

View File

@ -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.",

View File

@ -194,6 +194,10 @@
"settingsFeatures": "功能",
"settingsNotifyWithHaptic": "新通知时振动",
"settingsNotifyWithHapticDescription": "在应用在前台时收到新通知出现时出发轻量的振动。",
"settingsExpandPostLink": "展开帖子链接",
"settingsExpandPostLinkDescription": "在帖子列表中展开显示帖子中的链接。",
"settingsExpandChatLink": "展开聊天链接",
"settingsExpandChatLinkDescription": "在聊天信息中展开显示内容中的链接。",
"settingsNetwork": "网络",
"settingsNetworkServer": "HyperNet 服务器",
"settingsNetworkServerDescription": "设置 HyperNet 服务器地址,选择我们提供的,或者自己搭建。",

View File

@ -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();

View File

@ -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;

View File

@ -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(),
),
];

View File

@ -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},
);

View File

@ -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: () {},
),
);
});

View File

@ -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(

View File

@ -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(

View File

@ -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,
),
),

View File

@ -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';

View File

@ -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),

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
# 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.2.2+55
version: 2.2.2+56
environment:
sdk: ^3.5.4