From e96b49e3cdf17fd96b952e14a1cc3ac59aaaf4cc Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sat, 1 Jun 2024 21:39:28 +0800 Subject: [PATCH] :zap: Improved attachments --- ios/Podfile.lock | 7 ++ lib/platform.dart | 39 +++++++++++ lib/providers/auth.dart | 12 ++-- lib/providers/content/attachment.dart | 23 ++++++- lib/translations.dart | 21 +++++- lib/widgets/account/account_avatar.dart | 14 ++-- lib/widgets/attachments/attachment_item.dart | 33 ++++++++-- .../attachments/attachment_publish.dart | 8 ++- macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 64 +++++++++++++++++++ pubspec.yaml | 1 + 11 files changed, 204 insertions(+), 20 deletions(-) create mode 100644 lib/platform.dart diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 52cb95e..e33937d 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -61,6 +61,9 @@ PODS: - SDWebImage (5.19.2): - SDWebImage/Core (= 5.19.2) - SDWebImage/Core (5.19.2) + - sqflite (0.0.3): + - Flutter + - FlutterMacOS - SwiftyGif (5.4.5) - url_launcher_ios (0.0.1): - Flutter @@ -84,6 +87,7 @@ DEPENDENCIES: - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) + - sqflite (from `.symlinks/plugins/sqflite/darwin`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`) - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`) @@ -121,6 +125,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/path_provider_foundation/darwin" permission_handler_apple: :path: ".symlinks/plugins/permission_handler_apple/ios" + sqflite: + :path: ".symlinks/plugins/sqflite/darwin" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" video_player_avfoundation: @@ -144,6 +150,7 @@ SPEC CHECKSUMS: path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 SDWebImage: dfe95b2466a9823cf9f0c6d01217c06550d7b29a + sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3 diff --git a/lib/platform.dart b/lib/platform.dart new file mode 100644 index 0000000..5c7967e --- /dev/null +++ b/lib/platform.dart @@ -0,0 +1,39 @@ +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +abstract class PlatformInfo { + static bool get isWeb => kIsWeb; + + static bool get isLinux => !kIsWeb && Platform.isLinux; + + static bool get isWindows => !kIsWeb && Platform.isWindows; + + static bool get isMacOS => !kIsWeb && Platform.isMacOS; + + static bool get isIOS => !kIsWeb && Platform.isIOS; + + static bool get isAndroid => !kIsWeb && Platform.isAndroid; + + static bool get isMobile => isAndroid || isIOS; + + // Not first tier supported platform + static bool get isBetaDesktop => isWindows || isLinux; + + static bool get isDesktop => isLinux || isWindows || isMacOS; + + static bool get useTouchscreen => !isMobile; + + static bool get canCacheImage => isAndroid || isIOS || isMacOS; + + static bool get canRecord => (isMobile || isMacOS); + + static Future getVersion() async { + var version = kIsWeb ? 'Web' : 'Unknown'; + try { + version = (await PackageInfo.fromPlatform()).version; + } catch (_) {} + return version; + } +} \ No newline at end of file diff --git a/lib/providers/auth.dart b/lib/providers/auth.dart index 48b0a3b..d4da95d 100644 --- a/lib/providers/auth.dart +++ b/lib/providers/auth.dart @@ -72,7 +72,7 @@ class AuthProvider extends GetConnect { String username, String password, ) async { - _cacheUserProfileResponse = null; + _cachedUserProfileResponse = null; final resp = await oauth2.resourceOwnerPasswordGrant( tokenEndpoint, @@ -105,7 +105,7 @@ class AuthProvider extends GetConnect { } void signout() { - _cacheUserProfileResponse = null; + _cachedUserProfileResponse = null; Get.find().disconnect(); Get.find().disconnect(); @@ -115,13 +115,13 @@ class AuthProvider extends GetConnect { storage.deleteAll(); } - Response? _cacheUserProfileResponse; + Response? _cachedUserProfileResponse; Future get isAuthorized => storage.containsKey(key: 'auth_credentials'); Future getProfile({noCache = false}) async { - if (!noCache && _cacheUserProfileResponse != null) { - return _cacheUserProfileResponse!; + if (!noCache && _cachedUserProfileResponse != null) { + return _cachedUserProfileResponse!; } final client = GetConnect(maxAuthRetries: 3); @@ -132,7 +132,7 @@ class AuthProvider extends GetConnect { if (resp.statusCode != 200) { throw Exception(resp.bodyString); } else { - _cacheUserProfileResponse = resp; + _cachedUserProfileResponse = resp; } return resp; diff --git a/lib/providers/content/attachment.dart b/lib/providers/content/attachment.dart index 2c86e5f..c972f41 100644 --- a/lib/providers/content/attachment.dart +++ b/lib/providers/content/attachment.dart @@ -25,14 +25,25 @@ Future calculateFileAspectRatio(File file) async { } class AttachmentProvider extends GetConnect { + static Map mimetypeOverrides = {'mov': 'video/quicktime'}; + @override void onInit() { httpClient.baseUrl = ServiceFinder.services['paperclip']; } - static Map mimetypeOverrides = {'mov': 'video/quicktime'}; + final Map _cachedResponses = {}; - Future getMetadata(int id) => get('/api/attachments/$id/meta'); + Future getMetadata(int id, {noCache = false}) async { + if (!noCache && _cachedResponses.containsKey(id)) { + return _cachedResponses[id]!; + } + + final resp = await get('/api/attachments/$id/meta'); + _cachedResponses[id] = resp; + + return resp; + } Future createAttachment(File file, String hash, String usage, {double? ratio}) async { @@ -122,4 +133,12 @@ class AttachmentProvider extends GetConnect { return resp; } + + void clearCache({int? id}) { + if (id != null) { + _cachedResponses.remove(id); + } else { + _cachedResponses.clear(); + } + } } diff --git a/lib/translations.dart b/lib/translations.dart index a82efbf..f803d1a 100644 --- a/lib/translations.dart +++ b/lib/translations.dart @@ -155,13 +155,32 @@ class SolianMessages extends Translations { 'Are your sure to delete message @id? This action cannot be undone!', 'callOngoing': 'A call is ongoing...', 'callJoin': 'Join', + 'callResume': 'Resume', 'callMicrophone': 'Microphone', 'callMicrophoneDisabled': 'Microphone Disabled', 'callMicrophoneSelect': 'Select Microphone', 'callCamera': 'Camera', 'callCameraDisabled': 'Camera Disabled', 'callCameraSelect': 'Select Camera', - 'callDisconnected': 'Call has been disconnected... @reason', + 'callSpeakerSelect': 'Select Speaker', + 'callDisconnected': 'Call Disconnected... @reason', + 'callMicrophoneOn': 'Turn Microphone On', + 'callMicrophoneOff': 'Turn Microphone Off', + 'callCameraOn': 'Turn Camera On', + 'callCameraOff': 'Turn Camera Off', + 'callVideoFlip': 'Flip Video Input', + 'callSpeakerphoneToggle': 'Toggle Speakerphone Mode', + 'callScreenOn': 'Start Screen Sharing', + 'callScreenOff': 'Stop Screen Sharing', + 'callDisconnect': 'Disconnect', + 'callDisconnectCaption': + 'Are you sure you want to disconnect from this call? You can also just return to the page, and the call will continue in the background.', + 'callParticipantAction': 'Participant Actions', + 'callParticipantMicrophoneOff': 'Mute Participant', + 'callParticipantMicrophoneOn': 'Unmute Participant', + 'callParticipantVideoOff': 'Turn Off Participant Video', + 'callParticipantVideoOn': 'Turn On Participant Video', + 'callAlreadyOngoing': 'A call is already ongoing', }, 'zh_CN': { 'hide': '隐藏', diff --git a/lib/widgets/account/account_avatar.dart b/lib/widgets/account/account_avatar.dart index 9e406cf..97eaece 100644 --- a/lib/widgets/account/account_avatar.dart +++ b/lib/widgets/account/account_avatar.dart @@ -1,4 +1,6 @@ +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; +import 'package:solian/platform.dart'; import 'package:solian/services.dart'; class AccountAvatar extends StatelessWidget { @@ -25,16 +27,18 @@ class AccountAvatar extends StatelessWidget { if (!isEmpty) isEmpty = content.endsWith('/api/attachments/0'); } + final url = direct + ? content + : '${ServiceFinder.services['paperclip']}/api/attachments/$content'; + return CircleAvatar( key: Key('a$content'), radius: radius, backgroundColor: bgColor, backgroundImage: !isEmpty - ? NetworkImage( - direct - ? content - : '${ServiceFinder.services['paperclip']}/api/attachments/$content', - ) + ? (PlatformInfo.canCacheImage + ? CachedNetworkImageProvider(url) + : NetworkImage(url)) as ImageProvider? : null, child: isEmpty ? Icon( diff --git a/lib/widgets/attachments/attachment_item.dart b/lib/widgets/attachments/attachment_item.dart index 80253dc..06a4651 100644 --- a/lib/widgets/attachments/attachment_item.dart +++ b/lib/widgets/attachments/attachment_item.dart @@ -1,7 +1,9 @@ +import 'package:cached_network_image/cached_network_image.dart'; import 'package:chewie/chewie.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:solian/models/attachment.dart'; +import 'package:solian/platform.dart'; import 'package:solian/services.dart'; import 'package:url_launcher/url_launcher_string.dart'; import 'package:video_player/video_player.dart'; @@ -61,10 +63,33 @@ class _AttachmentItemState extends State { child: Stack( fit: StackFit.expand, children: [ - Image.network( - '${ServiceFinder.services['paperclip']}/api/attachments/${widget.item.id}', - fit: widget.fit, - ), + if (PlatformInfo.canCacheImage) + CachedNetworkImage( + imageUrl: + '${ServiceFinder.services['paperclip']}/api/attachments/${widget.item.id}', + progressIndicatorBuilder: (context, url, downloadProgress) => + CircularProgressIndicator( + value: downloadProgress.progress, + ), + fit: widget.fit, + ) + else + Image.network( + '${ServiceFinder.services['paperclip']}/api/attachments/${widget.item.id}', + fit: widget.fit, + loadingBuilder: (BuildContext context, Widget child, + ImageChunkEvent? loadingProgress) { + if (loadingProgress == null) return child; + return Center( + child: CircularProgressIndicator( + value: loadingProgress.expectedTotalBytes != null + ? loadingProgress.cumulativeBytesLoaded / + loadingProgress.expectedTotalBytes! + : null, + ), + ); + }, + ), if (widget.showBadge && widget.badge != null) Positioned( right: 12, diff --git a/lib/widgets/attachments/attachment_publish.dart b/lib/widgets/attachments/attachment_publish.dart index dc1e917..c2a5cb2 100644 --- a/lib/widgets/attachments/attachment_publish.dart +++ b/lib/widgets/attachments/attachment_publish.dart @@ -390,12 +390,16 @@ class _AttachmentEditingDialogState extends State { : null, isMature: _isMature, ); + + Get.find().clearCache(id: widget.item.id); + + setState(() => _isBusy = false); return Attachment.fromJson(resp.body); } catch (e) { context.showErrorDialog(e); - return null; - } finally { + setState(() => _isBusy = false); + return null; } } diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index ab43fc1..b80f274 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -14,6 +14,7 @@ import flutter_webrtc import livekit_client import package_info_plus import path_provider_foundation +import sqflite import url_launcher_macos import video_player_avfoundation import wakelock_plus @@ -28,6 +29,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 4da77f4..f75a0c0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -33,6 +33,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + sha256: "28ea9690a8207179c319965c13cd8df184d5ee721ae2ce60f398ced1219cea1f" + url: "https://pub.dev" + source: hosted + version: "3.3.1" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: "9e90e78ae72caa874a323d78fa6301b3fb8fa7ea76a8f96dc5b5bf79f283bf2f" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "205d6a9f1862de34b93184f22b9d2d94586b2f05c581d546695e3d8f6a805cd7" + url: "https://pub.dev" + source: hosted + version: "1.2.0" carousel_slider: dependency: "direct main" description: @@ -278,6 +302,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "395d6b7831f21f3b989ebedbb785545932adb9afe2622c1ffacf7f4b53a7e544" + url: "https://pub.dev" + source: hosted + version: "3.3.2" flutter_launcher_icons: dependency: "direct dev" description: @@ -680,6 +712,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.2" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "45b40f99622f11901238e18d48f5f12ea36426d8eced9f4cbf58479c7aa2430d" + url: "https://pub.dev" + source: hosted + version: "2.0.0" package_info_plus: dependency: transitive description: @@ -856,6 +896,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" + url: "https://pub.dev" + source: hosted + version: "0.27.7" sdp_transform: dependency: transitive description: @@ -893,6 +941,22 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d + url: "https://pub.dev" + source: hosted + version: "2.3.3+1" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "3da423ce7baf868be70e2c0976c28a1bb2f73644268b7ffa7d2e08eab71f16a4" + url: "https://pub.dev" + source: hosted + version: "2.5.4" stack_trace: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index bc9e456..562062e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -64,6 +64,7 @@ dependencies: flutter_webrtc: ^0.10.7 wakelock_plus: ^1.2.5 flutter_background: ^1.2.0 + cached_network_image: ^3.3.1 dev_dependencies: flutter_test: