Compare commits
No commits in common. "ec69b3487708d6f86da8aec9d37258407a215dca" and "1a5faabf8695eab0232fdd5dc8615300654c6067" have entirely different histories.
ec69b34877
...
1a5faabf86
@ -4,40 +4,6 @@ PODS:
|
|||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- device_info_plus (0.0.1):
|
- device_info_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- DKImagePickerController/Core (4.3.8):
|
|
||||||
- DKImagePickerController/ImageDataManager
|
|
||||||
- DKImagePickerController/Resource
|
|
||||||
- DKImagePickerController/ImageDataManager (4.3.8)
|
|
||||||
- DKImagePickerController/PhotoGallery (4.3.8):
|
|
||||||
- DKImagePickerController/Core
|
|
||||||
- DKPhotoGallery
|
|
||||||
- DKImagePickerController/Resource (4.3.8)
|
|
||||||
- DKPhotoGallery (0.0.17):
|
|
||||||
- DKPhotoGallery/Core (= 0.0.17)
|
|
||||||
- DKPhotoGallery/Model (= 0.0.17)
|
|
||||||
- DKPhotoGallery/Preview (= 0.0.17)
|
|
||||||
- DKPhotoGallery/Resource (= 0.0.17)
|
|
||||||
- SDWebImage
|
|
||||||
- SwiftyGif
|
|
||||||
- DKPhotoGallery/Core (0.0.17):
|
|
||||||
- DKPhotoGallery/Model
|
|
||||||
- DKPhotoGallery/Preview
|
|
||||||
- SDWebImage
|
|
||||||
- SwiftyGif
|
|
||||||
- DKPhotoGallery/Model (0.0.17):
|
|
||||||
- SDWebImage
|
|
||||||
- SwiftyGif
|
|
||||||
- DKPhotoGallery/Preview (0.0.17):
|
|
||||||
- DKPhotoGallery/Model
|
|
||||||
- DKPhotoGallery/Resource
|
|
||||||
- SDWebImage
|
|
||||||
- SwiftyGif
|
|
||||||
- DKPhotoGallery/Resource (0.0.17):
|
|
||||||
- SDWebImage
|
|
||||||
- SwiftyGif
|
|
||||||
- file_picker (0.0.1):
|
|
||||||
- DKImagePickerController/PhotoGallery
|
|
||||||
- Flutter
|
|
||||||
- Flutter (1.0.0)
|
- Flutter (1.0.0)
|
||||||
- flutter_local_notifications (0.0.1):
|
- flutter_local_notifications (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
@ -66,10 +32,6 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
- screen_brightness_ios (0.1.0):
|
- screen_brightness_ios (0.1.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- SDWebImage (5.19.1):
|
|
||||||
- SDWebImage/Core (= 5.19.1)
|
|
||||||
- SDWebImage/Core (5.19.1)
|
|
||||||
- SwiftyGif (5.4.5)
|
|
||||||
- url_launcher_ios (0.0.1):
|
- url_launcher_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- volume_controller (0.0.1):
|
- volume_controller (0.0.1):
|
||||||
@ -83,7 +45,6 @@ PODS:
|
|||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`)
|
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`)
|
||||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
||||||
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
||||||
@ -104,10 +65,6 @@ DEPENDENCIES:
|
|||||||
|
|
||||||
SPEC REPOS:
|
SPEC REPOS:
|
||||||
trunk:
|
trunk:
|
||||||
- DKImagePickerController
|
|
||||||
- DKPhotoGallery
|
|
||||||
- SDWebImage
|
|
||||||
- SwiftyGif
|
|
||||||
- WebRTC-SDK
|
- WebRTC-SDK
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
@ -115,8 +72,6 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/connectivity_plus/darwin"
|
:path: ".symlinks/plugins/connectivity_plus/darwin"
|
||||||
device_info_plus:
|
device_info_plus:
|
||||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||||
file_picker:
|
|
||||||
:path: ".symlinks/plugins/file_picker/ios"
|
|
||||||
Flutter:
|
Flutter:
|
||||||
:path: Flutter
|
:path: Flutter
|
||||||
flutter_local_notifications:
|
flutter_local_notifications:
|
||||||
@ -155,9 +110,6 @@ EXTERNAL SOURCES:
|
|||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db
|
connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db
|
||||||
device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d
|
device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d
|
||||||
DKImagePickerController: a7836546cfdfe014171694f643a7d575bc8ace7f
|
|
||||||
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
|
|
||||||
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
|
|
||||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||||
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
|
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
|
||||||
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
|
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
|
||||||
@ -171,8 +123,6 @@ SPEC CHECKSUMS:
|
|||||||
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
|
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
|
||||||
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||||
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
||||||
SDWebImage: 40b0b4053e36c660a764958bff99eed16610acbb
|
|
||||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
|
||||||
url_launcher_ios: 6116280ddcfe98ab8820085d8d76ae7449447586
|
url_launcher_ios: 6116280ddcfe98ab8820085d8d76ae7449447586
|
||||||
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
|
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
|
||||||
wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1
|
wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1
|
||||||
|
@ -59,10 +59,10 @@
|
|||||||
"comment": "Comment",
|
"comment": "Comment",
|
||||||
"attachment": "Attachment",
|
"attachment": "Attachment",
|
||||||
"attachmentAdd": "Add new attachment",
|
"attachmentAdd": "Add new attachment",
|
||||||
"pickMedia": "Gallery media",
|
"pickPhoto": "Gallery photo",
|
||||||
"pickFile": "Device file",
|
|
||||||
"takePhoto": "Capture photo",
|
"takePhoto": "Capture photo",
|
||||||
"takeVideo": "Capture video",
|
"pickVideo": "Gallery video",
|
||||||
|
"takeVideo": "Record video",
|
||||||
"newMoment": "Record a moment",
|
"newMoment": "Record a moment",
|
||||||
"newComment": "Leave a comment",
|
"newComment": "Leave a comment",
|
||||||
"connectingServer": "Connecting to server...",
|
"connectingServer": "Connecting to server...",
|
||||||
|
@ -59,9 +59,9 @@
|
|||||||
"comment": "评论",
|
"comment": "评论",
|
||||||
"attachment": "附件",
|
"attachment": "附件",
|
||||||
"attachmentAdd": "附加新附件",
|
"attachmentAdd": "附加新附件",
|
||||||
"pickMedia": "相册媒体",
|
"pickPhoto": "相册照片",
|
||||||
"pickFile": "设备文件",
|
|
||||||
"takePhoto": "拍摄照片",
|
"takePhoto": "拍摄照片",
|
||||||
|
"pickVideo": "相册视频",
|
||||||
"takeVideo": "拍摄视频",
|
"takeVideo": "拍摄视频",
|
||||||
"newMoment": "记录时刻",
|
"newMoment": "记录时刻",
|
||||||
"newComment": "留下评论",
|
"newComment": "留下评论",
|
||||||
|
@ -107,12 +107,6 @@ class ChatProvider extends ChangeNotifier {
|
|||||||
isCallShown = state;
|
isCallShown = state;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void unFocus() {
|
|
||||||
currentCall = null;
|
|
||||||
focusChannel = null;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChatCallInstance {
|
class ChatCallInstance {
|
||||||
|
@ -86,7 +86,7 @@ class _ChannelEditorScreenState extends State<ChannelEditorScreen> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final editingBanner = MaterialBanner(
|
final editingBanner = MaterialBanner(
|
||||||
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 20),
|
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 20),
|
||||||
leading: const Icon(Icons.edit_note),
|
leading: const Icon(Icons.edit_note),
|
||||||
backgroundColor: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.9),
|
backgroundColor: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.9),
|
||||||
dividerColor: const Color.fromARGB(1, 0, 0, 0),
|
dividerColor: const Color.fromARGB(1, 0, 0, 0),
|
||||||
@ -111,7 +111,6 @@ class _ChannelEditorScreenState extends State<ChannelEditorScreen> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
_isSubmitting ? const LinearProgressIndicator().animate().scaleX() : Container(),
|
_isSubmitting ? const LinearProgressIndicator().animate().scaleX() : Container(),
|
||||||
widget.editing != null ? editingBanner : Container(),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(AppLocalizations.of(context)!.chatChannelUsage),
|
title: Text(AppLocalizations.of(context)!.chatChannelUsage),
|
||||||
subtitle: Text(AppLocalizations.of(context)!.chatChannelUsageCaption),
|
subtitle: Text(AppLocalizations.of(context)!.chatChannelUsageCaption),
|
||||||
@ -175,6 +174,7 @@ class _ChannelEditorScreenState extends State<ChannelEditorScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
widget.editing != null ? editingBanner : Container(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -249,7 +249,8 @@ class _ChatScreenWidgetState extends State<ChatScreenWidget> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void deactivate() {
|
void deactivate() {
|
||||||
_chat.unFocus();
|
_chat.focusChannel = null;
|
||||||
|
_chat.ongoingCall = null;
|
||||||
super.deactivate();
|
super.deactivate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,7 +107,7 @@ class _CommentEditorScreenState extends State<CommentEditorScreen> {
|
|||||||
final auth = context.read<AuthProvider>();
|
final auth = context.read<AuthProvider>();
|
||||||
|
|
||||||
final editingBanner = MaterialBanner(
|
final editingBanner = MaterialBanner(
|
||||||
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 20),
|
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 20),
|
||||||
leading: const Icon(Icons.edit_note),
|
leading: const Icon(Icons.edit_note),
|
||||||
backgroundColor: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.9),
|
backgroundColor: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.9),
|
||||||
dividerColor: const Color.fromARGB(1, 0, 0, 0),
|
dividerColor: const Color.fromARGB(1, 0, 0, 0),
|
||||||
@ -175,7 +175,6 @@ class _CommentEditorScreenState extends State<CommentEditorScreen> {
|
|||||||
),
|
),
|
||||||
widget.editing != null ? editingBanner : Container(),
|
widget.editing != null ? editingBanner : Container(),
|
||||||
Container(
|
Container(
|
||||||
constraints: const BoxConstraints(minHeight: 56),
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
top: BorderSide(width: 0.3, color: Theme.of(context).dividerColor),
|
top: BorderSide(width: 0.3, color: Theme.of(context).dividerColor),
|
||||||
|
@ -97,7 +97,7 @@ class _MomentEditorScreenState extends State<MomentEditorScreen> {
|
|||||||
final auth = context.read<AuthProvider>();
|
final auth = context.read<AuthProvider>();
|
||||||
|
|
||||||
final editingBanner = MaterialBanner(
|
final editingBanner = MaterialBanner(
|
||||||
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 20),
|
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 20),
|
||||||
leading: const Icon(Icons.edit_note),
|
leading: const Icon(Icons.edit_note),
|
||||||
backgroundColor: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.9),
|
backgroundColor: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.9),
|
||||||
dividerColor: const Color.fromARGB(1, 0, 0, 0),
|
dividerColor: const Color.fromARGB(1, 0, 0, 0),
|
||||||
@ -121,7 +121,9 @@ class _MomentEditorScreenState extends State<MomentEditorScreen> {
|
|||||||
],
|
],
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
_isSubmitting ? const LinearProgressIndicator().animate().scaleX() : Container(),
|
_isSubmitting
|
||||||
|
? const LinearProgressIndicator().animate().scaleX()
|
||||||
|
: Container(),
|
||||||
FutureBuilder(
|
FutureBuilder(
|
||||||
future: auth.getProfiles(),
|
future: auth.getProfiles(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
@ -153,15 +155,16 @@ class _MomentEditorScreenState extends State<MomentEditorScreen> {
|
|||||||
keyboardType: TextInputType.multiline,
|
keyboardType: TextInputType.multiline,
|
||||||
controller: _textController,
|
controller: _textController,
|
||||||
decoration: InputDecoration.collapsed(
|
decoration: InputDecoration.collapsed(
|
||||||
hintText: AppLocalizations.of(context)!.postContentPlaceholder,
|
hintText:
|
||||||
|
AppLocalizations.of(context)!.postContentPlaceholder,
|
||||||
),
|
),
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
widget.editing != null ? editingBanner : Container(),
|
widget.editing != null ? editingBanner : Container(),
|
||||||
Container(
|
Container(
|
||||||
constraints: const BoxConstraints(minHeight: 56),
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
top: BorderSide(width: 0.3, color: Theme.of(context).dividerColor),
|
top: BorderSide(width: 0.3, color: Theme.of(context).dividerColor),
|
||||||
|
@ -5,7 +5,6 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:solian/models/call.dart';
|
import 'package:solian/models/call.dart';
|
||||||
import 'package:solian/models/channel.dart';
|
import 'package:solian/models/channel.dart';
|
||||||
import 'package:solian/providers/auth.dart';
|
import 'package:solian/providers/auth.dart';
|
||||||
import 'package:solian/providers/chat.dart';
|
|
||||||
import 'package:solian/router.dart';
|
import 'package:solian/router.dart';
|
||||||
import 'package:solian/utils/service_url.dart';
|
import 'package:solian/utils/service_url.dart';
|
||||||
import 'package:solian/widgets/exts.dart';
|
import 'package:solian/widgets/exts.dart';
|
||||||
@ -15,7 +14,8 @@ class ChannelCallAction extends StatefulWidget {
|
|||||||
final Channel channel;
|
final Channel channel;
|
||||||
final Function onUpdate;
|
final Function onUpdate;
|
||||||
|
|
||||||
const ChannelCallAction({super.key, this.call, required this.channel, required this.onUpdate});
|
const ChannelCallAction(
|
||||||
|
{super.key, this.call, required this.channel, required this.onUpdate});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ChannelCallAction> createState() => _ChannelCallActionState();
|
State<ChannelCallAction> createState() => _ChannelCallActionState();
|
||||||
@ -33,7 +33,8 @@ class _ChannelCallActionState extends State<ChannelCallAction> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var uri = getRequestUri('messaging', '/api/channels/${widget.channel.alias}/calls');
|
var uri = getRequestUri(
|
||||||
|
'messaging', '/api/channels/${widget.channel.alias}/calls');
|
||||||
|
|
||||||
var res = await auth.client!.post(uri);
|
var res = await auth.client!.post(uri);
|
||||||
if (res.statusCode != 200) {
|
if (res.statusCode != 200) {
|
||||||
@ -47,24 +48,19 @@ class _ChannelCallActionState extends State<ChannelCallAction> {
|
|||||||
Future<void> endsCall() async {
|
Future<void> endsCall() async {
|
||||||
setState(() => _isSubmitting = true);
|
setState(() => _isSubmitting = true);
|
||||||
|
|
||||||
final chat = context.read<ChatProvider>();
|
|
||||||
final auth = context.read<AuthProvider>();
|
final auth = context.read<AuthProvider>();
|
||||||
if (!await auth.isAuthorized()) {
|
if (!await auth.isAuthorized()) {
|
||||||
setState(() => _isSubmitting = false);
|
setState(() => _isSubmitting = false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var uri = getRequestUri('messaging', '/api/channels/${widget.channel.alias}/calls/ongoing');
|
var uri = getRequestUri(
|
||||||
|
'messaging', '/api/channels/${widget.channel.alias}/calls/ongoing');
|
||||||
|
|
||||||
var res = await auth.client!.delete(uri);
|
var res = await auth.client!.delete(uri);
|
||||||
if (res.statusCode != 200) {
|
if (res.statusCode != 200) {
|
||||||
var message = utf8.decode(res.bodyBytes);
|
var message = utf8.decode(res.bodyBytes);
|
||||||
context.showErrorDialog(message);
|
context.showErrorDialog(message);
|
||||||
} else {
|
|
||||||
if (chat.currentCall != null && chat.currentCall?.info.channelId == widget.channel.id) {
|
|
||||||
chat.currentCall!.deactivate();
|
|
||||||
chat.currentCall!.dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setState(() => _isSubmitting = false);
|
setState(() => _isSubmitting = false);
|
||||||
@ -82,7 +78,9 @@ class _ChannelCallActionState extends State<ChannelCallAction> {
|
|||||||
endsCall();
|
endsCall();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon: widget.call == null ? const Icon(Icons.call) : const Icon(Icons.call_end),
|
icon: widget.call == null
|
||||||
|
? const Icon(Icons.call)
|
||||||
|
: const Icon(Icons.call_end),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,7 +89,8 @@ class ChannelManageAction extends StatelessWidget {
|
|||||||
final Channel channel;
|
final Channel channel;
|
||||||
final Function onUpdate;
|
final Function onUpdate;
|
||||||
|
|
||||||
const ChannelManageAction({super.key, required this.channel, required this.onUpdate});
|
const ChannelManageAction(
|
||||||
|
{super.key, required this.channel, required this.onUpdate});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -106,7 +106,7 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final editingBanner = MaterialBanner(
|
final editingBanner = MaterialBanner(
|
||||||
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 20),
|
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 20),
|
||||||
leading: const Icon(Icons.edit_note),
|
leading: const Icon(Icons.edit_note),
|
||||||
backgroundColor: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.9),
|
backgroundColor: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.9),
|
||||||
dividerColor: const Color.fromARGB(1, 0, 0, 0),
|
dividerColor: const Color.fromARGB(1, 0, 0, 0),
|
||||||
@ -120,7 +120,7 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final replyingBanner = MaterialBanner(
|
final replyingBanner = MaterialBanner(
|
||||||
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 20),
|
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 20),
|
||||||
leading: const Icon(Icons.reply),
|
leading: const Icon(Icons.reply),
|
||||||
backgroundColor: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.9),
|
backgroundColor: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.9),
|
||||||
dividerColor: const Color.fromARGB(1, 0, 0, 0),
|
dividerColor: const Color.fromARGB(1, 0, 0, 0),
|
||||||
|
@ -3,7 +3,6 @@ import 'dart:io';
|
|||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_animate/flutter_animate.dart';
|
import 'package:flutter_animate/flutter_animate.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
@ -42,84 +41,25 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
|
|||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AttachmentEditorMethodPopup(
|
builder: (context) => AttachmentEditorMethodPopup(
|
||||||
pickMedia: () => pickMediaToUpload(context),
|
pickImage: () => pickImageToUpload(context, ImageSource.gallery),
|
||||||
pickFile: () => pickFileToUpload(context),
|
takeImage: () => pickImageToUpload(context, ImageSource.camera),
|
||||||
takeImage: () => takeMediaToUpload(context, false),
|
pickVideo: () => pickVideoToUpload(context, ImageSource.gallery),
|
||||||
takeVideo: () => takeMediaToUpload(context, true),
|
takeVideo: () => pickVideoToUpload(context, ImageSource.camera),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> pickMediaToUpload(BuildContext context) async {
|
Future<void> pickImageToUpload(
|
||||||
|
BuildContext context, ImageSource source) async {
|
||||||
final auth = context.read<AuthProvider>();
|
final auth = context.read<AuthProvider>();
|
||||||
if (!await auth.isAuthorized()) return;
|
if (!await auth.isAuthorized()) return;
|
||||||
|
|
||||||
final medias = await _imagePicker.pickMultipleMedia();
|
final image = await _imagePicker.pickImage(source: source);
|
||||||
if (medias.isEmpty) return;
|
if (image == null) return;
|
||||||
|
|
||||||
setState(() => _isSubmitting = true);
|
setState(() => _isSubmitting = true);
|
||||||
|
|
||||||
bool isPopped = false;
|
final file = File(image.path);
|
||||||
for (final media in medias) {
|
|
||||||
final file = File(media.path);
|
|
||||||
final hashcode = await calculateSha256(file);
|
|
||||||
try {
|
|
||||||
await uploadAttachment(file, hashcode);
|
|
||||||
} catch (err) {
|
|
||||||
context.showErrorDialog(err);
|
|
||||||
}
|
|
||||||
if (!isPopped && Navigator.canPop(context)) {
|
|
||||||
Navigator.pop(context);
|
|
||||||
isPopped = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() => _isSubmitting = false);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> pickFileToUpload(BuildContext context) async {
|
|
||||||
final auth = context.read<AuthProvider>();
|
|
||||||
if (!await auth.isAuthorized()) return;
|
|
||||||
|
|
||||||
FilePickerResult? result = await FilePicker.platform.pickFiles(allowMultiple: true);
|
|
||||||
if (result == null) return;
|
|
||||||
|
|
||||||
List<File> files = result.paths.map((path) => File(path!)).toList();
|
|
||||||
|
|
||||||
setState(() => _isSubmitting = true);
|
|
||||||
|
|
||||||
bool isPopped = false;
|
|
||||||
for (final file in files) {
|
|
||||||
final hashcode = await calculateSha256(file);
|
|
||||||
try {
|
|
||||||
await uploadAttachment(file, hashcode);
|
|
||||||
} catch (err) {
|
|
||||||
context.showErrorDialog(err);
|
|
||||||
}
|
|
||||||
if (!isPopped && Navigator.canPop(context)) {
|
|
||||||
Navigator.pop(context);
|
|
||||||
isPopped = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() => _isSubmitting = false);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> takeMediaToUpload(BuildContext context, bool isVideo) async {
|
|
||||||
final auth = context.read<AuthProvider>();
|
|
||||||
if (!await auth.isAuthorized()) return;
|
|
||||||
|
|
||||||
XFile? media;
|
|
||||||
if (isVideo) {
|
|
||||||
media = await _imagePicker.pickVideo(source: ImageSource.camera);
|
|
||||||
} else {
|
|
||||||
media = await _imagePicker.pickImage(source: ImageSource.camera);
|
|
||||||
}
|
|
||||||
if (media == null) return;
|
|
||||||
|
|
||||||
setState(() => _isSubmitting = true);
|
|
||||||
|
|
||||||
final file = File(media.path);
|
|
||||||
final hashcode = await calculateSha256(file);
|
final hashcode = await calculateSha256(file);
|
||||||
|
|
||||||
if (Navigator.canPop(context)) {
|
if (Navigator.canPop(context)) {
|
||||||
@ -130,15 +70,42 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
|
|||||||
await uploadAttachment(file, hashcode);
|
await uploadAttachment(file, hashcode);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isSubmitting = false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> pickVideoToUpload(
|
||||||
|
BuildContext context, ImageSource source) async {
|
||||||
|
final auth = context.read<AuthProvider>();
|
||||||
|
if (!await auth.isAuthorized()) return;
|
||||||
|
|
||||||
|
final image = await _imagePicker.pickVideo(source: source);
|
||||||
|
if (image == null) return;
|
||||||
|
|
||||||
|
setState(() => _isSubmitting = true);
|
||||||
|
|
||||||
|
final file = File(image.path);
|
||||||
|
final hashcode = await calculateSha256(file);
|
||||||
|
|
||||||
|
if (Navigator.canPop(context)) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await uploadAttachment(file, hashcode);
|
||||||
|
} catch (err) {
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
setState(() => _isSubmitting = false);
|
setState(() => _isSubmitting = false);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> uploadAttachment(File file, String hashcode) async {
|
Future<void> uploadAttachment(File file, String hashcode) async {
|
||||||
final auth = context.read<AuthProvider>();
|
final auth = context.read<AuthProvider>();
|
||||||
|
|
||||||
final req = MultipartRequest('POST', getRequestUri(widget.provider, '/api/attachments'));
|
final req = MultipartRequest(
|
||||||
|
'POST', getRequestUri(widget.provider, '/api/attachments'));
|
||||||
req.files.add(await MultipartFile.fromPath('attachment', file.path));
|
req.files.add(await MultipartFile.fromPath('attachment', file.path));
|
||||||
req.fields['hashcode'] = hashcode;
|
req.fields['hashcode'] = hashcode;
|
||||||
|
|
||||||
@ -154,10 +121,12 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> disposeAttachment(BuildContext context, Attachment item, int index) async {
|
Future<void> disposeAttachment(
|
||||||
|
BuildContext context, Attachment item, int index) async {
|
||||||
final auth = context.read<AuthProvider>();
|
final auth = context.read<AuthProvider>();
|
||||||
|
|
||||||
final req = MultipartRequest('DELETE', getRequestUri(widget.provider, '/api/attachments/${item.id}'));
|
final req = MultipartRequest('DELETE',
|
||||||
|
getRequestUri(widget.provider, '/api/attachments/${item.id}'));
|
||||||
|
|
||||||
setState(() => _isSubmitting = true);
|
setState(() => _isSubmitting = true);
|
||||||
var res = await auth.client!.send(req);
|
var res = await auth.client!.send(req);
|
||||||
@ -198,7 +167,17 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
|
|||||||
if (bytes == 0) return '0 Bytes';
|
if (bytes == 0) return '0 Bytes';
|
||||||
const k = 1024;
|
const k = 1024;
|
||||||
final dm = decimals < 0 ? 0 : decimals;
|
final dm = decimals < 0 ? 0 : decimals;
|
||||||
final sizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
|
final sizes = [
|
||||||
|
'Bytes',
|
||||||
|
'KiB',
|
||||||
|
'MiB',
|
||||||
|
'GiB',
|
||||||
|
'TiB',
|
||||||
|
'PiB',
|
||||||
|
'EiB',
|
||||||
|
'ZiB',
|
||||||
|
'YiB'
|
||||||
|
];
|
||||||
final i = (math.log(bytes) / math.log(k)).floor().toInt();
|
final i = (math.log(bytes) / math.log(k)).floor().toInt();
|
||||||
return '${(bytes / math.pow(k, i)).toStringAsFixed(dm)} ${sizes[i]}';
|
return '${(bytes / math.pow(k, i)).toStringAsFixed(dm)} ${sizes[i]}';
|
||||||
}
|
}
|
||||||
@ -216,7 +195,8 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
|
|||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.only(left: 8, right: 8, top: 20, bottom: 12),
|
padding:
|
||||||
|
const EdgeInsets.only(left: 8, right: 8, top: 20, bottom: 12),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
@ -235,7 +215,9 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
|
|||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasData && snapshot.data == true) {
|
if (snapshot.hasData && snapshot.data == true) {
|
||||||
return TextButton(
|
return TextButton(
|
||||||
onPressed: _isSubmitting ? null : () => viewAttachMethods(context),
|
onPressed: _isSubmitting
|
||||||
|
? null
|
||||||
|
: () => viewAttachMethods(context),
|
||||||
style: TextButton.styleFrom(shape: const CircleBorder()),
|
style: TextButton.styleFrom(shape: const CircleBorder()),
|
||||||
child: const Icon(Icons.add_circle),
|
child: const Icon(Icons.add_circle),
|
||||||
);
|
);
|
||||||
@ -247,14 +229,16 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
_isSubmitting ? const LinearProgressIndicator().animate().scaleX() : Container(),
|
_isSubmitting
|
||||||
|
? const LinearProgressIndicator().animate().scaleX()
|
||||||
|
: Container(),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView.builder(
|
child: ListView.separated(
|
||||||
itemCount: _attachments.length,
|
itemCount: _attachments.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
var element = _attachments[index];
|
var element = _attachments[index];
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.only(left: 16, right: 8, bottom: 16),
|
padding: const EdgeInsets.only(left: 16, right: 8),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
@ -279,12 +263,14 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
|
|||||||
foregroundColor: Colors.red,
|
foregroundColor: Colors.red,
|
||||||
),
|
),
|
||||||
child: const Icon(Icons.delete),
|
child: const Icon(Icons.delete),
|
||||||
onPressed: () => disposeAttachment(context, element, index),
|
onPressed: () =>
|
||||||
|
disposeAttachment(context, element, index),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
separatorBuilder: (context, index) => const Divider(thickness: 0.3),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -293,16 +279,16 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class AttachmentEditorMethodPopup extends StatelessWidget {
|
class AttachmentEditorMethodPopup extends StatelessWidget {
|
||||||
final Function pickMedia;
|
final Function pickImage;
|
||||||
final Function pickFile;
|
|
||||||
final Function takeImage;
|
final Function takeImage;
|
||||||
|
final Function pickVideo;
|
||||||
final Function takeVideo;
|
final Function takeVideo;
|
||||||
|
|
||||||
const AttachmentEditorMethodPopup({
|
const AttachmentEditorMethodPopup({
|
||||||
super.key,
|
super.key,
|
||||||
required this.pickMedia,
|
required this.pickImage,
|
||||||
required this.pickFile,
|
|
||||||
required this.takeImage,
|
required this.takeImage,
|
||||||
|
required this.pickVideo,
|
||||||
required this.takeVideo,
|
required this.takeVideo,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -333,28 +319,15 @@ class AttachmentEditorMethodPopup extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
InkWell(
|
InkWell(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
onTap: () => pickMedia(),
|
onTap: () => pickImage(),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.photo_library, color: Colors.indigo),
|
const Icon(Icons.add_photo_alternate,
|
||||||
|
color: Colors.indigo),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(AppLocalizations.of(context)!.pickMedia),
|
Text(AppLocalizations.of(context)!.pickPhoto),
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
InkWell(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
onTap: () => pickFile(),
|
|
||||||
child: Center(
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.file_present, color: Colors.amber),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text(AppLocalizations.of(context)!.pickFile),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -366,13 +339,27 @@ class AttachmentEditorMethodPopup extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.camera_alt, color: Colors.teal),
|
const Icon(Icons.camera_alt, color: Colors.indigo),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(AppLocalizations.of(context)!.takePhoto),
|
Text(AppLocalizations.of(context)!.takePhoto),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
onTap: () => pickVideo(),
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.camera, color: Colors.teal),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(AppLocalizations.of(context)!.pickVideo),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
InkWell(
|
InkWell(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
onTap: () => takeVideo(),
|
onTap: () => takeVideo(),
|
||||||
@ -380,7 +367,7 @@ class AttachmentEditorMethodPopup extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.movie, color: Colors.blue),
|
const Icon(Icons.video_call, color: Colors.teal),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(AppLocalizations.of(context)!.takeVideo),
|
Text(AppLocalizations.of(context)!.takeVideo),
|
||||||
],
|
],
|
||||||
|
@ -201,14 +201,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.0"
|
version: "7.0.0"
|
||||||
file_picker:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: file_picker
|
|
||||||
sha256: "29c90806ac5f5fb896547720b73b17ee9aed9bba540dc5d91fe29f8c5745b10a"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "8.0.3"
|
|
||||||
file_selector_linux:
|
file_selector_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -67,7 +67,6 @@ dependencies:
|
|||||||
wakelock_plus: ^1.2.4
|
wakelock_plus: ^1.2.4
|
||||||
flutter_local_notifications: ^17.1.0
|
flutter_local_notifications: ^17.1.0
|
||||||
draggable_float_widget: ^0.1.0
|
draggable_float_widget: ^0.1.0
|
||||||
file_picker: ^8.0.3
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user