feat: Drag&Drop to send multiple files on desktop and web
This commit is contained in:
parent
492f6e8228
commit
a134e06203
10 changed files with 246 additions and 188 deletions
|
|
@ -6,6 +6,7 @@ import 'package:flutter/scheduler.dart';
|
|||
import 'package:flutter/services.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:flutter_app_badger/flutter_app_badger.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/matrix_sdk_extensions.dart/filtered_timeline_extension.dart';
|
||||
import '../../utils/matrix_sdk_extensions.dart/matrix_file_extension.dart';
|
||||
import '../new_private_chat/send_file_dialog.dart';
|
||||
import '../new_private_chat/send_location_dialog.dart';
|
||||
import 'send_file_dialog.dart';
|
||||
import 'send_location_dialog.dart';
|
||||
import 'sticker_picker_dialog.dart';
|
||||
|
||||
class Chat extends StatefulWidget {
|
||||
|
|
@ -60,6 +61,28 @@ class ChatController extends State<Chat> {
|
|||
Timer typingCoolDown;
|
||||
Timer typingTimeout;
|
||||
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 = [];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:desktop_drop/desktop_drop.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
|
@ -168,202 +169,223 @@ class ChatView extends StatelessWidget {
|
|||
)
|
||||
: null,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
body: Stack(
|
||||
children: <Widget>[
|
||||
if (Matrix.of(context).wallpaper != null)
|
||||
Image.file(
|
||||
Matrix.of(context).wallpaper,
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
SafeArea(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
TombstoneDisplay(controller),
|
||||
Expanded(
|
||||
child: FutureBuilder<bool>(
|
||||
future: controller.getTimeline(),
|
||||
builder: (BuildContext context, snapshot) {
|
||||
if (snapshot.hasError) {
|
||||
SentryController.captureException(
|
||||
snapshot.error,
|
||||
StackTrace.current,
|
||||
);
|
||||
}
|
||||
if (controller.timeline == null) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator.adaptive(
|
||||
strokeWidth: 2),
|
||||
);
|
||||
}
|
||||
body: DropTarget(
|
||||
onDragDone: controller.onDragDone,
|
||||
onDragEntered: controller.onDragEntered,
|
||||
onDragExited: controller.onDragExited,
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
if (Matrix.of(context).wallpaper != null)
|
||||
Image.file(
|
||||
Matrix.of(context).wallpaper,
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
SafeArea(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
TombstoneDisplay(controller),
|
||||
Expanded(
|
||||
child: FutureBuilder<bool>(
|
||||
future: controller.getTimeline(),
|
||||
builder: (BuildContext context, snapshot) {
|
||||
if (snapshot.hasError) {
|
||||
SentryController.captureException(
|
||||
snapshot.error,
|
||||
StackTrace.current,
|
||||
);
|
||||
}
|
||||
if (controller.timeline == null) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator.adaptive(
|
||||
strokeWidth: 2),
|
||||
);
|
||||
}
|
||||
|
||||
// create a map of eventId --> index to greatly improve performance of
|
||||
// ListView's findChildIndexCallback
|
||||
final thisEventsKeyMap = <String, int>{};
|
||||
for (var i = 0;
|
||||
i < controller.filteredEvents.length;
|
||||
i++) {
|
||||
thisEventsKeyMap[
|
||||
controller.filteredEvents[i].eventId] = i;
|
||||
}
|
||||
return ListView.custom(
|
||||
padding: EdgeInsets.only(
|
||||
top: 16,
|
||||
bottom: 4,
|
||||
left: horizontalPadding,
|
||||
right: horizontalPadding,
|
||||
),
|
||||
reverse: true,
|
||||
controller: controller.scrollController,
|
||||
keyboardDismissBehavior: PlatformInfos.isIOS
|
||||
? ScrollViewKeyboardDismissBehavior.onDrag
|
||||
: ScrollViewKeyboardDismissBehavior.manual,
|
||||
childrenDelegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int i) {
|
||||
return i == controller.filteredEvents.length + 1
|
||||
? controller.timeline.isRequestingHistory
|
||||
? const Center(
|
||||
child: CircularProgressIndicator
|
||||
.adaptive(strokeWidth: 2),
|
||||
)
|
||||
: controller.canLoadMore
|
||||
? Center(
|
||||
child: OutlinedButton(
|
||||
style:
|
||||
OutlinedButton.styleFrom(
|
||||
backgroundColor: Theme.of(
|
||||
context)
|
||||
.scaffoldBackgroundColor,
|
||||
// create a map of eventId --> index to greatly improve performance of
|
||||
// ListView's findChildIndexCallback
|
||||
final thisEventsKeyMap = <String, int>{};
|
||||
for (var i = 0;
|
||||
i < controller.filteredEvents.length;
|
||||
i++) {
|
||||
thisEventsKeyMap[
|
||||
controller.filteredEvents[i].eventId] = i;
|
||||
}
|
||||
return ListView.custom(
|
||||
padding: EdgeInsets.only(
|
||||
top: 16,
|
||||
bottom: 4,
|
||||
left: horizontalPadding,
|
||||
right: horizontalPadding,
|
||||
),
|
||||
reverse: true,
|
||||
controller: controller.scrollController,
|
||||
keyboardDismissBehavior: PlatformInfos.isIOS
|
||||
? ScrollViewKeyboardDismissBehavior.onDrag
|
||||
: ScrollViewKeyboardDismissBehavior.manual,
|
||||
childrenDelegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int i) {
|
||||
return i ==
|
||||
controller.filteredEvents.length + 1
|
||||
? controller.timeline.isRequestingHistory
|
||||
? const Center(
|
||||
child: CircularProgressIndicator
|
||||
.adaptive(strokeWidth: 2),
|
||||
)
|
||||
: controller.canLoadMore
|
||||
? Center(
|
||||
child: OutlinedButton(
|
||||
style: OutlinedButton
|
||||
.styleFrom(
|
||||
backgroundColor: Theme.of(
|
||||
context)
|
||||
.scaffoldBackgroundColor,
|
||||
),
|
||||
onPressed: controller
|
||||
.requestHistory,
|
||||
child: Text(L10n.of(context)
|
||||
.loadMore),
|
||||
),
|
||||
onPressed:
|
||||
controller.requestHistory,
|
||||
child: Text(L10n.of(context)
|
||||
.loadMore),
|
||||
),
|
||||
)
|
||||
: Container()
|
||||
: i == 0
|
||||
? Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SeenByRow(controller),
|
||||
TypingIndicators(controller),
|
||||
],
|
||||
)
|
||||
: AutoScrollTag(
|
||||
key: ValueKey(controller
|
||||
.filteredEvents[i - 1].eventId),
|
||||
index: i - 1,
|
||||
controller:
|
||||
controller.scrollController,
|
||||
child: Swipeable(
|
||||
)
|
||||
: Container()
|
||||
: i == 0
|
||||
? Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SeenByRow(controller),
|
||||
TypingIndicators(controller),
|
||||
],
|
||||
)
|
||||
: AutoScrollTag(
|
||||
key: ValueKey(controller
|
||||
.filteredEvents[i - 1]
|
||||
.eventId),
|
||||
background: const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 12.0),
|
||||
child: Center(
|
||||
child: Icon(
|
||||
Icons.reply_outlined),
|
||||
index: i - 1,
|
||||
controller:
|
||||
controller.scrollController,
|
||||
child: Swipeable(
|
||||
key: ValueKey(controller
|
||||
.filteredEvents[i - 1]
|
||||
.eventId),
|
||||
background: const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 12.0),
|
||||
child: Center(
|
||||
child: Icon(
|
||||
Icons.reply_outlined),
|
||||
),
|
||||
),
|
||||
),
|
||||
direction:
|
||||
SwipeDirection.endToStart,
|
||||
onSwipe: (direction) =>
|
||||
controller.replyAction(
|
||||
replyTo: controller
|
||||
.filteredEvents[
|
||||
i - 1]),
|
||||
child: Message(
|
||||
controller
|
||||
.filteredEvents[i - 1],
|
||||
onInfoTab:
|
||||
controller.showEventInfo,
|
||||
onAvatarTab: (Event event) =>
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (c) =>
|
||||
UserBottomSheet(
|
||||
user: event.sender,
|
||||
outerContext: context,
|
||||
onMention: () => controller
|
||||
.sendController
|
||||
.text +=
|
||||
'${event.sender.mention} ',
|
||||
direction:
|
||||
SwipeDirection.endToStart,
|
||||
onSwipe: (direction) =>
|
||||
controller.replyAction(
|
||||
replyTo: controller
|
||||
.filteredEvents[
|
||||
i - 1]),
|
||||
child: Message(
|
||||
controller
|
||||
.filteredEvents[i - 1],
|
||||
onInfoTab: controller
|
||||
.showEventInfo,
|
||||
onAvatarTab: (Event event) =>
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (c) =>
|
||||
UserBottomSheet(
|
||||
user: event.sender,
|
||||
outerContext:
|
||||
context,
|
||||
onMention: () => controller
|
||||
.sendController
|
||||
.text +=
|
||||
'${event.sender.mention} ',
|
||||
),
|
||||
),
|
||||
),
|
||||
unfold: controller.unfold,
|
||||
onSelect: controller
|
||||
.onSelectMessage,
|
||||
scrollToEventId: (String eventId) => controller
|
||||
.scrollToEventId(eventId),
|
||||
longPressSelect: controller
|
||||
.selectedEvents.isEmpty,
|
||||
selected: controller.selectedEvents.any((e) =>
|
||||
e.eventId ==
|
||||
controller
|
||||
.filteredEvents[i - 1]
|
||||
.eventId),
|
||||
timeline: controller.timeline,
|
||||
nextEvent: i <
|
||||
controller
|
||||
.filteredEvents
|
||||
.length
|
||||
? controller.filteredEvents[i]
|
||||
: null),
|
||||
),
|
||||
);
|
||||
},
|
||||
childCount: controller.filteredEvents.length + 2,
|
||||
findChildIndexCallback: (key) =>
|
||||
controller.findChildIndexCallback(
|
||||
key, thisEventsKeyMap),
|
||||
unfold: controller.unfold,
|
||||
onSelect: controller
|
||||
.onSelectMessage,
|
||||
scrollToEventId:
|
||||
(String eventId) =>
|
||||
controller.scrollToEventId(
|
||||
eventId),
|
||||
longPressSelect: controller
|
||||
.selectedEvents.isEmpty,
|
||||
selected: controller
|
||||
.selectedEvents
|
||||
.any((e) =>
|
||||
e.eventId ==
|
||||
controller
|
||||
.filteredEvents[i - 1]
|
||||
.eventId),
|
||||
timeline: controller.timeline,
|
||||
nextEvent: i < controller.filteredEvents.length ? controller.filteredEvents[i] : null),
|
||||
),
|
||||
);
|
||||
},
|
||||
childCount:
|
||||
controller.filteredEvents.length + 2,
|
||||
findChildIndexCallback: (key) =>
|
||||
controller.findChildIndexCallback(
|
||||
key, thisEventsKeyMap),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (controller.room.canSendDefaultMessages &&
|
||||
controller.room.membership == Membership.join)
|
||||
Container(
|
||||
margin: EdgeInsets.only(
|
||||
bottom: bottomSheetPadding,
|
||||
left: bottomSheetPadding,
|
||||
right: bottomSheetPadding,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: FluffyThemes.columnWidth * 2.5),
|
||||
alignment: Alignment.center,
|
||||
child: Material(
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft:
|
||||
Radius.circular(AppConfig.borderRadius),
|
||||
bottomRight:
|
||||
Radius.circular(AppConfig.borderRadius),
|
||||
),
|
||||
elevation: 6,
|
||||
shadowColor: Theme.of(context)
|
||||
.secondaryHeaderColor
|
||||
.withAlpha(100),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
color:
|
||||
Theme.of(context).appBarTheme.backgroundColor,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const ConnectionStatusHeader(),
|
||||
ReactionsPicker(controller),
|
||||
ReplyDisplay(controller),
|
||||
ChatInputRow(controller),
|
||||
ChatEmojiPicker(controller),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (controller.room.canSendDefaultMessages &&
|
||||
controller.room.membership == Membership.join)
|
||||
Container(
|
||||
margin: EdgeInsets.only(
|
||||
bottom: bottomSheetPadding,
|
||||
left: bottomSheetPadding,
|
||||
right: bottomSheetPadding,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: FluffyThemes.columnWidth * 2.5),
|
||||
alignment: Alignment.center,
|
||||
child: Material(
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(AppConfig.borderRadius),
|
||||
bottomRight:
|
||||
Radius.circular(AppConfig.borderRadius),
|
||||
),
|
||||
elevation: 6,
|
||||
shadowColor: Theme.of(context)
|
||||
.secondaryHeaderColor
|
||||
.withAlpha(100),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
color: Theme.of(context).appBarTheme.backgroundColor,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const ConnectionStatusHeader(),
|
||||
ReactionsPicker(controller),
|
||||
ReplyDisplay(controller),
|
||||
ChatInputRow(controller),
|
||||
ChatEmojiPicker(controller),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (controller.dragging)
|
||||
Container(
|
||||
color: Theme.of(context)
|
||||
.scaffoldBackgroundColor
|
||||
.withOpacity(0.9),
|
||||
alignment: Alignment.center,
|
||||
child: const Icon(
|
||||
Icons.upload_outlined,
|
||||
size: 100,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
101
lib/pages/chat/send_file_dialog.dart
Normal file
101
lib/pages/chat/send_file_dialog.dart
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import '../../utils/matrix_sdk_extensions.dart/matrix_file_extension.dart';
|
||||
import '../../utils/resize_image.dart';
|
||||
import '../../utils/room_send_file_extension.dart';
|
||||
|
||||
class SendFileDialog extends StatefulWidget {
|
||||
final Room room;
|
||||
final MatrixFile file;
|
||||
|
||||
const SendFileDialog({
|
||||
this.room,
|
||||
this.file,
|
||||
Key key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_SendFileDialogState createState() => _SendFileDialogState();
|
||||
}
|
||||
|
||||
class _SendFileDialogState extends State<SendFileDialog> {
|
||||
bool origImage = false;
|
||||
bool _isSending = false;
|
||||
|
||||
Future<void> _send() async {
|
||||
var file = widget.file;
|
||||
if (file is MatrixImageFile && !origImage) {
|
||||
file = await file.resizeImage(quality: 40, max: 1200);
|
||||
}
|
||||
await widget.room.sendFileEventWithThumbnail(file);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var sendStr = L10n.of(context).sendFile;
|
||||
if (widget.file is MatrixImageFile) {
|
||||
sendStr = L10n.of(context).sendImage;
|
||||
} else if (widget.file is MatrixAudioFile) {
|
||||
sendStr = L10n.of(context).sendAudio;
|
||||
} else if (widget.file is MatrixVideoFile) {
|
||||
sendStr = L10n.of(context).sendVideo;
|
||||
}
|
||||
Widget contentWidget;
|
||||
if (widget.file is MatrixImageFile) {
|
||||
contentWidget = Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
|
||||
Flexible(
|
||||
child: Image.memory(
|
||||
widget.file.bytes,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
Text(widget.file.name),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Checkbox(
|
||||
value: origImage,
|
||||
onChanged: (v) => setState(() => origImage = v),
|
||||
),
|
||||
InkWell(
|
||||
onTap: () => setState(() => origImage = !origImage),
|
||||
child: Text(L10n.of(context).sendOriginal +
|
||||
' (${widget.file.sizeString})'),
|
||||
),
|
||||
],
|
||||
)
|
||||
]);
|
||||
} else {
|
||||
contentWidget = Text('${widget.file.name} (${widget.file.sizeString})');
|
||||
}
|
||||
return AlertDialog(
|
||||
title: Text(sendStr),
|
||||
content: contentWidget,
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
// just close the dialog
|
||||
Navigator.of(context, rootNavigator: false).pop();
|
||||
},
|
||||
child: Text(L10n.of(context).cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: _isSending
|
||||
? null
|
||||
: () async {
|
||||
setState(() {
|
||||
_isSending = true;
|
||||
});
|
||||
await showFutureLoadingDialog(
|
||||
context: context, future: () => _send());
|
||||
Navigator.of(context, rootNavigator: false).pop();
|
||||
},
|
||||
child: Text(L10n.of(context).send),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
146
lib/pages/chat/send_location_dialog.dart
Normal file
146
lib/pages/chat/send_location_dialog.dart
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/pages/chat/events/map_bubble.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
|
||||
class SendLocationDialog extends StatefulWidget {
|
||||
final Room room;
|
||||
|
||||
const SendLocationDialog({
|
||||
this.room,
|
||||
Key key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_SendLocationDialogState createState() => _SendLocationDialogState();
|
||||
}
|
||||
|
||||
class _SendLocationDialogState extends State<SendLocationDialog> {
|
||||
bool disabled = false;
|
||||
bool denied = false;
|
||||
bool isSending = false;
|
||||
Position position;
|
||||
Error error;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
requestLocation();
|
||||
}
|
||||
|
||||
Future<void> requestLocation() async {
|
||||
if (!(await Geolocator.isLocationServiceEnabled())) {
|
||||
setState(() => disabled = true);
|
||||
return;
|
||||
}
|
||||
var permission = await Geolocator.checkPermission();
|
||||
if (permission == LocationPermission.denied) {
|
||||
permission = await Geolocator.requestPermission();
|
||||
if (permission == LocationPermission.denied) {
|
||||
setState(() => denied = true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (permission == LocationPermission.deniedForever) {
|
||||
setState(() => denied = true);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Position _position;
|
||||
try {
|
||||
_position = await Geolocator.getCurrentPosition(
|
||||
desiredAccuracy: LocationAccuracy.best,
|
||||
timeLimit: const Duration(seconds: 30),
|
||||
);
|
||||
} on TimeoutException {
|
||||
_position = await Geolocator.getCurrentPosition(
|
||||
desiredAccuracy: LocationAccuracy.medium,
|
||||
timeLimit: const Duration(seconds: 30),
|
||||
);
|
||||
}
|
||||
setState(() => position = _position);
|
||||
} catch (e) {
|
||||
setState(() => error = e);
|
||||
}
|
||||
}
|
||||
|
||||
void sendAction() async {
|
||||
setState(() => isSending = true);
|
||||
final body =
|
||||
'https://www.openstreetmap.org/?mlat=${position.latitude}&mlon=${position.longitude}#map=16/${position.latitude}/${position.longitude}';
|
||||
final uri =
|
||||
'geo:${position.latitude},${position.longitude};u=${position.accuracy}';
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => widget.room.sendLocation(body, uri),
|
||||
);
|
||||
Navigator.of(context, rootNavigator: false).pop();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget contentWidget;
|
||||
if (position != null) {
|
||||
contentWidget = MapBubble(
|
||||
latitude: position.latitude,
|
||||
longitude: position.longitude,
|
||||
);
|
||||
} else if (disabled) {
|
||||
contentWidget = Text(L10n.of(context).locationDisabledNotice);
|
||||
} else if (denied) {
|
||||
contentWidget = Text(L10n.of(context).locationPermissionDeniedNotice);
|
||||
} else if (error != null) {
|
||||
contentWidget =
|
||||
Text(L10n.of(context).errorObtainingLocation(error.toString()));
|
||||
} else {
|
||||
contentWidget = Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const CupertinoActivityIndicator(),
|
||||
const SizedBox(width: 12),
|
||||
Text(L10n.of(context).obtainingLocation),
|
||||
],
|
||||
);
|
||||
}
|
||||
if (PlatformInfos.isCupertinoStyle) {
|
||||
return CupertinoAlertDialog(
|
||||
title: Text(L10n.of(context).shareLocation),
|
||||
content: contentWidget,
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
onPressed: Navigator.of(context, rootNavigator: false).pop,
|
||||
child: Text(L10n.of(context).cancel),
|
||||
),
|
||||
CupertinoDialogAction(
|
||||
onPressed: isSending ? null : sendAction,
|
||||
child: Text(L10n.of(context).send),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
return AlertDialog(
|
||||
title: Text(L10n.of(context).shareLocation),
|
||||
content: contentWidget,
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Navigator.of(context, rootNavigator: false).pop,
|
||||
child: Text(L10n.of(context).cancel),
|
||||
),
|
||||
if (position != null)
|
||||
TextButton(
|
||||
onPressed: isSending ? null : sendAction,
|
||||
child: Text(L10n.of(context).send),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue