Improved attachments

This commit is contained in:
LittleSheep 2024-06-01 21:39:28 +08:00
parent a651350104
commit e96b49e3cd
11 changed files with 204 additions and 20 deletions

View File

@ -61,6 +61,9 @@ PODS:
- SDWebImage (5.19.2): - SDWebImage (5.19.2):
- SDWebImage/Core (= 5.19.2) - SDWebImage/Core (= 5.19.2)
- SDWebImage/Core (5.19.2) - SDWebImage/Core (5.19.2)
- sqflite (0.0.3):
- Flutter
- FlutterMacOS
- SwiftyGif (5.4.5) - SwiftyGif (5.4.5)
- url_launcher_ios (0.0.1): - url_launcher_ios (0.0.1):
- Flutter - Flutter
@ -84,6 +87,7 @@ DEPENDENCIES:
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - 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`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`) - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`) - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
@ -121,6 +125,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/path_provider_foundation/darwin" :path: ".symlinks/plugins/path_provider_foundation/darwin"
permission_handler_apple: permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios" :path: ".symlinks/plugins/permission_handler_apple/ios"
sqflite:
:path: ".symlinks/plugins/sqflite/darwin"
url_launcher_ios: url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios" :path: ".symlinks/plugins/url_launcher_ios/ios"
video_player_avfoundation: video_player_avfoundation:
@ -144,6 +150,7 @@ SPEC CHECKSUMS:
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
SDWebImage: dfe95b2466a9823cf9f0c6d01217c06550d7b29a SDWebImage: dfe95b2466a9823cf9f0c6d01217c06550d7b29a
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3 video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3

39
lib/platform.dart Normal file
View File

@ -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<String> getVersion() async {
var version = kIsWeb ? 'Web' : 'Unknown';
try {
version = (await PackageInfo.fromPlatform()).version;
} catch (_) {}
return version;
}
}

View File

@ -72,7 +72,7 @@ class AuthProvider extends GetConnect {
String username, String username,
String password, String password,
) async { ) async {
_cacheUserProfileResponse = null; _cachedUserProfileResponse = null;
final resp = await oauth2.resourceOwnerPasswordGrant( final resp = await oauth2.resourceOwnerPasswordGrant(
tokenEndpoint, tokenEndpoint,
@ -105,7 +105,7 @@ class AuthProvider extends GetConnect {
} }
void signout() { void signout() {
_cacheUserProfileResponse = null; _cachedUserProfileResponse = null;
Get.find<ChatProvider>().disconnect(); Get.find<ChatProvider>().disconnect();
Get.find<AccountProvider>().disconnect(); Get.find<AccountProvider>().disconnect();
@ -115,13 +115,13 @@ class AuthProvider extends GetConnect {
storage.deleteAll(); storage.deleteAll();
} }
Response? _cacheUserProfileResponse; Response? _cachedUserProfileResponse;
Future<bool> get isAuthorized => storage.containsKey(key: 'auth_credentials'); Future<bool> get isAuthorized => storage.containsKey(key: 'auth_credentials');
Future<Response> getProfile({noCache = false}) async { Future<Response> getProfile({noCache = false}) async {
if (!noCache && _cacheUserProfileResponse != null) { if (!noCache && _cachedUserProfileResponse != null) {
return _cacheUserProfileResponse!; return _cachedUserProfileResponse!;
} }
final client = GetConnect(maxAuthRetries: 3); final client = GetConnect(maxAuthRetries: 3);
@ -132,7 +132,7 @@ class AuthProvider extends GetConnect {
if (resp.statusCode != 200) { if (resp.statusCode != 200) {
throw Exception(resp.bodyString); throw Exception(resp.bodyString);
} else { } else {
_cacheUserProfileResponse = resp; _cachedUserProfileResponse = resp;
} }
return resp; return resp;

View File

@ -25,14 +25,25 @@ Future<double> calculateFileAspectRatio(File file) async {
} }
class AttachmentProvider extends GetConnect { class AttachmentProvider extends GetConnect {
static Map<String, String> mimetypeOverrides = {'mov': 'video/quicktime'};
@override @override
void onInit() { void onInit() {
httpClient.baseUrl = ServiceFinder.services['paperclip']; httpClient.baseUrl = ServiceFinder.services['paperclip'];
} }
static Map<String, String> mimetypeOverrides = {'mov': 'video/quicktime'}; final Map<int, Response> _cachedResponses = {};
Future<Response> getMetadata(int id) => get('/api/attachments/$id/meta'); Future<Response> 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<Response> createAttachment(File file, String hash, String usage, Future<Response> createAttachment(File file, String hash, String usage,
{double? ratio}) async { {double? ratio}) async {
@ -122,4 +133,12 @@ class AttachmentProvider extends GetConnect {
return resp; return resp;
} }
void clearCache({int? id}) {
if (id != null) {
_cachedResponses.remove(id);
} else {
_cachedResponses.clear();
}
}
} }

View File

@ -155,13 +155,32 @@ class SolianMessages extends Translations {
'Are your sure to delete message @id? This action cannot be undone!', 'Are your sure to delete message @id? This action cannot be undone!',
'callOngoing': 'A call is ongoing...', 'callOngoing': 'A call is ongoing...',
'callJoin': 'Join', 'callJoin': 'Join',
'callResume': 'Resume',
'callMicrophone': 'Microphone', 'callMicrophone': 'Microphone',
'callMicrophoneDisabled': 'Microphone Disabled', 'callMicrophoneDisabled': 'Microphone Disabled',
'callMicrophoneSelect': 'Select Microphone', 'callMicrophoneSelect': 'Select Microphone',
'callCamera': 'Camera', 'callCamera': 'Camera',
'callCameraDisabled': 'Camera Disabled', 'callCameraDisabled': 'Camera Disabled',
'callCameraSelect': 'Select Camera', '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': { 'zh_CN': {
'hide': '隐藏', 'hide': '隐藏',

View File

@ -1,4 +1,6 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:solian/platform.dart';
import 'package:solian/services.dart'; import 'package:solian/services.dart';
class AccountAvatar extends StatelessWidget { class AccountAvatar extends StatelessWidget {
@ -25,16 +27,18 @@ class AccountAvatar extends StatelessWidget {
if (!isEmpty) isEmpty = content.endsWith('/api/attachments/0'); if (!isEmpty) isEmpty = content.endsWith('/api/attachments/0');
} }
final url = direct
? content
: '${ServiceFinder.services['paperclip']}/api/attachments/$content';
return CircleAvatar( return CircleAvatar(
key: Key('a$content'), key: Key('a$content'),
radius: radius, radius: radius,
backgroundColor: bgColor, backgroundColor: bgColor,
backgroundImage: !isEmpty backgroundImage: !isEmpty
? NetworkImage( ? (PlatformInfo.canCacheImage
direct ? CachedNetworkImageProvider(url)
? content : NetworkImage(url)) as ImageProvider<Object>?
: '${ServiceFinder.services['paperclip']}/api/attachments/$content',
)
: null, : null,
child: isEmpty child: isEmpty
? Icon( ? Icon(

View File

@ -1,7 +1,9 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:chewie/chewie.dart'; import 'package:chewie/chewie.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:solian/models/attachment.dart'; import 'package:solian/models/attachment.dart';
import 'package:solian/platform.dart';
import 'package:solian/services.dart'; import 'package:solian/services.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
import 'package:video_player/video_player.dart'; import 'package:video_player/video_player.dart';
@ -61,10 +63,33 @@ class _AttachmentItemState extends State<AttachmentItem> {
child: Stack( child: Stack(
fit: StackFit.expand, fit: StackFit.expand,
children: [ children: [
Image.network( if (PlatformInfo.canCacheImage)
'${ServiceFinder.services['paperclip']}/api/attachments/${widget.item.id}', CachedNetworkImage(
fit: widget.fit, 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) if (widget.showBadge && widget.badge != null)
Positioned( Positioned(
right: 12, right: 12,

View File

@ -390,12 +390,16 @@ class _AttachmentEditingDialogState extends State<AttachmentEditingDialog> {
: null, : null,
isMature: _isMature, isMature: _isMature,
); );
Get.find<AttachmentProvider>().clearCache(id: widget.item.id);
setState(() => _isBusy = false);
return Attachment.fromJson(resp.body); return Attachment.fromJson(resp.body);
} catch (e) { } catch (e) {
context.showErrorDialog(e); context.showErrorDialog(e);
return null;
} finally {
setState(() => _isBusy = false); setState(() => _isBusy = false);
return null;
} }
} }

View File

@ -14,6 +14,7 @@ import flutter_webrtc
import livekit_client import livekit_client
import package_info_plus import package_info_plus
import path_provider_foundation import path_provider_foundation
import sqflite
import url_launcher_macos import url_launcher_macos
import video_player_avfoundation import video_player_avfoundation
import wakelock_plus import wakelock_plus
@ -28,6 +29,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin")) LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin"))

View File

@ -33,6 +33,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" 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: carousel_slider:
dependency: "direct main" dependency: "direct main"
description: description:
@ -278,6 +302,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" 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: flutter_launcher_icons:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -680,6 +712,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.2" 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: package_info_plus:
dependency: transitive dependency: transitive
description: description:
@ -856,6 +896,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.1.4"
rxdart:
dependency: transitive
description:
name: rxdart
sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb"
url: "https://pub.dev"
source: hosted
version: "0.27.7"
sdp_transform: sdp_transform:
dependency: transitive dependency: transitive
description: description:
@ -893,6 +941,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.0.0" 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: stack_trace:
dependency: transitive dependency: transitive
description: description:

View File

@ -64,6 +64,7 @@ dependencies:
flutter_webrtc: ^0.10.7 flutter_webrtc: ^0.10.7
wakelock_plus: ^1.2.5 wakelock_plus: ^1.2.5
flutter_background: ^1.2.0 flutter_background: ^1.2.0
cached_network_image: ^3.3.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: