diff --git a/lib/screens/auth/login.dart b/lib/screens/auth/login.dart index e49ca19..1568fce 100644 --- a/lib/screens/auth/login.dart +++ b/lib/screens/auth/login.dart @@ -380,14 +380,17 @@ class _LoginLookupScreen extends HookConsumerWidget { data: { 'account': uname, 'device_id': await FlutterUdid.consistentUdid, - 'platform': switch (defaultTargetPlatform) { - TargetPlatform.iOS => 2, - TargetPlatform.android => 3, - TargetPlatform.macOS => 4, - TargetPlatform.windows => 5, - TargetPlatform.linux => 6, - _ => 1, - }, + 'platform': + kIsWeb + ? 1 + : switch (defaultTargetPlatform) { + TargetPlatform.iOS => 2, + TargetPlatform.android => 3, + TargetPlatform.macOS => 4, + TargetPlatform.windows => 5, + TargetPlatform.linux => 6, + _ => 0, + }, }, ); final result = SnAuthChallenge.fromJson(resp.data); diff --git a/lib/screens/chat/room.dart b/lib/screens/chat/room.dart index f70b849..845632e 100644 --- a/lib/screens/chat/room.dart +++ b/lib/screens/chat/room.dart @@ -12,11 +12,9 @@ import 'package:island/models/file.dart'; import 'package:island/pods/config.dart'; import 'package:island/pods/database.dart'; import 'package:island/pods/network.dart'; -import 'package:island/pods/userinfo.dart'; import 'package:island/pods/websocket.dart'; import 'package:island/route.gr.dart'; import 'package:island/screens/posts/compose.dart'; -import 'package:island/services/rtc.dart'; import 'package:island/widgets/alert.dart'; import 'package:island/widgets/content/cloud_file_collection.dart'; import 'package:island/widgets/content/cloud_files.dart'; @@ -473,21 +471,6 @@ class ChatRoomScreen extends HookConsumerWidget { error: (_, __) => const Text('Error'), ), actions: [ - IconButton( - onPressed: () { - final user = ref.watch(userInfoProvider); - if (currentlyJoined) { - leaveRealtimeChat(chatRoom.value!); - } else { - joinRealtimeChat( - ref.watch(apiClientProvider), - chatRoom.value!, - user.value!, - ); - } - }, - icon: const Icon(Symbols.video_call), - ), IconButton( icon: const Icon(Icons.more_vert), onPressed: () { diff --git a/lib/services/rtc.dart b/lib/services/rtc.dart deleted file mode 100644 index 3be7c24..0000000 --- a/lib/services/rtc.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'package:dio/dio.dart'; -import 'package:island/models/chat.dart'; -import 'package:island/models/user.dart'; -import 'package:tencent_rtc_sdk/trtc_cloud.dart'; -import 'package:tencent_rtc_sdk/trtc_cloud_def.dart'; -import 'package:tencent_rtc_sdk/trtc_cloud_listener.dart'; - -Future _getTRTCCloud() async { - return TRTCCloud.sharedInstance(); -} - -bool currentlyJoined = false; - -Future joinRealtimeChat( - Dio apiClient, - SnChatRoom chat, - SnAccount user, -) async { - final resp = await apiClient.get('/chat/realtime/${chat.id}'); - final data = ChatRealtimeJoinResponse.fromJson(resp.data); - final config = data.config; - final cloud = await _getTRTCCloud(); - cloud.setConsoleEnabled(true); - cloud.registerListener( - TRTCCloudListener( - onRemoteUserEnterRoom: (userId) { - print('onRemoteUserEnterRoom: $userId'); - }, - onRemoteUserLeaveRoom: (userId, reason) { - print('onRemoteUserLeaveRoom: $userId, $reason'); - }, - ), - ); - cloud.enterRoom( - TRTCParams( - sdkAppId: config['app_id'], - userId: user.name, - userSig: data.token, - roomId: chat.id, - role: TRTCRoleType.anchor, - ), - TRTCAppScene.voiceChatRoom, - ); - cloud.startLocalAudio(TRTCAudioQuality.speech); - currentlyJoined = true; -} - -Future leaveRealtimeChat(SnChatRoom chat) async { - final cloud = await _getTRTCCloud(); - cloud.exitRoom(); -} diff --git a/lib/widgets/alert.dart b/lib/widgets/alert.dart index 7675619..32ae3f0 100644 --- a/lib/widgets/alert.dart +++ b/lib/widgets/alert.dart @@ -7,6 +7,8 @@ import 'package:flutter_platform_alert/flutter_platform_alert.dart'; import 'package:gap/gap.dart'; import 'package:styled_widget/styled_widget.dart'; +// TODO support web here + String _parseRemoteError(DioException err) { log('${err.requestOptions.method} ${err.requestOptions.uri} ${err.message}'); if (err.response?.data is String) return err.response?.data; diff --git a/lib/widgets/content/image.native.dart b/lib/widgets/content/image.native.dart index 575554d..524d2fc 100644 --- a/lib/widgets/content/image.native.dart +++ b/lib/widgets/content/image.native.dart @@ -6,21 +6,48 @@ class UniversalImage extends StatelessWidget { final String uri; final String? blurHash; final BoxFit fit; + final double? width; + final double? height; + final bool noCacheOptimization; + const UniversalImage({ super.key, required this.uri, this.blurHash, this.fit = BoxFit.cover, + this.width, + this.height, + this.noCacheOptimization = false, }); @override Widget build(BuildContext context) { - return Stack( - fit: StackFit.expand, - children: [ - if (blurHash != null) BlurHash(hash: blurHash!), - CachedNetworkImage(imageUrl: uri, fit: fit), - ], + int? cacheWidth; + int? cacheHeight; + if (width != null && height != null && !noCacheOptimization) { + final devicePixelRatio = MediaQuery.of(context).devicePixelRatio; + cacheWidth = width != null ? (width! * devicePixelRatio).round() : null; + cacheHeight = + height != null ? (height! * devicePixelRatio).round() : null; + } + + return SizedBox( + width: width, + height: height, + child: Stack( + fit: StackFit.expand, + children: [ + if (blurHash != null) BlurHash(hash: blurHash!), + CachedNetworkImage( + imageUrl: uri, + fit: fit, + width: width, + height: height, + memCacheHeight: cacheHeight, + memCacheWidth: cacheWidth, + ), + ], + ), ); } } diff --git a/lib/widgets/content/image.web.dart b/lib/widgets/content/image.web.dart index 7146f7a..bd5fabe 100644 --- a/lib/widgets/content/image.web.dart +++ b/lib/widgets/content/image.web.dart @@ -5,11 +5,16 @@ class UniversalImage extends StatelessWidget { final String uri; final String? blurHash; final BoxFit fit; + final double? width; + final double? height; + const UniversalImage({ super.key, required this.uri, this.blurHash, this.fit = BoxFit.cover, + this.width, + this.height, }); @override @@ -19,8 +24,8 @@ class UniversalImage extends StatelessWidget { onElementCreated: (element) { element as web.HTMLImageElement; element.src = uri; - element.style.width = '100%'; - element.style.height = '100%'; + element.style.width = width?.toString() ?? '100%'; + element.style.height = height?.toString() ?? '100%'; element.style.objectFit = switch (fit) { BoxFit.cover || BoxFit.fitWidth || BoxFit.fitHeight => 'cover', BoxFit.fill => 'fill', diff --git a/lib/widgets/content/markdown.dart b/lib/widgets/content/markdown.dart index 1ae2b3b..7f29d31 100644 --- a/lib/widgets/content/markdown.dart +++ b/lib/widgets/content/markdown.dart @@ -1,16 +1,20 @@ import 'package:flutter/material.dart'; import 'package:flutter_highlight/flutter_highlight.dart'; import 'package:flutter_highlight/theme_map.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown_latex/flutter_markdown_latex.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:island/pods/config.dart'; import 'package:markdown/markdown.dart' as markdown; import 'package:url_launcher/url_launcher_string.dart'; -class MarkdownTextContent extends StatelessWidget { +import 'image.dart'; + +class MarkdownTextContent extends HookConsumerWidget { final String content; final bool isAutoWarp; - final bool isEnlargeSticker; final TextScaler? textScaler; final Color? textColor; @@ -18,13 +22,24 @@ class MarkdownTextContent extends StatelessWidget { super.key, required this.content, this.isAutoWarp = false, - this.isEnlargeSticker = false, this.textScaler, this.textColor, }); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final baseUrl = ref.watch(serverUrlProvider); + final doesEnlargeSticker = useMemoized(() { + // Check if content only contains one sticker by matching the sticker pattern + final stickerPattern = RegExp(r':([-\w]+):'); + final matches = stickerPattern.allMatches(content); + + // Content should only contain one sticker and nothing else (except whitespace) + final contentWithoutStickers = + content.replaceAll(stickerPattern, '').trim(); + return matches.length == 1 && contentWithoutStickers.isEmpty; + }, [content]); + return Markdown( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), @@ -70,6 +85,7 @@ class MarkdownTextContent extends StatelessWidget { ...markdown.ExtensionSet.gitHubFlavored.inlineSyntaxes, if (isAutoWarp) markdown.LineBreakSyntax(), _UserNameCardInlineSyntax(), + _StickerInlineSyntax(ref.read(serverUrlProvider)), markdown.AutolinkSyntax(), markdown.AutolinkExtensionSyntax(), markdown.CodeSyntax(), @@ -80,6 +96,35 @@ class MarkdownTextContent extends StatelessWidget { if (href == null) return; await launchUrlString(href, mode: LaunchMode.externalApplication); }, + imageBuilder: (uri, title, alt) { + if (uri.scheme == 'solink') { + switch (uri.host) { + case 'stickers': + final size = doesEnlargeSticker ? 96.0 : 24.0; + return ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(8)), + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainer, + borderRadius: const BorderRadius.all(Radius.circular(8)), + ), + child: UniversalImage( + uri: '$baseUrl/stickers/lookup/${uri.pathSegments[0]}/open', + width: size, + height: size, + fit: BoxFit.cover, + noCacheOptimization: true, + ), + ), + ); + } + } + final content = UniversalImage(uri: uri.toString(), fit: BoxFit.cover); + if (alt != null) { + return Tooltip(message: alt, child: content); + } + return content; + }, ); } } @@ -100,6 +145,21 @@ class _UserNameCardInlineSyntax extends markdown.InlineSyntax { } } +class _StickerInlineSyntax extends markdown.InlineSyntax { + final String baseUrl; + _StickerInlineSyntax(this.baseUrl) : super(r':([-\w]+):'); + + @override + bool onMatch(markdown.InlineParser parser, Match match) { + final placeholder = match[1]!; + final image = markdown.Element.text('img', '') + ..attributes['src'] = Uri.encodeFull('solink://stickers/$placeholder'); + parser.addNode(image); + + return true; + } +} + class HighlightBuilder extends MarkdownElementBuilder { @override Widget? visitElementAfterWithContext( diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 638995a..960c824 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -23,7 +23,6 @@ import shared_preferences_foundation import sqflite_darwin import sqlite3_flutter_libs import super_native_extensions -import tencent_rtc_sdk import url_launcher_macos import volume_controller import wakelock_plus @@ -47,7 +46,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin")) SuperNativeExtensionsPlugin.register(with: registry.registrar(forPlugin: "SuperNativeExtensionsPlugin")) - TencentRTCCloud.register(with: registry.registrar(forPlugin: "TencentRTCCloud")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) VolumeControllerPlugin.register(with: registry.registrar(forPlugin: "VolumeControllerPlugin")) WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 28dc1ac..312bc0a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1770,14 +1770,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.3" - tencent_rtc_sdk: - dependency: "direct main" - description: - name: tencent_rtc_sdk - sha256: "0ec7f3a32c443573a0509d06bf390e11a709fca338a46645c1d4dbc3f17f029a" - url: "https://pub.dev" - source: hosted - version: "12.3.6" term_glyph: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 4e2b810..60aeda6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -91,7 +91,6 @@ dependencies: flutter_expandable_fab: ^2.5.0 markdown_editor_plus: ^0.2.15 croppy: ^1.3.6 - tencent_rtc_sdk: ^12.3.6 table_calendar: ^3.1.3 relative_time: ^5.0.0 dropdown_button2: ^2.3.9 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 59a7204..c0ff750 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -17,7 +17,6 @@ #include #include #include -#include #include #include @@ -44,8 +43,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin")); SuperNativeExtensionsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("SuperNativeExtensionsPluginCApi")); - TrtcPluginCApiRegisterWithRegistrar( - registry->GetRegistrarForPlugin("TrtcPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); VolumeControllerPluginCApiRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index cf04a03..ecdf13b 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -14,7 +14,6 @@ list(APPEND FLUTTER_PLUGIN_LIST media_kit_video sqlite3_flutter_libs super_native_extensions - tencent_rtc_sdk url_launcher_windows volume_controller )