feat: Add Pending Sync Info Screen and enhance path storage viewer

This commit is contained in:
Prateek Sunal
2025-03-28 14:50:05 +05:30
parent 360223bd2f
commit cd2094f75e
4 changed files with 317 additions and 1 deletions

View File

@@ -0,0 +1,126 @@
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:logging/logging.dart';
import 'package:photos/theme/ente_theme.dart';
import 'package:photos/ui/components/captioned_text_widget.dart';
import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart';
import 'package:photos/utils/standalone/data.dart';
import 'package:photos/utils/standalone/directory_content.dart';
class PathInfoStorageItem {
final String path;
final String title;
final bool allowCacheClear;
final String match;
PathInfoStorageItem.name(
this.path,
this.title,
this.match, {
this.allowCacheClear = false,
});
}
class PathInfoStorageViewer extends StatefulWidget {
final PathInfoStorageItem item;
final bool removeTopRadius;
final bool removeBottomRadius;
final bool enableDoubleTapClear;
const PathInfoStorageViewer(
this.item, {
this.removeTopRadius = false,
this.removeBottomRadius = false,
this.enableDoubleTapClear = false,
super.key,
});
@override
State<PathInfoStorageViewer> createState() => _PathInfoStorageViewerState();
}
class _PathInfoStorageViewerState extends State<PathInfoStorageViewer> {
final Logger _logger = Logger((_PathInfoStorageViewerState).toString());
@override
void initState() {
super.initState();
}
void _safeRefresh() async {
if (mounted) {
setState(() => {});
}
}
@override
Widget build(BuildContext context) {
return FutureBuilder<DirectoryStat>(
future: getDirectoryStat(Directory(widget.item.path)),
builder: (context, snapshot) {
if (snapshot.hasData) {
return _buildMenuItemWidget(snapshot.data, null);
} else if (snapshot.hasError) {
_logger.severe(
"Failed to get state for ${widget.item.title}",
snapshot.error,
);
return _buildMenuItemWidget(null, snapshot.error);
} else {
return _buildMenuItemWidget(null, null);
}
},
);
}
Widget _buildMenuItemWidget(DirectoryStat? stat, Object? err) {
return MenuItemWidget(
key: UniqueKey(),
alignCaptionedTextToLeft: true,
captionedTextWidget: CaptionedTextWidget(
title: widget.item.title,
subTitle: stat != null ? '${stat.fileCount}' : null,
subTitleColor: getEnteColorScheme(context).textFaint,
),
trailingWidget: stat != null
? Padding(
padding: const EdgeInsets.only(left: 12.0),
child: Text(
formatBytes(stat.size),
style: getEnteTextTheme(context)
.small
.copyWith(color: getEnteColorScheme(context).textFaint),
),
)
: SizedBox.fromSize(
size: const Size.square(14),
child: CircularProgressIndicator(
strokeWidth: 2,
color: getEnteColorScheme(context).strokeMuted,
),
),
trailingIcon: err != null ? Icons.error_outline_outlined : null,
trailingIconIsMuted: err != null,
singleBorderRadius: 8,
menuItemColor: getEnteColorScheme(context).fillFaint,
isBottomBorderRadiusRemoved: widget.removeBottomRadius,
isTopBorderRadiusRemoved: widget.removeTopRadius,
showOnlyLoadingState: true,
onTap: () async {
if (kDebugMode) {
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);
_safeRefresh();
}
},
);
}
}

View File

