Merge branch 'master' into handle-cases-for-no-password-on-device

This commit is contained in:
ashilkn 2022-09-05 15:25:34 +05:30
commit 80557f0adf
14 changed files with 237 additions and 131 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -1,6 +1,7 @@
import 'dart:convert';
import 'package:photos/models/file.dart';
import 'package:photos/services/collections_service.dart';
class DuplicateFilesResponse {
final List<DuplicateItems> duplicates;
@ -43,9 +44,22 @@ class DuplicateItems {
class DuplicateFiles {
final List<File> files;
final int size;
static final collectionsService = CollectionsService.instance;
DuplicateFiles(this.files, this.size);
DuplicateFiles(this.files, this.size) {
sortByCollectionName();
}
@override
String toString() => 'DuplicateFiles(files: $files, size: $size)';
sortByCollectionName() {
files.sort((first, second) {
final firstName =
collectionsService.getCollectionNameByID(first.collectionID);
final secondName =
collectionsService.getCollectionNameByID(second.collectionID);
return firstName.compareTo(secondName);
});
}
}

View file

@ -787,6 +787,10 @@ class CollectionsService {
Bus.instance.fire(CollectionUpdatedEvent(toCollectionID, files));
}
String getCollectionNameByID(int collectionID) {
return getCollectionByID(collectionID).name;
}
void _validateMoveRequest(
int toCollectionID,
int fromCollectionID,

View file

@ -37,6 +37,7 @@ class RemoteSyncService {
int _completedUploads = 0;
SharedPreferences _prefs;
Completer<void> _existingSync;
bool _existingSyncSilent = false;
static const kHasSyncedArchiveKey = "has_synced_archive";
@ -73,12 +74,18 @@ class RemoteSyncService {
}
if (_existingSync != null) {
_logger.info("Remote sync already in progress, skipping");
// if current sync is silent but request sync is non-silent (demands UI
// updates), update the syncSilently flag
if (_existingSyncSilent == true && silently == false) {
_existingSyncSilent = false;
}
return _existingSync.future;
}
_existingSync = Completer<void>();
_existingSyncSilent = silently;
try {
await _pullDiff(silently);
await _pullDiff();
// sync trash but consume error during initial launch.
// this is to ensure that we don't pause upload due to any error during
// the trash sync. Impact: We may end up re-uploading a file which was
@ -89,7 +96,7 @@ class RemoteSyncService {
final filesToBeUploaded = await _getFilesToBeUploaded();
final hasUploadedFiles = await _uploadFiles(filesToBeUploaded);
if (hasUploadedFiles) {
await _pullDiff(true);
await _pullDiff();
_existingSync.complete();
_existingSync = null;
final hasMoreFilesToBackup = (await _getFilesToBeUploaded()).isNotEmpty;
@ -118,33 +125,31 @@ class RemoteSyncService {
} else {
_logger.severe("Error executing remote sync ", e, s);
}
} finally {
_existingSyncSilent = false;
}
}
Future<void> _pullDiff(bool silently) async {
Future<void> _pullDiff() async {
final isFirstSync = !_collectionsService.hasSyncedCollections();
await _collectionsService.sync();
if (isFirstSync || _hasReSynced()) {
await _syncUpdatedCollections(silently);
} else {
final syncSinceTime = _getSinceTimeForReSync();
await _resyncAllCollectionsSinceTime(syncSinceTime);
}
if (!_hasReSynced()) {
await _markReSyncAsDone();
// check and reset user's collection syncTime in past for older clients
if (isFirstSync) {
// not need reset syncTime, mark all flags as done if firstSync
await _markResetSyncTimeAsDone();
} else if (_shouldResetSyncTime()) {
_logger.warning('Resetting syncTime for for the client');
await _resetAllCollectionsSyncTime();
await _markResetSyncTimeAsDone();
}
await _syncUpdatedCollections();
unawaited(_localFileUpdateService.markUpdatedFilesForReUpload());
}
Future<void> _syncUpdatedCollections(bool silently) async {
Future<void> _syncUpdatedCollections() async {
final updatedCollections =
await _collectionsService.getCollectionsToBeSynced();
if (updatedCollections.isNotEmpty && !silently) {
Bus.instance.fire(SyncStatusUpdate(SyncStatus.applyingRemoteDiff));
}
for (final c in updatedCollections) {
await _syncCollectionDiff(
c.id,
@ -154,19 +159,21 @@ class RemoteSyncService {
}
}
Future<void> _resyncAllCollectionsSinceTime(int sinceTime) async {
_logger.info('re-sync collections sinceTime: $sinceTime');
Future<void> _resetAllCollectionsSyncTime() async {
final resetSyncTime = _getSinceTimeForReSync();
_logger.info('re-setting all collections syncTime to: $resetSyncTime');
final collections = _collectionsService.getActiveCollections();
for (final c in collections) {
await _syncCollectionDiff(
c.id,
min(_collectionsService.getCollectionSyncTime(c.id), sinceTime),
);
await _collectionsService.setCollectionSyncTime(c.id, c.updationTime);
final int newSyncTime =
min(_collectionsService.getCollectionSyncTime(c.id), resetSyncTime);
await _collectionsService.setCollectionSyncTime(c.id, newSyncTime);
}
}
Future<void> _syncCollectionDiff(int collectionID, int sinceTime) async {
if (!_existingSyncSilent) {
Bus.instance.fire(SyncStatusUpdate(SyncStatus.applyingRemoteDiff));
}
final diff =
await _diffFetcher.getEncryptedFilesDiff(collectionID, sinceTime);
if (diff.deletedFiles.isNotEmpty) {
@ -529,14 +536,18 @@ class RemoteSyncService {
// return true if the client needs to re-sync the collections from previous
// version
bool _hasReSynced() {
return _prefs.containsKey(kHasSyncedEditTime) &&
_prefs.containsKey(kHasSyncedArchiveKey);
bool _shouldResetSyncTime() {
return !_prefs.containsKey(kHasSyncedEditTime) ||
!_prefs.containsKey(kHasSyncedArchiveKey);
}
Future<void> _markReSyncAsDone() async {
Future<void> _markResetSyncTimeAsDone() async {
await _prefs.setBool(kHasSyncedArchiveKey, true);
await _prefs.setBool(kHasSyncedEditTime, true);
// Check to avoid regression because of change or additions of keys
if (_shouldResetSyncTime() == false) {
throw Exception("Has sync should return true after markReSyncAsDone");
}
}
int _getSinceTimeForReSync() {

View file

@ -16,6 +16,9 @@ class GrantPermissionsWidget extends StatelessWidget {
padding: const EdgeInsets.only(top: 20, bottom: 120),
child: Column(
children: [
const SizedBox(
height: 24,
),
Center(
child: Stack(
alignment: Alignment.center,
@ -43,6 +46,7 @@ class GrantPermissionsWidget extends StatelessWidget {
],
),
),
const SizedBox(height: 36),
Padding(
padding: const EdgeInsets.fromLTRB(40, 0, 40, 0),
child: RichText(

View file

@ -103,10 +103,9 @@ class _LoadingPhotosWidgetState extends State<LoadingPhotosWidget> {
color: Colors.white.withOpacity(0.25),
colorBlendMode: BlendMode.modulate,
),
const SizedBox(height: 20),
Column(
children: [
const SizedBox(height: 50),
const SizedBox(height: 24),
Lottie.asset(
'assets/loadingGalleryLottie.json',
height: 400,

View file

@ -6,6 +6,7 @@ import 'package:photos/ente_theme_data.dart';
import 'package:photos/events/user_details_changed_event.dart';
import 'package:photos/models/duplicate_files.dart';
import 'package:photos/models/file.dart';
import 'package:photos/services/collections_service.dart';
import 'package:photos/services/deduplication_service.dart';
import 'package:photos/ui/viewer/file/detail_page.dart';
import 'package:photos/ui/viewer/file/thumbnail_widget.dart';
@ -25,27 +26,19 @@ class DeduplicatePage extends StatefulWidget {
}
class _DeduplicatePageState extends State<DeduplicatePage> {
static const kHeaderRowCount = 3;
static final kDeleteIconOverlay = Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.transparent,
Colors.black.withOpacity(0.6),
],
stops: const [0.75, 1],
),
),
child: Align(
static const crossAxisCount = 4;
static const crossAxisSpacing = 4.0;
static const headerRowCount = 3;
static final selectedOverlay = Container(
color: Colors.black.withOpacity(0.4),
child: const Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.only(right: 8, bottom: 4),
padding: EdgeInsets.only(right: 4, bottom: 4),
child: Icon(
Icons.delete_forever,
size: 18,
color: Colors.red[700],
Icons.check_circle,
size: 24,
color: Colors.white,
),
),
),
@ -88,6 +81,47 @@ class _DeduplicatePageState extends State<DeduplicatePage> {
appBar: AppBar(
elevation: 0,
title: const Text("Deduplicate Files"),
actions: <Widget>[
PopupMenuButton(
constraints: const BoxConstraints(minWidth: 180),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(8),
),
),
onSelected: (value) {
setState(() {
_selectedFiles.clear();
});
},
offset: const Offset(0, 50),
itemBuilder: (BuildContext context) => [
PopupMenuItem(
value: true,
height: 32,
child: Row(
children: [
const Icon(
Icons.remove_circle_outline,
size: 20,
),
const SizedBox(width: 12),
Padding(
padding: const EdgeInsets.only(bottom: 1),
child: Text(
"Deselect all",
style: Theme.of(context)
.textTheme
.subtitle1
.copyWith(fontWeight: FontWeight.w600),
),
),
],
),
)
],
)
],
),
body: _getBody(),
);
@ -130,18 +164,18 @@ class _DeduplicatePageState extends State<DeduplicatePage> {
}
}
return Padding(
padding: const EdgeInsets.only(top: 10, bottom: 10),
padding: const EdgeInsets.symmetric(vertical: 8),
child: _getGridView(
_duplicates[index - kHeaderRowCount],
index - kHeaderRowCount,
_duplicates[index - headerRowCount],
index - headerRowCount,
),
);
},
itemCount: _duplicates.length + kHeaderRowCount,
itemCount: _duplicates.length + headerRowCount,
shrinkWrap: true,
),
),
_selectedFiles.isEmpty ? Container() : _getDeleteButton(),
_selectedFiles.isEmpty ? const SizedBox.shrink() : _getDeleteButton(),
],
);
}
@ -282,40 +316,47 @@ class _DeduplicatePageState extends State<DeduplicatePage> {
return SizedBox(
width: double.infinity,
child: SafeArea(
child: TextButton(
style: OutlinedButton.styleFrom(
backgroundColor: Colors.red[700],
),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
const Padding(padding: EdgeInsets.all(2)),
Text(
text,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
color: Colors.white,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 2),
child: TextButton(
style: OutlinedButton.styleFrom(
backgroundColor:
Theme.of(context).colorScheme.inverseBackgroundColor,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
const Padding(padding: EdgeInsets.all(crossAxisSpacing / 2)),
Text(
text,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
color: Theme.of(context).colorScheme.inverseTextColor,
),
textAlign: TextAlign.center,
),
textAlign: TextAlign.center,
),
const Padding(padding: EdgeInsets.all(2)),
Text(
formatBytes(size),
style: TextStyle(
color: Colors.white.withOpacity(0.7),
fontSize: 12,
const Padding(padding: EdgeInsets.all(2)),
Text(
formatBytes(size),
style: TextStyle(
color: Theme.of(context)
.colorScheme
.inverseTextColor
.withOpacity(0.7),
fontSize: 12,
),
),
),
const Padding(padding: EdgeInsets.all(2)),
],
const Padding(padding: EdgeInsets.all(2)),
],
),
onPressed: () async {
await deleteFilesFromRemoteOnly(context, _selectedFiles.toList());
Bus.instance.fire(UserDetailsChangedEvent());
Navigator.of(context)
.pop(DeduplicationResult(_selectedFiles.length, size));
},
),
onPressed: () async {
await deleteFilesFromRemoteOnly(context, _selectedFiles.toList());
Bus.instance.fire(UserDetailsChangedEvent());
Navigator.of(context)
.pop(DeduplicationResult(_selectedFiles.length, size));
},
),
),
);
@ -326,7 +367,7 @@ class _DeduplicatePageState extends State<DeduplicatePage> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 4, 4),
padding: const EdgeInsets.fromLTRB(2, 4, 4, 12),
child: Text(
duplicates.files.length.toString() +
" files, " +
@ -335,18 +376,23 @@ class _DeduplicatePageState extends State<DeduplicatePage> {
style: Theme.of(context).textTheme.subtitle2,
),
),
GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
// to disable GridView's scrolling
itemBuilder: (context, index) {
return _buildFile(context, duplicates.files[index], itemIndex);
},
itemCount: duplicates.files.length,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
Padding(
padding: const EdgeInsets.symmetric(horizontal: crossAxisSpacing / 2),
child: GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
// to disable GridView's scrolling
itemBuilder: (context, index) {
return _buildFile(context, duplicates.files[index], itemIndex);
},
itemCount: duplicates.files.length,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
crossAxisSpacing: crossAxisSpacing,
childAspectRatio: 0.75,
),
padding: const EdgeInsets.all(0),
),
padding: const EdgeInsets.all(0),
),
],
);
@ -379,31 +425,50 @@ class _DeduplicatePageState extends State<DeduplicatePage> {
forceCustomPageRoute: true,
);
},
child: Container(
margin: const EdgeInsets.all(2.0),
decoration: BoxDecoration(
border: _selectedFiles.contains(file)
? Border.all(
width: 3,
color: Colors.red[700],
)
: null,
),
child: Stack(
children: [
Hero(
tag: "deduplicate_" + file.tag(),
child: ThumbnailWidget(
file,
diskLoadDeferDuration: kThumbnailDiskLoadDeferDuration,
serverLoadDeferDuration: kThumbnailServerLoadDeferDuration,
shouldShowLivePhotoOverlay: true,
key: Key("deduplicate_" + file.tag()),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
//the numerator will give the width of the screen excuding the whitespaces in the the grid row
height: (MediaQuery.of(context).size.width -
(crossAxisSpacing * crossAxisCount)) /
crossAxisCount,
child: Stack(
children: [
Hero(
tag: "deduplicate_" + file.tag(),
child: ClipRRect(
borderRadius: BorderRadius.circular(4),
child: ThumbnailWidget(
file,
diskLoadDeferDuration: kThumbnailDiskLoadDeferDuration,
serverLoadDeferDuration:
kThumbnailServerLoadDeferDuration,
shouldShowLivePhotoOverlay: true,
key: Key("deduplicate_" + file.tag()),
),
),
),
_selectedFiles.contains(file)
? ClipRRect(
borderRadius: BorderRadius.circular(4),
child: selectedOverlay,
)
: const SizedBox.shrink(),
],
),
_selectedFiles.contains(file) ? kDeleteIconOverlay : Container(),
],
),
),
const SizedBox(height: 6),
Padding(
padding: const EdgeInsets.only(right: 2),
child: Text(
CollectionsService.instance
.getCollectionNameByID(file.collectionID),
style: Theme.of(context).textTheme.caption.copyWith(fontSize: 12),
overflow: TextOverflow.ellipsis,
),
),
],
),
);
}

View file

@ -53,6 +53,7 @@ class _FreeSpacePageState extends State<FreeSpacePage> {
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(height: 24),
Stack(
alignment: Alignment.center,
children: [
@ -62,13 +63,20 @@ class _FreeSpacePageState extends State<FreeSpacePage> {
color: Colors.white.withOpacity(0.4),
colorBlendMode: BlendMode.modulate,
)
: Image.asset('assets/loading_photos_background_dark.png'),
Image.asset(
"assets/gallery_locked.png",
height: 160,
: Image.asset(
'assets/loading_photos_background_dark.png',
color: Colors.white.withOpacity(0.25),
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Image.asset(
"assets/gallery_locked.png",
height: 160,
),
),
],
),
const SizedBox(height: 24),
Padding(
padding: const EdgeInsets.only(left: 36, right: 40),
child: Row(

View file

@ -31,18 +31,19 @@ class _LockScreenState extends State<LockScreen> {
Stack(
alignment: Alignment.center,
children: [
Image.asset(
MediaQuery.of(context).platformBrightness == Brightness.light
? 'assets/loading_photos_background.png'
: 'assets/loading_photos_background_dark.png',
Opacity(
opacity: 0.2,
child: Image.asset('assets/loading_photos_background.png'),
),
SizedBox(
width: 172,
width: 142,
child: GradientButton(
text: "Unlock",
iconData: Icons.lock_open_outlined,
paddingValue: 6,
onTap: () async {
_showLockScreen();
},
text: 'Unlock',
),
),
],