resolved merge conflicts

This commit is contained in:
ashilkn 2022-06-11 14:41:08 +05:30
commit b711dfe715
142 changed files with 4006 additions and 2612 deletions

View file

@ -14,6 +14,7 @@ linter:
- prefer_const_constructors_in_immutables
- prefer_const_declarations
- prefer_const_literals_to_create_immutables
- require_trailing_commas
- sized_box_for_whitespace
- use_full_hex_values_for_flutter_colors
- use_key_in_widget_constructors

View file

@ -24,7 +24,9 @@ final lightThemeData = ThemeData(
iconTheme: IconThemeData(color: Colors.black),
primaryIconTheme: IconThemeData(color: Colors.red, opacity: 1.0, size: 50.0),
colorScheme: ColorScheme.light(
primary: Colors.black, secondary: Color.fromARGB(255, 163, 163, 163)),
primary: Colors.black,
secondary: Color.fromARGB(255, 163, 163, 163),
),
accentColor: Color.fromRGBO(0, 0, 0, 0.6),
buttonColor: Color.fromRGBO(45, 194, 98, 1.0),
outlinedButtonTheme: buildOutlinedButtonThemeData(
@ -34,7 +36,9 @@ final lightThemeData = ThemeData(
fgEnabled: Colors.white,
),
elevatedButtonTheme: buildElevatedButtonThemeData(
onPrimary: Colors.white, primary: Colors.black),
onPrimary: Colors.white,
primary: Colors.black,
),
toggleableActiveColor: Colors.green[400],
scaffoldBackgroundColor: Colors.white,
backgroundColor: Colors.white,
@ -47,19 +51,25 @@ final lightThemeData = ThemeData(
//https://api.flutter.dev/flutter/material/TextTheme-class.html
textTheme: _buildTextTheme(Colors.black),
primaryTextTheme: TextTheme().copyWith(
bodyText2: TextStyle(color: Colors.yellow),
bodyText1: TextStyle(color: Colors.orange)),
bodyText2: TextStyle(color: Colors.yellow),
bodyText1: TextStyle(color: Colors.orange),
),
cardColor: Color.fromRGBO(250, 250, 250, 1.0),
dialogTheme: DialogTheme().copyWith(
backgroundColor: Color.fromRGBO(250, 250, 250, 1.0), //
titleTextStyle: TextStyle(
color: Colors.black, fontSize: 24, fontWeight: FontWeight.w600),
contentTextStyle: TextStyle(
fontFamily: 'Inter-Medium',
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.w500),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))),
backgroundColor: Color.fromRGBO(250, 250, 250, 1.0), //
titleTextStyle: TextStyle(
color: Colors.black,
fontSize: 24,
fontWeight: FontWeight.w600,
),
contentTextStyle: TextStyle(
fontFamily: 'Inter-Medium',
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.w500,
),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
inputDecorationTheme: InputDecorationTheme().copyWith(
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
@ -101,12 +111,15 @@ final darkThemeData = ThemeData(
textTheme: _buildTextTheme(Colors.white),
toggleableActiveColor: Colors.green[400],
outlinedButtonTheme: buildOutlinedButtonThemeData(
bgDisabled: Colors.grey.shade500,
bgEnabled: Colors.white,
fgDisabled: Colors.white,
fgEnabled: Colors.black),
bgDisabled: Colors.grey.shade500,
bgEnabled: Colors.white,
fgDisabled: Colors.white,
fgEnabled: Colors.black,
),
elevatedButtonTheme: buildElevatedButtonThemeData(
onPrimary: Colors.black, primary: Colors.white),
onPrimary: Colors.black,
primary: Colors.white,
),
scaffoldBackgroundColor: Colors.black,
backgroundColor: Colors.black,
appBarTheme: AppBarTheme().copyWith(

View file

@ -8,9 +8,11 @@ class ThumbnailLruCache {
static final LRUMap<String, Uint8List> _map = LRUMap(1000);
static Uint8List get(File photo, [int size]) {
return _map.get(photo.generatedID.toString() +
"_" +
(size != null ? size.toString() : kThumbnailLargeSize.toString()));
return _map.get(
photo.generatedID.toString() +
"_" +
(size != null ? size.toString() : kThumbnailLargeSize.toString()),
);
}
static void put(
@ -19,16 +21,19 @@ class ThumbnailLruCache {
int size,
]) {
_map.put(
photo.generatedID.toString() +
"_" +
(size != null ? size.toString() : kThumbnailLargeSize.toString()),
imageData);
photo.generatedID.toString() +
"_" +
(size != null ? size.toString() : kThumbnailLargeSize.toString()),
imageData,
);
}
static void clearCache(File file) {
_map.remove(
file.generatedID.toString() + "_" + kThumbnailLargeSize.toString());
file.generatedID.toString() + "_" + kThumbnailLargeSize.toString(),
);
_map.remove(
file.generatedID.toString() + "_" + kThumbnailSmallSize.toString());
file.generatedID.toString() + "_" + kThumbnailSmallSize.toString(),
);
}
}

View file

@ -187,8 +187,11 @@ class Configuration {
Sodium.bin2base64(encryptedRecoveryKey.encryptedData),
Sodium.bin2base64(encryptedRecoveryKey.nonce),
);
final privateAttributes = PrivateKeyAttributes(Sodium.bin2base64(masterKey),
Sodium.bin2hex(recoveryKey), Sodium.bin2base64(keyPair.sk));
final privateAttributes = PrivateKeyAttributes(
Sodium.bin2base64(masterKey),
Sodium.bin2hex(recoveryKey),
Sodium.bin2base64(keyPair.sk),
);
return KeyGenResult(attributes, privateAttributes);
}
@ -218,7 +221,9 @@ class Configuration {
}
Future<void> decryptAndSaveSecrets(
String password, KeyAttributes attributes) async {
String password,
KeyAttributes attributes,
) async {
final kek = await CryptoUtil.deriveKey(
utf8.encode(password),
Sodium.base642bin(attributes.kekSalt),
@ -227,23 +232,29 @@ class Configuration {
);
Uint8List key;
try {
key = CryptoUtil.decryptSync(Sodium.base642bin(attributes.encryptedKey),
kek, Sodium.base642bin(attributes.keyDecryptionNonce));
key = CryptoUtil.decryptSync(
Sodium.base642bin(attributes.encryptedKey),
kek,
Sodium.base642bin(attributes.keyDecryptionNonce),
);
} catch (e) {
throw Exception("Incorrect password");
}
await setKey(Sodium.bin2base64(key));
final secretKey = CryptoUtil.decryptSync(
Sodium.base642bin(attributes.encryptedSecretKey),
key,
Sodium.base642bin(attributes.secretKeyDecryptionNonce));
Sodium.base642bin(attributes.encryptedSecretKey),
key,
Sodium.base642bin(attributes.secretKeyDecryptionNonce),
);
await setSecretKey(Sodium.bin2base64(secretKey));
final token = CryptoUtil.openSealSync(
Sodium.base642bin(getEncryptedToken()),
Sodium.base642bin(attributes.publicKey),
secretKey);
Sodium.base642bin(getEncryptedToken()),
Sodium.base642bin(attributes.publicKey),
secretKey,
);
await setToken(
Sodium.bin2base64(token, variant: Sodium.base64VariantUrlsafe));
Sodium.bin2base64(token, variant: Sodium.base64VariantUrlsafe),
);
}
Future<KeyAttributes> createNewRecoveryKey() async {
@ -272,7 +283,8 @@ class Configuration {
if (recoveryKey.contains(' ')) {
if (recoveryKey.split(' ').length != kMnemonicKeyWordCount) {
throw AssertionError(
'recovery code should have $kMnemonicKeyWordCount words');
'recovery code should have $kMnemonicKeyWordCount words',
);
}
recoveryKey = bip39.mnemonicToEntropy(recoveryKey);
}
@ -280,25 +292,29 @@ class Configuration {
Uint8List masterKey;
try {
masterKey = await CryptoUtil.decrypt(
Sodium.base642bin(attributes.masterKeyEncryptedWithRecoveryKey),
Sodium.hex2bin(recoveryKey),
Sodium.base642bin(attributes.masterKeyDecryptionNonce));
Sodium.base642bin(attributes.masterKeyEncryptedWithRecoveryKey),
Sodium.hex2bin(recoveryKey),
Sodium.base642bin(attributes.masterKeyDecryptionNonce),
);
} catch (e) {
_logger.severe(e);
rethrow;
}
await setKey(Sodium.bin2base64(masterKey));
final secretKey = CryptoUtil.decryptSync(
Sodium.base642bin(attributes.encryptedSecretKey),
masterKey,
Sodium.base642bin(attributes.secretKeyDecryptionNonce));
Sodium.base642bin(attributes.encryptedSecretKey),
masterKey,
Sodium.base642bin(attributes.secretKeyDecryptionNonce),
);
await setSecretKey(Sodium.bin2base64(secretKey));
final token = CryptoUtil.openSealSync(
Sodium.base642bin(getEncryptedToken()),
Sodium.base642bin(attributes.publicKey),
secretKey);
Sodium.base642bin(getEncryptedToken()),
Sodium.base642bin(attributes.publicKey),
secretKey,
);
await setToken(
Sodium.bin2base64(token, variant: Sodium.base64VariantUrlsafe));
Sodium.bin2base64(token, variant: Sodium.base64VariantUrlsafe),
);
}
String getHttpEndpoint() {
@ -428,9 +444,10 @@ class Configuration {
Uint8List getRecoveryKey() {
final keyAttributes = getKeyAttributes();
return CryptoUtil.decryptSync(
Sodium.base642bin(keyAttributes.recoveryKeyEncryptedWithMasterKey),
getKey(),
Sodium.base642bin(keyAttributes.recoveryKeyDecryptionNonce));
Sodium.base642bin(keyAttributes.recoveryKeyEncryptedWithMasterKey),
getKey(),
Sodium.base642bin(keyAttributes.recoveryKeyDecryptionNonce),
);
}
String getDocumentsDirectory() {
@ -551,7 +568,9 @@ class Configuration {
iOptions: _secureStorageOptionsIOS,
);
await _preferences.setBool(
hasMigratedSecureStorageToFirstUnlockKey, true);
hasMigratedSecureStorageToFirstUnlockKey,
true,
);
}
}

View file

