✨ Posting with attachment
This commit is contained in:
parent
4af64ae89b
commit
d4538a9ef6
@ -8,23 +8,22 @@ plugins {
|
|||||||
android {
|
android {
|
||||||
namespace = "dev.solsynth.solian"
|
namespace = "dev.solsynth.solian"
|
||||||
compileSdk = flutter.compileSdkVersion
|
compileSdk = flutter.compileSdkVersion
|
||||||
ndkVersion = flutter.ndkVersion
|
ndkVersion = "27.0.12077973"
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = JavaVersion.VERSION_11.toString()
|
jvmTarget = JavaVersion.VERSION_17.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
|
||||||
applicationId = "dev.solsynth.solian"
|
applicationId = "dev.solsynth.solian"
|
||||||
// You can update the following values to match your application needs.
|
// You can update the following values to match your application needs.
|
||||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||||
minSdk = flutter.minSdkVersion
|
minSdk = 26
|
||||||
targetSdk = flutter.targetSdkVersion
|
targetSdk = flutter.targetSdkVersion
|
||||||
versionCode = flutter.versionCode
|
versionCode = flutter.versionCode
|
||||||
versionName = flutter.versionName
|
versionName = flutter.versionName
|
||||||
|
@ -1,12 +1,29 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<uses-feature android:name="android.hardware.camera" />
|
||||||
|
<uses-feature android:name="android.hardware.camera.autofocus" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
|
android:maxSdkVersion="29" />
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:label="island"
|
android:label="Solian"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:usesCleartextTraffic="true">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleInstance"
|
||||||
android:taskAffinity=""
|
android:taskAffinity=""
|
||||||
android:theme="@style/LaunchTheme"
|
android:theme="@style/LaunchTheme"
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
|
@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:image_picker_android/image_picker_android.dart';
|
||||||
import 'package:island/pods/config.dart';
|
import 'package:island/pods/config.dart';
|
||||||
import 'package:island/pods/theme.dart';
|
import 'package:island/pods/theme.dart';
|
||||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||||
@ -12,6 +13,7 @@ import 'package:island/pods/userinfo.dart';
|
|||||||
import 'package:island/route.dart';
|
import 'package:island/route.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
@ -28,6 +30,14 @@ void main() async {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!kIsWeb && Platform.isAndroid) {
|
||||||
|
final ImagePickerPlatform imagePickerImplementation =
|
||||||
|
ImagePickerPlatform.instance;
|
||||||
|
if (imagePickerImplementation is ImagePickerAndroid) {
|
||||||
|
imagePickerImplementation.useAndroidPhotoPicker = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
ProviderScope(
|
ProviderScope(
|
||||||
overrides: [sharedPreferencesProvider.overrideWithValue(prefs)],
|
overrides: [sharedPreferencesProvider.overrideWithValue(prefs)],
|
||||||
|
@ -14,7 +14,7 @@ abstract class SnCloudFile with _$SnCloudFile {
|
|||||||
required String? mimeType,
|
required String? mimeType,
|
||||||
required String? hash,
|
required String? hash,
|
||||||
required int size,
|
required int size,
|
||||||
required DateTime uploadedAt,
|
required DateTime? uploadedAt,
|
||||||
required String? uploadedTo,
|
required String? uploadedTo,
|
||||||
required int usedCount,
|
required int usedCount,
|
||||||
required DateTime createdAt,
|
required DateTime createdAt,
|
||||||
|
@ -16,7 +16,7 @@ T _$identity<T>(T value) => value;
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$SnCloudFile {
|
mixin _$SnCloudFile {
|
||||||
|
|
||||||
String get id; String get name; String? get description; Map<String, dynamic>? get fileMeta; Map<String, dynamic>? get userMeta; String? get mimeType; String? get hash; int get size; DateTime get uploadedAt; String? get uploadedTo; int get usedCount; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
|
String get id; String get name; String? get description; Map<String, dynamic>? get fileMeta; Map<String, dynamic>? get userMeta; String? get mimeType; String? get hash; int get size; DateTime? get uploadedAt; String? get uploadedTo; int get usedCount; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
|
||||||
/// Create a copy of SnCloudFile
|
/// Create a copy of SnCloudFile
|
||||||
/// 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)
|
||||||
@ -49,7 +49,7 @@ abstract mixin class $SnCloudFileCopyWith<$Res> {
|
|||||||
factory $SnCloudFileCopyWith(SnCloudFile value, $Res Function(SnCloudFile) _then) = _$SnCloudFileCopyWithImpl;
|
factory $SnCloudFileCopyWith(SnCloudFile value, $Res Function(SnCloudFile) _then) = _$SnCloudFileCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String id, String name, String? description, Map<String, dynamic>? fileMeta, Map<String, dynamic>? userMeta, String? mimeType, String? hash, int size, DateTime uploadedAt, String? uploadedTo, int usedCount, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
String id, String name, String? description, Map<String, dynamic>? fileMeta, Map<String, dynamic>? userMeta, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, int usedCount, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ class _$SnCloudFileCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnCloudFile
|
/// Create a copy of SnCloudFile
|
||||||
/// 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? id = null,Object? name = null,Object? description = freezed,Object? fileMeta = freezed,Object? userMeta = freezed,Object? mimeType = freezed,Object? hash = freezed,Object? size = null,Object? uploadedAt = null,Object? uploadedTo = freezed,Object? usedCount = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? description = freezed,Object? fileMeta = freezed,Object? userMeta = freezed,Object? mimeType = freezed,Object? hash = freezed,Object? size = null,Object? uploadedAt = freezed,Object? uploadedTo = freezed,Object? usedCount = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||||
@ -76,8 +76,8 @@ as Map<String, dynamic>?,userMeta: freezed == userMeta ? _self.userMeta : userMe
|
|||||||
as Map<String, dynamic>?,mimeType: freezed == mimeType ? _self.mimeType : mimeType // ignore: cast_nullable_to_non_nullable
|
as Map<String, dynamic>?,mimeType: freezed == mimeType ? _self.mimeType : mimeType // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,hash: freezed == hash ? _self.hash : hash // ignore: cast_nullable_to_non_nullable
|
as String?,hash: freezed == hash ? _self.hash : hash // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,size: null == size ? _self.size : size // ignore: cast_nullable_to_non_nullable
|
as String?,size: null == size ? _self.size : size // ignore: cast_nullable_to_non_nullable
|
||||||
as int,uploadedAt: null == uploadedAt ? _self.uploadedAt : uploadedAt // ignore: cast_nullable_to_non_nullable
|
as int,uploadedAt: freezed == uploadedAt ? _self.uploadedAt : uploadedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,uploadedTo: freezed == uploadedTo ? _self.uploadedTo : uploadedTo // ignore: cast_nullable_to_non_nullable
|
as DateTime?,uploadedTo: freezed == uploadedTo ? _self.uploadedTo : uploadedTo // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,usedCount: null == usedCount ? _self.usedCount : usedCount // ignore: cast_nullable_to_non_nullable
|
as String?,usedCount: null == usedCount ? _self.usedCount : usedCount // ignore: cast_nullable_to_non_nullable
|
||||||
as int,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
as int,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
@ -120,7 +120,7 @@ class _SnCloudFile implements SnCloudFile {
|
|||||||
@override final String? mimeType;
|
@override final String? mimeType;
|
||||||
@override final String? hash;
|
@override final String? hash;
|
||||||
@override final int size;
|
@override final int size;
|
||||||
@override final DateTime uploadedAt;
|
@override final DateTime? uploadedAt;
|
||||||
@override final String? uploadedTo;
|
@override final String? uploadedTo;
|
||||||
@override final int usedCount;
|
@override final int usedCount;
|
||||||
@override final DateTime createdAt;
|
@override final DateTime createdAt;
|
||||||
@ -160,7 +160,7 @@ abstract mixin class _$SnCloudFileCopyWith<$Res> implements $SnCloudFileCopyWith
|
|||||||
factory _$SnCloudFileCopyWith(_SnCloudFile value, $Res Function(_SnCloudFile) _then) = __$SnCloudFileCopyWithImpl;
|
factory _$SnCloudFileCopyWith(_SnCloudFile value, $Res Function(_SnCloudFile) _then) = __$SnCloudFileCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String id, String name, String? description, Map<String, dynamic>? fileMeta, Map<String, dynamic>? userMeta, String? mimeType, String? hash, int size, DateTime uploadedAt, String? uploadedTo, int usedCount, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
String id, String name, String? description, Map<String, dynamic>? fileMeta, Map<String, dynamic>? userMeta, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, int usedCount, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -177,7 +177,7 @@ class __$SnCloudFileCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnCloudFile
|
/// Create a copy of SnCloudFile
|
||||||
/// 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? id = null,Object? name = null,Object? description = freezed,Object? fileMeta = freezed,Object? userMeta = freezed,Object? mimeType = freezed,Object? hash = freezed,Object? size = null,Object? uploadedAt = null,Object? uploadedTo = freezed,Object? usedCount = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? description = freezed,Object? fileMeta = freezed,Object? userMeta = freezed,Object? mimeType = freezed,Object? hash = freezed,Object? size = null,Object? uploadedAt = freezed,Object? uploadedTo = freezed,Object? usedCount = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||||
return _then(_SnCloudFile(
|
return _then(_SnCloudFile(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||||
@ -187,8 +187,8 @@ as Map<String, dynamic>?,userMeta: freezed == userMeta ? _self._userMeta : userM
|
|||||||
as Map<String, dynamic>?,mimeType: freezed == mimeType ? _self.mimeType : mimeType // ignore: cast_nullable_to_non_nullable
|
as Map<String, dynamic>?,mimeType: freezed == mimeType ? _self.mimeType : mimeType // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,hash: freezed == hash ? _self.hash : hash // ignore: cast_nullable_to_non_nullable
|
as String?,hash: freezed == hash ? _self.hash : hash // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,size: null == size ? _self.size : size // ignore: cast_nullable_to_non_nullable
|
as String?,size: null == size ? _self.size : size // ignore: cast_nullable_to_non_nullable
|
||||||
as int,uploadedAt: null == uploadedAt ? _self.uploadedAt : uploadedAt // ignore: cast_nullable_to_non_nullable
|
as int,uploadedAt: freezed == uploadedAt ? _self.uploadedAt : uploadedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,uploadedTo: freezed == uploadedTo ? _self.uploadedTo : uploadedTo // ignore: cast_nullable_to_non_nullable
|
as DateTime?,uploadedTo: freezed == uploadedTo ? _self.uploadedTo : uploadedTo // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,usedCount: null == usedCount ? _self.usedCount : usedCount // ignore: cast_nullable_to_non_nullable
|
as String?,usedCount: null == usedCount ? _self.usedCount : usedCount // ignore: cast_nullable_to_non_nullable
|
||||||
as int,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
as int,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
@ -15,7 +15,10 @@ _SnCloudFile _$SnCloudFileFromJson(Map<String, dynamic> json) => _SnCloudFile(
|
|||||||
mimeType: json['mime_type'] as String?,
|
mimeType: json['mime_type'] as String?,
|
||||||
hash: json['hash'] as String?,
|
hash: json['hash'] as String?,
|
||||||
size: (json['size'] as num).toInt(),
|
size: (json['size'] as num).toInt(),
|
||||||
uploadedAt: DateTime.parse(json['uploaded_at'] as String),
|
uploadedAt:
|
||||||
|
json['uploaded_at'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['uploaded_at'] as String),
|
||||||
uploadedTo: json['uploaded_to'] as String?,
|
uploadedTo: json['uploaded_to'] as String?,
|
||||||
usedCount: (json['used_count'] as num).toInt(),
|
usedCount: (json['used_count'] as num).toInt(),
|
||||||
createdAt: DateTime.parse(json['created_at'] as String),
|
createdAt: DateTime.parse(json['created_at'] as String),
|
||||||
@ -36,7 +39,7 @@ Map<String, dynamic> _$SnCloudFileToJson(_SnCloudFile instance) =>
|
|||||||
'mime_type': instance.mimeType,
|
'mime_type': instance.mimeType,
|
||||||
'hash': instance.hash,
|
'hash': instance.hash,
|
||||||
'size': instance.size,
|
'size': instance.size,
|
||||||
'uploaded_at': instance.uploadedAt.toIso8601String(),
|
'uploaded_at': instance.uploadedAt?.toIso8601String(),
|
||||||
'uploaded_to': instance.uploadedTo,
|
'uploaded_to': instance.uploadedTo,
|
||||||
'used_count': instance.usedCount,
|
'used_count': instance.usedCount,
|
||||||
'created_at': instance.createdAt.toIso8601String(),
|
'created_at': instance.createdAt.toIso8601String(),
|
||||||
|
@ -121,8 +121,8 @@ Future<ThemeData> createAppTheme(
|
|||||||
TargetPlatform.windows: ZoomPageTransitionsBuilder(),
|
TargetPlatform.windows: ZoomPageTransitionsBuilder(),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
progressIndicatorTheme: ProgressIndicatorThemeData(),
|
progressIndicatorTheme: ProgressIndicatorThemeData(year2023: false),
|
||||||
sliderTheme: SliderThemeData(),
|
sliderTheme: SliderThemeData(year2023: false),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +1,18 @@
|
|||||||
import 'package:auto_route/annotations.dart';
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:gap/gap.dart';
|
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:island/models/file.dart';
|
||||||
import 'package:island/models/post.dart';
|
import 'package:island/models/post.dart';
|
||||||
|
import 'package:island/pods/config.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/screens/account/me/publishers.dart';
|
import 'package:island/screens/account/me/publishers.dart';
|
||||||
|
import 'package:island/services/file.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';
|
||||||
@ -30,10 +36,69 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
return null;
|
return null;
|
||||||
}, [publishers]);
|
}, [publishers]);
|
||||||
|
|
||||||
|
// Contains the XFile, ByteData, or SnCloudFile
|
||||||
|
final attachments = useState<List<dynamic>>([]);
|
||||||
final contentController = useTextEditingController();
|
final contentController = useTextEditingController();
|
||||||
|
|
||||||
final submitting = useState(false);
|
final submitting = useState(false);
|
||||||
|
|
||||||
|
Future<void> pickAttachment() async {
|
||||||
|
final result = await ref
|
||||||
|
.watch(imagePickerProvider)
|
||||||
|
.pickMultipleMedia(requestFullMetadata: true);
|
||||||
|
attachments.value = [...attachments.value, ...result];
|
||||||
|
}
|
||||||
|
|
||||||
|
final attachmentProgress = useState<Map<int, double>>({});
|
||||||
|
|
||||||
|
Future<void> uploadAttachment(int index) async {
|
||||||
|
final attachment = attachments.value[index];
|
||||||
|
if (attachment is SnCloudFile) return;
|
||||||
|
final baseUrl = ref.watch(serverUrlProvider);
|
||||||
|
final atk = await getFreshAtk(
|
||||||
|
ref.watch(tokenPairProvider),
|
||||||
|
baseUrl,
|
||||||
|
onRefreshed: (atk, rtk) {
|
||||||
|
setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk);
|
||||||
|
ref.invalidate(tokenPairProvider);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (atk == null) throw ArgumentError('Access token is null');
|
||||||
|
attachmentProgress.value = {...attachmentProgress.value, index: 0};
|
||||||
|
final cloudFile =
|
||||||
|
await putMediaToCloud(
|
||||||
|
fileData: attachment,
|
||||||
|
atk: atk,
|
||||||
|
baseUrl: baseUrl,
|
||||||
|
filename: attachment.name ?? 'Post media',
|
||||||
|
mimetype: attachment.mimeType ?? 'image/jpeg',
|
||||||
|
onProgress: (progress, estimate) {
|
||||||
|
attachmentProgress.value = {
|
||||||
|
...attachmentProgress.value,
|
||||||
|
index: progress,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
).future;
|
||||||
|
if (cloudFile == null) {
|
||||||
|
throw ArgumentError('Failed to upload the file...');
|
||||||
|
}
|
||||||
|
final clone = List.of(attachments.value);
|
||||||
|
clone[index] = cloudFile;
|
||||||
|
attachments.value = clone;
|
||||||
|
attachmentProgress.value = attachmentProgress.value..remove(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteAttachment(int index) async {
|
||||||
|
final attachment = attachments.value[index];
|
||||||
|
if (attachment is SnCloudFile) {
|
||||||
|
final client = ref.watch(apiClientProvider);
|
||||||
|
await client.delete('/files/${attachment.id}');
|
||||||
|
}
|
||||||
|
final clone = List.of(attachments.value);
|
||||||
|
clone.removeAt(index);
|
||||||
|
attachments.value = clone;
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> performAction() async {
|
Future<void> performAction() async {
|
||||||
if (!contentController.text.isNotEmpty) {
|
if (!contentController.text.isNotEmpty) {
|
||||||
return;
|
return;
|
||||||
@ -41,8 +106,25 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
submitting.value = true;
|
submitting.value = true;
|
||||||
|
|
||||||
|
await Future.wait(
|
||||||
|
attachments.value
|
||||||
|
.where((e) => e is! SnCloudFile)
|
||||||
|
.map((e) => uploadAttachment(e)),
|
||||||
|
);
|
||||||
|
|
||||||
final client = ref.watch(apiClientProvider);
|
final client = ref.watch(apiClientProvider);
|
||||||
await client.post('/posts', data: {'content': contentController.text});
|
await client.post(
|
||||||
|
'/posts',
|
||||||
|
data: {
|
||||||
|
'content': contentController.text,
|
||||||
|
'attachments':
|
||||||
|
attachments.value
|
||||||
|
.whereType<SnCloudFile>()
|
||||||
|
.map((e) => e.id)
|
||||||
|
.toList(),
|
||||||
|
},
|
||||||
|
);
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
context.maybePop(true);
|
context.maybePop(true);
|
||||||
}
|
}
|
||||||
@ -75,23 +157,258 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
ProfilePictureWidget(
|
ProfilePictureWidget(
|
||||||
item: currentPublisher.value?.picture,
|
item: currentPublisher.value?.picture,
|
||||||
radius: 24,
|
radius: 24,
|
||||||
),
|
).padding(top: 16),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TextField(
|
child: SingleChildScrollView(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
TextField(
|
||||||
controller: contentController,
|
controller: contentController,
|
||||||
decoration: InputDecoration.collapsed(
|
decoration: InputDecoration.collapsed(
|
||||||
hintText: 'What\'s happened?!',
|
hintText: 'What\'s happened?!',
|
||||||
),
|
),
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
onTapOutside:
|
onTapOutside:
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
(_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
const Gap(8),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
for (
|
||||||
|
var idx = 0;
|
||||||
|
idx < attachments.value.length;
|
||||||
|
idx++
|
||||||
|
)
|
||||||
|
_AttachmentPreview(
|
||||||
|
item: attachments.value[idx],
|
||||||
|
progress: attachmentProgress.value[idx],
|
||||||
|
onRequestUpload: () => uploadAttachment(idx),
|
||||||
|
onDelete: () => deleteAttachment(idx),
|
||||||
|
onMove: (delta) {
|
||||||
|
if (idx + delta < 0 ||
|
||||||
|
idx + delta >= attachments.value.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final clone = List.of(attachments.value);
|
||||||
|
clone.insert(
|
||||||
|
idx + delta,
|
||||||
|
clone.removeAt(idx),
|
||||||
|
);
|
||||||
|
attachments.value = clone;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).padding(all: 16),
|
).padding(horizontal: 16),
|
||||||
|
),
|
||||||
|
Material(
|
||||||
|
elevation: 2,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: pickAttachment,
|
||||||
|
icon: const Icon(LucideIcons.imagePlus),
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(
|
||||||
|
bottom: MediaQuery.of(context).padding.bottom,
|
||||||
|
horizontal: 16,
|
||||||
|
top: 8,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _AttachmentPreview extends StatelessWidget {
|
||||||
|
final dynamic item;
|
||||||
|
final double? progress;
|
||||||
|
final Function(int)? onMove;
|
||||||
|
final Function? onDelete;
|
||||||
|
final Function? onRequestUpload;
|
||||||
|
const _AttachmentPreview({
|
||||||
|
this.item,
|
||||||
|
this.progress,
|
||||||
|
this.onRequestUpload,
|
||||||
|
this.onMove,
|
||||||
|
this.onDelete,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AspectRatio(
|
||||||
|
aspectRatio: 1,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
|
child: Builder(
|
||||||
|
builder: (context) {
|
||||||
|
if (item is SnCloudFile) {
|
||||||
|
return CloudFileWidget(item: item);
|
||||||
|
} else if (item is XFile) {
|
||||||
|
if (item.mimeType?.startsWith('image') ?? false) {
|
||||||
|
return Image.file(File(item.path));
|
||||||
|
} else {
|
||||||
|
return Center(
|
||||||
|
child: Text(
|
||||||
|
'Preview is not supported for ${item.mimeType}',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (item is List<int> || item is Uint8List) {
|
||||||
|
return Image.memory(item);
|
||||||
|
}
|
||||||
|
return Placeholder();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (progress != null)
|
||||||
|
Positioned.fill(
|
||||||
|
child: Container(
|
||||||
|
color: Colors.black.withOpacity(0.3),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 40, vertical: 16),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Uploading...',
|
||||||
|
style: TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
Gap(4),
|
||||||
|
Center(child: LinearProgressIndicator(value: progress)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
left: 8,
|
||||||
|
top: 8,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: Container(
|
||||||
|
color: Colors.black.withOpacity(0.5),
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: const Icon(
|
||||||
|
LucideIcons.trash,
|
||||||
|
size: 14,
|
||||||
|
color: Colors.white,
|
||||||
|
).padding(horizontal: 8, vertical: 6),
|
||||||
|
onTap: () {
|
||||||
|
onDelete?.call();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 26,
|
||||||
|
child: const VerticalDivider(
|
||||||
|
width: 0.3,
|
||||||
|
color: Colors.white,
|
||||||
|
thickness: 0.3,
|
||||||
|
),
|
||||||
|
).padding(horizontal: 2),
|
||||||
|
InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: const Icon(
|
||||||
|
LucideIcons.arrowUp,
|
||||||
|
size: 14,
|
||||||
|
color: Colors.white,
|
||||||
|
).padding(horizontal: 8, vertical: 6),
|
||||||
|
onTap: () {
|
||||||
|
onMove?.call(-1);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: const Icon(
|
||||||
|
LucideIcons.arrowDown,
|
||||||
|
size: 14,
|
||||||
|
color: Colors.white,
|
||||||
|
).padding(horizontal: 8, vertical: 6),
|
||||||
|
onTap: () {
|
||||||
|
onMove?.call(1);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
top: 8,
|
||||||
|
right: 8,
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
onTap: () => onRequestUpload?.call(),
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: Container(
|
||||||
|
color: Colors.black.withOpacity(0.5),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
|
child:
|
||||||
|
(item is SnCloudFile)
|
||||||
|
? Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
LucideIcons.cloud,
|
||||||
|
size: 16,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
const Gap(8),
|
||||||
|
Text(
|
||||||
|
'On-cloud',
|
||||||
|
style: TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
LucideIcons.cloudOff,
|
||||||
|
size: 16,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
const Gap(8),
|
||||||
|
Text(
|
||||||
|
'On-device',
|
||||||
|
style: TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -44,7 +44,10 @@ class CloudFileList extends StatelessWidget {
|
|||||||
|
|
||||||
if (allImages) {
|
if (allImages) {
|
||||||
return ConstrainedBox(
|
return ConstrainedBox(
|
||||||
constraints: BoxConstraints(maxHeight: maxHeight),
|
constraints: BoxConstraints(
|
||||||
|
maxHeight: maxHeight,
|
||||||
|
minWidth: double.infinity,
|
||||||
|
),
|
||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: calculateAspectRatio(),
|
aspectRatio: calculateAspectRatio(),
|
||||||
child: CarouselView(
|
child: CarouselView(
|
||||||
@ -60,7 +63,10 @@ class CloudFileList extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return ConstrainedBox(
|
return ConstrainedBox(
|
||||||
constraints: BoxConstraints(maxHeight: maxHeight),
|
constraints: BoxConstraints(
|
||||||
|
maxHeight: maxHeight,
|
||||||
|
minWidth: double.infinity,
|
||||||
|
),
|
||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: calculateAspectRatio(),
|
aspectRatio: calculateAspectRatio(),
|
||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
|
@ -6,6 +6,14 @@
|
|||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.cs.allow-jit</key>
|
<key>com.apple.security.cs.allow-jit</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>com.apple.security.device.audio-input</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.files.downloads.read-write</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.files.user-selected.read-only</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.network.client</key>
|
||||||
|
<true/>
|
||||||
<key>com.apple.security.network.server</key>
|
<key>com.apple.security.network.server</key>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
|
@ -774,7 +774,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
version: "1.1.2"
|
||||||
image_picker_android:
|
image_picker_android:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: image_picker_android
|
name: image_picker_android
|
||||||
sha256: "317a5d961cec5b34e777b9252393f2afbd23084aa6e60fcf601dcf6341b9ebeb"
|
sha256: "317a5d961cec5b34e777b9252393f2afbd23084aa6e60fcf601dcf6341b9ebeb"
|
||||||
@ -814,7 +814,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.1+2"
|
version: "0.2.1+2"
|
||||||
image_picker_platform_interface:
|
image_picker_platform_interface:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: image_picker_platform_interface
|
name: image_picker_platform_interface
|
||||||
sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0"
|
sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0"
|
||||||
|
@ -76,6 +76,8 @@ dependencies:
|
|||||||
image_picker: ^1.1.2
|
image_picker: ^1.1.2
|
||||||
file_picker: ^10.1.2
|
file_picker: ^10.1.2
|
||||||
riverpod_annotation: ^2.6.1
|
riverpod_annotation: ^2.6.1
|
||||||
|
image_picker_platform_interface: ^2.10.1
|
||||||
|
image_picker_android: ^0.8.12+23
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user