Skip to content

Commit

Permalink
Add a drop zone on the user profile picture dialog for users to drop …
Browse files Browse the repository at this point in the history
…images
  • Loading branch information
JamesChenX committed Jan 13, 2025
1 parent d3c80a7 commit 87499fe
Show file tree
Hide file tree
Showing 4 changed files with 268 additions and 187 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export 't_dialog/t_dialog.dart';
export 't_divider/t_horizontal_divider.dart';
export 't_divider/t_vertical_divider.dart';
export 't_drawer/t_drawer.dart';
export 't_drop_zone/t_drop_zone.dart';
export 't_empty/t_empty.dart';
export 't_empty/t_empty_result.dart';
export 't_floating/t_floating.dart';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import 'dart:async';

import 'package:dotted_border/dotted_border.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:super_clipboard/super_clipboard.dart';
import 'package:super_drag_and_drop/super_drag_and_drop.dart';

import '../../../l10n/app_localizations.dart';
import '../../../l10n/view_models/app_localizations_view_model.dart';
import '../../../themes/app_theme_extension.dart';
import '../../../themes/sizes.dart';

class TDropZone extends ConsumerStatefulWidget {
const TDropZone(
{super.key,
required this.formats,
this.onDropOver,
required this.onPerformDrop,
this.onDropEnter,
this.onDropLeave,
this.onDropEnded,
required this.child});

final List<DataFormat> formats;
final FutureOr<DropOperation> Function(DropOverEvent)? onDropOver;
final Future<void> Function(PerformDropEvent) onPerformDrop;
final void Function(DropEvent)? onDropEnter;
final void Function(DropEvent)? onDropLeave;
final void Function(DropEvent)? onDropEnded;
final Widget child;

@override
ConsumerState createState() => _TDropZoneState();
}

class _TDropZoneState extends ConsumerState<TDropZone> {
bool _dragging = false;

@override
Widget build(BuildContext context) {
final theme = context.theme;
final appLocalizations = ref.watch(appLocalizationsViewModel);
return Stack(
children: [
DropRegion(
formats: widget.formats,
onDropOver: widget.onDropOver ?? _onDropOver,
onDropEnter: _onDropEnter,
onDropLeave: _onDropLeave,
onPerformDrop: widget.onPerformDrop,
child: Padding(
padding: Sizes.paddingV8H16,
child: widget.child,
)),
_buildMask(theme, appLocalizations)
],
);
}

IgnorePointer _buildMask(ThemeData theme, AppLocalizations localizations) =>
// Ignore pointer to not obstruct "DropRegion"
IgnorePointer(
child: AnimatedOpacity(
opacity: _dragging ? 1.0 : 0.0,
duration: const Duration(milliseconds: 100),
child: Padding(
padding: const EdgeInsets.only(right: 4, bottom: 4),
child: DottedBorder(
borderType: BorderType.RRect,
dashPattern: [12, 10],
color: theme.primaryColor,
strokeWidth: 2,
radius: const Radius.circular(8),
child: ClipRRect(
child: ColoredBox(
color: Colors.white.withValues(alpha: 0.6),
child: Center(
child: Text(
localizations.dropFilesHere,
style: TextStyle(color: theme.primaryColor),
),
),
),
),
),
)));

DropOperation _onDropOver(DropOverEvent event) =>
event.session.allowedOperations.contains(DropOperation.copy)
? DropOperation.copy
: DropOperation.none;

void _onDropEnter(DropEvent event) {
_dragging = true;
widget.onDropEnter?.call(event);
setState(() {});
}

void _onDropLeave(DropEvent event) {
_dragging = false;
widget.onDropLeave?.call(event);
setState(() {});
}
}

extension DropSessionExtensions on DropSession {
Future<List<DataReaderFile>> readFiles(
{bool includeDirectories = false}) async {
final futures = items.map((item) {
final completer = Completer<DataReaderFile>();
try {
item.dataReader!.getFile(null, completer.complete,
onError: completer.completeError);
} catch (e) {
completer.completeError(e);
}
return completer.future;
});
final files = await Future.wait(futures);
if (includeDirectories) {
return files;
}
return files
.where((file) =>
// The size of directory is null
file.fileSize != null)
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import 'dart:async';
import 'dart:io';

import 'package:dotted_border/dotted_border.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
Expand All @@ -22,7 +21,6 @@ import '../../../../../../../infra/random/random_utils.dart';
import '../../../../../../l10n/app_localizations.dart';
import '../../../../../../l10n/view_models/app_localizations_view_model.dart';
import '../../../../../../themes/app_theme_extension.dart';
import '../../../../../../themes/sizes.dart';
import '../../../../../components/giphy/client/models/gif.dart';
import '../../../../../components/index.dart';
import '../../view_models/selected_conversation_view_model.dart';
Expand All @@ -42,7 +40,6 @@ class ChatSessionPaneFooter extends ConsumerStatefulWidget {

class _ChatSessionPaneFooterState extends ConsumerState<ChatSessionPaneFooter> {
final List<DataReaderFile> _localFiles = [];
bool _dragging = false;

late EmojiTextEditingController _editorController;
late FocusNode _editorFocusNode;
Expand Down Expand Up @@ -118,10 +115,6 @@ class _ChatSessionPaneFooterState extends ConsumerState<ChatSessionPaneFooter> {
bool _tryAddNewFile(List<DataReaderFile> newFiles) {
var hasNewFile = false;
for (final newFile in newFiles) {
// Exclude directory (The size of directory is null).
if (newFile.fileSize == null) {
continue;
}
if (!_localFiles.any((existingFile) =>
existingFile.fileName == newFile.fileName &&
existingFile.fileSize == newFile.fileSize)) {
Expand All @@ -132,32 +125,8 @@ class _ChatSessionPaneFooterState extends ConsumerState<ChatSessionPaneFooter> {
return hasNewFile;
}

void _onDropEnter() {
_dragging = true;
setState(() {});
}

void _onDropLeave() {
_dragging = false;
setState(() {});
}

DropOperation _onDropOver(DropOverEvent event) {
if (event.session.allowedOperations.contains(DropOperation.copy)) {
return DropOperation.copy;
} else {
return DropOperation.none;
}
}

Future<void> _onPerformDrop(PerformDropEvent event) async {
final futures = event.session.items.map((item) {
final completer = Completer<DataReaderFile>();
item.dataReader!
.getFile(null, completer.complete, onError: completer.completeError);
return completer.future;
});
final newFiles = await Future.wait(futures);
final newFiles = await event.session.readFiles();
if (_tryAddNewFile(newFiles)) {
setState(() {});
}
Expand Down Expand Up @@ -295,24 +264,10 @@ class _ChatSessionPaneFooterState extends ConsumerState<ChatSessionPaneFooter> {
extension _ChatSessionPaneFooterView on _ChatSessionPaneFooterState {
Widget _buildView(ThemeData theme, AppThemeExtension appThemeExtension,
AppLocalizations appLocalizations) =>
Stack(
children: [
DropRegion(
formats: Formats.standardFormats,
onDropOver: _onDropOver,
onDropEnter: (event) {
_onDropEnter();
},
onDropLeave: (event) {
_onDropLeave();
},
onPerformDrop: _onPerformDrop,
child: Padding(
padding: Sizes.paddingV8H16,
child: _buildEditor(theme, appThemeExtension, appLocalizations),
)),
_buildDropZoneMask(theme, appLocalizations)
],
TDropZone(
formats: Formats.standardFormats,
onPerformDrop: _onPerformDrop,
child: _buildEditor(theme, appThemeExtension, appLocalizations),
);

Widget _buildEditor(ThemeData theme, AppThemeExtension appThemeExtension,
Expand Down Expand Up @@ -424,33 +379,4 @@ extension _ChatSessionPaneFooterView on _ChatSessionPaneFooterState {
),
],
);

IgnorePointer _buildDropZoneMask(
ThemeData theme, AppLocalizations localizations) =>
// Ignore pointer to not obstruct "DropRegion"
IgnorePointer(
child: AnimatedOpacity(
opacity: _dragging ? 1.0 : 0.0,
duration: const Duration(milliseconds: 100),
child: Padding(
padding: const EdgeInsets.only(right: 4, bottom: 4),
child: DottedBorder(
borderType: BorderType.RRect,
dashPattern: [12, 10],
color: theme.primaryColor,
strokeWidth: 2,
radius: const Radius.circular(8),
child: ClipRRect(
child: ColoredBox(
color: Colors.white.withValues(alpha: 0.6),
child: Center(
child: Text(
localizations.dropFilesHere,
style: TextStyle(color: theme.primaryColor),
),
),
),
),
),
)));
}
Loading

0 comments on commit 87499fe

Please sign in to comment.