Merge branch 'main' into report_bug_long_press
This commit is contained in:
commit
902ca7a7aa
87 changed files with 15034 additions and 377 deletions
2
.github/workflows/crowdin.yml
vendored
2
.github/workflows/crowdin.yml
vendored
|
@ -6,6 +6,8 @@ on:
|
|||
paths:
|
||||
- 'lib/l10n/intl_en.arb'
|
||||
branches: [ main ]
|
||||
schedule:
|
||||
- cron: '0 */12 * * *' # Every 12 hours - https://crontab.guru/#0_*/12_*_*_*
|
||||
|
||||
jobs:
|
||||
synchronize-with-crowdin:
|
||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -17,6 +17,7 @@
|
|||
|
||||
#Visual Studio Code related
|
||||
.vscode/launch.json
|
||||
.vscode/settings.json
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
|
@ -28,7 +29,6 @@
|
|||
.pub/
|
||||
/build/
|
||||
|
||||
lib/generated/*
|
||||
|
||||
# Web related
|
||||
lib/generated_plugin_registrant.dart
|
||||
|
|
|
@ -66,8 +66,9 @@ You can alternatively install the build from PlayStore or F-Droid.
|
|||
3. Pull in all submodules with `git submodule update --init --recursive`
|
||||
4. Enable repo git hooks `git config core.hooksPath hooks`
|
||||
5. Setup TensorFlowLite by executing `setup.sh`
|
||||
6. For Android, [setup your keystore](https://docs.flutter.dev/deployment/android#create-an-upload-keystore) and run `flutter build apk --release --flavor independent`
|
||||
7. For iOS, run `flutter build ios`
|
||||
6. If using Visual Studio Code, add the [Flutter Intl](https://marketplace.visualstudio.com/items?itemName=localizely.flutter-intl) extension
|
||||
7. For Android, [setup your keystore](https://docs.flutter.dev/deployment/android#create-an-upload-keystore) and run `flutter build apk --release --flavor independent`
|
||||
8. For iOS, run `flutter build ios`
|
||||
<br/>
|
||||
|
||||
## 🙋 Help
|
||||
|
|
17
lib/app.dart
17
lib/app.dart
|
@ -21,29 +21,44 @@ class EnteApp extends StatefulWidget {
|
|||
final Future<void> Function(String) runBackgroundTask;
|
||||
final Future<void> Function(String) killBackgroundTask;
|
||||
final AdaptiveThemeMode? savedThemeMode;
|
||||
final Locale locale;
|
||||
|
||||
const EnteApp(
|
||||
this.runBackgroundTask,
|
||||
this.killBackgroundTask,
|
||||
this.locale,
|
||||
this.savedThemeMode, {
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
static void setLocale(BuildContext context, Locale newLocale) {
|
||||
final state = context.findAncestorStateOfType<_EnteAppState>()!;
|
||||
state.setLocale(newLocale);
|
||||
}
|
||||
|
||||
@override
|
||||
State<EnteApp> createState() => _EnteAppState();
|
||||
}
|
||||
|
||||
class _EnteAppState extends State<EnteApp> with WidgetsBindingObserver {
|
||||
final _logger = Logger("EnteAppState");
|
||||
late Locale locale;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_logger.info('init App');
|
||||
super.initState();
|
||||
locale = widget.locale;
|
||||
setupIntentAction();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
}
|
||||
|
||||
setLocale(Locale newLocale) {
|
||||
setState(() {
|
||||
locale = newLocale;
|
||||
});
|
||||
}
|
||||
|
||||
void setupIntentAction() async {
|
||||
final mediaExtentionAction = Platform.isAndroid
|
||||
? await initIntentAction()
|
||||
|
@ -72,6 +87,7 @@ class _EnteAppState extends State<EnteApp> with WidgetsBindingObserver {
|
|||
: const HomeWidget(),
|
||||
debugShowCheckedModeBanner: false,
|
||||
builder: EasyLoading.init(),
|
||||
locale: locale,
|
||||
supportedLocales: appSupportedLocales,
|
||||
localeListResolutionCallback: localResolutionCallBack,
|
||||
localizationsDelegates: const [
|
||||
|
@ -89,6 +105,7 @@ class _EnteAppState extends State<EnteApp> with WidgetsBindingObserver {
|
|||
home: const HomeWidget(),
|
||||
debugShowCheckedModeBanner: false,
|
||||
builder: EasyLoading.init(),
|
||||
locale: locale,
|
||||
supportedLocales: appSupportedLocales,
|
||||
localeListResolutionCallback: localResolutionCallBack,
|
||||
localizationsDelegates: const [
|
||||
|
|
|
@ -57,3 +57,11 @@ const double restrictedMaxWidth = 430;
|
|||
const double mobileSmallThreshold = 336;
|
||||
|
||||
const publicLinkDeviceLimits = [50, 25, 10, 5, 2, 1];
|
||||
|
||||
const kilometersPerDegree = 111.16;
|
||||
|
||||
const radiusValues = <int>[2, 10, 20, 40, 80, 200, 400, 1200];
|
||||
|
||||
const defaultRadiusValueIndex = 4;
|
||||
|
||||
const galleryGridSpacing = 2.0;
|
||||
|
|
65
lib/db/entities_db.dart
Normal file
65
lib/db/entities_db.dart
Normal file
|
@ -0,0 +1,65 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:photos/db/files_db.dart';
|
||||
import "package:photos/models/api/entity/type.dart";
|
||||
import "package:photos/models/local_entity_data.dart";
|
||||
import 'package:sqflite/sqlite_api.dart';
|
||||
|
||||
extension EntitiesDB on FilesDB {
|
||||
Future<void> upsertEntities(
|
||||
List<LocalEntityData> data, {
|
||||
ConflictAlgorithm conflictAlgorithm = ConflictAlgorithm.replace,
|
||||
}) async {
|
||||
debugPrint("Inserting missing PathIDToLocalIDMapping");
|
||||
final db = await database;
|
||||
var batch = db.batch();
|
||||
int batchCounter = 0;
|
||||
for (LocalEntityData e in data) {
|
||||
if (batchCounter == 400) {
|
||||
await batch.commit(noResult: true);
|
||||
batch = db.batch();
|
||||
batchCounter = 0;
|
||||
}
|
||||
batch.insert(
|
||||
"entities",
|
||||
e.toJson(),
|
||||
conflictAlgorithm: conflictAlgorithm,
|
||||
);
|
||||
batchCounter++;
|
||||
}
|
||||
await batch.commit(noResult: true);
|
||||
}
|
||||
|
||||
Future<void> deleteEntities(
|
||||
List<String> ids,
|
||||
) async {
|
||||
final db = await database;
|
||||
var batch = db.batch();
|
||||
int batchCounter = 0;
|
||||
for (String id in ids) {
|
||||
if (batchCounter == 400) {
|
||||
await batch.commit(noResult: true);
|
||||
batch = db.batch();
|
||||
batchCounter = 0;
|
||||
}
|
||||
batch.delete(
|
||||
"entities",
|
||||
where: "id = ?",
|
||||
whereArgs: [id],
|
||||
);
|
||||
batchCounter++;
|
||||
}
|
||||
await batch.commit(noResult: true);
|
||||
}
|
||||
|
||||
Future<List<LocalEntityData>> getEntities(EntityType type) async {
|
||||
final db = await database;
|
||||
final List<Map<String, dynamic>> maps = await db.query(
|
||||
"entities",
|
||||
where: "type = ?",
|
||||
whereArgs: [type.typeToString()],
|
||||
);
|
||||
return List.generate(maps.length, (i) {
|
||||
return LocalEntityData.fromJson(maps[i]);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
import 'dart:developer' as dev;
|
||||
import 'dart:io' as io;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
@ -8,7 +9,7 @@ import 'package:photos/models/backup_status.dart';
|
|||
import 'package:photos/models/file.dart';
|
||||
import 'package:photos/models/file_load_result.dart';
|
||||
import 'package:photos/models/file_type.dart';
|
||||
import 'package:photos/models/location.dart';
|
||||
import 'package:photos/models/location/location.dart';
|
||||
import 'package:photos/models/magic_metadata.dart';
|
||||
import 'package:photos/utils/file_uploader_util.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
|
@ -79,6 +80,7 @@ class FilesDB {
|
|||
...createOnDeviceFilesAndPathCollection(),
|
||||
...addFileSizeColumn(),
|
||||
...updateIndexes(),
|
||||
...createEntityDataTable(),
|
||||
];
|
||||
|
||||
final dbConfig = MigrationConfig(
|
||||
|
@ -331,6 +333,20 @@ class FilesDB {
|
|||
];
|
||||
}
|
||||
|
||||
static List<String> createEntityDataTable() {
|
||||
return [
|
||||
'''
|
||||
CREATE TABLE IF NOT EXISTS entities (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
ownerID INTEGER NOT NULL,
|
||||
data TEXT NOT NULL DEFAULT '{}',
|
||||
updatedAt INTEGER NOT NULL
|
||||
);
|
||||
'''
|
||||
];
|
||||
}
|
||||
|
||||
static List<String> addFileSizeColumn() {
|
||||
return [
|
||||
'''
|
||||
|
@ -486,6 +502,8 @@ class FilesDB {
|
|||
int visibility = visibilityVisible,
|
||||
Set<int>? ignoredCollectionIDs,
|
||||
}) async {
|
||||
final stopWatch = Stopwatch()..start();
|
||||
|
||||
final db = await instance.database;
|
||||
final order = (asc ?? false ? 'ASC' : 'DESC');
|
||||
final results = await db.query(
|
||||
|
@ -501,6 +519,9 @@ class FilesDB {
|
|||
final files = convertToFiles(results);
|
||||
final List<File> deduplicatedFiles =
|
||||
_deduplicatedAndFilterIgnoredFiles(files, ignoredCollectionIDs);
|
||||
dev.log(
|
||||
"getAllPendingOrUploadedFiles time taken: ${stopWatch.elapsedMilliseconds} ms");
|
||||
stopWatch.stop();
|
||||
return FileLoadResult(deduplicatedFiles, files.length == limit);
|
||||
}
|
||||
|
||||
|
@ -1376,6 +1397,33 @@ class FilesDB {
|
|||
return filesCount;
|
||||
}
|
||||
|
||||
Future<FileLoadResult> fetchAllUploadedAndSharedFilesWithLocation(
|
||||
int startTime,
|
||||
int endTime, {
|
||||
int? limit,
|
||||
bool? asc,
|
||||
Set<int>? ignoredCollectionIDs,
|
||||
}) async {
|
||||
final db = await instance.database;
|
||||
final order = (asc ?? false ? 'ASC' : 'DESC');
|
||||
final results = await db.query(
|
||||
filesTable,
|
||||
where:
|
||||
'$columnLatitude IS NOT NULL AND $columnLongitude IS NOT NULL AND ($columnLatitude IS NOT 0 OR $columnLongitude IS NOT 0)'
|
||||
' AND $columnCreationTime >= ? AND $columnCreationTime <= ? AND '
|
||||
'($columnMMdVisibility IS NULL OR $columnMMdVisibility = ?)'
|
||||
' AND ($columnLocalID IS NOT NULL OR ($columnCollectionID IS NOT NULL AND $columnCollectionID IS NOT -1))',
|
||||
whereArgs: [startTime, endTime, visibilityVisible],
|
||||
orderBy:
|
||||
'$columnCreationTime ' + order + ', $columnModificationTime ' + order,
|
||||
limit: limit,
|
||||
);
|
||||
final files = convertToFiles(results);
|
||||
final List<File> deduplicatedFiles =
|
||||
_deduplicatedAndFilterIgnoredFiles(files, ignoredCollectionIDs);
|
||||
return FileLoadResult(deduplicatedFiles, files.length == limit);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _getRowForFile(File file) {
|
||||
final row = <String, dynamic>{};
|
||||
if (file.generatedID != null) {
|
||||
|
@ -1387,6 +1435,10 @@ class FilesDB {
|
|||
row[columnCollectionID] = file.collectionID ?? -1;
|
||||
row[columnTitle] = file.title;
|
||||
row[columnDeviceFolder] = file.deviceFolder;
|
||||
// if (file.location == null ||
|
||||
// (file.location!.latitude == null && file.location!.longitude == null)) {
|
||||
// file.location = Location.randomLocation();
|
||||
// }
|
||||
if (file.location != null) {
|
||||
row[columnLatitude] = file.location!.latitude;
|
||||
row[columnLongitude] = file.location!.longitude;
|
||||
|
@ -1471,7 +1523,10 @@ class FilesDB {
|
|||
file.title = row[columnTitle];
|
||||
file.deviceFolder = row[columnDeviceFolder];
|
||||
if (row[columnLatitude] != null && row[columnLongitude] != null) {
|
||||
file.location = Location(row[columnLatitude], row[columnLongitude]);
|
||||
file.location = Location(
|
||||
latitude: row[columnLatitude],
|
||||
longitude: row[columnLongitude],
|
||||
);
|
||||
}
|
||||
file.fileType = getFileType(row[columnFileType]);
|
||||
file.creationTime = row[columnCreationTime];
|
||||
|
|
16
lib/events/location_tag_updated_event.dart
Normal file
16
lib/events/location_tag_updated_event.dart
Normal file
|
@ -0,0 +1,16 @@
|
|||
import 'package:photos/events/event.dart';
|
||||
import "package:photos/models/local_entity_data.dart";
|
||||
import "package:photos/models/location_tag/location_tag.dart";
|
||||
|
||||
class LocationTagUpdatedEvent extends Event {
|
||||
final List<LocalEntity<LocationTag>>? updatedLocTagEntities;
|
||||
final LocTagEventType type;
|
||||
|
||||
LocationTagUpdatedEvent(this.type, {this.updatedLocTagEntities});
|
||||
}
|
||||
|
||||
enum LocTagEventType {
|
||||
add,
|
||||
update,
|
||||
delete,
|
||||
}
|
114
lib/gateways/entity_gw.dart
Normal file
114
lib/gateways/entity_gw.dart
Normal file
|
@ -0,0 +1,114 @@
|
|||
import "package:dio/dio.dart";
|
||||
import "package:photos/models/api/entity/data.dart";
|
||||
import "package:photos/models/api/entity/key.dart";
|
||||
import "package:photos/models/api/entity/type.dart";
|
||||
|
||||
class EntityGateway {
|
||||
final Dio _enteDio;
|
||||
|
||||
EntityGateway(this._enteDio);
|
||||
|
||||
Future<void> createKey(
|
||||
EntityType entityType,
|
||||
String encKey,
|
||||
String header,
|
||||
) async {
|
||||
await _enteDio.post(
|
||||
"/user-entity/key",
|
||||
data: {
|
||||
"type": entityType.typeToString(),
|
||||
"encryptedKey": encKey,
|
||||
"header": header,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<EntityKey> getKey(EntityType type) async {
|
||||
try {
|
||||
final response = await _enteDio.get(
|
||||
"/user-entity/key",
|
||||
queryParameters: {
|
||||
"type": type.typeToString(),
|
||||
},
|
||||
);
|
||||
return EntityKey.fromMap(response.data);
|
||||
} on DioError catch (e) {
|
||||
if (e.response != null && (e.response!.statusCode ?? 0) == 404) {
|
||||
throw EntityKeyNotFound();
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<EntityData> createEntity(
|
||||
EntityType type,
|
||||
String encryptedData,
|
||||
String header,
|
||||
) async {
|
||||
final response = await _enteDio.post(
|
||||
"/user-entity/entity",
|
||||
data: {
|
||||
"encryptedData": encryptedData,
|
||||
"header": header,
|
||||
"type": type.typeToString(),
|
||||
},
|
||||
);
|
||||
return EntityData.fromMap(response.data);
|
||||
}
|
||||
|
||||
Future<EntityData> updateEntity(
|
||||
EntityType type,
|
||||
String id,
|
||||
String encryptedData,
|
||||
String header,
|
||||
) async {
|
||||
final response = await _enteDio.put(
|
||||
"/user-entity/entity",
|
||||
data: {
|
||||
"id": id,
|
||||
"encryptedData": encryptedData,
|
||||
"header": header,
|
||||
"type": type.typeToString(),
|
||||
},
|
||||
);
|
||||
return EntityData.fromMap(response.data);
|
||||
}
|
||||
|
||||
Future<void> deleteEntity(
|
||||
String id,
|
||||
) async {
|
||||
await _enteDio.delete(
|
||||
"/user-entity/entity",
|
||||
queryParameters: {
|
||||
"id": id,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<EntityData>> getDiff(
|
||||
EntityType type,
|
||||
int sinceTime, {
|
||||
int limit = 500,
|
||||
}) async {
|
||||
final response = await _enteDio.get(
|
||||
"/user-entity/entity/diff",
|
||||
queryParameters: {
|
||||
"sinceTime": sinceTime,
|
||||
"limit": limit,
|
||||
"type": type.typeToString(),
|
||||
},
|
||||
);
|
||||
final List<EntityData> authEntities = <EntityData>[];
|
||||
final diff = response.data["diff"] as List;
|
||||
for (var entry in diff) {
|
||||
final EntityData entity = EntityData.fromMap(entry);
|
||||
authEntities.add(entity);
|
||||
}
|
||||
return authEntities;
|
||||
}
|
||||
}
|
||||
|
||||
class EntityKeyNotFound extends Error {}
|
99
lib/generated/intl/messages_all.dart
Normal file
99
lib/generated/intl/messages_all.dart
Normal file
|
@ -0,0 +1,99 @@
|
|||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that looks up messages for specific locales by
|
||||
// delegating to the appropriate library.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:implementation_imports, file_names, unnecessary_new
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering
|
||||
// ignore_for_file:argument_type_not_assignable, invalid_assignment
|
||||
// ignore_for_file:prefer_single_quotes, prefer_generic_function_type_aliases
|
||||
// ignore_for_file:comment_references
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
import 'package:intl/src/intl_helpers.dart';
|
||||
|
||||
import 'messages_cs.dart' as messages_cs;
|
||||
import 'messages_de.dart' as messages_de;
|
||||
import 'messages_en.dart' as messages_en;
|
||||
import 'messages_es.dart' as messages_es;
|
||||
import 'messages_fr.dart' as messages_fr;
|
||||
import 'messages_it.dart' as messages_it;
|
||||
import 'messages_ko.dart' as messages_ko;
|
||||
import 'messages_nl.dart' as messages_nl;
|
||||
import 'messages_pl.dart' as messages_pl;
|
||||
import 'messages_pt.dart' as messages_pt;
|
||||
|
||||
typedef Future<dynamic> LibraryLoader();
|
||||
Map<String, LibraryLoader> _deferredLibraries = {
|
||||
'cs': () => new SynchronousFuture(null),
|
||||
'de': () => new SynchronousFuture(null),
|
||||
'en': () => new SynchronousFuture(null),
|
||||
'es': () => new SynchronousFuture(null),
|
||||
'fr': () => new SynchronousFuture(null),
|
||||
'it': () => new SynchronousFuture(null),
|
||||
'ko': () => new SynchronousFuture(null),
|
||||
'nl': () => new SynchronousFuture(null),
|
||||
'pl': () => new SynchronousFuture(null),
|
||||
'pt': () => new SynchronousFuture(null),
|
||||
};
|
||||
|
||||
MessageLookupByLibrary? _findExact(String localeName) {
|
||||
switch (localeName) {
|
||||
case 'cs':
|
||||
return messages_cs.messages;
|
||||
case 'de':
|
||||
return messages_de.messages;
|
||||
case 'en':
|
||||
return messages_en.messages;
|
||||
case 'es':
|
||||
return messages_es.messages;
|
||||
case 'fr':
|
||||
return messages_fr.messages;
|
||||
case 'it':
|
||||
return messages_it.messages;
|
||||
case 'ko':
|
||||
return messages_ko.messages;
|
||||
case 'nl':
|
||||
return messages_nl.messages;
|
||||
case 'pl':
|
||||
return messages_pl.messages;
|
||||
case 'pt':
|
||||
return messages_pt.messages;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// User programs should call this before using [localeName] for messages.
|
||||
Future<bool> initializeMessages(String localeName) {
|
||||
var availableLocale = Intl.verifiedLocale(
|
||||
localeName, (locale) => _deferredLibraries[locale] != null,
|
||||
onFailure: (_) => null);
|
||||
if (availableLocale == null) {
|
||||
return new SynchronousFuture(false);
|
||||
}
|
||||
var lib = _deferredLibraries[availableLocale];
|
||||
lib == null ? new SynchronousFuture(false) : lib();
|
||||
initializeInternalMessageLookup(() => new CompositeMessageLookup());
|
||||
messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor);
|
||||
return new SynchronousFuture(true);
|
||||
}
|
||||
|
||||
bool _messagesExistFor(String locale) {
|
||||
try {
|
||||
return _findExact(locale) != null;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
MessageLookupByLibrary? _findGeneratedMessagesFor(String locale) {
|
||||
var actualLocale =
|
||||
Intl.verifiedLocale(locale, _messagesExistFor, onFailure: (_) => null);
|
||||
if (actualLocale == null) return null;
|
||||
return _findExact(actualLocale);
|
||||
}
|
25
lib/generated/intl/messages_cs.dart
Normal file
25
lib/generated/intl/messages_cs.dart
Normal file
|
@ -0,0 +1,25 @@
|
|||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a cs locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
|
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'cs';
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{};
|
||||
}
|
41
lib/generated/intl/messages_de.dart
Normal file
41
lib/generated/intl/messages_de.dart
Normal file
|
@ -0,0 +1,41 @@
|
|||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a de locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
|
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'de';
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"accountWelcomeBack":
|
||||
MessageLookupByLibrary.simpleMessage("Willkommen zurück!"),
|
||||
"activeSessions":
|
||||
MessageLookupByLibrary.simpleMessage("Aktive Sitzungen"),
|
||||
"cancel": MessageLookupByLibrary.simpleMessage("Abbrechen"),
|
||||
"createNewAccount":
|
||||
MessageLookupByLibrary.simpleMessage("Neues Konto erstellen"),
|
||||
"deleteAccount": MessageLookupByLibrary.simpleMessage("Konto löschen"),
|
||||
"email": MessageLookupByLibrary.simpleMessage("E-Mail"),
|
||||
"enterYourEmailAddress": MessageLookupByLibrary.simpleMessage(
|
||||
"Geben Sie Ihre E-Mail-Adresse ein"),
|
||||
"feedback": MessageLookupByLibrary.simpleMessage("Rückmeldung"),
|
||||
"insecureDevice":
|
||||
MessageLookupByLibrary.simpleMessage("Unsicheres Gerät"),
|
||||
"password": MessageLookupByLibrary.simpleMessage("Passwort")
|
||||
};
|
||||
}
|
1240
lib/generated/intl/messages_en.dart
Normal file
1240
lib/generated/intl/messages_en.dart
Normal file
File diff suppressed because it is too large
Load diff
25
lib/generated/intl/messages_es.dart
Normal file
25
lib/generated/intl/messages_es.dart
Normal file
|
@ -0,0 +1,25 @@
|
|||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a es locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
|
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'es';
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{};
|
||||
}
|
1056
lib/generated/intl/messages_fr.dart
Normal file
1056
lib/generated/intl/messages_fr.dart
Normal file
File diff suppressed because it is too large
Load diff
25
lib/generated/intl/messages_it.dart
Normal file
25
lib/generated/intl/messages_it.dart
Normal file
|
@ -0,0 +1,25 @@
|
|||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a it locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
|
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'it';
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{};
|
||||
}
|
25
lib/generated/intl/messages_ko.dart
Normal file
25
lib/generated/intl/messages_ko.dart
Normal file
|
@ -0,0 +1,25 @@
|
|||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a ko locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
|
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'ko';
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{};
|
||||
}
|
548
lib/generated/intl/messages_nl.dart
Normal file
548
lib/generated/intl/messages_nl.dart
Normal file
|
@ -0,0 +1,548 @@
|
|||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a nl locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
|
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'nl';
|
||||
|
||||
static String m4(user) =>
|
||||
"${user} zal geen foto\'s meer kunnen toevoegen aan dit album\n\nDe gebruiker zal nog steeds bestaande foto\'s kunnen verwijderen die door hen zijn toegevoegd";
|
||||
|
||||
static String m10(albumName) =>
|
||||
"Dit verwijdert de openbare link voor toegang tot \"${albumName}\".";
|
||||
|
||||
static String m11(supportEmail) =>
|
||||
"Stuur een e-mail naar ${supportEmail} vanaf het door jou geregistreerde e-mailadres";
|
||||
|
||||
static String m14(email) =>
|
||||
"${email} heeft geen ente account.\n\nStuur ze een uitnodiging om foto\'s te delen.";
|
||||
|
||||
static String m22(count) =>
|
||||
"${Intl.plural(count, one: '${count} item', other: '${count} items')}";
|
||||
|
||||
static String m24(expiryTime) => "Link vervalt op ${expiryTime}";
|
||||
|
||||
static String m25(maxValue) =>
|
||||
"Wanneer ingesteld op het maximum (${maxValue}), wordt het apparaatlimiet versoepeld om tijdelijke pieken van grote aantallen kijkers mogelijk te maken.";
|
||||
|
||||
static String m26(count) =>
|
||||
"${Intl.plural(count, zero: 'geen herinneringen', one: '${count} herinnering', other: '${count} herinneringen')}";
|
||||
|
||||
static String m29(passwordStrengthValue) =>
|
||||
"Wachtwoord sterkte: ${passwordStrengthValue}";
|
||||
|
||||
static String m36(userEmail) =>
|
||||
"${userEmail} zal worden verwijderd uit dit gedeelde album\n\nAlle door hen toegevoegde foto\'s worden ook uit het album verwijderd";
|
||||
|
||||
static String m38(count) => "${count} geselecteerd";
|
||||
|
||||
static String m39(count, yourCount) =>
|
||||
"${count} geselecteerd (${yourCount} van jou)";
|
||||
|
||||
static String m40(verificationID) =>
|
||||
"Hier is mijn verificatie-ID: ${verificationID} voor ente.io.";
|
||||
|
||||
static String m41(verificationID) =>
|
||||
"Hey, kunt u bevestigen dat dit uw ente.io verificatie-ID is: ${verificationID}";
|
||||
|
||||
static String m43(numberOfPeople) =>
|
||||
"${Intl.plural(numberOfPeople, zero: 'Deel met specifieke mensen', one: 'Gedeeld met 1 persoon', other: 'Gedeeld met ${numberOfPeople} mensen')}";
|
||||
|
||||
static String m45(fileType) =>
|
||||
"Dit ${fileType} zal worden verwijderd van jouw apparaat.";
|
||||
|
||||
static String m46(fileType) =>
|
||||
"Dit ${fileType} staat zowel in ente als in jouw apparaat.";
|
||||
|
||||
static String m47(fileType) =>
|
||||
"Dit ${fileType} zal worden verwijderd uit ente.";
|
||||
|
||||
static String m53(email) => "Dit is de verificatie-ID van ${email}";
|
||||
|
||||
static String m54(email) => "Verifieer ${email}";
|
||||
|
||||
static String m55(count) =>
|
||||
"${Intl.plural(count, one: '${count} jaar geleden', other: '${count} jaren geleden')}";
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"aNewVersionOfEnteIsAvailable": MessageLookupByLibrary.simpleMessage(
|
||||
"Er is een nieuwe versie van ente beschikbaar."),
|
||||
"about": MessageLookupByLibrary.simpleMessage("Over"),
|
||||
"account": MessageLookupByLibrary.simpleMessage("Account"),
|
||||
"accountWelcomeBack":
|
||||
MessageLookupByLibrary.simpleMessage("Welkom terug!"),
|
||||
"ackPasswordLostWarning": MessageLookupByLibrary.simpleMessage(
|
||||
"Ik begrijp dat als ik mijn wachtwoord verlies, ik mijn gegevens kan verliezen omdat mijn gegevens <underline>end-to-end versleuteld</underline> zijn."),
|
||||
"activeSessions":
|
||||
MessageLookupByLibrary.simpleMessage("Actieve sessies"),
|
||||
"addANewEmail":
|
||||
MessageLookupByLibrary.simpleMessage("Nieuw e-mailadres toevoegen"),
|
||||
"addCollaborator":
|
||||
MessageLookupByLibrary.simpleMessage("Samenwerker toevoegen"),
|
||||
"addMore": MessageLookupByLibrary.simpleMessage("Meer toevoegen"),
|
||||
"addViewer": MessageLookupByLibrary.simpleMessage("Voeg kijker toe"),
|
||||
"addedAs": MessageLookupByLibrary.simpleMessage("Toegevoegd als"),
|
||||
"addingToFavorites":
|
||||
MessageLookupByLibrary.simpleMessage("Toevoegen aan favorieten..."),
|
||||
"advancedSettings": MessageLookupByLibrary.simpleMessage("Geavanceerd"),
|
||||
"after1Day": MessageLookupByLibrary.simpleMessage("Na 1 dag"),
|
||||
"after1Hour": MessageLookupByLibrary.simpleMessage("Na 1 uur"),
|
||||
"after1Month": MessageLookupByLibrary.simpleMessage("Na 1 maand"),
|
||||
"after1Week": MessageLookupByLibrary.simpleMessage("Na 1 week"),
|
||||
"after1Year": MessageLookupByLibrary.simpleMessage("Na 1 jaar"),
|
||||
"albumOwner": MessageLookupByLibrary.simpleMessage("Eigenaar"),
|
||||
"albumUpdated":
|
||||
MessageLookupByLibrary.simpleMessage("Album bijgewerkt"),
|
||||
"albums": MessageLookupByLibrary.simpleMessage("Albums"),
|
||||
"allowAddPhotosDescription": MessageLookupByLibrary.simpleMessage(
|
||||
"Sta toe dat mensen met de link ook foto\'s kunnen toevoegen aan het gedeelde album."),
|
||||
"allowAddingPhotos":
|
||||
MessageLookupByLibrary.simpleMessage("Foto\'s toevoegen toestaan"),
|
||||
"allowDownloads":
|
||||
MessageLookupByLibrary.simpleMessage("Downloads toestaan"),
|
||||
"applyCodeTitle":
|
||||
MessageLookupByLibrary.simpleMessage("Code toepassen"),
|
||||
"archive": MessageLookupByLibrary.simpleMessage("Archiveer"),
|
||||
"areYouSureYouWantToLogout": MessageLookupByLibrary.simpleMessage(
|
||||
"Weet je zeker dat je wilt uitloggen?"),
|
||||
"askDeleteReason": MessageLookupByLibrary.simpleMessage(
|
||||
"Wat is de voornaamste reden dat je jouw account verwijdert?"),
|
||||
"authToChangeYourEmail": MessageLookupByLibrary.simpleMessage(
|
||||
"Gelieve te verifiëren om je e-mailadres te wijzigen"),
|
||||
"authToChangeYourPassword": MessageLookupByLibrary.simpleMessage(
|
||||
"Gelieve te verifiëren om je wachtwoord te wijzigen"),
|
||||
"authToInitiateAccountDeletion": MessageLookupByLibrary.simpleMessage(
|
||||
"Gelieve te verifiëren om het verwijderen van je account te starten"),
|
||||
"authToViewYourHiddenFiles": MessageLookupByLibrary.simpleMessage(
|
||||
"Gelieve te verifiëren om je verborgen bestanden te bekijken"),
|
||||
"backupOverMobileData": MessageLookupByLibrary.simpleMessage(
|
||||
"Back-up maken via mobiele data"),
|
||||
"backupSettings":
|
||||
MessageLookupByLibrary.simpleMessage("Back-up instellingen"),
|
||||
"backupVideos":
|
||||
MessageLookupByLibrary.simpleMessage("Back-up video\'s"),
|
||||
"canOnlyRemoveFilesOwnedByYou": MessageLookupByLibrary.simpleMessage(
|
||||
"Kan alleen bestanden verwijderen die jouw eigendom zijn"),
|
||||
"cancel": MessageLookupByLibrary.simpleMessage("Annuleer"),
|
||||
"cannotAddMorePhotosAfterBecomingViewer": m4,
|
||||
"changeEmail": MessageLookupByLibrary.simpleMessage("E-mail wijzigen"),
|
||||
"changePassword":
|
||||
MessageLookupByLibrary.simpleMessage("Wachtwoord wijzigen"),
|
||||
"changePasswordTitle":
|
||||
MessageLookupByLibrary.simpleMessage("Wachtwoord wijzigen"),
|
||||
"changePermissions":
|
||||
MessageLookupByLibrary.simpleMessage("Rechten aanpassen?"),
|
||||
"checkForUpdates":
|
||||
MessageLookupByLibrary.simpleMessage("Controleer op updates"),
|
||||
"checkInboxAndSpamFolder": MessageLookupByLibrary.simpleMessage(
|
||||
"Controleer je inbox (en spam) om verificatie te voltooien"),
|
||||
"checking": MessageLookupByLibrary.simpleMessage("Controleren..."),
|
||||
"codeCopiedToClipboard": MessageLookupByLibrary.simpleMessage(
|
||||
"Code gekopieerd naar klembord"),
|
||||
"collabLinkSectionDescription": MessageLookupByLibrary.simpleMessage(
|
||||
"Maak een link waarmee mensen foto\'s in jouw gedeelde album kunnen toevoegen en bekijken zonder dat ze daarvoor een ente app of account nodig hebben. Handig voor het verzamelen van foto\'s van evenementen."),
|
||||
"collaborativeLink":
|
||||
MessageLookupByLibrary.simpleMessage("Gezamenlijke link"),
|
||||
"collaborator": MessageLookupByLibrary.simpleMessage("Samenwerker"),
|
||||
"collaboratorsCanAddPhotosAndVideosToTheSharedAlbum":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Samenwerkers kunnen foto\'s en video\'s toevoegen aan het gedeelde album."),
|
||||
"collectPhotos":
|
||||
MessageLookupByLibrary.simpleMessage("Foto\'s verzamelen"),
|
||||
"confirm": MessageLookupByLibrary.simpleMessage("Bevestig"),
|
||||
"confirmAccountDeletion": MessageLookupByLibrary.simpleMessage(
|
||||
"Account verwijderen bevestigen"),
|
||||
"confirmDeletePrompt": MessageLookupByLibrary.simpleMessage(
|
||||
"Ja, ik wil permanent mijn account inclusief alle gegevens verwijderen."),
|
||||
"confirmPassword":
|
||||
MessageLookupByLibrary.simpleMessage("Wachtwoord bevestigen"),
|
||||
"confirmRecoveryKey":
|
||||
MessageLookupByLibrary.simpleMessage("Bevestig herstelsleutel"),
|
||||
"confirmYourRecoveryKey":
|
||||
MessageLookupByLibrary.simpleMessage("Bevestig herstelsleutel"),
|
||||
"contactSupport":
|
||||
MessageLookupByLibrary.simpleMessage("Contacteer ondersteuning"),
|
||||
"continueLabel": MessageLookupByLibrary.simpleMessage("Doorgaan"),
|
||||
"copyLink": MessageLookupByLibrary.simpleMessage("Kopieer link"),
|
||||
"copypasteThisCodentoYourAuthenticatorApp":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Kopieer en plak deze code\nnaar je authenticator app"),
|
||||
"createAccount":
|
||||
MessageLookupByLibrary.simpleMessage("Account aanmaken"),
|
||||
"createAlbumActionHint": MessageLookupByLibrary.simpleMessage(
|
||||
"Lang indrukken om foto\'s te selecteren en klik + om een album te maken"),
|
||||
"createNewAccount":
|
||||
MessageLookupByLibrary.simpleMessage("Nieuw account aanmaken"),
|
||||
"createPublicLink":
|
||||
MessageLookupByLibrary.simpleMessage("Maak publieke link"),
|
||||
"creatingLink":
|
||||
MessageLookupByLibrary.simpleMessage("Link aanmaken..."),
|
||||
"criticalUpdateAvailable": MessageLookupByLibrary.simpleMessage(
|
||||
"Belangrijke update beschikbaar"),
|
||||
"custom": MessageLookupByLibrary.simpleMessage("Aangepast"),
|
||||
"decrypting": MessageLookupByLibrary.simpleMessage("Ontsleutelen..."),
|
||||
"deleteAccount":
|
||||
MessageLookupByLibrary.simpleMessage("Account verwijderen"),
|
||||
"deleteAccountFeedbackPrompt": MessageLookupByLibrary.simpleMessage(
|
||||
"We vinden het jammer je te zien gaan. Deel je feedback om ons te helpen verbeteren."),
|
||||
"deleteAccountPermanentlyButton": MessageLookupByLibrary.simpleMessage(
|
||||
"Account permanent verwijderen"),
|
||||
"deleteAlbum": MessageLookupByLibrary.simpleMessage("Verwijder album"),
|
||||
"deleteAlbumDialog": MessageLookupByLibrary.simpleMessage(
|
||||
"Verwijder de foto\'s (en video\'s) van dit album ook uit <bold>alle</bold> andere albums waar deze deel van uitmaken?"),
|
||||
"deleteConfirmDialogBody": MessageLookupByLibrary.simpleMessage(
|
||||
"Je staat op het punt je account en alle bijbehorende gegevens permanent te verwijderen.\nDeze actie is onomkeerbaar."),
|
||||
"deleteEmailRequest": MessageLookupByLibrary.simpleMessage(
|
||||
"Stuur een e-mail naar <warning>account-deletion@ente.io</warning> vanaf het door jou geregistreerde e-mailadres."),
|
||||
"deleteFromBoth":
|
||||
MessageLookupByLibrary.simpleMessage("Verwijder van beide"),
|
||||
"deleteFromDevice":
|
||||
MessageLookupByLibrary.simpleMessage("Verwijder van apparaat"),
|
||||
"deleteFromEnte":
|
||||
MessageLookupByLibrary.simpleMessage("Verwijder van ente"),
|
||||
"deletePhotos":
|
||||
MessageLookupByLibrary.simpleMessage("Foto\'s verwijderen"),
|
||||
"deleteReason1": MessageLookupByLibrary.simpleMessage(
|
||||
"Ik mis een belangrijke functie"),
|
||||
"deleteReason2": MessageLookupByLibrary.simpleMessage(
|
||||
"De app of een bepaalde functie functioneert niet \nzoals ik verwacht"),
|
||||
"deleteReason3": MessageLookupByLibrary.simpleMessage(
|
||||
"Ik heb een andere dienst gevonden die me beter bevalt"),
|
||||
"deleteReason4": MessageLookupByLibrary.simpleMessage(
|
||||
"Mijn reden wordt niet vermeld"),
|
||||
"deleteRequestSLAText": MessageLookupByLibrary.simpleMessage(
|
||||
"Je verzoek wordt binnen 72 uur verwerkt."),
|
||||
"deleteSharedAlbum":
|
||||
MessageLookupByLibrary.simpleMessage("Gedeeld album verwijderen?"),
|
||||
"deleteSharedAlbumDialogBody": MessageLookupByLibrary.simpleMessage(
|
||||
"Het album wordt verwijderd voor iedereen\n\nJe verliest de toegang tot gedeelde foto\'s in dit album die eigendom zijn van anderen"),
|
||||
"deviceLockExplanation": MessageLookupByLibrary.simpleMessage(
|
||||
"Schakel de schermvergrendeling van het apparaat uit wanneer ente op de voorgrond is en er een back-up aan de gang is. Dit is normaal gesproken niet nodig, maar kan grote uploads en initiële imports van grote mappen sneller laten verlopen."),
|
||||
"disableAutoLock": MessageLookupByLibrary.simpleMessage(
|
||||
"Automatisch vergrendelen uitschakelen"),
|
||||
"disableDownloadWarningBody": MessageLookupByLibrary.simpleMessage(
|
||||
"Kijkers kunnen nog steeds screenshots maken of een kopie van je foto\'s opslaan met behulp van externe tools"),
|
||||
"disableDownloadWarningTitle":
|
||||
MessageLookupByLibrary.simpleMessage("Let op"),
|
||||
"disableLinkMessage": m10,
|
||||
"doThisLater": MessageLookupByLibrary.simpleMessage("Doe dit later"),
|
||||
"done": MessageLookupByLibrary.simpleMessage("Voltooid"),
|
||||
"downloading": MessageLookupByLibrary.simpleMessage("Downloaden..."),
|
||||
"dropSupportEmail": m11,
|
||||
"email": MessageLookupByLibrary.simpleMessage("E-mail"),
|
||||
"emailNoEnteAccount": m14,
|
||||
"encryption": MessageLookupByLibrary.simpleMessage("Encryptie"),
|
||||
"encryptionKeys":
|
||||
MessageLookupByLibrary.simpleMessage("Encryptiesleutels"),
|
||||
"enterCode": MessageLookupByLibrary.simpleMessage("Voer code in"),
|
||||
"enterEmail":
|
||||
MessageLookupByLibrary.simpleMessage("Voer e-mailadres in"),
|
||||
"enterNewPasswordToEncrypt": MessageLookupByLibrary.simpleMessage(
|
||||
"Voer een nieuw wachtwoord in dat we kunnen gebruiken om je gegevens te versleutelen"),
|
||||
"enterPassword":
|
||||
MessageLookupByLibrary.simpleMessage("Voer wachtwoord in"),
|
||||
"enterPasswordToEncrypt": MessageLookupByLibrary.simpleMessage(
|
||||
"Voer een wachtwoord in dat we kunnen gebruiken om je gegevens te versleutelen"),
|
||||
"enterThe6digitCodeFromnyourAuthenticatorApp":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Voer de 6-cijferige code van je verificatie-app in"),
|
||||
"enterValidEmail": MessageLookupByLibrary.simpleMessage(
|
||||
"Voer een geldig e-mailadres in."),
|
||||
"enterYourEmailAddress":
|
||||
MessageLookupByLibrary.simpleMessage("Voer je e-mailadres in"),
|
||||
"enterYourPassword":
|
||||
MessageLookupByLibrary.simpleMessage("Voer je wachtwoord in"),
|
||||
"enterYourRecoveryKey":
|
||||
MessageLookupByLibrary.simpleMessage("Voer je herstelcode in"),
|
||||
"expiredLinkInfo": MessageLookupByLibrary.simpleMessage(
|
||||
"Deze link is verlopen. Selecteer een nieuwe vervaltijd of schakel de vervaldatum uit."),
|
||||
"exportYourData":
|
||||
MessageLookupByLibrary.simpleMessage("Exporteer je gegevens"),
|
||||
"failedToLoadAlbums":
|
||||
MessageLookupByLibrary.simpleMessage("Laden van albums mislukt"),
|
||||
"feedback": MessageLookupByLibrary.simpleMessage("Feedback"),
|
||||
"forgotPassword":
|
||||
MessageLookupByLibrary.simpleMessage("Wachtwoord vergeten"),
|
||||
"generatingEncryptionKeys": MessageLookupByLibrary.simpleMessage(
|
||||
"Encryptiesleutels genereren..."),
|
||||
"hidden": MessageLookupByLibrary.simpleMessage("Verborgen"),
|
||||
"howItWorks": MessageLookupByLibrary.simpleMessage("Hoe het werkt"),
|
||||
"howToViewShareeVerificationID": MessageLookupByLibrary.simpleMessage(
|
||||
"Vraag hen om hun e-mailadres lang in te drukken op het instellingenscherm en te controleren dat de ID\'s op beide apparaten overeenkomen."),
|
||||
"importing": MessageLookupByLibrary.simpleMessage("Importeren...."),
|
||||
"incorrectPasswordTitle":
|
||||
MessageLookupByLibrary.simpleMessage("Onjuist wachtwoord"),
|
||||
"incorrectRecoveryKeyBody": MessageLookupByLibrary.simpleMessage(
|
||||
"De ingevoerde herstelsleutel is onjuist"),
|
||||
"incorrectRecoveryKeyTitle":
|
||||
MessageLookupByLibrary.simpleMessage("Onjuiste herstelsleutel"),
|
||||
"insecureDevice":
|
||||
MessageLookupByLibrary.simpleMessage("Onveilig apparaat"),
|
||||
"installManually":
|
||||
MessageLookupByLibrary.simpleMessage("Installeer handmatig"),
|
||||
"invalidEmailAddress":
|
||||
MessageLookupByLibrary.simpleMessage("Ongeldig e-mailadres"),
|
||||
"invalidKey": MessageLookupByLibrary.simpleMessage("Ongeldige sleutel"),
|
||||
"invalidRecoveryKey": MessageLookupByLibrary.simpleMessage(
|
||||
"De herstelsleutel die je hebt ingevoerd is niet geldig. Zorg ervoor dat deze 24 woorden bevat en controleer de spelling van elk van deze woorden.\n\nAls je een oudere herstelcode hebt ingevoerd, zorg ervoor dat deze 64 tekens lang is, en controleer ze allemaal."),
|
||||
"inviteToEnte":
|
||||
MessageLookupByLibrary.simpleMessage("Uitnodigen voor ente"),
|
||||
"itemCount": m22,
|
||||
"keepPhotos": MessageLookupByLibrary.simpleMessage("Foto\'s behouden"),
|
||||
"kindlyHelpUsWithThisInformation": MessageLookupByLibrary.simpleMessage(
|
||||
"Help ons alsjeblieft met deze informatie"),
|
||||
"linkDeviceLimit":
|
||||
MessageLookupByLibrary.simpleMessage("Apparaat limiet"),
|
||||
"linkEnabled": MessageLookupByLibrary.simpleMessage("Ingeschakeld"),
|
||||
"linkExpired": MessageLookupByLibrary.simpleMessage("Verlopen"),
|
||||
"linkExpiresOn": m24,
|
||||
"linkExpiry": MessageLookupByLibrary.simpleMessage("Vervaldatum"),
|
||||
"linkHasExpired":
|
||||
MessageLookupByLibrary.simpleMessage("Link is vervallen"),
|
||||
"linkNeverExpires": MessageLookupByLibrary.simpleMessage("Nooit"),
|
||||
"lockButtonLabel": MessageLookupByLibrary.simpleMessage("Vergrendel"),
|
||||
"logInLabel": MessageLookupByLibrary.simpleMessage("Inloggen"),
|
||||
"loginTerms": MessageLookupByLibrary.simpleMessage(
|
||||
"Door op inloggen te klikken, ga ik akkoord met de <u-terms>gebruiksvoorwaarden</u-terms> en <u-policy>privacybeleid</u-policy>"),
|
||||
"logout": MessageLookupByLibrary.simpleMessage("Uitloggen"),
|
||||
"lostDevice":
|
||||
MessageLookupByLibrary.simpleMessage("Apparaat verloren?"),
|
||||
"manage": MessageLookupByLibrary.simpleMessage("Beheren"),
|
||||
"manageDeviceStorage":
|
||||
MessageLookupByLibrary.simpleMessage("Apparaatopslag beheren"),
|
||||
"manageLink": MessageLookupByLibrary.simpleMessage("Beheer link"),
|
||||
"manageParticipants": MessageLookupByLibrary.simpleMessage("Beheren"),
|
||||
"manageSubscription":
|
||||
MessageLookupByLibrary.simpleMessage("Abonnement beheren"),
|
||||
"maxDeviceLimitSpikeHandling": m25,
|
||||
"memoryCount": m26,
|
||||
"moderateStrength": MessageLookupByLibrary.simpleMessage("Matig"),
|
||||
"movedToTrash":
|
||||
MessageLookupByLibrary.simpleMessage("Naar prullenbak verplaatst"),
|
||||
"never": MessageLookupByLibrary.simpleMessage("Nooit"),
|
||||
"newAlbum": MessageLookupByLibrary.simpleMessage("Nieuw album"),
|
||||
"noDuplicates":
|
||||
MessageLookupByLibrary.simpleMessage("✨ Geen duplicaten"),
|
||||
"noRecoveryKey":
|
||||
MessageLookupByLibrary.simpleMessage("Geen herstelcode?"),
|
||||
"noRecoveryKeyNoDecryption": MessageLookupByLibrary.simpleMessage(
|
||||
"Door de aard van ons end-to-end encryptieprotocol kunnen je gegevens niet worden ontsleuteld zonder je wachtwoord of herstelsleutel"),
|
||||
"ok": MessageLookupByLibrary.simpleMessage("Oké"),
|
||||
"oops": MessageLookupByLibrary.simpleMessage("Oeps"),
|
||||
"orPickAnExistingOne":
|
||||
MessageLookupByLibrary.simpleMessage("Of kies een bestaande"),
|
||||
"password": MessageLookupByLibrary.simpleMessage("Wachtwoord"),
|
||||
"passwordChangedSuccessfully": MessageLookupByLibrary.simpleMessage(
|
||||
"Wachtwoord succesvol aangepast"),
|
||||
"passwordLock": MessageLookupByLibrary.simpleMessage("Wachtwoord slot"),
|
||||
"passwordStrength": m29,
|
||||
"passwordWarning": MessageLookupByLibrary.simpleMessage(
|
||||
"Wij slaan dit wachtwoord niet op, dus als je het vergeet, kunnen <underline>we je gegevens niet ontsleutelen</underline>"),
|
||||
"photoGridSize":
|
||||
MessageLookupByLibrary.simpleMessage("Foto raster grootte"),
|
||||
"photoSmallCase": MessageLookupByLibrary.simpleMessage("foto"),
|
||||
"pleaseTryAgain":
|
||||
MessageLookupByLibrary.simpleMessage("Probeer het nog eens"),
|
||||
"pleaseWait":
|
||||
MessageLookupByLibrary.simpleMessage("Een ogenblik geduld..."),
|
||||
"privacy": MessageLookupByLibrary.simpleMessage("Privacy"),
|
||||
"privacyPolicyTitle":
|
||||
MessageLookupByLibrary.simpleMessage("Privacybeleid"),
|
||||
"publicLinkEnabled":
|
||||
MessageLookupByLibrary.simpleMessage("Publieke link ingeschakeld"),
|
||||
"recover": MessageLookupByLibrary.simpleMessage("Herstellen"),
|
||||
"recoverAccount":
|
||||
MessageLookupByLibrary.simpleMessage("Account herstellen"),
|
||||
"recoverButton": MessageLookupByLibrary.simpleMessage("Herstellen"),
|
||||
"recoveryKey": MessageLookupByLibrary.simpleMessage("Herstelsleutel"),
|
||||
"recoveryKeyCopiedToClipboard": MessageLookupByLibrary.simpleMessage(
|
||||
"Herstelsleutel gekopieerd naar klembord"),
|
||||
"recoveryKeyOnForgotPassword": MessageLookupByLibrary.simpleMessage(
|
||||
"Als je je wachtwoord vergeet, kun je alleen met deze sleutel je gegevens herstellen."),
|
||||
"recoveryKeySaveDescription": MessageLookupByLibrary.simpleMessage(
|
||||
"We slaan deze sleutel niet op, bewaar deze 24 woorden sleutel op een veilige plaats."),
|
||||
"recoveryKeySuccessBody": MessageLookupByLibrary.simpleMessage(
|
||||
"Super! Je herstelsleutel is geldig. Bedankt voor het verifiëren.\n\nVergeet niet om je herstelsleutel veilig te bewaren."),
|
||||
"recoveryKeyVerified": MessageLookupByLibrary.simpleMessage(
|
||||
"Herstel sleutel geverifieerd"),
|
||||
"recoveryKeyVerifyReason": MessageLookupByLibrary.simpleMessage(
|
||||
"Je herstelsleutel is de enige manier om je foto\'s te herstellen als je je wachtwoord bent vergeten. Je vindt je herstelsleutel in Instellingen > Account.\n\nVoer hier je herstelsleutel in om te controleren of je hem correct hebt opgeslagen."),
|
||||
"recoverySuccessful":
|
||||
MessageLookupByLibrary.simpleMessage("Herstel succesvol!"),
|
||||
"recreatePasswordBody": MessageLookupByLibrary.simpleMessage(
|
||||
"Het huidige apparaat is niet krachtig genoeg om je wachtwoord te verifiëren, dus moeten we de code een keer opnieuw genereren op een manier die met alle apparaten werkt.\n\nLog in met behulp van uw herstelcode en genereer opnieuw uw wachtwoord (je kunt dezelfde indien gewenst opnieuw gebruiken)."),
|
||||
"recreatePasswordTitle": MessageLookupByLibrary.simpleMessage(
|
||||
"Wachtwoord opnieuw instellen"),
|
||||
"remove": MessageLookupByLibrary.simpleMessage("Verwijder"),
|
||||
"removeLink": MessageLookupByLibrary.simpleMessage("Verwijder link"),
|
||||
"removeParticipant":
|
||||
MessageLookupByLibrary.simpleMessage("Deelnemer verwijderen"),
|
||||
"removeParticipantBody": m36,
|
||||
"removePublicLink":
|
||||
MessageLookupByLibrary.simpleMessage("Verwijder publieke link"),
|
||||
"removeShareItemsWarning": MessageLookupByLibrary.simpleMessage(
|
||||
"Sommige van de items die je verwijdert zijn door andere mensen toegevoegd, en je verliest de toegang daartoe"),
|
||||
"removeWithQuestionMark":
|
||||
MessageLookupByLibrary.simpleMessage("Verwijder?"),
|
||||
"removingFromFavorites": MessageLookupByLibrary.simpleMessage(
|
||||
"Verwijderen uit favorieten..."),
|
||||
"resendEmail":
|
||||
MessageLookupByLibrary.simpleMessage("E-mail opnieuw versturen"),
|
||||
"resetPasswordTitle":
|
||||
MessageLookupByLibrary.simpleMessage("Wachtwoord resetten"),
|
||||
"retry": MessageLookupByLibrary.simpleMessage("Opnieuw"),
|
||||
"saveKey": MessageLookupByLibrary.simpleMessage("Bewaar sleutel"),
|
||||
"saveYourRecoveryKeyIfYouHaventAlready":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Sla je herstelsleutel op als je dat nog niet gedaan hebt"),
|
||||
"scanCode": MessageLookupByLibrary.simpleMessage("Scan code"),
|
||||
"scanThisBarcodeWithnyourAuthenticatorApp":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Scan deze barcode met\nje authenticator app"),
|
||||
"selectAll": MessageLookupByLibrary.simpleMessage("Selecteer alles"),
|
||||
"selectFoldersForBackup": MessageLookupByLibrary.simpleMessage(
|
||||
"Selecteer mappen voor back-up"),
|
||||
"selectReason": MessageLookupByLibrary.simpleMessage("Selecteer reden"),
|
||||
"selectedFoldersWillBeEncryptedAndBackedUp":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Geselecteerde mappen worden versleuteld en geback-upt"),
|
||||
"selectedPhotos": m38,
|
||||
"selectedPhotosWithYours": m39,
|
||||
"sendEmail": MessageLookupByLibrary.simpleMessage("E-mail versturen"),
|
||||
"sendInvite":
|
||||
MessageLookupByLibrary.simpleMessage("Stuur een uitnodiging"),
|
||||
"sendLink": MessageLookupByLibrary.simpleMessage("Stuur link"),
|
||||
"setAPassword":
|
||||
MessageLookupByLibrary.simpleMessage("Stel een wachtwoord in"),
|
||||
"setPasswordTitle":
|
||||
MessageLookupByLibrary.simpleMessage("Wachtwoord instellen"),
|
||||
"setupComplete": MessageLookupByLibrary.simpleMessage("Setup voltooid"),
|
||||
"shareALink": MessageLookupByLibrary.simpleMessage("Deel een link"),
|
||||
"shareMyVerificationID": m40,
|
||||
"shareTextConfirmOthersVerificationID": m41,
|
||||
"shareWithNonenteUsers": MessageLookupByLibrary.simpleMessage(
|
||||
"Delen met niet-ente gebruikers"),
|
||||
"shareWithPeopleSectionTitle": m43,
|
||||
"sharedAlbumSectionDescription": MessageLookupByLibrary.simpleMessage(
|
||||
"Maak gedeelde en collaboratieve albums met andere ente gebruikers, inclusief gebruikers met gratis abonnementen."),
|
||||
"sharing": MessageLookupByLibrary.simpleMessage("Delen..."),
|
||||
"signUpTerms": MessageLookupByLibrary.simpleMessage(
|
||||
"Ik ga akkoord met de <u-terms>gebruiksvoorwaarden</u-terms> en <u-policy>privacybeleid</u-policy>"),
|
||||
"singleFileDeleteFromDevice": m45,
|
||||
"singleFileDeleteHighlight": MessageLookupByLibrary.simpleMessage(
|
||||
"Het wordt uit alle albums verwijderd."),
|
||||
"singleFileInBothLocalAndRemote": m46,
|
||||
"singleFileInRemoteOnly": m47,
|
||||
"skip": MessageLookupByLibrary.simpleMessage("Overslaan"),
|
||||
"someoneSharingAlbumsWithYouShouldSeeTheSameId":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Iemand die albums met je deelt zou hetzelfde ID op hun apparaat moeten zien."),
|
||||
"somethingWentWrong":
|
||||
MessageLookupByLibrary.simpleMessage("Er ging iets mis"),
|
||||
"somethingWentWrongPleaseTryAgain":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Er is iets fout gegaan, probeer het opnieuw"),
|
||||
"sorry": MessageLookupByLibrary.simpleMessage("Sorry"),
|
||||
"sorryCouldNotAddToFavorites": MessageLookupByLibrary.simpleMessage(
|
||||
"Sorry, kon niet aan favorieten worden toegevoegd!"),
|
||||
"sorryCouldNotRemoveFromFavorites":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Sorry, kon niet uit favorieten worden verwijderd!"),
|
||||
"sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Sorry, we konden geen beveiligde sleutels genereren op dit apparaat.\n\nGelieve je aan te melden vanaf een ander apparaat."),
|
||||
"sparkleSuccess": MessageLookupByLibrary.simpleMessage("✨ Succes"),
|
||||
"strongStrength": MessageLookupByLibrary.simpleMessage("Sterk"),
|
||||
"subscribe": MessageLookupByLibrary.simpleMessage("Abonneer"),
|
||||
"subscribeToEnableSharing": MessageLookupByLibrary.simpleMessage(
|
||||
"Het lijkt erop dat je abonnement is verlopen. Abonneer om delen mogelijk te maken."),
|
||||
"tapToCopy": MessageLookupByLibrary.simpleMessage("tik om te kopiëren"),
|
||||
"tapToEnterCode":
|
||||
MessageLookupByLibrary.simpleMessage("Tik om code in te voeren"),
|
||||
"terminate": MessageLookupByLibrary.simpleMessage("Beëindigen"),
|
||||
"terminateSession":
|
||||
MessageLookupByLibrary.simpleMessage("Sessie beëindigen?"),
|
||||
"terms": MessageLookupByLibrary.simpleMessage("Voorwaarden"),
|
||||
"termsOfServicesTitle":
|
||||
MessageLookupByLibrary.simpleMessage("Voorwaarden"),
|
||||
"theDownloadCouldNotBeCompleted": MessageLookupByLibrary.simpleMessage(
|
||||
"De download kon niet worden voltooid"),
|
||||
"thisCanBeUsedToRecoverYourAccountIfYou":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Dit kan worden gebruikt om je account te herstellen als je je tweede factor verliest"),
|
||||
"thisDevice": MessageLookupByLibrary.simpleMessage("Dit apparaat"),
|
||||
"thisIsPersonVerificationId": m53,
|
||||
"thisIsYourVerificationId":
|
||||
MessageLookupByLibrary.simpleMessage("Dit is uw verificatie-ID"),
|
||||
"thisWillLogYouOutOfTheFollowingDevice":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Dit zal je uitloggen van het volgende apparaat:"),
|
||||
"thisWillLogYouOutOfThisDevice": MessageLookupByLibrary.simpleMessage(
|
||||
"Dit zal je uitloggen van dit apparaat!"),
|
||||
"trash": MessageLookupByLibrary.simpleMessage("Prullenbak"),
|
||||
"tryAgain": MessageLookupByLibrary.simpleMessage("Probeer opnieuw"),
|
||||
"twofactorAuthenticationPageTitle":
|
||||
MessageLookupByLibrary.simpleMessage("Tweestapsverificatie"),
|
||||
"twofactorSetup":
|
||||
MessageLookupByLibrary.simpleMessage("Tweestapsverificatie"),
|
||||
"uncategorized":
|
||||
MessageLookupByLibrary.simpleMessage("Ongecategoriseerd"),
|
||||
"unselectAll":
|
||||
MessageLookupByLibrary.simpleMessage("Deselecteer alles"),
|
||||
"update": MessageLookupByLibrary.simpleMessage("Update"),
|
||||
"updateAvailable":
|
||||
MessageLookupByLibrary.simpleMessage("Update beschikbaar"),
|
||||
"updatingFolderSelection":
|
||||
MessageLookupByLibrary.simpleMessage("Map selectie bijwerken..."),
|
||||
"useRecoveryKey":
|
||||
MessageLookupByLibrary.simpleMessage("Herstelcode gebruiken"),
|
||||
"verificationId":
|
||||
MessageLookupByLibrary.simpleMessage("Verificatie ID"),
|
||||
"verify": MessageLookupByLibrary.simpleMessage("Verifiëren"),
|
||||
"verifyEmail": MessageLookupByLibrary.simpleMessage("Bevestig e-mail"),
|
||||
"verifyEmailID": m54,
|
||||
"verifyPassword":
|
||||
MessageLookupByLibrary.simpleMessage("Bevestig wachtwoord"),
|
||||
"verifyingRecoveryKey": MessageLookupByLibrary.simpleMessage(
|
||||
"Herstelsleutel verifiëren..."),
|
||||
"videoSmallCase": MessageLookupByLibrary.simpleMessage("video"),
|
||||
"viewRecoveryKey":
|
||||
MessageLookupByLibrary.simpleMessage("Toon herstelsleutel"),
|
||||
"viewer": MessageLookupByLibrary.simpleMessage("Kijker"),
|
||||
"weAreOpenSource":
|
||||
MessageLookupByLibrary.simpleMessage("We zijn open source!"),
|
||||
"weakStrength": MessageLookupByLibrary.simpleMessage("Zwak"),
|
||||
"welcomeBack": MessageLookupByLibrary.simpleMessage("Welkom terug!"),
|
||||
"weveSentAMailTo": MessageLookupByLibrary.simpleMessage(
|
||||
"We hebben een e-mail gestuurd naar"),
|
||||
"yearsAgo": m55,
|
||||
"yesConvertToViewer":
|
||||
MessageLookupByLibrary.simpleMessage("Ja, converteren naar viewer"),
|
||||
"yesDelete": MessageLookupByLibrary.simpleMessage("Ja, verwijderen"),
|
||||
"yesLogout": MessageLookupByLibrary.simpleMessage("Ja, log uit"),
|
||||
"yesRemove": MessageLookupByLibrary.simpleMessage("Ja, verwijderen"),
|
||||
"you": MessageLookupByLibrary.simpleMessage("Jij"),
|
||||
"youAreOnTheLatestVersion":
|
||||
MessageLookupByLibrary.simpleMessage("Je hebt de laatste versie"),
|
||||
"youCannotShareWithYourself": MessageLookupByLibrary.simpleMessage(
|
||||
"Je kunt niet met jezelf delen"),
|
||||
"yourAccountHasBeenDeleted":
|
||||
MessageLookupByLibrary.simpleMessage("Je account is verwijderd")
|
||||
};
|
||||
}
|
128
lib/generated/intl/messages_pl.dart
Normal file
128
lib/generated/intl/messages_pl.dart
Normal file
|
@ -0,0 +1,128 @@
|
|||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a pl locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
|
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'pl';
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"accountWelcomeBack":
|
||||
MessageLookupByLibrary.simpleMessage("Witaj ponownie!"),
|
||||
"activeSessions": MessageLookupByLibrary.simpleMessage("Aktywne sesje"),
|
||||
"askDeleteReason": MessageLookupByLibrary.simpleMessage(
|
||||
"Jaka jest przyczyna usunięcia konta?"),
|
||||
"cancel": MessageLookupByLibrary.simpleMessage("Anuluj"),
|
||||
"checkInboxAndSpamFolder": MessageLookupByLibrary.simpleMessage(
|
||||
"Sprawdź swoją skrzynkę odbiorczą (i spam), aby zakończyć weryfikację"),
|
||||
"confirm": MessageLookupByLibrary.simpleMessage("Potwierdź"),
|
||||
"confirmAccountDeletion":
|
||||
MessageLookupByLibrary.simpleMessage("Potwierdź usunięcie konta"),
|
||||
"confirmDeletePrompt": MessageLookupByLibrary.simpleMessage(
|
||||
"Tak, chcę trwale usunąć konto i wszystkie dane z nim powiązane."),
|
||||
"confirmPassword":
|
||||
MessageLookupByLibrary.simpleMessage("Powtórz hasło"),
|
||||
"continueLabel": MessageLookupByLibrary.simpleMessage("Kontynuuj"),
|
||||
"createAccount": MessageLookupByLibrary.simpleMessage("Stwórz konto"),
|
||||
"createNewAccount":
|
||||
MessageLookupByLibrary.simpleMessage("Stwórz nowe konto"),
|
||||
"decrypting":
|
||||
MessageLookupByLibrary.simpleMessage("Odszyfrowywanie..."),
|
||||
"deleteAccount": MessageLookupByLibrary.simpleMessage("Usuń konto"),
|
||||
"deleteAccountFeedbackPrompt": MessageLookupByLibrary.simpleMessage(
|
||||
"Przykro nam, że odchodzisz. Wyjaśnij nam, dlaczego nas opuszczasz, aby pomóc ulepszać nasze usługi."),
|
||||
"deleteAccountPermanentlyButton":
|
||||
MessageLookupByLibrary.simpleMessage("Usuń konto na stałe"),
|
||||
"deleteConfirmDialogBody": MessageLookupByLibrary.simpleMessage(
|
||||
"Zamierzasz trwale usunąć swoje konto i wszystkie jego dane.\nTa akcja jest nieodwracalna."),
|
||||
"deleteEmailRequest": MessageLookupByLibrary.simpleMessage(
|
||||
"Wyślij wiadomość e-mail na <warning>account-deletion@ente.io</warning> z zarejestrowanego adresu e-mail."),
|
||||
"deleteReason1": MessageLookupByLibrary.simpleMessage(
|
||||
"Brakuje kluczowej funkcji, której potrzebuję"),
|
||||
"deleteReason2": MessageLookupByLibrary.simpleMessage(
|
||||
"Aplikacja lub określona funkcja nie \nzachowuje się tak, jak sądzę, że powinna"),
|
||||
"deleteReason3": MessageLookupByLibrary.simpleMessage(
|
||||
"Znalazłem inną, lepszą usługę"),
|
||||
"deleteReason4": MessageLookupByLibrary.simpleMessage(
|
||||
"Inna, niewymieniona wyżej przyczyna"),
|
||||
"deleteRequestSLAText": MessageLookupByLibrary.simpleMessage(
|
||||
"Twoje żądanie zostanie przetworzone w ciągu 72 godzin."),
|
||||
"doThisLater": MessageLookupByLibrary.simpleMessage("Spróbuj później"),
|
||||
"email": MessageLookupByLibrary.simpleMessage("Adres e-mail"),
|
||||
"enterCode": MessageLookupByLibrary.simpleMessage("Wprowadź kod"),
|
||||
"enterValidEmail": MessageLookupByLibrary.simpleMessage(
|
||||
"Podaj poprawny adres e-mail."),
|
||||
"enterYourEmailAddress":
|
||||
MessageLookupByLibrary.simpleMessage("Podaj swój adres e-mail"),
|
||||
"enterYourPassword":
|
||||
MessageLookupByLibrary.simpleMessage("Wprowadź hasło"),
|
||||
"enterYourRecoveryKey": MessageLookupByLibrary.simpleMessage(
|
||||
"Wprowadź swój klucz odzyskiwania"),
|
||||
"feedback": MessageLookupByLibrary.simpleMessage("Informacja zwrotna"),
|
||||
"forgotPassword":
|
||||
MessageLookupByLibrary.simpleMessage("Nie pamiętam hasła"),
|
||||
"incorrectRecoveryKeyBody":
|
||||
MessageLookupByLibrary.simpleMessage("Kod jest nieprawidłowy"),
|
||||
"incorrectRecoveryKeyTitle": MessageLookupByLibrary.simpleMessage(
|
||||
"Nieprawidłowy klucz odzyskiwania"),
|
||||
"invalidEmailAddress":
|
||||
MessageLookupByLibrary.simpleMessage("Nieprawidłowy adres e-mail"),
|
||||
"kindlyHelpUsWithThisInformation":
|
||||
MessageLookupByLibrary.simpleMessage("Pomóż nam z tą informacją"),
|
||||
"logInLabel": MessageLookupByLibrary.simpleMessage("Zaloguj się"),
|
||||
"noRecoveryKey":
|
||||
MessageLookupByLibrary.simpleMessage("Brak klucza odzyskiwania?"),
|
||||
"noRecoveryKeyNoDecryption": MessageLookupByLibrary.simpleMessage(
|
||||
"Ze względu na charakter naszego protokołu szyfrowania end-to-end, dane nie mogą być odszyfrowane bez hasła lub klucza odzyskiwania"),
|
||||
"ok": MessageLookupByLibrary.simpleMessage("Ok"),
|
||||
"oops": MessageLookupByLibrary.simpleMessage("Ups"),
|
||||
"password": MessageLookupByLibrary.simpleMessage("Hasło"),
|
||||
"pleaseWait": MessageLookupByLibrary.simpleMessage("Proszę czekać..."),
|
||||
"recoverButton": MessageLookupByLibrary.simpleMessage("Odzyskaj"),
|
||||
"recoverySuccessful":
|
||||
MessageLookupByLibrary.simpleMessage("Odzyskano pomyślnie!"),
|
||||
"resetPasswordTitle":
|
||||
MessageLookupByLibrary.simpleMessage("Zresetuj hasło"),
|
||||
"selectReason": MessageLookupByLibrary.simpleMessage("Wybierz powód"),
|
||||
"sendEmail": MessageLookupByLibrary.simpleMessage("Wyślij e-mail"),
|
||||
"somethingWentWrongPleaseTryAgain":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Coś poszło nie tak, spróbuj ponownie"),
|
||||
"sorry": MessageLookupByLibrary.simpleMessage("Przepraszamy"),
|
||||
"strongStrength": MessageLookupByLibrary.simpleMessage("Silne"),
|
||||
"terminate": MessageLookupByLibrary.simpleMessage("Zakończ"),
|
||||
"terminateSession":
|
||||
MessageLookupByLibrary.simpleMessage("Zakończyć sesję?"),
|
||||
"termsOfServicesTitle":
|
||||
MessageLookupByLibrary.simpleMessage("Regulamin"),
|
||||
"thisDevice": MessageLookupByLibrary.simpleMessage("To urządzenie"),
|
||||
"thisWillLogYouOutOfTheFollowingDevice":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"To wyloguje Cię z tego urządzenia:"),
|
||||
"thisWillLogYouOutOfThisDevice": MessageLookupByLibrary.simpleMessage(
|
||||
"To wyloguje Cię z tego urządzenia!"),
|
||||
"tryAgain": MessageLookupByLibrary.simpleMessage("Spróbuj ponownie"),
|
||||
"verify": MessageLookupByLibrary.simpleMessage("Weryfikuj"),
|
||||
"verifyEmail":
|
||||
MessageLookupByLibrary.simpleMessage("Zweryfikuj adres e-mail"),
|
||||
"weakStrength": MessageLookupByLibrary.simpleMessage("Słabe"),
|
||||
"welcomeBack": MessageLookupByLibrary.simpleMessage("Witaj ponownie!"),
|
||||
"yourAccountHasBeenDeleted":
|
||||
MessageLookupByLibrary.simpleMessage("Twoje konto zostało usunięte")
|
||||
};
|
||||
}
|
204
lib/generated/intl/messages_pt.dart
Normal file
204
lib/generated/intl/messages_pt.dart
Normal file
|
@ -0,0 +1,204 @@
|
|||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a pt locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
|
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'pt';
|
||||
|
||||
static String m4(user) =>
|
||||
"${user} Não poderá adicionar mais fotos a este álbum\n\nEles ainda poderão remover as fotos existentes adicionadas por eles";
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"accountWelcomeBack":
|
||||
MessageLookupByLibrary.simpleMessage("Bem-vindo de volta!"),
|
||||
"activeSessions":
|
||||
MessageLookupByLibrary.simpleMessage("Sessões ativas"),
|
||||
"addANewEmail":
|
||||
MessageLookupByLibrary.simpleMessage("Adicionar um novo email"),
|
||||
"addCollaborator":
|
||||
MessageLookupByLibrary.simpleMessage("Adicionar colaborador"),
|
||||
"addMore": MessageLookupByLibrary.simpleMessage("Adicione mais"),
|
||||
"addViewer":
|
||||
MessageLookupByLibrary.simpleMessage("Adicionar visualizador"),
|
||||
"addedAs": MessageLookupByLibrary.simpleMessage("Adicionado como"),
|
||||
"albumOwner": MessageLookupByLibrary.simpleMessage("Proprietário"),
|
||||
"allowAddPhotosDescription": MessageLookupByLibrary.simpleMessage(
|
||||
"Permita que as pessoas com o link também adicionem fotos ao álbum compartilhado."),
|
||||
"allowAddingPhotos":
|
||||
MessageLookupByLibrary.simpleMessage("Permitir adicionar fotos"),
|
||||
"allowDownloads":
|
||||
MessageLookupByLibrary.simpleMessage("Permitir transferências"),
|
||||
"askDeleteReason": MessageLookupByLibrary.simpleMessage(
|
||||
"Qual é o principal motivo para você excluir sua conta?"),
|
||||
"cancel": MessageLookupByLibrary.simpleMessage("Cancelar"),
|
||||
"cannotAddMorePhotosAfterBecomingViewer": m4,
|
||||
"changePermissions":
|
||||
MessageLookupByLibrary.simpleMessage("Alterar permissões?"),
|
||||
"checkInboxAndSpamFolder": MessageLookupByLibrary.simpleMessage(
|
||||
"Verifique sua caixa de entrada (e ‘spam’) para concluir a verificação"),
|
||||
"codeCopiedToClipboard": MessageLookupByLibrary.simpleMessage(
|
||||
"Código copiado para a área de transferência"),
|
||||
"collaborator": MessageLookupByLibrary.simpleMessage("Colaborador"),
|
||||
"collaboratorsCanAddPhotosAndVideosToTheSharedAlbum":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Os colaboradores podem adicionar fotos e vídeos ao álbum compartilhado."),
|
||||
"confirm": MessageLookupByLibrary.simpleMessage("Confirme"),
|
||||
"confirmAccountDeletion":
|
||||
MessageLookupByLibrary.simpleMessage("Confirmar exclusão da conta"),
|
||||
"confirmDeletePrompt": MessageLookupByLibrary.simpleMessage(
|
||||
"Sim, desejo excluir permanentemente esta conta e todos os seus dados."),
|
||||
"confirmPassword":
|
||||
MessageLookupByLibrary.simpleMessage("Confirme sua senha"),
|
||||
"confirmRecoveryKey": MessageLookupByLibrary.simpleMessage(
|
||||
"Confirme a chave de recuperação"),
|
||||
"confirmYourRecoveryKey": MessageLookupByLibrary.simpleMessage(
|
||||
"Confirme sua chave de recuperação"),
|
||||
"copypasteThisCodentoYourAuthenticatorApp":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Copie e cole este código\npara seu aplicativo autenticador"),
|
||||
"createAccount":
|
||||
MessageLookupByLibrary.simpleMessage("Criar uma conta"),
|
||||
"createNewAccount":
|
||||
MessageLookupByLibrary.simpleMessage("Criar nova conta"),
|
||||
"decrypting":
|
||||
MessageLookupByLibrary.simpleMessage("Descriptografando..."),
|
||||
"deleteAccount": MessageLookupByLibrary.simpleMessage("Deletar conta"),
|
||||
"deleteAccountFeedbackPrompt": MessageLookupByLibrary.simpleMessage(
|
||||
"Lamentamos ver você partir. Por favor, compartilhe seus comentários para nos ajudar a melhorar."),
|
||||
"deleteAccountPermanentlyButton": MessageLookupByLibrary.simpleMessage(
|
||||
"Excluir conta permanentemente"),
|
||||
"deleteConfirmDialogBody": MessageLookupByLibrary.simpleMessage(
|
||||
"Você está prestes a excluir permanentemente sua conta e todos os seus dados.\nEsta ação é irreversível."),
|
||||
"deleteEmailRequest": MessageLookupByLibrary.simpleMessage(
|
||||
"Por favor, envie um email para <warning>account-deletion@ente.io</warning> a partir do seu endereço de email registrado."),
|
||||
"deleteReason1": MessageLookupByLibrary.simpleMessage(
|
||||
"Está faltando um recurso-chave que eu preciso"),
|
||||
"deleteReason2": MessageLookupByLibrary.simpleMessage(
|
||||
"O aplicativo ou um determinado recurso não\nestá funcionando como eu acredito que deveria"),
|
||||
"deleteReason3": MessageLookupByLibrary.simpleMessage(
|
||||
"Encontrei outro serviço que gosto mais"),
|
||||
"deleteReason4":
|
||||
MessageLookupByLibrary.simpleMessage("Meu motivo não está listado"),
|
||||
"deleteRequestSLAText": MessageLookupByLibrary.simpleMessage(
|
||||
"Sua solicitação será processada em até 72 horas."),
|
||||
"disableDownloadWarningBody": MessageLookupByLibrary.simpleMessage(
|
||||
"Os espectadores ainda podem tirar screenshots ou salvar uma cópia de suas fotos usando ferramentas externas"),
|
||||
"disableDownloadWarningTitle":
|
||||
MessageLookupByLibrary.simpleMessage("Observe"),
|
||||
"enterCode": MessageLookupByLibrary.simpleMessage("Coloque o código"),
|
||||
"enterEmail": MessageLookupByLibrary.simpleMessage("Digite o email"),
|
||||
"enterThe6digitCodeFromnyourAuthenticatorApp":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Digite o código de 6 dígitos de\nseu aplicativo autenticador"),
|
||||
"enterValidEmail": MessageLookupByLibrary.simpleMessage(
|
||||
"Por, favor insira um endereço de email válido."),
|
||||
"enterYourEmailAddress": MessageLookupByLibrary.simpleMessage(
|
||||
"Insira o seu endereço de email"),
|
||||
"enterYourRecoveryKey": MessageLookupByLibrary.simpleMessage(
|
||||
"Digite sua chave de recuperação"),
|
||||
"feedback": MessageLookupByLibrary.simpleMessage("Opinião"),
|
||||
"forgotPassword":
|
||||
MessageLookupByLibrary.simpleMessage("Esqueceu sua senha"),
|
||||
"incorrectRecoveryKeyBody": MessageLookupByLibrary.simpleMessage(
|
||||
"A chave de recuperação que você digitou está incorreta"),
|
||||
"incorrectRecoveryKeyTitle": MessageLookupByLibrary.simpleMessage(
|
||||
"Chave de recuperação incorreta"),
|
||||
"invalidEmailAddress":
|
||||
MessageLookupByLibrary.simpleMessage("Endereço de email invalido"),
|
||||
"invalidKey": MessageLookupByLibrary.simpleMessage("Chave inválida"),
|
||||
"invalidRecoveryKey": MessageLookupByLibrary.simpleMessage(
|
||||
"A chave de recuperação que você digitou não é válida. Certifique-se de que contém 24 palavras e verifique a ortografia de cada uma.\n\nSe você inseriu um código de recuperação mais antigo, verifique se ele tem 64 caracteres e verifique cada um deles."),
|
||||
"kindlyHelpUsWithThisInformation": MessageLookupByLibrary.simpleMessage(
|
||||
"Ajude-nos com esta informação"),
|
||||
"linkDeviceLimit":
|
||||
MessageLookupByLibrary.simpleMessage("Limite do dispositivo"),
|
||||
"linkExpired": MessageLookupByLibrary.simpleMessage("Expirado"),
|
||||
"linkExpiry": MessageLookupByLibrary.simpleMessage("Expiração do link"),
|
||||
"lostDevice":
|
||||
MessageLookupByLibrary.simpleMessage("Dispositivo perdido?"),
|
||||
"manage": MessageLookupByLibrary.simpleMessage("Gerenciar"),
|
||||
"noRecoveryKey": MessageLookupByLibrary.simpleMessage(
|
||||
"Nenhuma chave de recuperação?"),
|
||||
"noRecoveryKeyNoDecryption": MessageLookupByLibrary.simpleMessage(
|
||||
"Devido à natureza do nosso protocolo de criptografia de ponta a ponta, seus dados não podem ser descriptografados sem sua senha ou chave de recuperação"),
|
||||
"oops": MessageLookupByLibrary.simpleMessage("Ops"),
|
||||
"orPickAnExistingOne":
|
||||
MessageLookupByLibrary.simpleMessage("Ou escolha um existente"),
|
||||
"password": MessageLookupByLibrary.simpleMessage("Senha"),
|
||||
"passwordLock":
|
||||
MessageLookupByLibrary.simpleMessage("Bloqueio de senha"),
|
||||
"recoverButton": MessageLookupByLibrary.simpleMessage("Recuperar"),
|
||||
"recoveryKeySuccessBody": MessageLookupByLibrary.simpleMessage(
|
||||
"Ótimo! Sua chave de recuperação é válida. Obrigado por verificar.\n\nLembre-se de manter o backup seguro de sua chave de recuperação."),
|
||||
"recoveryKeyVerified": MessageLookupByLibrary.simpleMessage(
|
||||
"Chave de recuperação verificada"),
|
||||
"recoveryKeyVerifyReason": MessageLookupByLibrary.simpleMessage(
|
||||
"Sua chave de recuperação é a única maneira de recuperar suas fotos se você esquecer sua senha. Você pode encontrar sua chave de recuperação em Configurações > Conta.\n\nDigite sua chave de recuperação aqui para verificar se você a salvou corretamente."),
|
||||
"recoverySuccessful":
|
||||
MessageLookupByLibrary.simpleMessage("Recuperação bem sucedida!"),
|
||||
"remove": MessageLookupByLibrary.simpleMessage("Remover"),
|
||||
"removeParticipant":
|
||||
MessageLookupByLibrary.simpleMessage("Remover participante"),
|
||||
"saveYourRecoveryKeyIfYouHaventAlready":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Salve sua chave de recuperação, caso ainda não o tenha feito"),
|
||||
"scanCode": MessageLookupByLibrary.simpleMessage("Escanear código"),
|
||||
"scanThisBarcodeWithnyourAuthenticatorApp":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Escaneie este código de barras com\nseu aplicativo autenticador"),
|
||||
"selectReason":
|
||||
MessageLookupByLibrary.simpleMessage("Selecione o motivo"),
|
||||
"sendEmail": MessageLookupByLibrary.simpleMessage("Enviar email"),
|
||||
"setupComplete":
|
||||
MessageLookupByLibrary.simpleMessage("Configuração concluída"),
|
||||
"somethingWentWrongPleaseTryAgain":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Algo deu errado. Por favor, tente outra vez"),
|
||||
"sorry": MessageLookupByLibrary.simpleMessage("Desculpe"),
|
||||
"tapToCopy": MessageLookupByLibrary.simpleMessage("toque para copiar"),
|
||||
"terminate": MessageLookupByLibrary.simpleMessage("Terminar"),
|
||||
"terminateSession":
|
||||
MessageLookupByLibrary.simpleMessage("Encerrar sessão?"),
|
||||
"thisCanBeUsedToRecoverYourAccountIfYou":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Isso pode ser usado para recuperar sua conta se você perder seu segundo fator"),
|
||||
"thisDevice": MessageLookupByLibrary.simpleMessage("Este aparelho"),
|
||||
"thisWillLogYouOutOfTheFollowingDevice":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Isso fará com que você saia do seguinte dispositivo:"),
|
||||
"thisWillLogYouOutOfThisDevice": MessageLookupByLibrary.simpleMessage(
|
||||
"Isso fará com que você saia deste dispositivo!"),
|
||||
"tryAgain": MessageLookupByLibrary.simpleMessage("Tente novamente"),
|
||||
"twofactorAuthenticationPageTitle":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Autenticação de dois fatores"),
|
||||
"verify": MessageLookupByLibrary.simpleMessage("Verificar"),
|
||||
"verifyEmail": MessageLookupByLibrary.simpleMessage("Verificar email"),
|
||||
"verifyingRecoveryKey": MessageLookupByLibrary.simpleMessage(
|
||||
"Verificando chave de recuperação..."),
|
||||
"viewRecoveryKey":
|
||||
MessageLookupByLibrary.simpleMessage("Ver chave de recuperação"),
|
||||
"viewer": MessageLookupByLibrary.simpleMessage("Visualizador"),
|
||||
"yesConvertToViewer": MessageLookupByLibrary.simpleMessage(
|
||||
"Sim, converter para visualizador"),
|
||||
"you": MessageLookupByLibrary.simpleMessage("Você"),
|
||||
"yourAccountHasBeenDeleted":
|
||||
MessageLookupByLibrary.simpleMessage("Sua conta foi deletada")
|
||||
};
|
||||
}
|
6686
lib/generated/l10n.dart
Normal file
6686
lib/generated/l10n.dart
Normal file
File diff suppressed because it is too large
Load diff
|
@ -85,7 +85,7 @@
|
|||
"ackPasswordLostWarning": "I understand that if I lose my password, I may lose my data since my data is <underline>end-to-end encrypted</underline>.",
|
||||
"privacyPolicyTitle": "Privacy Policy",
|
||||
"termsOfServicesTitle": "Terms",
|
||||
"signUpTerms" : "I agree to the <u-terms>terms of service</u-terms> and <u-policy>privacy policy</u-policy>",
|
||||
"signUpTerms": "I agree to the <u-terms>terms of service</u-terms> and <u-policy>privacy policy</u-policy>",
|
||||
"logInLabel": "Log in",
|
||||
"loginTerms": "By clicking log in, I agree to the <u-terms>terms of service</u-terms> and <u-policy>privacy policy</u-policy>",
|
||||
"changeEmail": "Change email",
|
||||
|
@ -720,7 +720,7 @@
|
|||
"calculating": "Calculating...",
|
||||
"pleaseWaitDeletingAlbum": "Please wait, deleting album",
|
||||
"searchHintText": "Albums, months, days, years, ...",
|
||||
"searchByExamples": "\u2022 Album names (e.g. \"Camera\")\n\u2022 Types of files (e.g. \"Videos\", \".gif\")\n\u2022 Years and months (e.g. \"2022\", \"January\")\n\u2022 Holidays (e.g. \"Christmas\")\n\u2022 Photo descriptions (e.g. “#fun”)",
|
||||
"searchByExamples": "• Album names (e.g. \"Camera\")\n• Types of files (e.g. \"Videos\", \".gif\")\n• Years and months (e.g. \"2022\", \"January\")\n• Holidays (e.g. \"Christmas\")\n• Photo descriptions (e.g. “#fun”)",
|
||||
"youCanTrySearchingForADifferentQuery": "You can try searching for a different query.",
|
||||
"noResultsFound": "No results found",
|
||||
"addedBy": "Added by {emailOrName}",
|
||||
|
@ -755,7 +755,7 @@
|
|||
"@filesBackedUpInAlbum": {
|
||||
"description": "Text to tell user how many files have been backed up in the album",
|
||||
"placeholders": {
|
||||
"count" :{
|
||||
"count": {
|
||||
"example": "1",
|
||||
"type": "int"
|
||||
},
|
||||
|
@ -770,7 +770,7 @@
|
|||
"@filesBackedUpFromDevice": {
|
||||
"description": "Text to tell user how many files have been backed up from this device",
|
||||
"placeholders": {
|
||||
"count" :{
|
||||
"count": {
|
||||
"example": "1",
|
||||
"type": "int"
|
||||
},
|
||||
|
@ -787,7 +787,7 @@
|
|||
"freeUpAccessPostDelete": "You can still access {count, plural, one {it} other {them}} on ente as long as you have an active subscription",
|
||||
"@freeUpAccessPostDelete": {
|
||||
"placeholders": {
|
||||
"count" :{
|
||||
"count": {
|
||||
"example": "1",
|
||||
"type": "int"
|
||||
}
|
||||
|
@ -820,7 +820,7 @@
|
|||
"@syncProgress": {
|
||||
"description": "Text to tell user how many memories have been preserved",
|
||||
"placeholders": {
|
||||
"completed" :{
|
||||
"completed": {
|
||||
"type": "int"
|
||||
},
|
||||
"total": {
|
||||
|
@ -877,5 +877,22 @@
|
|||
"loadMessage6": "You can share links to your albums with your loved ones",
|
||||
"loadMessage7": "Our mobile apps run in the background to encrypt and backup any new photos you click",
|
||||
"loadMessage8": "web.ente.io has a slick uploader",
|
||||
"loadMessage9": "We use Xchacha20Poly1305 to safely encrypt your data"
|
||||
}
|
||||
"loadMessage9": "We use Xchacha20Poly1305 to safely encrypt your data",
|
||||
"language": "Language",
|
||||
"selectLanguage": "Select Language",
|
||||
"locationName": "Location name",
|
||||
"addLocation": "Add location",
|
||||
"groupNearbyPhotos": "Group nearby photos",
|
||||
"location": "Location",
|
||||
"kiloMeterUnit": "km",
|
||||
"addLocationButton": "Add",
|
||||
"radius": "Radius",
|
||||
"locationTagFeatureDescription": "A location tag groups all photos that were taken within some radius of a photo",
|
||||
"galleryMemoryLimitInfo": "Up to 1000 memories shown in gallery",
|
||||
"save": "Save",
|
||||
"centerPoint": "Center point",
|
||||
"pickCenterPoint": "Pick center point",
|
||||
"useSelectedPhoto": "Use selected photo",
|
||||
"edit": "Edit",
|
||||
"deleteLocation": "Delete location"
|
||||
}
|
|
@ -96,6 +96,7 @@
|
|||
"pleaseTryAgain": "Veuillez réessayer",
|
||||
"recreatePasswordTitle": "Recréer le mot de passe",
|
||||
"useRecoveryKey": "Utiliser la clé de récupération",
|
||||
"recreatePasswordBody": "L'appareil actuel n'est pas assez puissant pour vérifier votre mot de passe, mais nous pouvons régénérer d'une manière qui fonctionne avec tous les appareils.\n\nVeuillez vous connecter à l'aide de votre clé de récupération et régénérer votre mot de passe (vous pouvez réutiliser le même si vous le souhaitez).",
|
||||
"verifyPassword": "Vérifier le mot de passe",
|
||||
"recoveryKey": "Clé de récupération",
|
||||
"recoveryKeyOnForgotPassword": "Si vous oubliez votre mot de passe, la seule façon de récupérer vos données sera grâce à cette clé.",
|
||||
|
@ -182,6 +183,78 @@
|
|||
"linkNeverExpires": "Jamais",
|
||||
"expiredLinkInfo": "Ce lien a expiré. Veuillez sélectionner un nouveau délai d'expiration ou désactiver l'expiration du lien.",
|
||||
"setAPassword": "Définir un mot de passe",
|
||||
"lockButtonLabel": "Verrouiller",
|
||||
"enterPassword": "Saisissez le mot de passe",
|
||||
"removeLink": "Supprimer le lien",
|
||||
"manageLink": "Gérer le lien",
|
||||
"linkExpiresOn": "Le lien expirera le {expiryTime}",
|
||||
"albumUpdated": "Album mis à jour",
|
||||
"maxDeviceLimitSpikeHandling": "Lorsqu'elle est définie au maximum ({maxValue}), la limite de l'appareil sera assouplie pour permettre des pointes temporaires d'un grand nombre de téléspectateurs.",
|
||||
"@maxDeviceLimitSpikeHandling": {
|
||||
"placeholders": {
|
||||
"maxValue": {
|
||||
"type": "int",
|
||||
"example": "100"
|
||||
}
|
||||
}
|
||||
},
|
||||
"never": "Jamais",
|
||||
"custom": "Personnaliser",
|
||||
"@custom": {
|
||||
"description": "Label for setting custom value for link expiry"
|
||||
},
|
||||
"after1Hour": "Après 1 heure",
|
||||
"after1Day": "Après 1 jour",
|
||||
"after1Week": "Après 1 semaine",
|
||||
"after1Month": "Après 1 mois",
|
||||
"after1Year": "Après 1 an",
|
||||
"manageParticipants": "Gérer",
|
||||
"collabLinkSectionDescription": "Créez un lien pour permettre aux gens d'ajouter et de voir des photos dans votre album partagé sans avoir besoin d'une application ente ou d'un compte. Idéal pour collecter des photos d'événement.",
|
||||
"collectPhotos": "Récupérer les photos",
|
||||
"collaborativeLink": "Lien collaboratif",
|
||||
"shareWithNonenteUsers": "Partager avec des utilisateurs non-ente",
|
||||
"createPublicLink": "Créer un lien public",
|
||||
"sendLink": "Envoyer le lien",
|
||||
"copyLink": "Copier le lien",
|
||||
"linkHasExpired": "Le lien a expiré",
|
||||
"publicLinkEnabled": "Lien public activé",
|
||||
"shareALink": "Partager le lien",
|
||||
"sharedAlbumSectionDescription": "Créez des albums partagés et collaboratifs avec d'autres utilisateurs de ente, y compris des utilisateurs sur des plans gratuits.",
|
||||
"shareWithPeopleSectionTitle": "{numberOfPeople, plural, =0 {Partagez avec des personnes spécifiques} =1 {Partagé avec 1 personne} other {Partagé avec {numberOfPeople} des gens}}",
|
||||
"@shareWithPeopleSectionTitle": {
|
||||
"placeholders": {
|
||||
"numberOfPeople": {
|
||||
"type": "int",
|
||||
"example": "2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"thisIsYourVerificationId": "Ceci est votre ID de vérification",
|
||||
"someoneSharingAlbumsWithYouShouldSeeTheSameId": "Quelqu'un qui partage des albums avec vous devrait voir le même ID sur son appareil.",
|
||||
"howToViewShareeVerificationID": "Demandez-leur d'appuyer longuement sur leur adresse e-mail sur l'écran des paramètres et de vérifier que les identifiants des deux appareils correspondent.",
|
||||
"thisIsPersonVerificationId": "Ceci est l'ID de vérification de {email}",
|
||||
"@thisIsPersonVerificationId": {
|
||||
"placeholders": {
|
||||
"email": {
|
||||
"type": "String",
|
||||
"example": "someone@ente.io"
|
||||
}
|
||||
}
|
||||
},
|
||||
"verificationId": "ID de vérification",
|
||||
"verifyEmailID": "Vérifier {email}",
|
||||
"emailNoEnteAccount": "{email} n'a pas de compte ente.\n\nEnvoyez une invitation pour partager des photos.",
|
||||
"shareMyVerificationID": "Voici mon ID de vérification : {verificationID} pour ente.io.",
|
||||
"shareTextConfirmOthersVerificationID": "Hé, pouvez-vous confirmer qu'il s'agit de votre ID de vérification ente.io : {verificationID}",
|
||||
"somethingWentWrong": "Un problème est survenu",
|
||||
"sendInvite": "Envoyer Invitations",
|
||||
"shareTextRecommendUsingEnte": "Téléchargez ente pour que nous puissions facilement partager des photos et des vidéos de qualité originale\n\nhttps://ente.io/#download",
|
||||
"done": "Terminé",
|
||||
"applyCodeTitle": "Utiliser le code",
|
||||
"enterCodeDescription": "Entrez le code fourni par votre ami pour réclamer de l'espace de stockage gratuit pour vous deux",
|
||||
"apply": "Appliquer",
|
||||
"failedToApplyCode": "Impossible d'appliquer le code",
|
||||
"enterReferralCode": "Entrez le code de parrainage",
|
||||
"codeAppliedPageTitle": "Code appliqué",
|
||||
"storageInGB": "{storageAmountInGB} Go",
|
||||
"claimed": "Réclamée",
|
||||
|
@ -195,5 +268,460 @@
|
|||
"shareTextReferralCode": "code de parrainage ente : {referralCode} \n\nAppliquez le dans Paramètres → Général → Références pour obtenir {referralStorageInGB} Go gratuitement après votre inscription à un plan payant\n\nhttps://ente.io",
|
||||
"claimFreeStorage": "Réclamer le stockage gratuit",
|
||||
"inviteYourFriends": "Invite tes ami(e)s",
|
||||
"failedToFetchReferralDetails": "Impossible de récupérer les détails du parrainage. Veuillez réessayer plus tard."
|
||||
"failedToFetchReferralDetails": "Impossible de récupérer les détails du parrainage. Veuillez réessayer plus tard.",
|
||||
"referralStep1": "1. Donnez ce code à vos amis",
|
||||
"referralStep2": "2. Ils s'inscrivent à une offre payante",
|
||||
"referralStep3": "3. Vous recevez tous les deux {storageInGB} GB* gratuits",
|
||||
"referralsAreCurrentlyPaused": "Les recommandations sont actuellement en pause",
|
||||
"youCanAtMaxDoubleYourStorage": "* Vous pouvez au maximum doubler votre espace de stockage",
|
||||
"claimedStorageSoFar": "{isFamilyMember, select, true {Votre famille a demandé {storageAmountInGb} Gb jusqu'à présent} false {Vous avez réclamé {storageAmountInGb} Gb jusqu'à présent} other {Vous avez réclamé {storageAmountInGb} Gbjusqu'à présent!}}",
|
||||
"@claimedStorageSoFar": {
|
||||
"placeholders": {
|
||||
"isFamilyMember": {
|
||||
"type": "String",
|
||||
"example": "true"
|
||||
},
|
||||
"storageAmountInGb": {
|
||||
"type": "int",
|
||||
"example": "10"
|
||||
}
|
||||
}
|
||||
},
|
||||
"faq": "FAQ",
|
||||
"oopsSomethingWentWrong": "Oups, une erreur est arrivée",
|
||||
"peopleUsingYourCode": "Personnes utilisant votre code",
|
||||
"eligible": "éligible",
|
||||
"total": "total",
|
||||
"codeUsedByYou": "Code utilisé par vous",
|
||||
"freeStorageClaimed": "Stockage gratuit réclamé",
|
||||
"freeStorageUsable": "Stockage gratuit utilisable",
|
||||
"usableReferralStorageInfo": "Le stockage utilisable est limité par votre offre actuelle. Le stockage excédentaire deviendra automatiquement utilisable lorsque vous mettez à niveau votre offre.",
|
||||
"removeFromAlbumTitle": "Retirer de l'album ?",
|
||||
"removeFromAlbum": "Retirer de l'album",
|
||||
"itemsWillBeRemovedFromAlbum": "Les éléments sélectionnés seront supprimés de cet album",
|
||||
"removeShareItemsWarning": "Certains des objets que vous êtes en train de retirer ont été ajoutés par d'autres personnes, vous perdrez l'accès vers ces objets",
|
||||
"addingToFavorites": "Ajout aux favoris...",
|
||||
"removingFromFavorites": "Suppression des favoris…",
|
||||
"sorryCouldNotAddToFavorites": "Désolé, impossible d'ajouter aux favoris !",
|
||||
"sorryCouldNotRemoveFromFavorites": "Désolé, impossible de supprimer des favoris !",
|
||||
"subscribeToEnableSharing": "Il semble que votre abonnement ait expiré. Veuillez vous abonner pour activer le partage.",
|
||||
"subscribe": "S'abonner",
|
||||
"canOnlyRemoveFilesOwnedByYou": "Vous ne pouvez supprimer que les fichiers que vous possédez",
|
||||
"deleteSharedAlbum": "Supprimer l'album partagé ?",
|
||||
"deleteAlbum": "Supprimer l'album",
|
||||
"deleteAlbumDialog": "Supprimer aussi les photos (et vidéos) présentes dans cet album depuis <bold>tous</bold> les autres albums dont ils font partie ?",
|
||||
"deleteSharedAlbumDialogBody": "L'album sera supprimé pour tout le monde\n\nVous perdrez l'accès aux photos partagées dans cet album qui est détenues par d'autres personnes",
|
||||
"yesRemove": "Oui, supprimer",
|
||||
"creatingLink": "Création du lien...",
|
||||
"removeWithQuestionMark": "Enlever?",
|
||||
"removeParticipantBody": "{userEmail} sera retiré de cet album partagé\n\nToutes les photos ajoutées par eux seront également retirées de l'album",
|
||||
"keepPhotos": "Conserver les photos",
|
||||
"deletePhotos": "Supprimer des photos",
|
||||
"inviteToEnte": "Inviter à ente",
|
||||
"removePublicLink": "Supprimer le lien public",
|
||||
"disableLinkMessage": "Cela supprimera le lien public pour accéder à \"{albumName}\".",
|
||||
"sharing": "Partage...",
|
||||
"youCannotShareWithYourself": "Vous ne pouvez pas partager avec vous-même",
|
||||
"archive": "Archiver",
|
||||
"createAlbumActionHint": "Appuyez longuement pour sélectionner des photos et cliquez sur + pour créer un album",
|
||||
"importing": "Importation en cours...",
|
||||
"failedToLoadAlbums": "Impossible de charger les albums",
|
||||
"hidden": "Masqué",
|
||||
"authToViewYourHiddenFiles": "Veuillez vous authentifier pour voir vos fichiers cachés",
|
||||
"trash": "Corbeille",
|
||||
"uncategorized": "Aucune catégorie",
|
||||
"videoSmallCase": "vidéo",
|
||||
"photoSmallCase": "photo",
|
||||
"singleFileDeleteHighlight": "Il sera supprimé de tous les albums.",
|
||||
"singleFileInBothLocalAndRemote": "Ce {fileType} est à la fois dans ente et votre appareil.",
|
||||
"singleFileInRemoteOnly": "Ce {fileType} sera supprimé de ente.",
|
||||
"singleFileDeleteFromDevice": "Ce {fileType} sera supprimé de votre appareil.",
|
||||
"deleteFromEnte": "Supprimer de ente",
|
||||
"yesDelete": "Oui, supprimer",
|
||||
"movedToTrash": "Déplacé dans la corbeille",
|
||||
"deleteFromDevice": "Supprimer de l'appareil",
|
||||
"deleteFromBoth": "Supprimer des deux",
|
||||
"newAlbum": "Nouvel album",
|
||||
"albums": "Albums",
|
||||
"memoryCount": "{count, plural, one{{count} mémoire} other{{count} souvenirs}}",
|
||||
"@memoryCount": {
|
||||
"description": "The text to display the number of memories",
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"example": "1",
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"selectedPhotos": "{count} sélectionné",
|
||||
"@selectedPhotos": {
|
||||
"description": "Display the number of selected photos",
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"example": "5",
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"selectedPhotosWithYours": "{count} sélectionné ({yourCount} votre)",
|
||||
"@selectedPhotosWithYours": {
|
||||
"description": "Display the number of selected photos, including the number of selected photos owned by the user",
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"example": "12",
|
||||
"type": "int"
|
||||
},
|
||||
"yourCount": {
|
||||
"example": "2",
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"advancedSettings": "Avancé",
|
||||
"@advancedSettings": {
|
||||
"description": "The text to display in the advanced settings section"
|
||||
},
|
||||
"photoGridSize": "Taille de la grille photo",
|
||||
"manageDeviceStorage": "Gérer le stockage de l'appareil",
|
||||
"selectFoldersForBackup": "Sélectionner les dossiers à sauvegarder",
|
||||
"selectedFoldersWillBeEncryptedAndBackedUp": "Les dossiers sélectionnés seront cryptés et sauvegardés",
|
||||
"unselectAll": "Désélectionner tout",
|
||||
"selectAll": "Tout sélectionner",
|
||||
"skip": "Ignorer",
|
||||
"updatingFolderSelection": "Mise à jour de la sélection du dossier...",
|
||||
"itemCount": "{count, plural, one{{count} objet} other{{count} objets}}",
|
||||
"yearsAgo": "{count, plural, one{{count} il y a un an} other{{count} il y a des années}}",
|
||||
"backupSettings": "Paramètres de la sauvegarde",
|
||||
"backupOverMobileData": "Sauvegarde sur données mobiles",
|
||||
"backupVideos": "Sauvegarde des vidéos",
|
||||
"disableAutoLock": "Désactiver le verrouillage automatique",
|
||||
"deviceLockExplanation": "Désactiver le verrouillage de l'écran de l'appareil lorsque ente est au premier plan et il y a une sauvegarde en cours. Ce n'est normalement pas nécessaire, mais peut aider les gros téléchargements et les premières importations de grandes bibliothèques plus rapidement.",
|
||||
"about": "À propos",
|
||||
"weAreOpenSource": "Nous sommes open source !",
|
||||
"privacy": "Confidentialité",
|
||||
"terms": "Conditions",
|
||||
"checkForUpdates": "Vérifier les mises à jour",
|
||||
"checking": "Vérification...",
|
||||
"youAreOnTheLatestVersion": "Vous êtes sur la dernière version",
|
||||
"account": "Compte",
|
||||
"manageSubscription": "Gérer l'abonnement",
|
||||
"authToChangeYourEmail": "Veuillez vous authentifier pour modifier votre adresse e-mail",
|
||||
"changePassword": "Modifier le mot de passe",
|
||||
"authToChangeYourPassword": "Veuillez vous authentifier pour modifier votre mot de passe",
|
||||
"exportYourData": "Exportez vos données",
|
||||
"logout": "Déconnexion",
|
||||
"authToInitiateAccountDeletion": "Veuillez vous authentifier pour débuter la suppression du compte",
|
||||
"areYouSureYouWantToLogout": "Voulez-vous vraiment vous déconnecter ?",
|
||||
"yesLogout": "Oui, se déconnecter",
|
||||
"aNewVersionOfEnteIsAvailable": "Une nouvelle version de Wire est disponible.",
|
||||
"update": "Mise à jour",
|
||||
"installManually": "Installation manuelle",
|
||||
"criticalUpdateAvailable": "Mise à jour critique disponible",
|
||||
"updateAvailable": "Une mise à jour est disponible",
|
||||
"downloading": "Téléchargement en cours...",
|
||||
"theDownloadCouldNotBeCompleted": "Le téléchargement n'a pas pu être terminé",
|
||||
"retry": "Réessayer",
|
||||
"backedUpFolders": "Dossiers sauvegardés",
|
||||
"backup": "Sauvegarde",
|
||||
"freeUpDeviceSpace": "Libérer de l'espace sur l'appareil",
|
||||
"allClear": "✨ Tout est effacé",
|
||||
"noDeviceThatCanBeDeleted": "Vous n'avez pas de fichiers sur cet appareil qui peuvent être supprimés",
|
||||
"removeDuplicates": "Supprimer les doublons",
|
||||
"noDuplicates": "✨ Aucun doublon",
|
||||
"youveNoDuplicateFilesThatCanBeCleared": "Vous n'avez aucun fichier dédupliqué pouvant être nettoyé",
|
||||
"success": "Succès",
|
||||
"rateUs": "Évaluez-nous",
|
||||
"remindToEmptyDeviceTrash": "Également vide \"récemment supprimé\" de \"Paramètres\" -> \"Stockage\" pour réclamer l'espace libéré",
|
||||
"youHaveSuccessfullyFreedUp": "Vous avez libéré {storageSaved} avec succès !",
|
||||
"@youHaveSuccessfullyFreedUp": {
|
||||
"description": "The text to display when the user has successfully freed up storage",
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"storageSaved": {
|
||||
"example": "1.2 GB",
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"remindToEmptyEnteTrash": "Vide aussi votre \"Corbeille\" pour réclamer l'espace libéré",
|
||||
"sparkleSuccess": "✨ Succès",
|
||||
"duplicateFileCountWithStorageSaved": "Vous avez nettoyé {count, plural, one{{count} fichier dupliqué} other{{count} fichiers dupliqués}}, sauvegarde ({storageSaved}!)",
|
||||
"@duplicateFileCountWithStorageSaved": {
|
||||
"description": "The text to display when the user has successfully cleaned up duplicate files",
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"example": "1",
|
||||
"type": "int"
|
||||
},
|
||||
"storageSaved": {
|
||||
"example": "1.2 GB",
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"familyPlans": "Forfaits famille",
|
||||
"referrals": "Parrainages",
|
||||
"advanced": "Avancé",
|
||||
"general": "Général",
|
||||
"security": "Sécurité",
|
||||
"authToViewYourRecoveryKey": "Veuillez vous authentifier pour afficher votre clé de récupération",
|
||||
"twofactor": "Double authentification",
|
||||
"authToConfigureTwofactorAuthentication": "Veuillez vous authentifier pour configurer l'authentification à deux facteurs",
|
||||
"lockscreen": "Ecran de vérouillage",
|
||||
"authToChangeLockscreenSetting": "Veuillez vous authentifier pour modifier les paramètres de l'écran de verrouillage",
|
||||
"lockScreenEnablePreSteps": "Pour activer l'écran de verrouillage, veuillez configurer le code d'accès de l'appareil ou le verrouillage de l'écran dans les paramètres de votre système.",
|
||||
"viewActiveSessions": "Afficher les sessions actives",
|
||||
"authToViewYourActiveSessions": "Veuillez vous authentifier pour voir vos sessions actives",
|
||||
"disableTwofactor": "Désactiver la double-authentification",
|
||||
"confirm2FADisable": "Voulez-vous vraiment désactiver l'authentification à deux facteurs ?",
|
||||
"no": "Non",
|
||||
"yes": "Oui",
|
||||
"social": "Réseaux Sociaux",
|
||||
"rateUsOnStore": "Notez-nous sur {storeName}",
|
||||
"blog": "Blog",
|
||||
"merchandise": "Marchandise",
|
||||
"twitter": "Twitter",
|
||||
"mastodon": "Mastodon",
|
||||
"matrix": "Matrix",
|
||||
"discord": "Discord",
|
||||
"reddit": "Reddit",
|
||||
"yourStorageDetailsCouldNotBeFetched": "Vos informations de stockage n'ont pas pu être récupérées",
|
||||
"reportABug": "Signaler un bug",
|
||||
"reportBug": "Signaler un bug",
|
||||
"suggestFeatures": "Suggérer des fonctionnalités",
|
||||
"support": "Support",
|
||||
"theme": "Thème",
|
||||
"lightTheme": "Clair",
|
||||
"darkTheme": "Sombre",
|
||||
"systemTheme": "Système",
|
||||
"freeTrial": "Essai gratuit",
|
||||
"selectYourPlan": "Sélectionner votre offre",
|
||||
"enteSubscriptionPitch": "ente conserve vos souvenirs, donc ils sont toujours disponibles pour vous, même si vous perdez votre appareil.",
|
||||
"enteSubscriptionShareWithFamily": "Vous pouvez également ajouter votre famille à votre forfait.",
|
||||
"currentUsageIs": "L'utilisation actuelle est ",
|
||||
"@currentUsageIs": {
|
||||
"description": "This text is followed by storage usaged",
|
||||
"examples": {
|
||||
"0": "Current usage is 1.2 GB"
|
||||
},
|
||||
"type": "text"
|
||||
},
|
||||
"faqs": "FAQ",
|
||||
"renewsOn": "Renouvellement le {endDate}",
|
||||
"subWillBeCancelledOn": "Votre abonnement sera annulé le {endDate}",
|
||||
"subscription": "Abonnement",
|
||||
"paymentDetails": "Détails de paiement",
|
||||
"manageFamily": "Gérer la famille",
|
||||
"contactToManageSubscription": "Veuillez nous contacter à support@ente.io pour gérer votre abonnement {provider}.",
|
||||
"renewSubscription": "Renouveler l’abonnement",
|
||||
"cancelSubscription": "Annuler l'abonnement",
|
||||
"areYouSureYouWantToRenew": "Êtes-vous sûr de vouloir renouveler ?",
|
||||
"yesRenew": "Oui, renouveler",
|
||||
"areYouSureYouWantToCancel": "Es-tu sûre de vouloir annuler?",
|
||||
"yesCancel": "Oui, annuler",
|
||||
"failedToRenew": "Échec du renouvellement",
|
||||
"failedToCancel": "Échec de l'annulation",
|
||||
"twoMonthsFreeOnYearlyPlans": "2 mois gratuits sur les forfaits annuels",
|
||||
"monthly": "Mensuel",
|
||||
"@monthly": {
|
||||
"description": "The text to display for monthly plans",
|
||||
"type": "text"
|
||||
},
|
||||
"yearly": "Annuel",
|
||||
"@yearly": {
|
||||
"description": "The text to display for yearly plans",
|
||||
"type": "text"
|
||||
},
|
||||
"confirmPlanChange": "Confirmer le changement de l'offre",
|
||||
"areYouSureYouWantToChangeYourPlan": "Êtes-vous certains de vouloir changer d'offre ?",
|
||||
"youCannotDowngradeToThisPlan": "Vous ne pouvez pas rétrograder vers cette offre",
|
||||
"cancelOtherSubscription": "Veuillez d'abord annuler votre abonnement existant de {paymentProvider}",
|
||||
"@cancelOtherSubscription": {
|
||||
"description": "The text to display when the user has an existing subscription from a different payment provider",
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"paymentProvider": {
|
||||
"example": "Apple",
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"optionalAsShortAsYouLike": "Optionnel, aussi court que vous le souhaitez...",
|
||||
"send": "Envoyer",
|
||||
"askCancelReason": "Votre abonnement a été annulé. Souhaitez-vous partager la raison ?",
|
||||
"thankYouForSubscribing": "Merci de vous être abonné !",
|
||||
"yourPurchaseWasSuccessful": "Votre achat a été effectué avec succès",
|
||||
"yourPlanWasSuccessfullyUpgraded": "Votre offre a été mise à jour avec succès",
|
||||
"yourPlanWasSuccessfullyDowngraded": "Votre plan a été rétrogradé avec succès",
|
||||
"yourSubscriptionWasUpdatedSuccessfully": "Votre abonnement a été mis à jour avec succès",
|
||||
"googlePlayId": "Identifiant Google Play",
|
||||
"appleId": "Apple ID",
|
||||
"playstoreSubscription": "Abonnement au PlayStore",
|
||||
"thankYou": "Merci",
|
||||
"pleaseWaitForSometimeBeforeRetrying": "Veuillez attendre quelque temps avant de réessayer",
|
||||
"usePublicLinksForPeopleNotOnEnte": "Utiliser des liens publics pour les personnes qui ne sont pas sur ente",
|
||||
"allowPeopleToAddPhotos": "Autoriser les personnes à ajouter des photos",
|
||||
"shareAnAlbumNow": "Partagez un album maintenant",
|
||||
"collectEventPhotos": "Collecter des photos de l'événement",
|
||||
"sessionExpired": "Session expirée",
|
||||
"loggingOut": "Deconnexion...",
|
||||
"name": "Nom",
|
||||
"newest": "Le plus récent",
|
||||
"lastUpdated": "Dernière mise à jour",
|
||||
"deleteEmptyAlbums": "Supprimer les albums vides",
|
||||
"deleteEmptyAlbumsWithQuestionMark": "Supprimer les albums vides ?",
|
||||
"deleteAlbumsDialogBody": "Ceci supprimera tous les albums vides. Ceci est utile lorsque vous voulez réduire l'encombrement dans votre liste d'albums.",
|
||||
"deleteProgress": "Suppression de {currentlyDeleting} / {totalCount}",
|
||||
"permanentlyDelete": "Supprimer définitivement",
|
||||
"canOnlyCreateLinkForFilesOwnedByYou": "Ne peut créer de lien que pour les fichiers que vous possédez",
|
||||
"publicLinkCreated": "Lien public créé",
|
||||
"youCanManageYourLinksInTheShareTab": "Vous pouvez gérer vos liens dans l'onglet Partage.",
|
||||
"linkCopiedToClipboard": "Lien copié dans le presse-papiers",
|
||||
"restore": "Restaurer",
|
||||
"@restore": {
|
||||
"description": "Display text for an action which triggers a restore of item from trash",
|
||||
"type": "text"
|
||||
},
|
||||
"moveToAlbum": "Déplacer vers l'album",
|
||||
"unhide": "Dévoiler",
|
||||
"unarchive": "Désarchiver",
|
||||
"favorite": "Favori",
|
||||
"removeFromFavorite": "Retirer des favoris",
|
||||
"shareLink": "Partager le lien",
|
||||
"addToEnte": "Ajouter à ente",
|
||||
"addToAlbum": "Ajouter à l'album",
|
||||
"delete": "Supprimer",
|
||||
"hide": "Masquer",
|
||||
"itemSelectedCount": "{count} sélectionné",
|
||||
"@itemSelectedCount": {
|
||||
"description": "Text to indicate number of items selected",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"example": "1|2|3",
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"share": "Partager",
|
||||
"unhideToAlbum": "Afficher dans l'album",
|
||||
"restoreToAlbum": "Restaurer vers l'album",
|
||||
"moveItem": "{count, plural, one {Déplacez l'objet} other {Déplacez des objets}}",
|
||||
"@moveItem": {
|
||||
"description": "Page title while moving one or more items to an album"
|
||||
},
|
||||
"addItem": "{count, plural, one {Ajoutez un objet} other {Ajoutez des objets}}",
|
||||
"@addItem": {
|
||||
"description": "Page title while adding one or more items to album"
|
||||
},
|
||||
"createOrSelectAlbum": "Créer ou sélectionner un album",
|
||||
"selectAlbum": "Sélectionner album",
|
||||
"searchByAlbumNameHint": "Nom de l'album",
|
||||
"albumTitle": "Titre de l'album",
|
||||
"enterAlbumName": "Saisir un nom d'album",
|
||||
"restoringFiles": "Restauration des fichiers...",
|
||||
"movingFilesToAlbum": "Déplacement des fichiers vers l'album...",
|
||||
"unhidingFilesToAlbum": "Démasquage des fichiers vers l'album",
|
||||
"canNotUploadToAlbumsOwnedByOthers": "Impossible de télécharger dans les albums appartenant à d'autres personnes",
|
||||
"uploadingFilesToAlbum": "Envoi des fichiers vers l'album...",
|
||||
"addedSuccessfullyTo": "Ajouté avec succès à {albumName}",
|
||||
"movedSuccessfullyTo": "Déplacé avec succès vers {albumName}",
|
||||
"thisAlbumAlreadyHDACollaborativeLink": "Cet album a déjà un lien collaboratif",
|
||||
"collaborativeLinkCreatedFor": "Lien collaboratif créé pour {albumName}",
|
||||
"askYourLovedOnesToShare": "Demandez à vos proches de partager",
|
||||
"sharedWith": "Partagé avec {emailIDs}",
|
||||
"sharedByMe": "Partagé par moi",
|
||||
"doubleYourStorage": "Doubler votre espace de stockage",
|
||||
"referFriendsAnd2xYourPlan": "Parrainez des amis et 2x votre abonnement",
|
||||
"shareAlbumHint": "Ouvrez un album et appuyez sur le bouton de partage en haut à droite pour le partager.",
|
||||
"itemsShowTheNumberOfDaysRemainingBeforePermanentDeletion": "Les éléments montrent le nombre de jours restants avant la suppression définitive",
|
||||
"deleteAll": "Tout Supprimer",
|
||||
"renameAlbum": "Renommer l'album",
|
||||
"rename": "Renommer",
|
||||
"leaveSharedAlbum": "Quitter l'album partagé?",
|
||||
"leaveAlbum": "Quitter l'album",
|
||||
"photosAddedByYouWillBeRemovedFromTheAlbum": "Les photos ajoutées par vous seront retirées de l'album",
|
||||
"youveNoFilesInThisAlbumThatCanBeDeleted": "Vous n'avez pas de fichiers dans cet album qui peuvent être supprimés",
|
||||
"ignoredFolderUploadReason": "Certains fichiers de cet album sont ignorés parce qu'ils avaient été précédemment supprimés de ente.",
|
||||
"resetIgnoredFiles": "Réinitialiser les fichiers ignorés",
|
||||
"exif": "EXIF",
|
||||
"noResults": "Aucun résultat",
|
||||
"weDontSupportEditingPhotosAndAlbumsThatYouDont": "Nous ne prenons pas en charge l'édition des photos et des albums que vous ne possédez pas encore",
|
||||
"failedToFetchOriginalForEdit": "Impossible de récupérer l'original pour l'édition",
|
||||
"close": "Fermer",
|
||||
"setAs": "Définir comme",
|
||||
"fileSavedToGallery": "Fichier enregistré dans la galerie",
|
||||
"download": "Télécharger",
|
||||
"pressAndHoldToPlayVideo": "Appuyez et maintenez enfoncé pour lire la vidéo",
|
||||
"downloadFailed": "Échec du téléchargement",
|
||||
"deduplicateFiles": "Déduplication de fichiers",
|
||||
"deselectAll": "Tout déselectionner",
|
||||
"reviewDeduplicateItems": "Veuillez vérifier et supprimer les éléments que vous croyez dupliqués.",
|
||||
"clubByCaptureTime": "Durée du Club par capture",
|
||||
"totalSize": "Taille totale",
|
||||
"emailChangedTo": "L'e-mail a été changé en {newEmail}",
|
||||
"verifying": "Validation en cours...",
|
||||
"disablingTwofactorAuthentication": "Désactiver la double-authentification...",
|
||||
"allMemoriesPreserved": "Tous les souvenirs conservés",
|
||||
"loadingGallery": "Chargement de la galerie...",
|
||||
"syncing": "En cours de synchronisation...",
|
||||
"encryptingBackup": "Chiffrement de la sauvegarde...",
|
||||
"syncStopped": "Synchronisation arrêtée ?",
|
||||
"syncProgress": "{completed}/{total} souvenirs préservés",
|
||||
"@syncProgress": {
|
||||
"description": "Text to tell user how many memories have been preserved",
|
||||
"placeholders": {
|
||||
"completed": {
|
||||
"type": "int"
|
||||
},
|
||||
"total": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiving": "Archivage en cours...",
|
||||
"unarchiving": "Désarchivage en cours...",
|
||||
"successfullyArchived": "Archivé avec succès",
|
||||
"successfullyUnarchived": "Désarchivé avec succès",
|
||||
"renameFile": "Renommer le fichier",
|
||||
"enterFileName": "Entrez le nom du fichier",
|
||||
"filesDeleted": "Fichiers supprimés",
|
||||
"selectedFilesAreNotOnEnte": "Les fichiers sélectionnés ne sont pas sur ente",
|
||||
"thisActionCannotBeUndone": "Cette action ne peut pas être annulée",
|
||||
"emptyTrash": "Vider la corbeille ?",
|
||||
"permDeleteWarning": "Tous les éléments de la corbeille seront définitivement supprimés\n\nCette action ne peut pas être annulée",
|
||||
"empty": "Vide",
|
||||
"couldNotFreeUpSpace": "Impossible de libérer de l'espace",
|
||||
"permanentlyDeleteFromDevice": "Supprimer définitivement de l'appareil ?",
|
||||
"someOfTheFilesYouAreTryingToDeleteAre": "Certains des fichiers que vous essayez de supprimer ne sont disponibles que sur votre appareil et ne peuvent pas être récupérés s'ils sont supprimés",
|
||||
"theyWillBeDeletedFromAllAlbums": "Ils seront supprimés de tous les albums.",
|
||||
"someItemsAreInBothEnteAndYourDevice": "Certains éléments sont à la fois dans ente et votre appareil.",
|
||||
"selectedItemsWillBeDeletedFromAllAlbumsAndMoved": "Les éléments sélectionnés seront supprimés de tous les albums et déplacés dans la corbeille.",
|
||||
"theseItemsWillBeDeletedFromYourDevice": "Ces éléments seront supprimés de votre appareil.",
|
||||
"itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "Il semble qu'une erreur s'est produite. Veuillez réessayer après un certain temps. Si l'erreur persiste, veuillez contacter notre équipe d'assistance.",
|
||||
"error": "Erreur",
|
||||
"tempErrorContactSupportIfPersists": "Il semble qu'une erreur s'est produite. Veuillez réessayer après un certain temps. Si l'erreur persiste, veuillez contacter notre équipe d'assistance.",
|
||||
"cachedData": "Données mises en cache",
|
||||
"clearCaches": "Nettoyer le cache",
|
||||
"remoteImages": "Images distantes",
|
||||
"remoteVideos": "Vidéos distantes",
|
||||
"remoteThumbnails": "Miniatures distantes",
|
||||
"pendingSync": "Synchronisation en attente",
|
||||
"localGallery": "Galerie locale",
|
||||
"todaysLogs": "Journaux du jour",
|
||||
"viewLogs": "Afficher les journaux",
|
||||
"logsDialogBody": "Cela enverra des logs pour nous aider à déboguer votre problème. Veuillez noter que les noms de fichiers seront inclus pour aider à suivre les problèmes avec des fichiers spécifiques.",
|
||||
"preparingLogs": "Préparation des journaux...",
|
||||
"emailYourLogs": "Envoyez vos logs par e-mail",
|
||||
"pleaseSendTheLogsTo": "Envoyez les logs à {toEmail}",
|
||||
"copyEmailAddress": "Copier l’adresse e-mail",
|
||||
"exportLogs": "Exporter les logs",
|
||||
"pleaseEmailUsAt": "Merci de nous envoyer un e-mail à {toEmail}",
|
||||
"dismiss": "Rejeter",
|
||||
"didYouKnow": "Le savais-tu ?",
|
||||
"loadingMessage": "Chargement de vos photos...",
|
||||
"language": "Langue"
|
||||
}
|
|
@ -96,6 +96,7 @@
|
|||
"pleaseTryAgain": "Probeer het nog eens",
|
||||
"recreatePasswordTitle": "Wachtwoord opnieuw instellen",
|
||||
"useRecoveryKey": "Herstelcode gebruiken",
|
||||
"recreatePasswordBody": "Het huidige apparaat is niet krachtig genoeg om je wachtwoord te verifiëren, dus moeten we de code een keer opnieuw genereren op een manier die met alle apparaten werkt.\n\nLog in met behulp van uw herstelcode en genereer opnieuw uw wachtwoord (je kunt dezelfde indien gewenst opnieuw gebruiken).",
|
||||
"verifyPassword": "Bevestig wachtwoord",
|
||||
"recoveryKey": "Herstelsleutel",
|
||||
"recoveryKeyOnForgotPassword": "Als je je wachtwoord vergeet, kun je alleen met deze sleutel je gegevens herstellen.",
|
||||
|
@ -132,6 +133,7 @@
|
|||
"verifyingRecoveryKey": "Herstelsleutel verifiëren...",
|
||||
"recoveryKeyVerified": "Herstel sleutel geverifieerd",
|
||||
"recoveryKeySuccessBody": "Super! Je herstelsleutel is geldig. Bedankt voor het verifiëren.\n\nVergeet niet om je herstelsleutel veilig te bewaren.",
|
||||
"invalidRecoveryKey": "De herstelsleutel die je hebt ingevoerd is niet geldig. Zorg ervoor dat deze 24 woorden bevat en controleer de spelling van elk van deze woorden.\n\nAls je een oudere herstelcode hebt ingevoerd, zorg ervoor dat deze 64 tekens lang is, en controleer ze allemaal.",
|
||||
"invalidKey": "Ongeldige sleutel",
|
||||
"tryAgain": "Probeer opnieuw",
|
||||
"viewRecoveryKey": "Toon herstelsleutel",
|
||||
|
@ -216,6 +218,38 @@
|
|||
"copyLink": "Kopieer link",
|
||||
"linkHasExpired": "Link is vervallen",
|
||||
"publicLinkEnabled": "Publieke link ingeschakeld",
|
||||
"shareALink": "Deel een link",
|
||||
"sharedAlbumSectionDescription": "Maak gedeelde en collaboratieve albums met andere ente gebruikers, inclusief gebruikers met gratis abonnementen.",
|
||||
"shareWithPeopleSectionTitle": "{numberOfPeople, plural, =0 {Deel met specifieke mensen} =1 {Gedeeld met 1 persoon} other {Gedeeld met {numberOfPeople} mensen}}",
|
||||
"@shareWithPeopleSectionTitle": {
|
||||
"placeholders": {
|
||||
"numberOfPeople": {
|
||||
"type": "int",
|
||||
"example": "2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"thisIsYourVerificationId": "Dit is uw verificatie-ID",
|
||||
"someoneSharingAlbumsWithYouShouldSeeTheSameId": "Iemand die albums met je deelt zou hetzelfde ID op hun apparaat moeten zien.",
|
||||
"howToViewShareeVerificationID": "Vraag hen om hun e-mailadres lang in te drukken op het instellingenscherm en te controleren dat de ID's op beide apparaten overeenkomen.",
|
||||
"thisIsPersonVerificationId": "Dit is de verificatie-ID van {email}",
|
||||
"@thisIsPersonVerificationId": {
|
||||
"placeholders": {
|
||||
"email": {
|
||||
"type": "String",
|
||||
"example": "someone@ente.io"
|
||||
}
|
||||
}
|
||||
},
|
||||
"verificationId": "Verificatie ID",
|
||||
"verifyEmailID": "Verifieer {email}",
|
||||
"emailNoEnteAccount": "{email} heeft geen ente account.\n\nStuur ze een uitnodiging om foto's te delen.",
|
||||
"shareMyVerificationID": "Hier is mijn verificatie-ID: {verificationID} voor ente.io.",
|
||||
"shareTextConfirmOthersVerificationID": "Hey, kunt u bevestigen dat dit uw ente.io verificatie-ID is: {verificationID}",
|
||||
"somethingWentWrong": "Er ging iets mis",
|
||||
"sendInvite": "Stuur een uitnodiging",
|
||||
"done": "Voltooid",
|
||||
"applyCodeTitle": "Code toepassen",
|
||||
"removeShareItemsWarning": "Sommige van de items die je verwijdert zijn door andere mensen toegevoegd, en je verliest de toegang daartoe",
|
||||
"addingToFavorites": "Toevoegen aan favorieten...",
|
||||
"removingFromFavorites": "Verwijderen uit favorieten...",
|
||||
|
@ -340,5 +374,7 @@
|
|||
"updateAvailable": "Update beschikbaar",
|
||||
"downloading": "Downloaden...",
|
||||
"theDownloadCouldNotBeCompleted": "De download kon niet worden voltooid",
|
||||
"retry": "Opnieuw"
|
||||
"retry": "Opnieuw",
|
||||
"noDuplicates": "✨ Geen duplicaten",
|
||||
"sparkleSuccess": "✨ Succes"
|
||||
}
|
|
@ -1 +1,65 @@
|
|||
{}
|
||||
{
|
||||
"enterYourEmailAddress": "Podaj swój adres e-mail",
|
||||
"accountWelcomeBack": "Witaj ponownie!",
|
||||
"email": "Adres e-mail",
|
||||
"cancel": "Anuluj",
|
||||
"verify": "Weryfikuj",
|
||||
"invalidEmailAddress": "Nieprawidłowy adres e-mail",
|
||||
"enterValidEmail": "Podaj poprawny adres e-mail.",
|
||||
"deleteAccount": "Usuń konto",
|
||||
"askDeleteReason": "Jaka jest przyczyna usunięcia konta?",
|
||||
"deleteAccountFeedbackPrompt": "Przykro nam, że odchodzisz. Wyjaśnij nam, dlaczego nas opuszczasz, aby pomóc ulepszać nasze usługi.",
|
||||
"feedback": "Informacja zwrotna",
|
||||
"kindlyHelpUsWithThisInformation": "Pomóż nam z tą informacją",
|
||||
"confirmDeletePrompt": "Tak, chcę trwale usunąć konto i wszystkie dane z nim powiązane.",
|
||||
"confirmAccountDeletion": "Potwierdź usunięcie konta",
|
||||
"deleteConfirmDialogBody": "Zamierzasz trwale usunąć swoje konto i wszystkie jego dane.\nTa akcja jest nieodwracalna.",
|
||||
"deleteAccountPermanentlyButton": "Usuń konto na stałe",
|
||||
"yourAccountHasBeenDeleted": "Twoje konto zostało usunięte",
|
||||
"selectReason": "Wybierz powód",
|
||||
"deleteReason1": "Brakuje kluczowej funkcji, której potrzebuję",
|
||||
"deleteReason2": "Aplikacja lub określona funkcja nie \nzachowuje się tak, jak sądzę, że powinna",
|
||||
"deleteReason3": "Znalazłem inną, lepszą usługę",
|
||||
"deleteReason4": "Inna, niewymieniona wyżej przyczyna",
|
||||
"sendEmail": "Wyślij e-mail",
|
||||
"deleteRequestSLAText": "Twoje żądanie zostanie przetworzone w ciągu 72 godzin.",
|
||||
"deleteEmailRequest": "Wyślij wiadomość e-mail na <warning>account-deletion@ente.io</warning> z zarejestrowanego adresu e-mail.",
|
||||
"ok": "Ok",
|
||||
"createAccount": "Stwórz konto",
|
||||
"createNewAccount": "Stwórz nowe konto",
|
||||
"password": "Hasło",
|
||||
"confirmPassword": "Powtórz hasło",
|
||||
"activeSessions": "Aktywne sesje",
|
||||
"oops": "Ups",
|
||||
"somethingWentWrongPleaseTryAgain": "Coś poszło nie tak, spróbuj ponownie",
|
||||
"thisWillLogYouOutOfThisDevice": "To wyloguje Cię z tego urządzenia!",
|
||||
"thisWillLogYouOutOfTheFollowingDevice": "To wyloguje Cię z tego urządzenia:",
|
||||
"terminateSession": "Zakończyć sesję?",
|
||||
"terminate": "Zakończ",
|
||||
"thisDevice": "To urządzenie",
|
||||
"recoverButton": "Odzyskaj",
|
||||
"recoverySuccessful": "Odzyskano pomyślnie!",
|
||||
"decrypting": "Odszyfrowywanie...",
|
||||
"incorrectRecoveryKeyTitle": "Nieprawidłowy klucz odzyskiwania",
|
||||
"incorrectRecoveryKeyBody": "Kod jest nieprawidłowy",
|
||||
"forgotPassword": "Nie pamiętam hasła",
|
||||
"enterYourRecoveryKey": "Wprowadź swój klucz odzyskiwania",
|
||||
"noRecoveryKey": "Brak klucza odzyskiwania?",
|
||||
"sorry": "Przepraszamy",
|
||||
"noRecoveryKeyNoDecryption": "Ze względu na charakter naszego protokołu szyfrowania end-to-end, dane nie mogą być odszyfrowane bez hasła lub klucza odzyskiwania",
|
||||
"verifyEmail": "Zweryfikuj adres e-mail",
|
||||
"checkInboxAndSpamFolder": "Sprawdź swoją skrzynkę odbiorczą (i spam), aby zakończyć weryfikację",
|
||||
"resetPasswordTitle": "Zresetuj hasło",
|
||||
"weakStrength": "Słabe",
|
||||
"strongStrength": "Silne",
|
||||
"pleaseWait": "Proszę czekać...",
|
||||
"continueLabel": "Kontynuuj",
|
||||
"termsOfServicesTitle": "Regulamin",
|
||||
"logInLabel": "Zaloguj się",
|
||||
"enterYourPassword": "Wprowadź hasło",
|
||||
"welcomeBack": "Witaj ponownie!",
|
||||
"doThisLater": "Spróbuj później",
|
||||
"enterCode": "Wprowadź kod",
|
||||
"confirm": "Potwierdź",
|
||||
"tryAgain": "Spróbuj ponownie"
|
||||
}
|
|
@ -1 +1,112 @@
|
|||
{}
|
||||
{
|
||||
"enterYourEmailAddress": "Insira o seu endereço de email",
|
||||
"accountWelcomeBack": "Bem-vindo de volta!",
|
||||
"cancel": "Cancelar",
|
||||
"verify": "Verificar",
|
||||
"invalidEmailAddress": "Endereço de email invalido",
|
||||
"enterValidEmail": "Por, favor insira um endereço de email válido.",
|
||||
"deleteAccount": "Deletar conta",
|
||||
"askDeleteReason": "Qual é o principal motivo para você excluir sua conta?",
|
||||
"deleteAccountFeedbackPrompt": "Lamentamos ver você partir. Por favor, compartilhe seus comentários para nos ajudar a melhorar.",
|
||||
"feedback": "Opinião",
|
||||
"kindlyHelpUsWithThisInformation": "Ajude-nos com esta informação",
|
||||
"confirmDeletePrompt": "Sim, desejo excluir permanentemente esta conta e todos os seus dados.",
|
||||
"confirmAccountDeletion": "Confirmar exclusão da conta",
|
||||
"deleteConfirmDialogBody": "Você está prestes a excluir permanentemente sua conta e todos os seus dados.\nEsta ação é irreversível.",
|
||||
"deleteAccountPermanentlyButton": "Excluir conta permanentemente",
|
||||
"yourAccountHasBeenDeleted": "Sua conta foi deletada",
|
||||
"selectReason": "Selecione o motivo",
|
||||
"deleteReason1": "Está faltando um recurso-chave que eu preciso",
|
||||
"deleteReason2": "O aplicativo ou um determinado recurso não\nestá funcionando como eu acredito que deveria",
|
||||
"deleteReason3": "Encontrei outro serviço que gosto mais",
|
||||
"deleteReason4": "Meu motivo não está listado",
|
||||
"sendEmail": "Enviar email",
|
||||
"deleteRequestSLAText": "Sua solicitação será processada em até 72 horas.",
|
||||
"deleteEmailRequest": "Por favor, envie um email para <warning>account-deletion@ente.io</warning> a partir do seu endereço de email registrado.",
|
||||
"createAccount": "Criar uma conta",
|
||||
"createNewAccount": "Criar nova conta",
|
||||
"password": "Senha",
|
||||
"confirmPassword": "Confirme sua senha",
|
||||
"activeSessions": "Sessões ativas",
|
||||
"oops": "Ops",
|
||||
"somethingWentWrongPleaseTryAgain": "Algo deu errado. Por favor, tente outra vez",
|
||||
"thisWillLogYouOutOfThisDevice": "Isso fará com que você saia deste dispositivo!",
|
||||
"thisWillLogYouOutOfTheFollowingDevice": "Isso fará com que você saia do seguinte dispositivo:",
|
||||
"terminateSession": "Encerrar sessão?",
|
||||
"terminate": "Terminar",
|
||||
"thisDevice": "Este aparelho",
|
||||
"recoverButton": "Recuperar",
|
||||
"recoverySuccessful": "Recuperação bem sucedida!",
|
||||
"decrypting": "Descriptografando...",
|
||||
"incorrectRecoveryKeyTitle": "Chave de recuperação incorreta",
|
||||
"incorrectRecoveryKeyBody": "A chave de recuperação que você digitou está incorreta",
|
||||
"forgotPassword": "Esqueceu sua senha",
|
||||
"enterYourRecoveryKey": "Digite sua chave de recuperação",
|
||||
"noRecoveryKey": "Nenhuma chave de recuperação?",
|
||||
"sorry": "Desculpe",
|
||||
"noRecoveryKeyNoDecryption": "Devido à natureza do nosso protocolo de criptografia de ponta a ponta, seus dados não podem ser descriptografados sem sua senha ou chave de recuperação",
|
||||
"verifyEmail": "Verificar email",
|
||||
"checkInboxAndSpamFolder": "Verifique sua caixa de entrada (e ‘spam’) para concluir a verificação",
|
||||
"enterCode": "Coloque o código",
|
||||
"scanCode": "Escanear código",
|
||||
"codeCopiedToClipboard": "Código copiado para a área de transferência",
|
||||
"copypasteThisCodentoYourAuthenticatorApp": "Copie e cole este código\npara seu aplicativo autenticador",
|
||||
"tapToCopy": "toque para copiar",
|
||||
"scanThisBarcodeWithnyourAuthenticatorApp": "Escaneie este código de barras com\nseu aplicativo autenticador",
|
||||
"enterThe6digitCodeFromnyourAuthenticatorApp": "Digite o código de 6 dígitos de\nseu aplicativo autenticador",
|
||||
"confirm": "Confirme",
|
||||
"setupComplete": "Configuração concluída",
|
||||
"saveYourRecoveryKeyIfYouHaventAlready": "Salve sua chave de recuperação, caso ainda não o tenha feito",
|
||||
"thisCanBeUsedToRecoverYourAccountIfYou": "Isso pode ser usado para recuperar sua conta se você perder seu segundo fator",
|
||||
"twofactorAuthenticationPageTitle": "Autenticação de dois fatores",
|
||||
"lostDevice": "Dispositivo perdido?",
|
||||
"verifyingRecoveryKey": "Verificando chave de recuperação...",
|
||||
"recoveryKeyVerified": "Chave de recuperação verificada",
|
||||
"recoveryKeySuccessBody": "Ótimo! Sua chave de recuperação é válida. Obrigado por verificar.\n\nLembre-se de manter o backup seguro de sua chave de recuperação.",
|
||||
"invalidRecoveryKey": "A chave de recuperação que você digitou não é válida. Certifique-se de que contém 24 palavras e verifique a ortografia de cada uma.\n\nSe você inseriu um código de recuperação mais antigo, verifique se ele tem 64 caracteres e verifique cada um deles.",
|
||||
"invalidKey": "Chave inválida",
|
||||
"tryAgain": "Tente novamente",
|
||||
"viewRecoveryKey": "Ver chave de recuperação",
|
||||
"confirmRecoveryKey": "Confirme a chave de recuperação",
|
||||
"recoveryKeyVerifyReason": "Sua chave de recuperação é a única maneira de recuperar suas fotos se você esquecer sua senha. Você pode encontrar sua chave de recuperação em Configurações > Conta.\n\nDigite sua chave de recuperação aqui para verificar se você a salvou corretamente.",
|
||||
"confirmYourRecoveryKey": "Confirme sua chave de recuperação",
|
||||
"addViewer": "Adicionar visualizador",
|
||||
"addCollaborator": "Adicionar colaborador",
|
||||
"addANewEmail": "Adicionar um novo email",
|
||||
"orPickAnExistingOne": "Ou escolha um existente",
|
||||
"collaboratorsCanAddPhotosAndVideosToTheSharedAlbum": "Os colaboradores podem adicionar fotos e vídeos ao álbum compartilhado.",
|
||||
"enterEmail": "Digite o email",
|
||||
"albumOwner": "Proprietário",
|
||||
"@albumOwner": {
|
||||
"description": "Role of the album owner"
|
||||
},
|
||||
"you": "Você",
|
||||
"collaborator": "Colaborador",
|
||||
"addMore": "Adicione mais",
|
||||
"@addMore": {
|
||||
"description": "Button text to add more collaborators/viewers"
|
||||
},
|
||||
"viewer": "Visualizador",
|
||||
"remove": "Remover",
|
||||
"removeParticipant": "Remover participante",
|
||||
"@removeParticipant": {
|
||||
"description": "menuSectionTitle for removing a participant"
|
||||
},
|
||||
"manage": "Gerenciar",
|
||||
"addedAs": "Adicionado como",
|
||||
"changePermissions": "Alterar permissões?",
|
||||
"yesConvertToViewer": "Sim, converter para visualizador",
|
||||
"cannotAddMorePhotosAfterBecomingViewer": "{user} Não poderá adicionar mais fotos a este álbum\n\nEles ainda poderão remover as fotos existentes adicionadas por eles",
|
||||
"allowAddingPhotos": "Permitir adicionar fotos",
|
||||
"@allowAddingPhotos": {
|
||||
"description": "Switch button to enable uploading photos to a public link"
|
||||
},
|
||||
"allowAddPhotosDescription": "Permita que as pessoas com o link também adicionem fotos ao álbum compartilhado.",
|
||||
"passwordLock": "Bloqueio de senha",
|
||||
"disableDownloadWarningTitle": "Observe",
|
||||
"disableDownloadWarningBody": "Os espectadores ainda podem tirar screenshots ou salvar uma cópia de suas fotos usando ferramentas externas",
|
||||
"allowDownloads": "Permitir transferências",
|
||||
"linkDeviceLimit": "Limite do dispositivo",
|
||||
"linkExpiry": "Expiração do link",
|
||||
"linkExpired": "Expirado"
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
import "package:flutter/foundation.dart";
|
||||
import "package:flutter/widgets.dart";
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import "package:shared_preferences/shared_preferences.dart";
|
||||
|
||||
extension AppLocalizationsX on BuildContext {
|
||||
S get l10n => S.of(this);
|
||||
|
@ -8,9 +10,11 @@ extension AppLocalizationsX on BuildContext {
|
|||
// list of locales which are enabled for auth app.
|
||||
// Add more language to the list only when at least 90% of the strings are
|
||||
// translated in the corresponding language.
|
||||
const List<Locale> appSupportedLocales = <Locale>[
|
||||
Locale('en'),
|
||||
];
|
||||
const List<Locale> appSupportedLocales = kDebugMode
|
||||
? <Locale>[Locale('en'), Locale('fr'), Locale("nl")]
|
||||
: <Locale>[
|
||||
Locale('en'),
|
||||
];
|
||||
|
||||
Locale localResolutionCallBack(locales, supportedLocales) {
|
||||
for (Locale locale in locales) {
|
||||
|
@ -21,3 +25,21 @@ Locale localResolutionCallBack(locales, supportedLocales) {
|
|||
// if device language is not supported by the app, use en as default
|
||||
return const Locale('en');
|
||||
}
|
||||
|
||||
Future<Locale> getLocale() async {
|
||||
final String? savedLocale =
|
||||
(await SharedPreferences.getInstance()).getString('locale');
|
||||
if (savedLocale != null &&
|
||||
appSupportedLocales.contains(Locale(savedLocale))) {
|
||||
return Locale(savedLocale);
|
||||
}
|
||||
return const Locale('en');
|
||||
}
|
||||
|
||||
Future<void> setLocale(Locale locale) async {
|
||||
if (!appSupportedLocales.contains(locale)) {
|
||||
throw Exception('Locale $locale is not supported by the app');
|
||||
}
|
||||
await (await SharedPreferences.getInstance())
|
||||
.setString('locale', locale.languageCode);
|
||||
}
|
||||
|
|
|
@ -17,13 +17,16 @@ import 'package:photos/core/errors.dart';
|
|||
import 'package:photos/core/network/network.dart';
|
||||
import 'package:photos/db/upload_locks_db.dart';
|
||||
import 'package:photos/ente_theme_data.dart';
|
||||
import "package:photos/l10n/l10n.dart";
|
||||
import 'package:photos/services/app_lifecycle_service.dart';
|
||||
import 'package:photos/services/billing_service.dart';
|
||||
import 'package:photos/services/collections_service.dart';
|
||||
import "package:photos/services/entity_service.dart";
|
||||
import 'package:photos/services/favorites_service.dart';
|
||||
import 'package:photos/services/feature_flag_service.dart';
|
||||
import 'package:photos/services/local_file_update_service.dart';
|
||||
import 'package:photos/services/local_sync_service.dart';
|
||||
import "package:photos/services/location_service.dart";
|
||||
import 'package:photos/services/memories_service.dart';
|
||||
import 'package:photos/services/notification_service.dart';
|
||||
import "package:photos/services/object_detection/object_detection_service.dart";
|
||||
|
@ -67,13 +70,15 @@ Future<void> _runInForeground(AdaptiveThemeMode? savedThemeMode) async {
|
|||
return await _runWithLogs(() async {
|
||||
_logger.info("Starting app in foreground");
|
||||
await _init(false, via: 'mainMethod');
|
||||
final Locale locale = await getLocale();
|
||||
unawaited(_scheduleFGSync('appStart in FG'));
|
||||
runApp(
|
||||
AppLock(
|
||||
builder: (args) =>
|
||||
EnteApp(_runBackgroundTask, _killBGTask, savedThemeMode),
|
||||
EnteApp(_runBackgroundTask, _killBGTask, locale, savedThemeMode),
|
||||
lockScreen: const LockScreen(),
|
||||
enabled: Configuration.instance.shouldShowLockScreen(),
|
||||
locale: locale,
|
||||
lightTheme: lightThemeData,
|
||||
darkTheme: darkThemeData,
|
||||
backgroundLockLatency: kBackgroundLockLatency,
|
||||
|
@ -153,6 +158,9 @@ Future<void> _init(bool isBackground, {String via = ''}) async {
|
|||
await NetworkClient.instance.init();
|
||||
await Configuration.instance.init();
|
||||
await UserService.instance.init();
|
||||
await EntityService.instance.init();
|
||||
LocationService.instance.init(preferences);
|
||||
|
||||
await UserRemoteFlagService.instance.init();
|
||||
await UpdateService.instance.init();
|
||||
BillingService.instance.init();
|
||||
|
|
55
lib/models/api/entity/data.dart
Normal file
55
lib/models/api/entity/data.dart
Normal file
|
@ -0,0 +1,55 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@immutable
|
||||
class EntityData {
|
||||
final String id;
|
||||
|
||||
// encryptedData will be null for diff items when item is deleted
|
||||
final String? encryptedData;
|
||||
final String? header;
|
||||
final bool isDeleted;
|
||||
final int createdAt;
|
||||
final int updatedAt;
|
||||
final int userID;
|
||||
|
||||
const EntityData(
|
||||
this.id,
|
||||
this.userID,
|
||||
this.encryptedData,
|
||||
this.header,
|
||||
this.isDeleted,
|
||||
this.createdAt,
|
||||
this.updatedAt,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'id': id,
|
||||
'userID': userID,
|
||||
'encryptedData': encryptedData,
|
||||
'header': header,
|
||||
'isDeleted': isDeleted,
|
||||
'createdAt': createdAt,
|
||||
'updatedAt': updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
factory EntityData.fromMap(Map<String, dynamic> map) {
|
||||
return EntityData(
|
||||
map['id'],
|
||||
map['userID'],
|
||||
map['encryptedData'],
|
||||
map['header'],
|
||||
map['isDeleted']!,
|
||||
map['createdAt']!,
|
||||
map['updatedAt']!,
|
||||
);
|
||||
}
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory EntityData.fromJson(String source) =>
|
||||
EntityData.fromMap(json.decode(source));
|
||||
}
|
46
lib/models/api/entity/key.dart
Normal file
46
lib/models/api/entity/key.dart
Normal file
|
@ -0,0 +1,46 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import "package:photos/models/api/entity/type.dart";
|
||||
|
||||
@immutable
|
||||
class EntityKey {
|
||||
final int userID;
|
||||
final String encryptedKey;
|
||||
final EntityType type;
|
||||
final String header;
|
||||
final int createdAt;
|
||||
|
||||
const EntityKey(
|
||||
this.userID,
|
||||
this.encryptedKey,
|
||||
this.header,
|
||||
this.createdAt,
|
||||
this.type,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'userID': userID,
|
||||
'type': type.typeToString(),
|
||||
'encryptedKey': encryptedKey,
|
||||
'header': header,
|
||||
'createdAt': createdAt,
|
||||
};
|
||||
}
|
||||
|
||||
factory EntityKey.fromMap(Map<String, dynamic> map) {
|
||||
return EntityKey(
|
||||
map['userID']?.toInt() ?? 0,
|
||||
map['encryptedKey']!,
|
||||
map['header']!,
|
||||
map['createdAt']?.toInt() ?? 0,
|
||||
typeFromString(map['type']!),
|
||||
);
|
||||
}
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory EntityKey.fromJson(String source) =>
|
||||
EntityKey.fromMap(json.decode(source));
|
||||
}
|
26
lib/models/api/entity/type.dart
Normal file
26
lib/models/api/entity/type.dart
Normal file
|
@ -0,0 +1,26 @@
|
|||
import "package:flutter/foundation.dart";
|
||||
|
||||
enum EntityType {
|
||||
location,
|
||||
unknown,
|
||||
}
|
||||
|
||||
EntityType typeFromString(String type) {
|
||||
switch (type) {
|
||||
case "location":
|
||||
return EntityType.location;
|
||||
}
|
||||
debugPrint("unexpected collection type $type");
|
||||
return EntityType.unknown;
|
||||
}
|
||||
|
||||
extension EntityTypeExtn on EntityType {
|
||||
String typeToString() {
|
||||
switch (this) {
|
||||
case EntityType.location:
|
||||
return "location";
|
||||
case EntityType.unknown:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,7 +8,7 @@ import 'package:photos/core/configuration.dart';
|
|||
import 'package:photos/core/constants.dart';
|
||||
import 'package:photos/models/ente_file.dart';
|
||||
import 'package:photos/models/file_type.dart';
|
||||
import 'package:photos/models/location.dart';
|
||||
import 'package:photos/models/location/location.dart';
|
||||
import 'package:photos/models/magic_metadata.dart';
|
||||
import 'package:photos/services/feature_flag_service.dart';
|
||||
import 'package:photos/utils/date_time_util.dart';
|
||||
|
@ -72,7 +72,8 @@ class File extends EnteFile {
|
|||
file.localID = asset.id;
|
||||
file.title = asset.title;
|
||||
file.deviceFolder = pathName;
|
||||
file.location = Location(asset.latitude, asset.longitude);
|
||||
file.location =
|
||||
Location(latitude: asset.latitude, longitude: asset.longitude);
|
||||
file.fileType = _fileTypeFromAsset(asset);
|
||||
file.creationTime = parseFileCreationTime(file.title, asset);
|
||||
file.modificationTime = asset.modifiedDateTime.microsecondsSinceEpoch;
|
||||
|
@ -147,7 +148,7 @@ class File extends EnteFile {
|
|||
if (latitude == null || longitude == null) {
|
||||
location = null;
|
||||
} else {
|
||||
location = Location(latitude, longitude);
|
||||
location = Location(latitude: latitude, longitude: longitude);
|
||||
}
|
||||
fileType = getFileType(metadata["fileType"] ?? -1);
|
||||
fileSubType = metadata["subType"] ?? -1;
|
||||
|
|
|
@ -9,7 +9,8 @@ enum GalleryType {
|
|||
// indicator for gallery view of collections shared with the user
|
||||
sharedCollection,
|
||||
ownedCollection,
|
||||
searchResults
|
||||
searchResults,
|
||||
locationTag,
|
||||
}
|
||||
|
||||
extension GalleyTypeExtension on GalleryType {
|
||||
|
@ -21,6 +22,7 @@ extension GalleyTypeExtension on GalleryType {
|
|||
case GalleryType.ownedCollection:
|
||||
case GalleryType.searchResults:
|
||||
case GalleryType.favorite:
|
||||
case GalleryType.locationTag:
|
||||
return true;
|
||||
|
||||
case GalleryType.hidden:
|
||||
|
@ -45,6 +47,7 @@ extension GalleyTypeExtension on GalleryType {
|
|||
case GalleryType.homepage:
|
||||
case GalleryType.trash:
|
||||
case GalleryType.sharedCollection:
|
||||
case GalleryType.locationTag:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -59,6 +62,7 @@ extension GalleyTypeExtension on GalleryType {
|
|||
case GalleryType.favorite:
|
||||
case GalleryType.localFolder:
|
||||
case GalleryType.uncategorized:
|
||||
case GalleryType.locationTag:
|
||||
return true;
|
||||
case GalleryType.trash:
|
||||
case GalleryType.archive:
|
||||
|
@ -78,6 +82,7 @@ extension GalleyTypeExtension on GalleryType {
|
|||
case GalleryType.archive:
|
||||
case GalleryType.hidden:
|
||||
case GalleryType.localFolder:
|
||||
case GalleryType.locationTag:
|
||||
return true;
|
||||
case GalleryType.trash:
|
||||
case GalleryType.sharedCollection:
|
||||
|
@ -93,6 +98,7 @@ extension GalleyTypeExtension on GalleryType {
|
|||
case GalleryType.favorite:
|
||||
case GalleryType.archive:
|
||||
case GalleryType.uncategorized:
|
||||
case GalleryType.locationTag:
|
||||
return true;
|
||||
case GalleryType.hidden:
|
||||
case GalleryType.localFolder:
|
||||
|
@ -115,6 +121,7 @@ extension GalleyTypeExtension on GalleryType {
|
|||
case GalleryType.archive:
|
||||
case GalleryType.localFolder:
|
||||
case GalleryType.trash:
|
||||
case GalleryType.locationTag:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -133,6 +140,7 @@ extension GalleyTypeExtension on GalleryType {
|
|||
case GalleryType.localFolder:
|
||||
case GalleryType.trash:
|
||||
case GalleryType.sharedCollection:
|
||||
case GalleryType.locationTag:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -148,6 +156,7 @@ extension GalleyTypeExtension on GalleryType {
|
|||
case GalleryType.searchResults:
|
||||
case GalleryType.archive:
|
||||
case GalleryType.uncategorized:
|
||||
case GalleryType.locationTag:
|
||||
return true;
|
||||
|
||||
case GalleryType.hidden:
|
||||
|
@ -169,6 +178,7 @@ extension GalleyTypeExtension on GalleryType {
|
|||
case GalleryType.homepage:
|
||||
case GalleryType.searchResults:
|
||||
case GalleryType.uncategorized:
|
||||
case GalleryType.locationTag:
|
||||
return true;
|
||||
|
||||
case GalleryType.hidden:
|
||||
|
|
48
lib/models/local_entity_data.dart
Normal file
48
lib/models/local_entity_data.dart
Normal file
|
@ -0,0 +1,48 @@
|
|||
import "package:equatable/equatable.dart";
|
||||
import "package:photos/models/api/entity/type.dart";
|
||||
|
||||
class LocalEntityData {
|
||||
final String id;
|
||||
final EntityType type;
|
||||
final String data;
|
||||
final int ownerID;
|
||||
final int updatedAt;
|
||||
|
||||
LocalEntityData({
|
||||
required this.id,
|
||||
required this.type,
|
||||
required this.data,
|
||||
required this.ownerID,
|
||||
required this.updatedAt,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
"id": id,
|
||||
"type": type.typeToString(),
|
||||
"data": data,
|
||||
"ownerID": ownerID,
|
||||
"updatedAt": updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
factory LocalEntityData.fromJson(Map<String, dynamic> json) {
|
||||
return LocalEntityData(
|
||||
id: json["id"],
|
||||
type: typeFromString(json["type"]),
|
||||
data: json["data"],
|
||||
ownerID: json["ownerID"] as int,
|
||||
updatedAt: json["updatedAt"] as int,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LocalEntity<T> extends Equatable {
|
||||
final T item;
|
||||
final String id;
|
||||
|
||||
const LocalEntity(this.item, this.id);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [item, id];
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
class Location {
|
||||
final double? latitude;
|
||||
final double? longitude;
|
||||
|
||||
Location(this.latitude, this.longitude);
|
||||
|
||||
@override
|
||||
String toString() => 'Location(latitude: $latitude, longitude: $longitude)';
|
||||
}
|
16
lib/models/location/location.dart
Normal file
16
lib/models/location/location.dart
Normal file
|
@ -0,0 +1,16 @@
|
|||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'location.freezed.dart';
|
||||
|
||||
part 'location.g.dart';
|
||||
|
||||
@freezed
|
||||
class Location with _$Location {
|
||||
const factory Location({
|
||||
required double? latitude,
|
||||
required double? longitude,
|
||||
}) = _Location;
|
||||
|
||||
factory Location.fromJson(Map<String, Object?> json) =>
|
||||
_$LocationFromJson(json);
|
||||
}
|
168
lib/models/location/location.freezed.dart
Normal file
168
lib/models/location/location.freezed.dart
Normal file
|
@ -0,0 +1,168 @@
|
|||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'location.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
|
||||
|
||||
Location _$LocationFromJson(Map<String, dynamic> json) {
|
||||
return _Location.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$Location {
|
||||
double? get latitude => throw _privateConstructorUsedError;
|
||||
double? get longitude => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$LocationCopyWith<Location> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $LocationCopyWith<$Res> {
|
||||
factory $LocationCopyWith(Location value, $Res Function(Location) then) =
|
||||
_$LocationCopyWithImpl<$Res, Location>;
|
||||
@useResult
|
||||
$Res call({double? latitude, double? longitude});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$LocationCopyWithImpl<$Res, $Val extends Location>
|
||||
implements $LocationCopyWith<$Res> {
|
||||
_$LocationCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? latitude = freezed,
|
||||
Object? longitude = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
latitude: freezed == latitude
|
||||
? _value.latitude
|
||||
: latitude // ignore: cast_nullable_to_non_nullable
|
||||
as double?,
|
||||
longitude: freezed == longitude
|
||||
? _value.longitude
|
||||
: longitude // ignore: cast_nullable_to_non_nullable
|
||||
as double?,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$_LocationCopyWith<$Res> implements $LocationCopyWith<$Res> {
|
||||
factory _$$_LocationCopyWith(
|
||||
_$_Location value, $Res Function(_$_Location) then) =
|
||||
__$$_LocationCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({double? latitude, double? longitude});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$_LocationCopyWithImpl<$Res>
|
||||
extends _$LocationCopyWithImpl<$Res, _$_Location>
|
||||
implements _$$_LocationCopyWith<$Res> {
|
||||
__$$_LocationCopyWithImpl(
|
||||
_$_Location _value, $Res Function(_$_Location) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? latitude = freezed,
|
||||
Object? longitude = freezed,
|
||||
}) {
|
||||
return _then(_$_Location(
|
||||
latitude: freezed == latitude
|
||||
? _value.latitude
|
||||
: latitude // ignore: cast_nullable_to_non_nullable
|
||||
as double?,
|
||||
longitude: freezed == longitude
|
||||
? _value.longitude
|
||||
: longitude // ignore: cast_nullable_to_non_nullable
|
||||
as double?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$_Location implements _Location {
|
||||
const _$_Location({required this.latitude, required this.longitude});
|
||||
|
||||
factory _$_Location.fromJson(Map<String, dynamic> json) =>
|
||||
_$$_LocationFromJson(json);
|
||||
|
||||
@override
|
||||
final double? latitude;
|
||||
@override
|
||||
final double? longitude;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Location(latitude: $latitude, longitude: $longitude)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$_Location &&
|
||||
(identical(other.latitude, latitude) ||
|
||||
other.latitude == latitude) &&
|
||||
(identical(other.longitude, longitude) ||
|
||||
other.longitude == longitude));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, latitude, longitude);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$_LocationCopyWith<_$_Location> get copyWith =>
|
||||
__$$_LocationCopyWithImpl<_$_Location>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$_LocationToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _Location implements Location {
|
||||
const factory _Location(
|
||||
{required final double? latitude,
|
||||
required final double? longitude}) = _$_Location;
|
||||
|
||||
factory _Location.fromJson(Map<String, dynamic> json) = _$_Location.fromJson;
|
||||
|
||||
@override
|
||||
double? get latitude;
|
||||
@override
|
||||
double? get longitude;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$_LocationCopyWith<_$_Location> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
18
lib/models/location/location.g.dart
Normal file
18
lib/models/location/location.g.dart
Normal file
|
@ -0,0 +1,18 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'location.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$_Location _$$_LocationFromJson(Map<String, dynamic> json) => _$_Location(
|
||||
latitude: (json['latitude'] as num?)?.toDouble(),
|
||||
longitude: (json['longitude'] as num?)?.toDouble(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$_LocationToJson(_$_Location instance) =>
|
||||
<String, dynamic>{
|
||||
'latitude': instance.latitude,
|
||||
'longitude': instance.longitude,
|
||||
};
|
25
lib/models/location_tag/location_tag.dart
Normal file
25
lib/models/location_tag/location_tag.dart
Normal file
|
@ -0,0 +1,25 @@
|
|||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import "package:photos/core/constants.dart";
|
||||
import 'package:photos/models/location/location.dart';
|
||||
|
||||
part 'location_tag.freezed.dart';
|
||||
part 'location_tag.g.dart';
|
||||
|
||||
@freezed
|
||||
class LocationTag with _$LocationTag {
|
||||
const LocationTag._();
|
||||
const factory LocationTag({
|
||||
required String name,
|
||||
required int radius,
|
||||
required double aSquare,
|
||||
required double bSquare,
|
||||
required Location centerPoint,
|
||||
}) = _LocationTag;
|
||||
|
||||
factory LocationTag.fromJson(Map<String, Object?> json) =>
|
||||
_$LocationTagFromJson(json);
|
||||
|
||||
int get radiusIndex {
|
||||
return radiusValues.indexOf(radius);
|
||||
}
|
||||
}
|
252
lib/models/location_tag/location_tag.freezed.dart
Normal file
252
lib/models/location_tag/location_tag.freezed.dart
Normal file
|
@ -0,0 +1,252 @@
|
|||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'location_tag.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
|
||||
|
||||
LocationTag _$LocationTagFromJson(Map<String, dynamic> json) {
|
||||
return _LocationTag.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$LocationTag {
|
||||
String get name => throw _privateConstructorUsedError;
|
||||
int get radius => throw _privateConstructorUsedError;
|
||||
double get aSquare => throw _privateConstructorUsedError;
|
||||
double get bSquare => throw _privateConstructorUsedError;
|
||||
Location get centerPoint => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$LocationTagCopyWith<LocationTag> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $LocationTagCopyWith<$Res> {
|
||||
factory $LocationTagCopyWith(
|
||||
LocationTag value, $Res Function(LocationTag) then) =
|
||||
_$LocationTagCopyWithImpl<$Res, LocationTag>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{String name,
|
||||
int radius,
|
||||
double aSquare,
|
||||
double bSquare,
|
||||
Location centerPoint});
|
||||
|
||||
$LocationCopyWith<$Res> get centerPoint;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$LocationTagCopyWithImpl<$Res, $Val extends LocationTag>
|
||||
implements $LocationTagCopyWith<$Res> {
|
||||
_$LocationTagCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? name = null,
|
||||
Object? radius = null,
|
||||
Object? aSquare = null,
|
||||
Object? bSquare = null,
|
||||
Object? centerPoint = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
radius: null == radius
|
||||
? _value.radius
|
||||
: radius // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
aSquare: null == aSquare
|
||||
? _value.aSquare
|
||||
: aSquare // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
bSquare: null == bSquare
|
||||
? _value.bSquare
|
||||
: bSquare // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
centerPoint: null == centerPoint
|
||||
? _value.centerPoint
|
||||
: centerPoint // ignore: cast_nullable_to_non_nullable
|
||||
as Location,
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$LocationCopyWith<$Res> get centerPoint {
|
||||
return $LocationCopyWith<$Res>(_value.centerPoint, (value) {
|
||||
return _then(_value.copyWith(centerPoint: value) as $Val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$_LocationTagCopyWith<$Res>
|
||||
implements $LocationTagCopyWith<$Res> {
|
||||
factory _$$_LocationTagCopyWith(
|
||||
_$_LocationTag value, $Res Function(_$_LocationTag) then) =
|
||||
__$$_LocationTagCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{String name,
|
||||
int radius,
|
||||
double aSquare,
|
||||
double bSquare,
|
||||
Location centerPoint});
|
||||
|
||||
@override
|
||||
$LocationCopyWith<$Res> get centerPoint;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$_LocationTagCopyWithImpl<$Res>
|
||||
extends _$LocationTagCopyWithImpl<$Res, _$_LocationTag>
|
||||
implements _$$_LocationTagCopyWith<$Res> {
|
||||
__$$_LocationTagCopyWithImpl(
|
||||
_$_LocationTag _value, $Res Function(_$_LocationTag) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? name = null,
|
||||
Object? radius = null,
|
||||
Object? aSquare = null,
|
||||
Object? bSquare = null,
|
||||
Object? centerPoint = null,
|
||||
}) {
|
||||
return _then(_$_LocationTag(
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
radius: null == radius
|
||||
? _value.radius
|
||||
: radius // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
aSquare: null == aSquare
|
||||
? _value.aSquare
|
||||
: aSquare // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
bSquare: null == bSquare
|
||||
? _value.bSquare
|
||||
: bSquare // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
centerPoint: null == centerPoint
|
||||
? _value.centerPoint
|
||||
: centerPoint // ignore: cast_nullable_to_non_nullable
|
||||
as Location,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$_LocationTag extends _LocationTag {
|
||||
const _$_LocationTag(
|
||||
{required this.name,
|
||||
required this.radius,
|
||||
required this.aSquare,
|
||||
required this.bSquare,
|
||||
required this.centerPoint})
|
||||
: super._();
|
||||
|
||||
factory _$_LocationTag.fromJson(Map<String, dynamic> json) =>
|
||||
_$$_LocationTagFromJson(json);
|
||||
|
||||
@override
|
||||
final String name;
|
||||
@override
|
||||
final int radius;
|
||||
@override
|
||||
final double aSquare;
|
||||
@override
|
||||
final double bSquare;
|
||||
@override
|
||||
final Location centerPoint;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'LocationTag(name: $name, radius: $radius, aSquare: $aSquare, bSquare: $bSquare, centerPoint: $centerPoint)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$_LocationTag &&
|
||||
(identical(other.name, name) || other.name == name) &&
|
||||
(identical(other.radius, radius) || other.radius == radius) &&
|
||||
(identical(other.aSquare, aSquare) || other.aSquare == aSquare) &&
|
||||
(identical(other.bSquare, bSquare) || other.bSquare == bSquare) &&
|
||||
(identical(other.centerPoint, centerPoint) ||
|
||||
other.centerPoint == centerPoint));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, name, radius, aSquare, bSquare, centerPoint);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$_LocationTagCopyWith<_$_LocationTag> get copyWith =>
|
||||
__$$_LocationTagCopyWithImpl<_$_LocationTag>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$_LocationTagToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _LocationTag extends LocationTag {
|
||||
const factory _LocationTag(
|
||||
{required final String name,
|
||||
required final int radius,
|
||||
required final double aSquare,
|
||||
required final double bSquare,
|
||||
required final Location centerPoint}) = _$_LocationTag;
|
||||
const _LocationTag._() : super._();
|
||||
|
||||
factory _LocationTag.fromJson(Map<String, dynamic> json) =
|
||||
_$_LocationTag.fromJson;
|
||||
|
||||
@override
|
||||
String get name;
|
||||
@override
|
||||
int get radius;
|
||||
@override
|
||||
double get aSquare;
|
||||
@override
|
||||
double get bSquare;
|
||||
@override
|
||||
Location get centerPoint;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$_LocationTagCopyWith<_$_LocationTag> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
26
lib/models/location_tag/location_tag.g.dart
Normal file
26
lib/models/location_tag/location_tag.g.dart
Normal file
|
@ -0,0 +1,26 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'location_tag.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$_LocationTag _$$_LocationTagFromJson(Map<String, dynamic> json) =>
|
||||
_$_LocationTag(
|
||||
name: json['name'] as String,
|
||||
radius: json['radius'] as int,
|
||||
aSquare: (json['aSquare'] as num).toDouble(),
|
||||
bSquare: (json['bSquare'] as num).toDouble(),
|
||||
centerPoint:
|
||||
Location.fromJson(json['centerPoint'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$_LocationTagToJson(_$_LocationTag instance) =>
|
||||
<String, dynamic>{
|
||||
'name': instance.name,
|
||||
'radius': instance.radius,
|
||||
'aSquare': instance.aSquare,
|
||||
'bSquare': instance.bSquare,
|
||||
'centerPoint': instance.centerPoint,
|
||||
};
|
|
@ -1,3 +1,4 @@
|
|||
import "package:flutter/cupertino.dart";
|
||||
import 'package:photos/models/file.dart';
|
||||
import 'package:photos/models/search/search_result.dart';
|
||||
|
||||
|
@ -5,8 +6,9 @@ class GenericSearchResult extends SearchResult {
|
|||
final String _name;
|
||||
final List<File> _files;
|
||||
final ResultType _type;
|
||||
final Function(BuildContext context)? onResultTap;
|
||||
|
||||
GenericSearchResult(this._type, this._name, this._files);
|
||||
GenericSearchResult(this._type, this._name, this._files, {this.onResultTap});
|
||||
|
||||
@override
|
||||
String name() {
|
||||
|
|
|
@ -23,5 +23,5 @@ enum ResultType {
|
|||
fileType,
|
||||
fileExtension,
|
||||
fileCaption,
|
||||
event
|
||||
event,
|
||||
}
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import 'dart:async';
|
||||
|
||||
import "package:photos/models/location/location.dart";
|
||||
|
||||
typedef FutureVoidCallback = Future<void> Function();
|
||||
typedef BoolCallBack = bool Function();
|
||||
typedef FutureVoidCallbackParamStr = Future<void> Function(String);
|
||||
typedef VoidCallbackParamStr = void Function(String);
|
||||
typedef FutureOrVoidCallback = FutureOr<void> Function();
|
||||
typedef VoidCallbackParamInt = void Function(int);
|
||||
typedef VoidCallbackParamLocation = void Function(Location);
|
||||
|
|
189
lib/services/entity_service.dart
Normal file
189
lib/services/entity_service.dart
Normal file
|
@ -0,0 +1,189 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_sodium/flutter_sodium.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import "package:photos/core/configuration.dart";
|
||||
import "package:photos/core/network/network.dart";
|
||||
import "package:photos/db/entities_db.dart";
|
||||
import "package:photos/db/files_db.dart";
|
||||
import "package:photos/gateways/entity_gw.dart";
|
||||
import "package:photos/models/api/entity/data.dart";
|
||||
import "package:photos/models/api/entity/key.dart";
|
||||
import "package:photos/models/api/entity/type.dart";
|
||||
import "package:photos/models/local_entity_data.dart";
|
||||
import "package:photos/utils/crypto_util.dart";
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class EntityService {
|
||||
static const int fetchLimit = 500;
|
||||
final _logger = Logger((EntityService).toString());
|
||||
final _config = Configuration.instance;
|
||||
late SharedPreferences _prefs;
|
||||
late EntityGateway _gateway;
|
||||
late FilesDB _db;
|
||||
|
||||
EntityService._privateConstructor();
|
||||
|
||||
static final EntityService instance = EntityService._privateConstructor();
|
||||
|
||||
Future<void> init() async {
|
||||
_prefs = await SharedPreferences.getInstance();
|
||||
_db = FilesDB.instance;
|
||||
_gateway = EntityGateway(NetworkClient.instance.enteDio);
|
||||
}
|
||||
|
||||
String _getEntityKeyPrefix(EntityType type) {
|
||||
return "entity_key_" + type.typeToString();
|
||||
}
|
||||
|
||||
String _getEntityHeaderPrefix(EntityType type) {
|
||||
return "entity_key_header_" + type.typeToString();
|
||||
}
|
||||
|
||||
String _getEntityLastSyncTimePrefix(EntityType type) {
|
||||
return "entity_last_sync_time_" + type.typeToString();
|
||||
}
|
||||
|
||||
Future<List<LocalEntityData>> getEntities(EntityType type) async {
|
||||
return await _db.getEntities(type);
|
||||
}
|
||||
|
||||
Future<LocalEntityData> addOrUpdate(
|
||||
EntityType type,
|
||||
String plainText, {
|
||||
String? id,
|
||||
}) async {
|
||||
final key = await getOrCreateEntityKey(type);
|
||||
final encryptedKeyData = await CryptoUtil.encryptChaCha(
|
||||
utf8.encode(plainText) as Uint8List,
|
||||
key,
|
||||
);
|
||||
final String encryptedData =
|
||||
Sodium.bin2base64(encryptedKeyData.encryptedData!);
|
||||
final String header = Sodium.bin2base64(encryptedKeyData.header!);
|
||||
debugPrint("Adding entity of type: " + type.typeToString());
|
||||
final EntityData data = id == null
|
||||
? await _gateway.createEntity(type, encryptedData, header)
|
||||
: await _gateway.updateEntity(type, id, encryptedData, header);
|
||||
final LocalEntityData localData = LocalEntityData(
|
||||
id: data.id,
|
||||
type: type,
|
||||
data: plainText,
|
||||
ownerID: data.userID,
|
||||
updatedAt: data.updatedAt,
|
||||
);
|
||||
await _db.upsertEntities([localData]);
|
||||
syncEntities().ignore();
|
||||
return localData;
|
||||
}
|
||||
|
||||
Future<void> deleteEntry(String id) async {
|
||||
await _gateway.deleteEntity(id);
|
||||
await _db.deleteEntities([id]);
|
||||
}
|
||||
|
||||
Future<void> syncEntities() async {
|
||||
try {
|
||||
await _remoteToLocalSync(EntityType.location);
|
||||
} catch (e) {
|
||||
_logger.severe("Failed to sync entities", e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _remoteToLocalSync(EntityType type) async {
|
||||
final int lastSyncTime =
|
||||
_prefs.getInt(_getEntityLastSyncTimePrefix(type)) ?? 0;
|
||||
final List<EntityData> result = await _gateway.getDiff(
|
||||
type,
|
||||
lastSyncTime,
|
||||
limit: fetchLimit,
|
||||
);
|
||||
if (result.isEmpty) {
|
||||
debugPrint("No $type entries to sync");
|
||||
return;
|
||||
}
|
||||
final bool hasMoreItems = result.length == fetchLimit;
|
||||
_logger.info("${result.length} entries of type $type fetched");
|
||||
final maxSyncTime = result.map((e) => e.updatedAt).reduce(max);
|
||||
final List<String> deletedIDs =
|
||||
result.where((element) => element.isDeleted).map((e) => e.id).toList();
|
||||
if (deletedIDs.isNotEmpty) {
|
||||
_logger.info("${deletedIDs.length} entries of type $type deleted");
|
||||
await _db.deleteEntities(deletedIDs);
|
||||
}
|
||||
result.removeWhere((element) => element.isDeleted);
|
||||
if (result.isNotEmpty) {
|
||||
final entityKey = await getOrCreateEntityKey(type);
|
||||
final List<LocalEntityData> entities = [];
|
||||
for (EntityData e in result) {
|
||||
try {
|
||||
final decryptedValue = await CryptoUtil.decryptChaCha(
|
||||
Sodium.base642bin(e.encryptedData!),
|
||||
entityKey,
|
||||
Sodium.base642bin(e.header!),
|
||||
);
|
||||
final String plainText = utf8.decode(decryptedValue);
|
||||
entities.add(
|
||||
LocalEntityData(
|
||||
id: e.id,
|
||||
type: type,
|
||||
data: plainText,
|
||||
ownerID: e.userID,
|
||||
updatedAt: e.updatedAt,
|
||||
),
|
||||
);
|
||||
} catch (e, s) {
|
||||
_logger.severe("Failed to decrypted data for key $type", e, s);
|
||||
}
|
||||
}
|
||||
if (entities.isNotEmpty) {
|
||||
await _db.upsertEntities(entities);
|
||||
}
|
||||
}
|
||||
_prefs.setInt(_getEntityLastSyncTimePrefix(type), maxSyncTime);
|
||||
if (hasMoreItems) {
|
||||
_logger.info("Diff limit reached, pulling again");
|
||||
await _remoteToLocalSync(type);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Uint8List> getOrCreateEntityKey(EntityType type) async {
|
||||
late String encryptedKey;
|
||||
late String header;
|
||||
try {
|
||||
if (_prefs.containsKey(_getEntityKeyPrefix(type)) &&
|
||||
_prefs.containsKey(_getEntityHeaderPrefix(type))) {
|
||||
encryptedKey = _prefs.getString(_getEntityKeyPrefix(type))!;
|
||||
header = _prefs.getString(_getEntityHeaderPrefix(type))!;
|
||||
} else {
|
||||
final EntityKey response = await _gateway.getKey(type);
|
||||
encryptedKey = response.encryptedKey;
|
||||
header = response.header;
|
||||
await _prefs.setString(_getEntityKeyPrefix(type), encryptedKey);
|
||||
_prefs.setString(_getEntityHeaderPrefix(type), header);
|
||||
}
|
||||
final entityKey = CryptoUtil.decryptSync(
|
||||
Sodium.base642bin(encryptedKey),
|
||||
_config.getKey()!,
|
||||
Sodium.base642bin(header),
|
||||
);
|
||||
return entityKey;
|
||||
} on EntityKeyNotFound {
|
||||
_logger.info("EntityKeyNotFound generating key for type $type");
|
||||
final key = CryptoUtil.generateKey();
|
||||
final encryptedKeyData = CryptoUtil.encryptSync(key, _config.getKey()!);
|
||||
encryptedKey = Sodium.bin2base64(encryptedKeyData.encryptedData!);
|
||||
header = Sodium.bin2base64(encryptedKeyData.nonce!);
|
||||
await _gateway.createKey(type, encryptedKey, header);
|
||||
await _prefs.setString(_getEntityKeyPrefix(type), encryptedKey);
|
||||
await _prefs.setString(_getEntityHeaderPrefix(type), header);
|
||||
return key;
|
||||
} catch (e, s) {
|
||||
_logger.severe("Failed to getOrCreateKey for type $type", e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,8 +6,10 @@ import 'package:photos/core/network/network.dart';
|
|||
import 'package:photos/db/files_db.dart';
|
||||
import 'package:photos/extensions/list.dart';
|
||||
import 'package:photos/models/file.dart';
|
||||
import "package:photos/models/file_load_result.dart";
|
||||
import 'package:photos/models/magic_metadata.dart';
|
||||
import 'package:photos/services/file_magic_service.dart';
|
||||
import "package:photos/services/ignored_files_service.dart";
|
||||
import 'package:photos/utils/date_time_util.dart';
|
||||
|
||||
class FilesService {
|
||||
|
@ -94,6 +96,15 @@ class FilesService {
|
|||
);
|
||||
return timeResult?.microsecondsSinceEpoch;
|
||||
}
|
||||
|
||||
Future<void> removeIgnoredFiles(Future<FileLoadResult> result) async {
|
||||
final ignoredIDs = await IgnoredFilesService.instance.ignoredIDs;
|
||||
(await result).files.removeWhere(
|
||||
(f) =>
|
||||
f.uploadedFileID == null &&
|
||||
IgnoredFilesService.instance.shouldSkipUpload(ignoredIDs, f),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum EditTimeSource {
|
||||
|
|
216
lib/services/location_service.dart
Normal file
216
lib/services/location_service.dart
Normal file
|
@ -0,0 +1,216 @@
|
|||
import "dart:convert";
|
||||
import "dart:math";
|
||||
|
||||
import "package:logging/logging.dart";
|
||||
import "package:photos/core/constants.dart";
|
||||
import "package:photos/core/event_bus.dart";
|
||||
import "package:photos/events/location_tag_updated_event.dart";
|
||||
import "package:photos/models/api/entity/type.dart";
|
||||
import "package:photos/models/local_entity_data.dart";
|
||||
import "package:photos/models/location/location.dart";
|
||||
import 'package:photos/models/location_tag/location_tag.dart';
|
||||
import "package:photos/services/entity_service.dart";
|
||||
import "package:shared_preferences/shared_preferences.dart";
|
||||
|
||||
class LocationService {
|
||||
late SharedPreferences prefs;
|
||||
final Logger _logger = Logger((LocationService).toString());
|
||||
|
||||
LocationService._privateConstructor();
|
||||
|
||||
static final LocationService instance = LocationService._privateConstructor();
|
||||
|
||||
void init(SharedPreferences preferences) {
|
||||
prefs = preferences;
|
||||
}
|
||||
|
||||
Future<Iterable<LocalEntity<LocationTag>>> _getStoredLocationTags() async {
|
||||
final data = await EntityService.instance.getEntities(EntityType.location);
|
||||
return data.map(
|
||||
(e) => LocalEntity(LocationTag.fromJson(json.decode(e.data)), e.id),
|
||||
);
|
||||
}
|
||||
|
||||
Future<Iterable<LocalEntity<LocationTag>>> getLocationTags() {
|
||||
return _getStoredLocationTags();
|
||||
}
|
||||
|
||||
Future<void> addLocation(
|
||||
String location,
|
||||
Location centerPoint,
|
||||
int radius,
|
||||
) async {
|
||||
//The area enclosed by the location tag will be a circle on a 3D spherical
|
||||
//globe and an ellipse on a 2D Mercator projection (2D map)
|
||||
//a & b are the semi-major and semi-minor axes of the ellipse
|
||||
//Converting the unit from kilometers to degrees for a and b as that is
|
||||
//the unit on the caritesian plane
|
||||
|
||||
try {
|
||||
final a =
|
||||
(radius * _scaleFactor(centerPoint.latitude!)) / kilometersPerDegree;
|
||||
final b = radius / kilometersPerDegree;
|
||||
final locationTag = LocationTag(
|
||||
name: location,
|
||||
radius: radius,
|
||||
aSquare: a * a,
|
||||
bSquare: b * b,
|
||||
centerPoint: centerPoint,
|
||||
);
|
||||
await EntityService.instance
|
||||
.addOrUpdate(EntityType.location, json.encode(locationTag.toJson()));
|
||||
Bus.instance.fire(LocationTagUpdatedEvent(LocTagEventType.add));
|
||||
} catch (e, s) {
|
||||
_logger.severe("Failed to add location tag", e, s);
|
||||
}
|
||||
}
|
||||
|
||||
///The area bounded by the location tag becomes more elliptical with increase
|
||||
///in the magnitude of the latitude on the caritesian plane. When latitude is
|
||||
///0 degrees, the ellipse is a circle with a = b = r. When latitude incrases,
|
||||
///the major axis (a) has to be scaled by the secant of the latitude.
|
||||
double _scaleFactor(double lat) {
|
||||
return 1 / cos(lat * (pi / 180));
|
||||
}
|
||||
|
||||
Future<List<LocalEntity<LocationTag>>> enclosingLocationTags(
|
||||
Location fileCoordinates,
|
||||
) async {
|
||||
try {
|
||||
final result = List<LocalEntity<LocationTag>>.of([]);
|
||||
final locationTagEntities = await getLocationTags();
|
||||
for (LocalEntity<LocationTag> locationTagEntity in locationTagEntities) {
|
||||
final locationTag = locationTagEntity.item;
|
||||
final x = fileCoordinates.latitude! - locationTag.centerPoint.latitude!;
|
||||
final y =
|
||||
fileCoordinates.longitude! - locationTag.centerPoint.longitude!;
|
||||
if ((x * x) / (locationTag.aSquare) + (y * y) / (locationTag.bSquare) <=
|
||||
1) {
|
||||
result.add(
|
||||
locationTagEntity,
|
||||
);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} catch (e, s) {
|
||||
_logger.severe("Failed to get enclosing location tags", e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
bool isFileInsideLocationTag(
|
||||
Location centerPoint,
|
||||
Location fileCoordinates,
|
||||
int radius,
|
||||
) {
|
||||
final a =
|
||||
(radius * _scaleFactor(centerPoint.latitude!)) / kilometersPerDegree;
|
||||
final b = radius / kilometersPerDegree;
|
||||
final x = centerPoint.latitude! - fileCoordinates.latitude!;
|
||||
final y = centerPoint.longitude! - fileCoordinates.longitude!;
|
||||
if ((x * x) / (a * a) + (y * y) / (b * b) <= 1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
String convertLocationToDMS(Location centerPoint) {
|
||||
final lat = centerPoint.latitude!;
|
||||
final long = centerPoint.longitude!;
|
||||
final latRef = lat >= 0 ? "N" : "S";
|
||||
final longRef = long >= 0 ? "E" : "W";
|
||||
final latDMS = convertCoordinateToDMS(lat.abs());
|
||||
final longDMS = convertCoordinateToDMS(long.abs());
|
||||
return "${latDMS[0]}°${latDMS[1]}'${latDMS[2]}\"$latRef, ${longDMS[0]}°${longDMS[1]}'${longDMS[2]}\"$longRef";
|
||||
}
|
||||
|
||||
List<int> convertCoordinateToDMS(double coordinate) {
|
||||
final degrees = coordinate.floor();
|
||||
final minutes = ((coordinate - degrees) * 60).floor();
|
||||
final seconds = ((coordinate - degrees - minutes / 60) * 3600).floor();
|
||||
return [degrees, minutes, seconds];
|
||||
}
|
||||
|
||||
///Will only update if there is a change in the locationTag's properties
|
||||
Future<void> updateLocationTag({
|
||||
required LocalEntity<LocationTag> locationTagEntity,
|
||||
int? newRadius,
|
||||
Location? newCenterPoint,
|
||||
String? newName,
|
||||
}) async {
|
||||
try {
|
||||
final radius = newRadius ?? locationTagEntity.item.radius;
|
||||
final centerPoint = newCenterPoint ?? locationTagEntity.item.centerPoint;
|
||||
final name = newName ?? locationTagEntity.item.name;
|
||||
|
||||
final locationTag = locationTagEntity.item;
|
||||
//Exit if there is no change in locationTag's properties
|
||||
if (radius == locationTag.radius &&
|
||||
centerPoint == locationTag.centerPoint &&
|
||||
name == locationTag.name) {
|
||||
return;
|
||||
}
|
||||
final a =
|
||||
(radius * _scaleFactor(centerPoint.latitude!)) / kilometersPerDegree;
|
||||
final b = radius / kilometersPerDegree;
|
||||
final updatedLoationTag = locationTagEntity.item.copyWith(
|
||||
centerPoint: centerPoint,
|
||||
aSquare: a * a,
|
||||
bSquare: b * b,
|
||||
radius: radius,
|
||||
name: name,
|
||||
);
|
||||
|
||||
await EntityService.instance.addOrUpdate(
|
||||
EntityType.location,
|
||||
json.encode(updatedLoationTag.toJson()),
|
||||
id: locationTagEntity.id,
|
||||
);
|
||||
Bus.instance.fire(
|
||||
LocationTagUpdatedEvent(
|
||||
LocTagEventType.update,
|
||||
updatedLocTagEntities: [
|
||||
LocalEntity(updatedLoationTag, locationTagEntity.id)
|
||||
],
|
||||
),
|
||||
);
|
||||
} catch (e, s) {
|
||||
_logger.severe("Failed to update location tag", e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteLocationTag(String locTagEntityId) async {
|
||||
try {
|
||||
await EntityService.instance.deleteEntry(
|
||||
locTagEntityId,
|
||||
);
|
||||
Bus.instance.fire(
|
||||
LocationTagUpdatedEvent(
|
||||
LocTagEventType.delete,
|
||||
),
|
||||
);
|
||||
} catch (e, s) {
|
||||
_logger.severe("Failed to delete location tag", e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GPSData {
|
||||
final String latRef;
|
||||
final List<double> lat;
|
||||
final String longRef;
|
||||
final List<double> long;
|
||||
|
||||
GPSData(this.latRef, this.lat, this.longRef, this.long);
|
||||
|
||||
Location toLocationObj() {
|
||||
final latSign = latRef == "N" ? 1 : -1;
|
||||
final longSign = longRef == "E" ? 1 : -1;
|
||||
return Location(
|
||||
latitude: latSign * lat[0] + lat[1] / 60 + lat[2] / 3600,
|
||||
longitude: longSign * long[0] + long[1] / 60 + long[2] / 3600,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -9,13 +9,17 @@ import 'package:photos/models/collection.dart';
|
|||
import 'package:photos/models/collection_items.dart';
|
||||
import 'package:photos/models/file.dart';
|
||||
import 'package:photos/models/file_type.dart';
|
||||
import 'package:photos/models/location.dart';
|
||||
import "package:photos/models/local_entity_data.dart";
|
||||
import "package:photos/models/location_tag/location_tag.dart";
|
||||
import 'package:photos/models/search/album_search_result.dart';
|
||||
import 'package:photos/models/search/generic_search_result.dart';
|
||||
import 'package:photos/models/search/location_api_response.dart';
|
||||
import 'package:photos/models/search/search_result.dart';
|
||||
import 'package:photos/services/collections_service.dart';
|
||||
import "package:photos/services/location_service.dart";
|
||||
import "package:photos/states/location_screen_state.dart";
|
||||
import "package:photos/ui/viewer/location/location_screen.dart";
|
||||
import 'package:photos/utils/date_time_util.dart';
|
||||
import "package:photos/utils/navigation_util.dart";
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class SearchService {
|
||||
|
@ -53,46 +57,6 @@ class SearchService {
|
|||
_cachedFilesFuture = null;
|
||||
}
|
||||
|
||||
Future<List<GenericSearchResult>> getLocationSearchResults(
|
||||
String query,
|
||||
) async {
|
||||
final List<GenericSearchResult> searchResults = [];
|
||||
try {
|
||||
final List<File> allFiles = await _getAllFiles();
|
||||
// This code used an deprecated API earlier. We've retained the
|
||||
// scaffolding for when we implement a client side location search, and
|
||||
// meanwhile have replaced the API response.data with an empty map here.
|
||||
final matchedLocationSearchResults = LocationApiResponse.fromMap({});
|
||||
|
||||
for (var locationData in matchedLocationSearchResults.results) {
|
||||
final List<File> filesInLocation = [];
|
||||
|
||||
for (var file in allFiles) {
|
||||
if (_isValidLocation(file.location) &&
|
||||
_isLocationWithinBounds(file.location!, locationData)) {
|
||||
filesInLocation.add(file);
|
||||
}
|
||||
}
|
||||
filesInLocation.sort(
|
||||
(first, second) =>
|
||||
second.creationTime!.compareTo(first.creationTime!),
|
||||
);
|
||||
if (filesInLocation.isNotEmpty) {
|
||||
searchResults.add(
|
||||
GenericSearchResult(
|
||||
ResultType.location,
|
||||
locationData.place,
|
||||
filesInLocation,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
_logger.severe(e);
|
||||
}
|
||||
return searchResults;
|
||||
}
|
||||
|
||||
// getFilteredCollectionsWithThumbnail removes deleted or archived or
|
||||
// collections which don't have a file from search result
|
||||
Future<List<AlbumSearchResult>> getCollectionSearchResults(
|
||||
|
@ -263,6 +227,61 @@ class SearchService {
|
|||
return searchResults;
|
||||
}
|
||||
|
||||
Future<List<GenericSearchResult>> getLocationResults(
|
||||
String query,
|
||||
) async {
|
||||
final locationTagEntities =
|
||||
(await LocationService.instance.getLocationTags());
|
||||
final Map<LocalEntity<LocationTag>, List<File>> result = {};
|
||||
|
||||
final List<GenericSearchResult> searchResults = [];
|
||||
|
||||
for (LocalEntity<LocationTag> tag in locationTagEntities) {
|
||||
if (tag.item.name.toLowerCase().contains(query.toLowerCase())) {
|
||||
result[tag] = [];
|
||||
}
|
||||
}
|
||||
if (result.isEmpty) {
|
||||
return searchResults;
|
||||
}
|
||||
final allFiles = await _getAllFiles();
|
||||
for (File file in allFiles) {
|
||||
if (file.hasLocation) {
|
||||
for (LocalEntity<LocationTag> tag in result.keys) {
|
||||
if (LocationService.instance.isFileInsideLocationTag(
|
||||
tag.item.centerPoint,
|
||||
file.location!,
|
||||
tag.item.radius,
|
||||
)) {
|
||||
result[tag]!.add(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (MapEntry<LocalEntity<LocationTag>, List<File>> entry
|
||||
in result.entries) {
|
||||
if (entry.value.isNotEmpty) {
|
||||
searchResults.add(
|
||||
GenericSearchResult(
|
||||
ResultType.location,
|
||||
entry.key.item.name,
|
||||
entry.value,
|
||||
onResultTap: (ctx) {
|
||||
routeToPage(
|
||||
ctx,
|
||||
LocationScreenStateProvider(
|
||||
entry.key,
|
||||
const LocationScreen(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
return searchResults;
|
||||
}
|
||||
|
||||
Future<List<GenericSearchResult>> getMonthSearchResults(String query) async {
|
||||
final List<GenericSearchResult> searchResults = [];
|
||||
for (var month in _getMatchingMonths(query)) {
|
||||
|
@ -363,25 +382,6 @@ class SearchService {
|
|||
return durationsOfMonthInEveryYear;
|
||||
}
|
||||
|
||||
bool _isValidLocation(Location? location) {
|
||||
return location != null &&
|
||||
location.latitude != null &&
|
||||
location.latitude != 0 &&
|
||||
location.longitude != null &&
|
||||
location.longitude != 0;
|
||||
}
|
||||
|
||||
bool _isLocationWithinBounds(
|
||||
Location location,
|
||||
LocationDataFromResponse locationData,
|
||||
) {
|
||||
//format returned by the api is [lng,lat,lng,lat] where indexes 0 & 1 are southwest and 2 & 3 northeast
|
||||
return location.longitude! > locationData.bbox[0] &&
|
||||
location.latitude! > locationData.bbox[1] &&
|
||||
location.longitude! < locationData.bbox[2] &&
|
||||
location.latitude! < locationData.bbox[3];
|
||||
}
|
||||
|
||||
List<Tuple3<int, MonthData, int?>> _getPossibleEventDate(String query) {
|
||||
final List<Tuple3<int, MonthData, int?>> possibleEvents = [];
|
||||
if (query.trim().isEmpty) {
|
||||
|
|
|
@ -16,7 +16,7 @@ class UpdateService {
|
|||
static final UpdateService instance = UpdateService._privateConstructor();
|
||||
static const kUpdateAvailableShownTimeKey = "update_available_shown_time_key";
|
||||
static const changeLogVersionKey = "update_change_log_key";
|
||||
static const currentChangeLogVersion = 7;
|
||||
static const currentChangeLogVersion = 8;
|
||||
|
||||
LatestVersionInfo? _latestVersion;
|
||||
final _logger = Logger("UpdateService");
|
||||
|
|
77
lib/states/location_screen_state.dart
Normal file
77
lib/states/location_screen_state.dart
Normal file
|
@ -0,0 +1,77 @@
|
|||
import "dart:async";
|
||||
|
||||
import "package:flutter/material.dart";
|
||||
import "package:photos/core/event_bus.dart";
|
||||
import "package:photos/events/location_tag_updated_event.dart";
|
||||
import "package:photos/models/local_entity_data.dart";
|
||||
import 'package:photos/models/location_tag/location_tag.dart';
|
||||
|
||||
class LocationScreenStateProvider extends StatefulWidget {
|
||||
final LocalEntity<LocationTag> locationTagEntity;
|
||||
final Widget child;
|
||||
const LocationScreenStateProvider(
|
||||
this.locationTagEntity,
|
||||
this.child, {
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<LocationScreenStateProvider> createState() =>
|
||||
_LocationScreenStateProviderState();
|
||||
}
|
||||
|
||||
class _LocationScreenStateProviderState
|
||||
extends State<LocationScreenStateProvider> {
|
||||
late LocalEntity<LocationTag> _locationTagEntity;
|
||||
late final StreamSubscription _locTagUpdateListener;
|
||||
@override
|
||||
void initState() {
|
||||
_locationTagEntity = widget.locationTagEntity;
|
||||
_locTagUpdateListener =
|
||||
Bus.instance.on<LocationTagUpdatedEvent>().listen((event) {
|
||||
if (event.type == LocTagEventType.update) {
|
||||
setState(() {
|
||||
_locationTagEntity = event.updatedLocTagEntities!.first;
|
||||
});
|
||||
}
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
dispose() {
|
||||
_locTagUpdateListener.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InheritedLocationScreenState(
|
||||
_locationTagEntity,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class InheritedLocationScreenState extends InheritedWidget {
|
||||
final LocalEntity<LocationTag> locationTagEntity;
|
||||
const InheritedLocationScreenState(
|
||||
this.locationTagEntity, {
|
||||
super.key,
|
||||
required super.child,
|
||||
});
|
||||
|
||||
//This is used to show loading state when memory count is beign computed and to
|
||||
//show count after computation.
|
||||
static final memoryCountNotifier = ValueNotifier<int?>(null);
|
||||
|
||||
static InheritedLocationScreenState of(BuildContext context) {
|
||||
return context
|
||||
.dependOnInheritedWidgetOfExactType<InheritedLocationScreenState>()!;
|
||||
}
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(covariant InheritedLocationScreenState oldWidget) {
|
||||
return oldWidget.locationTagEntity != locationTagEntity;
|
||||
}
|
||||
}
|
132
lib/states/location_state.dart
Normal file
132
lib/states/location_state.dart
Normal file
|
@ -0,0 +1,132 @@
|
|||
import "dart:async";
|
||||
|
||||
import "package:flutter/material.dart";
|
||||
import "package:photos/core/constants.dart";
|
||||
import "package:photos/core/event_bus.dart";
|
||||
import "package:photos/events/location_tag_updated_event.dart";
|
||||
import "package:photos/models/local_entity_data.dart";
|
||||
import "package:photos/models/location/location.dart";
|
||||
import "package:photos/models/location_tag/location_tag.dart";
|
||||
import "package:photos/models/typedefs.dart";
|
||||
import "package:photos/utils/debouncer.dart";
|
||||
|
||||
class LocationTagStateProvider extends StatefulWidget {
|
||||
final LocalEntity<LocationTag>? locationTagEntity;
|
||||
final Location? centerPoint;
|
||||
final Widget child;
|
||||
const LocationTagStateProvider(
|
||||
this.child, {
|
||||
this.centerPoint,
|
||||
this.locationTagEntity,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<LocationTagStateProvider> createState() =>
|
||||
_LocationTagStateProviderState();
|
||||
}
|
||||
|
||||
class _LocationTagStateProviderState extends State<LocationTagStateProvider> {
|
||||
int _selectedRaduisIndex = defaultRadiusValueIndex;
|
||||
late Location? _centerPoint;
|
||||
late LocalEntity<LocationTag>? _locationTagEntity;
|
||||
final Debouncer _selectedRadiusDebouncer =
|
||||
Debouncer(const Duration(milliseconds: 300));
|
||||
late final StreamSubscription _locTagEntityListener;
|
||||
@override
|
||||
void initState() {
|
||||
_locationTagEntity = widget.locationTagEntity;
|
||||
_centerPoint = widget.centerPoint;
|
||||
assert(_centerPoint != null || _locationTagEntity != null);
|
||||
_centerPoint = _locationTagEntity?.item.centerPoint ?? _centerPoint!;
|
||||
_selectedRaduisIndex =
|
||||
_locationTagEntity?.item.radiusIndex ?? defaultRadiusValueIndex;
|
||||
_locTagEntityListener =
|
||||
Bus.instance.on<LocationTagUpdatedEvent>().listen((event) {
|
||||
_locationTagUpdateListener(event);
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_locTagEntityListener.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _locationTagUpdateListener(LocationTagUpdatedEvent event) {
|
||||
if (event.type == LocTagEventType.update) {
|
||||
if (event.updatedLocTagEntities!.first.id == _locationTagEntity!.id) {
|
||||
//Update state when locationTag is updated.
|
||||
setState(() {
|
||||
final updatedLocTagEntity = event.updatedLocTagEntities!.first;
|
||||
_selectedRaduisIndex = updatedLocTagEntity.item.radiusIndex;
|
||||
_centerPoint = updatedLocTagEntity.item.centerPoint;
|
||||
_locationTagEntity = updatedLocTagEntity;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _updateSelectedIndex(int index) {
|
||||
_selectedRadiusDebouncer.cancelDebounce();
|
||||
_selectedRadiusDebouncer.run(() async {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_selectedRaduisIndex = index;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _updateCenterPoint(Location centerPoint) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_centerPoint = centerPoint;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InheritedLocationTagData(
|
||||
_selectedRaduisIndex,
|
||||
_centerPoint!,
|
||||
_updateSelectedIndex,
|
||||
_locationTagEntity,
|
||||
_updateCenterPoint,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
///This InheritedWidget's state is used in add & edit location sheets
|
||||
class InheritedLocationTagData extends InheritedWidget {
|
||||
final int selectedRadiusIndex;
|
||||
final Location centerPoint;
|
||||
//locationTag is null when we are creating a new location tag in add location sheet
|
||||
final LocalEntity<LocationTag>? locationTagEntity;
|
||||
final VoidCallbackParamInt updateSelectedIndex;
|
||||
final VoidCallbackParamLocation updateCenterPoint;
|
||||
const InheritedLocationTagData(
|
||||
this.selectedRadiusIndex,
|
||||
this.centerPoint,
|
||||
this.updateSelectedIndex,
|
||||
this.locationTagEntity,
|
||||
this.updateCenterPoint, {
|
||||
required super.child,
|
||||
super.key,
|
||||
});
|
||||
|
||||
static InheritedLocationTagData of(BuildContext context) {
|
||||
return context
|
||||
.dependOnInheritedWidgetOfExactType<InheritedLocationTagData>()!;
|
||||
}
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(InheritedLocationTagData oldWidget) {
|
||||
return oldWidget.selectedRadiusIndex != selectedRadiusIndex ||
|
||||
oldWidget.centerPoint != centerPoint ||
|
||||
oldWidget.locationTagEntity != locationTagEntity;
|
||||
}
|
||||
}
|
|
@ -216,6 +216,7 @@ const Color tabIconDark = Color.fromRGBO(255, 255, 255, 0.80);
|
|||
// Fixed Colors
|
||||
|
||||
const Color fixedStrokeMutedWhite = Color.fromRGBO(255, 255, 255, 0.50);
|
||||
const Color strokeSolidMutedLight = Color.fromRGBO(147, 147, 147, 1);
|
||||
|
||||
const Color _primary700 = Color.fromRGBO(0, 179, 60, 1);
|
||||
const Color _primary500 = Color.fromRGBO(29, 185, 84, 1);
|
||||
|
|
|
@ -131,7 +131,7 @@ Future<void> showSingleFileDeleteSheet(
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> showInfoSheet(BuildContext context, File file) async {
|
||||
Future<void> showDetailsSheet(BuildContext context, File file) async {
|
||||
final colorScheme = getEnteColorScheme(context);
|
||||
return showBarModalBottomSheet(
|
||||
topControl: const SizedBox.shrink(),
|
||||
|
|
|
@ -159,8 +159,9 @@ class _CollectionActionSheetState extends State<CollectionActionSheet> {
|
|||
_searchQuery = value;
|
||||
});
|
||||
},
|
||||
cancellable: true,
|
||||
shouldUnfocusOnCancelOrSubmit: true,
|
||||
isClearable: true,
|
||||
shouldUnfocusOnClearOrSubmit: true,
|
||||
borderRadius: 2,
|
||||
),
|
||||
),
|
||||
_getCollectionItems(filesCount),
|
||||
|
|
|
@ -37,14 +37,16 @@ class ChipButtonWidget extends StatelessWidget {
|
|||
size: 17,
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
const SizedBox(width: 4),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Text(
|
||||
label ?? "",
|
||||
style: getEnteTextTheme(context).smallBold,
|
||||
),
|
||||
)
|
||||
if (label != null && leadingIcon != null)
|
||||
const SizedBox(width: 4),
|
||||
if (label != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Text(
|
||||
label!,
|
||||
style: getEnteTextTheme(context).smallBold,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -28,17 +28,23 @@ class DividerWidget extends StatelessWidget {
|
|||
: getEnteColorScheme(context).strokeFaint;
|
||||
|
||||
if (dividerType == DividerType.solid) {
|
||||
return Container(
|
||||
color: getEnteColorScheme(context).strokeFaint,
|
||||
width: double.infinity,
|
||||
height: 1,
|
||||
return Padding(
|
||||
padding: padding ?? EdgeInsets.zero,
|
||||
child: Container(
|
||||
color: getEnteColorScheme(context).strokeFaint,
|
||||
width: double.infinity,
|
||||
height: 1,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (dividerType == DividerType.bottomBar) {
|
||||
return Container(
|
||||
color: dividerColor,
|
||||
width: double.infinity,
|
||||
height: 1,
|
||||
return Padding(
|
||||
padding: padding ?? EdgeInsets.zero,
|
||||
child: Container(
|
||||
color: dividerColor,
|
||||
width: double.infinity,
|
||||
height: 1,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -10,12 +10,14 @@ class InfoItemWidget extends StatelessWidget {
|
|||
final String? title;
|
||||
final Future<List<Widget>> subtitleSection;
|
||||
final bool hasChipButtons;
|
||||
final VoidCallback? onTap;
|
||||
const InfoItemWidget({
|
||||
required this.leadingIcon,
|
||||
this.editOnTap,
|
||||
this.title,
|
||||
required this.subtitleSection,
|
||||
this.hasChipButtons = false,
|
||||
this.onTap,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
@ -83,10 +85,17 @@ class InfoItemWidget extends StatelessWidget {
|
|||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 3.5, 16, 3.5),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: children,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: onTap,
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: children,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -16,10 +16,15 @@ class TextInputWidget extends StatefulWidget {
|
|||
final Alignment? alignMessage;
|
||||
final bool? autoFocus;
|
||||
final int? maxLength;
|
||||
final double borderRadius;
|
||||
|
||||
///TextInputWidget will listen to this notifier and executes onSubmit when
|
||||
///notified.
|
||||
final ValueNotifier? submitNotifier;
|
||||
|
||||
///TextInputWidget will listen to this notifier and clears and unfocuses the
|
||||
///textFiled when notified.
|
||||
final ValueNotifier? cancelNotifier;
|
||||
final bool alwaysShowSuccessState;
|
||||
final bool showOnlyLoadingState;
|
||||
final FutureVoidCallbackParamStr? onSubmit;
|
||||
|
@ -28,8 +33,14 @@ class TextInputWidget extends StatefulWidget {
|
|||
final bool shouldSurfaceExecutionStates;
|
||||
final TextCapitalization? textCapitalization;
|
||||
final bool isPasswordInput;
|
||||
final bool cancellable;
|
||||
final bool shouldUnfocusOnCancelOrSubmit;
|
||||
|
||||
///Clear comes in the form of a suffix icon. It is unrelated to onCancel.
|
||||
final bool isClearable;
|
||||
final bool shouldUnfocusOnClearOrSubmit;
|
||||
final FocusNode? focusNode;
|
||||
final VoidCallback? onCancel;
|
||||
final TextEditingController? textEditingController;
|
||||
final ValueNotifier? isEmptyNotifier;
|
||||
const TextInputWidget({
|
||||
this.onSubmit,
|
||||
this.onChange,
|
||||
|
@ -42,14 +53,20 @@ class TextInputWidget extends StatefulWidget {
|
|||
this.autoFocus,
|
||||
this.maxLength,
|
||||
this.submitNotifier,
|
||||
this.cancelNotifier,
|
||||
this.alwaysShowSuccessState = false,
|
||||
this.showOnlyLoadingState = false,
|
||||
this.popNavAfterSubmission = false,
|
||||
this.shouldSurfaceExecutionStates = true,
|
||||
this.textCapitalization = TextCapitalization.none,
|
||||
this.isPasswordInput = false,
|
||||
this.cancellable = false,
|
||||
this.shouldUnfocusOnCancelOrSubmit = false,
|
||||
this.isClearable = false,
|
||||
this.shouldUnfocusOnClearOrSubmit = false,
|
||||
this.borderRadius = 8,
|
||||
this.focusNode,
|
||||
this.onCancel,
|
||||
this.textEditingController,
|
||||
this.isEmptyNotifier,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
@ -59,7 +76,7 @@ class TextInputWidget extends StatefulWidget {
|
|||
|
||||
class _TextInputWidgetState extends State<TextInputWidget> {
|
||||
ExecutionState executionState = ExecutionState.idle;
|
||||
final _textController = TextEditingController();
|
||||
late final TextEditingController _textController;
|
||||
final _debouncer = Debouncer(const Duration(milliseconds: 300));
|
||||
late final ValueNotifier<bool> _obscureTextNotifier;
|
||||
|
||||
|
@ -70,6 +87,8 @@ class _TextInputWidgetState extends State<TextInputWidget> {
|
|||
@override
|
||||
void initState() {
|
||||
widget.submitNotifier?.addListener(_onSubmit);
|
||||
widget.cancelNotifier?.addListener(_onCancel);
|
||||
_textController = widget.textEditingController ?? TextEditingController();
|
||||
|
||||
if (widget.initialValue != null) {
|
||||
_textController.value = TextEditingValue(
|
||||
|
@ -84,14 +103,22 @@ class _TextInputWidgetState extends State<TextInputWidget> {
|
|||
}
|
||||
_obscureTextNotifier = ValueNotifier(widget.isPasswordInput);
|
||||
_obscureTextNotifier.addListener(_safeRefresh);
|
||||
|
||||
if (widget.isEmptyNotifier != null) {
|
||||
_textController.addListener(() {
|
||||
widget.isEmptyNotifier!.value = _textController.text.isEmpty;
|
||||
});
|
||||
}
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.submitNotifier?.removeListener(_onSubmit);
|
||||
widget.cancelNotifier?.removeListener(_onCancel);
|
||||
_obscureTextNotifier.dispose();
|
||||
_textController.dispose();
|
||||
widget.isEmptyNotifier?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
@ -113,12 +140,13 @@ class _TextInputWidgetState extends State<TextInputWidget> {
|
|||
}
|
||||
textInputChildren.add(
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
borderRadius: BorderRadius.all(Radius.circular(widget.borderRadius)),
|
||||
child: Material(
|
||||
child: TextFormField(
|
||||
textCapitalization: widget.textCapitalization!,
|
||||
autofocus: widget.autoFocus ?? false,
|
||||
controller: _textController,
|
||||
focusNode: widget.focusNode,
|
||||
inputFormatters: widget.maxLength != null
|
||||
? [LengthLimitingTextInputFormatter(50)]
|
||||
: null,
|
||||
|
@ -155,9 +183,9 @@ class _TextInputWidgetState extends State<TextInputWidget> {
|
|||
obscureTextNotifier: _obscureTextNotifier,
|
||||
isPasswordInput: widget.isPasswordInput,
|
||||
textController: _textController,
|
||||
isCancellable: widget.cancellable,
|
||||
shouldUnfocusOnCancelOrSubmit:
|
||||
widget.shouldUnfocusOnCancelOrSubmit,
|
||||
isClearable: widget.isClearable,
|
||||
shouldUnfocusOnClearOrSubmit:
|
||||
widget.shouldUnfocusOnClearOrSubmit,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -224,7 +252,7 @@ class _TextInputWidgetState extends State<TextInputWidget> {
|
|||
});
|
||||
}),
|
||||
);
|
||||
if (widget.shouldUnfocusOnCancelOrSubmit) {
|
||||
if (widget.shouldUnfocusOnClearOrSubmit) {
|
||||
FocusScope.of(context).unfocus();
|
||||
}
|
||||
try {
|
||||
|
@ -303,6 +331,15 @@ class _TextInputWidgetState extends State<TextInputWidget> {
|
|||
}
|
||||
}
|
||||
|
||||
void _onCancel() {
|
||||
if (widget.onCancel != null) {
|
||||
widget.onCancel!();
|
||||
} else {
|
||||
_textController.clear();
|
||||
FocusScope.of(context).unfocus();
|
||||
}
|
||||
}
|
||||
|
||||
void _popNavigatorStack(BuildContext context, {Exception? e}) {
|
||||
Navigator.of(context).canPop() ? Navigator.of(context).pop(e) : null;
|
||||
}
|
||||
|
@ -315,8 +352,8 @@ class SuffixIconWidget extends StatelessWidget {
|
|||
final TextEditingController textController;
|
||||
final ValueNotifier? obscureTextNotifier;
|
||||
final bool isPasswordInput;
|
||||
final bool isCancellable;
|
||||
final bool shouldUnfocusOnCancelOrSubmit;
|
||||
final bool isClearable;
|
||||
final bool shouldUnfocusOnClearOrSubmit;
|
||||
|
||||
const SuffixIconWidget({
|
||||
required this.executionState,
|
||||
|
@ -324,8 +361,8 @@ class SuffixIconWidget extends StatelessWidget {
|
|||
required this.textController,
|
||||
this.obscureTextNotifier,
|
||||
this.isPasswordInput = false,
|
||||
this.isCancellable = false,
|
||||
this.shouldUnfocusOnCancelOrSubmit = false,
|
||||
this.isClearable = false,
|
||||
this.shouldUnfocusOnClearOrSubmit = false,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
@ -335,11 +372,11 @@ class SuffixIconWidget extends StatelessWidget {
|
|||
final colorScheme = getEnteColorScheme(context);
|
||||
if (executionState == ExecutionState.idle ||
|
||||
!shouldSurfaceExecutionStates) {
|
||||
if (isCancellable) {
|
||||
if (isClearable) {
|
||||
trailingWidget = GestureDetector(
|
||||
onTap: () {
|
||||
textController.clear();
|
||||
if (shouldUnfocusOnCancelOrSubmit) {
|
||||
if (shouldUnfocusOnClearOrSubmit) {
|
||||
FocusScope.of(context).unfocus();
|
||||
}
|
||||
},
|
||||
|
|
|
@ -13,6 +13,7 @@ class TitleBarWidget extends StatelessWidget {
|
|||
final bool isFlexibleSpaceDisabled;
|
||||
final bool isOnTopOfScreen;
|
||||
final Color? backgroundColor;
|
||||
final bool isSliver;
|
||||
const TitleBarWidget({
|
||||
this.leading,
|
||||
this.title,
|
||||
|
@ -24,103 +25,96 @@ class TitleBarWidget extends StatelessWidget {
|
|||
this.isFlexibleSpaceDisabled = false,
|
||||
this.isOnTopOfScreen = true,
|
||||
this.backgroundColor,
|
||||
this.isSliver = true,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const toolbarHeight = 48.0;
|
||||
final textTheme = getEnteTextTheme(context);
|
||||
final colorTheme = getEnteColorScheme(context);
|
||||
return SliverAppBar(
|
||||
backgroundColor: backgroundColor,
|
||||
primary: isOnTopOfScreen ? true : false,
|
||||
toolbarHeight: toolbarHeight,
|
||||
leadingWidth: 48,
|
||||
automaticallyImplyLeading: false,
|
||||
pinned: true,
|
||||
expandedHeight: isFlexibleSpaceDisabled ? toolbarHeight : 102,
|
||||
centerTitle: false,
|
||||
titleSpacing: 4,
|
||||
title: Padding(
|
||||
padding: EdgeInsets.only(left: isTitleH2WithoutLeading ? 16 : 0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
title == null
|
||||
? const SizedBox.shrink()
|
||||
: Text(
|
||||
title!,
|
||||
style: isTitleH2WithoutLeading
|
||||
? textTheme.h2Bold
|
||||
: textTheme.largeBold,
|
||||
),
|
||||
caption == null || isTitleH2WithoutLeading
|
||||
? const SizedBox.shrink()
|
||||
: Text(
|
||||
caption!,
|
||||
style: textTheme.mini.copyWith(color: colorTheme.textMuted),
|
||||
)
|
||||
],
|
||||
if (isSliver) {
|
||||
return SliverAppBar(
|
||||
backgroundColor: backgroundColor,
|
||||
primary: isOnTopOfScreen ? true : false,
|
||||
toolbarHeight: toolbarHeight,
|
||||
leadingWidth: 48,
|
||||
automaticallyImplyLeading: false,
|
||||
pinned: true,
|
||||
expandedHeight: isFlexibleSpaceDisabled ? toolbarHeight : 102,
|
||||
centerTitle: false,
|
||||
titleSpacing: 4,
|
||||
title: TitleWidget(
|
||||
title: title,
|
||||
caption: caption,
|
||||
isTitleH2WithoutLeading: isTitleH2WithoutLeading,
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Row(
|
||||
children: _actionsWithPaddingInBetween(),
|
||||
),
|
||||
),
|
||||
],
|
||||
leading: isTitleH2WithoutLeading
|
||||
? null
|
||||
: leading ??
|
||||
IconButtonWidget(
|
||||
icon: Icons.arrow_back_outlined,
|
||||
iconButtonType: IconButtonType.primary,
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
flexibleSpace: isFlexibleSpaceDisabled
|
||||
? null
|
||||
: FlexibleSpaceBar(
|
||||
background: SafeArea(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
const SizedBox(height: toolbarHeight),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 4,
|
||||
horizontal: 16,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
flexibleSpaceTitle == null
|
||||
? const SizedBox.shrink()
|
||||
: flexibleSpaceTitle!,
|
||||
flexibleSpaceCaption == null
|
||||
? const SizedBox.shrink()
|
||||
: Text(
|
||||
flexibleSpaceCaption!,
|
||||
style: textTheme.small.copyWith(
|
||||
color: colorTheme.textMuted,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Row(
|
||||
children: _actionsWithPaddingInBetween(),
|
||||
),
|
||||
);
|
||||
),
|
||||
],
|
||||
leading: isTitleH2WithoutLeading
|
||||
? null
|
||||
: leading ??
|
||||
IconButtonWidget(
|
||||
icon: Icons.arrow_back_outlined,
|
||||
iconButtonType: IconButtonType.primary,
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
flexibleSpace: isFlexibleSpaceDisabled
|
||||
? null
|
||||
: FlexibleSpaceBarWidget(
|
||||
flexibleSpaceTitle,
|
||||
flexibleSpaceCaption,
|
||||
toolbarHeight,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return AppBar(
|
||||
backgroundColor: backgroundColor,
|
||||
primary: isOnTopOfScreen ? true : false,
|
||||
toolbarHeight: toolbarHeight,
|
||||
leadingWidth: 48,
|
||||
automaticallyImplyLeading: false,
|
||||
centerTitle: false,
|
||||
titleSpacing: 4,
|
||||
title: TitleWidget(
|
||||
title: title,
|
||||
caption: caption,
|
||||
isTitleH2WithoutLeading: isTitleH2WithoutLeading,
|
||||
),
|
||||
actions: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Row(
|
||||
children: _actionsWithPaddingInBetween(),
|
||||
),
|
||||
),
|
||||
],
|
||||
leading: isTitleH2WithoutLeading
|
||||
? null
|
||||
: leading ??
|
||||
IconButtonWidget(
|
||||
icon: Icons.arrow_back_outlined,
|
||||
iconButtonType: IconButtonType.primary,
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
flexibleSpace: isFlexibleSpaceDisabled
|
||||
? null
|
||||
: FlexibleSpaceBarWidget(
|
||||
flexibleSpaceTitle,
|
||||
flexibleSpaceCaption,
|
||||
toolbarHeight,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_actionsWithPaddingInBetween() {
|
||||
|
@ -150,3 +144,89 @@ class TitleBarWidget extends StatelessWidget {
|
|||
return actions;
|
||||
}
|
||||
}
|
||||
|
||||
class TitleWidget extends StatelessWidget {
|
||||
final String? title;
|
||||
final String? caption;
|
||||
final bool isTitleH2WithoutLeading;
|
||||
const TitleWidget(
|
||||
{this.title,
|
||||
this.caption,
|
||||
required this.isTitleH2WithoutLeading,
|
||||
super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = getEnteTextTheme(context);
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(left: isTitleH2WithoutLeading ? 16 : 0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
title == null
|
||||
? const SizedBox.shrink()
|
||||
: Text(
|
||||
title!,
|
||||
style: isTitleH2WithoutLeading
|
||||
? textTheme.h2Bold
|
||||
: textTheme.largeBold,
|
||||
),
|
||||
caption == null || isTitleH2WithoutLeading
|
||||
? const SizedBox.shrink()
|
||||
: Text(
|
||||
caption!,
|
||||
style: textTheme.miniMuted,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FlexibleSpaceBarWidget extends StatelessWidget {
|
||||
final Widget? flexibleSpaceTitle;
|
||||
final String? flexibleSpaceCaption;
|
||||
final double toolbarHeight;
|
||||
const FlexibleSpaceBarWidget(
|
||||
this.flexibleSpaceTitle, this.flexibleSpaceCaption, this.toolbarHeight,
|
||||
{super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = getEnteTextTheme(context);
|
||||
return FlexibleSpaceBar(
|
||||
background: SafeArea(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
SizedBox(height: toolbarHeight),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 4,
|
||||
horizontal: 16,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
flexibleSpaceTitle == null
|
||||
? const SizedBox.shrink()
|
||||
: flexibleSpaceTitle!,
|
||||
flexibleSpaceCaption == null
|
||||
? const SizedBox.shrink()
|
||||
: Text(
|
||||
flexibleSpaceCaption!,
|
||||
style: textTheme.smallMuted,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:dots_indicator/dots_indicator.dart';
|
||||
import "package:flutter/foundation.dart";
|
||||
import 'package:flutter/material.dart';
|
||||
import "package:photos/app.dart";
|
||||
import 'package:photos/core/configuration.dart';
|
||||
import 'package:photos/ente_theme_data.dart';
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import "package:photos/l10n/l10n.dart";
|
||||
import 'package:photos/services/update_service.dart';
|
||||
import 'package:photos/ui/account/email_entry_page.dart';
|
||||
import 'package:photos/ui/account/login_page.dart';
|
||||
|
@ -15,6 +18,8 @@ import 'package:photos/ui/components/buttons/button_widget.dart';
|
|||
import 'package:photos/ui/components/dialog_widget.dart';
|
||||
import 'package:photos/ui/components/models/button_type.dart';
|
||||
import 'package:photos/ui/payment/subscription.dart';
|
||||
import "package:photos/ui/settings/language_picker.dart";
|
||||
import "package:photos/utils/navigation_util.dart";
|
||||
|
||||
class LandingPageWidget extends StatefulWidget {
|
||||
const LandingPageWidget({Key? key}) : super(key: key);
|
||||
|
@ -42,6 +47,31 @@ class _LandingPageWidgetState extends State<LandingPageWidget> {
|
|||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
kDebugMode
|
||||
? GestureDetector(
|
||||
child: const Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: Text("Lang"),
|
||||
),
|
||||
onTap: () async {
|
||||
final locale = await getLocale();
|
||||
routeToPage(
|
||||
context,
|
||||
LanguageSelectorPage(
|
||||
appSupportedLocales,
|
||||
(locale) async {
|
||||
await setLocale(locale);
|
||||
EnteApp.setLocale(context, locale);
|
||||
S.delegate.load(locale);
|
||||
},
|
||||
locale,
|
||||
),
|
||||
).then((value) {
|
||||
setState(() {});
|
||||
});
|
||||
},
|
||||
)
|
||||
: const SizedBox(),
|
||||
const Padding(padding: EdgeInsets.all(12)),
|
||||
const Text(
|
||||
"ente",
|
||||
|
|
|
@ -369,7 +369,7 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
|
|||
color: Colors.white, //same for both themes
|
||||
),
|
||||
onPressed: () {
|
||||
showInfoSheet(context, file);
|
||||
showDetailsSheet(context, file);
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
|
|
|
@ -101,7 +101,7 @@ class _StatusBarWidgetState extends State<StatusBarWidget> {
|
|||
_showErrorBanner
|
||||
? HeaderErrorWidget(error: _syncError)
|
||||
: const SizedBox.shrink(),
|
||||
!UserRemoteFlagService.instance.shouldShowRecoveryVerification()
|
||||
UserRemoteFlagService.instance.shouldShowRecoveryVerification()
|
||||
? Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12),
|
||||
|
|
|
@ -60,6 +60,8 @@ class HugeListView<T> extends StatefulWidget {
|
|||
|
||||
final EdgeInsetsGeometry? thumbPadding;
|
||||
|
||||
final bool disableScroll;
|
||||
|
||||
const HugeListView({
|
||||
Key? key,
|
||||
this.controller,
|
||||
|
@ -77,6 +79,7 @@ class HugeListView<T> extends StatefulWidget {
|
|||
this.bottomSafeArea = 120.0,
|
||||
this.isDraggableScrollbarEnabled = true,
|
||||
this.thumbPadding,
|
||||
this.disableScroll = false,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -160,6 +163,9 @@ class HugeListViewState<T> extends State<HugeListView<T>> {
|
|||
isEnabled: widget.isDraggableScrollbarEnabled,
|
||||
padding: widget.thumbPadding,
|
||||
child: ScrollablePositionedList.builder(
|
||||
physics: widget.disableScroll
|
||||
? const NeverScrollableScrollPhysics()
|
||||
: null,
|
||||
itemScrollController: widget.controller,
|
||||
itemPositionsListener: listener,
|
||||
initialScrollIndex: widget.startIndex,
|
||||
|
|
|
@ -32,11 +32,13 @@ class LazyLoadingGallery extends StatefulWidget {
|
|||
final Stream<FilesUpdatedEvent>? reloadEvent;
|
||||
final Set<EventType> removalEventTypes;
|
||||
final GalleryLoader asyncLoader;
|
||||
final SelectedFiles selectedFiles;
|
||||
final SelectedFiles? selectedFiles;
|
||||
final String tag;
|
||||
final String? logTag;
|
||||
final Stream<int> currentIndexStream;
|
||||
final int photoGirdSize;
|
||||
final bool areFilesCollatedByDay;
|
||||
final bool limitSelectionToOne;
|
||||
LazyLoadingGallery(
|
||||
this.files,
|
||||
this.index,
|
||||
|
@ -45,9 +47,11 @@ class LazyLoadingGallery extends StatefulWidget {
|
|||
this.asyncLoader,
|
||||
this.selectedFiles,
|
||||
this.tag,
|
||||
this.currentIndexStream, {
|
||||
this.currentIndexStream,
|
||||
this.areFilesCollatedByDay, {
|
||||
this.logTag = "",
|
||||
this.photoGirdSize = photoGridSizeDefault,
|
||||
this.limitSelectionToOne = false,
|
||||
Key? key,
|
||||
}) : super(key: key ?? UniqueKey());
|
||||
|
||||
|
@ -62,7 +66,7 @@ class _LazyLoadingGalleryState extends State<LazyLoadingGallery> {
|
|||
late Logger _logger;
|
||||
|
||||
late List<File> _files;
|
||||
late StreamSubscription<FilesUpdatedEvent> _reloadEventSubscription;
|
||||
late StreamSubscription<FilesUpdatedEvent>? _reloadEventSubscription;
|
||||
late StreamSubscription<int> _currentIndexSubscription;
|
||||
bool? _shouldRender;
|
||||
final ValueNotifier<bool> _toggleSelectAllFromDay = ValueNotifier(false);
|
||||
|
@ -72,7 +76,7 @@ class _LazyLoadingGalleryState extends State<LazyLoadingGallery> {
|
|||
@override
|
||||
void initState() {
|
||||
//this is for removing the 'select all from day' icon on unselecting all files with 'cancel'
|
||||
widget.selectedFiles.addListener(_selectedFilesListener);
|
||||
widget.selectedFiles?.addListener(_selectedFilesListener);
|
||||
super.initState();
|
||||
_init();
|
||||
}
|
||||
|
@ -81,7 +85,7 @@ class _LazyLoadingGalleryState extends State<LazyLoadingGallery> {
|
|||
_logger = Logger("LazyLoading_${widget.logTag}");
|
||||
_shouldRender = true;
|
||||
_files = widget.files;
|
||||
_reloadEventSubscription = widget.reloadEvent!.listen((e) => _onReload(e));
|
||||
_reloadEventSubscription = widget.reloadEvent?.listen((e) => _onReload(e));
|
||||
|
||||
_currentIndexSubscription =
|
||||
widget.currentIndexStream.listen((currentIndex) {
|
||||
|
@ -162,9 +166,9 @@ class _LazyLoadingGalleryState extends State<LazyLoadingGallery> {
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
_reloadEventSubscription.cancel();
|
||||
_reloadEventSubscription?.cancel();
|
||||
_currentIndexSubscription.cancel();
|
||||
widget.selectedFiles.removeListener(_selectedFilesListener);
|
||||
widget.selectedFiles?.removeListener(_selectedFilesListener);
|
||||
_toggleSelectAllFromDay.dispose();
|
||||
_showSelectAllButton.dispose();
|
||||
_areAllFromDaySelected.dispose();
|
||||
|
@ -175,7 +179,7 @@ class _LazyLoadingGalleryState extends State<LazyLoadingGallery> {
|
|||
void didUpdateWidget(LazyLoadingGallery oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (!listEquals(_files, widget.files)) {
|
||||
_reloadEventSubscription.cancel();
|
||||
_reloadEventSubscription?.cancel();
|
||||
_init();
|
||||
}
|
||||
}
|
||||
|
@ -190,47 +194,50 @@ class _LazyLoadingGalleryState extends State<LazyLoadingGallery> {
|
|||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
getDayWidget(
|
||||
context,
|
||||
_files[0].creationTime!,
|
||||
widget.photoGirdSize,
|
||||
),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _showSelectAllButton,
|
||||
builder: (context, dynamic value, _) {
|
||||
return !value
|
||||
? const SizedBox.shrink()
|
||||
: GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
child: SizedBox(
|
||||
width: 48,
|
||||
height: 44,
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: _areAllFromDaySelected,
|
||||
builder: (context, dynamic value, _) {
|
||||
return value
|
||||
? const Icon(
|
||||
Icons.check_circle,
|
||||
size: 18,
|
||||
)
|
||||
: Icon(
|
||||
Icons.check_circle_outlined,
|
||||
color: getEnteColorScheme(context)
|
||||
.strokeMuted,
|
||||
size: 18,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
//this value has no significance
|
||||
//changing only to notify the listeners
|
||||
_toggleSelectAllFromDay.value =
|
||||
!_toggleSelectAllFromDay.value;
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
if (widget.areFilesCollatedByDay)
|
||||
getDayWidget(
|
||||
context,
|
||||
_files[0].creationTime!,
|
||||
widget.photoGirdSize,
|
||||
),
|
||||
widget.limitSelectionToOne
|
||||
? const SizedBox.shrink()
|
||||
: ValueListenableBuilder(
|
||||
valueListenable: _showSelectAllButton,
|
||||
builder: (context, dynamic value, _) {
|
||||
return !value
|
||||
? const SizedBox.shrink()
|
||||
: GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
child: SizedBox(
|
||||
width: 48,
|
||||
height: 44,
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: _areAllFromDaySelected,
|
||||
builder: (context, dynamic value, _) {
|
||||
return value
|
||||
? const Icon(
|
||||
Icons.check_circle,
|
||||
size: 18,
|
||||
)
|
||||
: Icon(
|
||||
Icons.check_circle_outlined,
|
||||
color: getEnteColorScheme(context)
|
||||
.strokeMuted,
|
||||
size: 18,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
//this value has no significance
|
||||
//changing only to notify the listeners
|
||||
_toggleSelectAllFromDay.value =
|
||||
!_toggleSelectAllFromDay.value;
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
_shouldRender!
|
||||
|
@ -261,6 +268,7 @@ class _LazyLoadingGalleryState extends State<LazyLoadingGallery> {
|
|||
_toggleSelectAllFromDay,
|
||||
_areAllFromDaySelected,
|
||||
widget.photoGirdSize,
|
||||
limitSelectionToOne: widget.limitSelectionToOne,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -271,7 +279,7 @@ class _LazyLoadingGalleryState extends State<LazyLoadingGallery> {
|
|||
}
|
||||
|
||||
void _selectedFilesListener() {
|
||||
if (widget.selectedFiles.files.isEmpty) {
|
||||
if (widget.selectedFiles!.files.isEmpty) {
|
||||
_showSelectAllButton.value = false;
|
||||
} else {
|
||||
_showSelectAllButton.value = true;
|
||||
|
@ -283,12 +291,13 @@ class LazyLoadingGridView extends StatefulWidget {
|
|||
final String tag;
|
||||
final List<File> filesInDay;
|
||||
final GalleryLoader asyncLoader;
|
||||
final SelectedFiles selectedFiles;
|
||||
final SelectedFiles? selectedFiles;
|
||||
final bool shouldRender;
|
||||
final bool shouldRecycle;
|
||||
final ValueNotifier toggleSelectAllFromDay;
|
||||
final ValueNotifier areAllFilesSelected;
|
||||
final int? photoGridSize;
|
||||
final bool limitSelectionToOne;
|
||||
|
||||
LazyLoadingGridView(
|
||||
this.tag,
|
||||
|
@ -300,6 +309,7 @@ class LazyLoadingGridView extends StatefulWidget {
|
|||
this.toggleSelectAllFromDay,
|
||||
this.areAllFilesSelected,
|
||||
this.photoGridSize, {
|
||||
this.limitSelectionToOne = false,
|
||||
Key? key,
|
||||
}) : super(key: key ?? UniqueKey());
|
||||
|
||||
|
@ -316,7 +326,7 @@ class _LazyLoadingGridViewState extends State<LazyLoadingGridView> {
|
|||
void initState() {
|
||||
_shouldRender = widget.shouldRender;
|
||||
_currentUserID = Configuration.instance.getUserID();
|
||||
widget.selectedFiles.addListener(_selectedFilesListener);
|
||||
widget.selectedFiles?.addListener(_selectedFilesListener);
|
||||
_clearSelectionsEvent =
|
||||
Bus.instance.on<ClearSelectionsEvent>().listen((event) {
|
||||
if (mounted) {
|
||||
|
@ -329,7 +339,7 @@ class _LazyLoadingGridViewState extends State<LazyLoadingGridView> {
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.selectedFiles.removeListener(_selectedFilesListener);
|
||||
widget.selectedFiles?.removeListener(_selectedFilesListener);
|
||||
_clearSelectionsEvent.cancel();
|
||||
widget.toggleSelectAllFromDay
|
||||
.removeListener(_toggleSelectAllFromDayListener);
|
||||
|
@ -403,12 +413,12 @@ class _LazyLoadingGridViewState extends State<LazyLoadingGridView> {
|
|||
mainAxisSpacing: 2,
|
||||
crossAxisCount: widget.photoGridSize!,
|
||||
),
|
||||
padding: const EdgeInsets.all(0),
|
||||
padding: const EdgeInsets.symmetric(vertical: (galleryGridSpacing / 2)),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFile(BuildContext context, File file) {
|
||||
final isFileSelected = widget.selectedFiles.isFileSelected(file);
|
||||
final isFileSelected = widget.selectedFiles?.isFileSelected(file) ?? false;
|
||||
Color selectionColor = Colors.white;
|
||||
if (isFileSelected &&
|
||||
file.isUploaded &&
|
||||
|
@ -421,25 +431,15 @@ class _LazyLoadingGridViewState extends State<LazyLoadingGridView> {
|
|||
selectionColor = avatarColors[(randomID).remainder(avatarColors.length)];
|
||||
}
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
if (widget.selectedFiles.files.isNotEmpty) {
|
||||
_selectFile(file);
|
||||
} else {
|
||||
if (AppLifecycleService.instance.mediaExtensionAction.action ==
|
||||
IntentAction.pick) {
|
||||
final ioFile = await getFile(file);
|
||||
MediaExtension().setResult("file://${ioFile!.path}");
|
||||
} else {
|
||||
_routeToDetailPage(file, context);
|
||||
}
|
||||
}
|
||||
onTap: () {
|
||||
widget.limitSelectionToOne
|
||||
? _onTapWithSelectionLimit(file)
|
||||
: _onTapNoSelectionLimit(file);
|
||||
},
|
||||
onLongPress: () {
|
||||
if (AppLifecycleService.instance.mediaExtensionAction.action ==
|
||||
IntentAction.main) {
|
||||
HapticFeedback.lightImpact();
|
||||
_selectFile(file);
|
||||
}
|
||||
widget.limitSelectionToOne
|
||||
? _onLongPressWithSelectionLimit(file)
|
||||
: _onLongPressNoSelectionLimit(file);
|
||||
},
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(1),
|
||||
|
@ -485,8 +485,50 @@ class _LazyLoadingGridViewState extends State<LazyLoadingGridView> {
|
|||
);
|
||||
}
|
||||
|
||||
void _selectFile(File file) {
|
||||
widget.selectedFiles.toggleSelection(file);
|
||||
void _toggleFileSelection(File file) {
|
||||
widget.selectedFiles!.toggleSelection(file);
|
||||
}
|
||||
|
||||
void _onTapNoSelectionLimit(File file) async {
|
||||
if (widget.selectedFiles?.files.isNotEmpty ?? false) {
|
||||
_toggleFileSelection(file);
|
||||
} else {
|
||||
if (AppLifecycleService.instance.mediaExtensionAction.action ==
|
||||
IntentAction.pick) {
|
||||
final ioFile = await getFile(file);
|
||||
MediaExtension().setResult("file://${ioFile!.path}");
|
||||
} else {
|
||||
_routeToDetailPage(file, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _onTapWithSelectionLimit(File file) {
|
||||
if (widget.selectedFiles!.files.isNotEmpty &&
|
||||
widget.selectedFiles!.files.first != file) {
|
||||
widget.selectedFiles!.clearAll();
|
||||
}
|
||||
_toggleFileSelection(file);
|
||||
}
|
||||
|
||||
void _onLongPressNoSelectionLimit(File file) {
|
||||
if (widget.selectedFiles!.files.isNotEmpty) {
|
||||
_routeToDetailPage(file, context);
|
||||
} else if (AppLifecycleService.instance.mediaExtensionAction.action ==
|
||||
IntentAction.main) {
|
||||
HapticFeedback.lightImpact();
|
||||
_toggleFileSelection(file);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onLongPressWithSelectionLimit(File file) async {
|
||||
if (AppLifecycleService.instance.mediaExtensionAction.action ==
|
||||
IntentAction.pick) {
|
||||
final ioFile = await getFile(file);
|
||||
MediaExtension().setResult("file://${ioFile!.path}");
|
||||
} else {
|
||||
_routeToDetailPage(file, context);
|
||||
}
|
||||
}
|
||||
|
||||
void _routeToDetailPage(File file, BuildContext context) {
|
||||
|
@ -502,14 +544,14 @@ class _LazyLoadingGridViewState extends State<LazyLoadingGridView> {
|
|||
}
|
||||
|
||||
void _selectedFilesListener() {
|
||||
if (widget.selectedFiles.files.containsAll(widget.filesInDay.toSet())) {
|
||||
if (widget.selectedFiles!.files.containsAll(widget.filesInDay.toSet())) {
|
||||
widget.areAllFilesSelected.value = true;
|
||||
} else {
|
||||
widget.areAllFilesSelected.value = false;
|
||||
}
|
||||
bool shouldRefresh = false;
|
||||
for (final file in widget.filesInDay) {
|
||||
if (widget.selectedFiles.isPartOfLastSelected(file)) {
|
||||
if (widget.selectedFiles!.isPartOfLastSelected(file)) {
|
||||
shouldRefresh = true;
|
||||
}
|
||||
}
|
||||
|
@ -519,12 +561,12 @@ class _LazyLoadingGridViewState extends State<LazyLoadingGridView> {
|
|||
}
|
||||
|
||||
void _toggleSelectAllFromDayListener() {
|
||||
if (widget.selectedFiles.files.containsAll(widget.filesInDay.toSet())) {
|
||||
if (widget.selectedFiles!.files.containsAll(widget.filesInDay.toSet())) {
|
||||
setState(() {
|
||||
widget.selectedFiles.unSelectAll(widget.filesInDay.toSet());
|
||||
widget.selectedFiles!.unSelectAll(widget.filesInDay.toSet());
|
||||
});
|
||||
} else {
|
||||
widget.selectedFiles.selectAll(widget.filesInDay.toSet());
|
||||
widget.selectedFiles!.selectAll(widget.filesInDay.toSet());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import "dart:io";
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import 'package:photos/services/update_service.dart';
|
||||
|
@ -104,41 +102,27 @@ class _ChangeLogPageState extends State<ChangeLogPage> {
|
|||
Widget _getChangeLog() {
|
||||
final scrollController = ScrollController();
|
||||
final List<ChangeLogEntry> items = [];
|
||||
items.add(
|
||||
ChangeLogEntry(
|
||||
"Referrals ✨",
|
||||
"You can now double your storage by referring your friends and family. Both you and your loved ones will get 10 GB of storage when "
|
||||
"they upgrade to a paid plan.\n\nGo to Settings -> General -> "
|
||||
"Referrals to get started!",
|
||||
),
|
||||
);
|
||||
if (Platform.isAndroid) {
|
||||
items.add(
|
||||
ChangeLogEntry(
|
||||
"Pick Files",
|
||||
"While sharing photos and videos through other apps, ente will now "
|
||||
"be an option to pick files from. This means you can now easily"
|
||||
" attach files backed up to ente.\n\nConsider this the first "
|
||||
"step towards making ente your default gallery app!",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
items.add(
|
||||
ChangeLogEntry(
|
||||
"Verification ID",
|
||||
"Security of your end-to-end encryption with those you are sharing your "
|
||||
"albums with can now be verified, with the help of Verification IDs."
|
||||
"\n\nPlease click on the Verify button on the album sharing page to learn more.",
|
||||
"Security Audit",
|
||||
"It gives us immense joy to announce that ente's source code has been"
|
||||
" successfully audited by Cure53, in collaboration with Symbolic "
|
||||
"Software.\n\nTogether they have certified that ente's "
|
||||
"architecture is sound and that our implementation across all "
|
||||
"clients is cryptographically accurate.\n\nYou can find more "
|
||||
"details at ente.io/blog.",
|
||||
),
|
||||
);
|
||||
|
||||
items.add(
|
||||
ChangeLogEntry(
|
||||
"Prettier Pixels",
|
||||
"This release is also packed with a bunch of user interface improvements suggested by our community."
|
||||
"\n\nWe have added more actions to your Memories section, introduced archived albums to your Archived section, improved the experience of the Trash screen and sprinkled a few more improvements here and there.",
|
||||
isFeature: false,
|
||||
"Location tags",
|
||||
"This release includes a fresh, beautiful, privacy-friendly way to "
|
||||
"search through your photos by location!\n\nTag a photo with a "
|
||||
"location, define a radius, and ente will automatically cluster "
|
||||
"all photos clicked within that area.\n\nOpen a photo, and click on"
|
||||
" the Info button to get started!",
|
||||
),
|
||||
);
|
||||
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import "package:photos/app.dart";
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import "package:photos/l10n/l10n.dart";
|
||||
import 'package:photos/services/billing_service.dart';
|
||||
import "package:photos/services/feature_flag_service.dart";
|
||||
import 'package:photos/services/user_service.dart';
|
||||
import 'package:photos/theme/ente_theme.dart';
|
||||
import 'package:photos/ui/advanced_settings_screen.dart';
|
||||
|
@ -9,6 +12,7 @@ import 'package:photos/ui/components/expandable_menu_item_widget.dart';
|
|||
import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart';
|
||||
import "package:photos/ui/growth/referral_screen.dart";
|
||||
import 'package:photos/ui/settings/common_settings.dart';
|
||||
import "package:photos/ui/settings/language_picker.dart";
|
||||
import 'package:photos/utils/navigation_util.dart';
|
||||
|
||||
class GeneralSectionWidget extends StatelessWidget {
|
||||
|
@ -24,21 +28,10 @@ class GeneralSectionWidget extends StatelessWidget {
|
|||
}
|
||||
|
||||
Widget _getSectionOptions(BuildContext context) {
|
||||
final bool showLanguageChangeOption =
|
||||
FeatureFlagService.instance.isInternalUserOrDebugBuild();
|
||||
return Column(
|
||||
children: [
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: S.of(context).familyPlans,
|
||||
),
|
||||
pressedColor: getEnteColorScheme(context).fillFaint,
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
showOnlyLoadingState: true,
|
||||
onTap: () async {
|
||||
await _onFamilyPlansTapped(context);
|
||||
},
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
|
@ -56,6 +49,46 @@ class GeneralSectionWidget extends StatelessWidget {
|
|||
},
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: S.of(context).familyPlans,
|
||||
),
|
||||
pressedColor: getEnteColorScheme(context).fillFaint,
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
showOnlyLoadingState: true,
|
||||
onTap: () async {
|
||||
await _onFamilyPlansTapped(context);
|
||||
},
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
showLanguageChangeOption
|
||||
? MenuItemWidget(
|
||||
captionedTextWidget:
|
||||
CaptionedTextWidget(title: S.of(context).language),
|
||||
pressedColor: getEnteColorScheme(context).fillFaint,
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
final locale = await getLocale();
|
||||
routeToPage(
|
||||
context,
|
||||
LanguageSelectorPage(
|
||||
appSupportedLocales,
|
||||
(locale) async {
|
||||
await setLocale(locale);
|
||||
EnteApp.setLocale(context, locale);
|
||||
S.load(locale);
|
||||
},
|
||||
locale,
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
showLanguageChangeOption
|
||||
? sectionOptionSpacing
|
||||
: const SizedBox.shrink(),
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: S.of(context).advanced,
|
||||
|
|
173
lib/ui/settings/language_picker.dart
Normal file
173
lib/ui/settings/language_picker.dart
Normal file
|
@ -0,0 +1,173 @@
|
|||
import "package:flutter/foundation.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:photos/l10n/l10n.dart";
|
||||
import "package:photos/theme/ente_theme.dart";
|
||||
import "package:photos/ui/components/captioned_text_widget.dart";
|
||||
import "package:photos/ui/components/divider_widget.dart";
|
||||
import "package:photos/ui/components/menu_item_widget/menu_item_widget.dart";
|
||||
import "package:photos/ui/components/title_bar_title_widget.dart";
|
||||
import "package:photos/ui/components/title_bar_widget.dart";
|
||||
import "package:photos/utils/separators_util.dart";
|
||||
|
||||
class LanguageSelectorPage extends StatelessWidget {
|
||||
final List<Locale> supportedLocales;
|
||||
final ValueChanged<Locale> onLocaleChanged;
|
||||
final Locale currentLocale;
|
||||
|
||||
const LanguageSelectorPage(
|
||||
this.supportedLocales,
|
||||
this.onLocaleChanged,
|
||||
this.currentLocale, {
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: CustomScrollView(
|
||||
primary: false,
|
||||
slivers: <Widget>[
|
||||
TitleBarWidget(
|
||||
flexibleSpaceTitle: TitleBarTitleWidget(
|
||||
title: context.l10n.selectLanguage,
|
||||
),
|
||||
),
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 20,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(8)),
|
||||
child: ItemsWidget(
|
||||
supportedLocales,
|
||||
onLocaleChanged,
|
||||
currentLocale,
|
||||
),
|
||||
),
|
||||
// MenuSectionDescriptionWidget(
|
||||
// content: context.l10n.maxDeviceLimitSpikeHandling(50),
|
||||
// )
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
childCount: 1,
|
||||
),
|
||||
),
|
||||
const SliverPadding(padding: EdgeInsets.symmetric(vertical: 12)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ItemsWidget extends StatefulWidget {
|
||||
final List<Locale> supportedLocales;
|
||||
final ValueChanged<Locale> onLocaleChanged;
|
||||
final Locale currentLocale;
|
||||
|
||||
const ItemsWidget(
|
||||
this.supportedLocales,
|
||||
this.onLocaleChanged,
|
||||
this.currentLocale, {
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<ItemsWidget> createState() => _ItemsWidgetState();
|
||||
}
|
||||
|
||||
class _ItemsWidgetState extends State<ItemsWidget> {
|
||||
late Locale currentLocale;
|
||||
List<Widget> items = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
currentLocale = widget.currentLocale;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
items.clear();
|
||||
for (Locale locale in widget.supportedLocales) {
|
||||
items.add(
|
||||
_menuItemForPicker(locale),
|
||||
);
|
||||
}
|
||||
items = addSeparators(
|
||||
items,
|
||||
DividerWidget(
|
||||
dividerType: DividerType.menuNoIcon,
|
||||
bgColor: getEnteColorScheme(context).fillFaint,
|
||||
),
|
||||
);
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: items,
|
||||
);
|
||||
}
|
||||
|
||||
String _getLanguageName(Locale locale) {
|
||||
switch (locale.languageCode) {
|
||||
case 'en':
|
||||
return 'English';
|
||||
case 'es':
|
||||
return 'Español';
|
||||
case 'fr':
|
||||
return 'Français';
|
||||
case 'de':
|
||||
return 'Deutsch';
|
||||
case 'it':
|
||||
return 'Italiano';
|
||||
case 'nl':
|
||||
return 'Nederlands';
|
||||
case 'pt':
|
||||
return 'Português';
|
||||
case 'ru':
|
||||
return 'Русский';
|
||||
case 'tr':
|
||||
return 'Türkçe';
|
||||
case 'fi':
|
||||
return 'Suomi';
|
||||
case 'zh':
|
||||
return '中文';
|
||||
case 'ja':
|
||||
return '日本語';
|
||||
case 'ko':
|
||||
return '한국어';
|
||||
case 'ar':
|
||||
return 'العربية';
|
||||
default:
|
||||
return locale.languageCode;
|
||||
}
|
||||
}
|
||||
|
||||
Widget _menuItemForPicker(Locale locale) {
|
||||
return MenuItemWidget(
|
||||
key: ValueKey(locale.toString()),
|
||||
menuItemColor: getEnteColorScheme(context).fillFaint,
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: _getLanguageName(locale) + (kDebugMode ? ' ($locale)' : ''),
|
||||
),
|
||||
trailingIcon: currentLocale == locale ? Icons.check : null,
|
||||
alignCaptionedTextToLeft: true,
|
||||
isTopBorderRadiusRemoved: true,
|
||||
isBottomBorderRadiusRemoved: true,
|
||||
showOnlyLoadingState: true,
|
||||
onTap: () async {
|
||||
widget.onLocaleChanged(locale);
|
||||
currentLocale = locale;
|
||||
setState(() {});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -35,6 +35,7 @@ class AppLock extends StatefulWidget {
|
|||
final ThemeData? darkTheme;
|
||||
final ThemeData? lightTheme;
|
||||
final ThemeMode savedThemeMode;
|
||||
final Locale locale;
|
||||
|
||||
const AppLock({
|
||||
Key? key,
|
||||
|
@ -42,6 +43,7 @@ class AppLock extends StatefulWidget {
|
|||
required this.lockScreen,
|
||||
required this.savedThemeMode,
|
||||
this.enabled = true,
|
||||
this.locale = const Locale("en", "US"),
|
||||
this.backgroundLockLatency = const Duration(seconds: 0),
|
||||
this.darkTheme,
|
||||
this.lightTheme,
|
||||
|
@ -110,6 +112,7 @@ class _AppLockState extends State<AppLock> with WidgetsBindingObserver {
|
|||
themeMode: widget.savedThemeMode,
|
||||
theme: widget.lightTheme,
|
||||
darkTheme: widget.darkTheme,
|
||||
locale: widget.locale,
|
||||
supportedLocales: appSupportedLocales,
|
||||
localeListResolutionCallback: localResolutionCallBack,
|
||||
localizationsDelegates: const [
|
||||
|
|
|
@ -13,7 +13,7 @@ import 'package:photos/core/event_bus.dart';
|
|||
import 'package:photos/db/files_db.dart';
|
||||
import 'package:photos/events/local_photos_updated_event.dart';
|
||||
import 'package:photos/models/file.dart' as ente;
|
||||
import 'package:photos/models/location.dart';
|
||||
import 'package:photos/models/location/location.dart';
|
||||
import 'package:photos/services/sync_service.dart';
|
||||
import 'package:photos/ui/common/loading_widget.dart';
|
||||
import 'package:photos/ui/components/action_sheet_widget.dart';
|
||||
|
@ -362,7 +362,10 @@ class _ImageEditorPageState extends State<ImageEditorPage> {
|
|||
final assetEntity = await widget.originalFile.getAsset;
|
||||
if (assetEntity != null) {
|
||||
final latLong = await assetEntity.latlngAsync();
|
||||
newFile.location = Location(latLong.latitude, latLong.longitude);
|
||||
newFile.location = Location(
|
||||
latitude: latLong.latitude,
|
||||
longitude: latLong.longitude,
|
||||
);
|
||||
}
|
||||
}
|
||||
newFile.generatedID = await FilesDB.instance.insert(newFile);
|
||||
|
|
|
@ -76,7 +76,7 @@ class FadingBottomBarState extends State<FadingBottomBar> {
|
|||
color: Colors.white,
|
||||
),
|
||||
onPressed: () async {
|
||||
await _displayInfo(widget.file);
|
||||
await _displayDetails(widget.file);
|
||||
safeRefresh(); //to instantly show the new caption if keypad is closed after pressing 'done' - here the caption will be updated before the bottom sheet is closed
|
||||
await Future.delayed(
|
||||
const Duration(milliseconds: 500),
|
||||
|
@ -268,7 +268,7 @@ class FadingBottomBarState extends State<FadingBottomBar> {
|
|||
);
|
||||
}
|
||||
|
||||
Future<void> _displayInfo(File file) async {
|
||||
await showInfoSheet(context, file);
|
||||
Future<void> _displayDetails(File file) async {
|
||||
await showDetailsSheet(context, file);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ class _FileCaptionWidgetState extends State<FileCaptionWidget> {
|
|||
final _focusNode = FocusNode();
|
||||
String? editedCaption;
|
||||
String hintText = fileCaptionDefaultHint;
|
||||
Widget? keyboardTopButtoms;
|
||||
Widget? keyboardTopButtons;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -172,12 +172,12 @@ class _FileCaptionWidgetState extends State<FileCaptionWidget> {
|
|||
editedCaption = caption;
|
||||
}
|
||||
final bool hasFocus = _focusNode.hasFocus;
|
||||
keyboardTopButtoms ??= KeyboardTopButton(
|
||||
keyboardTopButtons ??= KeyboardTopButton(
|
||||
onDoneTap: onDoneTap,
|
||||
onCancelTap: onCancelTap,
|
||||
);
|
||||
if (hasFocus) {
|
||||
KeyboardOverlay.showOverlay(context, keyboardTopButtoms!);
|
||||
KeyboardOverlay.showOverlay(context, keyboardTopButtons!);
|
||||
} else {
|
||||
KeyboardOverlay.removeOverlay();
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import 'package:photos/ui/viewer/file_details/backed_up_time_item_widget.dart';
|
|||
import "package:photos/ui/viewer/file_details/creation_time_item_widget.dart";
|
||||
import 'package:photos/ui/viewer/file_details/exif_item_widgets.dart';
|
||||
import "package:photos/ui/viewer/file_details/file_properties_item_widget.dart";
|
||||
import "package:photos/ui/viewer/file_details/location_tags_widget.dart";
|
||||
import "package:photos/ui/viewer/file_details/objects_item_widget.dart";
|
||||
import "package:photos/utils/exif_util.dart";
|
||||
|
||||
|
@ -39,12 +40,17 @@ class _FileDetailsWidgetState extends State<FileDetailsWidget> {
|
|||
"takenOnDevice": null,
|
||||
"exposureTime": null,
|
||||
"ISO": null,
|
||||
"megaPixels": null
|
||||
"megaPixels": null,
|
||||
"lat": null,
|
||||
"long": null,
|
||||
"latRef": null,
|
||||
"longRef": null,
|
||||
};
|
||||
|
||||
bool _isImage = false;
|
||||
late int _currentUserID;
|
||||
bool showExifListTile = false;
|
||||
bool hasLocationData = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -52,6 +58,12 @@ class _FileDetailsWidgetState extends State<FileDetailsWidget> {
|
|||
_currentUserID = Configuration.instance.getUserID()!;
|
||||
_isImage = widget.file.fileType == FileType.image ||
|
||||
widget.file.fileType == FileType.livePhoto;
|
||||
_exifNotifier.addListener(() {
|
||||
if (_exifNotifier.value != null) {
|
||||
_generateExifForLocation(_exifNotifier.value!);
|
||||
hasLocationData = _hasLocationData();
|
||||
}
|
||||
});
|
||||
if (_isImage) {
|
||||
_exifNotifier.addListener(() {
|
||||
if (_exifNotifier.value != null) {
|
||||
|
@ -63,10 +75,10 @@ class _FileDetailsWidgetState extends State<FileDetailsWidget> {
|
|||
_exifData["exposureTime"] != null ||
|
||||
_exifData["ISO"] != null;
|
||||
});
|
||||
getExif(widget.file).then((exif) {
|
||||
_exifNotifier.value = exif;
|
||||
});
|
||||
}
|
||||
getExif(widget.file).then((exif) {
|
||||
_exifNotifier.value = exif;
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
@ -125,6 +137,25 @@ class _FileDetailsWidgetState extends State<FileDetailsWidget> {
|
|||
},
|
||||
),
|
||||
);
|
||||
if (FeatureFlagService.instance.isInternalUserOrDebugBuild()) {
|
||||
fileDetailsTiles.addAll([
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _exifNotifier,
|
||||
builder: (context, _, __) {
|
||||
return hasLocationData
|
||||
? Column(
|
||||
children: [
|
||||
LocationTagsWidget(
|
||||
widget.file.location!,
|
||||
),
|
||||
const FileDetailsDivider(),
|
||||
],
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
},
|
||||
)
|
||||
]);
|
||||
}
|
||||
if (_isImage) {
|
||||
fileDetailsTiles.addAll([
|
||||
ValueListenableBuilder(
|
||||
|
@ -200,6 +231,38 @@ class _FileDetailsWidgetState extends State<FileDetailsWidget> {
|
|||
);
|
||||
}
|
||||
|
||||
bool _hasLocationData() {
|
||||
final fileLocation = widget.file.location;
|
||||
final hasLocation = (fileLocation != null &&
|
||||
fileLocation.latitude != null &&
|
||||
fileLocation.longitude != null) &&
|
||||
(fileLocation.latitude != 0 || fileLocation.longitude != 0);
|
||||
return hasLocation;
|
||||
}
|
||||
|
||||
void _generateExifForLocation(Map<String, IfdTag> exif) {
|
||||
if (exif["GPS GPSLatitude"] != null) {
|
||||
_exifData["lat"] = exif["GPS GPSLatitude"]!
|
||||
.values
|
||||
.toList()
|
||||
.map((e) => ((e as Ratio).numerator / e.denominator))
|
||||
.toList();
|
||||
}
|
||||
if (exif["GPS GPSLongitude"] != null) {
|
||||
_exifData["long"] = exif["GPS GPSLongitude"]!
|
||||
.values
|
||||
.toList()
|
||||
.map((e) => ((e as Ratio).numerator / e.denominator))
|
||||
.toList();
|
||||
}
|
||||
if (exif["GPS GPSLatitudeRef"] != null) {
|
||||
_exifData["latRef"] = exif["GPS GPSLatitudeRef"].toString();
|
||||
}
|
||||
if (exif["GPS GPSLongitudeRef"] != null) {
|
||||
_exifData["longRef"] = exif["GPS GPSLongitudeRef"].toString();
|
||||
}
|
||||
}
|
||||
|
||||
_generateExifForDetails(Map<String, IfdTag> exif) {
|
||||
if (exif["EXIF FocalLength"] != null) {
|
||||
_exifData["focalLength"] =
|
||||
|
|
121
lib/ui/viewer/file_details/location_tags_widget.dart
Normal file
121
lib/ui/viewer/file_details/location_tags_widget.dart
Normal file
|
@ -0,0 +1,121 @@
|
|||
import "dart:async";
|
||||
|
||||
import "package:flutter/material.dart";
|
||||
import "package:photos/core/event_bus.dart";
|
||||
import "package:photos/events/location_tag_updated_event.dart";
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import "package:photos/models/location/location.dart";
|
||||
import "package:photos/services/location_service.dart";
|
||||
import "package:photos/states/location_screen_state.dart";
|
||||
import "package:photos/theme/ente_theme.dart";
|
||||
import "package:photos/ui/components/buttons/chip_button_widget.dart";
|
||||
import "package:photos/ui/components/info_item_widget.dart";
|
||||
import 'package:photos/ui/viewer/location/add_location_sheet.dart';
|
||||
import "package:photos/ui/viewer/location/location_screen.dart";
|
||||
import "package:photos/utils/navigation_util.dart";
|
||||
|
||||
class LocationTagsWidget extends StatefulWidget {
|
||||
final Location centerPoint;
|
||||
const LocationTagsWidget(this.centerPoint, {super.key});
|
||||
|
||||
@override
|
||||
State<LocationTagsWidget> createState() => _LocationTagsWidgetState();
|
||||
}
|
||||
|
||||
class _LocationTagsWidgetState extends State<LocationTagsWidget> {
|
||||
String? title;
|
||||
IconData? leadingIcon;
|
||||
bool? hasChipButtons;
|
||||
late Future<List<Widget>> locationTagChips;
|
||||
late StreamSubscription<LocationTagUpdatedEvent> _locTagUpdateListener;
|
||||
VoidCallback? onTap;
|
||||
@override
|
||||
void initState() {
|
||||
locationTagChips = _getLocationTags();
|
||||
_locTagUpdateListener =
|
||||
Bus.instance.on<LocationTagUpdatedEvent>().listen((event) {
|
||||
locationTagChips = _getLocationTags();
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_locTagUpdateListener.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
switchInCurve: Curves.easeInOutExpo,
|
||||
switchOutCurve: Curves.easeInOutExpo,
|
||||
child: InfoItemWidget(
|
||||
key: ValueKey(title),
|
||||
leadingIcon: leadingIcon ?? Icons.pin_drop_outlined,
|
||||
title: title,
|
||||
subtitleSection: locationTagChips,
|
||||
hasChipButtons: hasChipButtons ?? true,
|
||||
onTap: onTap,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<Widget>> _getLocationTags() async {
|
||||
final locationTags = await LocationService.instance
|
||||
.enclosingLocationTags(widget.centerPoint);
|
||||
if (locationTags.isEmpty) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
title = S.of(context).addLocation;
|
||||
leadingIcon = Icons.add_location_alt_outlined;
|
||||
hasChipButtons = false;
|
||||
onTap = () => showAddLocationSheet(
|
||||
context,
|
||||
widget.centerPoint,
|
||||
);
|
||||
});
|
||||
}
|
||||
return [
|
||||
Text(
|
||||
S.of(context).groupNearbyPhotos,
|
||||
style: getEnteTextTheme(context).smallMuted,
|
||||
)
|
||||
];
|
||||
} else {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
title = S.of(context).location;
|
||||
leadingIcon = Icons.pin_drop_outlined;
|
||||
hasChipButtons = true;
|
||||
onTap = null;
|
||||
});
|
||||
}
|
||||
final result = locationTags
|
||||
.map(
|
||||
(locationTagEntity) => ChipButtonWidget(
|
||||
locationTagEntity.item.name,
|
||||
onTap: () {
|
||||
routeToPage(
|
||||
context,
|
||||
LocationScreenStateProvider(
|
||||
locationTagEntity,
|
||||
const LocationScreen(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
result.add(
|
||||
ChipButtonWidget(
|
||||
null,
|
||||
leadingIcon: Icons.add_outlined,
|
||||
onTap: () => showAddLocationSheet(context, widget.centerPoint),
|
||||
),
|
||||
);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -33,18 +33,22 @@ class Gallery extends StatefulWidget {
|
|||
final Stream<FilesUpdatedEvent>? reloadEvent;
|
||||
final List<Stream<Event>>? forceReloadEvents;
|
||||
final Set<EventType> removalEventTypes;
|
||||
final SelectedFiles selectedFiles;
|
||||
final SelectedFiles? selectedFiles;
|
||||
final String tagPrefix;
|
||||
final Widget? header;
|
||||
final Widget? footer;
|
||||
final Widget emptyState;
|
||||
final String? albumName;
|
||||
final double scrollBottomSafeArea;
|
||||
final bool shouldCollateFilesByDay;
|
||||
final Widget loadingWidget;
|
||||
final bool disableScroll;
|
||||
final bool limitSelectionToOne;
|
||||
|
||||
const Gallery({
|
||||
required this.asyncLoader,
|
||||
required this.selectedFiles,
|
||||
required this.tagPrefix,
|
||||
this.selectedFiles,
|
||||
this.initialFiles,
|
||||
this.reloadEvent,
|
||||
this.forceReloadEvents,
|
||||
|
@ -54,6 +58,10 @@ class Gallery extends StatefulWidget {
|
|||
this.emptyState = const EmptyState(),
|
||||
this.scrollBottomSafeArea = 120.0,
|
||||
this.albumName = '',
|
||||
this.shouldCollateFilesByDay = true,
|
||||
this.loadingWidget = const EnteLoadingWidget(),
|
||||
this.disableScroll = false,
|
||||
this.limitSelectionToOne = false,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
|
@ -168,7 +176,8 @@ class _GalleryState extends State<Gallery> {
|
|||
|
||||
// Collates files and returns `true` if it resulted in a gallery reload
|
||||
bool _onFilesLoaded(List<File> files) {
|
||||
final updatedCollatedFiles = _collateFiles(files);
|
||||
final updatedCollatedFiles =
|
||||
widget.shouldCollateFilesByDay ? _collateFiles(files) : [files];
|
||||
if (_collatedFiles.length != updatedCollatedFiles.length ||
|
||||
_collatedFiles.isEmpty) {
|
||||
if (mounted) {
|
||||
|
@ -198,7 +207,7 @@ class _GalleryState extends State<Gallery> {
|
|||
Widget build(BuildContext context) {
|
||||
_logger.finest("Building Gallery ${widget.tagPrefix}");
|
||||
if (!_hasLoadedFiles) {
|
||||
return const EnteLoadingWidget();
|
||||
return widget.loadingWidget;
|
||||
}
|
||||
_photoGridSize = LocalSettings.instance.getPhotoGridSize();
|
||||
return _getListView();
|
||||
|
@ -211,6 +220,7 @@ class _GalleryState extends State<Gallery> {
|
|||
startIndex: 0,
|
||||
totalCount: _collatedFiles.length,
|
||||
isDraggableScrollbarEnabled: _collatedFiles.length > 10,
|
||||
disableScroll: widget.disableScroll,
|
||||
waitBuilder: (_) {
|
||||
return const EnteLoadingWidget();
|
||||
},
|
||||
|
@ -246,8 +256,10 @@ class _GalleryState extends State<Gallery> {
|
|||
.on<GalleryIndexUpdatedEvent>()
|
||||
.where((event) => event.tag == widget.tagPrefix)
|
||||
.map((event) => event.index),
|
||||
widget.shouldCollateFilesByDay,
|
||||
logTag: _logTag,
|
||||
photoGirdSize: _photoGridSize,
|
||||
limitSelectionToOne: widget.limitSelectionToOne,
|
||||
);
|
||||
if (widget.header != null && index == 0) {
|
||||
gallery = Column(children: [widget.header!, gallery]);
|
||||
|
|
267
lib/ui/viewer/location/add_location_sheet.dart
Normal file
267
lib/ui/viewer/location/add_location_sheet.dart
Normal file
|
@ -0,0 +1,267 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import "package:modal_bottom_sheet/modal_bottom_sheet.dart";
|
||||
import "package:photos/core/constants.dart";
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import "package:photos/models/location/location.dart";
|
||||
import "package:photos/services/location_service.dart";
|
||||
import 'package:photos/states/location_state.dart';
|
||||
import "package:photos/theme/colors.dart";
|
||||
import "package:photos/theme/ente_theme.dart";
|
||||
import "package:photos/ui/common/loading_widget.dart";
|
||||
import "package:photos/ui/components/bottom_of_title_bar_widget.dart";
|
||||
import "package:photos/ui/components/buttons/button_widget.dart";
|
||||
import "package:photos/ui/components/divider_widget.dart";
|
||||
import "package:photos/ui/components/keyboard/keybiard_oveylay.dart";
|
||||
import "package:photos/ui/components/keyboard/keyboard_top_button.dart";
|
||||
import "package:photos/ui/components/models/button_type.dart";
|
||||
import "package:photos/ui/components/text_input_widget.dart";
|
||||
import "package:photos/ui/components/title_bar_title_widget.dart";
|
||||
import 'package:photos/ui/viewer/location/dynamic_location_gallery_widget.dart';
|
||||
import "package:photos/ui/viewer/location/radius_picker_widget.dart";
|
||||
|
||||
showAddLocationSheet(
|
||||
BuildContext context,
|
||||
Location coordinates,
|
||||
) {
|
||||
showBarModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return LocationTagStateProvider(
|
||||
centerPoint: coordinates,
|
||||
const AddLocationSheet(),
|
||||
);
|
||||
},
|
||||
shape: const RoundedRectangleBorder(
|
||||
side: BorderSide(width: 0),
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(5),
|
||||
),
|
||||
),
|
||||
topControl: const SizedBox.shrink(),
|
||||
backgroundColor: getEnteColorScheme(context).backgroundElevated,
|
||||
barrierColor: backdropFaintDark,
|
||||
);
|
||||
}
|
||||
|
||||
class AddLocationSheet extends StatefulWidget {
|
||||
const AddLocationSheet({super.key});
|
||||
|
||||
@override
|
||||
State<AddLocationSheet> createState() => _AddLocationSheetState();
|
||||
}
|
||||
|
||||
class _AddLocationSheetState extends State<AddLocationSheet> {
|
||||
//The value of these notifiers has no significance.
|
||||
//When memoriesCountNotifier is null, we show the loading widget in the
|
||||
//memories count section which also means the gallery is loading.
|
||||
final ValueNotifier<int?> _memoriesCountNotifier = ValueNotifier(null);
|
||||
final ValueNotifier<bool> _submitNotifer = ValueNotifier(false);
|
||||
final ValueNotifier<bool> _cancelNotifier = ValueNotifier(false);
|
||||
final ValueNotifier<int> _selectedRadiusIndexNotifier =
|
||||
ValueNotifier(defaultRadiusValueIndex);
|
||||
final _focusNode = FocusNode();
|
||||
final _textEditingController = TextEditingController();
|
||||
final _isEmptyNotifier = ValueNotifier(true);
|
||||
Widget? _keyboardTopButtons;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_focusNode.addListener(_focusNodeListener);
|
||||
_selectedRadiusIndexNotifier.addListener(_selectedRadiusIndexListener);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_focusNode.removeListener(_focusNodeListener);
|
||||
_submitNotifer.dispose();
|
||||
_cancelNotifier.dispose();
|
||||
_selectedRadiusIndexNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = getEnteTextTheme(context);
|
||||
final colorScheme = getEnteColorScheme(context);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(0, 32, 0, 8),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
child: BottomOfTitleBarWidget(
|
||||
title: TitleBarTitleWidget(title: S.of(context).addLocation),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(
|
||||
decelerationRate: ScrollDecelerationRate.fast,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextInputWidget(
|
||||
hintText: S.of(context).locationName,
|
||||
borderRadius: 2,
|
||||
focusNode: _focusNode,
|
||||
submitNotifier: _submitNotifer,
|
||||
cancelNotifier: _cancelNotifier,
|
||||
popNavAfterSubmission: false,
|
||||
shouldUnfocusOnClearOrSubmit: true,
|
||||
alwaysShowSuccessState: true,
|
||||
textCapitalization: TextCapitalization.words,
|
||||
textEditingController: _textEditingController,
|
||||
isEmptyNotifier: _isEmptyNotifier,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _isEmptyNotifier,
|
||||
builder: (context, bool value, _) {
|
||||
return AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
switchInCurve: Curves.easeInOut,
|
||||
switchOutCurve: Curves.easeInOut,
|
||||
child: ButtonWidget(
|
||||
key: ValueKey(value),
|
||||
buttonType: ButtonType.secondary,
|
||||
buttonSize: ButtonSize.small,
|
||||
labelText: S.of(context).addLocationButton,
|
||||
isDisabled: value,
|
||||
onTap: () async {
|
||||
_focusNode.unfocus();
|
||||
await _addLocationTag();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
RadiusPickerWidget(
|
||||
_selectedRadiusIndexNotifier,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
S.of(context).locationTagFeatureDescription,
|
||||
style: textTheme.smallMuted,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const DividerWidget(
|
||||
dividerType: DividerType.solid,
|
||||
padding: EdgeInsets.only(top: 24, bottom: 20),
|
||||
),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: _memoriesCountNotifier,
|
||||
builder: (context, int? value, _) {
|
||||
Widget widget;
|
||||
if (value == null) {
|
||||
widget = RepaintBoundary(
|
||||
child: EnteLoadingWidget(
|
||||
size: 14,
|
||||
color: colorScheme.strokeMuted,
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: 3,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
widget = Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).memoryCount(value),
|
||||
style: textTheme.body,
|
||||
),
|
||||
if (value > 1000)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 2),
|
||||
child: Text(
|
||||
S.of(context).galleryMemoryLimitInfo,
|
||||
style: textTheme.miniMuted,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
switchInCurve: Curves.easeInOutExpo,
|
||||
switchOutCurve: Curves.easeInOutExpo,
|
||||
child: widget,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
DynamicLocationGalleryWidget(
|
||||
_memoriesCountNotifier,
|
||||
"Add_location",
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _addLocationTag() async {
|
||||
final locationData = InheritedLocationTagData.of(context);
|
||||
final coordinates = locationData.centerPoint;
|
||||
final radius = radiusValues[locationData.selectedRadiusIndex];
|
||||
await LocationService.instance.addLocation(
|
||||
_textEditingController.text.trim(),
|
||||
coordinates,
|
||||
radius,
|
||||
);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
|
||||
void _focusNodeListener() {
|
||||
final bool hasFocus = _focusNode.hasFocus;
|
||||
_keyboardTopButtons ??= KeyboardTopButton(
|
||||
onDoneTap: () {
|
||||
_submitNotifer.value = !_submitNotifer.value;
|
||||
},
|
||||
onCancelTap: () {
|
||||
_cancelNotifier.value = !_cancelNotifier.value;
|
||||
},
|
||||
);
|
||||
if (hasFocus) {
|
||||
KeyboardOverlay.showOverlay(context, _keyboardTopButtons!);
|
||||
} else {
|
||||
KeyboardOverlay.removeOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
void _selectedRadiusIndexListener() {
|
||||
InheritedLocationTagData.of(
|
||||
context,
|
||||
).updateSelectedIndex(
|
||||
_selectedRadiusIndexNotifier.value,
|
||||
);
|
||||
_memoriesCountNotifier.value = null;
|
||||
}
|
||||
}
|
144
lib/ui/viewer/location/dynamic_location_gallery_widget.dart
Normal file
144
lib/ui/viewer/location/dynamic_location_gallery_widget.dart
Normal file
|
@ -0,0 +1,144 @@
|
|||
import "dart:developer" as dev;
|
||||
import "dart:math";
|
||||
|
||||
import "package:flutter/material.dart";
|
||||
import "package:photos/core/constants.dart";
|
||||
import "package:photos/db/files_db.dart";
|
||||
import "package:photos/models/file.dart";
|
||||
import "package:photos/models/file_load_result.dart";
|
||||
import "package:photos/services/collections_service.dart";
|
||||
import "package:photos/services/files_service.dart";
|
||||
import "package:photos/services/location_service.dart";
|
||||
import 'package:photos/states/location_state.dart';
|
||||
import "package:photos/ui/viewer/gallery/gallery.dart";
|
||||
import "package:photos/utils/local_settings.dart";
|
||||
|
||||
///This gallery will get rebuilt with the updated radius when
|
||||
///InheritedLocationTagData notifies a change in radius.
|
||||
class DynamicLocationGalleryWidget extends StatefulWidget {
|
||||
final ValueNotifier<int?> memoriesCountNotifier;
|
||||
final String tagPrefix;
|
||||
const DynamicLocationGalleryWidget(
|
||||
this.memoriesCountNotifier,
|
||||
this.tagPrefix, {
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<DynamicLocationGalleryWidget> createState() =>
|
||||
_DynamicLocationGalleryWidgetState();
|
||||
}
|
||||
|
||||
class _DynamicLocationGalleryWidgetState
|
||||
extends State<DynamicLocationGalleryWidget> {
|
||||
late final Future<FileLoadResult> fileLoadResult;
|
||||
late Future<void> removeIgnoredFiles;
|
||||
double heightOfGallery = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final collectionsToHide =
|
||||
CollectionsService.instance.collectionsHiddenFromTimeline();
|
||||
fileLoadResult =
|
||||
FilesDB.instance.fetchAllUploadedAndSharedFilesWithLocation(
|
||||
galleryLoadStartTime,
|
||||
galleryLoadEndTime,
|
||||
limit: null,
|
||||
asc: false,
|
||||
ignoredCollectionIDs: collectionsToHide,
|
||||
);
|
||||
removeIgnoredFiles =
|
||||
FilesService.instance.removeIgnoredFiles(fileLoadResult);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const galleryFilesLimit = 1000;
|
||||
final selectedRadius = _selectedRadius();
|
||||
Future<FileLoadResult> filterFiles() async {
|
||||
final FileLoadResult result = await fileLoadResult;
|
||||
//wait for ignored files to be removed after init
|
||||
await removeIgnoredFiles;
|
||||
final stopWatch = Stopwatch()..start();
|
||||
final copyOfFiles = List<File>.from(result.files);
|
||||
copyOfFiles.removeWhere((f) {
|
||||
return !LocationService.instance.isFileInsideLocationTag(
|
||||
InheritedLocationTagData.of(context).centerPoint,
|
||||
f.location!,
|
||||
selectedRadius,
|
||||
);
|
||||
});
|
||||
dev.log(
|
||||
"Time taken to get all files in a location tag: ${stopWatch.elapsedMilliseconds} ms",
|
||||
);
|
||||
stopWatch.stop();
|
||||
widget.memoriesCountNotifier.value = copyOfFiles.length;
|
||||
final limitedResults = copyOfFiles.take(galleryFilesLimit).toList();
|
||||
|
||||
return Future.value(
|
||||
FileLoadResult(
|
||||
limitedResults,
|
||||
result.hasMore,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return FutureBuilder(
|
||||
//Only rebuild Gallery if the center point or radius changes
|
||||
key: ValueKey(
|
||||
"${InheritedLocationTagData.of(context).centerPoint}$selectedRadius",
|
||||
),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
return SizedBox(
|
||||
height: _galleryHeight(
|
||||
min(
|
||||
(widget.memoriesCountNotifier.value ?? 0),
|
||||
galleryFilesLimit,
|
||||
),
|
||||
),
|
||||
child: Gallery(
|
||||
loadingWidget: const SizedBox.shrink(),
|
||||
disableScroll: true,
|
||||
asyncLoader: (
|
||||
creationStartTime,
|
||||
creationEndTime, {
|
||||
limit,
|
||||
asc,
|
||||
}) async {
|
||||
return snapshot.data as FileLoadResult;
|
||||
},
|
||||
tagPrefix: widget.tagPrefix,
|
||||
shouldCollateFilesByDay: false,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
},
|
||||
future: filterFiles(),
|
||||
);
|
||||
}
|
||||
|
||||
int _selectedRadius() {
|
||||
return radiusValues[
|
||||
InheritedLocationTagData.of(context).selectedRadiusIndex];
|
||||
}
|
||||
|
||||
double _galleryHeight(int fileCount) {
|
||||
final photoGridSize = LocalSettings.instance.getPhotoGridSize();
|
||||
final totalWhiteSpaceBetweenPhotos =
|
||||
galleryGridSpacing * (photoGridSize - 1);
|
||||
|
||||
final thumbnailHeight =
|
||||
((MediaQuery.of(context).size.width - totalWhiteSpaceBetweenPhotos) /
|
||||
photoGridSize);
|
||||
|
||||
final numberOfRows = (fileCount / photoGridSize).ceil();
|
||||
|
||||
final galleryHeight = (thumbnailHeight * numberOfRows) +
|
||||
(galleryGridSpacing * (numberOfRows - 1));
|
||||
return galleryHeight + 120;
|
||||
}
|
||||
}
|
70
lib/ui/viewer/location/edit_center_point_tile_widget.dart
Normal file
70
lib/ui/viewer/location/edit_center_point_tile_widget.dart
Normal file
|
@ -0,0 +1,70 @@
|
|||
import "package:flutter/material.dart";
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import "package:photos/models/file.dart";
|
||||
import "package:photos/services/location_service.dart";
|
||||
import "package:photos/states/location_state.dart";
|
||||
import "package:photos/theme/ente_theme.dart";
|
||||
import "package:photos/ui/viewer/location/pick_center_point_widget.dart";
|
||||
|
||||
class EditCenterPointTileWidget extends StatelessWidget {
|
||||
const EditCenterPointTileWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = getEnteTextTheme(context);
|
||||
final colorScheme = getEnteColorScheme(context);
|
||||
return Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
color: colorScheme.fillFaint,
|
||||
child: Icon(
|
||||
Icons.location_on_outlined,
|
||||
color: colorScheme.strokeFaint,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 4.5, 16, 4.5),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).centerPoint,
|
||||
style: textTheme.body,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
LocationService.instance.convertLocationToDMS(
|
||||
InheritedLocationTagData.of(context)
|
||||
.locationTagEntity!
|
||||
.item
|
||||
.centerPoint,
|
||||
),
|
||||
style: textTheme.miniMuted,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
final File? centerPointFile = await showPickCenterPointSheet(
|
||||
context,
|
||||
InheritedLocationTagData.of(context).locationTagEntity!,
|
||||
);
|
||||
if (centerPointFile != null) {
|
||||
InheritedLocationTagData.of(context)
|
||||
.updateCenterPoint(centerPointFile.location!);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.edit),
|
||||
color: getEnteColorScheme(context).strokeMuted,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
275
lib/ui/viewer/location/edit_location_sheet.dart
Normal file
275
lib/ui/viewer/location/edit_location_sheet.dart
Normal file
|
@ -0,0 +1,275 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import "package:modal_bottom_sheet/modal_bottom_sheet.dart";
|
||||
import "package:photos/core/constants.dart";
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import "package:photos/models/local_entity_data.dart";
|
||||
import "package:photos/models/location_tag/location_tag.dart";
|
||||
import "package:photos/services/location_service.dart";
|
||||
import "package:photos/states/location_state.dart";
|
||||
import "package:photos/theme/colors.dart";
|
||||
import "package:photos/theme/ente_theme.dart";
|
||||
import "package:photos/ui/common/loading_widget.dart";
|
||||
import "package:photos/ui/components/bottom_of_title_bar_widget.dart";
|
||||
import "package:photos/ui/components/buttons/button_widget.dart";
|
||||
import "package:photos/ui/components/divider_widget.dart";
|
||||
import "package:photos/ui/components/keyboard/keybiard_oveylay.dart";
|
||||
import "package:photos/ui/components/keyboard/keyboard_top_button.dart";
|
||||
import "package:photos/ui/components/models/button_type.dart";
|
||||
import "package:photos/ui/components/text_input_widget.dart";
|
||||
import "package:photos/ui/components/title_bar_title_widget.dart";
|
||||
import 'package:photos/ui/viewer/location/dynamic_location_gallery_widget.dart';
|
||||
import "package:photos/ui/viewer/location/edit_center_point_tile_widget.dart";
|
||||
import "package:photos/ui/viewer/location/radius_picker_widget.dart";
|
||||
|
||||
showEditLocationSheet(
|
||||
BuildContext context,
|
||||
LocalEntity<LocationTag> locationTagEntity,
|
||||
) {
|
||||
showBarModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return LocationTagStateProvider(
|
||||
locationTagEntity: locationTagEntity,
|
||||
const EditLocationSheet(),
|
||||
);
|
||||
},
|
||||
shape: const RoundedRectangleBorder(
|
||||
side: BorderSide(width: 0),
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(5),
|
||||
),
|
||||
),
|
||||
topControl: const SizedBox.shrink(),
|
||||
backgroundColor: getEnteColorScheme(context).backgroundElevated,
|
||||
barrierColor: backdropFaintDark,
|
||||
);
|
||||
}
|
||||
|
||||
class EditLocationSheet extends StatefulWidget {
|
||||
const EditLocationSheet({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<EditLocationSheet> createState() => _EditLocationSheetState();
|
||||
}
|
||||
|
||||
class _EditLocationSheetState extends State<EditLocationSheet> {
|
||||
//The value of these notifiers has no significance.
|
||||
//When memoriesCountNotifier is null, we show the loading widget in the
|
||||
//memories count section which also means the gallery is loading.
|
||||
final ValueNotifier<int?> _memoriesCountNotifier = ValueNotifier(null);
|
||||
final ValueNotifier<bool> _submitNotifer = ValueNotifier(false);
|
||||
final ValueNotifier<bool> _cancelNotifier = ValueNotifier(false);
|
||||
final ValueNotifier<int> _selectedRadiusIndexNotifier =
|
||||
ValueNotifier(defaultRadiusValueIndex);
|
||||
final _focusNode = FocusNode();
|
||||
final _textEditingController = TextEditingController();
|
||||
final _isEmptyNotifier = ValueNotifier(false);
|
||||
Widget? _keyboardTopButtons;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_focusNode.addListener(_focusNodeListener);
|
||||
_selectedRadiusIndexNotifier.addListener(_selectedRadiusIndexListener);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_focusNode.removeListener(_focusNodeListener);
|
||||
_submitNotifer.dispose();
|
||||
_cancelNotifier.dispose();
|
||||
_selectedRadiusIndexNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = getEnteTextTheme(context);
|
||||
final colorScheme = getEnteColorScheme(context);
|
||||
final locationName =
|
||||
InheritedLocationTagData.of(context).locationTagEntity!.item.name;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(0, 32, 0, 8),
|
||||
child: Column(
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(bottom: 16),
|
||||
child: BottomOfTitleBarWidget(
|
||||
title: TitleBarTitleWidget(title: "Edit location"),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(
|
||||
decelerationRate: ScrollDecelerationRate.fast,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextInputWidget(
|
||||
hintText: S.of(context).locationName,
|
||||
borderRadius: 2,
|
||||
focusNode: _focusNode,
|
||||
submitNotifier: _submitNotifer,
|
||||
cancelNotifier: _cancelNotifier,
|
||||
popNavAfterSubmission: false,
|
||||
shouldUnfocusOnClearOrSubmit: true,
|
||||
alwaysShowSuccessState: true,
|
||||
initialValue: locationName,
|
||||
onCancel: () {
|
||||
_focusNode.unfocus();
|
||||
_textEditingController.value =
|
||||
TextEditingValue(text: locationName);
|
||||
},
|
||||
textEditingController: _textEditingController,
|
||||
isEmptyNotifier: _isEmptyNotifier,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _isEmptyNotifier,
|
||||
builder: (context, bool value, _) {
|
||||
return AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
switchInCurve: Curves.easeInOut,
|
||||
switchOutCurve: Curves.easeInOut,
|
||||
child: ButtonWidget(
|
||||
key: ValueKey(value),
|
||||
buttonType: ButtonType.secondary,
|
||||
buttonSize: ButtonSize.small,
|
||||
labelText: S.of(context).save,
|
||||
isDisabled: value,
|
||||
onTap: () async {
|
||||
_focusNode.unfocus();
|
||||
await _editLocation();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
const EditCenterPointTileWidget(),
|
||||
const SizedBox(height: 20),
|
||||
RadiusPickerWidget(
|
||||
_selectedRadiusIndexNotifier,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
const DividerWidget(
|
||||
dividerType: DividerType.solid,
|
||||
padding: EdgeInsets.only(top: 24, bottom: 20),
|
||||
),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: _memoriesCountNotifier,
|
||||
builder: (context, int? value, _) {
|
||||
Widget widget;
|
||||
if (value == null) {
|
||||
widget = RepaintBoundary(
|
||||
child: EnteLoadingWidget(
|
||||
size: 14,
|
||||
color: colorScheme.strokeMuted,
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: 3,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
widget = Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).memoryCount(value),
|
||||
style: textTheme.body,
|
||||
),
|
||||
if (value > 1000)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 2),
|
||||
child: Text(
|
||||
S.of(context).galleryMemoryLimitInfo,
|
||||
style: textTheme.miniMuted,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
switchInCurve: Curves.easeInOutExpo,
|
||||
switchOutCurve: Curves.easeInOutExpo,
|
||||
child: widget,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
DynamicLocationGalleryWidget(
|
||||
_memoriesCountNotifier,
|
||||
"Edit_location",
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _editLocation() async {
|
||||
final locationTagState = InheritedLocationTagData.of(context);
|
||||
await LocationService.instance.updateLocationTag(
|
||||
locationTagEntity: locationTagState.locationTagEntity!,
|
||||
newRadius: radiusValues[locationTagState.selectedRadiusIndex],
|
||||
newName: _textEditingController.text.trim(),
|
||||
newCenterPoint: InheritedLocationTagData.of(context).centerPoint,
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
void _focusNodeListener() {
|
||||
final bool hasFocus = _focusNode.hasFocus;
|
||||
_keyboardTopButtons ??= KeyboardTopButton(
|
||||
onDoneTap: () {
|
||||
_submitNotifer.value = !_submitNotifer.value;
|
||||
},
|
||||
onCancelTap: () {
|
||||
_cancelNotifier.value = !_cancelNotifier.value;
|
||||
},
|
||||
);
|
||||
if (hasFocus) {
|
||||
KeyboardOverlay.showOverlay(context, _keyboardTopButtons!);
|
||||
} else {
|
||||
KeyboardOverlay.removeOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
void _selectedRadiusIndexListener() {
|
||||
InheritedLocationTagData.of(
|
||||
context,
|
||||
).updateSelectedIndex(
|
||||
_selectedRadiusIndexNotifier.value,
|
||||
);
|
||||
_memoriesCountNotifier.value = null;
|
||||
}
|
||||
}
|
302
lib/ui/viewer/location/location_screen.dart
Normal file
302
lib/ui/viewer/location/location_screen.dart
Normal file
|
@ -0,0 +1,302 @@
|
|||
import 'dart:developer' as dev;
|
||||
import "package:flutter/material.dart";
|
||||
import "package:photos/core/constants.dart";
|
||||
import "package:photos/core/event_bus.dart";
|
||||
import "package:photos/db/files_db.dart";
|
||||
import "package:photos/events/files_updated_event.dart";
|
||||
import "package:photos/events/local_photos_updated_event.dart";
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import "package:photos/models/file.dart";
|
||||
import "package:photos/models/file_load_result.dart";
|
||||
import "package:photos/models/gallery_type.dart";
|
||||
import "package:photos/models/selected_files.dart";
|
||||
import "package:photos/services/collections_service.dart";
|
||||
import "package:photos/services/files_service.dart";
|
||||
import "package:photos/services/location_service.dart";
|
||||
import "package:photos/states/location_screen_state.dart";
|
||||
import "package:photos/theme/colors.dart";
|
||||
import "package:photos/theme/ente_theme.dart";
|
||||
import "package:photos/ui/common/loading_widget.dart";
|
||||
import "package:photos/ui/components/buttons/icon_button_widget.dart";
|
||||
import "package:photos/ui/components/title_bar_title_widget.dart";
|
||||
import "package:photos/ui/components/title_bar_widget.dart";
|
||||
import "package:photos/ui/viewer/actions/file_selection_overlay_bar.dart";
|
||||
import "package:photos/ui/viewer/gallery/gallery.dart";
|
||||
import "package:photos/ui/viewer/location/edit_location_sheet.dart";
|
||||
import "package:photos/utils/dialog_util.dart";
|
||||
|
||||
class LocationScreen extends StatelessWidget {
|
||||
const LocationScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: const PreferredSize(
|
||||
preferredSize: Size(double.infinity, 48),
|
||||
child: TitleBarWidget(
|
||||
isSliver: false,
|
||||
isFlexibleSpaceDisabled: true,
|
||||
actionIcons: [LocationScreenPopUpMenu()],
|
||||
),
|
||||
),
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height - 102,
|
||||
width: double.infinity,
|
||||
child: const LocationGalleryWidget(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LocationScreenPopUpMenu extends StatelessWidget {
|
||||
const LocationScreenPopUpMenu({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = getEnteTextTheme(context);
|
||||
final colorScheme = getEnteColorScheme(context);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 4),
|
||||
child: Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
),
|
||||
child: PopupMenuButton(
|
||||
elevation: 2,
|
||||
offset: const Offset(10, 50),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
color: colorScheme.backgroundElevated2,
|
||||
child: const IconButtonWidget(
|
||||
icon: Icons.more_horiz,
|
||||
iconButtonType: IconButtonType.primary,
|
||||
disableGestureDetector: true,
|
||||
),
|
||||
itemBuilder: (context) {
|
||||
return [
|
||||
PopupMenuItem(
|
||||
value: "edit",
|
||||
child: Text(
|
||||
S.of(context).edit,
|
||||
style: textTheme.bodyBold,
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
onTap: () {},
|
||||
value: "delete",
|
||||
child: Text(
|
||||
S.of(context).deleteLocation,
|
||||
style: textTheme.bodyBold.copyWith(color: warning500),
|
||||
),
|
||||
),
|
||||
];
|
||||
},
|
||||
onSelected: (value) async {
|
||||
if (value == "edit") {
|
||||
showEditLocationSheet(
|
||||
context,
|
||||
InheritedLocationScreenState.of(context).locationTagEntity,
|
||||
);
|
||||
} else if (value == "delete") {
|
||||
try {
|
||||
await LocationService.instance.deleteLocationTag(
|
||||
InheritedLocationScreenState.of(context).locationTagEntity.id,
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
} catch (e) {
|
||||
showGenericErrorDialog(context: context);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LocationGalleryWidget extends StatefulWidget {
|
||||
const LocationGalleryWidget({super.key});
|
||||
|
||||
@override
|
||||
State<LocationGalleryWidget> createState() => _LocationGalleryWidgetState();
|
||||
}
|
||||
|
||||
class _LocationGalleryWidgetState extends State<LocationGalleryWidget> {
|
||||
late final Future<FileLoadResult> fileLoadResult;
|
||||
late Future<void> removeIgnoredFiles;
|
||||
late Widget galleryHeaderWidget;
|
||||
final _selectedFiles = SelectedFiles();
|
||||
@override
|
||||
void initState() {
|
||||
final collectionsToHide =
|
||||
CollectionsService.instance.collectionsHiddenFromTimeline();
|
||||
fileLoadResult =
|
||||
FilesDB.instance.fetchAllUploadedAndSharedFilesWithLocation(
|
||||
galleryLoadStartTime,
|
||||
galleryLoadEndTime,
|
||||
limit: null,
|
||||
asc: false,
|
||||
ignoredCollectionIDs: collectionsToHide,
|
||||
);
|
||||
removeIgnoredFiles =
|
||||
FilesService.instance.removeIgnoredFiles(fileLoadResult);
|
||||
galleryHeaderWidget = const GalleryHeaderWidget();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
InheritedLocationScreenState.memoryCountNotifier.value = null;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final selectedRadius =
|
||||
InheritedLocationScreenState.of(context).locationTagEntity.item.radius;
|
||||
final centerPoint = InheritedLocationScreenState.of(context)
|
||||
.locationTagEntity
|
||||
.item
|
||||
.centerPoint;
|
||||
Future<FileLoadResult> filterFiles() async {
|
||||
final FileLoadResult result = await fileLoadResult;
|
||||
//wait for ignored files to be removed after init
|
||||
await removeIgnoredFiles;
|
||||
final stopWatch = Stopwatch()..start();
|
||||
final copyOfFiles = List<File>.from(result.files);
|
||||
copyOfFiles.removeWhere((f) {
|
||||
return !LocationService.instance.isFileInsideLocationTag(
|
||||
centerPoint,
|
||||
f.location!,
|
||||
selectedRadius,
|
||||
);
|
||||
});
|
||||
dev.log(
|
||||
"Time taken to get all files in a location tag: ${stopWatch.elapsedMilliseconds} ms",
|
||||
);
|
||||
stopWatch.stop();
|
||||
InheritedLocationScreenState.memoryCountNotifier.value =
|
||||
copyOfFiles.length;
|
||||
|
||||
return Future.value(
|
||||
FileLoadResult(
|
||||
copyOfFiles,
|
||||
result.hasMore,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return FutureBuilder(
|
||||
//rebuild gallery only when there is change in radius or center point
|
||||
key: ValueKey("$centerPoint$selectedRadius"),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
return Stack(
|
||||
children: [
|
||||
Gallery(
|
||||
loadingWidget: Column(
|
||||
children: [
|
||||
galleryHeaderWidget,
|
||||
EnteLoadingWidget(
|
||||
color: getEnteColorScheme(context).strokeMuted,
|
||||
),
|
||||
],
|
||||
),
|
||||
header: galleryHeaderWidget,
|
||||
asyncLoader: (
|
||||
creationStartTime,
|
||||
creationEndTime, {
|
||||
limit,
|
||||
asc,
|
||||
}) async {
|
||||
return snapshot.data as FileLoadResult;
|
||||
},
|
||||
reloadEvent: Bus.instance.on<LocalPhotosUpdatedEvent>(),
|
||||
removalEventTypes: const {
|
||||
EventType.deletedFromRemote,
|
||||
EventType.deletedFromEverywhere,
|
||||
},
|
||||
selectedFiles: _selectedFiles,
|
||||
tagPrefix: "location_gallery",
|
||||
),
|
||||
FileSelectionOverlayBar(
|
||||
GalleryType.locationTag,
|
||||
_selectedFiles,
|
||||
)
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Column(
|
||||
children: [
|
||||
galleryHeaderWidget,
|
||||
const Expanded(
|
||||
child: EnteLoadingWidget(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
},
|
||||
future: filterFiles(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class GalleryHeaderWidget extends StatefulWidget {
|
||||
const GalleryHeaderWidget({super.key});
|
||||
|
||||
@override
|
||||
State<GalleryHeaderWidget> createState() => _GalleryHeaderWidgetState();
|
||||
}
|
||||
|
||||
class _GalleryHeaderWidgetState extends State<GalleryHeaderWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final locationName =
|
||||
InheritedLocationScreenState.of(context).locationTagEntity.item.name;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 20),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 16),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
key: ValueKey(locationName),
|
||||
width: double.infinity,
|
||||
child: TitleBarTitleWidget(
|
||||
title: locationName,
|
||||
),
|
||||
),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: InheritedLocationScreenState.memoryCountNotifier,
|
||||
builder: (context, int? value, _) {
|
||||
if (value == null) {
|
||||
return RepaintBoundary(
|
||||
child: EnteLoadingWidget(
|
||||
size: 12,
|
||||
color: getEnteColorScheme(context).strokeMuted,
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: 2.5,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Text(
|
||||
S.of(context).memoryCount(value),
|
||||
style: getEnteTextTheme(context).smallMuted,
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
183
lib/ui/viewer/location/pick_center_point_widget.dart
Normal file
183
lib/ui/viewer/location/pick_center_point_widget.dart
Normal file
|
@ -0,0 +1,183 @@
|
|||
import "dart:math";
|
||||
|
||||
import "package:flutter/material.dart";
|
||||
import "package:modal_bottom_sheet/modal_bottom_sheet.dart";
|
||||
import "package:photos/core/constants.dart";
|
||||
import "package:photos/core/event_bus.dart";
|
||||
import "package:photos/db/files_db.dart";
|
||||
import "package:photos/events/local_photos_updated_event.dart";
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import "package:photos/models/file.dart";
|
||||
import "package:photos/models/file_load_result.dart";
|
||||
import "package:photos/models/local_entity_data.dart";
|
||||
import "package:photos/models/location_tag/location_tag.dart";
|
||||
import "package:photos/models/selected_files.dart";
|
||||
import "package:photos/services/collections_service.dart";
|
||||
import "package:photos/services/ignored_files_service.dart";
|
||||
import "package:photos/theme/colors.dart";
|
||||
import "package:photos/theme/ente_theme.dart";
|
||||
import "package:photos/ui/components/bottom_of_title_bar_widget.dart";
|
||||
import "package:photos/ui/components/buttons/button_widget.dart";
|
||||
import "package:photos/ui/components/models/button_type.dart";
|
||||
import "package:photos/ui/components/title_bar_title_widget.dart";
|
||||
import "package:photos/ui/viewer/gallery/gallery.dart";
|
||||
|
||||
Future<File?> showPickCenterPointSheet(
|
||||
BuildContext context,
|
||||
LocalEntity<LocationTag> locationTagEntity,
|
||||
) async {
|
||||
return await showBarModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return PickCenterPointWidget(locationTagEntity);
|
||||
},
|
||||
shape: const RoundedRectangleBorder(
|
||||
side: BorderSide(width: 0),
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(5),
|
||||
),
|
||||
),
|
||||
topControl: const SizedBox.shrink(),
|
||||
backgroundColor: getEnteColorScheme(context).backgroundElevated,
|
||||
barrierColor: backdropFaintDark,
|
||||
enableDrag: false,
|
||||
);
|
||||
}
|
||||
|
||||
class PickCenterPointWidget extends StatelessWidget {
|
||||
final LocalEntity<LocationTag> locationTagEntity;
|
||||
|
||||
const PickCenterPointWidget(
|
||||
this.locationTagEntity, {
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ValueNotifier<bool> isFileSelected = ValueNotifier(false);
|
||||
final selectedFiles = SelectedFiles();
|
||||
selectedFiles.addListener(() {
|
||||
isFileSelected.value = selectedFiles.files.isNotEmpty;
|
||||
});
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: min(428, MediaQuery.of(context).size.width),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(0, 32, 0, 8),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
BottomOfTitleBarWidget(
|
||||
title: TitleBarTitleWidget(
|
||||
title: S.of(context).pickCenterPoint,
|
||||
),
|
||||
caption: locationTagEntity.item.name,
|
||||
),
|
||||
Expanded(
|
||||
child: Gallery(
|
||||
asyncLoader: (
|
||||
creationStartTime,
|
||||
creationEndTime, {
|
||||
limit,
|
||||
asc,
|
||||
}) async {
|
||||
final collectionsToHide = CollectionsService
|
||||
.instance
|
||||
.collectionsHiddenFromTimeline();
|
||||
FileLoadResult result;
|
||||
result = await FilesDB.instance
|
||||
.fetchAllUploadedAndSharedFilesWithLocation(
|
||||
galleryLoadStartTime,
|
||||
galleryLoadEndTime,
|
||||
limit: null,
|
||||
asc: false,
|
||||
ignoredCollectionIDs: collectionsToHide,
|
||||
);
|
||||
|
||||
// hide ignored files from UI
|
||||
final ignoredIDs =
|
||||
await IgnoredFilesService.instance.ignoredIDs;
|
||||
result.files.removeWhere(
|
||||
(f) =>
|
||||
f.uploadedFileID == null &&
|
||||
IgnoredFilesService.instance
|
||||
.shouldSkipUpload(ignoredIDs, f),
|
||||
);
|
||||
return result;
|
||||
},
|
||||
reloadEvent:
|
||||
Bus.instance.on<LocalPhotosUpdatedEvent>(),
|
||||
tagPrefix: "pick_center_point_gallery",
|
||||
selectedFiles: selectedFiles,
|
||||
limitSelectionToOne: true,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SafeArea(
|
||||
child: Container(
|
||||
//inner stroke of 1pt + 15 pts of top padding = 16 pts
|
||||
padding: const EdgeInsets.fromLTRB(16, 15, 16, 8),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
top: BorderSide(
|
||||
color: getEnteColorScheme(context).strokeFaint,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
ValueListenableBuilder(
|
||||
valueListenable: isFileSelected,
|
||||
builder: (context, bool value, _) {
|
||||
return AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
switchInCurve: Curves.easeInOutExpo,
|
||||
switchOutCurve: Curves.easeInOutExpo,
|
||||
child: ButtonWidget(
|
||||
key: ValueKey(value),
|
||||
isDisabled: !value,
|
||||
buttonType: ButtonType.neutral,
|
||||
labelText: S.of(context).useSelectedPhoto,
|
||||
onTap: () async {
|
||||
final selectedFile =
|
||||
selectedFiles.files.first;
|
||||
Navigator.pop(context, selectedFile);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
ButtonWidget(
|
||||
buttonType: ButtonType.secondary,
|
||||
buttonAction: ButtonAction.cancel,
|
||||
labelText: S.of(context).cancel,
|
||||
onTap: () async {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
144
lib/ui/viewer/location/radius_picker_widget.dart
Normal file
144
lib/ui/viewer/location/radius_picker_widget.dart
Normal file
|
@ -0,0 +1,144 @@
|
|||
import "package:flutter/material.dart";
|
||||
import "package:photos/core/constants.dart";
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import "package:photos/states/location_state.dart";
|
||||
import "package:photos/theme/colors.dart";
|
||||
import "package:photos/theme/ente_theme.dart";
|
||||
|
||||
class CustomTrackShape extends RoundedRectSliderTrackShape {
|
||||
@override
|
||||
Rect getPreferredRect({
|
||||
required RenderBox parentBox,
|
||||
Offset offset = Offset.zero,
|
||||
required SliderThemeData sliderTheme,
|
||||
bool isEnabled = false,
|
||||
bool isDiscrete = false,
|
||||
}) {
|
||||
const trackHeight = 2.0;
|
||||
final trackWidth = parentBox.size.width;
|
||||
return Rect.fromLTWH(0, 0, trackWidth, trackHeight);
|
||||
}
|
||||
}
|
||||
|
||||
class RadiusPickerWidget extends StatefulWidget {
|
||||
///This notifier can be listened to get the selected radius index from
|
||||
///a parent widget.
|
||||
final ValueNotifier<int> selectedRadiusIndexNotifier;
|
||||
const RadiusPickerWidget(
|
||||
this.selectedRadiusIndexNotifier, {
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<RadiusPickerWidget> createState() => _RadiusPickerWidgetState();
|
||||
}
|
||||
|
||||
class _RadiusPickerWidgetState extends State<RadiusPickerWidget> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
widget.selectedRadiusIndexNotifier.value =
|
||||
InheritedLocationTagData.of(context).selectedRadiusIndex;
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final selectedRadiusIndex = widget.selectedRadiusIndexNotifier.value;
|
||||
final radiusValue = radiusValues[selectedRadiusIndex];
|
||||
final textTheme = getEnteTextTheme(context);
|
||||
final colorScheme = getEnteColorScheme(context);
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
height: 48,
|
||||
width: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.fillFaint,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(2)),
|
||||
),
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 6,
|
||||
child: Text(
|
||||
radiusValue.toString(),
|
||||
style: radiusValue != 1200
|
||||
? textTheme.largeBold
|
||||
: textTheme.bodyBold,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 5,
|
||||
child: Text(
|
||||
S.of(context).kiloMeterUnit,
|
||||
style: textTheme.miniMuted,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(S.of(context).radius, style: textTheme.body),
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(
|
||||
height: 16,
|
||||
child: SliderTheme(
|
||||
data: SliderThemeData(
|
||||
overlayColor: Colors.transparent,
|
||||
thumbColor: strokeSolidMutedLight,
|
||||
activeTrackColor: strokeSolidMutedLight,
|
||||
inactiveTrackColor: colorScheme.strokeFaint,
|
||||
activeTickMarkColor: colorScheme.strokeMuted,
|
||||
inactiveTickMarkColor: strokeSolidMutedLight,
|
||||
trackShape: CustomTrackShape(),
|
||||
thumbShape: const RoundSliderThumbShape(
|
||||
enabledThumbRadius: 6,
|
||||
pressedElevation: 0,
|
||||
elevation: 0,
|
||||
),
|
||||
tickMarkShape: const RoundSliderTickMarkShape(
|
||||
tickMarkRadius: 1,
|
||||
),
|
||||
),
|
||||
child: RepaintBoundary(
|
||||
child: Slider(
|
||||
value: selectedRadiusIndex.toDouble(),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
widget.selectedRadiusIndexNotifier.value =
|
||||
value.toInt();
|
||||
});
|
||||
},
|
||||
min: 0,
|
||||
max: radiusValues.length - 1,
|
||||
divisions: radiusValues.length - 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -74,7 +74,12 @@ class SearchSuggestionsWidget extends StatelessWidget {
|
|||
} else if (result is FileSearchResult) {
|
||||
return FileSearchResultWidget(result);
|
||||
} else if (result is GenericSearchResult) {
|
||||
return SearchResultWidget(result);
|
||||
return SearchResultWidget(
|
||||
result,
|
||||
onResultTap: result.onResultTap != null
|
||||
? () => result.onResultTap!(context)
|
||||
: null,
|
||||
);
|
||||
} else {
|
||||
Logger('SearchSuggestionsWidget')
|
||||
.info("Invalid/Unsupported value");
|
||||
|
|
|
@ -5,7 +5,6 @@ import 'package:logging/logging.dart';
|
|||
import 'package:photos/ente_theme_data.dart';
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import 'package:photos/models/search/search_result.dart';
|
||||
import 'package:photos/services/feature_flag_service.dart';
|
||||
import 'package:photos/services/search_service.dart';
|
||||
import 'package:photos/ui/components/buttons/icon_button_widget.dart';
|
||||
import 'package:photos/ui/viewer/search/result/no_result_widget.dart';
|
||||
|
@ -215,16 +214,19 @@ class _SearchWidgetState extends State<SearchWidget> {
|
|||
await _searchService.getFileExtensionResults(query);
|
||||
allResults.addAll(fileExtnResult);
|
||||
|
||||
final locationResult = await _searchService.getLocationResults(query);
|
||||
allResults.addAll(locationResult);
|
||||
|
||||
final collectionResults =
|
||||
await _searchService.getCollectionSearchResults(query);
|
||||
allResults.addAll(collectionResults);
|
||||
|
||||
if (FeatureFlagService.instance.isInternalUserOrDebugBuild() &&
|
||||
query.startsWith("l:")) {
|
||||
final locationResults = await _searchService
|
||||
.getLocationSearchResults(query.replaceAll("l:", ""));
|
||||
allResults.addAll(locationResults);
|
||||
}
|
||||
// if (FeatureFlagService.instance.isInternalUserOrDebugBuild() &&
|
||||
// query.startsWith("l:")) {
|
||||
// final locationResults = await _searchService
|
||||
// .getLocationSearchResults(query.replaceAll("l:", ""));
|
||||
// allResults.addAll(locationResults);
|
||||
// }
|
||||
|
||||
final monthResults = await _searchService.getMonthSearchResults(query);
|
||||
allResults.addAll(monthResults);
|
||||
|
|
|
@ -13,7 +13,7 @@ import 'package:photos/core/constants.dart';
|
|||
import 'package:photos/core/errors.dart';
|
||||
import 'package:photos/models/file.dart' as ente;
|
||||
import 'package:photos/models/file_type.dart';
|
||||
import 'package:photos/models/location.dart';
|
||||
import 'package:photos/models/location/location.dart';
|
||||
import 'package:photos/utils/crypto_util.dart';
|
||||
import 'package:photos/utils/file_util.dart';
|
||||
import 'package:video_thumbnail/video_thumbnail.dart';
|
||||
|
@ -155,7 +155,8 @@ Future<void> _decorateEnteFileData(ente.File file, AssetEntity asset) async {
|
|||
if (file.location == null ||
|
||||
(file.location!.latitude == 0 && file.location!.longitude == 0)) {
|
||||
final latLong = await asset.latlngAsync();
|
||||
file.location = Location(latLong.latitude, latLong.longitude);
|
||||
file.location =
|
||||
Location(latitude: latLong.latitude, longitude: latLong.longitude);
|
||||
}
|
||||
|
||||
if (file.title == null || file.title!.isEmpty) {
|
||||
|
|
14
lib/utils/lat_lon_util.dart
Normal file
14
lib/utils/lat_lon_util.dart
Normal file
|
@ -0,0 +1,14 @@
|
|||
String convertLatLng(double decimal, bool isLat) {
|
||||
final degree = "${decimal.toString().split(".")[0]}°";
|
||||
final minutesBeforeConversion =
|
||||
double.parse("0.${decimal.toString().split(".")[1]}");
|
||||
final minutes = "${(minutesBeforeConversion * 60).toString().split('.')[0]}'";
|
||||
final secondsBeforeConversion = double.parse(
|
||||
"0.${(minutesBeforeConversion * 60).toString().split('.')[1]}",
|
||||
);
|
||||
final seconds =
|
||||
'${double.parse((secondsBeforeConversion * 60).toString()).toStringAsFixed(0)}" ';
|
||||
final dmsOutput =
|
||||
"$degree$minutes$seconds${isLat ? decimal > 0 ? 'N' : 'S' : decimal > 0 ? 'E' : 'W'}";
|
||||
return dmsOutput;
|
||||
}
|
162
pubspec.lock
162
pubspec.lock
|
@ -97,6 +97,70 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
build:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build
|
||||
sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
build_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_config
|
||||
sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
build_daemon:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_daemon
|
||||
sha256: "757153e5d9cd88253cb13f28c2fb55a537dc31fefd98137549895b5beb7c6169"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
build_resolvers:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_resolvers
|
||||
sha256: db49b8609ef8c81cca2b310618c3017c00f03a92af44c04d310b907b2d692d95
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
build_runner:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.3"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_runner_core
|
||||
sha256: "14febe0f5bac5ae474117a36099b4de6f1dbc52df6c5e55534b3da9591bf4292"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.2.7"
|
||||
built_collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_collection
|
||||
sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.1.1"
|
||||
built_value:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_value
|
||||
sha256: "31b7c748fd4b9adf8d25d72a4c4a59ef119f12876cf414f94f8af5131d5fa2b0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.4.4"
|
||||
cached_network_image:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -169,6 +233,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
checked_yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: checked_yaml
|
||||
sha256: "3d1505d91afa809d177efd4eed5bb0eb65805097a1463abdd2add076effae311"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
chewie:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -184,6 +256,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
code_builder:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: code_builder
|
||||
sha256: "0d43dd1288fd145de1ecc9a3948ad4a6d5a82f0a14c4fdd0892260787d975cbe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.4.0"
|
||||
collection:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -272,6 +352,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
dart_style:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dart_style
|
||||
sha256: "5be16bf1707658e4c03078d4a9b90208ded217fb02c163e207d334082412f2fb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.5"
|
||||
dbus:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -472,6 +560,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.12"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
fk_user_agent:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -756,6 +852,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.1.3"
|
||||
freezed:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: freezed
|
||||
sha256: e819441678f1679b719008ff2ff0ef045d66eed9f9ec81166ca0d9b02a187454
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
freezed_annotation:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: freezed_annotation
|
||||
sha256: aeac15850ef1b38ee368d4c53ba9a847e900bb2c53a4db3f6881cbb3cb684338
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
frontend_server_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -780,6 +892,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.6"
|
||||
graphs:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: graphs
|
||||
sha256: f9e130f3259f52d26f0cfc0e964513796dafed572fa52e45d2f8d6ca14db39b2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
hex:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -917,13 +1037,21 @@ packages:
|
|||
source: hosted
|
||||
version: "0.6.5"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: json_annotation
|
||||
sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.8.0"
|
||||
json_serializable:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: json_serializable
|
||||
sha256: dadc08bd61f72559f938dd08ec20dbfec6c709bba83515085ea943d2078d187a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.6.1"
|
||||
like_button:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -1349,6 +1477,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
pubspec_parse:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pubspec_parse
|
||||
sha256: ec85d7d55339d85f44ec2b682a82fea340071e8978257e5a43e69f79e98ef50c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
quiver:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -1538,6 +1674,22 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.99"
|
||||
source_gen:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_gen
|
||||
sha256: c2bea18c95cfa0276a366270afaa2850b09b4a76db95d546f3d003dcc7011298
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.7"
|
||||
source_helper:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_helper
|
||||
sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.3"
|
||||
source_map_stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1723,6 +1875,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.0"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: timing
|
||||
sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
tuple:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
|
@ -12,7 +12,7 @@ description: ente photos application
|
|||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
|
||||
version: 0.7.39+439
|
||||
version: 0.7.40+440
|
||||
|
||||
environment:
|
||||
sdk: '>=2.17.0 <3.0.0'
|
||||
|
@ -68,12 +68,14 @@ dependencies:
|
|||
flutter_sodium: ^0.2.0
|
||||
flutter_typeahead: ^4.0.0
|
||||
fluttertoast: ^8.0.6
|
||||
freezed_annotation: ^2.2.0
|
||||
google_nav_bar: ^5.0.5
|
||||
http: ^0.13.4
|
||||
image: ^3.0.2
|
||||
image_editor: ^1.3.0
|
||||
in_app_purchase: ^3.0.7
|
||||
intl: ^0.17.0
|
||||
json_annotation: ^4.8.0
|
||||
like_button: ^2.0.2
|
||||
loading_animations: ^2.1.0
|
||||
local_auth: ^2.1.5
|
||||
|
@ -134,9 +136,12 @@ dependency_overrides:
|
|||
wakelock: ^0.6.1+2
|
||||
|
||||
dev_dependencies:
|
||||
build_runner: ^2.3.3
|
||||
flutter_lints: ^2.0.1
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
freezed: ^2.3.2
|
||||
json_serializable: ^6.6.1
|
||||
test:
|
||||
|
||||
flutter_icons:
|
||||
|
|
Loading…
Reference in a new issue