@ -63,7 +63,8 @@ class TunneledTransport implements Transport {
}
Future<StreamedRequest> _createStreamedRequest(
SentryEnvelope envelope) async {
SentryEnvelope envelope,
) async {
final streamedRequest = StreamedRequest('POST', _tunnel);
envelope
.envelopeStream(_options)
@ -88,7 +89,10 @@ class _CredentialBuilder {
_clock = clock;
factory _CredentialBuilder(
Dsn dsn, String sdkIdentifier, ClockProvider clock) {
Dsn dsn,
String sdkIdentifier,
ClockProvider clock,
) {
final authHeader = _buildAuthHeader(
publicKey: dsn.publicKey,
secretKey: dsn.secretKey,

View file

@ -17,11 +17,16 @@ class Network {
_alice = Alice(darkTheme: true, showNotification: kDebugMode);
await FkUserAgent.init();
final packageInfo = await PackageInfo.fromPlatform();
_dio = Dio(BaseOptions(connectTimeout: kConnectTimeout, headers: {
HttpHeaders.userAgentHeader: FkUserAgent.userAgent,
'X-Client-Version': packageInfo.version,
'X-Client-Package': packageInfo.packageName,
}));
_dio = Dio(
BaseOptions(
connectTimeout: kConnectTimeout,
headers: {
HttpHeaders.userAgentHeader: FkUserAgent.userAgent,
'X-Client-Version': packageInfo.version,
'X-Client-Package': packageInfo.packageName,
},
),
);
_dio.interceptors.add(RequestIdInterceptor());
_dio.interceptors.add(_alice.getDioInterceptor());
}

View file

@ -44,7 +44,9 @@ class CollectionsDB {
];
final dbConfig = MigrationConfig(
initializationScript: intitialScript, migrationScripts: migrationScripts);
initializationScript: intitialScript,
migrationScripts: migrationScripts,
);
CollectionsDB._privateConstructor();
@ -157,8 +159,11 @@ class CollectionsDB {
final db = await instance.database;
var batch = db.batch();
for (final collection in collections) {
batch.insert(table, _getRowForCollection(collection),
conflictAlgorithm: ConflictAlgorithm.replace);
batch.insert(
table,
_getRowForCollection(collection),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
return await batch.commit();
}
@ -239,12 +244,15 @@ class CollectionsDB {
pathDecryptionNonce: row[columnPathDecryptionNonce],
version: row[columnVersion],
),
List<User>.from((json.decode(row[columnSharees]) as List)
.map((x) => User.fromMap(x))),
List<User>.from(
(json.decode(row[columnSharees]) as List).map((x) => User.fromMap(x)),
),
row[columnPublicURLs] == null
? []
: List<PublicURL>.from((json.decode(row[columnPublicURLs]) as List)
.map((x) => PublicURL.fromMap(x))),
: List<PublicURL>.from(
(json.decode(row[columnPublicURLs]) as List)
.map((x) => PublicURL.fromMap(x)),
),
int.parse(row[columnUpdationTime]),
// default to False is columnIsDeleted is not set
isDeleted: (row[columnIsDeleted] ?? _sqlBoolFalse) == _sqlBoolTrue,

View file

@ -14,12 +14,14 @@ class FilesMigrationDB {
static final columnLocalID = 'local_id';
Future _onCreate(Database db, int version) async {
await db.execute('''
await db.execute(
'''
CREATE TABLE $tableName (
$columnLocalID TEXT NOT NULL,
UNIQUE($columnLocalID)
);
''');
''',
);
}
FilesMigrationDB._privateConstructor();
@ -73,10 +75,13 @@ class FilesMigrationDB {
await batch.commit(noResult: true);
final endTime = DateTime.now();
final duration = Duration(
microseconds:
endTime.microsecondsSinceEpoch - startTime.microsecondsSinceEpoch);
_logger.info("Batch insert of ${fileLocalIDs.length} "
"took ${duration.inMilliseconds} ms.");
microseconds:
endTime.microsecondsSinceEpoch - startTime.microsecondsSinceEpoch,
);
_logger.info(
"Batch insert of ${fileLocalIDs.length} "
"took ${duration.inMilliseconds} ms.",
);
}
Future<int> deleteByLocalIDs(List<String> localIDs) async {

View file

@ -76,8 +76,9 @@ class FilesDB {
];
final dbConfig = MigrationConfig(
initializationScript: initializationScript,
migrationScripts: migrationScripts);
initializationScript: initializationScript,
migrationScripts: migrationScripts,
);
// make this a singleton class
FilesDB._privateConstructor();
@ -319,13 +320,16 @@ class FilesDB {
await batch.commit(noResult: true);
final endTime = DateTime.now();
final duration = Duration(
microseconds:
endTime.microsecondsSinceEpoch - startTime.microsecondsSinceEpoch);
_logger.info("Batch insert of " +
files.length.toString() +
" took " +
duration.inMilliseconds.toString() +
"ms.");
microseconds:
endTime.microsecondsSinceEpoch - startTime.microsecondsSinceEpoch,
);
_logger.info(
"Batch insert of " +
files.length.toString() +
" took " +
duration.inMilliseconds.toString() +
"ms.",
);
}
Future<int> insert(File file) async {
@ -339,8 +343,11 @@ class FilesDB {
Future<File> getFile(int generatedID) async {
final db = await instance.database;
final results = await db.query(table,
where: '$columnGeneratedID = ?', whereArgs: [generatedID]);
final results = await db.query(
table,
where: '$columnGeneratedID = ?',
whereArgs: [generatedID],
);
if (results.isEmpty) {
return null;
}
@ -398,11 +405,14 @@ class FilesDB {
}
Future<FileLoadResult> getAllUploadedFiles(
int startTime, int endTime, int ownerID,
{int limit,
bool asc,
int visibility = kVisibilityVisible,
Set<int> ignoredCollectionIDs}) async {
int startTime,
int endTime,
int ownerID, {
int limit,
bool asc,
int visibility = kVisibilityVisible,
Set<int> ignoredCollectionIDs,
}) async {
final db = await instance.database;
final order = (asc ?? false ? 'ASC' : 'DESC');
final results = await db.query(
@ -422,8 +432,13 @@ class FilesDB {
}
Future<FileLoadResult> getAllLocalAndUploadedFiles(
int startTime, int endTime, int ownerID,
{int limit, bool asc, Set<int> ignoredCollectionIDs}) async {
int startTime,
int endTime,
int ownerID, {
int limit,
bool asc,
Set<int> ignoredCollectionIDs,
}) async {
final db = await instance.database;
final order = (asc ?? false ? 'ASC' : 'DESC');
final results = await db.query(
@ -443,8 +458,14 @@ class FilesDB {
}
Future<FileLoadResult> getImportantFiles(
int startTime, int endTime, int ownerID, List<String> paths,
{int limit, bool asc, Set<int> ignoredCollectionIDs}) async {
int startTime,
int endTime,
int ownerID,
List<String> paths, {
int limit,
bool asc,
Set<int> ignoredCollectionIDs,
}) async {
final db = await instance.database;
String inParam = "";
for (final path in paths) {
@ -469,7 +490,9 @@ class FilesDB {
}
List<File> _deduplicatedAndFilterIgnoredFiles(
List<File> files, Set<int> ignoredCollectionIDs) {
List<File> files,
Set<int> ignoredCollectionIDs,
) {
final uploadedFileIDs = <int>{};
final List<File> deduplicatedFiles = [];
for (final file in files) {
@ -488,8 +511,12 @@ class FilesDB {
}
Future<FileLoadResult> getFilesInCollection(
int collectionID, int startTime, int endTime,
{int limit, bool asc}) async {
int collectionID,
int startTime,
int endTime, {
int limit,
bool asc,
}) async {
final db = await instance.database;
final order = (asc ?? false ? 'ASC' : 'DESC');
final results = await db.query(
@ -506,8 +533,13 @@ class FilesDB {
return FileLoadResult(files, files.length == limit);
}
Future<FileLoadResult> getFilesInPath(String path, int startTime, int endTime,
{int limit, bool asc}) async {
Future<FileLoadResult> getFilesInPath(
String path,
int startTime,
int endTime, {
int limit,
bool asc,
}) async {
final db = await instance.database;
final order = (asc ?? false ? 'ASC' : 'DESC');
final results = await db.query(
@ -547,7 +579,8 @@ class FilesDB {
}
Future<List<File>> getFilesCreatedWithinDurations(
List<List<int>> durations) async {
List<List<int>> durations,
) async {
final db = await instance.database;
String whereClause = "";
for (int index = 0; index < durations.length; index++) {
@ -569,7 +602,8 @@ class FilesDB {
}
Future<List<File>> getFilesToBeUploadedWithinFolders(
Set<String> folders) async {
Set<String> folders,
) async {
if (folders.isEmpty) {
return [];
}
@ -604,10 +638,12 @@ class FilesDB {
var files = _convertToFiles(results);
// future-safe filter just to ensure that the query doesn't end up returning files
// which should not be backed up
files.removeWhere((e) =>
e.collectionID == null ||
e.localID == null ||
e.uploadedFileID != null);
files.removeWhere(
(e) =>
e.collectionID == null ||
e.localID == null ||
e.uploadedFileID != null,
);
return files;
}
@ -801,11 +837,13 @@ class FilesDB {
}
inParam = inParam.substring(0, inParam.length - 1);
final db = await instance.database;
await db.rawQuery('''
await db.rawQuery(
'''
UPDATE $table
SET $columnLocalID = NULL
WHERE $columnLocalID IN ($inParam);
''');
''',
);
}
Future<List<File>> getLocalFiles(List<String> localIDs) async {
@ -846,7 +884,9 @@ class FilesDB {
}
Future<int> deleteFilesFromCollection(
int collectionID, List<int> uploadedFileIDs) async {
int collectionID,
List<int> uploadedFileIDs,
) async {
final db = await instance.database;
return db.delete(
table,
@ -858,15 +898,21 @@ class FilesDB {
Future<int> collectionFileCount(int collectionID) async {
final db = await instance.database;
var count = Sqflite.firstIntValue(await db.rawQuery(
'SELECT COUNT(*) FROM $table where $columnCollectionID = $collectionID'));
var count = Sqflite.firstIntValue(
await db.rawQuery(
'SELECT COUNT(*) FROM $table where $columnCollectionID = $collectionID',
),
);
return count;
}
Future<int> fileCountWithVisibility(int visibility, int ownerID) async {
final db = await instance.database;
var count = Sqflite.firstIntValue(await db.rawQuery(
'SELECT COUNT(*) FROM $table where $columnMMdVisibility = $visibility AND $columnOwnerID = $ownerID'));
var count = Sqflite.firstIntValue(
await db.rawQuery(
'SELECT COUNT(*) FROM $table where $columnMMdVisibility = $visibility AND $columnOwnerID = $ownerID',
),
);
return count;
}
@ -891,7 +937,8 @@ class FilesDB {
Future<List<File>> getLatestLocalFiles() async {
final db = await instance.database;
final rows = await db.rawQuery('''
final rows = await db.rawQuery(
'''
SELECT $table.*
FROM $table
INNER JOIN
@ -903,7 +950,8 @@ class FilesDB {
) latest_files
ON $table.$columnDeviceFolder = latest_files.$columnDeviceFolder
AND $table.$columnCreationTime = latest_files.max_creation_time;
''');
''',
);
final files = _convertToFiles(rows);
// TODO: Do this de-duplication within the SQL Query
final folderMap = <String, File>{};
@ -920,7 +968,8 @@ class FilesDB {
Future<List<File>> getLatestCollectionFiles() async {
final db = await instance.database;
final rows = await db.rawQuery('''
final rows = await db.rawQuery(
'''
SELECT $table.*
FROM $table
INNER JOIN
@ -932,7 +981,8 @@ class FilesDB {
) latest_files
ON $table.$columnCollectionID = latest_files.$columnCollectionID
AND $table.$columnCreationTime = latest_files.max_creation_time;
''');
''',
);
final files = _convertToFiles(rows);
// TODO: Do this de-duplication within the SQL Query
final collectionMap = <int, File>{};
@ -965,12 +1015,14 @@ class FilesDB {
Future<Map<String, int>> getFileCountInDeviceFolders() async {
final db = await instance.database;
final rows = await db.rawQuery('''
final rows = await db.rawQuery(
'''
SELECT COUNT($columnGeneratedID) as count, $columnDeviceFolder
FROM $table
WHERE $columnLocalID IS NOT NULL
GROUP BY $columnDeviceFolder
''');
''',
);
final result = <String, int>{};
for (final row in rows) {
result[row[columnDeviceFolder]] = row["count"];
@ -1005,16 +1057,20 @@ class FilesDB {
}
inParam = inParam.substring(0, inParam.length - 1);
final db = await instance.database;
await db.rawUpdate('''
await db.rawUpdate(
'''
UPDATE $table
SET $columnUpdationTime = NULL
WHERE $columnLocalID IN ($inParam)
AND ($columnLatitude IS NULL OR $columnLongitude IS NULL OR $columnLongitude = 0.0 or $columnLongitude = 0.0);
''');
''',
);
}
Future<bool> doesFileExistInCollection(
int uploadedFileID, int collectionID) async {
int uploadedFileID,
int collectionID,
) async {
final db = await instance.database;
final rows = await db.query(
table,

View file

@ -22,7 +22,8 @@ class IgnoredFilesDB {
static final columnReason = 'reason';
Future _onCreate(Database db, int version) async {
await db.execute('''
await db.execute(
'''
CREATE TABLE $tableName (
$columnLocalID TEXT NOT NULL,
$columnTitle TEXT NOT NULL,
@ -32,7 +33,8 @@ class IgnoredFilesDB {
);
CREATE INDEX IF NOT EXISTS local_id_index ON $tableName($columnLocalID);
CREATE INDEX IF NOT EXISTS device_folder_index ON $tableName($columnDeviceFolder);
''');
''',
);
}
IgnoredFilesDB._privateConstructor();
@ -85,10 +87,13 @@ class IgnoredFilesDB {
await batch.commit(noResult: true);
final endTime = DateTime.now();
final duration = Duration(
microseconds:
endTime.microsecondsSinceEpoch - startTime.microsecondsSinceEpoch);
_logger.info("Batch insert of ${ignoredFiles.length} "
"took ${duration.inMilliseconds} ms.");
microseconds:
endTime.microsecondsSinceEpoch - startTime.microsecondsSinceEpoch,
);
_logger.info(
"Batch insert of ${ignoredFiles.length} "
"took ${duration.inMilliseconds} ms.",
);
}
Future<List<IgnoredFile>> getAll() async {
@ -102,8 +107,12 @@ class IgnoredFilesDB {
}
IgnoredFile _getIgnoredFileFromRow(Map<String, dynamic> row) {
return IgnoredFile(row[columnLocalID], row[columnTitle],
row[columnDeviceFolder], row[columnReason]);
return IgnoredFile(
row[columnLocalID],
row[columnTitle],
row[columnDeviceFolder],
row[columnReason],
);
}
Map<String, dynamic> _getRowForIgnoredFile(IgnoredFile ignoredFile) {

View file

@ -35,12 +35,14 @@ class MemoriesDB {
}
Future _onCreate(Database db, int version) async {
await db.execute('''
await db.execute(
'''
CREATE TABLE $table (
$columnFileID INTEGER PRIMARY KEY NOT NULL,
$columnSeenTime TEXT NOT NULL
)
''');
''',
);
}
Future<void> clearTable() async {
@ -59,8 +61,11 @@ class MemoriesDB {
Future<int> markMemoryAsSeen(Memory memory, int timestamp) async {
final db = await instance.database;
return await db.insert(table, _getRowForSeenMemory(memory, timestamp),
conflictAlgorithm: ConflictAlgorithm.replace);
return await db.insert(
table,
_getRowForSeenMemory(memory, timestamp),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
Future<Map<int, int>> getSeenTimes() async {

View file

@ -36,12 +36,14 @@ class PublicKeysDB {
}
Future _onCreate(Database db, int version) async {
await db.execute('''
await db.execute(
'''
CREATE TABLE $table (
$columnEmail TEXT PRIMARY KEY NOT NULL,
$columnPublicKey TEXT NOT NULL
)
''');
''',
);
}
Future<void> clearTable() async {
@ -51,17 +53,22 @@ class PublicKeysDB {
Future<int> setKey(PublicKey key) async {
final db = await instance.database;
return db.insert(table, _getRow(key),
conflictAlgorithm: ConflictAlgorithm.replace);
return db.insert(
table,
_getRow(key),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
Future<List<PublicKey>> searchByEmail(String email) async {
final db = await instance.database;
return _convertRows(await db.query(
table,
where: '$columnEmail LIKE ?',
whereArgs: ['%$email%'],
));
return _convertRows(
await db.query(
table,
where: '$columnEmail LIKE ?',
whereArgs: ['%$email%'],
),
);
}
Map<String, dynamic> _getRow(PublicKey key) {

View file

@ -42,7 +42,8 @@ class TrashDB {
static final columnPubMMdVersion = 'pub_mmd_ver';
Future _onCreate(Database db, int version) async {
await db.execute('''
await db.execute(
'''
CREATE TABLE $tableName (
$columnUploadedFileID INTEGER PRIMARY KEY NOT NULL,
$columnCollectionID INTEGER NOT NULL,
@ -65,7 +66,8 @@ class TrashDB {
CREATE INDEX IF NOT EXISTS creation_time_index ON $tableName($columnCreationTime);
CREATE INDEX IF NOT EXISTS delete_by_time_index ON $tableName($columnTrashDeleteBy);
CREATE INDEX IF NOT EXISTS updated_at_time_index ON $tableName($columnTrashUpdatedAt);
''');
''',
);
}
TrashDB._privateConstructor();
@ -101,8 +103,11 @@ class TrashDB {
// getRecentlyTrashedFile returns the file which was trashed recently
Future<TrashFile> getRecentlyTrashedFile() async {
final db = await instance.database;
var rows = await db.query(tableName,
orderBy: '$columnTrashDeleteBy DESC', limit: 1);
var rows = await db.query(
tableName,
orderBy: '$columnTrashDeleteBy DESC',
limit: 1,
);
if (rows == null || rows.isEmpty) {
return null;
}
@ -112,7 +117,8 @@ class TrashDB {
Future<int> count() async {
final db = await instance.database;
var count = Sqflite.firstIntValue(
await db.rawQuery('SELECT COUNT(*) FROM $tableName'));
await db.rawQuery('SELECT COUNT(*) FROM $tableName'),
);
return count;
}
@ -137,13 +143,16 @@ class TrashDB {
await batch.commit(noResult: true);
final endTime = DateTime.now();
final duration = Duration(
microseconds:
endTime.microsecondsSinceEpoch - startTime.microsecondsSinceEpoch);
_logger.info("Batch insert of " +
trashFiles.length.toString() +
" took " +
duration.inMilliseconds.toString() +
"ms.");
microseconds:
endTime.microsecondsSinceEpoch - startTime.microsecondsSinceEpoch,
);
_logger.info(
"Batch insert of " +
trashFiles.length.toString() +
" took " +
duration.inMilliseconds.toString() +
"ms.",
);
}
Future<int> insert(TrashFile trash) async {
@ -173,8 +182,12 @@ class TrashDB {
);
}
Future<FileLoadResult> getTrashedFiles(int startTime, int endTime,
{int limit, bool asc}) async {
Future<FileLoadResult> getTrashedFiles(
int startTime,
int endTime, {
int limit,
bool asc,
}) async {
final db = await instance.database;
final order = (asc ?? false ? 'ASC' : 'DESC');
final results = await db.query(

View file

@ -34,13 +34,15 @@ class UploadLocksDB {
}
Future _onCreate(Database db, int version) async {
await db.execute('''
await db.execute(
'''
CREATE TABLE $_table (
$_columnID TEXT PRIMARY KEY NOT NULL,
$_columnOwner TEXT NOT NULL,
$_columnTime TEXT NOT NULL
)
''');
''',
);
}
Future<void> clearTable() async {

View file

@ -29,10 +29,10 @@ extension CustomColorScheme on ColorScheme {
// todo: use brightness == Brightness.light for changing color for dark/light theme
ButtonStyle get optionalActionButtonStyle => buildElevatedButtonThemeData(
onPrimary: Color(0xFF777777),
primary: Color(0xFFF0F0F0),
elevation: 0)
.style;
onPrimary: Color(0xFF777777),
primary: Color(0xFFF0F0F0),
elevation: 0,
).style;
Color get recoveryKeyBoxColor => brightness == Brightness.light
? Color.fromRGBO(49, 155, 86, 0.2)
@ -63,11 +63,13 @@ extension CustomColorScheme on ColorScheme {
? DatePickerTheme(
backgroundColor: Colors.white,
itemStyle: TextStyle(color: Colors.black),
cancelStyle: TextStyle(color: Colors.black))
cancelStyle: TextStyle(color: Colors.black),
)
: DatePickerTheme(
backgroundColor: Colors.black,
itemStyle: TextStyle(color: Colors.white),
cancelStyle: TextStyle(color: Colors.white));
cancelStyle: TextStyle(color: Colors.white),
);
Color get stepProgressUnselectedColor => brightness == Brightness.light
? Color.fromRGBO(196, 196, 196, 0.6)
@ -121,8 +123,12 @@ extension CustomColorScheme on ColorScheme {
: Color.fromRGBO(100, 100, 100, 1);
}
OutlinedButtonThemeData buildOutlinedButtonThemeData(
{Color bgDisabled, Color bgEnabled, Color fgDisabled, Color fgEnabled}) {
OutlinedButtonThemeData buildOutlinedButtonThemeData({
Color bgDisabled,
Color bgEnabled,
Color fgDisabled,
Color fgEnabled,
}) {
return OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
shape: RoundedRectangleBorder(
@ -157,27 +163,28 @@ OutlinedButtonThemeData buildOutlinedButtonThemeData(
);
}
ElevatedButtonThemeData buildElevatedButtonThemeData(
{@required Color onPrimary, // text button color
@required Color primary,
double elevation = 2 // background color of button
}) {
ElevatedButtonThemeData buildElevatedButtonThemeData({
@required Color onPrimary, // text button color
@required Color primary,
double elevation = 2, // background color of button
}) {
return ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
elevation: elevation,
onPrimary: onPrimary,
primary: primary,
alignment: Alignment.center,
textStyle: TextStyle(
fontWeight: FontWeight.w600,
fontFamily: 'Inter-SemiBold',
fontSize: 18,
style: ElevatedButton.styleFrom(
elevation: elevation,
onPrimary: onPrimary,
primary: primary,
alignment: Alignment.center,
textStyle: TextStyle(
fontWeight: FontWeight.w600,
fontFamily: 'Inter-SemiBold',
fontSize: 18,
),
padding: EdgeInsets.symmetric(vertical: 18),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8)),
),
),
padding: EdgeInsets.symmetric(vertical: 18),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8)),
),
));
);
}
TextStyle gradientButtonTextTheme() {

View file

@ -58,13 +58,15 @@ Future<void> _runInForeground() async {
_logger.info("Starting app in foreground");
await _init(false, via: 'mainMethod');
_scheduleFGSync('appStart in FG');
runApp(AppLock(
builder: (args) => EnteApp(_runBackgroundTask, _killBGTask),
lockScreen: LockScreen(),
enabled: Configuration.instance.shouldShowLockScreen(),
lightTheme: lightThemeData,
darkTheme: darkThemeData,
));
runApp(
AppLock(
builder: (args) => EnteApp(_runBackgroundTask, _killBGTask),
lockScreen: LockScreen(),
enabled: Configuration.instance.shouldShowLockScreen(),
lightTheme: lightThemeData,
darkTheme: darkThemeData,
),
);
});
}
@ -74,10 +76,13 @@ Future<void> _runBackgroundTask(String taskId) async {
await _sync('bgTaskActiveProcess');
BackgroundFetch.finish(taskId);
} else {
_runWithLogs(() async {
_logger.info("run background task");
_runInBackground(taskId);
}, prefix: "[bg]");
_runWithLogs(
() async {
_logger.info("run background task");
_runInBackground(taskId);
},
prefix: "[bg]",
);
}
}
@ -139,7 +144,8 @@ Future<void> _init(bool isBackground, {String via = ''}) async {
if (Platform.isIOS) {
PushService.instance.init().then((_) {
FirebaseMessaging.onBackgroundMessage(
_firebaseMessagingBackgroundHandler);
_firebaseMessagingBackgroundHandler,
);
});
}
FeatureFlagService.instance.init();
@ -160,22 +166,25 @@ Future<void> _sync(String caller) async {
}
Future _runWithLogs(Function() function, {String prefix = ""}) async {
await SuperLogging.main(LogConfig(
body: function,
logDirPath: (await getTemporaryDirectory()).path + "/logs",
maxLogFiles: 5,
sentryDsn: kDebugMode ? kSentryDebugDSN : kSentryDSN,
tunnel: kSentryTunnel,
enableInDebugMode: true,
prefix: prefix,
));
await SuperLogging.main(
LogConfig(
body: function,
logDirPath: (await getTemporaryDirectory()).path + "/logs",
maxLogFiles: 5,
sentryDsn: kDebugMode ? kSentryDebugDSN : kSentryDSN,
tunnel: kSentryTunnel,
enableInDebugMode: true,
prefix: prefix,
),
);
}
Future<void> _scheduleHeartBeat(bool isBackground) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt(
isBackground ? kLastBGTaskHeartBeatTime : kLastFGTaskHeartBeatTime,
DateTime.now().microsecondsSinceEpoch);
isBackground ? kLastBGTaskHeartBeatTime : kLastFGTaskHeartBeatTime,
DateTime.now().microsecondsSinceEpoch,
);
Future.delayed(kHeartBeatFrequency, () async {
_scheduleHeartBeat(isBackground);
});
@ -209,7 +218,9 @@ Future<bool> _isRunningInForeground() async {
Future<void> _killBGTask([String taskId]) async {
await UploadLocksDB.instance.releaseLocksAcquiredByOwnerBefore(
ProcessType.background.toString(), DateTime.now().microsecondsSinceEpoch);
ProcessType.background.toString(),
DateTime.now().microsecondsSinceEpoch,
);
final prefs = await SharedPreferences.getInstance();
prefs.remove(kLastBGTaskHeartBeatTime);
if (taskId != null) {
@ -222,22 +233,26 @@ Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
bool isInForeground = AppLifecycleService.instance.isForeground;
if (_isProcessRunning) {
_logger.info(
"Background push received when app is alive and runningInFS: $isRunningInFG inForeground: $isInForeground");
"Background push received when app is alive and runningInFS: $isRunningInFG inForeground: $isInForeground",
);
if (PushService.shouldSync(message)) {
await _sync('firebaseBgSyncActiveProcess');
}
} else {
// App is dead
_runWithLogs(() async {
_logger.info("Background push received");
if (Platform.isIOS) {
_scheduleSuicide(kBGPushTimeout); // To prevent OS from punishing us
}
await _init(true, via: 'firebasePush');
if (PushService.shouldSync(message)) {
await _sync('firebaseBgSyncNoActiveProcess');
}
}, prefix: "[fbg]");
_runWithLogs(
() async {
_logger.info("Background push received");
if (Platform.isIOS) {
_scheduleSuicide(kBGPushTimeout); // To prevent OS from punishing us
}
await _init(true, via: 'firebasePush');
if (PushService.shouldSync(message)) {
await _sync('firebaseBgSyncNoActiveProcess');
}
},
prefix: "[fbg]",
);
}
}

View file

@ -33,7 +33,8 @@ class BillingPlans {
return BillingPlans(
plans: List<BillingPlan>.from(
map['plans']?.map((x) => BillingPlan.fromMap(x))),
map['plans']?.map((x) => BillingPlan.fromMap(x)),
),
freePlan: FreePlan.fromMap(map['freePlan']),
);
}

View file

@ -68,22 +68,23 @@ class Collection {
}
}
Collection copyWith(
{int id,
User owner,
String encryptedKey,
String keyDecryptionNonce,
String name,
String encryptedName,
String nameDecryptionNonce,
CollectionType type,
CollectionAttributes attributes,
List<User> sharees,
List<PublicURL> publicURLs,
int updationTime,
bool isDeleted,
String mMdEncodedJson,
int mMdVersion}) {
Collection copyWith({
int id,
User owner,
String encryptedKey,
String keyDecryptionNonce,
String name,
String encryptedName,
String nameDecryptionNonce,
CollectionType type,
CollectionAttributes attributes,
List<User> sharees,
List<PublicURL> publicURLs,
int updationTime,
bool isDeleted,
String mMdEncodedJson,
int mMdVersion,
}) {
Collection result = Collection(
id ?? this.id,
owner ?? this.owner,
@ -131,7 +132,8 @@ class Collection {
(map['publicURLs'] == null || map['publicURLs'].length == 0)
? <PublicURL>[]
: List<PublicURL>.from(
map['publicURLs'].map((x) => PublicURL.fromMap(x)));
map['publicURLs'].map((x) => PublicURL.fromMap(x)),
);
return Collection(
map['id'],
User.fromMap(map['owner']),
@ -338,12 +340,13 @@ class PublicURL {
bool enableDownload = true;
bool passwordEnabled = false;
PublicURL(
{this.url,
this.deviceLimit,
this.validTill,
this.enableDownload,
this.passwordEnabled});
PublicURL({
this.url,
this.deviceLimit,
this.validTill,
this.enableDownload,
this.passwordEnabled,
});
Map<String, dynamic> toMap() {
return {

View file

@ -9,7 +9,8 @@ class DuplicateFilesResponse {
factory DuplicateFilesResponse.fromMap(Map<String, dynamic> map) {
return DuplicateFilesResponse(
List<DuplicateItems>.from(
map['duplicates']?.map((x) => DuplicateItems.fromMap(x))),
map['duplicates']?.map((x) => DuplicateItems.fromMap(x)),
),
);
}

View file

@ -71,11 +71,12 @@ class File {
if (file.creationTime == 0) {
try {
final parsedDateTime = DateTime.parse(
basenameWithoutExtension(file.title)
.replaceAll("IMG_", "")
.replaceAll("VID_", "")
.replaceAll("DCIM_", "")
.replaceAll("_", " "));
basenameWithoutExtension(file.title)
.replaceAll("IMG_", "")
.replaceAll("VID_", "")
.replaceAll("DCIM_", "")
.replaceAll("_", " "),
);
file.creationTime = parsedDateTime.microsecondsSinceEpoch;
} catch (e) {
file.creationTime = asset.modifiedDateTime.microsecondsSinceEpoch;

View file

@ -22,7 +22,11 @@ class IgnoredFile {
return null;
}
return IgnoredFile(trashFile.localID, trashFile.title,
trashFile.deviceFolder, kIgnoreReasonTrash);
return IgnoredFile(
trashFile.localID,
trashFile.title,
trashFile.deviceFolder,
kIgnoreReasonTrash,
);
}
}

View file

@ -74,7 +74,8 @@ class CollectionMagicMetadata {
factory CollectionMagicMetadata.fromEncodedJson(String encodedJson) =>
CollectionMagicMetadata.fromJson(jsonDecode(encodedJson));
factory CollectionMagicMetadata.fromJson(dynamic json) => CollectionMagicMetadata.fromMap(json);
factory CollectionMagicMetadata.fromJson(dynamic json) =>
CollectionMagicMetadata.fromMap(json);
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};

View file

@ -135,7 +135,8 @@ class Attributes {
Attributes({
this.isCancelled,
this.customerID});
this.customerID,
});
Attributes.fromJson(dynamic json) {
isCancelled = json["isCancelled"];
@ -153,5 +154,4 @@ class Attributes {
String toString() {
return 'Attributes{isCancelled: $isCancelled, customerID: $customerID}';
}
}
}

View file

@ -1,7 +1,6 @@
import 'package:photos/models/file.dart';
class TrashFile extends File {
// time when file was put in the trash for first time
int createdAt;

View file

@ -0,0 +1 @@

View file

@ -40,10 +40,11 @@ class UserDetails {
int getFreeStorage() {
return max(
isPartOfFamily()
? (familyData.storage - familyData.getTotalUsage())
: (subscription.storage - (usage)),
0);
isPartOfFamily()
? (familyData.storage - familyData.getTotalUsage())
: (subscription.storage - (usage)),
0,
);
}
int getTotalStorage() {
@ -118,7 +119,8 @@ class FamilyData {
}
assert(map['members'] != null && map['members'].length >= 0);
final members = List<FamilyMember>.from(
map['members'].map((x) => FamilyMember.fromMap(x)));
map['members'].map((x) => FamilyMember.fromMap(x)),
);
return FamilyData(
members,
map['storage'] as int,

View file

@ -11,11 +11,15 @@ import 'package:photos/models/billing_plan.dart';
import 'package:photos/models/subscription.dart';
const kWebPaymentRedirectUrl = "https://payments.ente.io/frameRedirect";
const kWebPaymentBaseEndpoint = String.fromEnvironment("web-payment",
defaultValue: "https://payments.ente.io");
const kWebPaymentBaseEndpoint = String.fromEnvironment(
"web-payment",
defaultValue: "https://payments.ente.io",
);
const kFamilyPlanManagementUrl = String.fromEnvironment("web-family",
defaultValue: "https://family.ente.io");
const kFamilyPlanManagementUrl = String.fromEnvironment(
"web-family",
defaultValue: "https://family.ente.io",
);
class BillingService {
BillingService._privateConstructor();
@ -42,9 +46,10 @@ class BillingService {
}
for (final purchase in purchases) {
if (purchase.status == PurchaseStatus.purchased) {
verifySubscription(purchase.productID,
purchase.verificationData.serverVerificationData)
.then((response) {
verifySubscription(
purchase.productID,
purchase.verificationData.serverVerificationData,
).then((response) {
if (response != null) {
InAppPurchaseConnection.instance.completePurchase(purchase);
}
@ -166,8 +171,9 @@ class BillingService {
}
}
Future<String> getStripeCustomerPortalUrl(
{String endpoint = kWebPaymentRedirectUrl}) async {
Future<String> getStripeCustomerPortalUrl({
String endpoint = kWebPaymentRedirectUrl,
}) async {
try {
final response = await _dio.get(
_config.getHttpEndpoint() + "/billing/stripe/customer-portal",

View file

@ -192,7 +192,9 @@ class CollectionsService {
Future<void> share(int collectionID, String email, String publicKey) async {
final encryptedKey = CryptoUtil.sealSync(
getCollectionKey(collectionID), Sodium.base642bin(publicKey));
getCollectionKey(collectionID),
Sodium.base642bin(publicKey),
);
try {
await _dio.post(
Configuration.instance.getHttpEndpoint() + "/collections/share",
@ -202,7 +204,8 @@ class CollectionsService {
"encryptedKey": Sodium.bin2base64(encryptedKey),
},
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()}),
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
);
} on DioError catch (e) {
if (e.response.statusCode == 402) {
@ -222,7 +225,8 @@ class CollectionsService {
"email": email,
},
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()}),
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
);
_collectionIDToCollections[collectionID]
.sharees
@ -253,20 +257,26 @@ class CollectionsService {
Uint8List _getDecryptedKey(Collection collection) {
final encryptedKey = Sodium.base642bin(collection.encryptedKey);
if (collection.owner.id == _config.getUserID()) {
return CryptoUtil.decryptSync(encryptedKey, _config.getKey(),
Sodium.base642bin(collection.keyDecryptionNonce));
return CryptoUtil.decryptSync(
encryptedKey,
_config.getKey(),
Sodium.base642bin(collection.keyDecryptionNonce),
);
} else {
return CryptoUtil.openSealSync(
encryptedKey,
Sodium.base642bin(_config.getKeyAttributes().publicKey),
_config.getSecretKey());
encryptedKey,
Sodium.base642bin(_config.getKeyAttributes().publicKey),
_config.getSecretKey(),
);
}
}
Future<void> rename(Collection collection, String newName) async {
try {
final encryptedName = CryptoUtil.encryptSync(
utf8.encode(newName), getCollectionKey(collection.id));
utf8.encode(newName),
getCollectionKey(collection.id),
);
await _dio.post(
Configuration.instance.getHttpEndpoint() + "/collections/rename",
data: {
@ -275,7 +285,8 @@ class CollectionsService {
"nameDecryptionNonce": Sodium.bin2base64(encryptedName.nonce)
},
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()}),
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
);
// trigger sync to fetch the latest name from server
sync();
@ -286,7 +297,9 @@ class CollectionsService {
}
Future<void> updateMagicMetadata(
Collection collection, Map<String, dynamic> newMetadataUpdate) async {
Collection collection,
Map<String, dynamic> newMetadataUpdate,
) async {
final int ownerID = Configuration.instance.getUserID();
try {
if (collection.owner.id != ownerID) {
@ -307,7 +320,9 @@ class CollectionsService {
final key = getCollectionKey(collection.id);
final encryptedMMd = await CryptoUtil.encryptChaCha(
utf8.encode(jsonEncode(jsonToUpdate)), key);
utf8.encode(jsonEncode(jsonToUpdate)),
key,
);
// for required field, the json validator on golang doesn't treat 0 as valid
// value. Instead of changing version to ptr, decided to start version with 1.
int currentVersion = max(collection.mMdVersion, 1);
@ -325,7 +340,8 @@ class CollectionsService {
"/collections/magic-metadata",
data: params,
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()}),
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
);
collection.mMdVersion = currentVersion + 1;
_cacheCollectionAttributes(collection);
@ -351,7 +367,8 @@ class CollectionsService {
"collectionID": collection.id,
},
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()}),
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
);
collection.publicURLs?.add(PublicURL.fromMap(response.data["result"]));
await _db.insert(List.from([collection]));
@ -369,14 +386,17 @@ class CollectionsService {
}
Future<void> updateShareUrl(
Collection collection, Map<String, dynamic> prop) async {
Collection collection,
Map<String, dynamic> prop,
) async {
prop.putIfAbsent('collectionID', () => collection.id);
try {
final response = await _dio.put(
Configuration.instance.getHttpEndpoint() + "/collections/share-url",
data: json.encode(prop),
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()}),
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
);
// remove existing url information
collection.publicURLs?.clear();
@ -426,7 +446,8 @@ class CollectionsService {
"source": AppLifecycleService.instance.isForeground ? "fg" : "bg",
},
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()}),
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
);
final List<Collection> collections = [];
if (response != null) {
@ -436,13 +457,15 @@ class CollectionsService {
if (collectionData['magicMetadata'] != null) {
final decryptionKey = _getDecryptedKey(collection);
final utfEncodedMmd = await CryptoUtil.decryptChaCha(
Sodium.base642bin(collectionData['magicMetadata']['data']),
decryptionKey,
Sodium.base642bin(collectionData['magicMetadata']['header']));
Sodium.base642bin(collectionData['magicMetadata']['data']),
decryptionKey,
Sodium.base642bin(collectionData['magicMetadata']['header']),
);
collection.mMdEncodedJson = utf8.decode(utfEncodedMmd);
collection.mMdVersion = collectionData['magicMetadata']['version'];
collection.magicMetadata = CollectionMagicMetadata.fromEncodedJson(
collection.mMdEncodedJson);
collection.mMdEncodedJson,
);
}
collections.add(collection);
}
@ -464,20 +487,22 @@ class CollectionsService {
final key = CryptoUtil.generateKey();
final encryptedKeyData = CryptoUtil.encryptSync(key, _config.getKey());
final encryptedName = CryptoUtil.encryptSync(utf8.encode(albumName), key);
final collection = await createAndCacheCollection(Collection(
null,
null,
Sodium.bin2base64(encryptedKeyData.encryptedData),
Sodium.bin2base64(encryptedKeyData.nonce),
null,
Sodium.bin2base64(encryptedName.encryptedData),
Sodium.bin2base64(encryptedName.nonce),
CollectionType.album,
CollectionAttributes(),
null,
null,
null,
));
final collection = await createAndCacheCollection(
Collection(
null,
null,
Sodium.bin2base64(encryptedKeyData.encryptedData),
Sodium.bin2base64(encryptedKeyData.nonce),
null,
Sodium.bin2base64(encryptedName.encryptedData),
Sodium.bin2base64(encryptedName.nonce),
CollectionType.album,
CollectionAttributes(),
null,
null,
null,
),
);
return collection;
}
@ -487,7 +512,8 @@ class CollectionsService {
final response = await _dio.get(
Configuration.instance.getHttpEndpoint() + "/collections/$collectionID",
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()}),
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
);
assert(response != null && response.data != null);
final collectionData = response.data["collection"];
@ -495,9 +521,10 @@ class CollectionsService {
if (collectionData['magicMetadata'] != null) {
final decryptionKey = _getDecryptedKey(collection);
final utfEncodedMmd = await CryptoUtil.decryptChaCha(
Sodium.base642bin(collectionData['magicMetadata']['data']),
decryptionKey,
Sodium.base642bin(collectionData['magicMetadata']['header']));
Sodium.base642bin(collectionData['magicMetadata']['data']),
decryptionKey,
Sodium.base642bin(collectionData['magicMetadata']['header']),
);
collection.mMdEncodedJson = utf8.decode(utfEncodedMmd);
collection.mMdVersion = collectionData['magicMetadata']['version'];
collection.magicMetadata =
@ -523,38 +550,43 @@ class CollectionsService {
final key = CryptoUtil.generateKey();
final encryptedKeyData = CryptoUtil.encryptSync(key, _config.getKey());
final encryptedPath = CryptoUtil.encryptSync(utf8.encode(path), key);
final collection = await createAndCacheCollection(Collection(
null,
null,
Sodium.bin2base64(encryptedKeyData.encryptedData),
Sodium.bin2base64(encryptedKeyData.nonce),
null,
Sodium.bin2base64(encryptedPath.encryptedData),
Sodium.bin2base64(encryptedPath.nonce),
CollectionType.folder,
CollectionAttributes(
encryptedPath: Sodium.bin2base64(encryptedPath.encryptedData),
pathDecryptionNonce: Sodium.bin2base64(encryptedPath.nonce),
version: 1,
final collection = await createAndCacheCollection(
Collection(
null,
null,
Sodium.bin2base64(encryptedKeyData.encryptedData),
Sodium.bin2base64(encryptedKeyData.nonce),
null,
Sodium.bin2base64(encryptedPath.encryptedData),
Sodium.bin2base64(encryptedPath.nonce),
CollectionType.folder,
CollectionAttributes(
encryptedPath: Sodium.bin2base64(encryptedPath.encryptedData),
pathDecryptionNonce: Sodium.bin2base64(encryptedPath.nonce),
version: 1,
),
null,
null,
null,
),
null,
null,
null,
));
);
return collection;
}
Future<void> addToCollection(int collectionID, List<File> files) async {
final containsUploadedFile = files.firstWhere(
(element) => element.uploadedFileID != null,
orElse: () => null) !=
(element) => element.uploadedFileID != null,
orElse: () => null,
) !=
null;
if (containsUploadedFile) {
final existingFileIDsInCollection =
await FilesDB.instance.getUploadedFileIDs(collectionID);
files.removeWhere((element) =>
element.uploadedFileID != null &&
existingFileIDsInCollection.contains(element.uploadedFileID));
files.removeWhere(
(element) =>
element.uploadedFileID != null &&
existingFileIDsInCollection.contains(element.uploadedFileID),
);
}
if (files.isEmpty || !containsUploadedFile) {
_logger.info("nothing to add to the collection");
@ -574,9 +606,13 @@ class CollectionsService {
if (params["files"] == null) {
params["files"] = [];
}
params["files"].add(CollectionFileItem(
file.uploadedFileID, file.encryptedKey, file.keyDecryptionNonce)
.toMap());
params["files"].add(
CollectionFileItem(
file.uploadedFileID,
file.encryptedKey,
file.keyDecryptionNonce,
).toMap(),
);
}
try {
@ -584,7 +620,8 @@ class CollectionsService {
Configuration.instance.getHttpEndpoint() + "/collections/add-files",
data: params,
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()}),
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
);
await _filesDB.insertMultiple(files);
Bus.instance.fire(CollectionUpdatedEvent(collectionID, files));
@ -605,16 +642,21 @@ class CollectionsService {
final encryptedKeyData = CryptoUtil.encryptSync(key, toCollectionKey);
file.encryptedKey = Sodium.bin2base64(encryptedKeyData.encryptedData);
file.keyDecryptionNonce = Sodium.bin2base64(encryptedKeyData.nonce);
params["files"].add(CollectionFileItem(
file.uploadedFileID, file.encryptedKey, file.keyDecryptionNonce)
.toMap());
params["files"].add(
CollectionFileItem(
file.uploadedFileID,
file.encryptedKey,
file.keyDecryptionNonce,
).toMap(),
);
}
try {
await _dio.post(
Configuration.instance.getHttpEndpoint() + "/collections/restore-files",
data: params,
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()}),
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
);
await _filesDB.insertMultiple(files);
await TrashDB.instance
@ -641,7 +683,10 @@ class CollectionsService {
}
Future<void> move(
int toCollectionID, int fromCollectionID, List<File> files) async {
int toCollectionID,
int fromCollectionID,
List<File> files,
) async {
_validateMoveRequest(toCollectionID, fromCollectionID, files);
files.removeWhere((element) => element.uploadedFileID == null);
if (files.isEmpty) {
@ -660,9 +705,13 @@ class CollectionsService {
CryptoUtil.encryptSync(fileKey, getCollectionKey(toCollectionID));
file.encryptedKey = Sodium.bin2base64(encryptedKeyData.encryptedData);
file.keyDecryptionNonce = Sodium.bin2base64(encryptedKeyData.nonce);
params["files"].add(CollectionFileItem(
file.uploadedFileID, file.encryptedKey, file.keyDecryptionNonce)
.toMap());
params["files"].add(
CollectionFileItem(
file.uploadedFileID,
file.encryptedKey,
file.keyDecryptionNonce,
).toMap(),
);
}
await _dio.post(
Configuration.instance.getHttpEndpoint() + "/collections/move-files",
@ -673,20 +722,31 @@ class CollectionsService {
// remove files from old collection
await _filesDB.removeFromCollection(
fromCollectionID, files.map((e) => e.uploadedFileID).toList());
Bus.instance.fire(CollectionUpdatedEvent(fromCollectionID, files,
type: EventType.deletedFromRemote));
fromCollectionID,
files.map((e) => e.uploadedFileID).toList(),
);
Bus.instance.fire(
CollectionUpdatedEvent(
fromCollectionID,
files,
type: EventType.deletedFromRemote,
),
);
// insert new files in the toCollection which are not part of the toCollection
final existingUploadedIDs =
await FilesDB.instance.getUploadedFileIDs(toCollectionID);
files.removeWhere(
(element) => existingUploadedIDs.contains(element.uploadedFileID));
(element) => existingUploadedIDs.contains(element.uploadedFileID),
);
await _filesDB.insertMultiple(files);
Bus.instance.fire(CollectionUpdatedEvent(toCollectionID, files));
}
void _validateMoveRequest(
int toCollectionID, int fromCollectionID, List<File> files) {
int toCollectionID,
int fromCollectionID,
List<File> files,
) {
if (toCollectionID == fromCollectionID) {
throw AssertionError("can't move to same album");
}
@ -754,10 +814,13 @@ class CollectionsService {
final key = collection.attributes.version == 1
? _getDecryptedKey(collection)
: _config.getKey();
return utf8.decode(CryptoUtil.decryptSync(
return utf8.decode(
CryptoUtil.decryptSync(
Sodium.base642bin(collection.attributes.encryptedPath),
key,
Sodium.base642bin(collection.attributes.pathDecryptionNonce)));
Sodium.base642bin(collection.attributes.pathDecryptionNonce),
),
);
}
bool hasSyncedCollections() {
@ -770,13 +833,17 @@ class CollectionsService {
String name;
try {
final result = CryptoUtil.decryptSync(
Sodium.base642bin(collection.encryptedName),
_getDecryptedKey(collection),
Sodium.base642bin(collection.nameDecryptionNonce));
Sodium.base642bin(collection.encryptedName),
_getDecryptedKey(collection),
Sodium.base642bin(collection.nameDecryptionNonce),
);
name = utf8.decode(result);
} catch (e, s) {
_logger.severe(
"failed to decrypt collection name: ${collection.id}", e, s);
"failed to decrypt collection name: ${collection.id}",
e,
s,
);
name = "Unknown Album";
}
return collection.copyWith(name: name);
@ -830,7 +897,8 @@ class AddFilesRequest {
return AddFilesRequest(
map['collectionID'],
List<CollectionFileItem>.from(
map['files']?.map((x) => CollectionFileItem.fromMap(x))),
map['files']?.map((x) => CollectionFileItem.fromMap(x)),
),
);
}

View file

@ -52,11 +52,14 @@ class DeduplicationService {
}
if (missingFileIDs.isNotEmpty) {
_logger.severe(
"Missing files",
InvalidStateError("Could not find " +
"Missing files",
InvalidStateError(
"Could not find " +
missingFileIDs.length.toString() +
" files in local DB: " +
missingFileIDs.toString()));
missingFileIDs.toString(),
),
);
}
return result;
} catch (e) {

View file

@ -37,7 +37,9 @@ class FavoritesService {
return false;
}
return _filesDB.doesFileExistInCollection(
file.uploadedFileID, collection.id);
file.uploadedFileID,
collection.id,
);
}
Future<void> addToFavorites(File file) async {
@ -84,21 +86,22 @@ class FavoritesService {
final key = CryptoUtil.generateKey();
final encryptedKeyData = CryptoUtil.encryptSync(key, _config.getKey());
final encryptedName = CryptoUtil.encryptSync(utf8.encode("Favorites"), key);
final collection =
await _collectionsService.createAndCacheCollection(Collection(
null,
null,
Sodium.bin2base64(encryptedKeyData.encryptedData),
Sodium.bin2base64(encryptedKeyData.nonce),
null,
Sodium.bin2base64(encryptedName.encryptedData),
Sodium.bin2base64(encryptedName.nonce),
CollectionType.favorites,
CollectionAttributes(),
null,
null,
null,
));
final collection = await _collectionsService.createAndCacheCollection(
Collection(
null,
null,
Sodium.bin2base64(encryptedKeyData.encryptedData),
Sodium.bin2base64(encryptedKeyData.nonce),
null,
Sodium.bin2base64(encryptedName.encryptedData),
Sodium.bin2base64(encryptedName.nonce),
CollectionType.favorites,
CollectionAttributes(),
null,
null,
null,
),
);
_cachedFavoritesCollectionID = collection.id;
return collection.id;
}

View file

@ -101,21 +101,23 @@ class FeatureFlagService {
class FeatureFlags {
static FeatureFlags defaultFlags = FeatureFlags(
disableCFWorker: FFDefault.disableCFWorker,
disableUrlSharing: FFDefault.disableUrlSharing,
enableStripe: FFDefault.enableStripe,
enableMissingLocationMigration: FFDefault.enableMissingLocationMigration);
disableCFWorker: FFDefault.disableCFWorker,
disableUrlSharing: FFDefault.disableUrlSharing,
enableStripe: FFDefault.enableStripe,
enableMissingLocationMigration: FFDefault.enableMissingLocationMigration,
);
final bool disableCFWorker;
final bool disableUrlSharing;
final bool enableStripe;
final bool enableMissingLocationMigration;
FeatureFlags(
{@required this.disableCFWorker,
@required this.disableUrlSharing,
@required this.enableStripe,
@required this.enableMissingLocationMigration});
FeatureFlags({
@required this.disableCFWorker,
@required this.disableUrlSharing,
@required this.enableStripe,
@required this.enableMissingLocationMigration,
});
Map<String, dynamic> toMap() {
return {

View file

@ -44,7 +44,9 @@ class FileMagicService {
}
Future<void> updatePublicMagicMetadata(
List<File> files, Map<String, dynamic> newMetadataUpdate) async {
List<File> files,
Map<String, dynamic> newMetadataUpdate,
) async {
final params = <String, dynamic>{};
params['metadataList'] = [];
final int ownerID = Configuration.instance.getUserID();
@ -52,7 +54,8 @@ class FileMagicService {
for (final file in files) {
if (file.uploadedFileID == null) {
throw AssertionError(
"operation is only supported on backed up files");
"operation is only supported on backed up files",
);
} else if (file.ownerID != ownerID) {
throw AssertionError("cannot modify memories not owned by you");
}
@ -70,15 +73,20 @@ class FileMagicService {
final fileKey = decryptFileKey(file);
final encryptedMMd = await CryptoUtil.encryptChaCha(
utf8.encode(jsonEncode(jsonToUpdate)), fileKey);
params['metadataList'].add(UpdateMagicMetadataRequest(
utf8.encode(jsonEncode(jsonToUpdate)),
fileKey,
);
params['metadataList'].add(
UpdateMagicMetadataRequest(
id: file.uploadedFileID,
magicMetadata: MetadataRequest(
version: file.pubMmdVersion,
count: jsonToUpdate.length,
data: Sodium.bin2base64(encryptedMMd.encryptedData),
header: Sodium.bin2base64(encryptedMMd.header),
)));
),
),
);
file.pubMmdVersion = file.pubMmdVersion + 1;
}
@ -87,7 +95,8 @@ class FileMagicService {
"/files/public-magic-metadata",
data: params,
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()}),
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
);
// update the state of the selected file. Same file in other collection
// should be eventually synced after remote sync has completed
@ -105,7 +114,9 @@ class FileMagicService {
}
Future<void> _updateMagicData(
List<File> files, Map<String, dynamic> newMetadataUpdate) async {
List<File> files,
Map<String, dynamic> newMetadataUpdate,
) async {
final params = <String, dynamic>{};
params['metadataList'] = [];
final int ownerID = Configuration.instance.getUserID();
@ -113,7 +124,8 @@ class FileMagicService {
for (final file in files) {
if (file.uploadedFileID == null) {
throw AssertionError(
"operation is only supported on backed up files");
"operation is only supported on backed up files",
);
} else if (file.ownerID != ownerID) {
throw AssertionError("cannot modify memories not owned by you");
}
@ -131,15 +143,20 @@ class FileMagicService {
final fileKey = decryptFileKey(file);
final encryptedMMd = await CryptoUtil.encryptChaCha(
utf8.encode(jsonEncode(jsonToUpdate)), fileKey);
params['metadataList'].add(UpdateMagicMetadataRequest(
utf8.encode(jsonEncode(jsonToUpdate)),
fileKey,
);
params['metadataList'].add(
UpdateMagicMetadataRequest(
id: file.uploadedFileID,
magicMetadata: MetadataRequest(
version: file.mMdVersion,
count: jsonToUpdate.length,
data: Sodium.bin2base64(encryptedMMd.encryptedData),
header: Sodium.bin2base64(encryptedMMd.header),
)));
),
),
);
file.mMdVersion = file.mMdVersion + 1;
}
@ -147,7 +164,8 @@ class FileMagicService {
Configuration.instance.getHttpEndpoint() + "/files/magic-metadata",
data: params,
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()}),
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
);
// update the state of the selected file. Same file in other collection
// should be eventually synced after remote sync has completed
@ -173,10 +191,11 @@ class UpdateMagicMetadataRequest {
factory UpdateMagicMetadataRequest.fromJson(dynamic json) {
return UpdateMagicMetadataRequest(
id: json['id'],
magicMetadata: json['magicMetadata'] != null
? MetadataRequest.fromJson(json['magicMetadata'])
: null);
id: json['id'],
magicMetadata: json['magicMetadata'] != null
? MetadataRequest.fromJson(json['magicMetadata'])
: null,
);
}
Map<String, dynamic> toJson() {

View file

@ -81,13 +81,15 @@ class FileMigrationService {
final eTime = DateTime.now().microsecondsSinceEpoch;
final d = Duration(microseconds: eTime - sTime);
_logger.info(
'filesWithMissingLocation migration completed in ${d.inSeconds.toString()} seconds');
'filesWithMissingLocation migration completed in ${d.inSeconds.toString()} seconds',
);
}
await _markLocationMigrationAsCompleted();
}
Future<void> _checkAndMarkFilesForReUpload(
List<String> localIDsToProcess) async {
List<String> localIDsToProcess,
) async {
_logger.info("files to process ${localIDsToProcess.length}");
var localIDsWithLocation = <String>[];
for (var localID in localIDsToProcess) {
@ -101,7 +103,8 @@ class FileMigrationService {
if ((latLng.longitude ?? 0.0) != 0.0 ||
(latLng.longitude ?? 0.0) != 0.0) {
_logger.finest(
'found lat/long ${latLng.longitude}/${latLng.longitude} for ${assetEntity.title} ${assetEntity.relativePath} with id : $localID');
'found lat/long ${latLng.longitude}/${latLng.longitude} for ${assetEntity.title} ${assetEntity.relativePath} with id : $localID',
);
hasLocation = true;
}
} catch (e, s) {
@ -127,7 +130,8 @@ class FileMigrationService {
final eTime = DateTime.now().microsecondsSinceEpoch;
final d = Duration(microseconds: eTime - sTime);
_logger.info(
'importing completed, total files count ${fileLocalIDs.length} and took ${d.inSeconds.toString()} seconds');
'importing completed, total files count ${fileLocalIDs.length} and took ${d.inSeconds.toString()} seconds',
);
_prefs.setBool(isLocalImportDone, true);
}
}

View file

@ -25,10 +25,12 @@ class IgnoredFilesService {
Future<void> cacheAndInsert(List<IgnoredFile> ignoredFiles) async {
final existingIDs = await ignoredIDs;
existingIDs.addAll(ignoredFiles
.map((e) => _idForIgnoredFile(e))
.where((id) => id != null)
.toSet());
existingIDs.addAll(
ignoredFiles
.map((e) => _idForIgnoredFile(e))
.where((id) => id != null)
.toSet(),
);
return _db.insertMultiple(ignoredFiles);
}
@ -56,7 +58,10 @@ class IgnoredFilesService {
String _idForIgnoredFile(IgnoredFile ignoredFile) {
return _getIgnoreID(
ignoredFile.localID, ignoredFile.deviceFolder, ignoredFile.title);
ignoredFile.localID,
ignoredFile.deviceFolder,
ignoredFile.title,
);
}
// _computeIgnoreID will return null if don't have sufficient information

View file

@ -55,8 +55,10 @@ class LocalSyncService {
if (Platform.isAndroid && AppLifecycleService.instance.isForeground) {
final permissionState = await PhotoManager.requestPermissionExtend();
if (permissionState != PermissionState.authorized) {
_logger.severe("sync requested with invalid permission",
permissionState.toString());
_logger.severe(
"sync requested with invalid permission",
permissionState.toString(),
);
return;
}
}
@ -67,7 +69,8 @@ class LocalSyncService {
_existingSync = Completer<void>();
final existingLocalFileIDs = await _db.getExistingLocalFileIDs();
_logger.info(
existingLocalFileIDs.length.toString() + " localIDs were discovered");
existingLocalFileIDs.length.toString() + " localIDs were discovered",
);
final editedFileIDs = getEditedFileIDs().toSet();
final downloadedFileIDs = getDownloadedFileIDs().toSet();
final syncStartTime = DateTime.now().microsecondsSinceEpoch;
@ -127,11 +130,13 @@ class LocalSyncService {
final localAssets = await getAllLocalAssets();
final eTime = DateTime.now().microsecondsSinceEpoch;
final d = Duration(microseconds: eTime - sTime);
_logger.info("Loading from the beginning returned " +
localAssets.length.toString() +
" assets and took " +
d.inMilliseconds.toString() +
"ms");
_logger.info(
"Loading from the beginning returned " +
localAssets.length.toString() +
" assets and took " +
d.inMilliseconds.toString() +
"ms",
);
final existingIDs = await _db.getExistingLocalFileIDs();
final invalidIDs = getInvalidFileIDs().toSet();
final unsyncedFiles =
@ -139,7 +144,8 @@ class LocalSyncService {
if (unsyncedFiles.isNotEmpty) {
await _db.insertMultiple(unsyncedFiles);
_logger.info(
"Inserted " + unsyncedFiles.length.toString() + " unsynced files.");
"Inserted " + unsyncedFiles.length.toString() + " unsynced files.",
);
_updatePathsToBackup(unsyncedFiles);
Bus.instance.fire(LocalPhotosUpdatedEvent(unsyncedFiles));
return true;
@ -218,10 +224,12 @@ class LocalSyncService {
Set<String> editedFileIDs,
Set<String> downloadedFileIDs,
) async {
_logger.info("Loading photos from " +
DateTime.fromMicrosecondsSinceEpoch(fromTime).toString() +
" to " +
DateTime.fromMicrosecondsSinceEpoch(toTime).toString());
_logger.info(
"Loading photos from " +
DateTime.fromMicrosecondsSinceEpoch(fromTime).toString() +
" to " +
DateTime.fromMicrosecondsSinceEpoch(toTime).toString(),
);
final files = await getDeviceFiles(fromTime, toTime, _computer);
if (files.isNotEmpty) {
_logger.info("Fetched " + files.length.toString() + " files.");
@ -233,7 +241,8 @@ class LocalSyncService {
.removeWhere((file) => downloadedFileIDs.contains(file.localID));
if (updatedFiles.isNotEmpty) {
_logger.info(
updatedFiles.length.toString() + " local files were updated.");
updatedFiles.length.toString() + " local files were updated.",
);
}
for (final file in updatedFiles) {
await _db.updateUploadedFile(

View file

@ -30,7 +30,8 @@ class MemoriesService extends ChangeNotifier {
// Intention of delay is to give more CPU cycles to other tasks
Future.delayed(const Duration(seconds: 5), () {
_memoriesDB.clearMemoriesSeenBeforeTime(
DateTime.now().microsecondsSinceEpoch - (7 * kMicroSecondsInDay));
DateTime.now().microsecondsSinceEpoch - (7 * kMicroSecondsInDay),
);
});
}
@ -53,10 +54,13 @@ class MemoriesService extends ChangeNotifier {
Future<List<Memory>> _fetchMemories() async {
_logger.info("Fetching memories");
final presentTime = DateTime.now();
final present = presentTime.subtract(Duration(
final present = presentTime.subtract(
Duration(
hours: presentTime.hour,
minutes: presentTime.minute,
seconds: presentTime.second));
seconds: presentTime.second,
),
);
final List<List<int>> durations = [];
for (var yearAgo = 1; yearAgo <= yearsBefore; yearAgo++) {
final date = _getDate(present, yearAgo);
@ -94,7 +98,9 @@ class MemoriesService extends ChangeNotifier {
Future markMemoryAsSeen(Memory memory) async {
memory.markSeen();
await _memoriesDB.markMemoryAsSeen(
memory, DateTime.now().microsecondsSinceEpoch);
memory,
DateTime.now().microsecondsSinceEpoch,
);
notifyListeners();
}
}

View file

@ -20,8 +20,10 @@ class NotificationService {
InitializationSettings(
android: initializationSettingsAndroid,
);
await _flutterLocalNotificationsPlugin.initialize(initializationSettings,
onSelectNotification: selectNotification);
await _flutterLocalNotificationsPlugin.initialize(
initializationSettings,
onSelectNotification: selectNotification,
);
}
Future selectNotification(String payload) async {}
@ -42,6 +44,10 @@ class NotificationService {
const NotificationDetails platformChannelSpecifics =
NotificationDetails(android: androidPlatformChannelSpecifics);
await _flutterLocalNotificationsPlugin.show(
0, title, message, platformChannelSpecifics);
0,
title,
message,
platformChannelSpecifics,
);
}
}

View file

@ -61,7 +61,9 @@ class PushService {
await _setPushTokenOnServer(fcmToken, apnsToken);
await _prefs.setString(kFCMPushToken, fcmToken);
await _prefs.setInt(
kLastFCMTokenUpdationTime, DateTime.now().microsecondsSinceEpoch);
kLastFCMTokenUpdationTime,
DateTime.now().microsecondsSinceEpoch,
);
_logger.info("Push token updated on server");
} catch (e) {
_logger.severe("Could not set push token", e, StackTrace.current);
@ -88,7 +90,8 @@ class PushService {
_logger.info("Message data: ${message.data}");
if (message.notification != null) {
_logger.info(
"Message also contained a notification: ${message.notification}");
"Message also contained a notification: ${message.notification}",
);
}
if (shouldSync(message)) {
SyncService.instance.sync();

View file

@ -147,7 +147,9 @@ class RemoteSyncService {
}
for (final c in updatedCollections) {
await _syncCollectionDiff(
c.id, _collectionsService.getCollectionSyncTime(c.id));
c.id,
_collectionsService.getCollectionSyncTime(c.id),
);
await _collectionsService.setCollectionSyncTime(c.id, c.updationTime);
}
}
@ -156,8 +158,10 @@ class RemoteSyncService {
_logger.info('re-sync collections sinceTime: $sinceTime');
final collections = _collectionsService.getActiveCollections();
for (final c in collections) {
await _syncCollectionDiff(c.id,
min(_collectionsService.getCollectionSyncTime(c.id), sinceTime));
await _syncCollectionDiff(
c.id,
min(_collectionsService.getCollectionSyncTime(c.id), sinceTime),
);
await _collectionsService.setCollectionSyncTime(c.id, c.updationTime);
}
}
@ -170,17 +174,28 @@ class RemoteSyncService {
final deletedFiles =
(await FilesDB.instance.getFilesFromIDs(fileIDs)).values.toList();
await FilesDB.instance.deleteFilesFromCollection(collectionID, fileIDs);
Bus.instance.fire(CollectionUpdatedEvent(collectionID, deletedFiles,
type: EventType.deletedFromRemote));
Bus.instance.fire(LocalPhotosUpdatedEvent(deletedFiles,
type: EventType.deletedFromRemote));
Bus.instance.fire(
CollectionUpdatedEvent(
collectionID,
deletedFiles,
type: EventType.deletedFromRemote,
),
);
Bus.instance.fire(
LocalPhotosUpdatedEvent(
deletedFiles,
type: EventType.deletedFromRemote,
),
);
}
if (diff.updatedFiles.isNotEmpty) {
await _storeDiff(diff.updatedFiles, collectionID);
_logger.info("Updated " +
diff.updatedFiles.length.toString() +
" files in collection " +
collectionID.toString());
_logger.info(
"Updated " +
diff.updatedFiles.length.toString() +
" files in collection " +
collectionID.toString(),
);
Bus.instance.fire(LocalPhotosUpdatedEvent(diff.updatedFiles));
Bus.instance
.fire(CollectionUpdatedEvent(collectionID, diff.updatedFiles));
@ -188,11 +203,15 @@ class RemoteSyncService {
if (diff.latestUpdatedAtTime > 0) {
await _collectionsService.setCollectionSyncTime(
collectionID, diff.latestUpdatedAtTime);
collectionID,
diff.latestUpdatedAtTime,
);
}
if (diff.hasMore) {
return await _syncCollectionDiff(collectionID,
_collectionsService.getCollectionSyncTime(collectionID));
return await _syncCollectionDiff(
collectionID,
_collectionsService.getCollectionSyncTime(collectionID),
);
}
}
@ -213,11 +232,15 @@ class RemoteSyncService {
if (filesToBeUploaded.isNotEmpty) {
final int prevCount = filesToBeUploaded.length;
final ignoredIDs = await IgnoredFilesService.instance.ignoredIDs;
filesToBeUploaded.removeWhere((file) =>
IgnoredFilesService.instance.shouldSkipUpload(ignoredIDs, file));
filesToBeUploaded.removeWhere(
(file) =>
IgnoredFilesService.instance.shouldSkipUpload(ignoredIDs, file),
);
if (prevCount != filesToBeUploaded.length) {
_logger.info((prevCount - filesToBeUploaded.length).toString() +
" files were ignored for upload");
_logger.info(
(prevCount - filesToBeUploaded.length).toString() +
" files were ignored for upload",
);
}
}
if (filesToBeUploaded.isEmpty) {
@ -227,7 +250,8 @@ class RemoteSyncService {
}
_sortByTimeAndType(filesToBeUploaded);
_logger.info(
filesToBeUploaded.length.toString() + " new files to be uploaded.");
filesToBeUploaded.length.toString() + " new files to be uploaded.",
);
return filesToBeUploaded;
}
@ -320,13 +344,21 @@ class RemoteSyncService {
_completedUploads < 0 ||
toBeUploadedInThisSession < 0) {
_logger.info(
"Incorrect sync status",
InvalidSyncStatusError("Tried to report $_completedUploads as "
"uploaded out of $toBeUploadedInThisSession"));
"Incorrect sync status",
InvalidSyncStatusError(
"Tried to report $_completedUploads as "
"uploaded out of $toBeUploadedInThisSession",
),
);
return;
}
Bus.instance.fire(SyncStatusUpdate(SyncStatus.in_progress,
completed: _completedUploads, total: toBeUploadedInThisSession));
Bus.instance.fire(
SyncStatusUpdate(
SyncStatus.in_progress,
completed: _completedUploads,
total: toBeUploadedInThisSession,
),
);
}
Future _storeDiff(List<File> diff, int collectionID) async {
@ -362,17 +394,21 @@ class RemoteSyncService {
// case when localID for a file changes and the file is uploaded again in
// the same collection
final fileWithLocalID = existingFiles.firstWhere(
(e) =>
file.localID != null &&
e.localID != null &&
e.localID == file.localID,
orElse: () => existingFiles.firstWhere((e) => e.localID != null,
orElse: () => null));
(e) =>
file.localID != null &&
e.localID != null &&
e.localID == file.localID,
orElse: () => existingFiles.firstWhere(
(e) => e.localID != null,
orElse: () => null,
),
);
if (fileWithLocalID != null) {
// File should ideally have the same localID
if (file.localID != null && file.localID != fileWithLocalID.localID) {
_logger.severe(
"unexpected mismatch in localIDs remote: ${file.toString()} and existing: ${fileWithLocalID.toString()}");
"unexpected mismatch in localIDs remote: ${file.toString()} and existing: ${fileWithLocalID.toString()}",
);
}
file.localID = fileWithLocalID.localID;
} else {
@ -384,8 +420,10 @@ class RemoteSyncService {
file.generatedID = existingFiles[0].generatedID;
if (file.modificationTime != existingFiles[0].modificationTime) {
// File was updated since the app was uninstalled
_logger.info("Updated since last installation: " +
file.uploadedFileID.toString());
_logger.info(
"Updated since last installation: " +
file.uploadedFileID.toString(),
);
file.modificationTime = existingFiles[0].modificationTime;
file.updationTime = null;
updated++;

View file

@ -91,18 +91,28 @@ class SyncService {
} on WiFiUnavailableError {
_logger.warning("Not uploading over mobile data");
Bus.instance.fire(
SyncStatusUpdate(SyncStatus.paused, reason: "waiting for WiFi..."));
SyncStatusUpdate(SyncStatus.paused, reason: "waiting for WiFi..."),
);
} on SyncStopRequestedError {
_syncStopRequested = false;
Bus.instance.fire(
SyncStatusUpdate(SyncStatus.completed_backup, wasStopped: true));
SyncStatusUpdate(SyncStatus.completed_backup, wasStopped: true),
);
} on NoActiveSubscriptionError {
Bus.instance.fire(SyncStatusUpdate(SyncStatus.error,
error: NoActiveSubscriptionError()));
Bus.instance.fire(
SyncStatusUpdate(
SyncStatus.error,
error: NoActiveSubscriptionError(),
),
);
} on StorageLimitExceededError {
_showStorageLimitExceededNotification();
Bus.instance.fire(SyncStatusUpdate(SyncStatus.error,
error: StorageLimitExceededError()));
Bus.instance.fire(
SyncStatusUpdate(
SyncStatus.error,
error: StorageLimitExceededError(),
),
);
} on UnauthorizedError {
_logger.info("Logging user out");
Bus.instance.fire(TriggerLogoutEvent());
@ -112,8 +122,12 @@ class SyncService {
e.type == DioErrorType.sendTimeout ||
e.type == DioErrorType.receiveTimeout ||
e.type == DioErrorType.other) {
Bus.instance.fire(SyncStatusUpdate(SyncStatus.paused,
reason: "waiting for network..."));
Bus.instance.fire(
SyncStatusUpdate(
SyncStatus.paused,
reason: "waiting for network...",
),
);
_logger.severe("unable to connect", e, StackTrace.current);
return false;
}
@ -155,15 +169,21 @@ class SyncService {
}
void onFoldersSet(Set<String> paths) {
_uploader.removeFromQueueWhere((file) {
return !paths.contains(file.deviceFolder);
}, UserCancelledUploadError());
_uploader.removeFromQueueWhere(
(file) {
return !paths.contains(file.deviceFolder);
},
UserCancelledUploadError(),
);
}
void onVideoBackupPaused() {
_uploader.removeFromQueueWhere((file) {
return file.fileType == FileType.video;
}, UserCancelledUploadError());
_uploader.removeFromQueueWhere(
(file) {
return file.fileType == FileType.video;
},
UserCancelledUploadError(),
);
}
Future<void> deleteFilesOnServer(List<int> fileIDs) async {
@ -224,7 +244,9 @@ class SyncService {
if ((now - lastNotificationShownTime) > kMicroSecondsInDay) {
await _prefs.setInt(kLastStorageLimitExceededNotificationPushTime, now);
NotificationService.instance.showNotification(
"storage limit exceeded", "sorry, we had to pause your backups");
"storage limit exceeded",
"sorry, we had to pause your backups",
);
}
}
}

View file

@ -113,7 +113,8 @@ class TrashSyncService {
}
Future<Response<dynamic>> _trashFiles(
Map<String, dynamic> requestData) async {
Map<String, dynamic> requestData,
) async {
return _dio.post(
Configuration.instance.getHttpEndpoint() + "/files/trash",
options: Options(

View file

@ -69,7 +69,9 @@ class UpdateService {
hasBeen3DaysSinceLastNotification &&
_latestVersion.shouldNotify) {
NotificationService.instance.showNotification(
"update available", "click to install our best version yet");
"update available",
"click to install our best version yet",
);
await _prefs.setInt(kUpdateAvailableShownTimeKey, now);
} else {
_logger.info("Debouncing notification");

View file

@ -59,9 +59,11 @@ class UserService {
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {
return OTTVerificationPage(email,
isChangeEmail: isChangeEmail,
isCreateAccountScreen: isCreateAccountScreen);
return OTTVerificationPage(
email,
isChangeEmail: isChangeEmail,
isCreateAccountScreen: isCreateAccountScreen,
);
},
),
);
@ -143,15 +145,17 @@ class UserService {
Future<void> terminateSession(String token) async {
try {
await _dio.delete(_config.getHttpEndpoint() + "/users/session",
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
queryParameters: {
"token": token,
});
await _dio.delete(
_config.getHttpEndpoint() + "/users/session",
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
queryParameters: {
"token": token,
},
);
} on DioError catch (e) {
_logger.info(e);
rethrow;
@ -160,12 +164,14 @@ class UserService {
Future<void> leaveFamilyPlan() async {
try {
await _dio.delete(_config.getHttpEndpoint() + "/family/leave",
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
));
await _dio.delete(
_config.getHttpEndpoint() + "/family/leave",
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
} on DioError catch (e) {
_logger.warning('failed to leave family plan', e);
rethrow;
@ -176,13 +182,14 @@ class UserService {
final dialog = createProgressDialog(context, "Logging out...");
await dialog.show();
try {
final response =
await _dio.post(_config.getHttpEndpoint() + "/users/logout",
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
));
final response = await _dio.post(
_config.getHttpEndpoint() + "/users/logout",
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
if (response != null && response.statusCode == 200) {
await Configuration.instance.logout();
await dialog.hide();
@ -240,11 +247,17 @@ class UserService {
await dialog.hide();
if (e.response != null && e.response.statusCode == 410) {
await showErrorDialog(
context, "Oops", "Your verification code has expired");
context,
"Oops",
"Your verification code has expired",
);
Navigator.of(context).pop();
} else {
showErrorDialog(context, "Incorrect code",
"Sorry, the code you've entered is incorrect");
showErrorDialog(
context,
"Incorrect code",
"Sorry, the code you've entered is incorrect",
);
}
} catch (e) {
await dialog.hide();
@ -287,8 +300,11 @@ class UserService {
if (e.response != null && e.response.statusCode == 403) {
showErrorDialog(context, "Oops", "This email is already in use");
} else {
showErrorDialog(context, "Incorrect code",
"Authentication failed, please try again");
showErrorDialog(
context,
"Incorrect code",
"Authentication failed, please try again",
);
}
} catch (e) {
await dialog.hide();
@ -371,7 +387,10 @@ class UserService {
}
Future<void> verifyTwoFactor(
BuildContext context, String sessionID, String code) async {
BuildContext context,
String sessionID,
String code,
) async {
final dialog = createProgressDialog(context, "Authenticating...");
await dialog.show();
try {
@ -409,14 +428,20 @@ class UserService {
(route) => route.isFirst,
);
} else {
showErrorDialog(context, "Incorrect code",
"Authentication failed, please try again");
showErrorDialog(
context,
"Incorrect code",
"Authentication failed, please try again",
);
}
} catch (e) {
await dialog.hide();
_logger.severe(e);
showErrorDialog(
context, "Oops", "Authentication failed, please try again");
context,
"Oops",
"Authentication failed, please try again",
);
}
}
@ -435,9 +460,10 @@ class UserService {
MaterialPageRoute(
builder: (BuildContext context) {
return TwoFactorRecoveryPage(
sessionID,
response.data["encryptedSecret"],
response.data["secretDecryptionNonce"]);
sessionID,
response.data["encryptedSecret"],
response.data["secretDecryptionNonce"],
);
},
),
(route) => route.isFirst,
@ -457,12 +483,18 @@ class UserService {
);
} else {
showErrorDialog(
context, "Oops", "Something went wrong, please try again");
context,
"Oops",
"Something went wrong, please try again",
);
}
} catch (e) {
_logger.severe(e);
showErrorDialog(
context, "Oops", "Something went wrong, please try again");
context,
"Oops",
"Something went wrong, please try again",
);
} finally {
await dialog.hide();
}
@ -479,14 +511,20 @@ class UserService {
await dialog.show();
String secret;
try {
secret = Sodium.bin2base64(await CryptoUtil.decrypt(
secret = Sodium.bin2base64(
await CryptoUtil.decrypt(
Sodium.base642bin(encryptedSecret),
Sodium.hex2bin(recoveryKey.trim()),
Sodium.base642bin(secretDecryptionNonce)));
Sodium.base642bin(secretDecryptionNonce),
),
);
} catch (e) {
await dialog.hide();
showErrorDialog(context, "Incorrect recovery key",
"The recovery key you entered is incorrect");
showErrorDialog(
context,
"Incorrect recovery key",
"The recovery key you entered is incorrect",
);
return;
}
try {
@ -523,12 +561,18 @@ class UserService {
);
} else {
showErrorDialog(
context, "Oops", "Something went wrong, please try again");
context,
"Oops",
"Something went wrong, please try again",
);
}
} catch (e) {
_logger.severe(e);
showErrorDialog(
context, "Oops", "Something went wrong, please try again");
context,
"Oops",
"Something went wrong, please try again",
);
} finally {
await dialog.hide();
}
@ -548,9 +592,12 @@ class UserService {
);
await dialog.hide();
routeToPage(
context,
TwoFactorSetupPage(
response.data["secretCode"], response.data["qrCode"]));
context,
TwoFactorSetupPage(
response.data["secretCode"],
response.data["qrCode"],
),
);
} catch (e, s) {
await dialog.hide();
_logger.severe(e, s);
@ -559,7 +606,10 @@ class UserService {
}
Future<bool> enableTwoFactor(
BuildContext context, String secret, String code) async {
BuildContext context,
String secret,
String code,
) async {
Uint8List recoveryKey;
try {
recoveryKey = await getOrCreateRecoveryKey(context);
@ -596,13 +646,19 @@ class UserService {
_logger.severe(e, s);
if (e is DioError) {
if (e.response != null && e.response.statusCode == 401) {
showErrorDialog(context, "Incorrect code",
"Please verify the code you have entered");
showErrorDialog(
context,
"Incorrect code",
"Please verify the code you have entered",
);
return false;
}
}
showErrorDialog(context, "Something went wrong",
"Please contact support if the problem persists");
showErrorDialog(
context,
"Something went wrong",
"Please contact support if the problem persists",
);
}
return false;
}
@ -626,8 +682,11 @@ class UserService {
} catch (e, s) {
await dialog.hide();
_logger.severe(e, s);
showErrorDialog(context, "Something went wrong",
"Please contact support if the problem persists");
showErrorDialog(
context,
"Something went wrong",
"Please contact support if the problem persists",
);
}
}
@ -716,7 +775,8 @@ class UserService {
await Configuration.instance
.setEncryptedToken(response.data["encryptedToken"]);
await Configuration.instance.setKeyAttributes(
KeyAttributes.fromMap(response.data["keyAttributes"]));
KeyAttributes.fromMap(response.data["keyAttributes"]),
);
} else {
await Configuration.instance.setToken(response.data["token"]);
}

View file

@ -106,11 +106,13 @@ class _AppLockState extends State<AppLock> with WidgetsBindingObserver {
switch (settings.name) {
case '/lock-screen':
return PageRouteBuilder(
pageBuilder: (_, __, ___) => this._lockScreen);
pageBuilder: (_, __, ___) => this._lockScreen,
);
case '/unlocked':
return PageRouteBuilder(
pageBuilder: (_, __, ___) =>
this.widget.builder(settings.arguments));
pageBuilder: (_, __, ___) =>
this.widget.builder(settings.arguments),
);
}
return PageRouteBuilder(pageBuilder: (_, __, ___) => this._lockScreen);
},

View file

@ -19,10 +19,12 @@ class _AppUpdateDialogState extends State<AppUpdateDialog> {
Widget build(BuildContext context) {
final List<Widget> changelog = [];
for (final log in widget.latestVersionInfo.changelog) {
changelog.add(Padding(
padding: const EdgeInsets.fromLTRB(8, 4, 0, 4),
child: Text("- " + log, style: Theme.of(context).textTheme.caption),
));
changelog.add(
Padding(
padding: const EdgeInsets.fromLTRB(8, 4, 0, 4),
child: Text("- " + log, style: Theme.of(context).textTheme.caption),
),
);
}
final content = Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -36,10 +38,12 @@ class _AppUpdateDialogState extends State<AppUpdateDialog> {
),
),
Padding(padding: EdgeInsets.all(8)),
Text("Changelog",
style: TextStyle(
fontSize: 18,
)),
Text(
"Changelog",
style: TextStyle(
fontSize: 18,
),
),
Padding(padding: EdgeInsets.all(4)),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -79,9 +83,9 @@ class _AppUpdateDialogState extends State<AppUpdateDialog> {
return WillPopScope(
onWillPop: () async => !shouldForceUpdate,
child: AlertDialog(
title: Text(shouldForceUpdate
? "Critical update available"
: "Update available"),
title: Text(
shouldForceUpdate ? "Critical update available" : "Update available",
),
content: content,
),
);
@ -134,12 +138,15 @@ class _ApkDownloaderDialogState extends State<ApkDownloaderDialog> {
Future<void> _downloadApk() async {
try {
await Network.instance.getDio().download(widget.versionInfo.url, _saveUrl,
onReceiveProgress: (count, _) {
setState(() {
_downloadProgress = count / widget.versionInfo.size;
});
});
await Network.instance.getDio().download(
widget.versionInfo.url,
_saveUrl,
onReceiveProgress: (count, _) {
setState(() {
_downloadProgress = count / widget.versionInfo.size;
});
},
);
Navigator.of(context, rootNavigator: true).pop('dialog');
OpenFile.open(_saveUrl);
} catch (e) {

View file

@ -16,26 +16,32 @@ class ArchivePage extends StatelessWidget {
final GalleryType overlayType;
final _selectedFiles = SelectedFiles();
ArchivePage(
{this.tagPrefix = "archived_page",
this.appBarType = GalleryType.archive,
this.overlayType = GalleryType.archive,
Key key})
: super(key: key);
ArchivePage({
this.tagPrefix = "archived_page",
this.appBarType = GalleryType.archive,
this.overlayType = GalleryType.archive,
Key key,
}) : super(key: key);
@override
Widget build(Object context) {
final gallery = Gallery(
asyncLoader: (creationStartTime, creationEndTime, {limit, asc}) {
return FilesDB.instance.getAllUploadedFiles(creationStartTime,
creationEndTime, Configuration.instance.getUserID(),
visibility: kVisibilityArchive, limit: limit, asc: asc);
return FilesDB.instance.getAllUploadedFiles(
creationStartTime,
creationEndTime,
Configuration.instance.getUserID(),
visibility: kVisibilityArchive,
limit: limit,
asc: asc,
);
},
reloadEvent: Bus.instance.on<FilesUpdatedEvent>().where(
(event) =>
event.updatedFiles.firstWhere(
(element) => element.uploadedFileID != null,
orElse: () => null) !=
(element) => element.uploadedFileID != null,
orElse: () => null,
) !=
null,
),
removalEventTypes: const {EventType.unarchived},
@ -43,8 +49,9 @@ class ArchivePage extends StatelessWidget {
Bus.instance.on<FilesUpdatedEvent>().where(
(event) =>
event.updatedFiles.firstWhere(
(element) => element.uploadedFileID != null,
orElse: () => null) !=
(element) => element.uploadedFileID != null,
orElse: () => null,
) !=
null,
),
],

View file

@ -133,21 +133,28 @@ class _BackupFolderSelectionPageState extends State<BackupFolderSelectionPage> {
.compareTo(second.deviceFolder.toLowerCase());
});
setState(() {});
}),
},
),
Expanded(child: _getFolders()),
Hero(
tag: "select_folders",
child: Container(
width: double.infinity,
decoration: BoxDecoration(boxShadow: [
BoxShadow(
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Theme.of(context).backgroundColor,
blurRadius: 24,
offset: Offset(0, -8),
spreadRadius: 4)
]),
spreadRadius: 4,
)
],
),
padding: EdgeInsets.only(
left: 20, right: 20, bottom: Platform.isIOS ? 60 : 32),
left: 20,
right: 20,
bottom: Platform.isIOS ? 60 : 32,
),
child: OutlinedButton(
child: Text(widget.buttonText),
onPressed: _selectedFolders.isEmpty
@ -226,24 +233,26 @@ class _BackupFolderSelectionPageState extends State<BackupFolderSelectionPage> {
padding: const EdgeInsets.only(bottom: 1, right: 1),
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).colorScheme.boxUnSelectColor,
),
borderRadius: BorderRadius.all(
Radius.circular(12),
),
// color: isSelected
// ? Theme.of(context).colorScheme.boxSelectColor
// : Theme.of(context).colorScheme.boxUnSelectColor,
gradient: isSelected
? LinearGradient(colors: const [
Color(0xFF00DD4D),
Color(0xFF43BA6C)
]) //same for both themes
: LinearGradient(colors: [
border: Border.all(
color: Theme.of(context).colorScheme.boxUnSelectColor,
),
borderRadius: BorderRadius.all(
Radius.circular(12),
),
// color: isSelected
// ? Theme.of(context).colorScheme.boxSelectColor
// : Theme.of(context).colorScheme.boxUnSelectColor,
gradient: isSelected
? LinearGradient(
colors: const [Color(0xFF00DD4D), Color(0xFF43BA6C)],
) //same for both themes
: LinearGradient(
colors: [
Theme.of(context).colorScheme.boxUnSelectColor,
Theme.of(context).colorScheme.boxUnSelectColor
])),
],
),
),
padding: EdgeInsets.fromLTRB(8, 4, 4, 4),
child: InkWell(
child: Row(
@ -343,21 +352,25 @@ class _BackupFolderSelectionPageState extends State<BackupFolderSelectionPage> {
return ClipRRect(
borderRadius: BorderRadius.circular(8),
child: SizedBox(
child: Stack(alignment: AlignmentDirectional.bottomEnd, children: [
ThumbnailWidget(
file,
shouldShowSyncStatus: false,
key: Key("backup_selection_widget" + file.tag()),
),
Padding(
child: Stack(
alignment: AlignmentDirectional.bottomEnd,
children: [
ThumbnailWidget(
file,
shouldShowSyncStatus: false,
key: Key("backup_selection_widget" + file.tag()),
),
Padding(
padding: const EdgeInsets.all(9),
child: isSelected
? Icon(
Icons.local_police,
color: Colors.white,
)
: null),
]),
: null,
),
],
),
height: 88,
width: 88,
),

View file

@ -26,22 +26,26 @@ class BillingQuestionsWidget extends StatelessWidget {
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
final faqs = <Widget>[];
faqs.add(Padding(
padding: const EdgeInsets.all(24),
child: Text(
"FAQs",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
faqs.add(
Padding(
padding: const EdgeInsets.all(24),
child: Text(
"FAQs",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
));
);
for (final faq in snapshot.data) {
faqs.add(FaqWidget(faq: faq));
}
faqs.add(Padding(
padding: EdgeInsets.all(16),
));
faqs.add(
Padding(
padding: EdgeInsets.all(16),
),
);
return SingleChildScrollView(
child: Column(
children: faqs,

View file

@ -64,8 +64,11 @@ class _ChangeEmailDialogState extends State<ChangeEmailDialog> {
),
onPressed: () {
if (!isValidEmail(_email)) {
showErrorDialog(context, "Invalid email address",
"Please enter a valid email address.");
showErrorDialog(
context,
"Invalid email address",
"Please enter a valid email address.",
);
return;
}
UserService.instance.getOtt(context, _email, isChangeEmail: true);

View file

@ -17,12 +17,13 @@ class CollectionPage extends StatelessWidget {
final GalleryType overlayType;
final _selectedFiles = SelectedFiles();
CollectionPage(this.c,
{this.tagPrefix = "collection",
this.appBarType = GalleryType.owned_collection,
this.overlayType = GalleryType.owned_collection,
Key key})
: super(key: key);
CollectionPage(
this.c, {
this.tagPrefix = "collection",
this.appBarType = GalleryType.owned_collection,
this.overlayType = GalleryType.owned_collection,
Key key,
}) : super(key: key);
@override
Widget build(Object context) {
@ -30,8 +31,12 @@ class CollectionPage extends StatelessWidget {
final gallery = Gallery(
asyncLoader: (creationStartTime, creationEndTime, {limit, asc}) {
return FilesDB.instance.getFilesInCollection(
c.collection.id, creationStartTime, creationEndTime,
limit: limit, asc: asc);
c.collection.id,
creationStartTime,
creationEndTime,
limit: limit,
asc: asc,
);
},
reloadEvent: Bus.instance
.on<CollectionUpdatedEvent>()

View file

@ -100,8 +100,10 @@ class _CollectionsGalleryWidgetState extends State<CollectionsGalleryWidget>
for (final file in latestLocalFiles) {
folders.add(DeviceFolder(file.deviceFolder, file.deviceFolder, file));
}
folders.sort((first, second) =>
second.thumbnail.creationTime.compareTo(first.thumbnail.creationTime));
folders.sort(
(first, second) =>
second.thumbnail.creationTime.compareTo(first.thumbnail.creationTime),
);
final List<CollectionWithThumbnail> collectionsWithThumbnail = [];
final latestCollectionFiles =
@ -136,8 +138,8 @@ class _CollectionsGalleryWidgetState extends State<CollectionsGalleryWidget>
.textTheme
.subtitle1
.copyWith(
color:
Theme.of(context).textTheme.subtitle1.color.withOpacity(0.5));
color: Theme.of(context).textTheme.subtitle1.color.withOpacity(0.5),
);
Size size = MediaQuery.of(context).size;
int albumsCountInOneRow = max(size.width ~/ 220.0, 2);
final double sideOfThumbnail = (size.width / 2) -
@ -155,8 +157,8 @@ class _CollectionsGalleryWidgetState extends State<CollectionsGalleryWidget>
? Padding(
padding: const EdgeInsets.all(22),
child: nothingToSeeHere(
textColor:
Theme.of(context).colorScheme.defaultTextColor),
textColor: Theme.of(context).colorScheme.defaultTextColor,
),
)
: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
@ -168,7 +170,8 @@ class _CollectionsGalleryWidgetState extends State<CollectionsGalleryWidget>
? nothingToSeeHere(
textColor: Theme.of(context)
.colorScheme
.defaultTextColor)
.defaultTextColor,
)
: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
@ -203,21 +206,25 @@ class _CollectionsGalleryWidgetState extends State<CollectionsGalleryWidget>
// to disable GridView's scrolling
itemBuilder: (context, index) {
return _buildCollection(
context, items.collections, index);
context,
items.collections,
index,
);
},
itemCount: items.collections.length + 1,
// To include the + button
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: albumsCountInOneRow,
mainAxisSpacing: 12,
crossAxisSpacing: crossAxisSpacingOfGrid,
childAspectRatio: sideOfThumbnail /
(sideOfThumbnail +
24)), //24 is height of album title
crossAxisCount: albumsCountInOneRow,
mainAxisSpacing: 12,
crossAxisSpacing: crossAxisSpacingOfGrid,
childAspectRatio:
sideOfThumbnail / (sideOfThumbnail + 24),
), //24 is height of album title
),
)
: nothingToSeeHere(
textColor: Theme.of(context).colorScheme.defaultTextColor),
textColor: Theme.of(context).colorScheme.defaultTextColor,
),
const SizedBox(height: 10),
const Divider(),
const SizedBox(height: 16),
@ -258,31 +265,38 @@ class _CollectionsGalleryWidgetState extends State<CollectionsGalleryWidget>
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data > 0) {
return RichText(
text: TextSpan(
style: trashAndHiddenTextStyle,
children: [
text: TextSpan(
style: trashAndHiddenTextStyle,
children: [
TextSpan(
text: "Trash",
style: Theme.of(context)
.textTheme
.subtitle1),
text: "Trash",
style: Theme.of(context)
.textTheme
.subtitle1,
),
TextSpan(text: " \u2022 "),
TextSpan(
text: snapshot.data.toString()),
text: snapshot.data.toString(),
),
//need to query in db and bring this value
]));
],
),
);
} else {
return RichText(
text: TextSpan(
style: trashAndHiddenTextStyle,
children: [
text: TextSpan(
style: trashAndHiddenTextStyle,
children: [
TextSpan(
text: "Trash",
style: Theme.of(context)
.textTheme
.subtitle1),
text: "Trash",
style: Theme.of(context)
.textTheme
.subtitle1,
),
//need to query in db and bring this value
]));
],
),
);
}
},
),
@ -333,38 +347,46 @@ class _CollectionsGalleryWidgetState extends State<CollectionsGalleryWidget>
),
Padding(padding: EdgeInsets.all(6)),
FutureBuilder<int>(
future: FilesDB.instance
.fileCountWithVisibility(
kVisibilityArchive,
Configuration.instance.getUserID()),
future:
FilesDB.instance.fileCountWithVisibility(
kVisibilityArchive,
Configuration.instance.getUserID(),
),
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data > 0) {
return RichText(
text: TextSpan(
style: trashAndHiddenTextStyle,
children: [
text: TextSpan(
style: trashAndHiddenTextStyle,
children: [
TextSpan(
text: "Hidden",
style: Theme.of(context)
.textTheme
.subtitle1),
text: "Hidden",
style: Theme.of(context)
.textTheme
.subtitle1,
),
TextSpan(text: " \u2022 "),
TextSpan(
text: snapshot.data.toString()),
text: snapshot.data.toString(),
),
//need to query in db and bring this value
]));
],
),
);
} else {
return RichText(
text: TextSpan(
style: trashAndHiddenTextStyle,
children: [
text: TextSpan(
style: trashAndHiddenTextStyle,
children: [
TextSpan(
text: "Hidden",
style: Theme.of(context)
.textTheme
.subtitle1),
text: "Hidden",
style: Theme.of(context)
.textTheme
.subtitle1,
),
//need to query in db and bring this value
]));
],
),
);
}
},
),
@ -408,10 +430,13 @@ class _CollectionsGalleryWidgetState extends State<CollectionsGalleryWidget>
case AlbumSortKey.lastUpdated:
text = "Last updated";
}
return Text(text,
style: Theme.of(context).textTheme.subtitle1.copyWith(
return Text(
text,
style: Theme.of(context).textTheme.subtitle1.copyWith(
fontSize: 14,
color: Theme.of(context).iconTheme.color.withOpacity(0.7)));
color: Theme.of(context).iconTheme.color.withOpacity(0.7),
),
);
}
return Padding(
@ -460,8 +485,11 @@ class _CollectionsGalleryWidgetState extends State<CollectionsGalleryWidget>
);
}
Widget _buildCollection(BuildContext context,
List<CollectionWithThumbnail> collections, int index) {
Widget _buildCollection(
BuildContext context,
List<CollectionWithThumbnail> collections,
int index,
) {
if (index < collections.length) {
final c = collections[index];
return CollectionItem(c);
@ -473,10 +501,11 @@ class _CollectionsGalleryWidgetState extends State<CollectionsGalleryWidget>
color: Theme.of(context).backgroundColor,
boxShadow: [
BoxShadow(
blurRadius: 2,
spreadRadius: 0,
offset: Offset(0, 0),
color: Theme.of(context).iconTheme.color.withOpacity(0.3))
blurRadius: 2,
spreadRadius: 0,
offset: Offset(0, 0),
color: Theme.of(context).iconTheme.color.withOpacity(0.3),
)
],
borderRadius: BorderRadius.circular(4),
),
@ -486,9 +515,11 @@ class _CollectionsGalleryWidgetState extends State<CollectionsGalleryWidget>
),
),
onTap: () async {
await showToast(context,
"long press to select photos and click + to create an album",
toastLength: Toast.LENGTH_LONG);
await showToast(
context,
"long press to select photos and click + to create an album",
toastLength: Toast.LENGTH_LONG,
);
Bus.instance
.fire(TabChangedEvent(0, TabChangedEventSource.collections_page));
},
@ -566,9 +597,11 @@ class DeviceFolderIcon extends StatelessWidget {
ThumbnailWidget(
folder.thumbnail,
shouldShowSyncStatus: false,
key: Key("device_folder:" +
folder.path +
folder.thumbnail.tag()),
key: Key(
"device_folder:" +
folder.path +
folder.thumbnail.tag(),
),
),
isBackedUp ? Container() : kUnsyncedIconOverlay,
],
@ -621,17 +654,19 @@ class CollectionItem extends StatelessWidget {
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: SizedBox(
child: Hero(
tag: "collection" + c.thumbnail.tag(),
child: ThumbnailWidget(
c.thumbnail,
shouldShowArchiveStatus: c.collection.isArchived(),
key: Key(
"collection" + c.thumbnail.tag(),
),
)),
height: sideOfThumbnail,
width: sideOfThumbnail),
child: Hero(
tag: "collection" + c.thumbnail.tag(),
child: ThumbnailWidget(
c.thumbnail,
shouldShowArchiveStatus: c.collection.isArchived(),
key: Key(
"collection" + c.thumbnail.tag(),
),
),
),
height: sideOfThumbnail,
width: sideOfThumbnail,
),
),
SizedBox(height: 4),
Row(
@ -649,14 +684,16 @@ class CollectionItem extends StatelessWidget {
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data > 0) {
return RichText(
text: TextSpan(
style: albumTitleTextStyle.copyWith(
color:
albumTitleTextStyle.color.withOpacity(0.5)),
children: [
text: TextSpan(
style: albumTitleTextStyle.copyWith(
color: albumTitleTextStyle.color.withOpacity(0.5),
),
children: [
TextSpan(text: " \u2022 "),
TextSpan(text: snapshot.data.toString()),
]));
],
),
);
} else {
return Container();
}

View file

@ -14,13 +14,13 @@ class BottomShadowWidget extends StatelessWidget {
color: Colors.transparent,
boxShadow: [
BoxShadow(
color: shadowColor == null
? Theme.of(context).backgroundColor
: shadowColor,
spreadRadius: 42,
blurRadius: 42,
offset: Offset(0, offsetDy) // changes position of shadow
),
color: shadowColor == null
? Theme.of(context).backgroundColor
: shadowColor,
spreadRadius: 42,
blurRadius: 42,
offset: Offset(0, offsetDy), // changes position of shadow
),
],
),
);

View file

@ -9,13 +9,13 @@ class DynamicFAB extends StatelessWidget {
final String buttonText;
final Function onPressedFunction;
DynamicFAB(
{Key key,
this.isKeypadOpen,
this.buttonText,
this.isFormValid,
this.onPressedFunction})
: super(key: key);
DynamicFAB({
Key key,
this.isKeypadOpen,
this.buttonText,
this.isFormValid,
this.onPressedFunction,
}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -36,24 +36,24 @@ class DynamicFAB extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
heroTag: 'FAB',
backgroundColor:
Theme.of(context).colorScheme.dynamicFABBackgroundColor,
foregroundColor:
Theme.of(context).colorScheme.dynamicFABTextColor,
child: Transform.rotate(
angle: isFormValid ? 0 : math.pi / 2,
child: Icon(
Icons.chevron_right,
size: 36,
),
),
onPressed: isFormValid
? onPressedFunction
: () {
FocusScope.of(context).unfocus();
} //keypad down here
heroTag: 'FAB',
backgroundColor:
Theme.of(context).colorScheme.dynamicFABBackgroundColor,
foregroundColor:
Theme.of(context).colorScheme.dynamicFABTextColor,
child: Transform.rotate(
angle: isFormValid ? 0 : math.pi / 2,
child: Icon(
Icons.chevron_right,
size: 36,
),
),
onPressed: isFormValid
? onPressedFunction
: () {
FocusScope.of(context).unfocus();
}, //keypad down here
),
],
),
);

View file

@ -10,20 +10,23 @@ class onlyOuterShadow extends BoxShadow {
double spreadRadius = 0.0,
this.blurStyle = BlurStyle.normal,
}) : super(
color: color,
offset: offset,
blurRadius: blurRadius,
spreadRadius: spreadRadius);
color: color,
offset: offset,
blurRadius: blurRadius,
spreadRadius: spreadRadius,
);
@override
Paint toPaint() {
final Paint result = Paint()
..color = color
..maskFilter = MaskFilter.blur(this.blurStyle, blurSigma);
assert(() {
if (debugDisableShadows) result.maskFilter = null;
return true;
}());
assert(
() {
if (debugDisableShadows) result.maskFilter = null;
return true;
}(),
);
return result;
}
}

View file

@ -19,8 +19,12 @@ PopupMenuButton<dynamic> reportBugPopupMenu(BuildContext context) {
},
onSelected: (value) async {
if (value == 1) {
await sendLogs(context, "Contact support", "support@ente.io",
postShare: () {});
await sendLogs(
context,
"Contact support",
"support@ente.io",
postShare: () {},
);
}
},
);

View file

@ -43,9 +43,12 @@ class CreateCollectionPage extends StatefulWidget {
final List<SharedMediaFile> sharedFiles;
final CollectionActionType actionType;
const CreateCollectionPage(this.selectedFiles, this.sharedFiles,
{Key key, this.actionType = CollectionActionType.addFiles})
: super(key: key);
const CreateCollectionPage(
this.selectedFiles,
this.sharedFiles, {
Key key,
this.actionType = CollectionActionType.addFiles,
}) : super(key: key);
@override
_CreateCollectionPageState createState() => _CreateCollectionPageState();
@ -78,7 +81,11 @@ class _CreateCollectionPageState extends State<CreateCollectionPage> {
Expanded(
child: Padding(
padding: const EdgeInsets.only(
top: 30, bottom: 12, left: 40, right: 40),
top: 30,
bottom: 12,
left: 40,
right: 40,
),
child: GradientButton(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
@ -182,10 +189,11 @@ class _CreateCollectionPageState extends State<CreateCollectionPage> {
onTap: () async {
if (await _runCollectionAction(item.collection.id)) {
showShortToast(
context,
widget.actionType == CollectionActionType.addFiles
? "Added successfully to " + item.collection.name
: "Moved successfully to " + item.collection.name);
context,
widget.actionType == CollectionActionType.addFiles
? "Added successfully to " + item.collection.name
: "Moved successfully to " + item.collection.name,
);
_navigateToCollection(item.collection);
}
},
@ -243,10 +251,14 @@ class _CreateCollectionPageState extends State<CreateCollectionPage> {
if (await _runCollectionAction(collection.id)) {
if (widget.actionType == CollectionActionType.restoreFiles) {
showShortToast(
context, 'Restored files to album ' + _albumName);
context,
'Restored files to album ' + _albumName,
);
} else {
showShortToast(
context, "Album '" + _albumName + "' created.");
context,
"Album '" + _albumName + "' created.",
);
}
_navigateToCollection(collection);
}
@ -267,12 +279,14 @@ class _CreateCollectionPageState extends State<CreateCollectionPage> {
void _navigateToCollection(Collection collection) {
Navigator.pop(context);
Navigator.push(
context,
PageTransition(
type: PageTransitionType.bottomToTop,
child: CollectionPage(
CollectionWithThumbnail(collection, null),
)));
context,
PageTransition(
type: PageTransitionType.bottomToTop,
child: CollectionPage(
CollectionWithThumbnail(collection, null),
),
),
);
}
Future<bool> _runCollectionAction(int collectionID) async {
@ -292,8 +306,11 @@ class _CreateCollectionPageState extends State<CreateCollectionPage> {
await dialog.show();
try {
int fromCollectionID = widget.selectedFiles.files?.first?.collectionID;
await CollectionsService.instance.move(toCollectionID, fromCollectionID,
widget.selectedFiles.files?.toList());
await CollectionsService.instance.move(
toCollectionID,
fromCollectionID,
widget.selectedFiles.files?.toList(),
);
RemoteSyncService.instance.sync(silently: true);
widget.selectedFiles?.clearAll();
await dialog.hide();
@ -339,8 +356,12 @@ class _CreateCollectionPageState extends State<CreateCollectionPage> {
final List<File> files = [];
final List<File> filesPendingUpload = [];
if (widget.sharedFiles != null) {
filesPendingUpload.addAll(await convertIncomingSharedMediaToFile(
widget.sharedFiles, collectionID));
filesPendingUpload.addAll(
await convertIncomingSharedMediaToFile(
widget.sharedFiles,
collectionID,
),
);
} else {
final List<File> filesPendingUpload = [];
for (final file in widget.selectedFiles.files) {

View file

@ -126,15 +126,17 @@ class _DeduplicatePageState extends State<DeduplicatePage> {
return Padding(
padding: EdgeInsets.only(top: 32),
child: nothingToSeeHere(
textColor:
Theme.of(context).colorScheme.defaultTextColor),
textColor: Theme.of(context).colorScheme.defaultTextColor,
),
);
}
}
return Padding(
padding: const EdgeInsets.only(top: 10, bottom: 10),
child: _getGridView(_duplicates[index - kHeaderRowCount],
index - kHeaderRowCount),
child: _getGridView(
_duplicates[index - kHeaderRowCount],
index - kHeaderRowCount,
),
);
},
itemCount: _duplicates.length + kHeaderRowCount,
@ -153,10 +155,11 @@ class _DeduplicatePageState extends State<DeduplicatePage> {
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
"Following files were clubbed based on their sizes" +
((_shouldClubByCaptureTime ? " and capture times." : ".") +
", please review and delete the items you believe are duplicates."),
style: Theme.of(context).textTheme.subtitle2),
"Following files were clubbed based on their sizes" +
((_shouldClubByCaptureTime ? " and capture times." : ".") +
", please review and delete the items you believe are duplicates."),
style: Theme.of(context).textTheme.subtitle2,
),
Padding(
padding: EdgeInsets.all(12),
),
@ -210,8 +213,9 @@ class _DeduplicatePageState extends State<DeduplicatePage> {
return Text(
text,
style: Theme.of(context).textTheme.subtitle1.copyWith(
fontSize: 14,
color: Theme.of(context).iconTheme.color.withOpacity(0.7)),
fontSize: 14,
color: Theme.of(context).iconTheme.color.withOpacity(0.7),
),
);
}
@ -377,19 +381,21 @@ class _DeduplicatePageState extends State<DeduplicatePage> {
)
: null,
),
child: Stack(children: [
Hero(
tag: "deduplicate_" + file.tag(),
child: ThumbnailWidget(
file,
diskLoadDeferDuration: kThumbnailDiskLoadDeferDuration,
serverLoadDeferDuration: kThumbnailServerLoadDeferDuration,
shouldShowLivePhotoOverlay: true,
key: Key("deduplicate_" + file.tag()),
child: Stack(
children: [
Hero(
tag: "deduplicate_" + file.tag(),
child: ThumbnailWidget(
file,
diskLoadDeferDuration: kThumbnailDiskLoadDeferDuration,
serverLoadDeferDuration: kThumbnailServerLoadDeferDuration,
shouldShowLivePhotoOverlay: true,
key: Key("deduplicate_" + file.tag()),
),
),
),
_selectedFiles.contains(file) ? kDeleteIconOverlay : Container(),
]),
_selectedFiles.contains(file) ? kDeleteIconOverlay : Container(),
],
),
),
);
}

View file

@ -91,13 +91,15 @@ class _DetailPageState extends State<DetailPage> {
@override
Widget build(BuildContext context) {
_logger.info("Opening " +
_files[_selectedIndex].toString() +
". " +
(_selectedIndex + 1).toString() +
" / " +
_files.length.toString() +
" files .");
_logger.info(
"Opening " +
_files[_selectedIndex].toString() +
". " +
(_selectedIndex + 1).toString() +
" / " +
_files.length.toString() +
" files .",
);
_appBarKey = GlobalKey<FadingAppBarState>();
_bottomBarKey = GlobalKey<FadingBottomBarState>();
return Scaffold(
@ -198,10 +200,11 @@ class _DetailPageState extends State<DetailPage> {
}
if (_selectedIndex == 0 && !_hasLoadedTillStart) {
final result = await widget.config.asyncLoader(
_files[_selectedIndex].creationTime + 1,
DateTime.now().microsecondsSinceEpoch,
limit: kLoadLimit,
asc: true);
_files[_selectedIndex].creationTime + 1,
DateTime.now().microsecondsSinceEpoch,
limit: kLoadLimit,
asc: true,
);
setState(() {
// Returned result could be a subtype of File
// ignore: unnecessary_cast
@ -218,8 +221,10 @@ class _DetailPageState extends State<DetailPage> {
}
if (_selectedIndex == _files.length - 1 && !_hasLoadedTillEnd) {
final result = await widget.config.asyncLoader(
kGalleryLoadStartTime, _files[_selectedIndex].creationTime - 1,
limit: kLoadLimit);
kGalleryLoadStartTime,
_files[_selectedIndex].creationTime - 1,
limit: kLoadLimit,
);
setState(() {
if (!result.hasMore) {
_hasLoadedTillEnd = true;
@ -248,13 +253,17 @@ class _DetailPageState extends State<DetailPage> {
if (_selectedIndex == totalFiles - 1) {
// Deleted the last file
await _pageController.previousPage(
duration: Duration(milliseconds: 200), curve: Curves.easeInOut);
duration: Duration(milliseconds: 200),
curve: Curves.easeInOut,
);
setState(() {
_files.remove(file);
});
} else {
await _pageController.nextPage(
duration: Duration(milliseconds: 200), curve: Curves.easeInOut);
duration: Duration(milliseconds: 200),
curve: Curves.easeInOut,
);
setState(() {
_selectedIndex--;
_files.remove(file);
@ -265,10 +274,16 @@ class _DetailPageState extends State<DetailPage> {
Future<void> _onEditFileRequested(File file) async {
if (file.uploadedFileID != null &&
file.ownerID != Configuration.instance.getUserID()) {
_logger.severe("Attempt to edit unowned file", UnauthorizedEditError(),
StackTrace.current);
showErrorDialog(context, "Sorry",
"We don't support editing photos and albums that you don't own yet");
_logger.severe(
"Attempt to edit unowned file",
UnauthorizedEditError(),
StackTrace.current,
);
showErrorDialog(
context,
"Sorry",
"We don't support editing photos and albums that you don't own yet",
);
return;
}
final dialog = createProgressDialog(context, "Please wait...");

View file

@ -24,8 +24,12 @@ class DeviceFolderPage extends StatelessWidget {
final gallery = Gallery(
asyncLoader: (creationStartTime, creationEndTime, {limit, asc}) {
return FilesDB.instance.getFilesInPath(
folder.path, creationStartTime, creationEndTime,
limit: limit, asc: asc);
folder.path,
creationStartTime,
creationEndTime,
limit: limit,
asc: asc,
);
},
reloadEvent: Bus.instance.on<LocalPhotosUpdatedEvent>(),
removalEventTypes: const {
@ -96,10 +100,11 @@ class _BackupConfigurationHeaderWidgetState
: Text(
"Backup disabled",
style: TextStyle(
color: Theme.of(context)
.colorScheme
.defaultTextColor
.withOpacity(0.7)),
color: Theme.of(context)
.colorScheme
.defaultTextColor
.withOpacity(0.7),
),
),
Switch(
value: isBackedUp,

View file

@ -18,19 +18,23 @@ class FilteredImage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ColorFiltered(
colorFilter:
ColorFilter.matrix(ColorFilterGenerator.brightnessAdjustMatrix(
value: brightness ?? 1,
)),
colorFilter: ColorFilter.matrix(
ColorFilterGenerator.brightnessAdjustMatrix(
value: brightness ?? 1,
),
),
child: ColorFiltered(
colorFilter:
ColorFilter.matrix(ColorFilterGenerator.saturationAdjustMatrix(
value: saturation ?? 1,
)),
colorFilter: ColorFilter.matrix(
ColorFilterGenerator.saturationAdjustMatrix(
value: saturation ?? 1,
),
),
child: ColorFiltered(
colorFilter: ColorFilter.matrix(ColorFilterGenerator.hueAdjustMatrix(
value: hue ?? 0,
)),
colorFilter: ColorFilter.matrix(
ColorFilterGenerator.hueAdjustMatrix(
value: hue ?? 0,
),
),
child: child,
),
),

View file

@ -88,15 +88,16 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
},
),
title: Material(
type: MaterialType.transparency,
child: StepProgressIndicator(
totalSteps: 4,
currentStep: 1,
selectedColor: Theme.of(context).buttonColor,
roundedEdges: Radius.circular(10),
unselectedColor:
Theme.of(context).colorScheme.stepProgressUnselectedColor,
)),
type: MaterialType.transparency,
child: StepProgressIndicator(
totalSteps: 4,
currentStep: 1,
selectedColor: Theme.of(context).buttonColor,
roundedEdges: Radius.circular(10),
unselectedColor:
Theme.of(context).colorScheme.stepProgressUnselectedColor,
),
),
);
return Scaffold(
appBar: appBar,
@ -136,8 +137,10 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
Padding(
padding:
const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
child: Text('Create new account',
style: Theme.of(context).textTheme.headline4),
child: Text(
'Create new account',
style: Theme.of(context).textTheme.headline4,
),
),
Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
@ -151,8 +154,9 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
contentPadding:
EdgeInsets.symmetric(horizontal: 16, vertical: 14),
border: UnderlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(6)),
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(6),
),
suffixIcon: _emailIsValid
? Icon(
Icons.check,
@ -221,8 +225,9 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
)
: null,
border: UnderlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(6)),
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(6),
),
),
focusNode: _password1FocusNode,
onChanged: (password) {
@ -284,8 +289,9 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
)
: null,
border: UnderlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(6)),
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(6),
),
),
focusNode: _password2FocusNode,
onChanged: (cnfPassword) {
@ -348,13 +354,14 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
child: Row(
children: [
Checkbox(
value: _hasAgreedToTOS,
side: CheckboxTheme.of(context).side,
onChanged: (value) {
setState(() {
_hasAgreedToTOS = value;
});
}),
value: _hasAgreedToTOS,
side: CheckboxTheme.of(context).side,
onChanged: (value) {
setState(() {
_hasAgreedToTOS = value;
});
},
),
Expanded(
child: RichText(
text: TextSpan(
@ -390,7 +397,9 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
MaterialPageRoute(
builder: (BuildContext context) {
return WebPage(
"Privacy", "https://ente.io/privacy");
"Privacy",
"https://ente.io/privacy",
);
},
),
);
@ -448,7 +457,9 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
MaterialPageRoute(
builder: (BuildContext context) {
return WebPage(
"encryption", "https://ente.io/architecture");
"encryption",
"https://ente.io/architecture",
);
},
),
);
@ -526,12 +537,14 @@ class PricingWidget extends StatelessWidget {
children: planWidgets,
),
),
Text("We offer a free trial of " +
convertBytesToReadableFormat(freePlan.storage) +
" for " +
freePlan.duration.toString() +
" " +
freePlan.period),
Text(
"We offer a free trial of " +
convertBytesToReadableFormat(freePlan.storage) +
" for " +
freePlan.duration.toString() +
" " +
freePlan.period,
),
GestureDetector(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,

View file

@ -178,21 +178,22 @@ class _ExpansionTileState extends State<ExpansionCard>
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTileTheme.merge(
iconColor: _iconColor.value,
textColor: _headerColor.value,
child: Container(
margin: widget.margin,
child: ListTile(
onTap: _handleTap,
leading: widget.leading,
title: widget.title,
trailing: widget.trailing ??
RotationTransition(
turns: _iconTurns,
child: const Icon(Icons.expand_more),
),
),
)),
iconColor: _iconColor.value,
textColor: _headerColor.value,
child: Container(
margin: widget.margin,
child: ListTile(
onTap: _handleTap,
leading: widget.leading,
title: widget.title,
trailing: widget.trailing ??
RotationTransition(
turns: _iconTurns,
child: const Icon(Icons.expand_more),
),
),
),
),
ClipRect(
child: Align(
heightFactor: _heightFactor.value,

View file

@ -368,14 +368,28 @@ class _PageViewState extends State<ExtentsPageView> {
description
.add(EnumProperty<Axis>('scrollDirection', widget.scrollDirection));
description.add(
FlagProperty('reverse', value: widget.reverse, ifTrue: 'reversed'));
description.add(DiagnosticsProperty<PageController>(
'controller', widget.controller,
showName: false));
description.add(DiagnosticsProperty<ScrollPhysics>(
'physics', widget.physics,
showName: false));
description.add(FlagProperty('pageSnapping',
value: widget.pageSnapping, ifFalse: 'snapping disabled'));
FlagProperty('reverse', value: widget.reverse, ifTrue: 'reversed'),
);
description.add(
DiagnosticsProperty<PageController>(
'controller',
widget.controller,
showName: false,
),
);
description.add(
DiagnosticsProperty<ScrollPhysics>(
'physics',
widget.physics,
showName: false,
),
);
description.add(
FlagProperty(
'pageSnapping',
value: widget.pageSnapping,
ifFalse: 'snapping disabled',
),
);
}
}

View file

@ -99,87 +99,89 @@ class FadingAppBarState extends State<FadingAppBar> {
if (widget.file.ownerID == null || widget.file.ownerID == widget.userID) {
actions.add(_getFavoriteButton());
}
actions.add(PopupMenuButton(
itemBuilder: (context) {
final List<PopupMenuItem> items = [];
if (widget.file.isRemoteFile()) {
items.add(
PopupMenuItem(
value: 1,
child: Row(
children: [
Icon(
Platform.isAndroid
? Icons.download
: CupertinoIcons.cloud_download,
color: Theme.of(context).iconTheme.color,
),
Padding(
padding: EdgeInsets.all(8),
),
Text("Download"),
],
),
),
);
}
// options for files owned by the user
if (widget.file.ownerID == null ||
widget.file.ownerID == widget.userID) {
if (widget.file.uploadedFileID != null) {
actions.add(
PopupMenuButton(
itemBuilder: (context) {
final List<PopupMenuItem> items = [];
if (widget.file.isRemoteFile()) {
items.add(
PopupMenuItem(
value: 2,
value: 1,
child: Row(
children: [
Icon(
Platform.isAndroid
? Icons.access_time_rounded
: CupertinoIcons.time,
? Icons.download
: CupertinoIcons.cloud_download,
color: Theme.of(context).iconTheme.color,
),
Padding(
padding: EdgeInsets.all(8),
),
Text("Edit time"),
Text("Download"),
],
),
),
);
}
// options for files owned by the user
if (widget.file.ownerID == null ||
widget.file.ownerID == widget.userID) {
if (widget.file.uploadedFileID != null) {
items.add(
PopupMenuItem(
value: 2,
child: Row(
children: [
Icon(
Platform.isAndroid
? Icons.access_time_rounded
: CupertinoIcons.time,
color: Theme.of(context).iconTheme.color,
),
Padding(
padding: EdgeInsets.all(8),
),
Text("Edit time"),
],
),
),
);
}
items.add(
PopupMenuItem(
value: 3,
child: Row(
children: [
Icon(
Platform.isAndroid
? Icons.delete_outline
: CupertinoIcons.delete,
color: Theme.of(context).iconTheme.color,
),
Padding(
padding: EdgeInsets.all(8),
),
Text("Delete"),
],
items.add(
PopupMenuItem(
value: 3,
child: Row(
children: [
Icon(
Platform.isAndroid
? Icons.delete_outline
: CupertinoIcons.delete,
color: Theme.of(context).iconTheme.color,
),
Padding(
padding: EdgeInsets.all(8),
),
Text("Delete"),
],
),
),
),
);
}
return items;
},
onSelected: (value) {
if (value == 1) {
_download(widget.file);
} else if (value == 2) {
_showDateTimePicker(widget.file);
} else if (value == 3) {
_showDeleteSheet(widget.file);
}
},
));
);
}
return items;
},
onSelected: (value) {
if (value == 1) {
_download(widget.file);
} else if (value == 2) {
_showDateTimePicker(widget.file);
} else if (value == 3) {
_showDeleteSheet(widget.file);
}
},
),
);
return AppBar(
iconTheme: IconThemeData(color: Colors.white), //same for both themes
actions: shouldShowActions ? actions : [],
@ -267,8 +269,11 @@ class FadingAppBarState extends State<FadingAppBar> {
theme: Theme.of(context).colorScheme.dateTimePickertheme,
);
if (dateWithTimeResult != null) {
if (await editTime(context, List.of([widget.file]),
dateWithTimeResult.microsecondsSinceEpoch)) {
if (await editTime(
context,
List.of([widget.file]),
dateWithTimeResult.microsecondsSinceEpoch,
)) {
widget.file.creationTime = dateWithTimeResult.microsecondsSinceEpoch;
setState(() {});
}
@ -278,48 +283,56 @@ class FadingAppBarState extends State<FadingAppBar> {
void _showDeleteSheet(File file) {
final List<Widget> actions = [];
if (file.uploadedFileID == null || file.localID == null) {
actions.add(CupertinoActionSheetAction(
child: Text("Everywhere"),
isDestructiveAction: true,
onPressed: () async {
await deleteFilesFromEverywhere(context, [file]);
Navigator.of(context, rootNavigator: true).pop();
widget.onFileDeleted(file);
},
));
actions.add(
CupertinoActionSheetAction(
child: Text("Everywhere"),
isDestructiveAction: true,
onPressed: () async {
await deleteFilesFromEverywhere(context, [file]);
Navigator.of(context, rootNavigator: true).pop();
widget.onFileDeleted(file);
},
),
);
} else {
// uploaded file which is present locally too
actions.add(CupertinoActionSheetAction(
child: Text("Device"),
isDestructiveAction: true,
onPressed: () async {
await deleteFilesOnDeviceOnly(context, [file]);
showToast(context, "File deleted from device");
Navigator.of(context, rootNavigator: true).pop();
// TODO: Fix behavior when inside a device folder
},
));
actions.add(
CupertinoActionSheetAction(
child: Text("Device"),
isDestructiveAction: true,
onPressed: () async {
await deleteFilesOnDeviceOnly(context, [file]);
showToast(context, "File deleted from device");
Navigator.of(context, rootNavigator: true).pop();
// TODO: Fix behavior when inside a device folder
},
),
);
actions.add(CupertinoActionSheetAction(
child: Text("ente"),
isDestructiveAction: true,
onPressed: () async {
await deleteFilesFromRemoteOnly(context, [file]);
showShortToast(context, "Moved to trash");
Navigator.of(context, rootNavigator: true).pop();
// TODO: Fix behavior when inside a collection
},
));
actions.add(
CupertinoActionSheetAction(
child: Text("ente"),
isDestructiveAction: true,
onPressed: () async {
await deleteFilesFromRemoteOnly(context, [file]);
showShortToast(context, "Moved to trash");
Navigator.of(context, rootNavigator: true).pop();
// TODO: Fix behavior when inside a collection
},
),
);
actions.add(CupertinoActionSheetAction(
child: Text("Everywhere"),
isDestructiveAction: true,
onPressed: () async {
await deleteFilesFromEverywhere(context, [file]);
Navigator.of(context, rootNavigator: true).pop();
widget.onFileDeleted(file);
},
));
actions.add(
CupertinoActionSheetAction(
child: Text("Everywhere"),
isDestructiveAction: true,
onPressed: () async {
await deleteFilesFromEverywhere(context, [file]);
Navigator.of(context, rootNavigator: true).pop();
widget.onFileDeleted(file);
},
),
);
}
final action = CupertinoActionSheet(
title: Text("Delete file?"),

View file

@ -192,14 +192,16 @@ class FadingBottomBarState extends State<FadingBottomBar> {
final selectedFiles = SelectedFiles();
selectedFiles.toggleSelection(widget.file);
Navigator.push(
context,
PageTransition(
type: PageTransitionType.bottomToTop,
child: CreateCollectionPage(
selectedFiles,
null,
actionType: CollectionActionType.restoreFiles,
)));
context,
PageTransition(
type: PageTransitionType.bottomToTop,
child: CreateCollectionPage(
selectedFiles,
null,
actionType: CollectionActionType.restoreFiles,
),
),
);
},
),
),

View file

@ -134,7 +134,8 @@ class _FileInfoWidgetState extends State<FileInfoWidget> {
Padding(padding: EdgeInsets.all(4)),
Text(
getFormattedTime(
DateTime.fromMicrosecondsSinceEpoch(file.updationTime)),
DateTime.fromMicrosecondsSinceEpoch(file.updationTime),
),
style: TextStyle(color: infoColor),
),
],
@ -385,8 +386,10 @@ class _FileInfoWidgetState extends State<FileInfoWidget> {
children: [
Icon(Icons.center_focus_strong_outlined, color: infoColor),
Padding(padding: EdgeInsets.all(4)),
Text(focalLength.toString() + " mm",
style: TextStyle(color: infoColor)),
Text(
focalLength.toString() + " mm",
style: TextStyle(color: infoColor),
),
],
),
Padding(padding: EdgeInsets.all(6)),

View file

@ -30,8 +30,9 @@ class _FreeSpacePageState extends State<FreeSpacePage> {
}
Widget _getBody() {
Logger("FreeSpacePage").info("Number of uploaded files: " +
widget.status.localIDs.length.toString());
Logger("FreeSpacePage").info(
"Number of uploaded files: " + widget.status.localIDs.length.toString(),
);
Logger("FreeSpacePage")
.info("Space consumed: " + widget.status.size.toString());
return _getWidget(widget.status);

View file

@ -79,11 +79,13 @@ class _GalleryState extends State<Gallery> {
}
if (widget.forceReloadEvents != null) {
for (final event in widget.forceReloadEvents) {
_forceReloadEventSubscriptions.add(event.listen((event) async {
_logger.info("Force reload triggered");
final result = await _loadFiles();
_setFilesAndReload(result.files);
}));
_forceReloadEventSubscriptions.add(
event.listen((event) async {
_logger.info("Force reload triggered");
final result = await _loadFiles();
_setFilesAndReload(result.files);
}),
);
}
}
if (widget.initialFiles != null) {
@ -111,15 +113,19 @@ class _GalleryState extends State<Gallery> {
try {
final startTime = DateTime.now().microsecondsSinceEpoch;
final result = await widget.asyncLoader(
kGalleryLoadStartTime, DateTime.now().microsecondsSinceEpoch,
limit: limit);
kGalleryLoadStartTime,
DateTime.now().microsecondsSinceEpoch,
limit: limit,
);
final endTime = DateTime.now().microsecondsSinceEpoch;
final duration = Duration(microseconds: endTime - startTime);
_logger.info("Time taken to load " +
result.files.length.toString() +
" files :" +
duration.inMilliseconds.toString() +
"ms");
_logger.info(
"Time taken to load " +
result.files.length.toString() +
" files :" +
duration.inMilliseconds.toString() +
"ms",
);
return result;
} catch (e, s) {
_logger.severe("failed to load files", e, s);
@ -178,10 +184,13 @@ class _GalleryState extends State<Gallery> {
if (widget.header != null) {
children.add(widget.header);
}
children.add(Expanded(
child: nothingToSeeHere(
textColor: Theme.of(context).colorScheme.defaultTextColor),
));
children.add(
Expanded(
child: nothingToSeeHere(
textColor: Theme.of(context).colorScheme.defaultTextColor,
),
),
);
if (widget.footer != null) {
children.add(widget.footer);
}
@ -215,8 +224,11 @@ class _GalleryState extends State<Gallery> {
return gallery;
},
labelTextBuilder: (int index) {
return getMonthAndYear(DateTime.fromMicrosecondsSinceEpoch(
_collatedFiles[index][0].creationTime));
return getMonthAndYear(
DateTime.fromMicrosecondsSinceEpoch(
_collatedFiles[index][0].creationTime,
),
);
},
thumbBackgroundColor:
Theme.of(context).colorScheme.galleryThumbBackgroundColor,
@ -234,7 +246,9 @@ class _GalleryState extends State<Gallery> {
for (int index = 0; index < files.length; index++) {
if (index > 0 &&
!_areFromSameDay(
files[index - 1].creationTime, files[index].creationTime)) {
files[index - 1].creationTime,
files[index].creationTime,
)) {
final List<File> collatedDailyFiles = [];
collatedDailyFiles.addAll(dailyFiles);
collatedFiles.add(collatedDailyFiles);

View file

@ -135,56 +135,59 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
);
}
if (widget.type == GalleryType.owned_collection) {
actions.add(PopupMenuButton(
itemBuilder: (context) {
final List<PopupMenuItem> items = [];
if (widget.collection.type == CollectionType.album) {
actions.add(
PopupMenuButton(
itemBuilder: (context) {
final List<PopupMenuItem> items = [];
if (widget.collection.type == CollectionType.album) {
items.add(
PopupMenuItem(
value: 1,
child: Row(
children: const [
Icon(Icons.edit),
Padding(
padding: EdgeInsets.all(8),
),
Text("Rename"),
],
),
),
);
}
bool isArchived = widget.collection.isArchived();
items.add(
PopupMenuItem(
value: 1,
value: 2,
child: Row(
children: const [
Icon(Icons.edit),
children: [
Icon(isArchived ? Icons.visibility : Icons.visibility_off),
Padding(
padding: EdgeInsets.all(8),
),
Text("Rename"),
Text(isArchived ? "Unhide" : "Hide"),
],
),
),
);
}
bool isArchived = widget.collection.isArchived();
items.add(
PopupMenuItem(
value: 2,
child: Row(
children: [
Icon(isArchived ? Icons.visibility : Icons.visibility_off),
Padding(
padding: EdgeInsets.all(8),
),
Text(isArchived ? "Unhide" : "Hide"),
],
),
),
);
return items;
},
onSelected: (value) async {
if (value == 1) {
await _renameAlbum(context);
}
if (value == 2) {
await changeCollectionVisibility(
return items;
},
onSelected: (value) async {
if (value == 1) {
await _renameAlbum(context);
}
if (value == 2) {
await changeCollectionVisibility(
context,
widget.collection,
widget.collection.isArchived()
? kVisibilityVisible
: kVisibilityArchive);
}
},
));
: kVisibilityArchive,
);
}
},
),
);
}
return actions;
}
@ -200,7 +203,8 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
await CollectionsService.instance.getOrCreateForPath(widget.path);
} else {
throw Exception(
"Cannot create a collection of type" + widget.type.toString());
"Cannot create a collection of type" + widget.type.toString(),
);
}
} else {
final sharees =

View file

@ -27,9 +27,13 @@ class GalleryOverlayWidget extends StatefulWidget {
final SelectedFiles selectedFiles;
final String path;
final Collection collection;
const GalleryOverlayWidget(this.type, this.selectedFiles,
{this.path, this.collection, Key key})
: super(key: key);
const GalleryOverlayWidget(
this.type,
this.selectedFiles, {
this.path,
this.collection,
Key key,
}) : super(key: key);
@override
State<GalleryOverlayWidget> createState() => _GalleryOverlayWidgetState();
@ -216,25 +220,29 @@ class _OverlayWidgetState extends State<OverlayWidget> {
Future<void> _createAlbum() async {
Navigator.push(
context,
PageTransition(
type: PageTransitionType.bottomToTop,
child: CreateCollectionPage(
widget.selectedFiles,
null,
)));
context,
PageTransition(
type: PageTransitionType.bottomToTop,
child: CreateCollectionPage(
widget.selectedFiles,
null,
),
),
);
}
Future<void> _moveFiles() async {
Navigator.push(
context,
PageTransition(
type: PageTransitionType.bottomToTop,
child: CreateCollectionPage(
widget.selectedFiles,
null,
actionType: CollectionActionType.moveFiles,
)));
context,
PageTransition(
type: PageTransitionType.bottomToTop,
child: CreateCollectionPage(
widget.selectedFiles,
null,
actionType: CollectionActionType.moveFiles,
),
),
);
}
List<Widget> _getActions(BuildContext context) {
@ -276,9 +284,11 @@ class _OverlayWidgetState extends State<OverlayWidget> {
message: "Move",
child: IconButton(
color: Theme.of(context).colorScheme.iconColor,
icon: Icon(Platform.isAndroid
? Icons.arrow_forward
: CupertinoIcons.arrow_right),
icon: Icon(
Platform.isAndroid
? Icons.arrow_forward
: CupertinoIcons.arrow_right,
),
onPressed: () {
_moveFiles();
},
@ -352,44 +362,52 @@ class _OverlayWidgetState extends State<OverlayWidget> {
if (widget.type == GalleryType.homepage ||
widget.type == GalleryType.archive) {
bool showArchive = widget.type == GalleryType.homepage;
actions.add(Tooltip(
message: showArchive ? "Hide" : "Unhide",
child: IconButton(
color: Theme.of(context).colorScheme.iconColor,
icon: Icon(
showArchive ? Icons.visibility_off : Icons.visibility,
actions.add(
Tooltip(
message: showArchive ? "Hide" : "Unhide",
child: IconButton(
color: Theme.of(context).colorScheme.iconColor,
icon: Icon(
showArchive ? Icons.visibility_off : Icons.visibility,
),
onPressed: () {
_handleVisibilityChangeRequest(
context,
showArchive ? kVisibilityArchive : kVisibilityVisible,
);
},
),
onPressed: () {
_handleVisibilityChangeRequest(
context, showArchive ? kVisibilityArchive : kVisibilityVisible);
},
),
));
);
}
return actions;
}
void _addTrashAction(List<Widget> actions) {
actions.add(Tooltip(
message: "Restore",
child: IconButton(
color: Theme.of(context).colorScheme.iconColor,
icon: Icon(
Icons.restore,
),
onPressed: () {
Navigator.push(
actions.add(
Tooltip(
message: "Restore",
child: IconButton(
color: Theme.of(context).colorScheme.iconColor,
icon: Icon(
Icons.restore,
),
onPressed: () {
Navigator.push(
context,
PageTransition(
type: PageTransitionType.bottomToTop,
child: CreateCollectionPage(
widget.selectedFiles,
null,
actionType: CollectionActionType.restoreFiles,
)));
},
type: PageTransitionType.bottomToTop,
child: CreateCollectionPage(
widget.selectedFiles,
null,
actionType: CollectionActionType.restoreFiles,
),
),
);
},
),
),
));
);
actions.add(
Tooltip(
message: "Delete permanently",
@ -400,7 +418,9 @@ class _OverlayWidgetState extends State<OverlayWidget> {
),
onPressed: () async {
if (await deleteFromTrash(
context, widget.selectedFiles.files.toList())) {
context,
widget.selectedFiles.files.toList(),
)) {
_clearSelectedFiles();
}
},
@ -410,10 +430,15 @@ class _OverlayWidgetState extends State<OverlayWidget> {
}
Future<void> _handleVisibilityChangeRequest(
BuildContext context, int newVisibility) async {
BuildContext context,
int newVisibility,
) async {
try {
await changeVisibility(
context, widget.selectedFiles.files.toList(), newVisibility);
context,
widget.selectedFiles.files.toList(),
newVisibility,
);
} catch (e, s) {
_logger.severe("failed to update file visibility", e, s);
await showGenericErrorDialog(context);
@ -423,8 +448,11 @@ class _OverlayWidgetState extends State<OverlayWidget> {
}
void _shareSelected(BuildContext context) {
share(context, widget.selectedFiles.files.toList(),
shareButtonKey: shareButtonKey);
share(
context,
widget.selectedFiles.files.toList(),
shareButtonKey: shareButtonKey,
);
}
void _showDeleteSheet(BuildContext context) {
@ -440,56 +468,74 @@ class _OverlayWidgetState extends State<OverlayWidget> {
}
final actions = <Widget>[];
if (containsUploadedFile && containsLocalFile) {
actions.add(CupertinoActionSheetAction(
child: Text("Device"),
isDestructiveAction: true,
onPressed: () async {
Navigator.of(context, rootNavigator: true).pop();
await deleteFilesOnDeviceOnly(
context, widget.selectedFiles.files.toList());
_clearSelectedFiles();
showToast(context, "Files deleted from device");
},
));
actions.add(CupertinoActionSheetAction(
child: Text("ente"),
isDestructiveAction: true,
onPressed: () async {
Navigator.of(context, rootNavigator: true).pop();
await deleteFilesFromRemoteOnly(
context, widget.selectedFiles.files.toList());
_clearSelectedFiles();
showShortToast(context, "Moved to trash");
},
));
actions.add(CupertinoActionSheetAction(
child: Text("Everywhere"),
isDestructiveAction: true,
onPressed: () async {
Navigator.of(context, rootNavigator: true).pop();
await deleteFilesFromEverywhere(
context, widget.selectedFiles.files.toList());
_clearSelectedFiles();
},
));
actions.add(
CupertinoActionSheetAction(
child: Text("Device"),
isDestructiveAction: true,
onPressed: () async {
Navigator.of(context, rootNavigator: true).pop();
await deleteFilesOnDeviceOnly(
context,
widget.selectedFiles.files.toList(),
);
_clearSelectedFiles();
showToast(context, "Files deleted from device");
},
),
);
actions.add(
CupertinoActionSheetAction(
child: Text("ente"),
isDestructiveAction: true,
onPressed: () async {
Navigator.of(context, rootNavigator: true).pop();
await deleteFilesFromRemoteOnly(
context,
widget.selectedFiles.files.toList(),
);
_clearSelectedFiles();
showShortToast(context, "Moved to trash");
},
),
);
actions.add(
CupertinoActionSheetAction(
child: Text("Everywhere"),
isDestructiveAction: true,
onPressed: () async {
Navigator.of(context, rootNavigator: true).pop();
await deleteFilesFromEverywhere(
context,
widget.selectedFiles.files.toList(),
);
_clearSelectedFiles();
},
),
);
} else {
actions.add(CupertinoActionSheetAction(
child: Text("Delete"),
isDestructiveAction: true,
onPressed: () async {
Navigator.of(context, rootNavigator: true).pop();
await deleteFilesFromEverywhere(
context, widget.selectedFiles.files.toList());
_clearSelectedFiles();
},
));
actions.add(
CupertinoActionSheetAction(
child: Text("Delete"),
isDestructiveAction: true,
onPressed: () async {
Navigator.of(context, rootNavigator: true).pop();
await deleteFilesFromEverywhere(
context,
widget.selectedFiles.files.toList(),
);
_clearSelectedFiles();
},
),
);
}
final action = CupertinoActionSheet(
title: Text("Delete " +
count.toString() +
" file" +
(count == 1 ? "" : "s") +
(containsUploadedFile && containsLocalFile ? " from" : "?")),
title: Text(
"Delete " +
count.toString() +
" file" +
(count == 1 ? "" : "s") +
(containsUploadedFile && containsLocalFile ? " from" : "?"),
),
actions: actions,
cancelButton: CupertinoActionSheetAction(
child: Text("Cancel"),
@ -508,13 +554,15 @@ class _OverlayWidgetState extends State<OverlayWidget> {
void _showRemoveFromCollectionSheet(BuildContext context) {
final count = widget.selectedFiles.files.length;
final action = CupertinoActionSheet(
title: Text("Remove " +
count.toString() +
" file" +
(count == 1 ? "" : "s") +
" from " +
widget.collection.name +
"?"),
title: Text(
"Remove " +
count.toString() +
" file" +
(count == 1 ? "" : "s") +
" from " +
widget.collection.name +
"?",
),
actions: <Widget>[
CupertinoActionSheetAction(
child: Text("Remove"),
@ -525,7 +573,9 @@ class _OverlayWidgetState extends State<OverlayWidget> {
await dialog.show();
try {
await CollectionsService.instance.removeFromCollection(
widget.collection.id, widget.selectedFiles.files.toList());
widget.collection.id,
widget.selectedFiles.files.toList(),
);
await dialog.hide();
widget.selectedFiles.clearAll();
} catch (e, s) {

View file

@ -47,28 +47,34 @@ class GrantPermissionsWidget extends StatelessWidget {
Padding(
padding: const EdgeInsets.symmetric(horizontal: 40),
child: RichText(
text: TextSpan(
text: TextSpan(
style: Theme.of(context)
.textTheme
.headline5
.copyWith(fontWeight: FontWeight.w700),
children: [
TextSpan(text: 'ente '),
TextSpan(
text: "needs permission to ",
style: Theme.of(context)
.textTheme
.headline5
.copyWith(fontWeight: FontWeight.w700),
children: [
TextSpan(text: 'ente '),
TextSpan(
text: "needs permission to ",
style: Theme.of(context)
.textTheme
.headline5
.copyWith(fontWeight: FontWeight.w400)),
.copyWith(fontWeight: FontWeight.w400),
),
TextSpan(text: 'preserve your photos')
])),
],
),
),
),
],
),
Container(
width: double.infinity,
padding: EdgeInsets.only(
left: 20, right: 20, bottom: Platform.isIOS ? 84 : 60),
left: 20,
right: 20,
bottom: Platform.isIOS ? 84 : 60,
),
child: OutlinedButton(
child: Text("Grant permission"),
onPressed: () async {
@ -80,7 +86,8 @@ class GrantPermissionsWidget extends StatelessWidget {
AlertDialog alert = AlertDialog(
title: Text("Please grant permissions"),
content: Text(
"ente can encrypt and preserve files only if you grant access to them"),
"ente can encrypt and preserve files only if you grant access to them",
),
actions: [
TextButton(
child: Text(
@ -89,8 +96,9 @@ class GrantPermissionsWidget extends StatelessWidget {
.textTheme
.subtitle1
.copyWith(
fontSize: 14,
fontWeight: FontWeight.w700),
fontSize: 14,
fontWeight: FontWeight.w700,
),
),
onPressed: () {
Navigator.of(context, rootNavigator: true)

View file

@ -183,7 +183,8 @@ class _HomeWidgetState extends State<HomeWidget> {
context: context,
builder: (BuildContext context) {
return AppUpdateDialog(
UpdateService.instance.getLatestVersionInfo());
UpdateService.instance.getLatestVersionInfo(),
);
},
barrierColor: Colors.black.withOpacity(0.85),
);
@ -209,14 +210,17 @@ class _HomeWidgetState extends State<HomeWidget> {
}
void _initMediaShareSubscription() {
_intentDataStreamSubscription = ReceiveSharingIntent.getMediaStream()
.listen((List<SharedMediaFile> value) {
setState(() {
_sharedFiles = value;
});
}, onError: (err) {
_logger.severe("getIntentDataStream error: $err");
});
_intentDataStreamSubscription =
ReceiveSharingIntent.getMediaStream().listen(
(List<SharedMediaFile> value) {
setState(() {
_sharedFiles = value;
});
},
onError: (err) {
_logger.severe("getIntentDataStream error: $err");
},
);
// For sharing images coming from outside the app while the app is closed
ReceiveSharingIntent.getInitialMedia().then((List<SharedMediaFile> value) {
setState(() {
@ -283,10 +287,12 @@ class _HomeWidgetState extends State<HomeWidget> {
_settingsPage,
],
onPageChanged: (page) {
Bus.instance.fire(TabChangedEvent(
page,
TabChangedEventSource.page_view,
));
Bus.instance.fire(
TabChangedEvent(
page,
TabChangedEventSource.page_view,
),
);
},
physics: NeverScrollableScrollPhysics(),
controller: _pageController,
@ -330,12 +336,15 @@ class _HomeWidgetState extends State<HomeWidget> {
}
// Attach a listener to the stream
linkStream.listen((String link) {
_logger.info("Link received: " + link);
_getCredentials(context, link);
}, onError: (err) {
_logger.severe(err);
});
linkStream.listen(
(String link) {
_logger.info("Link received: " + link);
_getCredentials(context, link);
},
onError: (err) {
_logger.severe(err);
},
);
return false;
}
@ -362,31 +371,43 @@ class _HomeWidgetState extends State<HomeWidget> {
CollectionsService.instance.getArchivedCollections();
FileLoadResult result;
if (importantPaths.isNotEmpty) {
result = await FilesDB.instance.getImportantFiles(creationStartTime,
creationEndTime, ownerID, importantPaths.toList(),
limit: limit,
asc: asc,
ignoredCollectionIDs: archivedCollectionIds);
result = await FilesDB.instance.getImportantFiles(
creationStartTime,
creationEndTime,
ownerID,
importantPaths.toList(),
limit: limit,
asc: asc,
ignoredCollectionIDs: archivedCollectionIds,
);
} else {
if (LocalSyncService.instance.hasGrantedLimitedPermissions()) {
result = await FilesDB.instance.getAllLocalAndUploadedFiles(
creationStartTime, creationEndTime, ownerID,
limit: limit,
asc: asc,
ignoredCollectionIDs: archivedCollectionIds);
creationStartTime,
creationEndTime,
ownerID,
limit: limit,
asc: asc,
ignoredCollectionIDs: archivedCollectionIds,
);
} else {
result = await FilesDB.instance.getAllUploadedFiles(
creationStartTime, creationEndTime, ownerID,
limit: limit,
asc: asc,
ignoredCollectionIDs: archivedCollectionIds);
creationStartTime,
creationEndTime,
ownerID,
limit: limit,
asc: asc,
ignoredCollectionIDs: archivedCollectionIds,
);
}
}
// hide ignored files from home page UI
final ignoredIDs = await IgnoredFilesService.instance.ignoredIDs;
result.files.removeWhere((f) =>
f.uploadedFileID == null &&
IgnoredFilesService.instance.shouldSkipUpload(ignoredIDs, f));
result.files.removeWhere(
(f) =>
f.uploadedFileID == null &&
IgnoredFilesService.instance.shouldSkipUpload(ignoredIDs, f),
);
return result;
},
reloadEvent: Bus.instance.on<LocalPhotosUpdatedEvent>(),
@ -426,11 +447,13 @@ class _HomeWidgetState extends State<HomeWidget> {
height: 206,
),
),
Text('No photos are being backed up right now',
style: Theme.of(context)
.textTheme
.caption
.copyWith(fontFamily: 'Inter-Medium', fontSize: 16)),
Text(
'No photos are being backed up right now',
style: Theme.of(context)
.textTheme
.caption
.copyWith(fontFamily: 'Inter-Medium', fontSize: 16),
),
Center(
child: Hero(
tag: "select_folders",
@ -560,10 +583,12 @@ class _HomeBottomNavigationBarState extends State<HomeBottomNavigationBar> {
}
void _onTabChange(int index) {
Bus.instance.fire(TabChangedEvent(
index,
TabChangedEventSource.tab_bar,
));
Bus.instance.fire(
TabChangedEvent(
index,
TabChangedEventSource.tab_bar,
),
);
}
@override
@ -580,111 +605,112 @@ class _HomeBottomNavigationBarState extends State<HomeBottomNavigationBar> {
child: IgnorePointer(
ignoring: filesAreSelected,
child: ListView(
physics: const NeverScrollableScrollPhysics(),
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(36),
child: Container(
alignment: Alignment.bottomCenter,
height: 52,
width: 240,
child: ClipRect(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20),
child: GNav(
curve: Curves.easeOutExpo,
backgroundColor: Theme.of(context)
.colorScheme
.gNavBackgroundColor,
mainAxisAlignment: MainAxisAlignment.center,
rippleColor: Colors.white.withOpacity(0.1),
activeColor: Theme.of(context)
.colorScheme
.gNavBarActiveColor,
iconSize: 24,
padding: EdgeInsets.fromLTRB(16, 8, 16, 8),
duration: Duration(milliseconds: 200),
gap: 0,
tabBorderRadius: 24,
tabBackgroundColor: Theme.of(context)
.colorScheme
.gNavBarActiveColor,
haptic: false,
tabs: [
GButton(
margin: EdgeInsets.fromLTRB(6, 6, 0, 6),
icon: Icons.home,
iconColor: Theme.of(context)
.colorScheme
.gNavIconColor,
iconActiveColor: Theme.of(context)
.colorScheme
.gNavActiveIconColor,
text: '',
onPressed: () {
_onTabChange(
0); // To take care of occasional missing events
},
),
GButton(
margin: EdgeInsets.fromLTRB(0, 6, 0, 6),
icon: Icons.photo_library,
iconColor: Theme.of(context)
.colorScheme
.gNavIconColor,
iconActiveColor: Theme.of(context)
.colorScheme
.gNavActiveIconColor,
text: '',
onPressed: () {
_onTabChange(
1); // To take care of occasional missing events
},
),
GButton(
margin: EdgeInsets.fromLTRB(0, 6, 0, 6),
icon: Icons.folder_shared,
iconColor: Theme.of(context)
.colorScheme
.gNavIconColor,
iconActiveColor: Theme.of(context)
.colorScheme
.gNavActiveIconColor,
text: '',
onPressed: () {
_onTabChange(
2); // To take care of occasional missing events
},
),
GButton(
margin: EdgeInsets.fromLTRB(0, 6, 6, 6),
icon: Icons.person,
iconColor: Theme.of(context)
.colorScheme
.gNavIconColor,
iconActiveColor: Theme.of(context)
.colorScheme
.gNavActiveIconColor,
text: '',
onPressed: () {
_onTabChange(
3); // To take care of occasional missing events
},
)
],
selectedIndex: currentTabIndex,
onTabChange: _onTabChange,
),
physics: const NeverScrollableScrollPhysics(),
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(36),
child: Container(
alignment: Alignment.bottomCenter,
height: 52,
width: 240,
child: ClipRect(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20),
child: GNav(
curve: Curves.easeOutExpo,
backgroundColor: Theme.of(context)
.colorScheme
.gNavBackgroundColor,
mainAxisAlignment: MainAxisAlignment.center,
rippleColor: Colors.white.withOpacity(0.1),
activeColor: Theme.of(context)
.colorScheme
.gNavBarActiveColor,
iconSize: 24,
padding: EdgeInsets.fromLTRB(16, 8, 16, 8),
duration: Duration(milliseconds: 200),
gap: 0,
tabBorderRadius: 24,
tabBackgroundColor: Theme.of(context)
.colorScheme
.gNavBarActiveColor,
haptic: false,
tabs: [
GButton(
margin: EdgeInsets.fromLTRB(6, 6, 0, 6),
icon: Icons.home,
iconColor:
Theme.of(context).colorScheme.gNavIconColor,
iconActiveColor: Theme.of(context)
.colorScheme
.gNavActiveIconColor,
text: '',
onPressed: () {
_onTabChange(
0,
); // To take care of occasional missing events
},
),
GButton(
margin: EdgeInsets.fromLTRB(0, 6, 0, 6),
icon: Icons.photo_library,
iconColor:
Theme.of(context).colorScheme.gNavIconColor,
iconActiveColor: Theme.of(context)
.colorScheme
.gNavActiveIconColor,
text: '',
onPressed: () {
_onTabChange(
1,
); // To take care of occasional missing events
},
),
GButton(
margin: EdgeInsets.fromLTRB(0, 6, 0, 6),
icon: Icons.folder_shared,
iconColor:
Theme.of(context).colorScheme.gNavIconColor,
iconActiveColor: Theme.of(context)
.colorScheme
.gNavActiveIconColor,
text: '',
onPressed: () {
_onTabChange(
2,
); // To take care of occasional missing events
},
),
GButton(
margin: EdgeInsets.fromLTRB(0, 6, 6, 6),
icon: Icons.person,
iconColor:
Theme.of(context).colorScheme.gNavIconColor,
iconActiveColor: Theme.of(context)
.colorScheme
.gNavActiveIconColor,
text: '',
onPressed: () {
_onTabChange(
3,
); // To take care of occasional missing events
},
)
],
selectedIndex: currentTabIndex,
onTabChange: _onTabChange,
),
),
),
),
],
),
]),
),
],
),
],
),
),
),
);

View file

@ -62,9 +62,10 @@ class DraggableScrollbarState extends State<DraggableScrollbar>
if (widget.initialScrollIndex > 0 && widget.totalCount > 1) {
WidgetsBinding.instance?.addPostFrameCallback((_) {
setState(() => thumbOffset =
(widget.initialScrollIndex / widget.totalCount) *
(thumbMax - thumbMin));
setState(
() => thumbOffset = (widget.initialScrollIndex / widget.totalCount) *
(thumbMax - thumbMin),
);
});
}
@ -121,7 +122,6 @@ class DraggableScrollbarState extends State<DraggableScrollbar>
} else {
return buildThumb();
}
}
Widget buildThumb() => Container(
@ -191,25 +191,33 @@ class DraggableScrollbarState extends State<DraggableScrollbar>
void keyHandler(RawKeyEvent value) {
if (value.runtimeType == RawKeyDownEvent) {
if (value.logicalKey == LogicalKeyboardKey.arrowDown) {
onDragUpdate(DragUpdateDetails(
globalPosition: Offset.zero,
delta: Offset(0, 2),
));
onDragUpdate(
DragUpdateDetails(
globalPosition: Offset.zero,
delta: Offset(0, 2),
),
);
} else if (value.logicalKey == LogicalKeyboardKey.arrowUp) {
onDragUpdate(DragUpdateDetails(
globalPosition: Offset.zero,
delta: Offset(0, -2),
));
onDragUpdate(
DragUpdateDetails(
globalPosition: Offset.zero,
delta: Offset(0, -2),
),
);
} else if (value.logicalKey == LogicalKeyboardKey.pageDown) {
onDragUpdate(DragUpdateDetails(
globalPosition: Offset.zero,
delta: Offset(0, 25),
));
onDragUpdate(
DragUpdateDetails(
globalPosition: Offset.zero,
delta: Offset(0, 25),
),
);
} else if (value.logicalKey == LogicalKeyboardKey.pageUp) {
onDragUpdate(DragUpdateDetails(
globalPosition: Offset.zero,
delta: Offset(0, -25),
));
onDragUpdate(
DragUpdateDetails(
globalPosition: Offset.zero,
delta: Offset(0, -25),
),
);
}
}
}

View file

@ -5,9 +5,13 @@ import 'package:photos/ui/huge_listview/draggable_scrollbar.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
typedef HugeListViewItemBuilder<T> = Widget Function(
BuildContext context, int index);
BuildContext context,
int index,
);
typedef HugeListViewErrorBuilder = Widget Function(
BuildContext context, dynamic error);
BuildContext context,
dynamic error,
);
class HugeListView<T> extends StatefulWidget {
/// A [ScrollablePositionedList] controller for jumping or scrolling to an item.
@ -61,11 +65,11 @@ class HugeListView<T> extends StatefulWidget {
this.emptyResultBuilder,
this.errorBuilder,
this.firstShown,
this.thumbBackgroundColor = Colors.red,// Colors.white,
this.thumbBackgroundColor = Colors.red, // Colors.white,
this.thumbDrawColor = Colors.yellow, //Colors.grey,
this.thumbHeight = 48.0,
this.isDraggableScrollbarEnabled = true,
}) : super(key: key);
}) : super(key: key);
@override
HugeListViewState<T> createState() => HugeListViewState<T>();

View file

@ -101,8 +101,9 @@ class _LazyLoadingGalleryState extends State<LazyLoadingGallery> {
final dayStartTime =
DateTime(galleryDate.year, galleryDate.month, galleryDate.day);
final result = await widget.asyncLoader(
dayStartTime.microsecondsSinceEpoch,
dayStartTime.microsecondsSinceEpoch + kMicroSecondsInDay - 1);
dayStartTime.microsecondsSinceEpoch,
dayStartTime.microsecondsSinceEpoch + kMicroSecondsInDay - 1,
);
if (mounted) {
setState(() {
_files = result.files;
@ -152,7 +153,10 @@ class _LazyLoadingGalleryState extends State<LazyLoadingGallery> {
child: Column(
children: [
getDayWidget(
context, _files[0].creationTime, widget.smallerTodayFont),
context,
_files[0].creationTime,
widget.smallerTodayFont,
),
_shouldRender ? _getGallery() : PlaceHolderWidget(_files.length),
],
),
@ -162,14 +166,17 @@ class _LazyLoadingGalleryState extends State<LazyLoadingGallery> {
Widget _getGallery() {
List<Widget> childGalleries = [];
for (int index = 0; index < _files.length; index += kSubGalleryItemLimit) {
childGalleries.add(LazyLoadingGridView(
widget.tag,
_files.sublist(index, min(index + kSubGalleryItemLimit, _files.length)),
widget.asyncLoader,
widget.selectedFiles,
index == 0,
_files.length > kRecycleLimit,
));
childGalleries.add(
LazyLoadingGridView(
widget.tag,
_files.sublist(
index, min(index + kSubGalleryItemLimit, _files.length)),
widget.asyncLoader,
widget.selectedFiles,
index == 0,
_files.length > kRecycleLimit,
),
);
}
return Column(
@ -314,9 +321,11 @@ class _LazyLoadingGridViewState extends State<LazyLoadingGridView> {
tag: widget.tag + file.tag(),
child: ColorFiltered(
colorFilter: ColorFilter.mode(
Colors.black.withOpacity(
widget.selectedFiles.files.contains(file) ? 0.4 : 0),
BlendMode.darken),
Colors.black.withOpacity(
widget.selectedFiles.files.contains(file) ? 0.4 : 0,
),
BlendMode.darken,
),
child: ThumbnailWidget(
file,
diskLoadDeferDuration: kThumbnailDiskLoadDeferDuration,
@ -350,12 +359,14 @@ class _LazyLoadingGridViewState extends State<LazyLoadingGridView> {
}
void _routeToDetailPage(File file, BuildContext context) {
final page = DetailPage(DetailPageConfiguration(
List.unmodifiable(widget.files),
widget.asyncLoader,
widget.files.indexOf(file),
widget.tag,
));
final page = DetailPage(
DetailPageConfiguration(
List.unmodifiable(widget.files),
widget.asyncLoader,
widget.files.indexOf(file),
widget.tag,
),
);
routeToPage(context, page);
}
}

View file

@ -62,8 +62,8 @@ class ScrollBarThumb extends StatelessWidget {
child: Material(
elevation: 4.0,
child: Container(
constraints:
BoxConstraints.tight(Size(height * 0.6, height))),
constraints: BoxConstraints.tight(Size(height * 0.6, height)),
),
color: backgroundColor,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(height),
@ -100,20 +100,28 @@ class _ArrowCustomPainter extends CustomPainter {
final baseY = size.height / 2;
canvas.drawPath(
trianglePath(Offset(baseX - 2.0, baseY - 2.0), width, height, true),
paint);
trianglePath(Offset(baseX - 2.0, baseY - 2.0), width, height, true),
paint,
);
canvas.drawPath(
trianglePath(Offset(baseX - 2.0, baseY + 2.0), width, height, false),
paint);
trianglePath(Offset(baseX - 2.0, baseY + 2.0), width, height, false),
paint,
);
}
static Path trianglePath(
Offset offset, double width, double height, bool isUp) {
Offset offset,
double width,
double height,
bool isUp,
) {
return Path()
..moveTo(offset.dx, offset.dy)
..lineTo(offset.dx + width, offset.dy)
..lineTo(offset.dx + (width / 2),
isUp ? offset.dy - height : offset.dy + height)
..lineTo(
offset.dx + (width / 2),
isUp ? offset.dy - height : offset.dy + height,
)
..close();
}
}

View file

@ -308,7 +308,8 @@ class _ImageEditorPageState extends State<ImageEditorPage> {
option.addOption(ClipOption.fromRect(rect));
option.addOption(
FlipOption(horizontal: flipHorizontal, vertical: flipVertical));
FlipOption(horizontal: flipHorizontal, vertical: flipVertical),
);
if (action.hasRotateAngle) {
option.addOption(RotateOption(radian.toInt()));
}
@ -363,8 +364,9 @@ class _ImageEditorPageState extends State<ImageEditorPage> {
_logger.info("Saved edits to file " + newFile.toString());
final existingFiles = widget.detailPageConfig.files;
final files = (await widget.detailPageConfig.asyncLoader(
existingFiles[existingFiles.length - 1].creationTime,
existingFiles[0].creationTime))
existingFiles[existingFiles.length - 1].creationTime,
existingFiles[0].creationTime,
))
.files;
replacePage(
context,

View file

@ -55,9 +55,11 @@ class _LandingPageWidgetState extends State<LandingPageWidget> {
Theme.of(context).colorScheme.dotsIndicatorActiveColor,
color: Theme.of(context).colorScheme.dotsIndicatorInactiveColor,
activeShape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(3)),
borderRadius: BorderRadius.circular(3),
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(3)),
borderRadius: BorderRadius.circular(3),
),
size: Size(100, 5),
activeSize: Size(100, 5),
spacing: EdgeInsets.all(3),

View file

@ -45,11 +45,12 @@ class _LoadingPhotosWidgetState extends State<LoadingPhotosWidget> {
// Do nothing, let HomeWidget refresh
} else {
routeToPage(
context,
BackupFolderSelectionPage(
shouldSelectAll: true,
buttonText: "Start backup",
));
context,
BackupFolderSelectionPage(
shouldSelectAll: true,
buttonText: "Start backup",
),
);
}
}
});
@ -134,23 +135,25 @@ class _LoadingPhotosWidgetState extends State<LoadingPhotosWidget> {
),
SizedBox(
height: 175,
child: Stack(children: [
PageView.builder(
scrollDirection: Axis.vertical,
controller: _pageController,
itemBuilder: (context, index) {
return _getMessage(_messages[index]);
},
itemCount: _messages.length,
physics: NeverScrollableScrollPhysics(),
),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: BottomShadowWidget(),
)
]),
child: Stack(
children: [
PageView.builder(
scrollDirection: Axis.vertical,
controller: _pageController,
itemBuilder: (context, index) {
return _getMessage(_messages[index]);
},
itemCount: _messages.length,
physics: NeverScrollableScrollPhysics(),
),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: BottomShadowWidget(),
)
],
),
),
],
),

View file

@ -48,7 +48,8 @@ class _LocationSearchWidgetState extends State<LocationSearchWidget> {
"query": pattern,
},
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()}),
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
)
.then((response) {
if (_searchString == pattern) {
@ -63,21 +64,23 @@ class _LocationSearchWidgetState extends State<LocationSearchWidget> {
},
onSuggestionSelected: (suggestion) {
Navigator.pop(context);
Navigator.of(context).push(MaterialPageRoute(
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => LocationSearchResultsPage(
ViewPort(
Location(
suggestion['geometry']['viewport']['northeast']
['lat'],
suggestion['geometry']['viewport']['northeast']
['lng']),
Location(
suggestion['geometry']['viewport']['southwest']
['lat'],
suggestion['geometry']['viewport']['southwest']
['lng'])),
suggestion['name'],
)));
ViewPort(
Location(
suggestion['geometry']['viewport']['northeast']['lat'],
suggestion['geometry']['viewport']['northeast']['lng'],
),
Location(
suggestion['geometry']['viewport']['southwest']['lat'],
suggestion['geometry']['viewport']['southwest']['lng'],
),
),
suggestion['name'],
),
),
);
},
);
}
@ -95,25 +98,27 @@ class LocationSearchResultWidget extends StatelessWidget {
return Container(
padding: new EdgeInsets.symmetric(vertical: 6.0, horizontal: 6.0),
margin: EdgeInsets.symmetric(vertical: 6.0),
child: Column(children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Icon(
Icons.location_on,
),
Padding(padding: EdgeInsets.only(left: 20.0)),
Flexible(
child: Container(
child: Text(
name,
overflow: TextOverflow.clip,
child: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Icon(
Icons.location_on,
),
Padding(padding: EdgeInsets.only(left: 20.0)),
Flexible(
child: Container(
child: Text(
name,
overflow: TextOverflow.clip,
),
),
),
),
],
),
]),
],
),
],
),
);
}
}

View file

@ -26,35 +26,37 @@ class _LockScreenState extends State<LockScreen> {
return Scaffold(
body: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Stack(
alignment: Alignment.center,
children: [
Image.asset(MediaQuery.of(context).platformBrightness ==
Brightness.light
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Stack(
alignment: Alignment.center,
children: [
Image.asset(
MediaQuery.of(context).platformBrightness == Brightness.light
? 'assets/loading_photos_light.png'
: 'assets/loading_photos_dark.png'),
SizedBox(
width: 172,
child: GradientButton(
child: Text(
'Unlock',
style: gradientButtonTextTheme(),
),
linearGradientColors: const [
Color(0xFF2CD267),
Color(0xFF1DB954),
],
onTap: () async {
_showLockScreen();
},
: 'assets/loading_photos_dark.png',
),
SizedBox(
width: 172,
child: GradientButton(
child: Text(
'Unlock',
style: gradientButtonTextTheme(),
),
linearGradientColors: const [
Color(0xFF2CD267),
Color(0xFF1DB954),
],
onTap: () async {
_showLockScreen();
},
),
],
),
]),
),
],
),
],
),
),
);
}
@ -63,7 +65,8 @@ class _LockScreenState extends State<LockScreen> {
_logger.info("Showing lockscreen");
try {
final result = await requestAuthentication(
"Please authenticate to view your memories");
"Please authenticate to view your memories",
);
if (result) {
AppLock.of(context).didUnlock();
}

View file

@ -75,8 +75,10 @@ class _LoginPageState extends State<LoginPage> {
Padding(
padding:
const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
child: Text('Welcome back!',
style: Theme.of(context).textTheme.headline4),
child: Text(
'Welcome back!',
style: Theme.of(context).textTheme.headline4,
),
),
Padding(
padding: const EdgeInsets.fromLTRB(20, 24, 20, 0),
@ -89,8 +91,9 @@ class _LoginPageState extends State<LoginPage> {
contentPadding:
EdgeInsets.symmetric(horizontal: 15, vertical: 15),
border: UnderlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(6)),
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(6),
),
suffixIcon: _emailIsValid
? Icon(
Icons.check,
@ -141,7 +144,8 @@ class _LoginPageState extends State<LoginPage> {
.copyWith(fontSize: 12),
children: [
TextSpan(
text: "By clicking log in, I agree to the "),
text: "By clicking log in, I agree to the ",
),
TextSpan(
text: "terms of service",
style: TextStyle(
@ -153,7 +157,9 @@ class _LoginPageState extends State<LoginPage> {
MaterialPageRoute(
builder: (BuildContext context) {
return WebPage(
"terms", "https://ente.io/terms");
"terms",
"https://ente.io/terms",
);
},
),
);
@ -170,8 +176,10 @@ class _LoginPageState extends State<LoginPage> {
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {
return WebPage("privacy",
"https://ente.io/privacy");
return WebPage(
"privacy",
"https://ente.io/privacy",
);
},
),
);

View file

@ -137,12 +137,15 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
inputResult == 'ok' &&
_textFieldController.text.trim().isNotEmpty) {
var propToUpdate = await _getEncryptedPassword(
_textFieldController.text);
_textFieldController.text,
);
await _updateUrlSettings(context, propToUpdate);
}
} else {
await _updateUrlSettings(
context, {'disablePassword': true});
context,
{'disablePassword': true},
);
}
setState(() {});
},
@ -166,25 +169,31 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
onChanged: (value) async {
if (!value) {
final choice = await showChoiceDialog(
context,
'Disable downloads',
'Are you sure that you want to disable the download button for files?',
firstAction: 'No',
secondAction: 'Yes',
firstActionColor:
Theme.of(context).colorScheme.greenText,
secondActionColor: Theme.of(context)
.colorScheme
.inverseBackgroundColor);
context,
'Disable downloads',
'Are you sure that you want to disable the download button for files?',
firstAction: 'No',
secondAction: 'Yes',
firstActionColor:
Theme.of(context).colorScheme.greenText,
secondActionColor: Theme.of(context)
.colorScheme
.inverseBackgroundColor,
);
if (choice != DialogUserChoice.secondChoice) {
return;
}
}
await _updateUrlSettings(
context, {'enableDownload': value});
context,
{'enableDownload': value},
);
if (!value) {
showErrorDialog(context, "Please note",
"Viewers can still take screenshots or save a copy of your photos using external tools");
showErrorDialog(
context,
"Please note",
"Viewers can still take screenshots or save a copy of your photos using external tools",
);
}
setState(() {});
},
@ -262,7 +271,9 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
}
if (newValidTill >= 0) {
await _updateUrlSettings(
context, {'validTill': newValidTill});
context,
{'validTill': newValidTill},
);
setState(() {});
}
Navigator.of(context).pop('');
@ -333,10 +344,11 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
Future<String> _displayLinkPasswordInput(BuildContext context) async {
_textFieldController.clear();
return showDialog<String>(
context: context,
builder: (context) {
bool _passwordVisible = false;
return StatefulBuilder(builder: (context, setState) {
context: context,
builder: (context) {
bool _passwordVisible = false;
return StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
title: Text('Enter password'),
content: TextFormField(
@ -369,8 +381,10 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
),
actions: <Widget>[
TextButton(
child: Text('Cancel',
style: Theme.of(context).textTheme.subtitle2),
child: Text(
'Cancel',
style: Theme.of(context).textTheme.subtitle2,
),
onPressed: () {
Navigator.pop(context, 'cancel');
},
@ -387,18 +401,26 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
),
],
);
});
});
},
);
},
);
}
Future<Map<String, dynamic>> _getEncryptedPassword(String pass) async {
assert(Sodium.cryptoPwhashAlgArgon2id13 == Sodium.cryptoPwhashAlgDefault,
"mismatch in expected default pw hashing algo");
assert(
Sodium.cryptoPwhashAlgArgon2id13 == Sodium.cryptoPwhashAlgDefault,
"mismatch in expected default pw hashing algo",
);
int memLimit = Sodium.cryptoPwhashMemlimitInteractive;
int opsLimit = Sodium.cryptoPwhashOpslimitInteractive;
final kekSalt = CryptoUtil.getSaltToDeriveKey();
final result = await CryptoUtil.deriveKey(
utf8.encode(pass), kekSalt, memLimit, opsLimit);
utf8.encode(pass),
kekSalt,
memLimit,
opsLimit,
);
return {
'passHash': Sodium.bin2base64(result),
'nonce': Sodium.bin2base64(kekSalt),
@ -408,7 +430,9 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
}
Future<void> _updateUrlSettings(
BuildContext context, Map<String, dynamic> prop) async {
BuildContext context,
Map<String, dynamic> prop,
) async {
final dialog = createProgressDialog(context, "Please wait...");
await dialog.show();
try {
@ -451,7 +475,8 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
List<Text> options = [];
for (int i = 50; i > 0; i--) {
options.add(
Text(i.toString(), style: Theme.of(context).textTheme.subtitle1));
Text(i.toString(), style: Theme.of(context).textTheme.subtitle1),
);
}
return showCupertinoModalPopup(
context: context,
@ -493,7 +518,8 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
onPressed: () async {
await _updateUrlSettings(context, {
'deviceLimit': int.tryParse(
options[_selectedDeviceLimitIndex].data),
options[_selectedDeviceLimitIndex].data,
),
});
setState(() {});
Navigator.of(context).pop('');

View file

@ -95,7 +95,9 @@ class _MemoryWidgetState extends State<MemoryWidget> {
return GestureDetector(
onTap: () async {
await routeToPage(
context, FullScreenMemory(title, widget.memories, index));
context,
FullScreenMemory(title, widget.memories, index),
);
setState(() {});
},
child: SizedBox(
@ -261,9 +263,12 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
),
Text(
getFormattedDate(
DateTime.fromMicrosecondsSinceEpoch(file.creationTime)),
DateTime.fromMicrosecondsSinceEpoch(file.creationTime),
),
style: Theme.of(context).textTheme.subtitle1.copyWith(
fontSize: 14, color: Colors.white), //same for both themes
fontSize: 14,
color: Colors.white,
), //same for both themes
),
],
),
@ -271,16 +276,17 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
),
flexibleSpace: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.black.withOpacity(0.6),
Colors.black.withOpacity(0.5),
Colors.transparent,
],
stops: const [0, 0.6, 1],
)),
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.black.withOpacity(0.6),
Colors.black.withOpacity(0.5),
Colors.transparent,
],
stops: const [0, 0.6, 1],
),
),
),
backgroundColor: Color(0x00000000),
elevation: 0,
@ -288,12 +294,15 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
extendBodyBehindAppBar: true,
body: Container(
color: Colors.black,
child: Stack(alignment: Alignment.bottomCenter, children: [
_buildSwiper(),
bottomGradient(),
_buildTitleText(),
_buildBottomIcons(),
]),
child: Stack(
alignment: Alignment.bottomCenter,
children: [
_buildSwiper(),
bottomGradient(),
_buildTitleText(),
_buildBottomIcons(),
],
),
),
);
}
@ -307,11 +316,13 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
child: AnimatedOpacity(
opacity: _opacity,
duration: Duration(milliseconds: 500),
child: Text(widget.title,
style: Theme.of(context)
.textTheme
.headline4
.copyWith(color: Colors.white)),
child: Text(
widget.title,
style: Theme.of(context)
.textTheme
.headline4
.copyWith(color: Colors.white),
),
),
),
);
@ -320,17 +331,18 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
Widget _buildBottomIcons() {
final file = widget.memories[_index].file;
return Container(
alignment: Alignment.bottomRight,
padding: EdgeInsets.fromLTRB(0, 0, 26, 20),
child: IconButton(
icon: Icon(
Icons.adaptive.share,
color: Colors.white,
), //same for both themes
onPressed: () {
share(context, [file]);
},
));
alignment: Alignment.bottomRight,
padding: EdgeInsets.fromLTRB(0, 0, 26, 20),
child: IconButton(
icon: Icon(
Icons.adaptive.share,
color: Colors.white,
), //same for both themes
onPressed: () {
share(context, [file]);
},
),
);
}
Widget bottomGradient() {
@ -338,15 +350,16 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
height: 124,
width: double.infinity,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
Colors.black.withOpacity(0.5), //same for both themes
Colors.transparent,
],
stops: const [0, 0.8],
)),
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
Colors.black.withOpacity(0.5), //same for both themes
Colors.transparent,
],
stops: const [0, 0.8],
),
),
);
}

View file

@ -77,65 +77,65 @@ class _GNavState extends State<GNav> {
selectedIndex = widget.selectedIndex;
return Container(
color: widget.backgroundColor ?? Colors.transparent,
// padding: EdgeInsets.all(12),
// alignment: Alignment.center,
child: Row(
mainAxisAlignment: widget.mainAxisAlignment,
children: widget.tabs
.map((t) => GButton(
key: t.key,
border: t.border ?? widget.tabBorder,
activeBorder: t.activeBorder ?? widget.tabActiveBorder,
borderRadius:
t.borderRadius ?? widget.tabBorderRadius != null
? BorderRadius.all(
Radius.circular(widget.tabBorderRadius))
: const BorderRadius.all(Radius.circular(100.0)),
debug: widget.debug ?? false,
margin: t.margin ?? widget.tabMargin,
active: selectedIndex == widget.tabs.indexOf(t),
gap: t.gap ?? widget.gap,
iconActiveColor: t.iconActiveColor ?? widget.activeColor,
iconColor: t.iconColor ?? widget.color,
iconSize: t.iconSize ?? widget.iconSize,
textColor: t.textColor ?? widget.activeColor,
rippleColor: t.rippleColor ??
widget.rippleColor ??
Colors.transparent,
hoverColor: t.hoverColor ??
widget.hoverColor ??
Colors.transparent,
padding: t.padding ?? widget.padding,
icon: t.icon,
haptic: widget.haptic ?? true,
leading: t.leading,
curve: widget.curve ?? Curves.easeInCubic,
backgroundGradient:
t.backgroundGradient ?? widget.tabBackgroundGradient,
backgroundColor: t.backgroundColor ??
widget.tabBackgroundColor ??
Colors.transparent,
duration:
widget.duration ?? const Duration(milliseconds: 500),
onPressed: () {
if (!clickable) return;
setState(() {
selectedIndex = widget.tabs.indexOf(t);
clickable = false;
});
widget.onTabChange(selectedIndex);
color: widget.backgroundColor ?? Colors.transparent,
// padding: EdgeInsets.all(12),
// alignment: Alignment.center,
child: Row(
mainAxisAlignment: widget.mainAxisAlignment,
children: widget.tabs
.map(
(t) => GButton(
key: t.key,
border: t.border ?? widget.tabBorder,
activeBorder: t.activeBorder ?? widget.tabActiveBorder,
borderRadius: t.borderRadius ?? widget.tabBorderRadius != null
? BorderRadius.all(
Radius.circular(widget.tabBorderRadius),
)
: const BorderRadius.all(Radius.circular(100.0)),
debug: widget.debug ?? false,
margin: t.margin ?? widget.tabMargin,
active: selectedIndex == widget.tabs.indexOf(t),
gap: t.gap ?? widget.gap,
iconActiveColor: t.iconActiveColor ?? widget.activeColor,
iconColor: t.iconColor ?? widget.color,
iconSize: t.iconSize ?? widget.iconSize,
textColor: t.textColor ?? widget.activeColor,
rippleColor:
t.rippleColor ?? widget.rippleColor ?? Colors.transparent,
hoverColor:
t.hoverColor ?? widget.hoverColor ?? Colors.transparent,
padding: t.padding ?? widget.padding,
icon: t.icon,
haptic: widget.haptic ?? true,
leading: t.leading,
curve: widget.curve ?? Curves.easeInCubic,
backgroundGradient:
t.backgroundGradient ?? widget.tabBackgroundGradient,
backgroundColor: t.backgroundColor ??
widget.tabBackgroundColor ??
Colors.transparent,
duration: widget.duration ?? const Duration(milliseconds: 500),
onPressed: () {
if (!clickable) return;
setState(() {
selectedIndex = widget.tabs.indexOf(t);
clickable = false;
});
widget.onTabChange(selectedIndex);
Future.delayed(
widget.duration ??
const Duration(milliseconds: 500), () {
setState(() {
clickable = true;
});
});
},
))
.toList()));
Future.delayed(
widget.duration ?? const Duration(milliseconds: 500), () {
setState(() {
clickable = true;
});
});
},
),
)
.toList(),
),
);
}
}
@ -237,31 +237,31 @@ class _GButtonState extends State<GButton> {
}
class Button extends StatefulWidget {
const Button(
{Key key,
this.icon,
this.iconSize,
this.leading,
this.iconActiveColor,
this.iconColor,
this.text,
this.gap = 0,
this.color,
this.rippleColor,
this.hoverColor,
this.onPressed,
this.duration,
this.curve,
this.padding = const EdgeInsets.all(25),
this.margin = const EdgeInsets.all(0),
this.active = false,
this.debug,
this.gradient,
this.borderRadius = const BorderRadius.all(Radius.circular(100.0)),
this.border,
this.activeBorder,
this.shadow})
: super(key: key);
const Button({
Key key,
this.icon,
this.iconSize,
this.leading,
this.iconActiveColor,
this.iconColor,
this.text,
this.gap = 0,
this.color,
this.rippleColor,
this.hoverColor,
this.onPressed,
this.duration,
this.curve,
this.padding = const EdgeInsets.all(25),
this.margin = const EdgeInsets.all(0),
this.active = false,
this.debug,
this.gradient,
this.borderRadius = const BorderRadius.all(Radius.circular(100.0)),
this.border,
this.activeBorder,
this.shadow,
}) : super(key: key);
final IconData icon;
final double iconSize;
@ -317,7 +317,8 @@ class _ButtonState extends State<Button> with TickerProviderStateMixin {
Widget build(BuildContext context) {
var curveValue = expandController
.drive(
CurveTween(curve: _expanded ? widget.curve : widget.curve.flipped))
CurveTween(curve: _expanded ? widget.curve : widget.curve.flipped),
)
.value;
_expanded = !widget.active;
@ -370,9 +371,11 @@ class _ButtonState extends State<Button> with TickerProviderStateMixin {
),
child: FittedBox(
fit: BoxFit.fitHeight,
child: Stack(children: [
Align(alignment: Alignment.centerLeft, child: icon),
]),
child: Stack(
children: [
Align(alignment: Alignment.centerLeft, child: icon),
],
),
),
),
),

View file

@ -69,7 +69,10 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
onPressedFunction: () {
if (widget.isChangeEmail) {
UserService.instance.changeEmail(
context, widget.email, _verificationCodeController.text);
context,
widget.email,
_verificationCodeController.text,
);
} else {
UserService.instance
.verifyEmail(context, _verificationCodeController.text);
@ -89,8 +92,10 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
children: [
Padding(
padding: const EdgeInsets.fromLTRB(20, 30, 20, 15),
child: Text('Verify email',
style: Theme.of(context).textTheme.headline4),
child: Text(
'Verify email',
style: Theme.of(context).textTheme.headline4,
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
@ -111,9 +116,11 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
children: [
TextSpan(text: "We've sent a mail to "),
TextSpan(
text: widget.email,
style: TextStyle(
color: Theme.of(context).buttonColor))
text: widget.email,
style: TextStyle(
color: Theme.of(context).buttonColor,
),
)
],
),
),
@ -144,8 +151,9 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
hintText: 'Tap to enter code',
contentPadding: EdgeInsets.all(15),
border: UnderlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(6)),
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(6),
),
),
controller: _verificationCodeController,
autofocus: false,
@ -166,13 +174,18 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
children: [
TextButton(
onPressed: () {
UserService.instance.getOtt(context, widget.email,
isCreateAccountScreen: widget.isCreateAccountScreen);
UserService.instance.getOtt(
context,
widget.email,
isCreateAccountScreen: widget.isCreateAccountScreen,
);
},
child: Text(
"Resend email",
style: Theme.of(context).textTheme.subtitle1.copyWith(
fontSize: 14, decoration: TextDecoration.underline),
fontSize: 14,
decoration: TextDecoration.underline,
),
),
)
],

View file

@ -59,7 +59,9 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
_volatilePassword = Configuration.instance.getVolatilePassword();
if (_volatilePassword != null) {
Future.delayed(
Duration.zero, () => _showRecoveryCodeDialog(_volatilePassword));
Duration.zero,
() => _showRecoveryCodeDialog(_volatilePassword),
);
}
_password1FocusNode.addListener(() {
setState(() {
@ -108,16 +110,17 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
),
body: _getBody(title),
floatingActionButton: DynamicFAB(
isKeypadOpen: isKeypadOpen,
isFormValid: _passwordsMatch,
buttonText: title,
onPressedFunction: () {
if (widget.mode == PasswordEntryMode.set) {
_showRecoveryCodeDialog(_passwordController1.text);
} else {
_updatePassword();
}
}),
isKeypadOpen: isKeypadOpen,
isFormValid: _passwordsMatch,
buttonText: title,
onPressedFunction: () {
if (widget.mode == PasswordEntryMode.set) {
_showRecoveryCodeDialog(_passwordController1.text);
} else {
_updatePassword();
}
},
),
floatingActionButtonLocation: fabLocation(),
floatingActionButtonAnimator: NoScalingAnimation(),
);
@ -146,8 +149,10 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
Padding(
padding:
const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
child: Text(buttonTextAndHeading,
style: Theme.of(context).textTheme.headline4),
child: Text(
buttonTextAndHeading,
style: Theme.of(context).textTheme.headline4,
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
@ -166,22 +171,26 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: RichText(
text: TextSpan(
style: Theme.of(context)
.textTheme
.subtitle1
.copyWith(fontSize: 14),
children: [
text: TextSpan(
style: Theme.of(context)
.textTheme
.subtitle1
.copyWith(fontSize: 14),
children: [
TextSpan(
text:
"We don't store this password, so if you forget, "),
text:
"We don't store this password, so if you forget, ",
),
TextSpan(
text: "we cannot decrypt your data",
style: Theme.of(context).textTheme.subtitle1.copyWith(
fontSize: 14,
decoration: TextDecoration.underline),
fontSize: 14,
decoration: TextDecoration.underline,
),
),
])),
],
),
),
),
Padding(padding: EdgeInsets.all(12)),
Visibility(
@ -209,8 +218,9 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
hintText: "Password",
contentPadding: EdgeInsets.all(20),
border: UnderlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(6)),
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(6),
),
suffixIcon: _password1InFocus
? IconButton(
icon: Icon(
@ -274,7 +284,9 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
filled: true,
hintText: "Confirm password",
contentPadding: EdgeInsets.symmetric(
horizontal: 20, vertical: 20),
horizontal: 20,
vertical: 20,
),
suffixIcon: _password2InFocus
? IconButton(
icon: Icon(
@ -301,8 +313,9 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
)
: null,
border: UnderlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(6)),
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(6),
),
),
focusNode: _password2FocusNode,
onChanged: (cnfPassword) {
@ -321,66 +334,74 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
visible:
((_passwordInInputBox != '') && _password1InFocus),
child: Positioned(
bottom: 24,
child: Row(
children: [
SizedBox(
width: MediaQuery.of(context).size.width,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20),
child: Container(
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
spreadRadius: 0.5,
color: Theme.of(context).hintColor,
offset: Offset(0, -0.325),
),
],
borderRadius: BorderRadius.only(
topLeft: Radius.zero,
topRight: Radius.zero,
bottomLeft: Radius.circular(5),
bottomRight: Radius.circular(5),
bottom: 24,
child: Row(
children: [
SizedBox(
width: MediaQuery.of(context).size.width,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20,
),
child: Container(
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
spreadRadius: 0.5,
color: Theme.of(context).hintColor,
offset: Offset(0, -0.325),
),
color: Theme.of(context)
.dialogTheme
.backgroundColor,
],
borderRadius: BorderRadius.only(
topLeft: Radius.zero,
topRight: Radius.zero,
bottomLeft: Radius.circular(5),
bottomRight: Radius.circular(5),
),
width: double.infinity,
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(
4.0, 4, 4.0, 4.0),
child: Row(
children: [
Padding(
padding:
EdgeInsets.symmetric(
horizontal: 10,
vertical: 5),
child: Text(
'Password Strength: $passwordStrengthText',
style: TextStyle(
color:
passwordStrengthColor),
)),
],
color: Theme.of(context)
.dialogTheme
.backgroundColor,
),
width: double.infinity,
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(
4.0,
4,
4.0,
4.0,
),
child: Row(
children: [
Padding(
padding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 5,
),
child: Text(
'Password Strength: $passwordStrengthText',
style: TextStyle(
color: passwordStrengthColor,
),
),
),
),
]),
],
),
),
],
),
),
),
SizedBox(
width: 20,
),
],
)),
),
SizedBox(
width: 20,
),
],
),
),
),
],
clipBehavior: Clip.none,
@ -395,7 +416,9 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
MaterialPageRoute(
builder: (BuildContext context) {
return WebPage(
"How it works", "https://ente.io/architecture");
"How it works",
"https://ente.io/architecture",
);
},
),
);
@ -404,10 +427,12 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
padding: EdgeInsets.symmetric(horizontal: 20),
child: RichText(
text: TextSpan(
text: "How it works",
style: Theme.of(context).textTheme.subtitle1.copyWith(
text: "How it works",
style: Theme.of(context).textTheme.subtitle1.copyWith(
fontSize: 14,
decoration: TextDecoration.underline)),
decoration: TextDecoration.underline,
),
),
),
),
),
@ -473,21 +498,25 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
}
routeToPage(
context,
RecoveryKeyPage(
result.privateKeyAttributes.recoveryKey,
"Continue",
showAppBar: false,
isDismissible: false,
onDone: onDone,
showProgressBar: true,
));
context,
RecoveryKeyPage(
result.privateKeyAttributes.recoveryKey,
"Continue",
showAppBar: false,
isDismissible: false,
onDone: onDone,
showProgressBar: true,
),
);
} catch (e) {
_logger.severe(e);
await dialog.hide();
if (e is UnsupportedError) {
showErrorDialog(context, "Insecure device",
"Sorry, we could not generate secure keys on this device.\n\nplease sign up from a different device.");
showErrorDialog(
context,
"Insecure device",
"Sorry, we could not generate secure keys on this device.\n\nplease sign up from a different device.",
);
} else {
showGenericErrorDialog(context);
}

View file

@ -63,8 +63,9 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
await dialog.show();
try {
await Configuration.instance.decryptAndSaveSecrets(
_passwordController.text,
Configuration.instance.getKeyAttributes());
_passwordController.text,
Configuration.instance.getKeyAttributes(),
);
} catch (e) {
Logger("PRP").warning(e);
await dialog.hide();
@ -90,8 +91,10 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
Padding(
padding:
const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
child: Text('Welcome back!',
style: Theme.of(context).textTheme.headline4),
child: Text(
'Welcome back!',
style: Theme.of(context).textTheme.headline4,
),
),
Padding(
padding: const EdgeInsets.fromLTRB(20, 24, 20, 0),
@ -102,8 +105,9 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
filled: true,
contentPadding: EdgeInsets.all(20),
border: UnderlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(6)),
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(6),
),
suffixIcon: _passwordInFocus
? IconButton(
icon: Icon(
@ -159,13 +163,14 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
},
child: Container(
child: Center(
child: Text("Forgot password",
style: Theme.of(context)
.textTheme
.subtitle1
.copyWith(
child: Text(
"Forgot password",
style:
Theme.of(context).textTheme.subtitle1.copyWith(
fontSize: 14,
decoration: TextDecoration.underline)),
decoration: TextDecoration.underline,
),
),
),
),
),
@ -182,13 +187,14 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
},
child: Container(
child: Center(
child: Text("Change email",
style: Theme.of(context)
.textTheme
.subtitle1
.copyWith(
child: Text(
"Change email",
style:
Theme.of(context).textTheme.subtitle1.copyWith(
fontSize: 14,
decoration: TextDecoration.underline)),
decoration: TextDecoration.underline,
),
),
),
),
),

