From 510d4991a6475c56395dbde068898424c5b521a4 Mon Sep 17 00:00:00 2001 From: TU-Lin Date: Fri, 17 Feb 2023 01:17:28 +0800 Subject: [PATCH] Fix aos storage permission not be requested when api above 33 (#168) * chore: add image/video, music/audio permission into android manifest * feat: seperate permission request content by aos api 33 * chore: add device into package * refactor: migrate file page to null safety, with pieces of UI improvements such as color * refactor: null-safety migrations --- android/app/src/main/AndroidManifest.xml | 11 +- lib/src/config/constants.dart | 4 +- lib/src/providers/category_provider.dart | 63 +-- lib/src/util/permissions_util.dart | 37 +- lib/ui/pages/fileviewer/file_viewer_page.dart | 378 +++++++++--------- .../fileviewer/widgets/custom_alert.dart | 4 +- lib/ui/pages/fileviewer/widgets/dir_item.dart | 16 +- .../pages/fileviewer/widgets/dir_popup.dart | 14 +- lib/ui/pages/fileviewer/widgets/path_bar.dart | 8 +- .../pages/fileviewer/widgets/sort_sheet.dart | 8 +- pubspec.lock | 18 +- pubspec.yaml | 1 + 12 files changed, 296 insertions(+), 266 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index a6562099..389f3c94 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -7,10 +7,13 @@ - - - - + + + + + sortList = [ "File name (A to Z)", "File name (Z to A)", "Date (oldest first)", diff --git a/lib/src/providers/category_provider.dart b/lib/src/providers/category_provider.dart index 257cc918..1a970f51 100644 --- a/lib/src/providers/category_provider.dart +++ b/lib/src/providers/category_provider.dart @@ -1,5 +1,3 @@ -// TODO: remove sdk version selector after migrating to null-safety. -// @dart=2.10 import 'dart:io'; import 'package:flutter/foundation.dart'; @@ -15,14 +13,14 @@ class CategoryProvider extends ChangeNotifier { } bool loading = false; - List downloads = []; - List downloadTabs = []; + final List downloads = []; + final List downloadTabs = []; - List images = []; - List imageTabs = []; + final List images = []; + final List imageTabs = []; - List audio = []; - List audioTabs = []; + final List audio = []; + final List audioTabs = []; bool showHidden = false; int sort = 0; @@ -32,15 +30,18 @@ class CategoryProvider extends ChangeNotifier { downloadTabs.clear(); downloads.clear(); downloadTabs.add("All"); - List storages = await FileUtils.getStorageList(); - for (var dir in storages) { + final List storages = await FileUtils.getStorageList(); + for (final dir in storages) { if (Directory("${dir.path}Download").existsSync()) { - List files = Directory("${dir.path}Download").listSync(); + final List files = Directory("${dir.path}Download").listSync(); for (final file in files) { if (FileSystemEntity.isFileSync(file.path)) { downloads.add(file); - downloadTabs.add(file.path.split("/")[file.path.split("/").length - 2]); - downloadTabs = downloadTabs.toSet().toList(); + final tmpDownloadTabs = [...downloadTabs, file.path.split("/")[file.path.split("/").length - 2]] + ..toSet().toList(); + downloadTabs + ..clear() + ..addAll(tmpDownloadTabs); notifyListeners(); } } @@ -54,13 +55,15 @@ class CategoryProvider extends ChangeNotifier { imageTabs.clear(); images.clear(); imageTabs.add("All"); - List files = await FileUtils.getAllFiles(showHidden: showHidden); - for (var file in files) { - String mimeType = mime(file.path) ?? ""; + final List files = await FileUtils.getAllFiles(showHidden: showHidden); + for (final file in files) { + final mimeType = mime(file.path) ?? ""; if (mimeType.split("/")[0] == type) { images.add(file); - imageTabs.add(file.path.split("/")[file.path.split("/").length - 2]); - imageTabs = imageTabs.toSet().toList(); + final tmpImageTabs = [...imageTabs, file.path.split("/")[file.path.split("/").length - 2]]..toSet().toList(); + imageTabs + ..clear() + ..addAll(tmpImageTabs); } notifyListeners(); } @@ -72,17 +75,19 @@ class CategoryProvider extends ChangeNotifier { audioTabs.clear(); audio.clear(); audioTabs.add("All"); - List files = await FileUtils.getAllFiles(showHidden: showHidden); - for (var file in files) { - String mimeType = mime(file.path); + final List files = await FileUtils.getAllFiles(showHidden: showHidden); + for (final file in files) { + final mimeType = mime(file.path); if (type == "text" && extension(file.path) == ".pdf") { audio.add(file); } if (mimeType != null) { if (mimeType.split("/")[0] == type) { audio.add(file); - audioTabs.add(file.path.split("/")[file.path.split("/").length - 2]); - audioTabs = audioTabs.toSet().toList(); + final tmpAudioTabs = [...audioTabs, file.path.split("/")[file.path.split("/").length - 2]]..toSet().toList(); + audioTabs + ..clear() + ..addAll(tmpAudioTabs); } notifyListeners(); } @@ -96,28 +101,28 @@ class CategoryProvider extends ChangeNotifier { } setHidden(value) async { - SharedPreferences prefs = await SharedPreferences.getInstance(); + final SharedPreferences prefs = await SharedPreferences.getInstance(); await prefs.setBool("hidden", value); showHidden = value; notifyListeners(); } getHidden() async { - SharedPreferences prefs = await SharedPreferences.getInstance(); - bool h = prefs.getBool("hidden") ?? false; + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final bool h = prefs.getBool("hidden") ?? false; setHidden(h); } Future setSort(value) async { - SharedPreferences prefs = await SharedPreferences.getInstance(); + final SharedPreferences prefs = await SharedPreferences.getInstance(); await prefs.setInt("sort", value); sort = value; notifyListeners(); } getSort() async { - SharedPreferences prefs = await SharedPreferences.getInstance(); - int h = prefs.getInt("sort") ?? 0; + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final int h = prefs.getInt("sort") ?? 0; setSort(h); } } diff --git a/lib/src/util/permissions_util.dart b/lib/src/util/permissions_util.dart index fe2643de..775a0ee8 100644 --- a/lib/src/util/permissions_util.dart +++ b/lib/src/util/permissions_util.dart @@ -1,18 +1,43 @@ import 'dart:io'; +import 'package:device_info_plus/device_info_plus.dart'; import 'package:permission_handler/permission_handler.dart'; class PermissionsUtil { /// Checks if the APP can access to the file sys on the current device. - /// - /// If it can not access to, an External Storage (id = 15) warning will occurred. - /// So please check if the androidManifest.xml has the following permission declarations: - /// - uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" - /// - uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" static Future checkHasAosStoragePermission() async { + // On iOS, the permission is managed by xcode project config. if (Platform.isIOS) return true; assert(Platform.isAndroid, 'The platform most be either aos or ios.'); - return await Permission.storage.request().isGranted; + final deviceInfoPlugin = DeviceInfoPlugin(); + final androidSdkVersion = (await deviceInfoPlugin.androidInfo).version.sdkInt; + final requiredPermissions = []; + + // About the new `Granular media permissions` policy: + // Above Android API 33, the permission of storage is split into image/videos and music/audio. + // Which means we can't request the `READ_EXTERNAL_STORAGE`. + // And since the `permission_handler v10.2.0` still not support the new policy, + // we have to request the separate permissions. + // Please refer to https://developer.android.com/about/versions/13/behavior-changes-13#granular-media-permissions + if (androidSdkVersion >= 33) { + requiredPermissions.addAll([ + Permission.videos, + Permission.audio, + ]); + } else { + requiredPermissions.addAll([ + Permission.storage, + ]); + } + + await requiredPermissions.request(); + final determineResult = await Future.wait(requiredPermissions.map((permission) => permission.isGranted)); + + // Since the requiredPermissions is a list, user can choose to grant or deny any of them. + // So we have to check if any of them (instead of `every`) is granted. + // This depends on all the permissions in the list are able to make the App + // access the device's storage once they've been granted. + return determineResult.any((permission) => permission); } } diff --git a/lib/ui/pages/fileviewer/file_viewer_page.dart b/lib/ui/pages/fileviewer/file_viewer_page.dart index 876032f0..6e2904b2 100644 --- a/lib/ui/pages/fileviewer/file_viewer_page.dart +++ b/lib/ui/pages/fileviewer/file_viewer_page.dart @@ -1,10 +1,8 @@ -// TODO: remove sdk version selector after migrating to null-safety. -// @dart=2.10 import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:flutter_app/src/r.dart'; import 'package:flutter_app/src/providers/category_provider.dart'; +import 'package:flutter_app/src/r.dart'; import 'package:flutter_app/src/util/file_utils.dart'; import 'package:flutter_app/ui/other/my_toast.dart'; import "package:flutter_feather_icons/flutter_feather_icons.dart"; @@ -23,19 +21,20 @@ class FileViewerPage extends StatefulWidget { final String path; const FileViewerPage({ - Key key, - @required this.title, - @required this.path, - }) : super(key: key); + super.key, + required this.title, + required this.path, + }); @override State createState() => _FileViewerPageState(); } class _FileViewerPageState extends State with WidgetsBindingObserver { - String path; + String path = ""; final List paths = []; List files = []; + final isDarkModeEnabled = Get.isDarkMode; bool showHidden = false; @override @@ -46,13 +45,13 @@ class _FileViewerPageState extends State with WidgetsBindingObse } getFiles() async { - Directory dir = Directory(path); - List l = dir.listSync(); + final Directory dir = Directory(path); + final List l = dir.listSync(); files.clear(); setState(() { showHidden = Provider.of(context, listen: false).showHidden; }); - for (FileSystemEntity file in l) { + for (final file in l) { if (!showHidden) { if (!path_lib.basename(file.path).startsWith(".")) { setState(() { @@ -85,164 +84,163 @@ class _FileViewerPageState extends State with WidgetsBindingObse } @override - Widget build(BuildContext context) => WillPopScope( - onWillPop: () async { - if (paths.length == 1) { - return true; - } else { - paths.removeLast(); - setState(() { - path = paths.last; - }); - getFiles(); - return false; - } - }, - child: Scaffold( - appBar: AppBar( - leading: IconButton( - icon: const Icon( - Icons.arrow_back, - ), - onPressed: () { - if (paths.length == 1) { - Navigator.pop(context); - } else { - paths.removeLast(); - setState(() { - path = paths.last; - }); - getFiles(); - } - }, - ), - elevation: 4, - title: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - widget.title, - ), - ], + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + final labelAndIconColor = isDarkModeEnabled ? colorScheme.onPrimaryContainer : colorScheme.onPrimary; + return WillPopScope( + onWillPop: () async { + if (paths.length == 1) { + return true; + } else { + paths.removeLast(); + setState(() { + path = paths.last; + }); + getFiles(); + return false; + } + }, + child: Scaffold( + appBar: AppBar( + leading: IconButton( + icon: const Icon( + Icons.arrow_back, ), - bottom: PathBar( - child: SizedBox( - height: 50, - child: Align( - alignment: Alignment.centerLeft, - child: ListView.separated( - scrollDirection: Axis.horizontal, - shrinkWrap: true, - itemCount: paths.length, - itemBuilder: (BuildContext context, int index) { - final i = paths[index]; - final split = i.split("/"); - return index == 0 - ? IconButton( - icon: Icon( - widget.path.toString().contains("emulated") ? FeatherIcons.smartphone : Icons.sd_card, - color: index == paths.length - 1 - ? Theme.of(context).colorScheme.secondary - : Theme.of(context).textTheme.titleLarge.color, - ), - onPressed: () { - setState(() { - path = paths[index]; - paths.removeRange(index + 1, paths.length); - }); - getFiles(); - }, - ) - : InkWell( - onTap: () { - setState(() { - path = paths[index]; - paths.removeRange(index + 1, paths.length); - }); - getFiles(); - }, - child: SizedBox( - height: 40, - child: Center( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 5), - child: Text( - split[split.length - 1], - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: index == paths.length - 1 - ? Theme.of(context).colorScheme.secondary - : Theme.of(context).textTheme.titleLarge.color, - ), + onPressed: () { + if (paths.length == 1) { + Navigator.pop(context); + } else { + paths.removeLast(); + setState(() { + path = paths.last; + }); + getFiles(); + } + }, + ), + elevation: 4, + title: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(widget.title), + ], + ), + bottom: PathBar( + child: SizedBox( + height: 50, + child: Align( + alignment: Alignment.centerLeft, + child: ListView.separated( + scrollDirection: Axis.horizontal, + shrinkWrap: true, + itemCount: paths.length, + itemBuilder: (BuildContext context, int index) { + final i = paths[index]; + final split = i.split("/"); + return index == 0 + ? IconButton( + icon: Icon( + widget.path.toString().contains("emulated") ? FeatherIcons.smartphone : Icons.sd_card, + color: labelAndIconColor, + ), + onPressed: () { + setState(() { + path = paths[index]; + paths.removeRange(index + 1, paths.length); + }); + getFiles(); + }, + ) + : InkWell( + onTap: () { + setState(() { + path = paths[index]; + paths.removeRange(index + 1, paths.length); + }); + getFiles(); + }, + child: SizedBox( + height: 40, + child: Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 5), + child: Text( + split[split.length - 1], + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: labelAndIconColor, ), ), ), ), - ); - }, - separatorBuilder: (BuildContext context, int index) { - return const Icon( - Icons.arrow_forward_ios, - ); - }, + ), + ); + }, + separatorBuilder: (_, __) => Icon( + Icons.arrow_forward_ios, + color: labelAndIconColor, ), ), ), ), - actions: [ - IconButton( - onPressed: () { - showModalBottomSheet( - context: context, - builder: (context) => const SortSheet(), - ).then((v) { - getFiles(); - }); - }, - tooltip: R.current.sortBy, - icon: const Icon( - Icons.sort, - ), - ), - ], ), - body: files.isEmpty - ? Center( - child: Text(R.current.nothingHere), - ) - : ListView.separated( - padding: const EdgeInsets.only(left: 20), - itemCount: files.length, - itemBuilder: (BuildContext context, int index) { - final file = files[index]; - return file.toString().split(":")[0] == "Directory" - ? DirectoryItem( - popTap: (v) async { - if (v == 0) { - renameDialog(context, file.path, "dir"); - } else if (v == 1) { - await Directory(file.path) - .delete(recursive: true) //將會刪除資料夾內所有東西 - .catchError((e) { - if (e.toString().contains("Permission denied")) { - MyToast.show(R.current.cannotWrite); - } - }); - getFiles(); - } - }, - file: file, - tap: () { - paths.add(file.path); - setState(() { - path = file.path; + actions: [ + IconButton( + onPressed: () { + showModalBottomSheet( + context: context, + builder: (context) => const SortSheet(), + ).then((v) { + getFiles(); + }); + }, + tooltip: R.current.sortBy, + icon: const Icon( + Icons.sort, + ), + ), + ], + ), + body: files.isEmpty + ? Center( + child: Text(R.current.nothingHere), + ) + : ListView.builder( + itemCount: files.length, + itemBuilder: (BuildContext context, int index) { + final file = files[index]; + return file.toString().split(":")[0] == "Directory" + ? DirectoryItem( + popTap: (v) async { + if (v == 0) { + renameDialog(context, file.path, "dir"); + } else if (v == 1) { + await Directory(file.path) + .delete(recursive: true) //將會刪除資料夾內所有東西 + .catchError((e) { + if (e.toString().contains("Permission denied")) { + MyToast.show(R.current.cannotWrite); + } + + return Directory(file.path); }); getFiles(); - }, - ) - : FileItem( + } + }, + file: file, + tap: () { + paths.add(file.path); + setState(() { + path = file.path; + }); + getFiles(); + }, + ) + : Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: FileItem( file: file, popTap: (v) async { if (v == 0) { @@ -252,37 +250,27 @@ class _FileViewerPageState extends State with WidgetsBindingObse if (e.toString().contains("Permission denied")) { MyToast.show(R.current.cannotWrite); } + return File(file.path); }); getFiles(); } else if (v == 2) {} }, - ); - }, - separatorBuilder: (BuildContext context, int index) { - return Stack( - children: [ - Align( - alignment: Alignment.centerRight, - child: Container( - height: 1, - color: Theme.of(context).dividerColor, - width: MediaQuery.of(context).size.width - 70, ), - ), - ], - ); - }, - ), - floatingActionButton: FloatingActionButton( - onPressed: () => addDialog(context, path), - tooltip: "Add Folder", - child: const Icon(FeatherIcons.plus), - ), + ); + }, + ), + floatingActionButton: FloatingActionButton( + onPressed: () => addDialog(context, path), + tooltip: "Add Folder", + child: const Icon(FeatherIcons.plus), ), - ); + ), + ); + } addDialog(BuildContext context, String path) { final name = TextEditingController(); + final colorScheme = Theme.of(context).colorScheme; Get.dialog( CustomAlert( child: Padding( @@ -291,7 +279,7 @@ class _FileViewerPageState extends State with WidgetsBindingObse mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, - children: [ + children: [ const SizedBox(height: 15), Text( R.current.createNewFolder, @@ -308,7 +296,7 @@ class _FileViewerPageState extends State with WidgetsBindingObse const SizedBox(height: 40), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ + children: [ SizedBox( height: 40, width: 130, @@ -318,12 +306,12 @@ class _FileViewerPageState extends State with WidgetsBindingObse borderRadius: BorderRadius.circular(5.0), ), side: BorderSide(color: Theme.of(context).colorScheme.secondary), - backgroundColor: Colors.white, + backgroundColor: colorScheme.secondary, ), child: Text( R.current.cancel, style: TextStyle( - color: Theme.of(context).colorScheme.secondary, + color: colorScheme.onSecondary, ), ), onPressed: () => Navigator.pop(context), @@ -334,15 +322,15 @@ class _FileViewerPageState extends State with WidgetsBindingObse width: 130, child: ElevatedButton( style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).colorScheme.secondary, + backgroundColor: colorScheme.tertiary, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(5.0), ), ), child: Text( R.current.createFolder, - style: const TextStyle( - color: Colors.white, + style: TextStyle( + color: colorScheme.onTertiary, ), ), onPressed: () async { @@ -352,6 +340,7 @@ class _FileViewerPageState extends State with WidgetsBindingObse if (e.toString().contains("Permission denied")) { MyToast.show(R.current.cannotWrite); } + return Directory("$path/${name.text}"); }); } else { MyToast.show(R.current.folderNameAlreadyExists); @@ -375,6 +364,7 @@ class _FileViewerPageState extends State with WidgetsBindingObse renameDialog(BuildContext context, String path, String type) { final name = TextEditingController(); + final colorScheme = Theme.of(context).colorScheme; setState(() { name.text = path_lib.basename(path); }); @@ -386,7 +376,7 @@ class _FileViewerPageState extends State with WidgetsBindingObse mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, - children: [ + children: [ const SizedBox(height: 15), Text( R.current.renameItem, @@ -403,7 +393,7 @@ class _FileViewerPageState extends State with WidgetsBindingObse const SizedBox(height: 40), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ + children: [ SizedBox( height: 40, width: 130, @@ -413,12 +403,12 @@ class _FileViewerPageState extends State with WidgetsBindingObse borderRadius: BorderRadius.circular(5.0), ), side: BorderSide(color: Theme.of(context).colorScheme.secondary), - backgroundColor: Colors.white, + backgroundColor: colorScheme.secondary, ), child: Text( R.current.cancel, style: TextStyle( - color: Theme.of(context).colorScheme.secondary, + color: colorScheme.onSecondary, ), ), onPressed: () => Navigator.pop(context), @@ -432,12 +422,12 @@ class _FileViewerPageState extends State with WidgetsBindingObse shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(5.0), ), - backgroundColor: Theme.of(context).colorScheme.secondary, + backgroundColor: colorScheme.tertiary, ), child: Text( R.current.rename, - style: const TextStyle( - color: Colors.white, + style: TextStyle( + color: colorScheme.onTertiary, ), ), onPressed: () async { @@ -450,6 +440,7 @@ class _FileViewerPageState extends State with WidgetsBindingObse if (e.toString().contains("Permission denied")) { MyToast.show(R.current.cannotWrite); } + return File(path); }); } else { MyToast.show(R.current.fileNameAlreadyExists); @@ -464,6 +455,7 @@ class _FileViewerPageState extends State with WidgetsBindingObse if (e.toString().contains("Permission denied")) { MyToast.show(R.current.cannotWrite); } + return Directory(path); }); } } diff --git a/lib/ui/pages/fileviewer/widgets/custom_alert.dart b/lib/ui/pages/fileviewer/widgets/custom_alert.dart index aa6a9ff6..5c743d07 100644 --- a/lib/ui/pages/fileviewer/widgets/custom_alert.dart +++ b/lib/ui/pages/fileviewer/widgets/custom_alert.dart @@ -1,5 +1,3 @@ -// TODO: remove sdk version selector after migrating to null-safety. -// @dart=2.10 import 'dart:ui'; import 'package:flutter/material.dart'; @@ -7,7 +5,7 @@ import 'package:flutter/material.dart'; class CustomAlert extends StatelessWidget { final Widget child; - const CustomAlert({Key key, @required this.child}) : super(key: key); + const CustomAlert({super.key, required this.child}); @override Widget build(BuildContext context) { diff --git a/lib/ui/pages/fileviewer/widgets/dir_item.dart b/lib/ui/pages/fileviewer/widgets/dir_item.dart index 642a19b2..cea252b3 100644 --- a/lib/ui/pages/fileviewer/widgets/dir_item.dart +++ b/lib/ui/pages/fileviewer/widgets/dir_item.dart @@ -1,5 +1,3 @@ -// TODO: remove sdk version selector after migrating to null-safety. -// @dart=2.10 import 'dart:io'; import 'package:flutter/material.dart'; @@ -10,15 +8,15 @@ import 'dir_popup.dart'; class DirectoryItem extends StatelessWidget { final FileSystemEntity file; - final Function tap; - final Function popTap; + final VoidCallback tap; + final void Function(int)? popTap; const DirectoryItem({ - Key key, - @required this.file, - @required this.tap, - @required this.popTap, - }) : super(key: key); + super.key, + required this.file, + required this.tap, + required this.popTap, + }); @override Widget build(BuildContext context) => ListTile( diff --git a/lib/ui/pages/fileviewer/widgets/dir_popup.dart b/lib/ui/pages/fileviewer/widgets/dir_popup.dart index debf3bf5..aaf87ab2 100644 --- a/lib/ui/pages/fileviewer/widgets/dir_popup.dart +++ b/lib/ui/pages/fileviewer/widgets/dir_popup.dart @@ -1,17 +1,15 @@ -// TODO: remove sdk version selector after migrating to null-safety. -// @dart=2.10 import 'package:flutter/material.dart'; import 'package:flutter_app/src/r.dart'; class DirPopup extends StatelessWidget { final String path; - final Function popTap; + final void Function(int)? popTap; const DirPopup({ - Key key, - @required this.path, - @required this.popTap, - }) : super(key: key); + super.key, + required this.path, + required this.popTap, + }); @override Widget build(BuildContext context) => PopupMenuButton( @@ -32,7 +30,7 @@ class DirPopup extends StatelessWidget { ], icon: Icon( Icons.arrow_drop_down, - color: Theme.of(context).textTheme.titleLarge.color, + color: Theme.of(context).textTheme.titleLarge?.color, ), color: Theme.of(context).scaffoldBackgroundColor, offset: const Offset(0, 30), diff --git a/lib/ui/pages/fileviewer/widgets/path_bar.dart b/lib/ui/pages/fileviewer/widgets/path_bar.dart index 8bf1f491..5c5d5e04 100644 --- a/lib/ui/pages/fileviewer/widgets/path_bar.dart +++ b/lib/ui/pages/fileviewer/widgets/path_bar.dart @@ -1,14 +1,12 @@ -// TODO: remove sdk version selector after migrating to null-safety. -// @dart=2.10 import 'package:flutter/material.dart'; class PathBar extends StatelessWidget implements PreferredSizeWidget { final Widget child; const PathBar({ - Key key, - @required this.child, - }) : super(key: key); + super.key, + required this.child, + }); @override Widget build(BuildContext context) => child; diff --git a/lib/ui/pages/fileviewer/widgets/sort_sheet.dart b/lib/ui/pages/fileviewer/widgets/sort_sheet.dart index 85e837a9..856f4dec 100644 --- a/lib/ui/pages/fileviewer/widgets/sort_sheet.dart +++ b/lib/ui/pages/fileviewer/widgets/sort_sheet.dart @@ -1,5 +1,3 @@ -// TODO: remove sdk version selector after migrating to null-safety. -// @dart=2.10 import 'package:flutter/material.dart'; import 'package:flutter_app/src/config/constants.dart'; import 'package:flutter_app/src/providers/category_provider.dart'; @@ -9,7 +7,7 @@ import 'package:get/get.dart'; import 'package:provider/provider.dart'; class SortSheet extends StatelessWidget { - const SortSheet({Key key}) : super(key: key); + const SortSheet({super.key}); @override Widget build(BuildContext context) => FractionallySizedBox( @@ -49,12 +47,12 @@ class SortSheet extends StatelessWidget { ) : const SizedBox(), title: Text( - "${Constants.sortList[index]}", + Constants.sortList[index], style: TextStyle( fontSize: 14.0, color: index == Provider.of(context, listen: false).sort ? Colors.blue - : Theme.of(context).textTheme.titleLarge.color, + : Theme.of(context).textTheme.titleLarge?.color, ), ), ); diff --git a/pubspec.lock b/pubspec.lock index 24bff18c..ae6d491d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -385,6 +385,22 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.2" + device_info_plus: + dependency: "direct main" + description: + name: device_info_plus + sha256: "1d6e5a61674ba3a68fb048a7c7b4ff4bebfed8d7379dbe8f2b718231be9a7c95" + url: "https://pub.dev" + source: hosted + version: "8.1.0" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64 + url: "https://pub.dev" + source: hosted + version: "7.0.0" dio: dependency: "direct main" description: @@ -1750,5 +1766,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <4.0.0" + dart: ">=2.19.0 <3.0.0" flutter: ">=3.7.0" diff --git a/pubspec.yaml b/pubspec.yaml index e7758c11..b96e3bcf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -83,6 +83,7 @@ dependencies: path: modal_bottom_sheet email_validator: ^2.1.17 weekday_selector: ^1.1.0 + device_info_plus: ^8.1.0 firebase_core: ^2.4.1 firebase_analytics: ^10.1.0