♻️ Better file upload
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
PODS:
|
PODS:
|
||||||
- Alamofire (5.10.2)
|
- Alamofire (5.10.2)
|
||||||
|
- app_links (6.4.1):
|
||||||
|
- Flutter
|
||||||
- connectivity_plus (0.0.1):
|
- connectivity_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- croppy (0.0.1):
|
- croppy (0.0.1):
|
||||||
@@ -303,6 +305,7 @@ PODS:
|
|||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- Alamofire
|
- Alamofire
|
||||||
|
- app_links (from `.symlinks/plugins/app_links/ios`)
|
||||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||||
- croppy (from `.symlinks/plugins/croppy/ios`)
|
- croppy (from `.symlinks/plugins/croppy/ios`)
|
||||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||||
@@ -379,6 +382,8 @@ SPEC REPOS:
|
|||||||
- WebRTC-SDK
|
- WebRTC-SDK
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
|
app_links:
|
||||||
|
:path: ".symlinks/plugins/app_links/ios"
|
||||||
connectivity_plus:
|
connectivity_plus:
|
||||||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||||
croppy:
|
croppy:
|
||||||
@@ -468,6 +473,7 @@ EXTERNAL SOURCES:
|
|||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
Alamofire: 7193b3b92c74a07f85569e1a6c4f4237291e7496
|
Alamofire: 7193b3b92c74a07f85569e1a6c4f4237291e7496
|
||||||
|
app_links: 3dbc685f76b1693c66a6d9dd1e9ab6f73d97dc0a
|
||||||
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
||||||
croppy: 979e8ddc254f4642bffe7d52dc7193354b27ba30
|
croppy: 979e8ddc254f4642bffe7d52dc7193354b27ba30
|
||||||
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
|
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
|
||||||
|
@@ -14,6 +14,7 @@ sealed class UniversalFile with _$UniversalFile {
|
|||||||
required dynamic data,
|
required dynamic data,
|
||||||
required UniversalFileType type,
|
required UniversalFileType type,
|
||||||
@Default(false) bool isLink,
|
@Default(false) bool isLink,
|
||||||
|
String? displayName,
|
||||||
}) = _UniversalFile;
|
}) = _UniversalFile;
|
||||||
|
|
||||||
factory UniversalFile.fromJson(Map<String, dynamic> json) =>
|
factory UniversalFile.fromJson(Map<String, dynamic> json) =>
|
||||||
@@ -31,6 +32,7 @@ sealed class UniversalFile with _$UniversalFile {
|
|||||||
'video' => UniversalFileType.video,
|
'video' => UniversalFileType.video,
|
||||||
_ => UniversalFileType.file,
|
_ => UniversalFileType.file,
|
||||||
},
|
},
|
||||||
|
displayName: attachment.name,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -15,7 +15,7 @@ T _$identity<T>(T value) => value;
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$UniversalFile {
|
mixin _$UniversalFile {
|
||||||
|
|
||||||
dynamic get data; UniversalFileType get type; bool get isLink;
|
dynamic get data; UniversalFileType get type; bool get isLink; String? get displayName;
|
||||||
/// Create a copy of UniversalFile
|
/// Create a copy of UniversalFile
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -28,16 +28,16 @@ $UniversalFileCopyWith<UniversalFile> get copyWith => _$UniversalFileCopyWithImp
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is UniversalFile&&const DeepCollectionEquality().equals(other.data, data)&&(identical(other.type, type) || other.type == type)&&(identical(other.isLink, isLink) || other.isLink == isLink));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is UniversalFile&&const DeepCollectionEquality().equals(other.data, data)&&(identical(other.type, type) || other.type == type)&&(identical(other.isLink, isLink) || other.isLink == isLink)&&(identical(other.displayName, displayName) || other.displayName == displayName));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(data),type,isLink);
|
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(data),type,isLink,displayName);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'UniversalFile(data: $data, type: $type, isLink: $isLink)';
|
return 'UniversalFile(data: $data, type: $type, isLink: $isLink, displayName: $displayName)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ abstract mixin class $UniversalFileCopyWith<$Res> {
|
|||||||
factory $UniversalFileCopyWith(UniversalFile value, $Res Function(UniversalFile) _then) = _$UniversalFileCopyWithImpl;
|
factory $UniversalFileCopyWith(UniversalFile value, $Res Function(UniversalFile) _then) = _$UniversalFileCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
dynamic data, UniversalFileType type, bool isLink
|
dynamic data, UniversalFileType type, bool isLink, String? displayName
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -65,12 +65,13 @@ class _$UniversalFileCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of UniversalFile
|
/// Create a copy of UniversalFile
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? data = freezed,Object? type = null,Object? isLink = null,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? data = freezed,Object? type = null,Object? isLink = null,Object? displayName = freezed,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable
|
data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable
|
||||||
as dynamic,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
as dynamic,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
as UniversalFileType,isLink: null == isLink ? _self.isLink : isLink // ignore: cast_nullable_to_non_nullable
|
as UniversalFileType,isLink: null == isLink ? _self.isLink : isLink // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,displayName: freezed == displayName ? _self.displayName : displayName // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,10 +153,10 @@ return $default(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( dynamic data, UniversalFileType type, bool isLink)? $default,{required TResult orElse(),}) {final _that = this;
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( dynamic data, UniversalFileType type, bool isLink, String? displayName)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _UniversalFile() when $default != null:
|
case _UniversalFile() when $default != null:
|
||||||
return $default(_that.data,_that.type,_that.isLink);case _:
|
return $default(_that.data,_that.type,_that.isLink,_that.displayName);case _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -173,10 +174,10 @@ return $default(_that.data,_that.type,_that.isLink);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( dynamic data, UniversalFileType type, bool isLink) $default,) {final _that = this;
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( dynamic data, UniversalFileType type, bool isLink, String? displayName) $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _UniversalFile():
|
case _UniversalFile():
|
||||||
return $default(_that.data,_that.type,_that.isLink);}
|
return $default(_that.data,_that.type,_that.isLink,_that.displayName);}
|
||||||
}
|
}
|
||||||
/// A variant of `when` that fallback to returning `null`
|
/// A variant of `when` that fallback to returning `null`
|
||||||
///
|
///
|
||||||
@@ -190,10 +191,10 @@ return $default(_that.data,_that.type,_that.isLink);}
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( dynamic data, UniversalFileType type, bool isLink)? $default,) {final _that = this;
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( dynamic data, UniversalFileType type, bool isLink, String? displayName)? $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _UniversalFile() when $default != null:
|
case _UniversalFile() when $default != null:
|
||||||
return $default(_that.data,_that.type,_that.isLink);case _:
|
return $default(_that.data,_that.type,_that.isLink,_that.displayName);case _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -205,12 +206,13 @@ return $default(_that.data,_that.type,_that.isLink);case _:
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _UniversalFile extends UniversalFile {
|
class _UniversalFile extends UniversalFile {
|
||||||
const _UniversalFile({required this.data, required this.type, this.isLink = false}): super._();
|
const _UniversalFile({required this.data, required this.type, this.isLink = false, this.displayName}): super._();
|
||||||
factory _UniversalFile.fromJson(Map<String, dynamic> json) => _$UniversalFileFromJson(json);
|
factory _UniversalFile.fromJson(Map<String, dynamic> json) => _$UniversalFileFromJson(json);
|
||||||
|
|
||||||
@override final dynamic data;
|
@override final dynamic data;
|
||||||
@override final UniversalFileType type;
|
@override final UniversalFileType type;
|
||||||
@override@JsonKey() final bool isLink;
|
@override@JsonKey() final bool isLink;
|
||||||
|
@override final String? displayName;
|
||||||
|
|
||||||
/// Create a copy of UniversalFile
|
/// Create a copy of UniversalFile
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@@ -225,16 +227,16 @@ Map<String, dynamic> toJson() {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _UniversalFile&&const DeepCollectionEquality().equals(other.data, data)&&(identical(other.type, type) || other.type == type)&&(identical(other.isLink, isLink) || other.isLink == isLink));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _UniversalFile&&const DeepCollectionEquality().equals(other.data, data)&&(identical(other.type, type) || other.type == type)&&(identical(other.isLink, isLink) || other.isLink == isLink)&&(identical(other.displayName, displayName) || other.displayName == displayName));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(data),type,isLink);
|
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(data),type,isLink,displayName);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'UniversalFile(data: $data, type: $type, isLink: $isLink)';
|
return 'UniversalFile(data: $data, type: $type, isLink: $isLink, displayName: $displayName)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -245,7 +247,7 @@ abstract mixin class _$UniversalFileCopyWith<$Res> implements $UniversalFileCopy
|
|||||||
factory _$UniversalFileCopyWith(_UniversalFile value, $Res Function(_UniversalFile) _then) = __$UniversalFileCopyWithImpl;
|
factory _$UniversalFileCopyWith(_UniversalFile value, $Res Function(_UniversalFile) _then) = __$UniversalFileCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
dynamic data, UniversalFileType type, bool isLink
|
dynamic data, UniversalFileType type, bool isLink, String? displayName
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -262,12 +264,13 @@ class __$UniversalFileCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of UniversalFile
|
/// Create a copy of UniversalFile
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? data = freezed,Object? type = null,Object? isLink = null,}) {
|
@override @pragma('vm:prefer-inline') $Res call({Object? data = freezed,Object? type = null,Object? isLink = null,Object? displayName = freezed,}) {
|
||||||
return _then(_UniversalFile(
|
return _then(_UniversalFile(
|
||||||
data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable
|
data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable
|
||||||
as dynamic,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
as dynamic,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
as UniversalFileType,isLink: null == isLink ? _self.isLink : isLink // ignore: cast_nullable_to_non_nullable
|
as UniversalFileType,isLink: null == isLink ? _self.isLink : isLink // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,displayName: freezed == displayName ? _self.displayName : displayName // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -11,6 +11,7 @@ _UniversalFile _$UniversalFileFromJson(Map<String, dynamic> json) =>
|
|||||||
data: json['data'],
|
data: json['data'],
|
||||||
type: $enumDecode(_$UniversalFileTypeEnumMap, json['type']),
|
type: $enumDecode(_$UniversalFileTypeEnumMap, json['type']),
|
||||||
isLink: json['is_link'] as bool? ?? false,
|
isLink: json['is_link'] as bool? ?? false,
|
||||||
|
displayName: json['display_name'] as String?,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$UniversalFileToJson(_UniversalFile instance) =>
|
Map<String, dynamic> _$UniversalFileToJson(_UniversalFile instance) =>
|
||||||
@@ -18,6 +19,7 @@ Map<String, dynamic> _$UniversalFileToJson(_UniversalFile instance) =>
|
|||||||
'data': instance.data,
|
'data': instance.data,
|
||||||
'type': _$UniversalFileTypeEnumMap[instance.type]!,
|
'type': _$UniversalFileTypeEnumMap[instance.type]!,
|
||||||
'is_link': instance.isLink,
|
'is_link': instance.isLink,
|
||||||
|
'display_name': instance.displayName,
|
||||||
};
|
};
|
||||||
|
|
||||||
const _$UniversalFileTypeEnumMap = {
|
const _$UniversalFileTypeEnumMap = {
|
||||||
|
@@ -7,7 +7,7 @@ part of 'chat_subscribe.dart';
|
|||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$chatSubscribeNotifierHash() =>
|
String _$chatSubscribeNotifierHash() =>
|
||||||
r'df65ecf15d0e97d7e6850ac57b4e681606e77179';
|
r'c605e0c9c45df64e5ba7b65f8de9b47bde8e2b3b';
|
||||||
|
|
||||||
/// Copied from Dart SDK
|
/// Copied from Dart SDK
|
||||||
class _SystemHash {
|
class _SystemHash {
|
||||||
|
@@ -6,7 +6,7 @@ part of 'chat_summary.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$chatSummaryHash() => r'7b79dba7445f634373fbb2ee0ced99b2302097c2';
|
String _$chatSummaryHash() => r'33815a3bd81d20902b7063e8194fe336930df9b4';
|
||||||
|
|
||||||
/// See also [ChatSummary].
|
/// See also [ChatSummary].
|
||||||
@ProviderFor(ChatSummary)
|
@ProviderFor(ChatSummary)
|
||||||
|
@@ -7,11 +7,10 @@ import "package:island/database/drift_db.dart";
|
|||||||
import "package:island/database/message.dart";
|
import "package:island/database/message.dart";
|
||||||
import "package:island/models/chat.dart";
|
import "package:island/models/chat.dart";
|
||||||
import "package:island/models/file.dart";
|
import "package:island/models/file.dart";
|
||||||
import "package:island/pods/config.dart";
|
|
||||||
import "package:island/pods/database.dart";
|
import "package:island/pods/database.dart";
|
||||||
import "package:island/pods/lifecycle.dart";
|
import "package:island/pods/lifecycle.dart";
|
||||||
import "package:island/pods/network.dart";
|
import "package:island/pods/network.dart";
|
||||||
import "package:island/services/file.dart";
|
import "package:island/services/file_uploader.dart";
|
||||||
import "package:island/talker.dart";
|
import "package:island/talker.dart";
|
||||||
import "package:island/widgets/alert.dart";
|
import "package:island/widgets/alert.dart";
|
||||||
import "package:riverpod_annotation/riverpod_annotation.dart";
|
import "package:riverpod_annotation/riverpod_annotation.dart";
|
||||||
@@ -362,9 +361,6 @@ class MessagesNotifier extends _$MessagesNotifier {
|
|||||||
}) async {
|
}) async {
|
||||||
final nonce = const Uuid().v4();
|
final nonce = const Uuid().v4();
|
||||||
talker.log('Sending message with nonce $nonce');
|
talker.log('Sending message with nonce $nonce');
|
||||||
final baseUrl = ref.read(serverUrlProvider);
|
|
||||||
final token = await getToken(ref.watch(tokenProvider));
|
|
||||||
if (token == null) throw ArgumentError('Access token is null');
|
|
||||||
|
|
||||||
final mockMessage = SnChatMessage(
|
final mockMessage = SnChatMessage(
|
||||||
id: 'pending_$nonce',
|
id: 'pending_$nonce',
|
||||||
@@ -393,19 +389,9 @@ class MessagesNotifier extends _$MessagesNotifier {
|
|||||||
var cloudAttachments = List.empty(growable: true);
|
var cloudAttachments = List.empty(growable: true);
|
||||||
for (var idx = 0; idx < attachments.length; idx++) {
|
for (var idx = 0; idx < attachments.length; idx++) {
|
||||||
final cloudFile =
|
final cloudFile =
|
||||||
await putFileToCloud(
|
await FileUploader.createCloudFile(
|
||||||
fileData: attachments[idx],
|
fileData: attachments[idx],
|
||||||
atk: token,
|
client: ref.read(apiClientProvider),
|
||||||
baseUrl: baseUrl,
|
|
||||||
filename: attachments[idx].data.name ?? 'Post media',
|
|
||||||
mimetype:
|
|
||||||
attachments[idx].data.mimeType ??
|
|
||||||
switch (attachments[idx].type) {
|
|
||||||
UniversalFileType.image => 'image/unknown',
|
|
||||||
UniversalFileType.video => 'video/unknown',
|
|
||||||
UniversalFileType.audio => 'audio/unknown',
|
|
||||||
UniversalFileType.file => 'application/octet-stream',
|
|
||||||
},
|
|
||||||
onProgress: (progress, _) {
|
onProgress: (progress, _) {
|
||||||
_fileUploadProgress[localMessage.id]?[idx] = progress;
|
_fileUploadProgress[localMessage.id]?[idx] = progress;
|
||||||
onProgress?.call(
|
onProgress?.call(
|
||||||
|
@@ -6,7 +6,7 @@ part of 'messages_notifier.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$messagesNotifierHash() => r'3aad1491b777570913f3867abd280fa59949b1f1';
|
String _$messagesNotifierHash() => r'b0cff44cea9f15a2684b602c48b32cd3d78875ab';
|
||||||
|
|
||||||
/// Copied from Dart SDK
|
/// Copied from Dart SDK
|
||||||
class _SystemHash {
|
class _SystemHash {
|
||||||
|
@@ -9,10 +9,10 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:island/models/file.dart';
|
import 'package:island/models/file.dart';
|
||||||
import 'package:island/models/account.dart';
|
import 'package:island/models/account.dart';
|
||||||
import 'package:island/pods/config.dart';
|
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/pods/userinfo.dart';
|
import 'package:island/pods/userinfo.dart';
|
||||||
import 'package:island/services/file.dart';
|
import 'package:island/services/file.dart';
|
||||||
|
import 'package:island/services/file_uploader.dart';
|
||||||
import 'package:island/services/timezone.dart';
|
import 'package:island/services/timezone.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
@@ -62,19 +62,13 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
submitting.value = true;
|
submitting.value = true;
|
||||||
try {
|
try {
|
||||||
final baseUrl = ref.watch(serverUrlProvider);
|
|
||||||
final token = await getToken(ref.watch(tokenProvider));
|
|
||||||
if (token == null) throw ArgumentError('Token is null');
|
|
||||||
final cloudFile =
|
final cloudFile =
|
||||||
await putFileToCloud(
|
await FileUploader.createCloudFile(
|
||||||
|
client: ref.read(apiClientProvider),
|
||||||
fileData: UniversalFile(
|
fileData: UniversalFile(
|
||||||
data: result,
|
data: result,
|
||||||
type: UniversalFileType.image,
|
type: UniversalFileType.image,
|
||||||
),
|
),
|
||||||
atk: token,
|
|
||||||
baseUrl: baseUrl,
|
|
||||||
filename: result.name,
|
|
||||||
mimetype: result.mimeType ?? 'image/jpeg',
|
|
||||||
).future;
|
).future;
|
||||||
if (cloudFile == null) {
|
if (cloudFile == null) {
|
||||||
throw ArgumentError('Failed to upload the file...');
|
throw ArgumentError('Failed to upload the file...');
|
||||||
|
@@ -13,10 +13,10 @@ import 'package:island/models/file.dart';
|
|||||||
import 'package:island/models/realm.dart';
|
import 'package:island/models/realm.dart';
|
||||||
import 'package:island/pods/chat/call.dart';
|
import 'package:island/pods/chat/call.dart';
|
||||||
import 'package:island/pods/chat/chat_summary.dart';
|
import 'package:island/pods/chat/chat_summary.dart';
|
||||||
import 'package:island/pods/config.dart';
|
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/screens/realm/realms.dart';
|
import 'package:island/screens/realm/realms.dart';
|
||||||
import 'package:island/services/file.dart';
|
import 'package:island/services/file.dart';
|
||||||
|
import 'package:island/services/file_uploader.dart';
|
||||||
import 'package:island/services/responsive.dart';
|
import 'package:island/services/responsive.dart';
|
||||||
import 'package:island/widgets/account/account_picker.dart';
|
import 'package:island/widgets/account/account_picker.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
@@ -644,19 +644,13 @@ class EditChatScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
submitting.value = true;
|
submitting.value = true;
|
||||||
try {
|
try {
|
||||||
final baseUrl = ref.watch(serverUrlProvider);
|
|
||||||
final token = await getToken(ref.watch(tokenProvider));
|
|
||||||
if (token == null) throw ArgumentError('Token is null');
|
|
||||||
final cloudFile =
|
final cloudFile =
|
||||||
await putFileToCloud(
|
await FileUploader.createCloudFile(
|
||||||
|
client: ref.read(apiClientProvider),
|
||||||
fileData: UniversalFile(
|
fileData: UniversalFile(
|
||||||
data: result,
|
data: result,
|
||||||
type: UniversalFileType.image,
|
type: UniversalFileType.image,
|
||||||
),
|
),
|
||||||
atk: token,
|
|
||||||
baseUrl: baseUrl,
|
|
||||||
filename: result.name,
|
|
||||||
mimetype: result.mimeType ?? 'image/jpeg',
|
|
||||||
).future;
|
).future;
|
||||||
if (cloudFile == null) {
|
if (cloudFile == null) {
|
||||||
throw ArgumentError('Failed to upload the file...');
|
throw ArgumentError('Failed to upload the file...');
|
||||||
|
@@ -11,11 +11,10 @@ import "package:island/models/chat.dart";
|
|||||||
import "package:island/models/file.dart";
|
import "package:island/models/file.dart";
|
||||||
import "package:island/pods/chat/chat_rooms.dart";
|
import "package:island/pods/chat/chat_rooms.dart";
|
||||||
import "package:island/pods/chat/chat_subscribe.dart";
|
import "package:island/pods/chat/chat_subscribe.dart";
|
||||||
import "package:island/pods/config.dart";
|
|
||||||
import "package:island/pods/chat/messages_notifier.dart";
|
import "package:island/pods/chat/messages_notifier.dart";
|
||||||
import "package:island/pods/network.dart";
|
import "package:island/pods/network.dart";
|
||||||
import "package:island/pods/chat/chat_online_count.dart";
|
import "package:island/pods/chat/chat_online_count.dart";
|
||||||
import "package:island/services/file.dart";
|
import "package:island/services/file_uploader.dart";
|
||||||
import "package:island/screens/chat/chat.dart";
|
import "package:island/screens/chat/chat.dart";
|
||||||
import "package:island/services/responsive.dart";
|
import "package:island/services/responsive.dart";
|
||||||
import "package:island/widgets/alert.dart";
|
import "package:island/widgets/alert.dart";
|
||||||
@@ -24,7 +23,6 @@ import "package:island/widgets/attachment_uploader.dart";
|
|||||||
import "package:island/widgets/chat/call_overlay.dart";
|
import "package:island/widgets/chat/call_overlay.dart";
|
||||||
import "package:island/widgets/chat/message_item.dart";
|
import "package:island/widgets/chat/message_item.dart";
|
||||||
import "package:island/widgets/content/cloud_files.dart";
|
import "package:island/widgets/content/cloud_files.dart";
|
||||||
import "package:island/widgets/post/compose_shared.dart";
|
|
||||||
import "package:island/widgets/response.dart";
|
import "package:island/widgets/response.dart";
|
||||||
import "package:material_symbols_icons/material_symbols_icons.dart";
|
import "package:material_symbols_icons/material_symbols_icons.dart";
|
||||||
import "package:styled_widget/styled_widget.dart";
|
import "package:styled_widget/styled_widget.dart";
|
||||||
@@ -348,10 +346,6 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
if (config == null) return;
|
if (config == null) return;
|
||||||
|
|
||||||
final baseUrl = ref.watch(serverUrlProvider);
|
|
||||||
final token = await getToken(ref.watch(tokenProvider));
|
|
||||||
if (token == null) throw ArgumentError('Token is null');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Use 'chat-upload' as temporary key for progress
|
// Use 'chat-upload' as temporary key for progress
|
||||||
attachmentProgress.value = {
|
attachmentProgress.value = {
|
||||||
@@ -360,15 +354,10 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
};
|
};
|
||||||
|
|
||||||
final cloudFile =
|
final cloudFile =
|
||||||
await putFileToCloud(
|
await FileUploader.createCloudFile(
|
||||||
|
client: ref.read(apiClientProvider),
|
||||||
fileData: attachment,
|
fileData: attachment,
|
||||||
atk: token,
|
|
||||||
baseUrl: baseUrl,
|
|
||||||
poolId: config.poolId,
|
poolId: config.poolId,
|
||||||
filename: attachment.data.name ?? 'Chat media',
|
|
||||||
mimetype:
|
|
||||||
attachment.data.mimeType ??
|
|
||||||
ComposeLogic.getMimeTypeFromFileType(attachment.type),
|
|
||||||
mode:
|
mode:
|
||||||
attachment.type == UniversalFileType.file
|
attachment.type == UniversalFileType.file
|
||||||
? FileUploadMode.generic
|
? FileUploadMode.generic
|
||||||
|
@@ -10,11 +10,11 @@ import 'package:image_picker/image_picker.dart';
|
|||||||
import 'package:island/models/file.dart';
|
import 'package:island/models/file.dart';
|
||||||
import 'package:island/models/publisher.dart';
|
import 'package:island/models/publisher.dart';
|
||||||
import 'package:island/models/realm.dart';
|
import 'package:island/models/realm.dart';
|
||||||
import 'package:island/pods/config.dart';
|
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/pods/userinfo.dart';
|
import 'package:island/pods/userinfo.dart';
|
||||||
import 'package:island/screens/realm/realms.dart';
|
import 'package:island/screens/realm/realms.dart';
|
||||||
import 'package:island/services/file.dart';
|
import 'package:island/services/file.dart';
|
||||||
|
import 'package:island/services/file_uploader.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
@@ -94,19 +94,13 @@ class EditPublisherScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
submitting.value = true;
|
submitting.value = true;
|
||||||
try {
|
try {
|
||||||
final baseUrl = ref.watch(serverUrlProvider);
|
|
||||||
final token = await getToken(ref.watch(tokenProvider));
|
|
||||||
if (token == null) throw ArgumentError('Token is null');
|
|
||||||
final cloudFile =
|
final cloudFile =
|
||||||
await putFileToCloud(
|
await FileUploader.createCloudFile(
|
||||||
fileData: UniversalFile(
|
fileData: UniversalFile(
|
||||||
data: result,
|
data: result,
|
||||||
type: UniversalFileType.image,
|
type: UniversalFileType.image,
|
||||||
),
|
),
|
||||||
atk: token,
|
client: ref.read(apiClientProvider),
|
||||||
baseUrl: baseUrl,
|
|
||||||
filename: result.name,
|
|
||||||
mimetype: result.mimeType ?? 'image/jpeg',
|
|
||||||
).future;
|
).future;
|
||||||
if (cloudFile == null) {
|
if (cloudFile == null) {
|
||||||
throw ArgumentError('Failed to upload the file...');
|
throw ArgumentError('Failed to upload the file...');
|
||||||
|
@@ -6,10 +6,10 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:island/models/custom_app.dart';
|
import 'package:island/models/custom_app.dart';
|
||||||
import 'package:island/models/file.dart';
|
import 'package:island/models/file.dart';
|
||||||
import 'package:island/pods/config.dart';
|
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/screens/developers/apps.dart';
|
import 'package:island/screens/developers/apps.dart';
|
||||||
import 'package:island/services/file.dart';
|
import 'package:island/services/file.dart';
|
||||||
|
import 'package:island/services/file_uploader.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
@@ -137,19 +137,13 @@ class EditAppScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
submitting.value = true;
|
submitting.value = true;
|
||||||
try {
|
try {
|
||||||
final baseUrl = ref.watch(serverUrlProvider);
|
|
||||||
final token = await getToken(ref.watch(tokenProvider));
|
|
||||||
if (token == null) throw ArgumentError('Token is null');
|
|
||||||
final cloudFile =
|
final cloudFile =
|
||||||
await putFileToCloud(
|
await FileUploader.createCloudFile(
|
||||||
|
client: ref.read(apiClientProvider),
|
||||||
fileData: UniversalFile(
|
fileData: UniversalFile(
|
||||||
data: result,
|
data: result,
|
||||||
type: UniversalFileType.image,
|
type: UniversalFileType.image,
|
||||||
),
|
),
|
||||||
atk: token,
|
|
||||||
baseUrl: baseUrl,
|
|
||||||
filename: result.name,
|
|
||||||
mimetype: result.mimeType ?? 'image/jpeg',
|
|
||||||
).future;
|
).future;
|
||||||
if (cloudFile == null) {
|
if (cloudFile == null) {
|
||||||
throw ArgumentError('Failed to upload the file...');
|
throw ArgumentError('Failed to upload the file...');
|
||||||
|
@@ -7,9 +7,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:island/models/bot.dart';
|
import 'package:island/models/bot.dart';
|
||||||
import 'package:island/models/file.dart';
|
import 'package:island/models/file.dart';
|
||||||
import 'package:island/pods/config.dart';
|
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/services/file.dart';
|
import 'package:island/services/file.dart';
|
||||||
|
import 'package:island/services/file_uploader.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
@@ -123,19 +123,13 @@ class EditBotScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
submitting.value = true;
|
submitting.value = true;
|
||||||
try {
|
try {
|
||||||
final baseUrl = ref.watch(serverUrlProvider);
|
|
||||||
final token = await getToken(ref.watch(tokenProvider));
|
|
||||||
if (token == null) throw ArgumentError('Token is null');
|
|
||||||
final cloudFile =
|
final cloudFile =
|
||||||
await putFileToCloud(
|
await FileUploader.createCloudFile(
|
||||||
fileData: UniversalFile(
|
fileData: UniversalFile(
|
||||||
data: result,
|
data: result,
|
||||||
type: UniversalFileType.image,
|
type: UniversalFileType.image,
|
||||||
),
|
),
|
||||||
atk: token,
|
client: ref.read(apiClientProvider),
|
||||||
baseUrl: baseUrl,
|
|
||||||
filename: result.name,
|
|
||||||
mimetype: result.mimeType ?? 'image/jpeg',
|
|
||||||
).future;
|
).future;
|
||||||
if (cloudFile == null) {
|
if (cloudFile == null) {
|
||||||
throw ArgumentError('Failed to upload the file...');
|
throw ArgumentError('Failed to upload the file...');
|
||||||
|
@@ -9,9 +9,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:island/models/file.dart';
|
import 'package:island/models/file.dart';
|
||||||
import 'package:island/models/realm.dart';
|
import 'package:island/models/realm.dart';
|
||||||
import 'package:island/pods/config.dart';
|
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/services/file.dart';
|
import 'package:island/services/file.dart';
|
||||||
|
import 'package:island/services/file_uploader.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
@@ -204,19 +204,13 @@ class EditRealmScreen extends HookConsumerWidget {
|
|||||||
showLoadingModal(context);
|
showLoadingModal(context);
|
||||||
submitting.value = true;
|
submitting.value = true;
|
||||||
try {
|
try {
|
||||||
final baseUrl = ref.watch(serverUrlProvider);
|
|
||||||
final token = await getToken(ref.watch(tokenProvider));
|
|
||||||
if (token == null) throw ArgumentError('Access token is null');
|
|
||||||
final cloudFile =
|
final cloudFile =
|
||||||
await putFileToCloud(
|
await FileUploader.createCloudFile(
|
||||||
|
client: ref.read(apiClientProvider),
|
||||||
fileData: UniversalFile(
|
fileData: UniversalFile(
|
||||||
data: result,
|
data: result,
|
||||||
type: UniversalFileType.image,
|
type: UniversalFileType.image,
|
||||||
),
|
),
|
||||||
atk: token,
|
|
||||||
baseUrl: baseUrl,
|
|
||||||
filename: result.name,
|
|
||||||
mimetype: result.mimeType ?? 'image/jpeg',
|
|
||||||
).future;
|
).future;
|
||||||
if (cloudFile == null) {
|
if (cloudFile == null) {
|
||||||
throw ArgumentError('Failed to upload the file...');
|
throw ArgumentError('Failed to upload the file...');
|
||||||
|
@@ -7,7 +7,7 @@ part of 'compose_storage_db.dart';
|
|||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$composeStorageNotifierHash() =>
|
String _$composeStorageNotifierHash() =>
|
||||||
r'8baf17aa06b6f69641c20645ba8a3dfe01c97f8c';
|
r'e78dbfd8dbaf728970985aaa2ac4df3575ddfcdf';
|
||||||
|
|
||||||
/// See also [ComposeStorageNotifier].
|
/// See also [ComposeStorageNotifier].
|
||||||
@ProviderFor(ComposeStorageNotifier)
|
@ProviderFor(ComposeStorageNotifier)
|
||||||
|
@@ -2,14 +2,7 @@ import 'dart:async';
|
|||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
import 'package:croppy/croppy.dart';
|
import 'package:croppy/croppy.dart';
|
||||||
import 'package:cross_file/cross_file.dart';
|
import 'package:cross_file/cross_file.dart';
|
||||||
import 'package:dio/dio.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:island/models/file.dart';
|
|
||||||
import 'package:island/services/file_uploader.dart';
|
|
||||||
import 'package:native_exif/native_exif.dart';
|
|
||||||
|
|
||||||
enum FileUploadMode { generic, mediaSafe }
|
|
||||||
|
|
||||||
Future<XFile?> cropImage(
|
Future<XFile?> cropImage(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
@@ -17,9 +10,12 @@ Future<XFile?> cropImage(
|
|||||||
List<CropAspectRatio?>? allowedAspectRatios,
|
List<CropAspectRatio?>? allowedAspectRatios,
|
||||||
bool replacePath = true,
|
bool replacePath = true,
|
||||||
}) async {
|
}) async {
|
||||||
|
if (!context.mounted) return null;
|
||||||
|
final imageBytes = await image.readAsBytes();
|
||||||
|
if (!context.mounted) return null;
|
||||||
final result = await showMaterialImageCropper(
|
final result = await showMaterialImageCropper(
|
||||||
context,
|
context,
|
||||||
imageProvider: MemoryImage(await image.readAsBytes()),
|
imageProvider: MemoryImage(imageBytes),
|
||||||
showLoadingIndicatorOnSubmit: true,
|
showLoadingIndicatorOnSubmit: true,
|
||||||
allowedAspectRatios: allowedAspectRatios,
|
allowedAspectRatios: allowedAspectRatios,
|
||||||
);
|
);
|
||||||
@@ -38,170 +34,3 @@ Future<XFile?> cropImage(
|
|||||||
mimeType: image.mimeType,
|
mimeType: image.mimeType,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Completer<SnCloudFile?> putFileToCloud({
|
|
||||||
required UniversalFile fileData,
|
|
||||||
required String atk,
|
|
||||||
required String baseUrl,
|
|
||||||
String? poolId,
|
|
||||||
String? filename,
|
|
||||||
String? mimetype,
|
|
||||||
FileUploadMode? mode,
|
|
||||||
Function(double progress, Duration estimate)? onProgress,
|
|
||||||
}) {
|
|
||||||
final completer = Completer<SnCloudFile?>();
|
|
||||||
|
|
||||||
final effectiveMode =
|
|
||||||
mode ??
|
|
||||||
(fileData.type == UniversalFileType.file
|
|
||||||
? FileUploadMode.generic
|
|
||||||
: FileUploadMode.mediaSafe);
|
|
||||||
|
|
||||||
if (effectiveMode == FileUploadMode.mediaSafe &&
|
|
||||||
fileData.isOnDevice &&
|
|
||||||
fileData.type == UniversalFileType.image) {
|
|
||||||
final data = fileData.data;
|
|
||||||
if (data is XFile &&
|
|
||||||
!kIsWeb &&
|
|
||||||
(defaultTargetPlatform == TargetPlatform.iOS ||
|
|
||||||
defaultTargetPlatform == TargetPlatform.android)) {
|
|
||||||
Exif.fromPath(data.path)
|
|
||||||
.then((exif) async {
|
|
||||||
final gpsAttributes = {
|
|
||||||
'GPSLatitude': '',
|
|
||||||
'GPSLatitudeRef': '',
|
|
||||||
'GPSLongitude': '',
|
|
||||||
'GPSLongitudeRef': '',
|
|
||||||
'GPSAltitude': '',
|
|
||||||
'GPSAltitudeRef': '',
|
|
||||||
'GPSTimeStamp': '',
|
|
||||||
'GPSProcessingMethod': '',
|
|
||||||
'GPSDateStamp': '',
|
|
||||||
};
|
|
||||||
await exif.writeAttributes(gpsAttributes);
|
|
||||||
})
|
|
||||||
.then(
|
|
||||||
(_) => _processUpload(
|
|
||||||
fileData,
|
|
||||||
atk,
|
|
||||||
baseUrl,
|
|
||||||
poolId,
|
|
||||||
filename,
|
|
||||||
mimetype,
|
|
||||||
onProgress,
|
|
||||||
completer,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.catchError((e) {
|
|
||||||
debugPrint('Error removing GPS EXIF data: $e');
|
|
||||||
return _processUpload(
|
|
||||||
fileData,
|
|
||||||
atk,
|
|
||||||
baseUrl,
|
|
||||||
poolId,
|
|
||||||
filename,
|
|
||||||
mimetype,
|
|
||||||
onProgress,
|
|
||||||
completer,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return completer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_processUpload(
|
|
||||||
fileData,
|
|
||||||
atk,
|
|
||||||
baseUrl,
|
|
||||||
poolId,
|
|
||||||
filename,
|
|
||||||
mimetype,
|
|
||||||
onProgress,
|
|
||||||
completer,
|
|
||||||
);
|
|
||||||
return completer;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper method to process the upload after any EXIF processing
|
|
||||||
Completer<SnCloudFile?> _processUpload(
|
|
||||||
UniversalFile fileData,
|
|
||||||
String atk,
|
|
||||||
String baseUrl,
|
|
||||||
String? poolId,
|
|
||||||
String? filename,
|
|
||||||
String? mimetype,
|
|
||||||
Function(double progress, Duration estimate)? onProgress,
|
|
||||||
Completer<SnCloudFile?> completer,
|
|
||||||
) {
|
|
||||||
late XFile file;
|
|
||||||
String actualFilename = filename ?? 'randomly_file';
|
|
||||||
String actualMimetype = mimetype ?? '';
|
|
||||||
Uint8List? byteData;
|
|
||||||
|
|
||||||
// Handle the data based on what's in the UniversalFile
|
|
||||||
final data = fileData.data;
|
|
||||||
|
|
||||||
if (data is XFile) {
|
|
||||||
file = data;
|
|
||||||
actualFilename = filename ?? data.name;
|
|
||||||
actualMimetype = mimetype ?? data.mimeType ?? '';
|
|
||||||
} else if (data is List<int> || data is Uint8List) {
|
|
||||||
byteData = data is List<int> ? Uint8List.fromList(data) : data;
|
|
||||||
actualFilename = filename ?? 'uploaded_file';
|
|
||||||
actualMimetype = mimetype ?? 'application/octet-stream';
|
|
||||||
if (mimetype == null) {
|
|
||||||
completer.completeError(
|
|
||||||
ArgumentError('Mimetype is required when providing raw bytes.'),
|
|
||||||
);
|
|
||||||
return completer;
|
|
||||||
}
|
|
||||||
file = XFile.fromData(byteData!, mimeType: actualMimetype);
|
|
||||||
} else if (data is SnCloudFile) {
|
|
||||||
// If the file is already on the cloud, just return it
|
|
||||||
completer.complete(data);
|
|
||||||
return completer;
|
|
||||||
} else {
|
|
||||||
completer.completeError(
|
|
||||||
ArgumentError(
|
|
||||||
'Invalid fileData type. Expected data to be XFile, List<int>, Uint8List, or SnCloudFile.',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return completer;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create Dio instance
|
|
||||||
final dio = Dio(
|
|
||||||
BaseOptions(
|
|
||||||
baseUrl: baseUrl,
|
|
||||||
headers: {
|
|
||||||
'Authorization': 'AtField $atk',
|
|
||||||
'Accept': 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final uploader = FileUploader(dio);
|
|
||||||
|
|
||||||
// Call progress start
|
|
||||||
onProgress?.call(0.0, Duration.zero);
|
|
||||||
uploader
|
|
||||||
.uploadFile(
|
|
||||||
file: file,
|
|
||||||
fileName: actualFilename,
|
|
||||||
contentType: actualMimetype,
|
|
||||||
poolId: poolId,
|
|
||||||
)
|
|
||||||
.then((result) {
|
|
||||||
// Call progress end
|
|
||||||
onProgress?.call(1.0, Duration.zero);
|
|
||||||
completer.complete(result);
|
|
||||||
})
|
|
||||||
.catchError((e) {
|
|
||||||
completer.completeError(e);
|
|
||||||
throw e;
|
|
||||||
});
|
|
||||||
|
|
||||||
return completer;
|
|
||||||
}
|
|
||||||
|
@@ -1,17 +1,19 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:cross_file/cross_file.dart';
|
import 'package:cross_file/cross_file.dart';
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:island/models/file.dart';
|
import 'package:island/models/file.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:mime/mime.dart';
|
||||||
|
import 'package:native_exif/native_exif.dart';
|
||||||
|
|
||||||
class FileUploader {
|
class FileUploader {
|
||||||
final Dio _dio;
|
final Dio _client;
|
||||||
|
|
||||||
FileUploader(this._dio);
|
FileUploader(this._client);
|
||||||
|
|
||||||
/// Calculates the MD5 hash of a file.
|
/// Calculates the MD5 hash of a file.
|
||||||
Future<String> _calculateFileHash(XFile file) async {
|
Future<String> _calculateFileHash(XFile file) async {
|
||||||
@@ -34,7 +36,7 @@ class FileUploader {
|
|||||||
final hash = await _calculateFileHash(file);
|
final hash = await _calculateFileHash(file);
|
||||||
final fileSize = await file.length();
|
final fileSize = await file.length();
|
||||||
|
|
||||||
final response = await _dio.post(
|
final response = await _client.post(
|
||||||
'/drive/files/upload/create',
|
'/drive/files/upload/create',
|
||||||
data: {
|
data: {
|
||||||
'hash': hash,
|
'hash': hash,
|
||||||
@@ -65,7 +67,7 @@ class FileUploader {
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
await _dio.post(
|
await _client.post(
|
||||||
'/drive/files/upload/chunk/$taskId/$chunkIndex',
|
'/drive/files/upload/chunk/$taskId/$chunkIndex',
|
||||||
data: formData,
|
data: formData,
|
||||||
);
|
);
|
||||||
@@ -73,7 +75,7 @@ class FileUploader {
|
|||||||
|
|
||||||
/// Completes the upload and returns the CloudFile object.
|
/// Completes the upload and returns the CloudFile object.
|
||||||
Future<SnCloudFile> completeUpload(String taskId) async {
|
Future<SnCloudFile> completeUpload(String taskId) async {
|
||||||
final response = await _dio.post('/drive/files/upload/complete/$taskId');
|
final response = await _client.post('/drive/files/upload/complete/$taskId');
|
||||||
|
|
||||||
return SnCloudFile.fromJson(response.data);
|
return SnCloudFile.fromJson(response.data);
|
||||||
}
|
}
|
||||||
@@ -146,8 +148,155 @@ class FileUploader {
|
|||||||
// Step 3: Complete upload
|
// Step 3: Complete upload
|
||||||
return await completeUpload(taskId);
|
return await completeUpload(taskId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Completer<SnCloudFile?> createCloudFile({
|
||||||
|
required UniversalFile fileData,
|
||||||
|
required Dio client,
|
||||||
|
String? poolId,
|
||||||
|
FileUploadMode? mode,
|
||||||
|
Function(double progress, Duration estimate)? onProgress,
|
||||||
|
}) {
|
||||||
|
final completer = Completer<SnCloudFile?>();
|
||||||
|
|
||||||
|
final effectiveMode =
|
||||||
|
mode ??
|
||||||
|
(fileData.type == UniversalFileType.file
|
||||||
|
? FileUploadMode.generic
|
||||||
|
: FileUploadMode.mediaSafe);
|
||||||
|
|
||||||
|
if (effectiveMode == FileUploadMode.mediaSafe &&
|
||||||
|
fileData.isOnDevice &&
|
||||||
|
fileData.type == UniversalFileType.image) {
|
||||||
|
final data = fileData.data;
|
||||||
|
if (data is XFile &&
|
||||||
|
!kIsWeb &&
|
||||||
|
(defaultTargetPlatform == TargetPlatform.iOS ||
|
||||||
|
defaultTargetPlatform == TargetPlatform.android)) {
|
||||||
|
Exif.fromPath(data.path)
|
||||||
|
.then((exif) async {
|
||||||
|
final gpsAttributes = {
|
||||||
|
'GPSLatitude': '',
|
||||||
|
'GPSLatitudeRef': '',
|
||||||
|
'GPSLongitude': '',
|
||||||
|
'GPSLongitudeRef': '',
|
||||||
|
'GPSAltitude': '',
|
||||||
|
'GPSAltitudeRef': '',
|
||||||
|
'GPSTimeStamp': '',
|
||||||
|
'GPSProcessingMethod': '',
|
||||||
|
'GPSDateStamp': '',
|
||||||
|
};
|
||||||
|
await exif.writeAttributes(gpsAttributes);
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
(_) => _processUpload(
|
||||||
|
fileData,
|
||||||
|
client,
|
||||||
|
poolId,
|
||||||
|
onProgress,
|
||||||
|
completer,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.catchError((e) {
|
||||||
|
debugPrint('Error removing GPS EXIF data: $e');
|
||||||
|
return _processUpload(
|
||||||
|
fileData,
|
||||||
|
client,
|
||||||
|
poolId,
|
||||||
|
onProgress,
|
||||||
|
completer,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return completer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_processUpload(fileData, client, poolId, onProgress, completer);
|
||||||
|
return completer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method to process the upload
|
||||||
|
static Completer<SnCloudFile?> _processUpload(
|
||||||
|
UniversalFile fileData,
|
||||||
|
Dio client,
|
||||||
|
String? poolId,
|
||||||
|
Function(double progress, Duration estimate)? onProgress,
|
||||||
|
Completer<SnCloudFile?> completer,
|
||||||
|
) {
|
||||||
|
String actualMimetype = getMimeType(fileData);
|
||||||
|
late XFile file;
|
||||||
|
String actualFilename = fileData.displayName ?? 'randomly_file';
|
||||||
|
Uint8List? byteData;
|
||||||
|
|
||||||
|
// Handle the data based on what's in the UniversalFile
|
||||||
|
final data = fileData.data;
|
||||||
|
|
||||||
|
if (data is XFile) {
|
||||||
|
file = data;
|
||||||
|
actualFilename = fileData.displayName ?? data.name;
|
||||||
|
} else if (data is List<int> || data is Uint8List) {
|
||||||
|
byteData = data is List<int> ? Uint8List.fromList(data) : data;
|
||||||
|
actualFilename = fileData.displayName ?? 'uploaded_file';
|
||||||
|
file = XFile.fromData(byteData!, mimeType: actualMimetype);
|
||||||
|
} else if (data is SnCloudFile) {
|
||||||
|
// If the file is already on the cloud, just return it
|
||||||
|
completer.complete(data);
|
||||||
|
return completer;
|
||||||
|
} else {
|
||||||
|
completer.completeError(
|
||||||
|
ArgumentError(
|
||||||
|
'Invalid fileData type. Expected data to be XFile, List<int>, Uint8List, or SnCloudFile.',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return completer;
|
||||||
|
}
|
||||||
|
|
||||||
|
final uploader = FileUploader(client);
|
||||||
|
|
||||||
|
// Call progress start
|
||||||
|
onProgress?.call(0.0, Duration.zero);
|
||||||
|
uploader
|
||||||
|
.uploadFile(
|
||||||
|
file: file,
|
||||||
|
fileName: actualFilename,
|
||||||
|
contentType: actualMimetype,
|
||||||
|
poolId: poolId,
|
||||||
|
)
|
||||||
|
.then((result) {
|
||||||
|
// Call progress end
|
||||||
|
onProgress?.call(1.0, Duration.zero);
|
||||||
|
completer.complete(result);
|
||||||
|
})
|
||||||
|
.catchError((e) {
|
||||||
|
completer.completeError(e);
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
|
|
||||||
|
return completer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the MIME type of a UniversalFile.
|
||||||
|
static String getMimeType(UniversalFile file) {
|
||||||
|
final data = file.data;
|
||||||
|
if (data is XFile) {
|
||||||
|
final mime = data.mimeType;
|
||||||
|
if (mime != null && mime.isNotEmpty) return mime;
|
||||||
|
final filename = file.displayName ?? data.name;
|
||||||
|
final detected = lookupMimeType(filename);
|
||||||
|
if (detected != null) return detected;
|
||||||
|
throw Exception('Cannot detect mime type for file: $filename');
|
||||||
|
} else if (data is List<int> || data is Uint8List) {
|
||||||
|
return 'application/octet-stream';
|
||||||
|
} else if (data is SnCloudFile) {
|
||||||
|
return data.mimeType ?? 'application/octet-stream';
|
||||||
|
} else {
|
||||||
|
throw ArgumentError('Invalid file data type');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum FileUploadMode { generic, mediaSafe }
|
||||||
|
|
||||||
// Riverpod provider for the FileUploader service
|
// Riverpod provider for the FileUploader service
|
||||||
final fileUploaderProvider = Provider<FileUploader>((ref) {
|
final fileUploaderProvider = Provider<FileUploader>((ref) {
|
||||||
final dio = ref.watch(apiClientProvider);
|
final dio = ref.watch(apiClientProvider);
|
||||||
|
@@ -10,6 +10,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:island/models/file.dart';
|
import 'package:island/models/file.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/services/file.dart';
|
import 'package:island/services/file.dart';
|
||||||
|
import 'package:island/services/file_uploader.dart';
|
||||||
import 'package:island/utils/format.dart';
|
import 'package:island/utils/format.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
@@ -107,13 +108,23 @@ class AttachmentPreview extends HookConsumerWidget {
|
|||||||
static final GlobalKey<SensitiveMarksSelectorState> _sensitiveSelectorKey =
|
static final GlobalKey<SensitiveMarksSelectorState> _sensitiveSelectorKey =
|
||||||
GlobalKey<SensitiveMarksSelectorState>();
|
GlobalKey<SensitiveMarksSelectorState>();
|
||||||
|
|
||||||
Future<void> _showRenameDialog(BuildContext context, WidgetRef ref) async {
|
String _getDisplayName() {
|
||||||
final nameController = TextEditingController(text: item.data.name);
|
return item.displayName ??
|
||||||
|
(item.data is XFile
|
||||||
|
? (item.data as XFile).name
|
||||||
|
: item.isOnCloud
|
||||||
|
? item.data.name
|
||||||
|
: '');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _showRenameSheet(BuildContext context, WidgetRef ref) async {
|
||||||
|
final nameController = TextEditingController(text: _getDisplayName());
|
||||||
String? errorMessage;
|
String? errorMessage;
|
||||||
|
|
||||||
await showModalBottomSheet(
|
await showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
|
useRootNavigator: true,
|
||||||
builder:
|
builder:
|
||||||
(context) => SheetScaffold(
|
(context) => SheetScaffold(
|
||||||
heightFactor: 0.6,
|
heightFactor: 0.6,
|
||||||
@@ -152,22 +163,32 @@ class AttachmentPreview extends HookConsumerWidget {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if (item.isOnCloud) {
|
||||||
showLoadingModal(context);
|
try {
|
||||||
final apiClient = ref.watch(apiClientProvider);
|
showLoadingModal(context);
|
||||||
await apiClient.patch(
|
final apiClient = ref.watch(apiClientProvider);
|
||||||
'/drive/files/${item.data.id}/name',
|
await apiClient.patch(
|
||||||
data: jsonEncode(newName),
|
'/drive/files/${item.data.id}/name',
|
||||||
);
|
data: jsonEncode(newName),
|
||||||
final newData = item.data;
|
);
|
||||||
newData.name = newName;
|
final newData = item.data;
|
||||||
final updatedFile = item.copyWith(data: newData);
|
newData.name = newName;
|
||||||
onUpdate?.call(item.copyWith(data: updatedFile));
|
onUpdate?.call(
|
||||||
|
item.copyWith(
|
||||||
|
data: newData,
|
||||||
|
displayName: newName,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (context.mounted) Navigator.pop(context);
|
||||||
|
} catch (err) {
|
||||||
|
showErrorAlert(err);
|
||||||
|
} finally {
|
||||||
|
if (context.mounted) hideLoadingModal(context);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Local file rename
|
||||||
|
onUpdate?.call(item.copyWith(displayName: newName));
|
||||||
if (context.mounted) Navigator.pop(context);
|
if (context.mounted) Navigator.pop(context);
|
||||||
} catch (err) {
|
|
||||||
showErrorAlert(err);
|
|
||||||
} finally {
|
|
||||||
if (context.mounted) hideLoadingModal(context);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Text('rename'.tr()),
|
child: Text('rename'.tr()),
|
||||||
@@ -292,6 +313,8 @@ class AttachmentPreview extends HookConsumerWidget {
|
|||||||
_ => Symbols.insert_drive_file,
|
_ => Symbols.insert_drive_file,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
final mimeType = FileUploader.getMimeType(item);
|
||||||
|
|
||||||
if (item.isOnCloud) {
|
if (item.isOnCloud) {
|
||||||
return CloudFileWidget(item: item.data);
|
return CloudFileWidget(item: item.data);
|
||||||
} else if (item.data is XFile) {
|
} else if (item.data is XFile) {
|
||||||
@@ -321,7 +344,12 @@ class AttachmentPreview extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
Icon(fallbackIcon),
|
Icon(fallbackIcon),
|
||||||
const Gap(6),
|
const Gap(6),
|
||||||
Text(file.name, textAlign: TextAlign.center),
|
Text(
|
||||||
|
_getDisplayName(),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
Text(mimeType, style: TextStyle(fontSize: 10)),
|
||||||
|
const Gap(1),
|
||||||
FutureBuilder(
|
FutureBuilder(
|
||||||
future: file.length(),
|
future: file.length(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
@@ -347,6 +375,8 @@ class AttachmentPreview extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
Icon(fallbackIcon),
|
Icon(fallbackIcon),
|
||||||
const Gap(6),
|
const Gap(6),
|
||||||
|
Text(mimeType, style: TextStyle(fontSize: 10)),
|
||||||
|
const Gap(1),
|
||||||
Text(
|
Text(
|
||||||
formatFileSize(item.data.length),
|
formatFileSize(item.data.length),
|
||||||
).fontSize(11),
|
).fontSize(11),
|
||||||
@@ -542,12 +572,20 @@ class AttachmentPreview extends HookConsumerWidget {
|
|||||||
onUpdate?.call(item.copyWith(data: result));
|
onUpdate?.call(item.copyWith(data: result));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
if (item.isOnDevice)
|
||||||
|
MenuAction(
|
||||||
|
title: 'rename'.tr(),
|
||||||
|
image: MenuImage.icon(Symbols.edit),
|
||||||
|
callback: () async {
|
||||||
|
await _showRenameSheet(context, ref);
|
||||||
|
},
|
||||||
|
),
|
||||||
if (item.isOnCloud)
|
if (item.isOnCloud)
|
||||||
MenuAction(
|
MenuAction(
|
||||||
title: 'rename'.tr(),
|
title: 'rename'.tr(),
|
||||||
image: MenuImage.icon(Symbols.edit),
|
image: MenuImage.icon(Symbols.edit),
|
||||||
callback: () async {
|
callback: () async {
|
||||||
await _showRenameDialog(context, ref);
|
await _showRenameSheet(context, ref);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (item.isOnCloud)
|
if (item.isOnCloud)
|
||||||
|
@@ -6,9 +6,8 @@ import 'package:gap/gap.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:island/models/file.dart';
|
import 'package:island/models/file.dart';
|
||||||
import 'package:island/pods/config.dart';
|
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/services/file.dart';
|
import 'package:island/services/file_uploader.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/content/attachment_preview.dart';
|
import 'package:island/widgets/content/attachment_preview.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
@@ -42,10 +41,6 @@ class CloudFilePicker extends HookConsumerWidget {
|
|||||||
Future<void> startUpload() async {
|
Future<void> startUpload() async {
|
||||||
if (files.value.isEmpty) return;
|
if (files.value.isEmpty) return;
|
||||||
|
|
||||||
final baseUrl = ref.read(serverUrlProvider);
|
|
||||||
final token = await getToken(ref.watch(tokenProvider));
|
|
||||||
if (token == null) throw Exception("Unauthorized");
|
|
||||||
|
|
||||||
List<SnCloudFile> result = List.empty(growable: true);
|
List<SnCloudFile> result = List.empty(growable: true);
|
||||||
|
|
||||||
uploadProgress.value = 0;
|
uploadProgress.value = 0;
|
||||||
@@ -55,19 +50,9 @@ class CloudFilePicker extends HookConsumerWidget {
|
|||||||
uploadPosition.value = idx;
|
uploadPosition.value = idx;
|
||||||
final file = files.value[idx];
|
final file = files.value[idx];
|
||||||
final cloudFile =
|
final cloudFile =
|
||||||
await putFileToCloud(
|
await FileUploader.createCloudFile(
|
||||||
fileData: file,
|
fileData: file,
|
||||||
atk: token,
|
client: ref.read(apiClientProvider),
|
||||||
baseUrl: baseUrl,
|
|
||||||
filename: file.data.name ?? 'Post media',
|
|
||||||
mimetype:
|
|
||||||
file.data.mimeType ??
|
|
||||||
switch (file.type) {
|
|
||||||
UniversalFileType.image => 'image/unknown',
|
|
||||||
UniversalFileType.video => 'video/unknown',
|
|
||||||
UniversalFileType.audio => 'audio/unknown',
|
|
||||||
UniversalFileType.file => 'application/octet-stream',
|
|
||||||
},
|
|
||||||
onProgress: (progress, _) {
|
onProgress: (progress, _) {
|
||||||
uploadProgress.value = progress;
|
uploadProgress.value = progress;
|
||||||
},
|
},
|
||||||
|
@@ -32,7 +32,7 @@ class PostComposeCard extends HookConsumerWidget {
|
|||||||
final Function(SnPost)? onSubmit;
|
final Function(SnPost)? onSubmit;
|
||||||
final Function(ComposeState)? onStateChanged;
|
final Function(ComposeState)? onStateChanged;
|
||||||
|
|
||||||
PostComposeCard({
|
const PostComposeCard({
|
||||||
super.key,
|
super.key,
|
||||||
this.originalPost,
|
this.originalPost,
|
||||||
this.initialState,
|
this.initialState,
|
||||||
|
@@ -14,9 +14,8 @@ import 'package:island/models/post.dart';
|
|||||||
import 'package:island/models/post_category.dart';
|
import 'package:island/models/post_category.dart';
|
||||||
import 'package:island/models/publisher.dart';
|
import 'package:island/models/publisher.dart';
|
||||||
import 'package:island/models/realm.dart';
|
import 'package:island/models/realm.dart';
|
||||||
import 'package:island/pods/config.dart';
|
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/services/file.dart';
|
import 'package:island/services/file_uploader.dart';
|
||||||
import 'package:island/services/compose_storage_db.dart';
|
import 'package:island/services/compose_storage_db.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/post/compose_link_attachments.dart';
|
import 'package:island/widgets/post/compose_link_attachments.dart';
|
||||||
@@ -177,25 +176,14 @@ class ComposeLogic {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Upload any local attachments first
|
// Upload any local attachments first
|
||||||
final baseUrl = ref.watch(serverUrlProvider);
|
|
||||||
final token = await getToken(ref.watch(tokenProvider));
|
|
||||||
if (token == null) throw ArgumentError('Token is null');
|
|
||||||
|
|
||||||
for (int i = 0; i < state.attachments.value.length; i++) {
|
for (int i = 0; i < state.attachments.value.length; i++) {
|
||||||
final attachment = state.attachments.value[i];
|
final attachment = state.attachments.value[i];
|
||||||
if (attachment.data is! SnCloudFile) {
|
if (attachment.data is! SnCloudFile) {
|
||||||
try {
|
try {
|
||||||
final cloudFile =
|
final cloudFile =
|
||||||
await putFileToCloud(
|
await FileUploader.createCloudFile(
|
||||||
|
client: ref.read(apiClientProvider),
|
||||||
fileData: attachment,
|
fileData: attachment,
|
||||||
atk: token,
|
|
||||||
baseUrl: baseUrl,
|
|
||||||
filename:
|
|
||||||
attachment.data.name ??
|
|
||||||
(state.postType == 1 ? 'Article media' : 'Post media'),
|
|
||||||
mimetype:
|
|
||||||
attachment.data.mimeType ??
|
|
||||||
ComposeLogic.getMimeTypeFromFileType(attachment.type),
|
|
||||||
).future;
|
).future;
|
||||||
if (cloudFile != null) {
|
if (cloudFile != null) {
|
||||||
// Update attachments list with cloud file
|
// Update attachments list with cloud file
|
||||||
@@ -509,15 +497,11 @@ class ComposeLogic {
|
|||||||
WidgetRef ref,
|
WidgetRef ref,
|
||||||
ComposeState state,
|
ComposeState state,
|
||||||
int index, {
|
int index, {
|
||||||
String? poolId, // For Unit Test
|
String? poolId,
|
||||||
}) async {
|
}) async {
|
||||||
final attachment = state.attachments.value[index];
|
final attachment = state.attachments.value[index];
|
||||||
if (attachment.isOnCloud) return;
|
if (attachment.isOnCloud) return;
|
||||||
|
|
||||||
final baseUrl = ref.watch(serverUrlProvider);
|
|
||||||
final token = await getToken(ref.watch(tokenProvider));
|
|
||||||
if (token == null) throw ArgumentError('Token is null');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
state.attachmentProgress.value = {
|
state.attachmentProgress.value = {
|
||||||
...state.attachmentProgress.value,
|
...state.attachmentProgress.value,
|
||||||
@@ -530,19 +514,10 @@ class ComposeLogic {
|
|||||||
final selectedPoolId = resolveDefaultPoolId(ref, pools);
|
final selectedPoolId = resolveDefaultPoolId(ref, pools);
|
||||||
|
|
||||||
cloudFile =
|
cloudFile =
|
||||||
await putFileToCloud(
|
await FileUploader.createCloudFile(
|
||||||
|
client: ref.read(apiClientProvider),
|
||||||
fileData: attachment,
|
fileData: attachment,
|
||||||
atk: token,
|
poolId: poolId ?? selectedPoolId,
|
||||||
baseUrl: baseUrl,
|
|
||||||
poolId: selectedPoolId,
|
|
||||||
filename:
|
|
||||||
attachment.data.name ??
|
|
||||||
(attachment.type == UniversalFileType.file
|
|
||||||
? 'General file'
|
|
||||||
: 'Post media'),
|
|
||||||
mimetype:
|
|
||||||
attachment.data.mimeType ??
|
|
||||||
getMimeTypeFromFileType(attachment.type),
|
|
||||||
mode:
|
mode:
|
||||||
attachment.type == UniversalFileType.file
|
attachment.type == UniversalFileType.file
|
||||||
? FileUploadMode.generic
|
? FileUploadMode.generic
|
||||||
@@ -563,7 +538,7 @@ class ComposeLogic {
|
|||||||
clone[index] = UniversalFile(data: cloudFile, type: attachment.type);
|
clone[index] = UniversalFile(data: cloudFile, type: attachment.type);
|
||||||
state.attachments.value = clone;
|
state.attachments.value = clone;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showErrorAlert(err.toString());
|
showErrorAlert(err);
|
||||||
} finally {
|
} finally {
|
||||||
state.attachmentProgress.value = {...state.attachmentProgress.value}
|
state.attachmentProgress.value = {...state.attachmentProgress.value}
|
||||||
..remove(index);
|
..remove(index);
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
|
import 'dart:io';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/services/file_uploader.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/content/sheet.dart';
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
@@ -10,11 +12,7 @@ import 'package:island/screens/posts/compose.dart';
|
|||||||
import 'package:island/models/file.dart';
|
import 'package:island/models/file.dart';
|
||||||
import 'package:island/pods/link_preview.dart';
|
import 'package:island/pods/link_preview.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/pods/config.dart';
|
|
||||||
import 'package:island/services/file.dart';
|
|
||||||
import 'package:mime/mime.dart';
|
import 'package:mime/mime.dart';
|
||||||
|
|
||||||
import 'dart:io';
|
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
import 'package:island/models/chat.dart';
|
import 'package:island/models/chat.dart';
|
||||||
import 'package:island/screens/chat/chat.dart';
|
import 'package:island/screens/chat/chat.dart';
|
||||||
@@ -192,7 +190,6 @@ class _ShareSheetState extends ConsumerState<ShareSheet> {
|
|||||||
setState(() => _isLoading = true);
|
setState(() => _isLoading = true);
|
||||||
try {
|
try {
|
||||||
final apiClient = ref.read(apiClientProvider);
|
final apiClient = ref.read(apiClientProvider);
|
||||||
final serverUrl = ref.read(serverUrlProvider);
|
|
||||||
|
|
||||||
String content = _messageController.text.trim();
|
String content = _messageController.text.trim();
|
||||||
List<String> attachmentIds = [];
|
List<String> attachmentIds = [];
|
||||||
@@ -216,11 +213,6 @@ class _ShareSheetState extends ConsumerState<ShareSheet> {
|
|||||||
case ShareContentType.file:
|
case ShareContentType.file:
|
||||||
// Upload files to cloud storage
|
// Upload files to cloud storage
|
||||||
if (widget.content.files?.isNotEmpty == true) {
|
if (widget.content.files?.isNotEmpty == true) {
|
||||||
final token = ref.watch(tokenProvider)?.token;
|
|
||||||
if (token == null) {
|
|
||||||
throw Exception('Authentication required');
|
|
||||||
}
|
|
||||||
|
|
||||||
final universalFiles =
|
final universalFiles =
|
||||||
widget.content.files!.map((file) {
|
widget.content.files!.map((file) {
|
||||||
UniversalFileType fileType;
|
UniversalFileType fileType;
|
||||||
@@ -247,19 +239,9 @@ class _ShareSheetState extends ConsumerState<ShareSheet> {
|
|||||||
for (var idx = 0; idx < universalFiles.length; idx++) {
|
for (var idx = 0; idx < universalFiles.length; idx++) {
|
||||||
final file = universalFiles[idx];
|
final file = universalFiles[idx];
|
||||||
final cloudFile =
|
final cloudFile =
|
||||||
await putFileToCloud(
|
await FileUploader.createCloudFile(
|
||||||
|
client: apiClient,
|
||||||
fileData: file,
|
fileData: file,
|
||||||
atk: token,
|
|
||||||
baseUrl: serverUrl,
|
|
||||||
filename: file.data.name ?? 'Shared file',
|
|
||||||
mimetype:
|
|
||||||
file.data.mimeType ??
|
|
||||||
switch (file.type) {
|
|
||||||
UniversalFileType.image => 'image/unknown',
|
|
||||||
UniversalFileType.video => 'video/unknown',
|
|
||||||
UniversalFileType.audio => 'audio/unknown',
|
|
||||||
UniversalFileType.file => 'application/octet-stream',
|
|
||||||
},
|
|
||||||
onProgress: (progress, _) {
|
onProgress: (progress, _) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
Reference in New Issue
Block a user