Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added drag and drop feature to the task view #521

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 14 additions & 1 deletion ui/flutter/lib/app/modules/app/controllers/app_controller.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'dart:ui';

import 'package:app_links/app_links.dart';
Expand Down Expand Up @@ -80,6 +81,11 @@ class AppController extends GetxController with WindowListener, TrayListener {
late AppLinks _appLinks;
StreamSubscription<Uri>? _linkSubscription;

//url controller turned *global* for usage in Multiple Views
final urlController = TextEditingController();

final fileDataUri = "".obs;

@override
void onReady() {
super.onReady();
Expand Down Expand Up @@ -274,7 +280,9 @@ class AppController extends GetxController with WindowListener, TrayListener {
}

Future<void> _toCreate(Uri uri) async {
final path = (uri.scheme == "magnet" || uri.scheme == "http" || uri.scheme == "https")
final path = (uri.scheme == "magnet" ||
uri.scheme == "http" ||
uri.scheme == "https")
? uri.toString()
: (await toFile(uri.toString())).path;
await Get.rootDelegate.offAndToNamed(Routes.CREATE, arguments: path);
Expand Down Expand Up @@ -434,4 +442,9 @@ class AppController extends GetxController with WindowListener, TrayListener {
apiToken: startConfig.value.apiToken));
await putConfig(downloaderConfig.value);
}

void setFileDataUri(Uint8List bytes) {
fileDataUri.value =
"data:application/x-bittorrent;base64,${base64.encode(bytes)}";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,6 @@ class CreateController extends GetxController
super.onClose();
}

void setFileDataUri(Uint8List bytes) {
fileDataUri.value =
"data:application/x-bittorrent;base64,${base64.encode(bytes)}";
}

void clearFileDataUri() {
fileDataUri.value = "";
}
Expand Down
71 changes: 39 additions & 32 deletions ui/flutter/lib/app/modules/create/views/create_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import '../controllers/create_controller.dart';
class CreateView extends GetView<CreateController> {
final _confirmFormKey = GlobalKey<FormState>();

final _urlController = TextEditingController();
final _renameController = TextEditingController();
final _connectionsController = TextEditingController();
final _pathController = TextEditingController();
Expand Down Expand Up @@ -54,12 +53,12 @@ class CreateView extends GetView<CreateController> {
}

final String? filePath = Get.rootDelegate.arguments();
if (_urlController.text.isEmpty) {
if (appController.urlController.text.isEmpty) {
if (filePath?.isNotEmpty ?? false) {
// get file path from route arguments
_urlController.text = filePath!;
_urlController.selection = TextSelection.fromPosition(
TextPosition(offset: _urlController.text.length));
appController.urlController.text = filePath!;
appController.urlController.selection = TextSelection.fromPosition(
TextPosition(offset: appController.urlController.text.length));
} else {
// read clipboard
Clipboard.getData('text/plain').then((value) {
Expand All @@ -69,13 +68,14 @@ class CreateView extends GetView<CreateController> {
value!.text!.startsWith(e) ||
value.text!.startsWith(e.toUpperCase()))
.isNotEmpty) {
_urlController.text = value!.text!;
_urlController.selection = TextSelection.fromPosition(
TextPosition(offset: _urlController.text.length));
appController.urlController.text = value!.text!;
appController.urlController.selection =
TextSelection.fromPosition(TextPosition(
offset: appController.urlController.text.length));
return;
}

recognizeMagnetUri(value!.text!);
recognizeMagnetUri(value!.text!, appController.urlController);
}
});
}
Expand All @@ -92,12 +92,12 @@ class CreateView extends GetView<CreateController> {
body: DropTarget(
onDragDone: (details) async {
if (!Util.isWeb()) {
_urlController.text = details.files[0].path;
appController.urlController.text = details.files[0].path;
return;
}
_urlController.text = details.files[0].name;
appController.urlController.text = details.files[0].name;
final bytes = await details.files[0].readAsBytes();
controller.setFileDataUri(bytes);
appController.setFileDataUri(bytes);
},
child: GestureDetector(
behavior: HitTestBehavior.opaque,
Expand All @@ -117,7 +117,7 @@ class CreateView extends GetView<CreateController> {
Expanded(
child: TextFormField(
autofocus: !Util.isMobile(),
controller: _urlController,
controller: appController.urlController,
minLines: 1,
maxLines: 5,
decoration: InputDecoration(
Expand All @@ -127,7 +127,7 @@ class CreateView extends GetView<CreateController> {
icon: const Icon(Icons.link),
suffixIcon: IconButton(
onPressed: () {
_urlController.clear();
appController.urlController.clear();
controller.clearFileDataUri();
},
icon: const Icon(Icons.clear),
Expand All @@ -141,7 +141,8 @@ class CreateView extends GetView<CreateController> {
onChanged: (v) async {
controller.clearFileDataUri();
if (controller.oldUrl.value.isEmpty) {
recognizeMagnetUri(v);
recognizeMagnetUri(
v, appController.urlController);
}
controller.oldUrl.value = v;
},
Expand All @@ -155,11 +156,12 @@ class CreateView extends GetView<CreateController> {
allowedExtensions: ["torrent"]);
if (pr != null) {
if (!Util.isWeb()) {
_urlController.text = pr.files[0].path ?? "";
appController.urlController.text =
pr.files[0].path ?? "";
return;
}
_urlController.text = pr.files[0].name;
controller.setFileDataUri(pr.files[0].bytes!);
appController.urlController.text = pr.files[0].name;
appController.setFileDataUri(pr.files[0].bytes!);
}
},
),
Expand All @@ -185,7 +187,7 @@ class CreateView extends GetView<CreateController> {
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
_urlController.text =
appController.urlController.text =
resultOfHistories[index];
Navigator.pop(context);
},
Expand Down Expand Up @@ -360,7 +362,8 @@ class CreateView extends GetView<CreateController> {
width: 150,
child: RoundedLoadingButton(
color: Get.theme.colorScheme.secondary,
onPressed: _doConfirm,
onPressed: futureToVoidConfirm(
appController.urlController),
controller: _confirmController,
child: Text('confirm'.tr),
),
Expand All @@ -380,20 +383,24 @@ class CreateView extends GetView<CreateController> {
}

// recognize magnet uri, if length == 40, auto add magnet prefix
recognizeMagnetUri(String text) {
recognizeMagnetUri(String text, TextEditingController urlController) {
if (text.length != 40) {
return;
}
final exp = RegExp(r"[0-9a-fA-F]+");
if (exp.hasMatch(text)) {
final uri = "magnet:?xt=urn:btih:$text";
_urlController.text = uri;
_urlController.selection = TextSelection.fromPosition(
TextPosition(offset: _urlController.text.length));
urlController.text = uri;
urlController.selection = TextSelection.fromPosition(
TextPosition(offset: urlController.text.length));
}
}

Future<void> _doConfirm() async {
futureToVoidConfirm(TextEditingController urlController) {
_doConfirm(urlController);
}

Future<void> _doConfirm(TextEditingController urlController) async {
if (controller.isConfirming.value) {
return;
}
Expand All @@ -403,9 +410,8 @@ class CreateView extends GetView<CreateController> {
if (_confirmFormKey.currentState!.validate()) {
final isWebFileChosen =
Util.isWeb() && controller.fileDataUri.isNotEmpty;
final submitUrl = isWebFileChosen
? controller.fileDataUri.value
: _urlController.text;
final submitUrl =
isWebFileChosen ? controller.fileDataUri.value : urlController.text;

final urls = Util.textToLines(submitUrl);
// Add url to the history
Expand All @@ -425,7 +431,8 @@ class CreateView extends GetView<CreateController> {
if (isDirect) {
await Future.wait(urls.map((url) {
return createTask(CreateTask(
req: Request(url: url, extra: parseReqExtra(url)),
req:
Request(url: url, extra: parseReqExtra(url, urlController)),
opt: Options(
name: isMultiLine ? "" : _renameController.text,
path: _pathController.text,
Expand All @@ -436,7 +443,7 @@ class CreateView extends GetView<CreateController> {
} else {
final rr = await resolve(Request(
url: submitUrl,
extra: parseReqExtra(_urlController.text),
extra: parseReqExtra(urlController.text, urlController),
));
await _showResolveDialog(rr);
}
Expand All @@ -449,10 +456,10 @@ class CreateView extends GetView<CreateController> {
}
}

Object? parseReqExtra(String url) {
Object? parseReqExtra(String url, TextEditingController urlController) {
Object? reqExtra;
if (controller.showAdvanced.value) {
final u = Uri.parse(_urlController.text);
final u = Uri.parse(urlController.text);
if (u.scheme.startsWith("http")) {
reqExtra = ReqExtraHttp()
..header = {
Expand Down
103 changes: 63 additions & 40 deletions ui/flutter/lib/app/modules/task/views/task_view.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import 'package:desktop_drop/desktop_drop.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:gopeed/util/util.dart';

import '../../../routes/app_pages.dart';
import '../../app/controllers/app_controller.dart';
import '../controllers/task_controller.dart';
import '../controllers/task_downloaded_controller.dart';
import '../controllers/task_downloading_controller.dart';
Expand All @@ -12,49 +16,68 @@ class TaskView extends GetView<TaskController> {

@override
Widget build(BuildContext context) {
final appController = Get.find<AppController>();

return DefaultTabController(
length: 2,
child: Scaffold(
appBar: PreferredSize(
preferredSize: const Size.fromHeight(56),
child: AppBar(
bottom: TabBar(
tabs: const [
Tab(
icon: Icon(Icons.file_download),
),
Tab(
icon: Icon(Icons.done),
length: 2,
child: Scaffold(
appBar: PreferredSize(
preferredSize: const Size.fromHeight(56),
child: AppBar(
bottom: TabBar(
tabs: const [
Tab(
icon: Icon(Icons.file_download),
),
Tab(
icon: Icon(Icons.done),
),
],
onTap: (index) {
if (controller.tabIndex.value != index) {
controller.tabIndex.value = index;
final downloadingController =
Get.find<TaskDownloadingController>();
final downloadedController =
Get.find<TaskDownloadedController>();
switch (index) {
case 0:
downloadingController.start();
downloadedController.stop();
break;
case 1:
downloadingController.stop();
downloadedController.start();
break;
}
}
},
),
],
onTap: (index) {
if (controller.tabIndex.value != index) {
controller.tabIndex.value = index;
final downloadingController =
Get.find<TaskDownloadingController>();
final downloadedController =
Get.find<TaskDownloadedController>();
switch (index) {
case 0:
downloadingController.start();
downloadedController.stop();
break;
case 1:
downloadingController.stop();
downloadedController.start();
break;
}
}
)),
body: DropTarget(
onDragDone: (details) async {
if (!Util.isWeb()) {
appController.urlController.text = details.files[0].path;
Get.rootDelegate.toNamed(Routes.CREATE);
return;
}
appController.urlController.text = details.files[0].name;
final bytes = await details.files[0].readAsBytes();
appController.setFileDataUri(bytes);
Get.rootDelegate.toNamed(Routes.CREATE);
},
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
},
child: const TabBarView(
children: [
TaskDownloadingView(),
TaskDownloadedView(),
],
),
),
)),
body: const TabBarView(
children: [
TaskDownloadingView(),
TaskDownloadedView(),
],
),
),
);
)));
}
}