Merge branch 'master' into handle-cases-for-no-password-on-device
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 63 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 16 KiB |
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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',
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|