View file

@ -52,69 +52,69 @@ class _PaymentWebPageState extends State<PaymentWebPage> {
return loadWidget;
}
return WillPopScope(
onWillPop: () async => _buildPageExitWidget(context),
child: Scaffold(
appBar: AppBar(
title: const Text('Subscription'),
),
body: Column(
children: <Widget>[
(progress != 1.0)
? LinearProgressIndicator(value: progress)
: Container(),
Expanded(
child: InAppWebView(
initialUrlRequest: URLRequest(url: initPaymentUrl),
onProgressChanged:
(InAppWebViewController controller, int progress) {
setState(() {
this.progress = progress / 100;
});
},
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
useShouldOverrideUrlLoading: true,
),
onWillPop: () async => _buildPageExitWidget(context),
child: Scaffold(
appBar: AppBar(
title: const Text('Subscription'),
),
body: Column(
children: <Widget>[
(progress != 1.0)
? LinearProgressIndicator(value: progress)
: Container(),
Expanded(
child: InAppWebView(
initialUrlRequest: URLRequest(url: initPaymentUrl),
onProgressChanged:
(InAppWebViewController controller, int progress) {
setState(() {
this.progress = progress / 100;
});
},
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
useShouldOverrideUrlLoading: true,
),
shouldOverrideUrlLoading:
(controller, navigationAction) async {
var loadingUri = navigationAction.request.url;
_logger.info("Loading url $loadingUri");
// handle the payment response
if (_isPaymentActionComplete(loadingUri)) {
await _handlePaymentResponse(loadingUri);
return NavigationActionPolicy.CANCEL;
}
return NavigationActionPolicy.ALLOW;
},
onConsoleMessage: (controller, consoleMessage) {
_logger.info(consoleMessage);
},
onLoadStart: (controller, navigationAction) async {
if (!_dialog.isShowing()) {
await _dialog.show();
}
},
onLoadError: (controller, navigationAction, code, msg) async {
if (_dialog.isShowing()) {
await _dialog.hide();
}
},
onLoadHttpError:
(controller, navigationAction, code, msg) async {
_logger.info("onHttpError with $code and msg = $msg");
},
onLoadStop: (controller, navigationAction) async {
_logger.info("loadStart" + navigationAction.toString());
if (_dialog.isShowing()) {
await _dialog.hide();
}
},
),
shouldOverrideUrlLoading: (controller, navigationAction) async {
var loadingUri = navigationAction.request.url;
_logger.info("Loading url $loadingUri");
// handle the payment response
if (_isPaymentActionComplete(loadingUri)) {
await _handlePaymentResponse(loadingUri);
return NavigationActionPolicy.CANCEL;
}
return NavigationActionPolicy.ALLOW;
},
onConsoleMessage: (controller, consoleMessage) {
_logger.info(consoleMessage);
},
onLoadStart: (controller, navigationAction) async {
if (!_dialog.isShowing()) {
await _dialog.show();
}
},
onLoadError: (controller, navigationAction, code, msg) async {
if (_dialog.isShowing()) {
await _dialog.hide();
}
},
onLoadHttpError:
(controller, navigationAction, code, msg) async {
_logger.info("onHttpError with $code and msg = $msg");
},
onLoadStop: (controller, navigationAction) async {
_logger.info("loadStart" + navigationAction.toString());
if (_dialog.isShowing()) {
await _dialog.hide();
}
},
),
].where((Object o) => o != null).toList(),
),
));
),
].where((Object o) => o != null).toList(),
),
),
);
}
@override
@ -146,10 +146,12 @@ class _PaymentWebPageState extends State<PaymentWebPage> {
title: Text('Are you sure you want to exit?'),
actions: <Widget>[
TextButton(
child: Text('Yes',
style: TextStyle(
color: Colors.redAccent,
)),
child: Text(
'Yes',
style: TextStyle(
color: Colors.redAccent,
),
),
onPressed: () => Navigator.of(context).pop(true),
),
TextButton(
@ -188,19 +190,21 @@ class _PaymentWebPageState extends State<PaymentWebPage> {
Future<void> _handlePaymentFailure(String reason) async {
await showDialog(
context: context,
barrierDismissible: false,
builder: (context) => AlertDialog(
title: Text('Payment failed'),
content:
Text("Unfortunately your payment failed due to $reason"),
actions: <Widget>[
TextButton(
child: Text('Ok'),
onPressed: () {
Navigator.of(context).pop('dialog');
}),
]));
context: context,
barrierDismissible: false,
builder: (context) => AlertDialog(
title: Text('Payment failed'),
content: Text("Unfortunately your payment failed due to $reason"),
actions: <Widget>[
TextButton(
child: Text('Ok'),
onPressed: () {
Navigator.of(context).pop('dialog');
},
),
],
),
);
Navigator.of(context).pop(true);
}
@ -210,8 +214,10 @@ class _PaymentWebPageState extends State<PaymentWebPage> {
await _dialog.show();
try {
var response = await billingService.verifySubscription(
widget.planId, checkoutSessionID,
paymentProvider: kStripe);
widget.planId,
checkoutSessionID,
paymentProvider: kStripe,
);
await _dialog.hide();
if (response != null) {
var content = widget.actionType == 'buy'
@ -241,13 +247,14 @@ class _PaymentWebPageState extends State<PaymentWebPage> {
content: Text(content),
actions: <Widget>[
TextButton(
child: Text(
'Ok',
style: TextStyle(color: Theme.of(context).buttonColor),
),
onPressed: () {
Navigator.of(context).pop('dialog');
}),
child: Text(
'Ok',
style: TextStyle(color: Theme.of(context).buttonColor),
),
onPressed: () {
Navigator.of(context).pop('dialog');
},
),
],
),
).then((val) => Navigator.pop(context, true));

View file

@ -117,31 +117,36 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
final appBar = PreferredSize(
preferredSize: Size(double.infinity, 60),
child: Container(
decoration: BoxDecoration(boxShadow: [
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Theme.of(context).backgroundColor,
blurRadius: 16,
offset: Offset(0, 8))
]),
child: widget.isOnboarding
? AppBar(
elevation: 0,
title: Hero(
tag: "subscription",
child: StepProgressIndicator(
totalSteps: 4,
currentStep: 4,
selectedColor: Theme.of(context).buttonColor,
roundedEdges: Radius.circular(10),
unselectedColor: Theme.of(context)
.colorScheme
.stepProgressUnselectedColor,
)),
)
: AppBar(
elevation: 0,
title: Text("Subscription"),
)),
color: Theme.of(context).backgroundColor,
blurRadius: 16,
offset: Offset(0, 8),
)
],
),
child: widget.isOnboarding
? AppBar(
elevation: 0,
title: Hero(
tag: "subscription",
child: StepProgressIndicator(
totalSteps: 4,
currentStep: 4,
selectedColor: Theme.of(context).buttonColor,
roundedEdges: Radius.circular(10),
unselectedColor: Theme.of(context)
.colorScheme
.stepProgressUnselectedColor,
),
),
)
: AppBar(
elevation: 0,
title: Text("Subscription"),
),
),
);
return Scaffold(
appBar: appBar,
@ -176,15 +181,18 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
Widget _buildPlans() {
final widgets = <Widget>[];
widgets.add(SubscriptionHeaderWidget(
isOnboarding: widget.isOnboarding,
currentUsage: _userDetails.getFamilyOrPersonalUsage(),
));
widgets.add(
SubscriptionHeaderWidget(
isOnboarding: widget.isOnboarding,
currentUsage: _userDetails.getFamilyOrPersonalUsage(),
),
);
widgets.addAll([
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: _getStripePlanWidgets()),
mainAxisAlignment: MainAxisAlignment.center,
children: _getStripePlanWidgets(),
),
Padding(padding: EdgeInsets.all(4)),
]);
@ -218,16 +226,19 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
break;
case kPlayStore:
launch(
"https://play.google.com/store/account/subscriptions?sku=" +
_currentSubscription.productID +
"&package=io.ente.photos");
"https://play.google.com/store/account/subscriptions?sku=" +
_currentSubscription.productID +
"&package=io.ente.photos",
);
break;
case kAppStore:
launch("https://apps.apple.com/account/billing");
break;
default:
_logger.severe(
"unexpected payment provider ", _currentSubscription);
"unexpected payment provider ",
_currentSubscription,
);
}
},
child: Container(
@ -236,17 +247,18 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
children: [
RichText(
text: TextSpan(
text: !_isStripeSubscriber
? "visit ${_currentSubscription.paymentProvider} to manage your subscription"
: "Payment details",
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface,
fontFamily: 'Inter-Medium',
fontSize: 14,
decoration: _isStripeSubscriber
? TextDecoration.underline
: TextDecoration.none,
)),
text: !_isStripeSubscriber
? "visit ${_currentSubscription.paymentProvider} to manage your subscription"
: "Payment details",
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface,
fontFamily: 'Inter-Medium',
fontSize: 14,
decoration: _isStripeSubscriber
? TextDecoration.underline
: TextDecoration.none,
),
),
textAlign: TextAlign.center,
),
],
@ -316,21 +328,26 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
Future<void> _launchFamilyPortal() async {
if (_userDetails.subscription.productID == kFreeProductID) {
await showErrorDialog(
context,
"Now you can share your storage plan with your family members!",
"Customers on paid plans can add up to 5 family members without paying extra. Each member gets their own private space.");
context,
"Now you can share your storage plan with your family members!",
"Customers on paid plans can add up to 5 family members without paying extra. Each member gets their own private space.",
);
return;
}
await _dialog.show();
try {
final String jwtToken = await _userService.getFamiliesToken();
final bool familyExist = _userDetails.isPartOfFamily();
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) {
return WebPage("Family",
'$kFamilyPlanManagementUrl?token=$jwtToken&isFamilyCreated=$familyExist');
},
)).then((value) => onWebPaymentGoBack);
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {
return WebPage(
"Family",
'$kFamilyPlanManagementUrl?token=$jwtToken&isFamilyCreated=$familyExist',
);
},
),
).then((value) => onWebPaymentGoBack);
} catch (e) {
await _dialog.hide();
showGenericErrorDialog(context);
@ -357,15 +374,22 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
bool confirmAction = false;
if (isRenewCancelled) {
var choice = await showChoiceDialog(
context, title, "are you sure you want to renew?",
firstAction: "no", secondAction: "yes");
context,
title,
"are you sure you want to renew?",
firstAction: "no",
secondAction: "yes",
);
confirmAction = choice == DialogUserChoice.secondChoice;
} else {
var choice = await showChoiceDialog(
context, title, 'are you sure you want to cancel?',
firstAction: 'yes, cancel',
secondAction: 'no',
actionType: ActionType.critical);
context,
title,
'are you sure you want to cancel?',
firstAction: 'yes, cancel',
secondAction: 'no',
actionType: ActionType.critical,
);
confirmAction = choice == DialogUserChoice.firstChoice;
}
if (confirmAction) {
@ -384,7 +408,9 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
await _fetchSub();
} catch (e) {
showToast(
context, isRenewCancelled ? 'failed to renew' : 'failed to cancel');
context,
isRenewCancelled ? 'failed to renew' : 'failed to cancel',
);
}
await _dialog.hide();
}
@ -414,24 +440,31 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
if (!_isStripeSubscriber &&
_hasActiveSubscription &&
_currentSubscription.productID != kFreeProductID) {
showErrorDialog(context, "Sorry",
"please cancel your existing subscription from ${_currentSubscription.paymentProvider} first");
showErrorDialog(
context,
"Sorry",
"please cancel your existing subscription from ${_currentSubscription.paymentProvider} first",
);
return;
}
if (_userDetails.getFamilyOrPersonalUsage() > plan.storage) {
showErrorDialog(
context, "Sorry", "you cannot downgrade to this plan");
context,
"Sorry",
"you cannot downgrade to this plan",
);
return;
}
String stripPurChaseAction = 'buy';
if (_isStripeSubscriber && _hasActiveSubscription) {
// confirm if user wants to change plan or not
var result = await showChoiceDialog(
context,
"Confirm plan change",
"Are you sure you want to change your plan?",
firstAction: "No",
secondAction: 'Yes');
context,
"Confirm plan change",
"Are you sure you want to change your plan?",
firstAction: "No",
secondAction: 'Yes',
);
if (result != DialogUserChoice.secondChoice) {
return;
}

