diff --git a/lib/constants.dart b/lib/constants.dart index 281db6f..542cad3 100644 --- a/lib/constants.dart +++ b/lib/constants.dart @@ -48,7 +48,7 @@ enum Format { '"18/22/bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best"', this.format = 'MP4', this.ffmpegCmd = '', - this.extension = '', + this.extension = 'mp4', }); } diff --git a/lib/dlForm/cubit/dl_form_cubit.dart b/lib/dlForm/cubit/dl_form_cubit.dart index 8ce0e71..0f32d70 100644 --- a/lib/dlForm/cubit/dl_form_cubit.dart +++ b/lib/dlForm/cubit/dl_form_cubit.dart @@ -2,7 +2,6 @@ import 'dart:convert'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; -import 'package:flutter/material.dart'; import 'package:notube/constants.dart'; import 'package:notube/models/video.dart'; import 'package:notube/services/download.dart'; @@ -29,6 +28,9 @@ class DlFormCubit extends Cubit { void parseUrl() async { dlService = await DLServices.init(); + if (state.url.isEmpty) { + return; + } var shellStream = await dlService.analyseUrl(state.url); shellStream.listen((line) { diff --git a/lib/dlForm/formComponents/format_dropdown.dart b/lib/dlForm/formComponents/format_dropdown.dart index 1bf1a27..3b4adfa 100644 --- a/lib/dlForm/formComponents/format_dropdown.dart +++ b/lib/dlForm/formComponents/format_dropdown.dart @@ -25,7 +25,7 @@ class DropdownFormat extends StatelessWidget { onChanged: (String? value) { var format = Format.values.firstWhere((element) => element.format == value); - context.read().setFormat(format!); + context.read().setFormat(format); }, underline: Container(), items: Format.values.map>((Format format) { diff --git a/lib/dlForm/formComponents/submit_button.dart b/lib/dlForm/formComponents/submit_button.dart index 4ca5fd2..d2bd990 100644 --- a/lib/dlForm/formComponents/submit_button.dart +++ b/lib/dlForm/formComponents/submit_button.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter/rendering.dart'; import 'package:notube/constants.dart'; import 'package:notube/models/video.dart'; import 'package:notube/videoList/cubit/videos_cubit.dart'; @@ -35,23 +37,27 @@ class _SubmitButtonState extends State { constraints: BoxConstraints.tightFor(width: 200), child: MouseRegion( cursor: SystemMouseCursors.click, + onHover: (PointerHoverEvent event) { + setState(() => isHovering = true); + }, + onExit: (PointerExitEvent event) { + setState(() => isHovering = false); + }, child: GestureDetector( - onTap: () { - context.read().parseUrl(); - }, - child: InkWell( - onHover: (hovering) { - debugPrint('Hovering: $hovering'); - setState(() => isHovering = hovering); - }, - child: Container( - padding: const EdgeInsets.symmetric(vertical: 14), - decoration: BoxDecoration( - color: isHovering ? colorDarkRed : colorMainRed, - ), - child: const Text('Ok', textAlign: TextAlign.center).tr(), - ), - ))), + onTap: () { + context.read().parseUrl(); + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 14), + decoration: BoxDecoration( + color: isHovering ? colorDarkRed : colorMainRed, + borderRadius: BorderRadius.only( + topRight: Radius.circular(3), + bottomRight: Radius.circular(3), + )), + child: const Text('Ok', textAlign: TextAlign.center).tr(), + ), + )), )); } } diff --git a/lib/dlForm/formComponents/url_text_field.dart b/lib/dlForm/formComponents/url_text_field.dart index 888f64f..9913610 100644 --- a/lib/dlForm/formComponents/url_text_field.dart +++ b/lib/dlForm/formComponents/url_text_field.dart @@ -11,25 +11,34 @@ class UrlTextField extends StatelessWidget { @override Widget build(BuildContext context) { return Flexible( - child: TextFormField( - decoration: InputDecoration( - border: InputBorder.none, - hintText: 'search_url'.tr(), - filled: true, - fillColor: colorBackgroundBlack, - contentPadding: EdgeInsets.symmetric( - horizontal: 20, + child: Container( + decoration: BoxDecoration( + color: colorBackgroundBlack, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(3), + bottomLeft: Radius.circular(3), ), ), - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter some text'; - } - return null; - }, - onChanged: (newUrl) { - context.read().setUrl(newUrl); - }, + child: TextFormField( + decoration: InputDecoration( + border: InputBorder.none, + hintText: 'search_url'.tr(), + filled: true, + fillColor: Colors.transparent, + contentPadding: EdgeInsets.symmetric( + horizontal: 20, + ), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter some text'; + } + return null; + }, + onChanged: (newUrl) { + context.read().setUrl(newUrl); + }, + ), ), ); } diff --git a/lib/main.dart b/lib/main.dart index 372446d..39689fe 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:notube/screens/settings.dart'; import 'package:notube/wrapper.dart'; import 'package:notube/constants.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:window_manager/window_manager.dart'; import 'package:upgrader/upgrader.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -63,9 +65,13 @@ class _NoTubeAppState extends State { localizationsDelegates: context.localizationDelegates, supportedLocales: context.supportedLocales, locale: context.locale, - home: BlocProvider( - create: (context) => VideosCubit(), - child: UpgradeAlert(upgrader: upgrader, child: const Wrapper())), + initialRoute: '/', + routes: { + '/': (context) => BlocProvider( + create: (context) => VideosCubit(), + child: UpgradeAlert(upgrader: upgrader, child: const Wrapper())), + '/settings': (context) => Settings(), + }, ); } } diff --git a/lib/screens/home.dart b/lib/screens/home.dart index f66e2fd..180019d 100644 --- a/lib/screens/home.dart +++ b/lib/screens/home.dart @@ -12,6 +12,16 @@ class Home extends StatelessWidget { return Scaffold( appBar: AppBar( title: Text('home').tr(), + actions: [ + SizedBox( + width: 50, + child: IconButton( + icon: Icon(Icons.settings), + onPressed: () { + Navigator.pushNamed(context, '/settings'); + }, + )) + ], ), body: LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) { diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart new file mode 100644 index 0000000..f7d4d3b --- /dev/null +++ b/lib/screens/settings.dart @@ -0,0 +1,115 @@ +import 'dart:io'; + +import 'package:downloadsfolder/downloadsfolder.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:filesystem_picker/filesystem_picker.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class Settings extends StatefulWidget { + Settings({super.key}); + + @override + State createState() => _SettingsState(); +} + +class _SettingsState extends State { + late Directory downloadFolder; + late SharedPreferences prefs; + + Future _init() async { + prefs = await SharedPreferences.getInstance(); + Directory defaultDownloadDirectory = await getDownloadDirectory(); + downloadFolder = Directory( + prefs.getString('downloadFolder') ?? defaultDownloadDirectory.path); + return 'ok'; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('settings').tr(), + ), + body: FutureBuilder( + future: _init(), + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return CircularProgressIndicator(); + } else if (snapshot.hasError) { + return Center(child: Text('Error: ${snapshot.error}')); + } else if (snapshot.hasData) { + return LayoutBuilder(builder: + (BuildContext context, BoxConstraints viewportConstraints) { + return viewportConstraints.maxHeight < double.infinity + ? Center( + child: Container( + constraints: viewportConstraints, + child: Column( + children: [ + SizedBox(height: 10), + Text('settings').tr(), + SizedBox(height: 10), + Flexible( + child: ListView( + children: [ + ListTile( + title: Text('download_folder').tr(), + subtitle: Text(downloadFolder.path), + trailing: IconButton( + icon: Icon(Icons.folder), + onPressed: () async { + String? selectedDirectory = + await FilePicker.platform + .getDirectoryPath(); + + if (selectedDirectory != null) { + prefs.setString('downloadFolder', + selectedDirectory); + setState(() { + downloadFolder = + Directory(selectedDirectory); + }); + } + }, + ), + ), + ListTile( + title: Text('language').tr(), + subtitle: + Text(context.locale.toString()).tr(), + trailing: DropdownButton( + items: context.supportedLocales + .map((locale) { + return DropdownMenuItem( + value: locale.toString(), + child: Text(locale.toString()).tr(), + ); + }).toList(), + value: context.locale.toString(), + onChanged: (String? value) => { + if (value != null) + { + context.setLocale(Locale( + value.split("_")[0], + value.split("_")[1])), + } + }, + ), + ) + ], + )), + ], + ), + ), + ) + : CircularProgressIndicator(); + }); + } else { + return Center(child: Text('No data')); + } + }, + )); + } +} diff --git a/lib/translations/en-US.json b/lib/translations/en-US.json index 811d59e..ace7174 100644 --- a/lib/translations/en-US.json +++ b/lib/translations/en-US.json @@ -2,5 +2,10 @@ "test": "EN_test", "home": "EN_home", "home_intro": "EN_home_intro", - "search_url": "EN_search_url" + "search_url": "EN_search_url", + "settings": "EN_Settings", + "download_folder": "Download folder", + "en_US": "English", + "fr_FR": "French", + "Ok": "OK" } \ No newline at end of file diff --git a/lib/translations/fr-FR.json b/lib/translations/fr-FR.json index ab26c04..b0d82df 100644 --- a/lib/translations/fr-FR.json +++ b/lib/translations/fr-FR.json @@ -2,5 +2,10 @@ "test": "Test", "home": "Accueil", "home_intro": "Saisissez l'url d'une video et laissez la magie opérer!", - "search_url": "Url" + "search_url": "Url", + "settings": "Paramètres", + "download_folder": "Dossier de téléchargement", + "en_US": "Anglais", + "fr_FR": "Français", + "Ok": "OK" } \ No newline at end of file diff --git a/lib/videoList/cubit/videos_cubit.dart b/lib/videoList/cubit/videos_cubit.dart index ea4c175..eaf2e4a 100644 --- a/lib/videoList/cubit/videos_cubit.dart +++ b/lib/videoList/cubit/videos_cubit.dart @@ -92,8 +92,18 @@ class VideosCubit extends Cubit { File('temp/${video.filename}_done.${video.format.extension}'); final newFile = File( - '${downloadDirectory.path}/${cleanTitle}.${video.format.extension}'); - await tmpFile.rename(newFile.path); + '${downloadDirectory.path}/$cleanTitle.${video.format.extension}'); + + var isMoved = false; + while (!isMoved) { + try { + await tmpFile.rename(newFile.path); + isMoved = true; + } on FileSystemException catch (e) { + debugPrint('Error moving file: $e'); + await Future.delayed(Duration(seconds: 1)); + } + } changeStatus(video, 'done'); debugPrint('File moved to ${newFile.path}'); } diff --git a/pubspec.lock b/pubspec.lock index 17eab51..05c85f8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -57,6 +57,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32" + url: "https://pub.dev" + source: hosted + version: "0.3.4+1" crypto: dependency: transitive description: @@ -169,6 +177,22 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + file_picker: + dependency: "direct main" + description: + name: file_picker + sha256: "824f5b9f389bfc4dddac3dea76cd70c51092d9dff0b2ece7ef4f53db8547d258" + url: "https://pub.dev" + source: hosted + version: "8.0.6" + filesystem_picker: + dependency: "direct main" + description: + name: filesystem_picker + sha256: cc2bfe7e5a4ce21afd5b1b03824c0e6e5386a981ed6cce7bda062b1af805cf62 + url: "https://pub.dev" + source: hosted + version: "4.1.0" flutter: dependency: "direct main" description: flutter @@ -203,6 +227,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: c6b0b4c05c458e1c01ad9bcc14041dd7b1f6783d487be4386f793f47a8a4d03e + url: "https://pub.dev" + source: hosted + version: "2.0.20" flutter_redux: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index c688415..21194a0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,6 +30,8 @@ dependencies: bloc: ^8.1.4 shared_preferences: ^2.2.3 downloadsfolder: ^1.1.0 + filesystem_picker: ^4.1.0 + file_picker: ^8.0.6 dev_dependencies: flutter_test: