소스 검색

Support for showing in-app storage

Neeraj Gupta 2 년 전
부모
커밋
640b8d32b1

+ 2 - 2
lib/ui/advanced_settings_screen.dart

@@ -9,7 +9,7 @@ import 'package:photos/ui/components/icon_button_widget.dart';
 import 'package:photos/ui/components/menu_item_widget.dart';
 import 'package:photos/ui/components/title_bar_title_widget.dart';
 import 'package:photos/ui/components/title_bar_widget.dart';
-import 'package:photos/ui/tools/debug/cache_size_view.dart';
+import 'package:photos/ui/tools/debug/app_storage_viewer.dart';
 import 'package:photos/utils/local_settings.dart';
 import 'package:photos/utils/navigation_util.dart';
 
@@ -99,7 +99,7 @@ class _AdvancedSettingsScreenState extends State<AdvancedSettingsScreen> {
                               borderRadius: 8,
                               alignCaptionedTextToLeft: true,
                               onTap: () {
-                                routeToPage(context, const CacheSizeViewer());
+                                routeToPage(context, const AppStorageViewer());
                               },
                             ),
                           ],

+ 0 - 14
lib/ui/settings/debug_section_widget.dart

@@ -12,8 +12,6 @@ import 'package:photos/ui/components/captioned_text_widget.dart';
 import 'package:photos/ui/components/expandable_menu_item_widget.dart';
 import 'package:photos/ui/components/menu_item_widget.dart';
 import 'package:photos/ui/settings/common_settings.dart';
-import 'package:photos/ui/tools/debug/cache_size_view.dart';
-import 'package:photos/utils/navigation_util.dart';
 import 'package:photos/utils/toast_util.dart';
 
 class DebugSectionWidget extends StatelessWidget {
@@ -58,18 +56,6 @@ class DebugSectionWidget extends StatelessWidget {
           },
         ),
         sectionOptionSpacing,