View file

@ -10,9 +10,11 @@ class SubscriptionHeaderWidget extends StatefulWidget {
final bool isOnboarding;
final int currentUsage;
const SubscriptionHeaderWidget(
{Key key, this.isOnboarding, this.currentUsage})
: super(key: key);
const SubscriptionHeaderWidget({
Key key,
this.isOnboarding,
this.currentUsage,
}) : super(key: key);
@override
State<StatefulWidget> createState() {
@ -57,8 +59,9 @@ class _SubscriptionHeaderWidgetState extends State<SubscriptionHeaderWidget> {
text: TextSpan(
children: [
TextSpan(
text: "Current usage is ",
style: Theme.of(context).textTheme.subtitle1),
text: "Current usage is ",
style: Theme.of(context).textTheme.subtitle1,
),
TextSpan(
text: formatBytes(widget.currentUsage),
style: Theme.of(context)
@ -86,7 +89,8 @@ class ValidityWidget extends StatelessWidget {
return Container();
}
var endDate = getDateAndMonthAndYear(
DateTime.fromMicrosecondsSinceEpoch(currentSubscription.expiryTime));
DateTime.fromMicrosecondsSinceEpoch(currentSubscription.expiryTime),
);
var message = "Renews on $endDate";
if (currentSubscription.productID == kFreeProductID) {
message = "Free plan valid till $endDate";

View file

@ -99,11 +99,12 @@ class _SubscriptionPageState extends State<SubscriptionPage> {
_logger.warning("Could not complete payment ", e);
await _dialog.hide();
showErrorDialog(
context,
"payment failed",
"please talk to " +
(Platform.isAndroid ? "PlayStore" : "AppStore") +
" support if you were charged");
context,
"payment failed",
"please talk to " +
(Platform.isAndroid ? "PlayStore" : "AppStore") +
" support if you were charged",
);
return;
}
} else if (Platform.isIOS && purchase.pendingCompletePurchase) {
@ -175,10 +176,12 @@ class _SubscriptionPageState extends State<SubscriptionPage> {
Widget _buildPlans() {
final widgets = <Widget>[];
widgets.add(SubscriptionHeaderWidget(
isOnboarding: widget.isOnboarding,
currentUsage: _userDetails.getFamilyOrPersonalUsage(),
));
widgets.add(
SubscriptionHeaderWidget(
isOnboarding: widget.isOnboarding,
currentUsage: _userDetails.getFamilyOrPersonalUsage(),
),
);
widgets.addAll([
Column(
@ -213,9 +216,10 @@ class _SubscriptionPageState extends State<SubscriptionPage> {
}
if (Platform.isAndroid) {
launch(
"https://play.google.com/store/account/subscriptions?sku=" +
_currentSubscription.productID +
"&package=io.ente.photos");
"https://play.google.com/store/account/subscriptions?sku=" +
_currentSubscription.productID +
"&package=io.ente.photos",
);
} else {
launch("https://apps.apple.com/account/billing");
}
@ -307,8 +311,11 @@ class _SubscriptionPageState extends State<SubscriptionPage> {
if (isActive) {
return;
}
showErrorDialog(context, "Sorry",
"Please visit web.ente.io to manage your subscription");
showErrorDialog(
context,
"Sorry",
"Please visit web.ente.io to manage your subscription",
);
},
child: SubscriptionPlanWidget(
storage: plan.storage,
@ -357,7 +364,10 @@ class _SubscriptionPageState extends State<SubscriptionPage> {
}
if (_userDetails.getFamilyOrPersonalUsage() > plan.storage) {
showErrorDialog(
context, "Sorry", "you cannot downgrade to this plan");
context,
"Sorry",
"you cannot downgrade to this plan",
);
return;
}
await _dialog.show();
@ -365,8 +375,9 @@ class _SubscriptionPageState extends State<SubscriptionPage> {
await InAppPurchaseConnection.instance
.queryProductDetails({productID});
if (response.notFoundIDs.isNotEmpty) {
_logger.severe("Could not find products: " +
response.notFoundIDs.toString());
_logger.severe(
"Could not find products: " + response.notFoundIDs.toString(),
);
await _dialog.hide();
showGenericErrorDialog(context);
return;
@ -380,8 +391,10 @@ class _SubscriptionPageState extends State<SubscriptionPage> {
await InAppPurchaseConnection.instance
.queryProductDetails({_currentSubscription.productID});
if (existingProductDetailsResponse.notFoundIDs.isNotEmpty) {
_logger.severe("Could not find existing products: " +
response.notFoundIDs.toString());
_logger.severe(
"Could not find existing products: " +
response.notFoundIDs.toString(),
);
await _dialog.hide();
showGenericErrorDialog(context);
return;
@ -451,21 +464,26 @@ class _SubscriptionPageState extends State<SubscriptionPage> {
Future<void> _launchFamilyPortal() async {
if (_userDetails.subscription.productID == kFreeProductID) {
await showErrorDialog(
context,
"Now you can share your storage plan with your family members!",
"Customers on paid plans can add up to 5 family members without paying extra. Each member gets their own private space.");
context,
"Now you can share your storage plan with your family members!",
"Customers on paid plans can add up to 5 family members without paying extra. Each member gets their own private space.",
);
return;
}
await _dialog.show();
try {
final String jwtToken = await _userService.getFamiliesToken();
final bool familyExist = _userDetails.isPartOfFamily();
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) {
return WebPage("Family",
'$kFamilyPlanManagementUrl?token=$jwtToken&isFamilyCreated=$familyExist');
},
));
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {
return WebPage(
"Family",
'$kFamilyPlanManagementUrl?token=$jwtToken&isFamilyCreated=$familyExist',
);
},
),
);
} catch (e) {
await _dialog.hide();
showGenericErrorDialog(context);

View file

@ -58,9 +58,13 @@ class SubscriptionPlanWidget extends StatelessWidget {
.headline6
.copyWith(color: textColor),
),
Text(_displayPrice(),
style: Theme.of(context).textTheme.headline6.copyWith(
color: textColor, fontWeight: FontWeight.normal)),
Text(
_displayPrice(),
style: Theme.of(context).textTheme.headline6.copyWith(
color: textColor,
fontWeight: FontWeight.normal,
),
),
],
),
],

View file

@ -21,9 +21,15 @@ bool _barrierDismissible = true, _showLogs = false;
Color _barrierColor;
TextStyle _progressTextStyle = TextStyle(
color: Colors.black, fontSize: 12.0, fontWeight: FontWeight.w400),
color: Colors.black,
fontSize: 12.0,
fontWeight: FontWeight.w400,
),
_messageStyle = TextStyle(
color: Colors.black, fontSize: 18.0, fontWeight: FontWeight.w600);
color: Colors.black,
fontSize: 18.0,
fontWeight: FontWeight.w600,
);
double _dialogElevation = 8.0, _borderRadius = 8.0;
Color _backgroundColor = Colors.white;
@ -56,21 +62,22 @@ class ProgressDialog {
_barrierColor = barrierColor ?? barrierColor;
}
void style(
{Widget child,
double progress,
double maxProgress,
String message,
Widget progressWidget,
Color backgroundColor,
TextStyle progressTextStyle,
TextStyle messageTextStyle,
double elevation,
TextAlign textAlign,
double borderRadius,
Curve insetAnimCurve,
EdgeInsets padding,
Alignment progressWidgetAlignment}) {
void style({
Widget child,
double progress,
double maxProgress,
String message,
Widget progressWidget,
Color backgroundColor,
TextStyle progressTextStyle,
TextStyle messageTextStyle,
double elevation,
TextAlign textAlign,
double borderRadius,
Curve insetAnimCurve,
EdgeInsets padding,
Alignment progressWidgetAlignment,
}) {
if (_isShowing) return;
if (_progressDialogType == ProgressDialogType.Download) {
_progress = progress ?? _progress;
@ -92,13 +99,14 @@ class ProgressDialog {
progressWidgetAlignment ?? _progressWidgetAlignment;
}
void update(
{double progress,
double maxProgress,
String message,
Widget progressWidget,
TextStyle progressTextStyle,
TextStyle messageTextStyle}) {
void update({
double progress,
double maxProgress,
String message,
Widget progressWidget,
TextStyle progressTextStyle,
TextStyle messageTextStyle,
}) {
if (_progressDialogType == ProgressDialogType.Download) {
_progress = progress ?? _progress;
}
@ -152,8 +160,9 @@ class ProgressDialog {
insetAnimationDuration: Duration(milliseconds: 100),
elevation: _dialogElevation,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.all(Radius.circular(_borderRadius))),
borderRadius:
BorderRadius.all(Radius.circular(_borderRadius)),
),
child: _dialog,
),
);
@ -232,11 +241,12 @@ class _BodyState extends State<_Body> {
Row(
children: <Widget>[
Expanded(
child: Text(
_dialogMessage,
style: _messageStyle,
textDirection: _direction,
)),
child: Text(
_dialogMessage,
style: _messageStyle,
textDirection: _direction,
),
),
],
),
SizedBox(height: 4.0),

View file

@ -22,16 +22,18 @@ class RecoveryKeyPage extends StatefulWidget {
final String subText;
final bool showProgressBar;
const RecoveryKeyPage(this.recoveryKey, this.doneText,
{Key key,
this.showAppBar,
this.onDone,
this.isDismissible,
this.title,
this.text,
this.subText,
this.showProgressBar = false})
: super(key: key);
const RecoveryKeyPage(
this.recoveryKey,
this.doneText, {
Key key,
this.showAppBar,
this.onDone,
this.isDismissible,
this.title,
this.text,
this.subText,
this.showProgressBar = false,
}) : super(key: key);
@override
_RecoveryKeyPageState createState() => _RecoveryKeyPageState();
@ -40,14 +42,16 @@ class RecoveryKeyPage extends StatefulWidget {
class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
bool _hasTriedToSave = false;
final _recoveryKeyFile = io.File(
Configuration.instance.getTempDirectory() + "ente-recovery-key.txt");
Configuration.instance.getTempDirectory() + "ente-recovery-key.txt",
);
@override
Widget build(BuildContext context) {
final String recoveryKey = bip39.entropyToMnemonic(widget.recoveryKey);
if (recoveryKey.split(' ').length != kMnemonicKeyWordCount) {
throw AssertionError(
'recovery code should have $kMnemonicKeyWordCount words');
'recovery code should have $kMnemonicKeyWordCount words',
);
}
return Scaffold(
@ -74,22 +78,25 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
: null,
body: Padding(
padding: EdgeInsets.fromLTRB(
20,
widget.showAppBar
? 40
: widget.showProgressBar
? 32
: 120,
20,
20),
20,
widget.showAppBar
? 40
: widget.showProgressBar
? 32
: 120,
20,
20,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.max,
children: [
widget.showAppBar
? const SizedBox.shrink()
: Text(widget.title ?? "Recovery key",
style: Theme.of(context).textTheme.headline4),
: Text(
widget.title ?? "Recovery key",
style: Theme.of(context).textTheme.headline4,
),
Padding(padding: EdgeInsets.all(widget.showAppBar ? 0 : 12)),
Text(
widget.text ??
@ -116,7 +123,8 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
GestureDetector(
onTap: () async {
await Clipboard.setData(
ClipboardData(text: recoveryKey));
ClipboardData(text: recoveryKey),
);
showToast(context, "Recovery key copied to clipboard");
setState(() {
_hasTriedToSave = true;
@ -150,12 +158,13 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
height: 80,
width: double.infinity,
child: Padding(
child: Text(
widget.subText ??
"We dont store this key, please save this in a safe place.",
style: Theme.of(context).textTheme.bodyText1,
),
padding: EdgeInsets.symmetric(vertical: 20)),
child: Text(
widget.subText ??
"We dont store this key, please save this in a safe place.",
style: Theme.of(context).textTheme.bodyText1,
),
padding: EdgeInsets.symmetric(vertical: 20),
),
),
Expanded(
child: Container(
@ -163,9 +172,10 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
width: double.infinity,
padding: EdgeInsets.fromLTRB(10, 10, 10, 24),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _saveOptions(context, recoveryKey)),
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _saveOptions(context, recoveryKey),
),
),
)
],
@ -177,37 +187,43 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
List<Widget> _saveOptions(BuildContext context, String recoveryKey) {
List<Widget> childrens = [];
if (!_hasTriedToSave) {
childrens.add(ElevatedButton(
child: Text('Do this later'),
style: Theme.of(context).colorScheme.optionalActionButtonStyle,
onPressed: () async {
await _saveKeys();
},
));
childrens.add(
ElevatedButton(
child: Text('Do this later'),
style: Theme.of(context).colorScheme.optionalActionButtonStyle,
onPressed: () async {
await _saveKeys();
},
),
);
childrens.add(SizedBox(height: 10));
}
childrens.add(GradientButton(
child: Text(
'Save key',
style: gradientButtonTextTheme(),
childrens.add(
GradientButton(
child: Text(
'Save key',
style: gradientButtonTextTheme(),
),
linearGradientColors: const [
Color(0xFF2CD267),
Color(0xFF1DB954),
],
onTap: () async {
await _shareRecoveryKey(recoveryKey);
},
),
linearGradientColors: const [
Color(0xFF2CD267),
Color(0xFF1DB954),
],
onTap: () async {
await _shareRecoveryKey(recoveryKey);
},
));
);
if (_hasTriedToSave) {
childrens.add(SizedBox(height: 10));
childrens.add(ElevatedButton(
child: Text(widget.doneText),
onPressed: () async {
await _saveKeys();
},
));
childrens.add(
ElevatedButton(
child: Text(widget.doneText),
onPressed: () async {
await _saveKeys();
},
),
);
}
childrens.add(SizedBox(height: 12));
return childrens;

View file

@ -31,37 +31,38 @@ class _RecoveryPageState extends State<RecoveryPage> {
),
),
floatingActionButton: DynamicFAB(
isKeypadOpen: false,
isFormValid: _recoveryKey.text.isNotEmpty,
buttonText: 'Recover',
onPressedFunction: () async {
final dialog = createProgressDialog(context, "Decrypting...");
await dialog.show();
try {
await Configuration.instance.recover(_recoveryKey.text.trim());
await dialog.hide();
showToast(context, "Recovery successful!");
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (BuildContext context) {
return WillPopScope(
onWillPop: () async => false,
child: PasswordEntryPage(
mode: PasswordEntryMode.reset,
),
);
},
),
);
} catch (e) {
await dialog.hide();
String errMessage = 'the recovery key you entered is incorrect';
if (e is AssertionError) {
errMessage = '$errMessage : ${e.message}';
}
showErrorDialog(context, "incorrect recovery key", errMessage);
isKeypadOpen: false,
isFormValid: _recoveryKey.text.isNotEmpty,
buttonText: 'Recover',
onPressedFunction: () async {
final dialog = createProgressDialog(context, "Decrypting...");
await dialog.show();
try {
await Configuration.instance.recover(_recoveryKey.text.trim());
await dialog.hide();
showToast(context, "Recovery successful!");
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (BuildContext context) {
return WillPopScope(
onWillPop: () async => false,
child: PasswordEntryPage(
mode: PasswordEntryMode.reset,
),
);
},
),
);
} catch (e) {
await dialog.hide();
String errMessage = 'the recovery key you entered is incorrect';
if (e is AssertionError) {
errMessage = '$errMessage : ${e.message}';
}
}),
showErrorDialog(context, "incorrect recovery key", errMessage);
}
},
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButtonAnimator: NoScalingAnimation(),
body: Column(
@ -72,8 +73,10 @@ class _RecoveryPageState extends State<RecoveryPage> {
Padding(
padding:
const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
child: Text('Forgot password',
style: Theme.of(context).textTheme.headline4),
child: Text(
'Forgot password',
style: Theme.of(context).textTheme.headline4,
),
),
Padding(
padding: const EdgeInsets.fromLTRB(20, 24, 20, 0),
@ -83,8 +86,9 @@ class _RecoveryPageState extends State<RecoveryPage> {
hintText: "Enter your recovery key",
contentPadding: EdgeInsets.all(20),
border: UnderlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(6)),
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(6),
),
),
style: TextStyle(
fontSize: 14,
@ -122,12 +126,11 @@ class _RecoveryPageState extends State<RecoveryPage> {
child: Center(
child: Text(
"No recovery key?",
style: Theme.of(context)
.textTheme
.subtitle1
.copyWith(
fontSize: 14,
decoration: TextDecoration.underline),
style:
Theme.of(context).textTheme.subtitle1.copyWith(
fontSize: 14,
decoration: TextDecoration.underline,
),
),
),
),

View file

@ -74,12 +74,18 @@ class _RenameDialogState extends State<RenameDialog> {
onPressed: () {
if (_newName.trim().isEmpty) {
showErrorDialog(
context, "Empty name", "${widget.type} name cannot be empty");
context,
"Empty name",
"${widget.type} name cannot be empty",
);
return;
}
if (_newName.trim().length > widget.maxLength) {
showErrorDialog(context, "Name too large",
"${widget.type} name should be less than ${widget.maxLength} characters");
showErrorDialog(
context,
"Name too large",
"${widget.type} name should be less than ${widget.maxLength} characters",
);
return;
}
Navigator.of(context).pop(_newName.trim());

View file

@ -120,7 +120,10 @@ class _SessionsPageState extends State<SessionsPage> {
await dialog.hide();
_logger.severe('failed to terminate', e, s);
showErrorDialog(
context, 'Oops', "Something went wrong, please try again");
context,
'Oops',
"Something went wrong, please try again",
);
}
}

View file

@ -73,7 +73,9 @@ class _SetWallpaperDialogState extends State<SetWallpaperDialog> {
await dialog.show();
try {
await WallpaperManagerFlutter().setwallpaperfromFile(
await getFile(widget.file), _lockscreenValue);
await getFile(widget.file),
_lockscreenValue,
);
await dialog.hide();
showToast(context, "Wallpaper set successfully");
} catch (e, s) {

View file

@ -50,7 +50,9 @@ class AccountSectionWidgetState extends State<AccountSectionWidget> {
);
},
child: SettingsTextItem(
text: "Subscription plan", icon: Icons.navigate_next),
text: "Subscription plan",
icon: Icons.navigate_next,
),
),
SectionOptionDivider,
GestureDetector(
@ -74,9 +76,14 @@ class AccountSectionWidgetState extends State<AccountSectionWidget> {
return;
}
routeToPage(
context,
RecoveryKeyPage(recoveryKey, "OK",
showAppBar: true, onDone: () {}));
context,
RecoveryKeyPage(
recoveryKey,
"OK",
showAppBar: true,
onDone: () {},
),
);
},
child:
SettingsTextItem(text: "Recovery key", icon: Icons.navigate_next),
@ -130,7 +137,9 @@ class AccountSectionWidgetState extends State<AccountSectionWidget> {
);
},
child: SettingsTextItem(
text: "Change password", icon: Icons.navigate_next),
text: "Change password",
icon: Icons.navigate_next,
),
),
],
);
@ -138,6 +147,7 @@ class AccountSectionWidgetState extends State<AccountSectionWidget> {
Future<String> _getOrCreateRecoveryKey() async {
return Sodium.bin2hex(
await UserService.instance.getOrCreateRecoveryKey(context));
await UserService.instance.getOrCreateRecoveryKey(context),
);
}
}

Some files were not shown because too many files have changed in this diff Show more