feat: Add Pending Sync Info Screen and enhance path storage viewer
This commit is contained in:
@@ -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();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user