-        MenuItemWidget(
-          captionedTextWidget: const CaptionedTextWidget(
-            title: "View Cache Detail",
-          ),
-          pressedColor: getEnteColorScheme(context).fillFaint,
-          trailingIcon: Icons.chevron_right_outlined,
-          trailingIconIsMuted: true,
-          onTap: () async {
-            routeToPage(context, const CacheSizeViewer());
-          },
-        ),
-        sectionOptionSpacing,
         MenuItemWidget(
           captionedTextWidget: const CaptionedTextWidget(
             title: "Allow auto-upload for ignored files",

+ 184 - 0
lib/ui/tools/debug/app_storage_viewer.dart

@@ -0,0 +1,184 @@
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_cache_manager/flutter_cache_manager.dart';
+import 'package:path_provider/path_provider.dart';
+import 'package:photos/core/cache/video_cache_manager.dart';
+import 'package:photos/core/configuration.dart';
+import 'package:photos/services/feature_flag_service.dart';
+import 'package:photos/theme/ente_theme.dart';
+import 'package:photos/ui/components/captioned_text_widget.dart';
+import 'package:photos/ui/components/icon_button_widget.dart';
+import 'package:photos/ui/components/menu_item_widget.dart';
+import 'package:photos/ui/components/title_bar_title_widget.dart';
+import 'package:photos/ui/components/title_bar_widget.dart';
+import 'package:photos/ui/tools/debug/path_storage_viewer.dart';
+import 'package:photos/utils/directory_content.dart';
+
+class AppStorageViewer extends StatefulWidget {
+  const AppStorageViewer({Key? key}) : super(key: key);
+
+  @override
+  State<AppStorageViewer> createState() => _AppStorageViewerState();
+}
+
+class _AppStorageViewerState extends State<AppStorageViewer> {
+  final List<PathStorageItem> paths = [];
+  late bool internalUser;
+  int _refreshCounterKey = 0;
+
+  @override
+  void initState() {
+    internalUser = FeatureFlagService.instance.isInternalUserOrDebugBuild();
+    addPath();
+    super.initState();
+  }
+
+  void addPath() async {
+    final appDocumentsDirectory = (await getApplicationDocumentsDirectory());
+    final appSupportDirectory = (await getApplicationSupportDirectory());
+    final appTemporaryDirectory = (await getTemporaryDirectory());
+    final iOSOnlyTempDirectory = "${appDocumentsDirectory.parent.path}/tmp/";
+
+    final String tempDownload = Configuration.instance.getTempDirectory();
+    final String cacheDirectory =
+        Configuration.instance.getThumbnailCacheDirectory();
+    final imageCachePath =
+        appTemporaryDirectory.path + "/" + DefaultCacheManager.key;
+    final videoCachePath =
+        appTemporaryDirectory.path + "/" + VideoCacheManager.key;
+    paths.addAll([
+      PathStorageItem.name(
+        imageCachePath,
+        "Remote images",
+        allowCacheClear: true,
+      ),
+      PathStorageItem.name(
+        videoCachePath,
+        "Remote videos",
+        allowCacheClear: true,
+      ),
+      PathStorageItem.name(
+        cacheDirectory,
+        "Remote thumbnails",
+        allowCacheClear: true,
+      ),
+      PathStorageItem.name(tempDownload, "Pending sync"),
+      PathStorageItem.name(
+        iOSOnlyTempDirectory + "flutter-images",
+        "Local gallery",
+        allowCacheClear: true,
+      ),
+    ]);
+    if (internalUser) {
+      paths.addAll([
+        PathStorageItem.name(appDocumentsDirectory.path, "App Documents"),
+        PathStorageItem.name(appSupportDirectory.path, "App Support"),
+        PathStorageItem.name(appTemporaryDirectory.path, "App Temporary"),
+      ]);
+    }
+    if (mounted) {
+      setState(() => {});
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    debugPrint("$runtimeType building");
+
+    return Scaffold(
+      body: CustomScrollView(
+        primary: false,
+        slivers: <Widget>[
+          TitleBarWidget(
+            flexibleSpaceTitle: const TitleBarTitleWidget(
+              title: "Manage device storage",
+            ),
+            actionIcons: [
+              IconButtonWidget(
+                icon: Icons.close_outlined,
+                iconButtonType: IconButtonType.secondary,
+                onTap: () {
+                  Navigator.pop(context);
+                  if (Navigator.canPop(context)) {
+                    Navigator.pop(context);
+                  }
+                  if (Navigator.canPop(context)) {
+                    Navigator.pop(context);
+                  }
+                },
+              ),
+            ],
+          ),
+          SliverList(
+            delegate: SliverChildBuilderDelegate(
+              (context, index) {
+                return Padding(
+                  padding: const EdgeInsets.symmetric(horizontal: 16),
+                  child: Padding(
+                    padding: const EdgeInsets.symmetric(vertical: 1),
+                    child: Column(
+                      mainAxisSize: MainAxisSize.min,
+                      children: [
+                        Column(
+                          children: [
+                            ListView.builder(
+                              shrinkWrap: true,
+                              // padding: const EdgeInsets.fromLTRB(6, 0, 6, 0),
+                              physics: const ScrollPhysics(),
+                              // to disable GridView's scrolling
+                              itemBuilder: (context, index) {
+                                final path = paths[index];
+                                debugPrint("Showing for path ${path.title}");
+                                return PathStorageViewer(
+                                  path,
+                                  removeTopRadius: index > 0,
+                                  removeBottomRadius: index < paths.length - 1,
+                                  enableDoubleTapClear: internalUser,
+                                  key: ValueKey("$index-$_refreshCounterKey"),
+                                );
+                              },
+                              itemCount: paths.length,
+                            ),
+                            const SizedBox(
+                              height: 24,
+                            ),
+                            MenuItemWidget(
+                              leadingIcon: Icons.delete_sweep_outlined,
+                              captionedTextWidget: const CaptionedTextWidget(
+                                title: "Clear caches",
+                              ),
+                              menuItemColor:
+                                  getEnteColorScheme(context).fillFaint,
+                              borderRadius: 8,
+                              onTap: () async {
+                                for (var pathItem in paths) {
+                                  if (pathItem.allowCacheClear) {
+                                    await deleteDirectoryContents(
+                                      pathItem.path,
+                                    );
+                                  }
+                                }
+                                _refreshCounterKey++;
+                                if (mounted) {
+                                  setState(() => {});
+                                }
+                              },
+                            ),
+                            const SizedBox(
+                              height: 24,
+                            ),
+                          ],
+                        ),
+                      ],
+                    ),
+                  ),
+                );
+              },
+              childCount: 1,
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+}

+ 0 - 93
lib/ui/tools/debug/cache_size_view.dart

@@ -1,93 +0,0 @@
-// @dart=2.9
-
-import 'package:flutter/material.dart';
-import 'package:flutter_cache_manager/flutter_cache_manager.dart';
-import 'package:path_provider/path_provider.dart';
-import 'package:photos/core/cache/video_cache_manager.dart';
-import 'package:photos/core/configuration.dart';
-import 'package:photos/ui/tools/debug/path_storage_viewer.dart';
-
-class CacheSizeViewer extends StatefulWidget {
-  const CacheSizeViewer({Key key}) : super(key: key);
-
-  @override
-  State<CacheSizeViewer> createState() => _CacheSizeViewerState();
-}
-
-class _CacheSizeViewerState extends State<CacheSizeViewer> {
-  final List<PathStorageItem> paths = [];
-
-  @override
-  void initState() {
-    addPath();
-    super.initState();
-  }
-
-  void addPath() async {
-    final appDocumentsDirectory = (await getApplicationDocumentsDirectory());
-    final appSupportDirectory = (await getApplicationSupportDirectory());
-    final appTemporaryDirectory = (await getTemporaryDirectory());
-    final iOSOnlyTempDirectory = "${appDocumentsDirectory.parent.path}/tmp/";
-    final logsDirectory = appSupportDirectory.path + "/logs";
-
-    String tempDownload = Configuration.instance.getTempDirectory();
-    String cacheDirectory = Configuration.instance.getThumbnailCacheDirectory();
-    final imageCachePath =
-        appTemporaryDirectory.path + "/" + DefaultCacheManager.key;
-    final videoCachePath =
-        appTemporaryDirectory.path + "/" + VideoCacheManager.key;
-    paths.addAll([
-      PathStorageItem.name(imageCachePath, "Remote images"),
-      PathStorageItem.name(videoCachePath, "Remote videos"),
-      PathStorageItem.name(cacheDirectory, "Remote thumbnails"),
-      PathStorageItem.name(tempDownload, "Pending sync"),
-      PathStorageItem.name(logsDirectory, "Application logs"),
-      PathStorageItem.name(
-          iOSOnlyTempDirectory + "flutter-images", "Local Gallery"),
-      PathStorageItem.name(appDocumentsDirectory.path, "App Documents"),
-      PathStorageItem.name(appSupportDirectory.path, "App Support"),
-      PathStorageItem.name(appTemporaryDirectory.path, "App Temporary"),
-    ]);
-    appTemporaryDirectory.list().forEach((element) {
-      paths.add(
-        PathStorageItem.name(
-          element.path,
-          "App Temp " + element.path.substring(element.path.length - 10),
-        ),
-      );
-    });
-    if (mounted) {
-      setState(() => {});
-    }
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    return Scaffold(
-      appBar: AppBar(
-        elevation: 0,
-        title: const Text("Storage view"),
-      ),
-      body: _getBody(),
-    );
-  }
-
-  Widget _getBody() {
-    return Container(
-      padding: const EdgeInsets.only(left: 12, top: 8, right: 12),
-      child: SingleChildScrollView(
-        child: ListView.builder(
-          shrinkWrap: true,
-          padding: const EdgeInsets.fromLTRB(6, 0, 6, 0),
-          physics: const ScrollPhysics(),
-          // to disable GridView's scrolling
-          itemBuilder: (context, index) {
-            final path = paths[index];
-            return PathStorageViewer(path);
-          },
-          itemCount: paths.length,
-        ),
-      ),
-    );
-  }
-}

+ 32 - 15
lib/ui/tools/debug/path_storage_viewer.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
@@ -7,8 +5,8 @@ import 'package:photos/theme/ente_theme.dart';
 import 'package:photos/ui/common/loading_widget.dart';
 import 'package:photos/ui/components/captioned_text_widget.dart';
 import 'package:photos/ui/components/menu_item_widget.dart';
-import 'package:photos/utils/app_size.dart';
 import 'package:photos/utils/data_util.dart';
+import 'package:photos/utils/directory_content.dart';
 
 class PathStorageItem {
   final String path;
@@ -23,11 +21,17 @@ class PathStorageItem {
 }
 
 class PathStorageViewer extends StatefulWidget {
-  final PathStorageItem path;
+  final PathStorageItem item;
+  final bool removeTopRadius;
+  final bool removeBottomRadius;
+  final bool enableDoubleTapClear;
 
   const PathStorageViewer(
-    this.path, {
-    Key key,
+    this.item, {
+    this.removeTopRadius = false,
+    this.removeBottomRadius = false,
+    this.enableDoubleTapClear = false,
+    Key? key,
   }) : super(key: key);
 
   @override
@@ -35,16 +39,21 @@ class PathStorageViewer extends StatefulWidget {
 }
 
 class _PathStorageViewerState extends State<PathStorageViewer> {
-  Map<String, int> _logs;
+  Map<String, int>? _logs;
 
   @override
   void initState() {
-    directoryStat(widget.path.path).then((logs) {
+    _initLogs();
+    super.initState();
+  }
+
+  void _initLogs() {
+    debugPrint("Calculate for path ${widget.item.title}");
+    directoryStat(widget.item.path).then((logs) {
       setState(() {
         _logs = logs;
       });
     });
-    super.initState();
   }
 
   @override
@@ -56,13 +65,13 @@ class _PathStorageViewerState extends State<PathStorageViewer> {
     if (_logs == null) {
       return const EnteLoadingWidget();
     }
-    final int fileCount = _logs["fileCount"] ?? -1;
-    final int size = _logs['size'];
+    final int fileCount = _logs!["fileCount"] ?? -1;
+    final int size = _logs!['size'] ?? 0;
 
     return MenuItemWidget(
       alignCaptionedTextToLeft: true,
       captionedTextWidget: CaptionedTextWidget(
-        title: widget.path.title,
+        title: widget.item.title,
         subTitle: '$fileCount',
         subTitleColor: getEnteColorScheme(context).textFaint,
       ),
@@ -72,12 +81,20 @@ class _PathStorageViewerState extends State<PathStorageViewer> {
             .small
             .copyWith(color: getEnteColorScheme(context).textFaint),
       ),
-      trailingIcon: widget.path.allowCacheClear ? Icons.chevron_right : null,
+      borderRadius: 8,
       menuItemColor: getEnteColorScheme(context).fillFaint,
+      isBottomBorderRadiusRemoved: widget.removeBottomRadius,
+      isTopBorderRadiusRemoved: widget.removeTopRadius,
       onTap: () async {
         if (kDebugMode) {
-          await Clipboard.setData(ClipboardData(text: widget.path.path));
-          debugPrint(widget.path.path);
+          await Clipboard.setData(ClipboardData(text: widget.item.path));
+          debugPrint(widget.item.path);
+        }
+      },
+      onDoubleTap: () async {
+        if (widget.item.allowCacheClear && widget.enableDoubleTapClear) {
+          await deleteDirectoryContents(widget.item.path);
+          _initLogs();
         }
       },
     );

+ 11 - 0
lib/utils/app_size.dart → lib/utils/directory_content.dart

@@ -24,6 +24,17 @@ Future<Map<String, int>> directoryStat(String dirPath) async {
   return {'fileCount': fileCount, 'size': totalSizeInBytes};
 }
 
+Future<void> deleteDirectoryContents(String directoryPath) async {
+  // Mark variables as final if they don't need to be modified
+  final directory = Directory(directoryPath);
+  final contents = await directory.list().toList();
+
+  // Iterate through the list and delete each file or directory
+  for (final fileOrDirectory in contents) {
+    await fileOrDirectory.delete();
+  }
+}
+
 Future<int> getFileSize(String path) async {
   final file = File(path);
   return await file.exists() ? await file.length() : 0;