diff --git a/android/app/build.gradle b/android/app/build.gradle
index 19cf245..9a8b448 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -28,6 +28,8 @@ apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion flutter.compileSdkVersion
+ compileSdkVersion 33
+
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index f5aa7ca..ec5490a 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -7,6 +7,7 @@
+
diff --git a/lib/main.dart b/lib/main.dart
index a320819..3d0a82e 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
+import 'package:flutter_phoenix/flutter_phoenix.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:get/get.dart';
import 'package:hive_flutter/adapters.dart';
@@ -17,24 +18,10 @@ import 'pages/splash/splash_screen.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
+ await initializeHive();
+ await initializeGetX();
- // Initialize Hive and Hive Flutter
- await Hive.initFlutter();
- registerAdapters();
- Hive.openBox('user');
- Hive.openBox('collection');
- Hive.openBox('watchlist');
- Hive.openBox('history');
- Hive.openBox('artists');
-
- // Initialize the controllers
- Get.put(MainController());
- Get.put(HomeDataController());
- Get.put(SearchBarController());
- Get.put(ProfileController());
- Get.put(CacheData());
-
- runApp(const ProviderScope(child: App()));
+ runApp(ProviderScope(child: Phoenix(child: const App())));
}
class App extends StatelessWidget {
@@ -54,3 +41,24 @@ class App extends StatelessWidget {
);
}
}
+
+Future? initializeHive() async {
+ // Initialize Hive and Hive Flutter
+ await Hive.initFlutter();
+ registerAdapters();
+ Hive.openBox('user');
+ Hive.openBox('collection');
+ Hive.openBox('watchlist');
+ Hive.openBox('history');
+ Hive.openBox('artists');
+}
+
+Future? initializeGetX() {
+ // Initialize the controllers
+ Get.put(MainController());
+ Get.put(HomeDataController());
+ Get.put(SearchBarController());
+ Get.put(ProfileController());
+ Get.put(CacheData());
+ return null;
+}
diff --git a/lib/models/hive/models/user.dart b/lib/models/hive/models/user.dart
index 0bf2379..351fb78 100644
--- a/lib/models/hive/models/user.dart
+++ b/lib/models/hive/models/user.dart
@@ -13,4 +13,8 @@ class HiveUser extends HiveObject {
late String username;
@HiveField(UserFields.imageUrl)
late String imageUrl;
+
+ // factory HiveUser.fromJson(Map json) {
+ // return HiveUser..name = json['name']..username = json['username']..imageUrl = json['imageUrl'];
+ // }
}
diff --git a/lib/models/show_models/show_preview_model.dart b/lib/models/show_models/show_preview_model.dart
index 2e4e818..1bb8e26 100644
--- a/lib/models/show_models/show_preview_model.dart
+++ b/lib/models/show_models/show_preview_model.dart
@@ -98,6 +98,22 @@ class ShowPreview {
domestic: json['domestic'] ?? "",
foreignLifetimeGross: json['foreignLifetimeGross'] ?? "",
foreign: json['foreign'] ?? "",
+ companies: json['companies'] ?? "",
+ contentRating: json['contentRating'] ?? "",
+ countries: json['countries'] ?? "",
+ genres: json['genres'] ?? "",
+ languages: json['languages'] ?? "",
+ similars: json['similars'] == null
+ ? null
+ : [
+ for (var similar in json['similars']!)
+ ShowPreview.fromJson(similar)
+ ],
+ watchDate:
+ json["watchDate"] == null ? null : DateTime.parse(json["watchDate"]),
+ watchTime: json['watchTime'] == null
+ ? null
+ : TimeOfDay.fromDateTime(DateTime.parse(json["watchDate"])),
);
}
@@ -105,10 +121,26 @@ class ShowPreview {
'id': show.id,
'rank': show.rank,
'title': show.title,
+ 'type': show.type,
'crew': show.crew,
'image': show.image,
'year': show.year,
'imDbRating': show.imDbRating,
+ 'imDbVotes': show.imDbVotes,
+ 'released': show.released,
+ 'seasonNumber': show.seasonNumber,
+ 'episodeNumber': show.episodeNumber,
+ 'plot': show.plot,
+ 'genres': show.genres,
+ 'countries': show.countries,
+ 'languages': show.languages,
+ 'companies': show.companies,
+ 'contentRating': show.contentRating,
+ 'watchDate': show.watchDate?.toString(),
+ 'watchTime': show.watchTime?.toString(),
+ 'similars': [
+ for (var similar in show.similars!) ShowPreview.toMap(similar)
+ ]
};
static String encode(List shows) => json.encode(
diff --git a/lib/modules/Recommender/Recommender.dart b/lib/modules/Recommender/Recommender.dart
index 55bf31f..a307c7e 100644
--- a/lib/modules/Recommender/Recommender.dart
+++ b/lib/modules/Recommender/Recommender.dart
@@ -8,6 +8,7 @@ Future recommender() async {
PreferencesShareholder preferencesShareholder = PreferencesShareholder();
HomeDataController homeDataController = Get.find();
List> allLists = await preferencesShareholder.getAllLists();
+ print(allLists);
List allSimilars = await getAllSimilars(allLists: allLists);
Map allSimilarsStats = {};
int i = 0;
diff --git a/lib/modules/preferences/backup.dart b/lib/modules/preferences/backup.dart
new file mode 100644
index 0000000..4b72bf8
--- /dev/null
+++ b/lib/modules/preferences/backup.dart
@@ -0,0 +1,185 @@
+import 'dart:convert';
+import 'dart:io';
+import 'package:external_path/external_path.dart';
+import 'package:file_picker/file_picker.dart';
+import 'package:flutter/foundation.dart';
+import 'package:hive_flutter/hive_flutter.dart';
+import 'package:movielab/models/hive/models/show_preview.dart';
+import 'package:movielab/models/hive/models/user.dart';
+import 'package:movielab/models/show_models/show_preview_model.dart';
+import 'package:movielab/models/user_model/user_model.dart';
+import 'package:movielab/modules/preferences/preferences_shareholder.dart';
+import 'package:movielab/modules/recommender/recommender.dart';
+import 'package:movielab/pages/splash/get_user_data.dart';
+import 'package:permission_handler/permission_handler.dart';
+
+Future createBackup() async {
+ try {
+ Map userData = {};
+
+ PreferencesShareholder preferencesShareholder = PreferencesShareholder();
+ User user = await preferencesShareholder.getUser();
+ userData['personal'] = {
+ "name": user.name,
+ "username": user.username,
+ "imageUrl": user.imageUrl
+ };
+
+ List watchlist =
+ await preferencesShareholder.getList(listName: "watchlist");
+ userData['watchlist'] = [
+ for (ShowPreview show in watchlist) ShowPreview.toMap(show)
+ ];
+ List history =
+ await preferencesShareholder.getList(listName: "history");
+ userData['history'] = [
+ for (ShowPreview show in history) ShowPreview.toMap(show)
+ ];
+ List collection =
+ await preferencesShareholder.getList(listName: "collection");
+ userData['collection'] = [
+ for (ShowPreview show in collection) ShowPreview.toMap(show)
+ ];
+ List artists =
+ await preferencesShareholder.getList(listName: "artists");
+ userData['artists'] = [
+ for (ShowPreview show in artists) ShowPreview.toMap(show)
+ ];
+ String jsonData = jsonEncode(userData);
+ await Permission.storage.request();
+ String formattedDate = DateTime.now()
+ .toString()
+ .replaceAll('.', '-')
+ .replaceAll(' ', '-')
+ .replaceAll(':', '-');
+ String? dir = await FilePicker.platform.getDirectoryPath();
+ String path = '$dir/MovieLab-backup-$formattedDate.db';
+ File backupFile = File(path);
+ await backupFile.writeAsString(jsonData);
+ return true;
+ } catch (e) {
+ return false;
+ }
+}
+
+Future restoreBackup() async {
+ PreferencesShareholder shareholder = PreferencesShareholder();
+ FilePickerResult? file = await FilePicker.platform.pickFiles(
+ allowMultiple: false,
+ initialDirectory: await ExternalPath.getExternalStoragePublicDirectory(
+ ExternalPath.DIRECTORY_DOCUMENTS),
+ dialogTitle: "MovieLab backup file");
+ bool success = false;
+ if (file != null) {
+ File files = File(file.files.single.path.toString());
+
+ await backupFileTester(files).then((value) async {
+ if (value) {
+ Map backup = jsonDecode(await files.readAsString());
+ Box user = Hive.box('user');
+ Box watchlist = Hive.box('watchlist');
+ Box history = Hive.box('history');
+ Box collection =
+ Hive.box('collection');
+ Box artists = Hive.box('artists');
+
+ user.deleteAt(0);
+ shareholder.deleteList("watchlist");
+ shareholder.deleteList("history");
+ shareholder.deleteList("collection");
+ shareholder.deleteList("artists");
+
+ // print("history: ${history.length}");
+ // for (int i = 0; i <= history.length + 1; i++) {
+ // print("history $i deliting");
+ // history.delete(i);
+ // print("done");
+ // }
+ // print(collection.length);
+ // for (int i = 0; i <= collection.length + 1; i++) {
+ // collection.delete(i);
+ // }
+ // print(artists.length);
+ // for (int i = 0; i <= artists.length; i++) {
+ // artists.delete(i);
+ // }
+ user.put(
+ 0,
+ HiveUser()
+ ..name = backup['personal']['name']
+ ..username = backup['personal']['username']
+ ..imageUrl = backup['personal']['imageUrl']);
+
+ for (var item in backup['watchlist']) {
+ shareholder.addShowToList(
+ listName: 'watchlist',
+ showPreview: ShowPreview.fromJson(item),
+ genres: item['genres'],
+ countries: item['countries'],
+ languages: item['languages'],
+ companies: item['companies'],
+ contentRating: item['contentRating'],
+ similars: [
+ for (Map show in item['similars'])
+ ShowPreview.fromJson(show)
+ ],
+ );
+ }
+
+ for (var item in backup['history']) {
+ shareholder.addShowToList(
+ listName: 'history',
+ showPreview: ShowPreview.fromJson(item),
+ genres: item['genres'],
+ countries: item['countries'],
+ languages: item['languages'],
+ companies: item['companies'],
+ contentRating: item['contentRating'],
+ similars: [
+ for (Map show in item['similars'])
+ ShowPreview.fromJson(show)
+ ],
+ );
+ }
+ for (var item in backup['collection']) {
+ shareholder.addShowToList(
+ listName: 'collection',
+ showPreview: ShowPreview.fromJson(item),
+ genres: item['genres'],
+ countries: item['countries'],
+ languages: item['languages'],
+ companies: item['companies'],
+ contentRating: item['contentRating'],
+ similars: [
+ for (Map show in item['similars'])
+ ShowPreview.fromJson(show)
+ ],
+ );
+ }
+
+ recommender();
+ getUserData();
+ success = true;
+ }
+ });
+ return success;
+ } else {
+ return success;
+ }
+}
+
+Future backupFileTester(final File files) async {
+ try {
+ Map backup = jsonDecode(await files.readAsString());
+ backup['personal'];
+ if (kDebugMode) {
+ print("The imported file is a real backup.");
+ }
+ return true;
+ } catch (e) {
+ if (kDebugMode) {
+ print('There\'s a problem with the imported file.');
+ }
+ return false;
+ }
+}
diff --git a/lib/modules/preferences/preferences_shareholder.dart b/lib/modules/preferences/preferences_shareholder.dart
index 5633db2..61d6cb9 100644
--- a/lib/modules/preferences/preferences_shareholder.dart
+++ b/lib/modules/preferences/preferences_shareholder.dart
@@ -1,13 +1,13 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:hive_flutter/adapters.dart';
+import 'package:movielab/models/hive/convertor.dart';
+import 'package:movielab/models/hive/models/show_preview.dart';
import 'package:movielab/models/hive/models/user.dart';
import 'package:movielab/models/show_models/show_preview_model.dart';
import 'package:movielab/models/user_model/user_model.dart';
import 'package:movielab/modules/Recommender/Recommender.dart';
import 'package:movielab/pages/splash/get_user_data.dart';
-import '../../models/hive/convertor.dart';
-import '../../models/hive/models/show_preview.dart';
class PreferencesShareholder {
// Delete all items of a list in the shared preferences
diff --git a/lib/pages/shared/settings_page/sections/backup_page.dart b/lib/pages/shared/settings_page/sections/backup_page.dart
new file mode 100644
index 0000000..a4452f7
--- /dev/null
+++ b/lib/pages/shared/settings_page/sections/backup_page.dart
@@ -0,0 +1,115 @@
+import 'package:flutter/material.dart';
+import 'package:fluttertoast/fluttertoast.dart';
+import 'package:movielab/constants/colors.dart';
+import 'package:movielab/modules/preferences/backup.dart';
+import 'package:movielab/widgets/default_appbar.dart';
+import 'package:movielab/widgets/guide_modal.dart';
+import 'package:movielab/widgets/toast.dart';
+
+import '../settings_page.dart';
+
+class BackupPage extends StatefulWidget {
+ const BackupPage({Key? key}) : super(key: key);
+
+ @override
+ State createState() => _BackupPageState();
+}
+
+class _BackupPageState extends State with TickerProviderStateMixin {
+ FToast fToast = FToast();
+
+ @override
+ Widget build(BuildContext context) {
+ fToast.init(context);
+ return Scaffold(
+ appBar: defaultAppBar(context, title: "Backup"),
+ body: SingleChildScrollView(
+ physics: const BouncingScrollPhysics(),
+ child: Column(
+ children: [
+ settingSection(
+ icon: Icons.cloud_download_rounded,
+ iconSize: 30,
+ title: "Export backup file",
+ description:
+ "You can export the entire database for keeping a backup or to import it in a new device\nAfter exporting, it's recommended to upload this backup file to a cloud server like Google Drive",
+ onPressed: () async {
+ guideModalSheet(context,
+ vsync: this,
+ title: "Select File Location",
+ decription:
+ "Select the directory where you want to save this file.",
+ onTap: () async {
+ await createBackup().then((final bool success) {
+ if (success) {
+ fToast.removeQueuedCustomToasts();
+ fToast.showToast(
+ child: ToastWidget(
+ mainText: "The backup file successfully created.",
+ buttonText: "Ok",
+ buttonColor: kAccentColor,
+ buttonOnTap: () async {
+ await Future.delayed(
+ const Duration(milliseconds: 200));
+ fToast.removeCustomToast();
+ },
+ ),
+ gravity: ToastGravity.BOTTOM,
+ toastDuration: const Duration(seconds: 2),
+ );
+ } else {
+ fToast.removeQueuedCustomToasts();
+ fToast.showToast(
+ child: ToastWidget(
+ mainText: "Something went wrong!",
+ buttonText: "Ok",
+ buttonColor: kPrimaryColor,
+ buttonOnTap: () async {
+ await Future.delayed(
+ const Duration(milliseconds: 200));
+ fToast.removeCustomToast();
+ },
+ ),
+ gravity: ToastGravity.BOTTOM,
+ toastDuration: const Duration(seconds: 2),
+ );
+ }
+ });
+ Navigator.pop(context);
+ });
+ }),
+ settingSection(
+ icon: Icons.cloud_upload_rounded,
+ iconSize: 30,
+ title: "Import backup file",
+ description:
+ "Select the exported movielab-backup-date.db from file manager",
+ onPressed: () async {
+ await restoreBackup().then((final bool success) async {
+ fToast.removeQueuedCustomToasts();
+ fToast.showToast(
+ child: ToastWidget(
+ mainText: success
+ ? "The backup file imported successfully."
+ : "There's a problem with the imported file!",
+ mainTextColor: success ? Colors.green : kPrimaryColor,
+ buttonText: "Ok",
+ fontSize: 13,
+ buttonColor: Colors.black,
+ buttonOnTap: () async {
+ await Future.delayed(
+ const Duration(milliseconds: 200));
+ fToast.removeCustomToast();
+ },
+ ),
+ gravity: ToastGravity.BOTTOM,
+ toastDuration: const Duration(seconds: 2),
+ );
+ });
+ }),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/pages/shared/settings_page/settings_page.dart b/lib/pages/shared/settings_page/settings_page.dart
index 6ca4971..cd6e77c 100644
--- a/lib/pages/shared/settings_page/settings_page.dart
+++ b/lib/pages/shared/settings_page/settings_page.dart
@@ -1,8 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:fluttertoast/fluttertoast.dart';
-import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:movielab/constants/colors.dart';
+import 'package:movielab/modules/tools/navigate.dart';
+import 'package:movielab/pages/shared/settings_page/sections/backup_page.dart';
import 'package:movielab/widgets/default_appbar.dart';
import 'package:movielab/widgets/toast.dart';
@@ -19,7 +20,15 @@ class SettingsPage extends StatelessWidget {
child: Column(
children: [
settingSection(
- icon: FontAwesomeIcons.xmark,
+ icon: Icons.backup_sharp,
+ title: "Offline import/export database",
+ description:
+ "Get a backup file of your personal data locally on your phone",
+ onPressed: () async {
+ Navigate.pushTo(context, BackupPage());
+ }),
+ settingSection(
+ icon: Icons.cancel_sharp,
title: "Clear media content cache",
description:
"Remove all cached content, but not your personal data",
@@ -49,6 +58,7 @@ class SettingsPage extends StatelessWidget {
Widget settingSection(
{required final IconData icon,
+ double iconSize = 30,
required final String title,
required final String description,
required final void Function() onPressed}) {
@@ -56,6 +66,7 @@ Widget settingSection(
leading: Icon(
icon,
color: Colors.white,
+ size: iconSize,
),
title: Padding(
padding: const EdgeInsets.only(bottom: 5),
diff --git a/lib/pages/splash/get_user_data.dart b/lib/pages/splash/get_user_data.dart
index 135d413..bf4d662 100644
--- a/lib/pages/splash/get_user_data.dart
+++ b/lib/pages/splash/get_user_data.dart
@@ -1,3 +1,4 @@
+import 'package:flutter/foundation.dart';
import 'package:get/get.dart';
import 'package:movielab/models/show_models/show_preview_model.dart';
import 'package:movielab/models/user_model/user_model.dart';
@@ -202,7 +203,9 @@ Future updateUserStats() async {
sortedContentRatings: sortedContentRatings,
contentRatingsLength: contentRatingsLength,
contentRatingsOthers: contentRatingsOthers);
- print("User stats updated");
+ if (kDebugMode) {
+ print("User stats updated");
+ }
return true;
}
return false;
diff --git a/lib/widgets/buttons/activeable_button.dart b/lib/widgets/buttons/activeable_button.dart
index a7907fa..ab0c6fb 100644
--- a/lib/widgets/buttons/activeable_button.dart
+++ b/lib/widgets/buttons/activeable_button.dart
@@ -4,7 +4,7 @@ import 'package:flutter_spinkit/flutter_spinkit.dart';
class ActiveableButton extends StatelessWidget {
final String text;
final String? activeText;
- final IconData icon;
+ final IconData? icon;
final IconData? activeIcon;
final VoidCallback onTap;
final bool isActive;
@@ -52,14 +52,19 @@ class ActiveableButton extends StatelessWidget {
),
),
child: Row(
+ mainAxisAlignment: icon != null
+ ? MainAxisAlignment.start
+ : MainAxisAlignment.center,
children: [
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 25),
- child: Icon(
- isActive ? activeIcon ?? icon : icon,
- size: 20,
- ),
- ),
+ icon != null
+ ? Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 25),
+ child: Icon(
+ isActive ? activeIcon ?? icon : icon,
+ size: 20,
+ ),
+ )
+ : const SizedBox.shrink(),
Text(
isActive ? activeText ?? text : text,
style: const TextStyle(
diff --git a/lib/widgets/guide_modal.dart b/lib/widgets/guide_modal.dart
new file mode 100644
index 0000000..6337189
--- /dev/null
+++ b/lib/widgets/guide_modal.dart
@@ -0,0 +1,63 @@
+import 'package:flutter/material.dart';
+import 'package:movielab/constants/colors.dart';
+import 'package:movielab/widgets/buttons/activeable_button.dart';
+
+Future guideModalSheet(BuildContext context,
+ {required dynamic vsync,
+ final Color backgroundColor = kBackgroundColor,
+ required final String title,
+ required final String decription,
+ required final VoidCallback onTap}) {
+ return showModalBottomSheet(
+ context: context,
+ shape: const RoundedRectangleBorder(
+ borderRadius: BorderRadius.vertical(
+ top: Radius.circular(30),
+ )),
+ clipBehavior: Clip.antiAliasWithSaveLayer,
+ transitionAnimationController: AnimationController(
+ duration: const Duration(milliseconds: 250), vsync: vsync),
+ builder: (context) {
+ return Container(
+ padding: const EdgeInsets.symmetric(horizontal: 50),
+ color: backgroundColor,
+ height: 250,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Container(
+ width: MediaQuery.of(context).size.width / 3,
+ margin: const EdgeInsets.only(top: 8.5, bottom: 32.5),
+ height: 3,
+ color: Colors.white.withOpacity(0.4),
+ ),
+ Text(
+ title,
+ style:
+ const TextStyle(fontWeight: FontWeight.w600, fontSize: 18.5),
+ ),
+ const SizedBox(
+ height: 30,
+ ),
+ Text(
+ decription,
+ textAlign: TextAlign.center,
+ style:
+ const TextStyle(fontWeight: FontWeight.w500, fontSize: 16.5),
+ ),
+ const SizedBox(
+ height: 30,
+ ),
+ ActiveableButton(
+ isActive: true,
+ text: "Select Folder",
+ icon: null,
+ onTap: onTap,
+ activeColor: kAccentColor,
+ )
+ ],
+ ),
+ );
+ },
+ );
+}
diff --git a/lib/widgets/toast.dart b/lib/widgets/toast.dart
index 866284b..08da2bf 100644
--- a/lib/widgets/toast.dart
+++ b/lib/widgets/toast.dart
@@ -2,12 +2,16 @@ import 'package:flutter/material.dart';
class ToastWidget extends StatelessWidget {
final String mainText;
+ final Color mainTextColor;
final String buttonText;
final Color buttonColor;
final VoidCallback buttonOnTap;
+ final double fontSize;
const ToastWidget(
{Key? key,
required this.mainText,
+ this.mainTextColor = Colors.black,
+ this.fontSize = 14,
required this.buttonText,
required this.buttonColor,
required this.buttonOnTap})
@@ -29,10 +33,10 @@ class ToastWidget extends StatelessWidget {
children: [
Text(
mainText,
- style: const TextStyle(
- fontWeight: FontWeight.w600,
- color: Colors.black,
- ),
+ style: TextStyle(
+ fontWeight: FontWeight.w600,
+ color: mainTextColor,
+ fontSize: fontSize),
),
TextButton(
onPressed: buttonOnTap,
@@ -40,8 +44,10 @@ class ToastWidget extends StatelessWidget {
TextButton.styleFrom(primary: buttonColor.withOpacity(0.5)),
child: Text(
buttonText,
- style:
- TextStyle(fontWeight: FontWeight.w600, color: buttonColor),
+ style: TextStyle(
+ fontWeight: FontWeight.w600,
+ color: buttonColor,
+ fontSize: fontSize),
))
],
),
diff --git a/pubspec.lock b/pubspec.lock
index e495ea9..fcf6c7a 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -238,7 +238,7 @@ packages:
name: ffi
url: "https://pub.dartlang.org"
source: hosted
- version: "1.1.2"
+ version: "2.0.1"
file:
dependency: transitive
description:
@@ -246,6 +246,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "6.1.2"
+ file_picker:
+ dependency: "direct main"
+ description:
+ name: file_picker
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "5.0.1"
fixnum:
dependency: transitive
description:
@@ -286,6 +293,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
+ flutter_phoenix:
+ dependency: "direct main"
+ description:
+ name: flutter_phoenix
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.1.0"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
@@ -624,7 +638,7 @@ packages:
name: path_provider_linux
url: "https://pub.dartlang.org"
source: hosted
- version: "2.1.5"
+ version: "2.1.7"
path_provider_macos:
dependency: transitive
description:
@@ -645,7 +659,7 @@ packages:
name: path_provider_windows
url: "https://pub.dartlang.org"
source: hosted
- version: "2.0.5"
+ version: "2.1.2"
pedantic:
dependency: transitive
description:
@@ -653,6 +667,41 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.11.1"
+ permission_handler:
+ dependency: "direct main"
+ description:
+ name: permission_handler
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "10.0.0"
+ permission_handler_android:
+ dependency: transitive
+ description:
+ name: permission_handler_android
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "10.0.0"
+ permission_handler_apple:
+ dependency: transitive
+ description:
+ name: permission_handler_apple
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "9.0.4"
+ permission_handler_platform_interface:
+ dependency: transitive
+ description:
+ name: permission_handler_platform_interface
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "3.7.0"
+ permission_handler_windows:
+ dependency: transitive
+ description:
+ name: permission_handler_windows
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.1.0"
petitparser:
dependency: transitive
description:
@@ -1098,7 +1147,7 @@ packages:
name: win32
url: "https://pub.dartlang.org"
source: hosted
- version: "2.3.10"
+ version: "2.7.0"
xdg_directories:
dependency: transitive
description:
@@ -1121,5 +1170,5 @@ packages:
source: hosted
version: "3.1.0"
sdks:
- dart: ">=2.17.0-206.0.dev <3.0.0"
+ dart: ">=2.17.0 <3.0.0"
flutter: ">=3.0.0"
diff --git a/pubspec.yaml b/pubspec.yaml
index 69af281..dcc4e76 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -44,6 +44,9 @@ dependencies:
flutter_cache_manager: ^3.3.0
gallery_saver: ^2.3.2
external_path: ^1.0.1
+ permission_handler: ^10.0.0
+ file_picker: ^5.0.1
+ flutter_phoenix: ^1.1.0