feat: Drag&Drop to send multiple files on desktop and web

This commit is contained in:
Krille Fear 2021-11-28 11:43:36 +01:00
parent 0e984677b8
commit 2737768a60
10 changed files with 247 additions and 189 deletions

View File

@ -27,6 +27,7 @@
- feat: Send reactions to multiple events - feat: Send reactions to multiple events
- feat: Speed up app start - feat: Speed up app start
- feat: Use SalomonBottomBar - feat: Use SalomonBottomBar
- feat: Drag&Drop to send multiple files on desktop and web
- fix: Adjust color - fix: Adjust color
- fix: Automatic key requests - fix: Automatic key requests
- fix: Bootstrap loop - fix: Bootstrap loop

View File

@ -6,6 +6,7 @@ import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:desktop_drop/desktop_drop.dart';
import 'package:file_picker_cross/file_picker_cross.dart'; import 'package:file_picker_cross/file_picker_cross.dart';
import 'package:flutter_app_badger/flutter_app_badger.dart'; import 'package:flutter_app_badger/flutter_app_badger.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
@ -29,8 +30,8 @@ import '../../utils/account_bundles.dart';
import '../../utils/localized_exception_extension.dart'; import '../../utils/localized_exception_extension.dart';
import '../../utils/matrix_sdk_extensions.dart/filtered_timeline_extension.dart'; import '../../utils/matrix_sdk_extensions.dart/filtered_timeline_extension.dart';
import '../../utils/matrix_sdk_extensions.dart/matrix_file_extension.dart'; import '../../utils/matrix_sdk_extensions.dart/matrix_file_extension.dart';
import '../new_private_chat/send_file_dialog.dart'; import 'send_file_dialog.dart';
import '../new_private_chat/send_location_dialog.dart'; import 'send_location_dialog.dart';
import 'sticker_picker_dialog.dart'; import 'sticker_picker_dialog.dart';
class Chat extends StatefulWidget { class Chat extends StatefulWidget {
@ -60,6 +61,28 @@ class ChatController extends State<Chat> {
Timer typingCoolDown; Timer typingCoolDown;
Timer typingTimeout; Timer typingTimeout;
bool currentlyTyping = false; bool currentlyTyping = false;
bool dragging = false;
void onDragEntered(_) => setState(() => dragging = true);
void onDragExited(_) => setState(() => dragging = false);
void onDragDone(DropDoneDetails details) async {
setState(() => dragging = false);
for (final url in details.urls) {
final file = File.fromUri(url);
final bytes = await file.readAsBytes();
await showDialog(
context: context,
useRootNavigator: false,
builder: (c) => SendFileDialog(
file: MatrixFile(
bytes: bytes,
name: file.path.split('/').last,
).detectFileType,
room: room,
),
);
}
}
List<Event> selectedEvents = []; List<Event> selectedEvents = [];

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:desktop_drop/desktop_drop.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
@ -168,7 +169,11 @@ class ChatView extends StatelessWidget {
) )
: null, : null,
backgroundColor: Theme.of(context).colorScheme.surface, backgroundColor: Theme.of(context).colorScheme.surface,
body: Stack( body: DropTarget(
onDragDone: controller.onDragDone,
onDragEntered: controller.onDragEntered,
onDragExited: controller.onDragExited,
child: Stack(
children: <Widget>[ children: <Widget>[
if (Matrix.of(context).wallpaper != null) if (Matrix.of(context).wallpaper != null)
Image.file( Image.file(
@ -221,7 +226,8 @@ class ChatView extends StatelessWidget {
: ScrollViewKeyboardDismissBehavior.manual, : ScrollViewKeyboardDismissBehavior.manual,
childrenDelegate: SliverChildBuilderDelegate( childrenDelegate: SliverChildBuilderDelegate(
(BuildContext context, int i) { (BuildContext context, int i) {
return i == controller.filteredEvents.length + 1 return i ==
controller.filteredEvents.length + 1
? controller.timeline.isRequestingHistory ? controller.timeline.isRequestingHistory
? const Center( ? const Center(
child: CircularProgressIndicator child: CircularProgressIndicator
@ -230,14 +236,14 @@ class ChatView extends StatelessWidget {
: controller.canLoadMore : controller.canLoadMore
? Center( ? Center(
child: OutlinedButton( child: OutlinedButton(
style: style: OutlinedButton
OutlinedButton.styleFrom( .styleFrom(
backgroundColor: Theme.of( backgroundColor: Theme.of(
context) context)
.scaffoldBackgroundColor, .scaffoldBackgroundColor,
), ),
onPressed: onPressed: controller
controller.requestHistory, .requestHistory,
child: Text(L10n.of(context) child: Text(L10n.of(context)
.loadMore), .loadMore),
), ),
@ -253,7 +259,8 @@ class ChatView extends StatelessWidget {
) )
: AutoScrollTag( : AutoScrollTag(
key: ValueKey(controller key: ValueKey(controller
.filteredEvents[i - 1].eventId), .filteredEvents[i - 1]
.eventId),
index: i - 1, index: i - 1,
controller: controller:
controller.scrollController, controller.scrollController,
@ -279,15 +286,16 @@ class ChatView extends StatelessWidget {
child: Message( child: Message(
controller controller
.filteredEvents[i - 1], .filteredEvents[i - 1],
onInfoTab: onInfoTab: controller
controller.showEventInfo, .showEventInfo,
onAvatarTab: (Event event) => onAvatarTab: (Event event) =>
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
builder: (c) => builder: (c) =>
UserBottomSheet( UserBottomSheet(
user: event.sender, user: event.sender,
outerContext: context, outerContext:
context,
onMention: () => controller onMention: () => controller
.sendController .sendController
.text += .text +=
@ -297,26 +305,26 @@ class ChatView extends StatelessWidget {
unfold: controller.unfold, unfold: controller.unfold,
onSelect: controller onSelect: controller
.onSelectMessage, .onSelectMessage,
scrollToEventId: (String eventId) => controller scrollToEventId:
.scrollToEventId(eventId), (String eventId) =>
controller.scrollToEventId(
eventId),
longPressSelect: controller longPressSelect: controller
.selectedEvents.isEmpty, .selectedEvents.isEmpty,
selected: controller.selectedEvents.any((e) => selected: controller
.selectedEvents
.any((e) =>
e.eventId == e.eventId ==
controller controller
.filteredEvents[i - 1] .filteredEvents[i - 1]
.eventId), .eventId),
timeline: controller.timeline, timeline: controller.timeline,
nextEvent: i < nextEvent: i < controller.filteredEvents.length ? controller.filteredEvents[i] : null),
controller
.filteredEvents
.length
? controller.filteredEvents[i]
: null),
), ),
); );
}, },
childCount: controller.filteredEvents.length + 2, childCount:
controller.filteredEvents.length + 2,
findChildIndexCallback: (key) => findChildIndexCallback: (key) =>
controller.findChildIndexCallback( controller.findChildIndexCallback(
key, thisEventsKeyMap), key, thisEventsKeyMap),
@ -338,7 +346,8 @@ class ChatView extends StatelessWidget {
alignment: Alignment.center, alignment: Alignment.center,
child: Material( child: Material(
borderRadius: const BorderRadius.only( borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(AppConfig.borderRadius), bottomLeft:
Radius.circular(AppConfig.borderRadius),
bottomRight: bottomRight:
Radius.circular(AppConfig.borderRadius), Radius.circular(AppConfig.borderRadius),
), ),
@ -347,7 +356,8 @@ class ChatView extends StatelessWidget {
.secondaryHeaderColor .secondaryHeaderColor
.withAlpha(100), .withAlpha(100),
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
color: Theme.of(context).appBarTheme.backgroundColor, color:
Theme.of(context).appBarTheme.backgroundColor,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@ -363,10 +373,22 @@ class ChatView extends StatelessWidget {
], ],
), ),
), ),
if (controller.dragging)
Container(
color: Theme.of(context)
.scaffoldBackgroundColor
.withOpacity(0.9),
alignment: Alignment.center,
child: const Icon(
Icons.upload_outlined,
size: 100,
),
),
], ],
), ),
), ),
), ),
),
); );
} }
} }

View File

@ -13,7 +13,7 @@ import 'package:fluffychat/utils/room_status_extension.dart';
import '../../utils/date_time_extension.dart'; import '../../utils/date_time_extension.dart';
import '../../widgets/avatar.dart'; import '../../widgets/avatar.dart';
import '../../widgets/matrix.dart'; import '../../widgets/matrix.dart';
import '../new_private_chat/send_file_dialog.dart'; import '../chat/send_file_dialog.dart';
enum ArchivedRoomAction { delete, rejoin } enum ArchivedRoomAction { delete, rejoin }

View File

@ -6,11 +6,15 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <desktop_drop/desktop_drop_plugin.h>
#include <file_selector_linux/file_selector_plugin.h> #include <file_selector_linux/file_selector_plugin.h>
#include <flutter_secure_storage/flutter_secure_storage_plugin.h> #include <flutter_secure_storage/flutter_secure_storage_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h> #include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) { void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) desktop_drop_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopDropPlugin");
desktop_drop_plugin_register_with_registrar(desktop_drop_registrar);
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
file_selector_plugin_register_with_registrar(file_selector_linux_registrar); file_selector_plugin_register_with_registrar(file_selector_linux_registrar);

View File

@ -3,6 +3,7 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
desktop_drop
file_selector_linux file_selector_linux
flutter_secure_storage flutter_secure_storage
url_launcher_linux url_launcher_linux

View File

@ -246,6 +246,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.3" version: "0.4.3"
desktop_drop:
dependency: "direct main"
description:
name: desktop_drop
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.0"
desktop_notifications: desktop_notifications:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -14,6 +14,7 @@ dependencies:
cached_network_image: ^3.1.0 cached_network_image: ^3.1.0
chewie: ^1.2.2 chewie: ^1.2.2
cupertino_icons: any cupertino_icons: any
desktop_drop: ^0.2.0
desktop_notifications: ">=0.4.0 <0.5.0" # Version 0.5.0 breaks web builds: https://github.com/canonical/dbus.dart/issues/250 desktop_notifications: ">=0.4.0 <0.5.0" # Version 0.5.0 breaks web builds: https://github.com/canonical/dbus.dart/issues/250
email_validator: ^2.0.1 email_validator: ^2.0.1
emoji_picker_flutter: ^1.0.7 emoji_picker_flutter: ^1.0.7
@ -23,7 +24,6 @@ dependencies:
file_picker_cross: ^4.5.0 file_picker_cross: ^4.5.0
flutter: flutter:
sdk: flutter sdk: flutter
# From this fix: https://github.com/g123k/flutter_app_badger/pull/47
flutter_app_badger: ^1.3.0 flutter_app_badger: ^1.3.0
flutter_app_lock: ^2.0.0 flutter_app_lock: ^2.0.0
flutter_blurhash: ^0.6.0 flutter_blurhash: ^0.6.0