@@ -0,0 +1,173 @@
import "package:flutter/material.dart";
import "package:photos/core/configuration.dart";
import "package:photos/generated/l10n.dart";
import "package:photos/service_locator.dart";
import "package:photos/ui/components/buttons/icon_button_widget.dart";
import "package:photos/ui/components/menu_section_title.dart";
import "package:photos/ui/components/title_bar_title_widget.dart";
import "package:photos/ui/components/title_bar_widget.dart";
import "package:photos/ui/settings/pending_sync/path_info_storage_viewer.dart";
// Preview Video related items -> pv
// final String tempDir = Configuration.instance.getTempDirectory();
// final String prefix = "${tempDir}_${enteFile.uploadedFileID}_${newID("pv")}";
//
// Recovery Key -> ente-recovery-key.txt
// Configuration.instance.getTempDirectory() + "ente-recovery-key.txt",
//
// Encrypted files (upload), decrypted files (download) -> .encrypted & .decrypted
// final String tempDir = Configuration.instance.getTempDirectory();
// final String encryptedFilePath = "$tempDir${file.uploadedFileID}.encrypted";
// final String decryptedFilePath = "$tempDir${file.uploadedFileID}.decrypted";
//
// Live photo compressed version -> .elp
// final livePhotoPath = tempPath + uniqueId + "_${file.generatedID}.elp";
//
// Explicit uploads -> _file.encrpyted & _thumb.encrypted
// final encryptedFilePath = multipartEntryExists
// ? '$tempDirectory$existingMultipartEncFileName'
// : '$tempDirectory$uploadTempFilePrefix${uniqueID}_file.encrypted';
// final encryptedThumbnailPath =
// '$tempDirectory$uploadTempFilePrefix${uniqueID}_thumb.encrypted';
class PendingSyncInfoScreen extends StatefulWidget {
const PendingSyncInfoScreen({super.key});
@override
State<PendingSyncInfoScreen> createState() => _PendingSyncInfoScreenState();
}
class _PendingSyncInfoScreenState extends State<PendingSyncInfoScreen> {
final List<PathInfoStorageItem> paths = [];
late bool internalUser;
final int _refreshCounterKey = 0;
@override
void initState() {
super.initState();
internalUser = flagService.internalUser;
addPath();
}
void addPath() async {
final String tempDownload = Configuration.instance.getTempDirectory();
paths.addAll([
PathInfoStorageItem.name(
tempDownload,
"Explicit file uploads",
"_file.encrpyted",
allowCacheClear: false,
),
PathInfoStorageItem.name(
tempDownload,
"Explicit thumb uploads",
"_thumb.encrypted",
allowCacheClear: false,
),
PathInfoStorageItem.name(
tempDownload,
"Live photo compressed",
".elp",
allowCacheClear: false,
),
PathInfoStorageItem.name(
tempDownload,
"Encrypted files (any)",
".encrypted",
allowCacheClear: false,
),
PathInfoStorageItem.name(
tempDownload,
"Decrypted files (any)",
".decrypted",
allowCacheClear: false,
),
PathInfoStorageItem.name(
tempDownload,
"Recovery Key",
"ente-recovery-key.txt",
allowCacheClear: false,
),
PathInfoStorageItem.name(
tempDownload,
"Preview Related",
"pv",
allowCacheClear: false,
),
]);
if (mounted) {
setState(() => {});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
primary: false,
slivers: <Widget>[
TitleBarWidget(
flexibleSpaceTitle: const TitleBarTitleWidget(title: "App Temp"),
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: Column(
mainAxisSize: MainAxisSize.min,
children: [
Column(
children: [
MenuSectionTitle(
title: S.of(context).cachedData,
),
ListView.builder(
shrinkWrap: true,
padding: const EdgeInsets.all(0),
physics: const ScrollPhysics(),
// to disable GridView's scrolling
itemBuilder: (context, index) {
final path = paths[index];
return PathInfoStorageViewer(
path,
removeTopRadius: index > 0,
removeBottomRadius: index < paths.length - 1,
enableDoubleTapClear: internalUser,
key: ValueKey("$index-$_refreshCounterKey"),
);
},
itemCount: paths.length,
),
const SizedBox(
height: 24,
),
],
),
],
),
);
},
childCount: 1,
),
),
],
),
);
}
}

View File

@@ -4,9 +4,11 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:logging/logging.dart';
import "package:photos/generated/l10n.dart";
import 'package:photos/theme/ente_theme.dart';
import 'package:photos/ui/components/captioned_text_widget.dart';
import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart';
import "package:photos/ui/settings/pending_sync/pending_sync_info_screen.dart";
import 'package:photos/utils/standalone/data.dart';
import 'package:photos/utils/standalone/directory_content.dart';
@@ -112,6 +114,14 @@ class _PathStorageViewerState extends State<PathStorageViewer> {
await Clipboard.setData(ClipboardData(text: widget.item.path));
debugPrint(widget.item.path);
}
if (widget.item.title == S.of(context).pendingSync) {
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const PendingSyncInfoScreen(),
),
);
}
},
onDoubleTap: () async {
if (widget.item.allowCacheClear && widget.enableDoubleTapClear) {

View File

@@ -81,7 +81,10 @@ void _buildPrettyString(
}
}
Future<DirectoryStat> getDirectoryStat(Directory directory) async {
Future<DirectoryStat> getDirectoryStat(
Directory directory, {
String? prefix,
}) async {
int size = 0;
final List<DirectoryStat> subDirectories = [];
final Map<String, int> fileNameToSize = {};
@@ -89,6 +92,10 @@ Future<DirectoryStat> getDirectoryStat(Directory directory) async {
if (await directory.exists()) {
final List<FileSystemEntity> entities = directory.listSync();
for (FileSystemEntity entity in entities) {
if (prefix != null && !entity.path.contains(prefix)) {
continue;
}
if (entity is File) {
final int fileSize = await File(entity.path).length();
size += fileSize;