mirror of
https://github.com/immich-app/immich.git
synced 2025-05-31 20:26:27 -04:00
Merge ac3d71eee81f997c246ff6c4438b518d245034a8 into b0bcc6c03ecedff0d756a0e54352586672cea761
This commit is contained in:
commit
ae0395c3aa
@ -65,6 +65,8 @@ PODS:
|
|||||||
- maplibre_gl (0.0.1):
|
- maplibre_gl (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- MapLibre (= 5.14.0-pre3)
|
- MapLibre (= 5.14.0-pre3)
|
||||||
|
- native_video_player (1.0.0):
|
||||||
|
- Flutter
|
||||||
- package_info_plus (0.4.5):
|
- package_info_plus (0.4.5):
|
||||||
- Flutter
|
- Flutter
|
||||||
- path_provider_foundation (0.0.1):
|
- path_provider_foundation (0.0.1):
|
||||||
@ -116,6 +118,7 @@ DEPENDENCIES:
|
|||||||
- integration_test (from `.symlinks/plugins/integration_test/ios`)
|
- integration_test (from `.symlinks/plugins/integration_test/ios`)
|
||||||
- isar_flutter_libs (from `.symlinks/plugins/isar_flutter_libs/ios`)
|
- isar_flutter_libs (from `.symlinks/plugins/isar_flutter_libs/ios`)
|
||||||
- maplibre_gl (from `.symlinks/plugins/maplibre_gl/ios`)
|
- maplibre_gl (from `.symlinks/plugins/maplibre_gl/ios`)
|
||||||
|
- native_video_player (from `.symlinks/plugins/native_video_player/ios`)
|
||||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
|
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
|
||||||
@ -170,6 +173,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/isar_flutter_libs/ios"
|
:path: ".symlinks/plugins/isar_flutter_libs/ios"
|
||||||
maplibre_gl:
|
maplibre_gl:
|
||||||
:path: ".symlinks/plugins/maplibre_gl/ios"
|
:path: ".symlinks/plugins/maplibre_gl/ios"
|
||||||
|
native_video_player:
|
||||||
|
:path: ".symlinks/plugins/native_video_player/ios"
|
||||||
package_info_plus:
|
package_info_plus:
|
||||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
@ -212,6 +217,7 @@ SPEC CHECKSUMS:
|
|||||||
isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073
|
isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073
|
||||||
MapLibre: 620fc933c1d6029b33738c905c1490d024e5d4ef
|
MapLibre: 620fc933c1d6029b33738c905c1490d024e5d4ef
|
||||||
maplibre_gl: a2efec727dd340e4c65e26d2b03b584f14881fd9
|
maplibre_gl: a2efec727dd340e4c65e26d2b03b584f14881fd9
|
||||||
|
native_video_player: d12af78a1a4a8cf09775a5177d5b392def6fd23c
|
||||||
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
|
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
|
||||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||||
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
|
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
|
||||||
|
141
mobile/lib/entities/asset.entity.g.dart
generated
141
mobile/lib/entities/asset.entity.g.dart
generated
@ -57,64 +57,69 @@ const AssetSchema = CollectionSchema(
|
|||||||
name: r'isFavorite',
|
name: r'isFavorite',
|
||||||
type: IsarType.bool,
|
type: IsarType.bool,
|
||||||
),
|
),
|
||||||
r'isTrashed': PropertySchema(
|
r'isOffline': PropertySchema(
|
||||||
id: 8,
|
id: 8,
|
||||||
|
name: r'isOffline',
|
||||||
|
type: IsarType.bool,
|
||||||
|
),
|
||||||
|
r'isTrashed': PropertySchema(
|
||||||
|
id: 9,
|
||||||
name: r'isTrashed',
|
name: r'isTrashed',
|
||||||
type: IsarType.bool,
|
type: IsarType.bool,
|
||||||
),
|
),
|
||||||
r'livePhotoVideoId': PropertySchema(
|
r'livePhotoVideoId': PropertySchema(
|
||||||
id: 9,
|
id: 10,
|
||||||
name: r'livePhotoVideoId',
|
name: r'livePhotoVideoId',
|
||||||
type: IsarType.string,
|
type: IsarType.string,
|
||||||
),
|
),
|
||||||
r'localId': PropertySchema(
|
r'localId': PropertySchema(
|
||||||
id: 10,
|
id: 11,
|
||||||
name: r'localId',
|
name: r'localId',
|
||||||
type: IsarType.string,
|
type: IsarType.string,
|
||||||
),
|
),
|
||||||
r'ownerId': PropertySchema(
|
r'ownerId': PropertySchema(
|
||||||
id: 11,
|
id: 12,
|
||||||
name: r'ownerId',
|
name: r'ownerId',
|
||||||
type: IsarType.long,
|
type: IsarType.long,
|
||||||
),
|
),
|
||||||
r'remoteId': PropertySchema(
|
r'remoteId': PropertySchema(
|
||||||
id: 12,
|
id: 13,
|
||||||
name: r'remoteId',
|
name: r'remoteId',
|
||||||
type: IsarType.string,
|
type: IsarType.string,
|
||||||
),
|
),
|
||||||
r'stackCount': PropertySchema(
|
r'stackCount': PropertySchema(
|
||||||
id: 13,
|
id: 14,
|
||||||
name: r'stackCount',
|
name: r'stackCount',
|
||||||
type: IsarType.long,
|
type: IsarType.long,
|
||||||
),
|
),
|
||||||
r'stackId': PropertySchema(
|
r'stackId': PropertySchema(
|
||||||
id: 14,
|
id: 15,
|
||||||
name: r'stackId',
|
name: r'stackId',
|
||||||
type: IsarType.string,
|
type: IsarType.string,
|
||||||
),
|
),
|
||||||
r'stackPrimaryAssetId': PropertySchema(
|
r'stackPrimaryAssetId': PropertySchema(
|
||||||
id: 15,
|
id: 16,
|
||||||
name: r'stackPrimaryAssetId',
|
name: r'stackPrimaryAssetId',
|
||||||
type: IsarType.string,
|
type: IsarType.string,
|
||||||
),
|
),
|
||||||
r'thumbhash': PropertySchema(
|
r'thumbhash': PropertySchema(
|
||||||
id: 16,
|
id: 17,
|
||||||
name: r'thumbhash',
|
name: r'thumbhash',
|
||||||
type: IsarType.string,
|
type: IsarType.string,
|
||||||
),
|
),
|
||||||
r'type': PropertySchema(
|
r'type': PropertySchema(
|
||||||
id: 17,
|
id: 18,
|
||||||
name: r'type',
|
name: r'type',
|
||||||
type: IsarType.byte,
|
type: IsarType.byte,
|
||||||
enumMap: _AssettypeEnumValueMap,
|
enumMap: _AssettypeEnumValueMap,
|
||||||
),
|
),
|
||||||
r'updatedAt': PropertySchema(
|
r'updatedAt': PropertySchema(
|
||||||
id: 18,
|
id: 19,
|
||||||
name: r'updatedAt',
|
name: r'updatedAt',
|
||||||
type: IsarType.dateTime,
|
type: IsarType.dateTime,
|
||||||
),
|
),
|
||||||
r'width': PropertySchema(
|
r'width': PropertySchema(
|
||||||
id: 19,
|
id: 20,
|
||||||
name: r'width',
|
name: r'width',
|
||||||
type: IsarType.int,
|
type: IsarType.int,
|
||||||
)
|
)
|
||||||
@ -239,18 +244,19 @@ void _assetSerialize(
|
|||||||
writer.writeInt(offsets[5], object.height);
|
writer.writeInt(offsets[5], object.height);
|
||||||
writer.writeBool(offsets[6], object.isArchived);
|
writer.writeBool(offsets[6], object.isArchived);
|
||||||
writer.writeBool(offsets[7], object.isFavorite);
|
writer.writeBool(offsets[7], object.isFavorite);
|
||||||
writer.writeBool(offsets[8], object.isTrashed);
|
writer.writeBool(offsets[8], object.isOffline);
|
||||||
writer.writeString(offsets[9], object.livePhotoVideoId);
|
writer.writeBool(offsets[9], object.isTrashed);
|
||||||
writer.writeString(offsets[10], object.localId);
|
writer.writeString(offsets[10], object.livePhotoVideoId);
|
||||||
writer.writeLong(offsets[11], object.ownerId);
|
writer.writeString(offsets[11], object.localId);
|
||||||
writer.writeString(offsets[12], object.remoteId);
|
writer.writeLong(offsets[12], object.ownerId);
|
||||||
writer.writeLong(offsets[13], object.stackCount);
|
writer.writeString(offsets[13], object.remoteId);
|
||||||
writer.writeString(offsets[14], object.stackId);
|
writer.writeLong(offsets[14], object.stackCount);
|
||||||
writer.writeString(offsets[15], object.stackPrimaryAssetId);
|
writer.writeString(offsets[15], object.stackId);
|
||||||
writer.writeString(offsets[16], object.thumbhash);
|
writer.writeString(offsets[16], object.stackPrimaryAssetId);
|
||||||
writer.writeByte(offsets[17], object.type.index);
|
writer.writeString(offsets[17], object.thumbhash);
|
||||||
writer.writeDateTime(offsets[18], object.updatedAt);
|
writer.writeByte(offsets[18], object.type.index);
|
||||||
writer.writeInt(offsets[19], object.width);
|
writer.writeDateTime(offsets[19], object.updatedAt);
|
||||||
|
writer.writeInt(offsets[20], object.width);
|
||||||
}
|
}
|
||||||
|
|
||||||
Asset _assetDeserialize(
|
Asset _assetDeserialize(
|
||||||
@ -269,19 +275,20 @@ Asset _assetDeserialize(
|
|||||||
id: id,
|
id: id,
|
||||||
isArchived: reader.readBoolOrNull(offsets[6]) ?? false,
|
isArchived: reader.readBoolOrNull(offsets[6]) ?? false,
|
||||||
isFavorite: reader.readBoolOrNull(offsets[7]) ?? false,
|
isFavorite: reader.readBoolOrNull(offsets[7]) ?? false,
|
||||||
isTrashed: reader.readBoolOrNull(offsets[8]) ?? false,
|
isOffline: reader.readBoolOrNull(offsets[8]) ?? false,
|
||||||
livePhotoVideoId: reader.readStringOrNull(offsets[9]),
|
isTrashed: reader.readBoolOrNull(offsets[9]) ?? false,
|
||||||
localId: reader.readStringOrNull(offsets[10]),
|
livePhotoVideoId: reader.readStringOrNull(offsets[10]),
|
||||||
ownerId: reader.readLong(offsets[11]),
|
localId: reader.readStringOrNull(offsets[11]),
|
||||||
remoteId: reader.readStringOrNull(offsets[12]),
|
ownerId: reader.readLong(offsets[12]),
|
||||||
stackCount: reader.readLongOrNull(offsets[13]) ?? 0,
|
remoteId: reader.readStringOrNull(offsets[13]),
|
||||||
stackId: reader.readStringOrNull(offsets[14]),
|
stackCount: reader.readLongOrNull(offsets[14]) ?? 0,
|
||||||
stackPrimaryAssetId: reader.readStringOrNull(offsets[15]),
|
stackId: reader.readStringOrNull(offsets[15]),
|
||||||
thumbhash: reader.readStringOrNull(offsets[16]),
|
stackPrimaryAssetId: reader.readStringOrNull(offsets[16]),
|
||||||
type: _AssettypeValueEnumMap[reader.readByteOrNull(offsets[17])] ??
|
thumbhash: reader.readStringOrNull(offsets[17]),
|
||||||
|
type: _AssettypeValueEnumMap[reader.readByteOrNull(offsets[18])] ??
|
||||||
AssetType.other,
|
AssetType.other,
|
||||||
updatedAt: reader.readDateTime(offsets[18]),
|
updatedAt: reader.readDateTime(offsets[19]),
|
||||||
width: reader.readIntOrNull(offsets[19]),
|
width: reader.readIntOrNull(offsets[20]),
|
||||||
);
|
);
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
@ -312,27 +319,29 @@ P _assetDeserializeProp<P>(
|
|||||||
case 8:
|
case 8:
|
||||||
return (reader.readBoolOrNull(offset) ?? false) as P;
|
return (reader.readBoolOrNull(offset) ?? false) as P;
|
||||||
case 9:
|
case 9:
|
||||||
return (reader.readStringOrNull(offset)) as P;
|
return (reader.readBoolOrNull(offset) ?? false) as P;
|
||||||
case 10:
|
case 10:
|
||||||
return (reader.readStringOrNull(offset)) as P;
|
return (reader.readStringOrNull(offset)) as P;
|
||||||
case 11:
|
case 11:
|
||||||
return (reader.readLong(offset)) as P;
|
return (reader.readStringOrNull(offset)) as P;
|
||||||
case 12:
|
case 12:
|
||||||
return (reader.readStringOrNull(offset)) as P;
|
return (reader.readLong(offset)) as P;
|
||||||
case 13:
|
case 13:
|
||||||
return (reader.readLongOrNull(offset) ?? 0) as P;
|
|
||||||
case 14:
|
|
||||||
return (reader.readStringOrNull(offset)) as P;
|
return (reader.readStringOrNull(offset)) as P;
|
||||||
|
case 14:
|
||||||
|
return (reader.readLongOrNull(offset) ?? 0) as P;
|
||||||
case 15:
|
case 15:
|
||||||
return (reader.readStringOrNull(offset)) as P;
|
return (reader.readStringOrNull(offset)) as P;
|
||||||
case 16:
|
case 16:
|
||||||
return (reader.readStringOrNull(offset)) as P;
|
return (reader.readStringOrNull(offset)) as P;
|
||||||
case 17:
|
case 17:
|
||||||
|
return (reader.readStringOrNull(offset)) as P;
|
||||||
|
case 18:
|
||||||
return (_AssettypeValueEnumMap[reader.readByteOrNull(offset)] ??
|
return (_AssettypeValueEnumMap[reader.readByteOrNull(offset)] ??
|
||||||
AssetType.other) as P;
|
AssetType.other) as P;
|
||||||
case 18:
|
|
||||||
return (reader.readDateTime(offset)) as P;
|
|
||||||
case 19:
|
case 19:
|
||||||
|
return (reader.readDateTime(offset)) as P;
|
||||||
|
case 20:
|
||||||
return (reader.readIntOrNull(offset)) as P;
|
return (reader.readIntOrNull(offset)) as P;
|
||||||
default:
|
default:
|
||||||
throw IsarError('Unknown property with id $propertyId');
|
throw IsarError('Unknown property with id $propertyId');
|
||||||
@ -1353,6 +1362,16 @@ extension AssetQueryFilter on QueryBuilder<Asset, Asset, QFilterCondition> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterFilterCondition> isOfflineEqualTo(
|
||||||
|
bool value) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.equalTo(
|
||||||
|
property: r'isOffline',
|
||||||
|
value: value,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QAfterFilterCondition> isTrashedEqualTo(
|
QueryBuilder<Asset, Asset, QAfterFilterCondition> isTrashedEqualTo(
|
||||||
bool value) {
|
bool value) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
@ -2628,6 +2647,18 @@ extension AssetQuerySortBy on QueryBuilder<Asset, Asset, QSortBy> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterSortBy> sortByIsOffline() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addSortBy(r'isOffline', Sort.asc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterSortBy> sortByIsOfflineDesc() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addSortBy(r'isOffline', Sort.desc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QAfterSortBy> sortByIsTrashed() {
|
QueryBuilder<Asset, Asset, QAfterSortBy> sortByIsTrashed() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addSortBy(r'isTrashed', Sort.asc);
|
return query.addSortBy(r'isTrashed', Sort.asc);
|
||||||
@ -2882,6 +2913,18 @@ extension AssetQuerySortThenBy on QueryBuilder<Asset, Asset, QSortThenBy> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterSortBy> thenByIsOffline() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addSortBy(r'isOffline', Sort.asc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterSortBy> thenByIsOfflineDesc() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addSortBy(r'isOffline', Sort.desc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QAfterSortBy> thenByIsTrashed() {
|
QueryBuilder<Asset, Asset, QAfterSortBy> thenByIsTrashed() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addSortBy(r'isTrashed', Sort.asc);
|
return query.addSortBy(r'isTrashed', Sort.asc);
|
||||||
@ -3078,6 +3121,12 @@ extension AssetQueryWhereDistinct on QueryBuilder<Asset, Asset, QDistinct> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QDistinct> distinctByIsOffline() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addDistinctBy(r'isOffline');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QDistinct> distinctByIsTrashed() {
|
QueryBuilder<Asset, Asset, QDistinct> distinctByIsTrashed() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addDistinctBy(r'isTrashed');
|
return query.addDistinctBy(r'isTrashed');
|
||||||
@ -3214,6 +3263,12 @@ extension AssetQueryProperty on QueryBuilder<Asset, Asset, QQueryProperty> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, bool, QQueryOperations> isOfflineProperty() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addPropertyName(r'isOffline');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<Asset, bool, QQueryOperations> isTrashedProperty() {
|
QueryBuilder<Asset, bool, QQueryOperations> isTrashedProperty() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addPropertyName(r'isTrashed');
|
return query.addPropertyName(r'isTrashed');
|
||||||
|
@ -23,6 +23,7 @@ class ExifInfo {
|
|||||||
String? state;
|
String? state;
|
||||||
String? country;
|
String? country;
|
||||||
String? description;
|
String? description;
|
||||||
|
String? orientation;
|
||||||
|
|
||||||
@ignore
|
@ignore
|
||||||
bool get hasCoordinates =>
|
bool get hasCoordinates =>
|
||||||
@ -45,6 +46,9 @@ class ExifInfo {
|
|||||||
@ignore
|
@ignore
|
||||||
String get focalLength => mm != null ? mm!.toStringAsFixed(1) : "";
|
String get focalLength => mm != null ? mm!.toStringAsFixed(1) : "";
|
||||||
|
|
||||||
|
@ignore
|
||||||
|
bool get isFlipped => _isOrientationFlipped(orientation);
|
||||||
|
|
||||||
@ignore
|
@ignore
|
||||||
double? get latitude => lat;
|
double? get latitude => lat;
|
||||||
|
|
||||||
@ -67,7 +71,8 @@ class ExifInfo {
|
|||||||
city = dto.city,
|
city = dto.city,
|
||||||
state = dto.state,
|
state = dto.state,
|
||||||
country = dto.country,
|
country = dto.country,
|
||||||
description = dto.description;
|
description = dto.description,
|
||||||
|
orientation = dto.orientation;
|
||||||
|
|
||||||
ExifInfo({
|
ExifInfo({
|
||||||
this.id,
|
this.id,
|
||||||
@ -87,6 +92,7 @@ class ExifInfo {
|
|||||||
this.state,
|
this.state,
|
||||||
this.country,
|
this.country,
|
||||||
this.description,
|
this.description,
|
||||||
|
this.orientation,
|
||||||
});
|
});
|
||||||
|
|
||||||
ExifInfo copyWith({
|
ExifInfo copyWith({
|
||||||
@ -107,6 +113,7 @@ class ExifInfo {
|
|||||||
String? state,
|
String? state,
|
||||||
String? country,
|
String? country,
|
||||||
String? description,
|
String? description,
|
||||||
|
String? orientation,
|
||||||
}) =>
|
}) =>
|
||||||
ExifInfo(
|
ExifInfo(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
@ -126,6 +133,7 @@ class ExifInfo {
|
|||||||
state: state ?? this.state,
|
state: state ?? this.state,
|
||||||
country: country ?? this.country,
|
country: country ?? this.country,
|
||||||
description: description ?? this.description,
|
description: description ?? this.description,
|
||||||
|
orientation: orientation ?? this.orientation,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -147,7 +155,8 @@ class ExifInfo {
|
|||||||
city == other.city &&
|
city == other.city &&
|
||||||
state == other.state &&
|
state == other.state &&
|
||||||
country == other.country &&
|
country == other.country &&
|
||||||
description == other.description;
|
description == other.description &&
|
||||||
|
orientation == other.orientation;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -169,7 +178,8 @@ class ExifInfo {
|
|||||||
city.hashCode ^
|
city.hashCode ^
|
||||||
state.hashCode ^
|
state.hashCode ^
|
||||||
country.hashCode ^
|
country.hashCode ^
|
||||||
description.hashCode;
|
description.hashCode ^
|
||||||
|
orientation.hashCode;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
@ -192,10 +202,21 @@ class ExifInfo {
|
|||||||
state: $state,
|
state: $state,
|
||||||
country: $country,
|
country: $country,
|
||||||
description: $description,
|
description: $description,
|
||||||
|
orientation: $orientation
|
||||||
}""";
|
}""";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _isOrientationFlipped(String? orientation) {
|
||||||
|
final value = orientation != null ? int.tryParse(orientation) : null;
|
||||||
|
if (value == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final isRotated90CW = value == 5 || value == 6 || value == 90;
|
||||||
|
final isRotated270CW = value == 7 || value == 8 || value == -90;
|
||||||
|
return isRotated90CW || isRotated270CW;
|
||||||
|
}
|
||||||
|
|
||||||
double? _exposureTimeToSeconds(String? s) {
|
double? _exposureTimeToSeconds(String? s) {
|
||||||
if (s == null) {
|
if (s == null) {
|
||||||
return null;
|
return null;
|
||||||
|
213
mobile/lib/entities/exif_info.entity.g.dart
generated
213
mobile/lib/entities/exif_info.entity.g.dart
generated
@ -87,13 +87,18 @@ const ExifInfoSchema = CollectionSchema(
|
|||||||
name: r'model',
|
name: r'model',
|
||||||
type: IsarType.string,
|
type: IsarType.string,
|
||||||
),
|
),
|
||||||
r'state': PropertySchema(
|
r'orientation': PropertySchema(
|
||||||
id: 14,
|
id: 14,
|
||||||
|
name: r'orientation',
|
||||||
|
type: IsarType.string,
|
||||||
|
),
|
||||||
|
r'state': PropertySchema(
|
||||||
|
id: 15,
|
||||||
name: r'state',
|
name: r'state',
|
||||||
type: IsarType.string,
|
type: IsarType.string,
|
||||||
),
|
),
|
||||||
r'timeZone': PropertySchema(
|
r'timeZone': PropertySchema(
|
||||||
id: 15,
|
id: 16,
|
||||||
name: r'timeZone',
|
name: r'timeZone',
|
||||||
type: IsarType.string,
|
type: IsarType.string,
|
||||||
)
|
)
|
||||||
@ -154,6 +159,12 @@ int _exifInfoEstimateSize(
|
|||||||
bytesCount += 3 + value.length * 3;
|
bytesCount += 3 + value.length * 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
final value = object.orientation;
|
||||||
|
if (value != null) {
|
||||||
|
bytesCount += 3 + value.length * 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
{
|
{
|
||||||
final value = object.state;
|
final value = object.state;
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
@ -189,8 +200,9 @@ void _exifInfoSerialize(
|
|||||||
writer.writeString(offsets[11], object.make);
|
writer.writeString(offsets[11], object.make);
|
||||||
writer.writeFloat(offsets[12], object.mm);
|
writer.writeFloat(offsets[12], object.mm);
|
||||||
writer.writeString(offsets[13], object.model);
|
writer.writeString(offsets[13], object.model);
|
||||||
writer.writeString(offsets[14], object.state);
|
writer.writeString(offsets[14], object.orientation);
|
||||||
writer.writeString(offsets[15], object.timeZone);
|
writer.writeString(offsets[15], object.state);
|
||||||
|
writer.writeString(offsets[16], object.timeZone);
|
||||||
}
|
}
|
||||||
|
|
||||||
ExifInfo _exifInfoDeserialize(
|
ExifInfo _exifInfoDeserialize(
|
||||||
@ -215,8 +227,9 @@ ExifInfo _exifInfoDeserialize(
|
|||||||
make: reader.readStringOrNull(offsets[11]),
|
make: reader.readStringOrNull(offsets[11]),
|
||||||
mm: reader.readFloatOrNull(offsets[12]),
|
mm: reader.readFloatOrNull(offsets[12]),
|
||||||
model: reader.readStringOrNull(offsets[13]),
|
model: reader.readStringOrNull(offsets[13]),
|
||||||
state: reader.readStringOrNull(offsets[14]),
|
orientation: reader.readStringOrNull(offsets[14]),
|
||||||
timeZone: reader.readStringOrNull(offsets[15]),
|
state: reader.readStringOrNull(offsets[15]),
|
||||||
|
timeZone: reader.readStringOrNull(offsets[16]),
|
||||||
);
|
);
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
@ -260,6 +273,8 @@ P _exifInfoDeserializeProp<P>(
|
|||||||
return (reader.readStringOrNull(offset)) as P;
|
return (reader.readStringOrNull(offset)) as P;
|
||||||
case 15:
|
case 15:
|
||||||
return (reader.readStringOrNull(offset)) as P;
|
return (reader.readStringOrNull(offset)) as P;
|
||||||
|
case 16:
|
||||||
|
return (reader.readStringOrNull(offset)) as P;
|
||||||
default:
|
default:
|
||||||
throw IsarError('Unknown property with id $propertyId');
|
throw IsarError('Unknown property with id $propertyId');
|
||||||
}
|
}
|
||||||
@ -1909,6 +1924,155 @@ extension ExifInfoQueryFilter
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<ExifInfo, ExifInfo, QAfterFilterCondition> orientationIsNull() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(const FilterCondition.isNull(
|
||||||
|
property: r'orientation',
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<ExifInfo, ExifInfo, QAfterFilterCondition>
|
||||||
|
orientationIsNotNull() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(const FilterCondition.isNotNull(
|
||||||
|
property: r'orientation',
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<ExifInfo, ExifInfo, QAfterFilterCondition> orientationEqualTo(
|
||||||
|
String? value, {
|
||||||
|
bool caseSensitive = true,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.equalTo(
|
||||||
|
property: r'orientation',
|
||||||
|
value: value,
|
||||||
|
caseSensitive: caseSensitive,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<ExifInfo, ExifInfo, QAfterFilterCondition>
|
||||||
|
orientationGreaterThan(
|
||||||
|
String? value, {
|
||||||
|
bool include = false,
|
||||||
|
bool caseSensitive = true,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||||
|
include: include,
|
||||||
|
property: r'orientation',
|
||||||
|
value: value,
|
||||||
|
caseSensitive: caseSensitive,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<ExifInfo, ExifInfo, QAfterFilterCondition> orientationLessThan(
|
||||||
|
String? value, {
|
||||||
|
bool include = false,
|
||||||
|
bool caseSensitive = true,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.lessThan(
|
||||||
|
include: include,
|
||||||
|
property: r'orientation',
|
||||||
|
value: value,
|
||||||
|
caseSensitive: caseSensitive,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<ExifInfo, ExifInfo, QAfterFilterCondition> orientationBetween(
|
||||||
|
String? lower,
|
||||||
|
String? upper, {
|
||||||
|
bool includeLower = true,
|
||||||
|
bool includeUpper = true,
|
||||||
|
bool caseSensitive = true,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.between(
|
||||||
|
property: r'orientation',
|
||||||
|
lower: lower,
|
||||||
|
includeLower: includeLower,
|
||||||
|
upper: upper,
|
||||||
|
includeUpper: includeUpper,
|
||||||
|
caseSensitive: caseSensitive,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<ExifInfo, ExifInfo, QAfterFilterCondition> orientationStartsWith(
|
||||||
|
String value, {
|
||||||
|
bool caseSensitive = true,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.startsWith(
|
||||||
|
property: r'orientation',
|
||||||
|
value: value,
|
||||||
|
caseSensitive: caseSensitive,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<ExifInfo, ExifInfo, QAfterFilterCondition> orientationEndsWith(
|
||||||
|
String value, {
|
||||||
|
bool caseSensitive = true,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.endsWith(
|
||||||
|
property: r'orientation',
|
||||||
|
value: value,
|
||||||
|
caseSensitive: caseSensitive,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<ExifInfo, ExifInfo, QAfterFilterCondition> orientationContains(
|
||||||
|
String value,
|
||||||
|
{bool caseSensitive = true}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.contains(
|
||||||
|
property: r'orientation',
|
||||||
|
value: value,
|
||||||
|
caseSensitive: caseSensitive,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<ExifInfo, ExifInfo, QAfterFilterCondition> orientationMatches(
|
||||||
|
String pattern,
|
||||||
|
{bool caseSensitive = true}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.matches(
|
||||||
|
property: r'orientation',
|
||||||
|
wildcard: pattern,
|
||||||
|
caseSensitive: caseSensitive,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<ExifInfo, ExifInfo, QAfterFilterCondition> orientationIsEmpty() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.equalTo(
|
||||||
|
property: r'orientation',
|
||||||
|
value: '',
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<ExifInfo, ExifInfo, QAfterFilterCondition>
|
||||||
|
orientationIsNotEmpty() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||||
|
property: r'orientation',
|
||||||
|
value: '',
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<ExifInfo, ExifInfo, QAfterFilterCondition> stateIsNull() {
|
QueryBuilder<ExifInfo, ExifInfo, QAfterFilterCondition> stateIsNull() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addFilterCondition(const FilterCondition.isNull(
|
return query.addFilterCondition(const FilterCondition.isNull(
|
||||||
@ -2377,6 +2541,18 @@ extension ExifInfoQuerySortBy on QueryBuilder<ExifInfo, ExifInfo, QSortBy> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<ExifInfo, ExifInfo, QAfterSortBy> sortByOrientation() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addSortBy(r'orientation', Sort.asc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<ExifInfo, ExifInfo, QAfterSortBy> sortByOrientationDesc() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addSortBy(r'orientation', Sort.desc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<ExifInfo, ExifInfo, QAfterSortBy> sortByState() {
|
QueryBuilder<ExifInfo, ExifInfo, QAfterSortBy> sortByState() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addSortBy(r'state', Sort.asc);
|
return query.addSortBy(r'state', Sort.asc);
|
||||||
@ -2584,6 +2760,18 @@ extension ExifInfoQuerySortThenBy
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<ExifInfo, ExifInfo, QAfterSortBy> thenByOrientation() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addSortBy(r'orientation', Sort.asc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<ExifInfo, ExifInfo, QAfterSortBy> thenByOrientationDesc() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addSortBy(r'orientation', Sort.desc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<ExifInfo, ExifInfo, QAfterSortBy> thenByState() {
|
QueryBuilder<ExifInfo, ExifInfo, QAfterSortBy> thenByState() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addSortBy(r'state', Sort.asc);
|
return query.addSortBy(r'state', Sort.asc);
|
||||||
@ -2701,6 +2889,13 @@ extension ExifInfoQueryWhereDistinct
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<ExifInfo, ExifInfo, QDistinct> distinctByOrientation(
|
||||||
|
{bool caseSensitive = true}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addDistinctBy(r'orientation', caseSensitive: caseSensitive);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<ExifInfo, ExifInfo, QDistinct> distinctByState(
|
QueryBuilder<ExifInfo, ExifInfo, QDistinct> distinctByState(
|
||||||
{bool caseSensitive = true}) {
|
{bool caseSensitive = true}) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
@ -2809,6 +3004,12 @@ extension ExifInfoQueryProperty
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<ExifInfo, String?, QQueryOperations> orientationProperty() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addPropertyName(r'orientation');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<ExifInfo, String?, QQueryOperations> stateProperty() {
|
QueryBuilder<ExifInfo, String?, QQueryOperations> stateProperty() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addPropertyName(r'state');
|
return query.addPropertyName(r'state');
|
||||||
|
@ -11,8 +11,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:immich_mobile/constants/constants.dart';
|
import 'package:immich_mobile/constants/constants.dart';
|
||||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/pages/common/native_video_viewer.page.dart';
|
||||||
import 'package:immich_mobile/pages/common/download_panel.dart';
|
import 'package:immich_mobile/pages/common/download_panel.dart';
|
||||||
import 'package:immich_mobile/pages/common/video_viewer.page.dart';
|
|
||||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/asset_stack.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/asset_stack.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart';
|
||||||
@ -62,7 +62,6 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
final localPosition = useState<Offset?>(null);
|
final localPosition = useState<Offset?>(null);
|
||||||
final currentIndex = useState(initialIndex);
|
final currentIndex = useState(initialIndex);
|
||||||
final currentAsset = loadAsset(currentIndex.value);
|
final currentAsset = loadAsset(currentIndex.value);
|
||||||
|
|
||||||
// Update is playing motion video
|
// Update is playing motion video
|
||||||
ref.listen(videoPlaybackValueProvider.select((v) => v.state), (_, state) {
|
ref.listen(videoPlaybackValueProvider.select((v) => v.state), (_, state) {
|
||||||
isPlayingVideo.value = state == VideoPlaybackState.playing;
|
isPlayingVideo.value = state == VideoPlaybackState.playing;
|
||||||
@ -367,7 +366,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
maxScale: 1.0,
|
maxScale: 1.0,
|
||||||
minScale: 1.0,
|
minScale: 1.0,
|
||||||
basePosition: Alignment.center,
|
basePosition: Alignment.center,
|
||||||
child: VideoViewerPage(
|
child: NativeVideoViewerPage(
|
||||||
key: ValueKey(a),
|
key: ValueKey(a),
|
||||||
asset: a,
|
asset: a,
|
||||||
isMotionVideo: a.livePhotoVideoId != null,
|
isMotionVideo: a.livePhotoVideoId != null,
|
||||||
|
308
mobile/lib/pages/common/native_video_viewer.page.dart
Normal file
308
mobile/lib/pages/common/native_video_viewer.page.dart
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||||
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
|
import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/asset_viewer/video_player_controls_provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider.dart';
|
||||||
|
import 'package:immich_mobile/services/api.service.dart';
|
||||||
|
import 'package:immich_mobile/services/asset.service.dart';
|
||||||
|
import 'package:immich_mobile/widgets/asset_viewer/custom_video_player_controls.dart';
|
||||||
|
import 'package:immich_mobile/widgets/common/delayed_loading_indicator.dart';
|
||||||
|
import 'package:native_video_player/native_video_player.dart';
|
||||||
|
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||||
|
|
||||||
|
class NativeVideoViewerPage extends HookConsumerWidget {
|
||||||
|
final Asset asset;
|
||||||
|
final bool isMotionVideo;
|
||||||
|
final Widget? placeholder;
|
||||||
|
final bool showControls;
|
||||||
|
final Duration hideControlsTimer;
|
||||||
|
final bool loopVideo;
|
||||||
|
|
||||||
|
const NativeVideoViewerPage({
|
||||||
|
super.key,
|
||||||
|
required this.asset,
|
||||||
|
this.isMotionVideo = false,
|
||||||
|
this.placeholder,
|
||||||
|
this.showControls = true,
|
||||||
|
this.hideControlsTimer = const Duration(seconds: 5),
|
||||||
|
this.loopVideo = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final controller = useState<NativeVideoPlayerController?>(null);
|
||||||
|
final lastVideoPosition = useRef(-1);
|
||||||
|
final isBuffering = useRef(false);
|
||||||
|
final width = useRef<double>(asset.width?.toDouble() ?? 1.0);
|
||||||
|
final height = useRef<double>(asset.height?.toDouble() ?? 1.0);
|
||||||
|
|
||||||
|
void checkIfBuffering([Timer? timer]) {
|
||||||
|
if (!context.mounted) {
|
||||||
|
timer?.cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final videoPlayback = ref.read(videoPlaybackValueProvider);
|
||||||
|
if ((isBuffering.value ||
|
||||||
|
videoPlayback.state == VideoPlaybackState.initializing) &&
|
||||||
|
videoPlayback.state != VideoPlaybackState.buffering) {
|
||||||
|
ref.read(videoPlaybackValueProvider.notifier).value =
|
||||||
|
videoPlayback.copyWith(state: VideoPlaybackState.buffering);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// timer to mark videos as buffering if the position does not change
|
||||||
|
final bufferingTimer = useRef<Timer>(
|
||||||
|
Timer.periodic(const Duration(seconds: 5), checkIfBuffering),
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<VideoSource> createSource(Asset asset) async {
|
||||||
|
if (asset.isLocal && asset.livePhotoVideoId == null) {
|
||||||
|
final entity = await asset.local!.obtainForNewProperties();
|
||||||
|
final file = await entity?.file;
|
||||||
|
if (entity == null || file == null) {
|
||||||
|
throw Exception('No file found for the video');
|
||||||
|
}
|
||||||
|
|
||||||
|
width.value = entity.orientatedWidth.toDouble();
|
||||||
|
height.value = entity.orientatedHeight.toDouble();
|
||||||
|
|
||||||
|
return await VideoSource.init(
|
||||||
|
path: file.path,
|
||||||
|
type: VideoSourceType.file,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
final assetWithExif =
|
||||||
|
await ref.read(assetServiceProvider).loadExif(asset);
|
||||||
|
final shouldFlip = assetWithExif.exifInfo?.isFlipped ?? false;
|
||||||
|
width.value = (shouldFlip ? assetWithExif.height : assetWithExif.width)
|
||||||
|
?.toDouble() ??
|
||||||
|
width.value;
|
||||||
|
height.value = (shouldFlip ? assetWithExif.width : assetWithExif.height)
|
||||||
|
?.toDouble() ??
|
||||||
|
height.value;
|
||||||
|
|
||||||
|
// Use a network URL for the video player controller
|
||||||
|
final serverEndpoint = Store.get(StoreKey.serverEndpoint);
|
||||||
|
final String videoUrl = asset.livePhotoVideoId != null
|
||||||
|
? '$serverEndpoint/assets/${asset.livePhotoVideoId}/video/playback'
|
||||||
|
: '$serverEndpoint/assets/${asset.remoteId}/video/playback';
|
||||||
|
|
||||||
|
return await VideoSource.init(
|
||||||
|
path: videoUrl,
|
||||||
|
type: VideoSourceType.network,
|
||||||
|
headers: ApiService.getRequestHeaders(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When the volume changes, set the volume
|
||||||
|
ref.listen(videoPlayerControlsProvider.select((value) => value.mute),
|
||||||
|
(_, mute) {
|
||||||
|
try {
|
||||||
|
if (mute) {
|
||||||
|
controller.value?.setVolume(0.0);
|
||||||
|
} else {
|
||||||
|
controller.value?.setVolume(0.7);
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
// Consume error from the controller
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// When the position changes, seek to the position
|
||||||
|
ref.listen(videoPlayerControlsProvider.select((value) => value.position),
|
||||||
|
(_, position) {
|
||||||
|
if (controller.value == null) {
|
||||||
|
// No seeeking if there is no video
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the position to seek to
|
||||||
|
final Duration seek = asset.duration * (position / 100.0);
|
||||||
|
try {
|
||||||
|
controller.value?.seekTo(seek.inSeconds);
|
||||||
|
} catch (_) {
|
||||||
|
// Consume error from the controller
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// When the custom video controls paus or plays
|
||||||
|
ref.listen(videoPlayerControlsProvider.select((value) => value.pause),
|
||||||
|
(_, pause) {
|
||||||
|
try {
|
||||||
|
if (pause) {
|
||||||
|
controller.value?.pause();
|
||||||
|
} else {
|
||||||
|
controller.value?.play();
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
// Consume error from the controller
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
void updateVideoPlayback() {
|
||||||
|
if (controller.value == null || !context.mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final videoPlayback =
|
||||||
|
VideoPlaybackValue.fromNativeController(controller.value!);
|
||||||
|
ref.read(videoPlaybackValueProvider.notifier).value = videoPlayback;
|
||||||
|
// Check if the video is buffering
|
||||||
|
if (videoPlayback.state == VideoPlaybackState.playing) {
|
||||||
|
isBuffering.value =
|
||||||
|
lastVideoPosition.value == videoPlayback.position.inSeconds;
|
||||||
|
lastVideoPosition.value = videoPlayback.position.inSeconds;
|
||||||
|
} else {
|
||||||
|
isBuffering.value = false;
|
||||||
|
lastVideoPosition.value = -1;
|
||||||
|
}
|
||||||
|
final state = videoPlayback.state;
|
||||||
|
|
||||||
|
// Enable the WakeLock while the video is playing
|
||||||
|
if (state == VideoPlaybackState.playing) {
|
||||||
|
// Sync with the controls playing
|
||||||
|
WakelockPlus.enable();
|
||||||
|
} else {
|
||||||
|
// Sync with the controls pause
|
||||||
|
WakelockPlus.disable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onPlaybackReady() {
|
||||||
|
try {
|
||||||
|
controller.value?.play();
|
||||||
|
controller.value?.setVolume(0.9);
|
||||||
|
} catch (_) {
|
||||||
|
// Consume error from the controller
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onPlaybackPositionChanged() {
|
||||||
|
updateVideoPlayback();
|
||||||
|
}
|
||||||
|
|
||||||
|
void onPlaybackEnded() {
|
||||||
|
try {
|
||||||
|
if (loopVideo) {
|
||||||
|
controller.value?.play();
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
// Consume error from the controller
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> initController(NativeVideoPlayerController nc) async {
|
||||||
|
if (controller.value != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nc.onPlaybackPositionChanged.addListener(onPlaybackPositionChanged);
|
||||||
|
nc.onPlaybackStatusChanged.addListener(onPlaybackPositionChanged);
|
||||||
|
nc.onPlaybackReady.addListener(onPlaybackReady);
|
||||||
|
nc.onPlaybackEnded.addListener(onPlaybackEnded);
|
||||||
|
|
||||||
|
final videoSource = await createSource(asset);
|
||||||
|
nc.loadVideoSource(videoSource);
|
||||||
|
|
||||||
|
controller.value = nc;
|
||||||
|
Timer(const Duration(milliseconds: 200), checkIfBuffering);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() {
|
||||||
|
Future.microtask(
|
||||||
|
() => ref.read(videoPlayerControlsProvider.notifier).reset(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isMotionVideo) {
|
||||||
|
// ignore: prefer-extracting-callbacks
|
||||||
|
Future.microtask(() {
|
||||||
|
ref.read(showControlsProvider.notifier).show = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return () {
|
||||||
|
bufferingTimer.value.cancel();
|
||||||
|
try {
|
||||||
|
controller.value?.onPlaybackPositionChanged
|
||||||
|
.removeListener(onPlaybackPositionChanged);
|
||||||
|
controller.value?.onPlaybackStatusChanged
|
||||||
|
.removeListener(onPlaybackPositionChanged);
|
||||||
|
controller.value?.onPlaybackReady.removeListener(onPlaybackReady);
|
||||||
|
controller.value?.onPlaybackEnded.removeListener(onPlaybackEnded);
|
||||||
|
controller.value?.stop();
|
||||||
|
} catch (_) {
|
||||||
|
// Consume error from the controller
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
double calculateAspectRatio() {
|
||||||
|
if (width.value == 0 || height.value == 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return width.value / height.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
final size = MediaQuery.sizeOf(context);
|
||||||
|
|
||||||
|
return SizedBox(
|
||||||
|
height: size.height,
|
||||||
|
width: size.width,
|
||||||
|
child: GestureDetector(
|
||||||
|
behavior: HitTestBehavior.deferToChild,
|
||||||
|
child: PopScope(
|
||||||
|
onPopInvokedWithResult: (didPop, _) => ref
|
||||||
|
.read(videoPlaybackValueProvider.notifier)
|
||||||
|
.value = VideoPlaybackValue.uninitialized(),
|
||||||
|
child: SizedBox(
|
||||||
|
height: size.height,
|
||||||
|
width: size.width,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Center(
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: calculateAspectRatio(),
|
||||||
|
child: NativeVideoPlayerView(
|
||||||
|
onViewReady: initController,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (showControls)
|
||||||
|
Center(
|
||||||
|
child: CustomVideoPlayerControls(
|
||||||
|
hideTimerDuration: hideControlsTimer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Visibility(
|
||||||
|
visible: controller.value == null,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
if (placeholder != null) placeholder!,
|
||||||
|
const Positioned.fill(
|
||||||
|
child: Center(
|
||||||
|
child: DelayedLoadingIndicator(
|
||||||
|
fadeInDuration: Duration(milliseconds: 500),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:native_video_player/native_video_player.dart';
|
||||||
import 'package:video_player/video_player.dart';
|
import 'package:video_player/video_player.dart';
|
||||||
|
|
||||||
enum VideoPlaybackState {
|
enum VideoPlaybackState {
|
||||||
@ -29,6 +30,38 @@ class VideoPlaybackValue {
|
|||||||
required this.volume,
|
required this.volume,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
factory VideoPlaybackValue.fromNativeController(
|
||||||
|
NativeVideoPlayerController controller,
|
||||||
|
) {
|
||||||
|
PlaybackInfo? playbackInfo;
|
||||||
|
VideoInfo? videoInfo;
|
||||||
|
try {
|
||||||
|
playbackInfo = controller.playbackInfo;
|
||||||
|
videoInfo = controller.videoInfo;
|
||||||
|
} catch (_) {
|
||||||
|
// Consume error from the controller
|
||||||
|
}
|
||||||
|
late VideoPlaybackState s;
|
||||||
|
if (playbackInfo?.status == null) {
|
||||||
|
s = VideoPlaybackState.initializing;
|
||||||
|
} else if (playbackInfo?.status == PlaybackStatus.stopped &&
|
||||||
|
(playbackInfo?.positionFraction == 1 ||
|
||||||
|
playbackInfo?.positionFraction == 0)) {
|
||||||
|
s = VideoPlaybackState.completed;
|
||||||
|
} else if (playbackInfo?.status == PlaybackStatus.playing) {
|
||||||
|
s = VideoPlaybackState.playing;
|
||||||
|
} else {
|
||||||
|
s = VideoPlaybackState.paused;
|
||||||
|
}
|
||||||
|
|
||||||
|
return VideoPlaybackValue(
|
||||||
|
position: Duration(seconds: playbackInfo?.position ?? 0),
|
||||||
|
duration: Duration(seconds: videoInfo?.duration ?? 0),
|
||||||
|
state: s,
|
||||||
|
volume: playbackInfo?.volume ?? 0.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
factory VideoPlaybackValue.fromController(VideoPlayerController? controller) {
|
factory VideoPlaybackValue.fromController(VideoPlayerController? controller) {
|
||||||
final video = controller?.value;
|
final video = controller?.value;
|
||||||
late VideoPlaybackState s;
|
late VideoPlaybackState s;
|
||||||
@ -60,6 +93,20 @@ class VideoPlaybackValue {
|
|||||||
volume: 0.0,
|
volume: 0.0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VideoPlaybackValue copyWith({
|
||||||
|
Duration? position,
|
||||||
|
Duration? duration,
|
||||||
|
VideoPlaybackState? state,
|
||||||
|
double? volume,
|
||||||
|
}) {
|
||||||
|
return VideoPlaybackValue(
|
||||||
|
position: position ?? this.position,
|
||||||
|
duration: duration ?? this.duration,
|
||||||
|
state: state ?? this.state,
|
||||||
|
volume: volume ?? this.volume,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final videoPlaybackValueProvider =
|
final videoPlaybackValueProvider =
|
||||||
|
@ -4,7 +4,7 @@ import 'package:immich_mobile/entities/store.entity.dart';
|
|||||||
import 'package:immich_mobile/utils/db.dart';
|
import 'package:immich_mobile/utils/db.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
|
|
||||||
const int targetVersion = 6;
|
const int targetVersion = 7;
|
||||||
|
|
||||||
Future<void> migrateDatabaseIfNeeded(Isar db) async {
|
Future<void> migrateDatabaseIfNeeded(Isar db) async {
|
||||||
final int version = Store.get(StoreKey.version, 1);
|
final int version = Store.get(StoreKey.version, 1);
|
||||||
|
@ -4,9 +4,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/video_player_controls_provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/video_player_controls_provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider.dart';
|
||||||
|
import 'package:immich_mobile/utils/hooks/timer_hook.dart';
|
||||||
import 'package:immich_mobile/widgets/asset_viewer/center_play_button.dart';
|
import 'package:immich_mobile/widgets/asset_viewer/center_play_button.dart';
|
||||||
import 'package:immich_mobile/widgets/common/delayed_loading_indicator.dart';
|
import 'package:immich_mobile/widgets/common/delayed_loading_indicator.dart';
|
||||||
import 'package:immich_mobile/utils/hooks/timer_hook.dart';
|
|
||||||
|
|
||||||
class CustomVideoPlayerControls extends HookConsumerWidget {
|
class CustomVideoPlayerControls extends HookConsumerWidget {
|
||||||
final Duration hideTimerDuration;
|
final Duration hideTimerDuration;
|
||||||
@ -86,12 +86,8 @@ class CustomVideoPlayerControls extends HookConsumerWidget {
|
|||||||
)
|
)
|
||||||
else
|
else
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () =>
|
||||||
if (state != VideoPlaybackState.playing) {
|
ref.read(showControlsProvider.notifier).show = false,
|
||||||
togglePlay();
|
|
||||||
}
|
|
||||||
ref.read(showControlsProvider.notifier).show = false;
|
|
||||||
},
|
|
||||||
child: CenterPlayButton(
|
child: CenterPlayButton(
|
||||||
backgroundColor: Colors.black54,
|
backgroundColor: Colors.black54,
|
||||||
iconColor: Colors.white,
|
iconColor: Colors.white,
|
||||||
|
@ -2,9 +2,9 @@ import 'dart:ui';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
|
||||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||||
import 'package:immich_mobile/pages/common/video_viewer.page.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/pages/common/native_video_viewer.page.dart';
|
||||||
import 'package:immich_mobile/utils/hooks/blurhash_hook.dart';
|
import 'package:immich_mobile/utils/hooks/blurhash_hook.dart';
|
||||||
import 'package:immich_mobile/widgets/common/immich_image.dart';
|
import 'package:immich_mobile/widgets/common/immich_image.dart';
|
||||||
|
|
||||||
@ -68,10 +68,9 @@ class MemoryCard extends StatelessWidget {
|
|||||||
} else {
|
} else {
|
||||||
return Hero(
|
return Hero(
|
||||||
tag: 'memory-${asset.id}',
|
tag: 'memory-${asset.id}',
|
||||||
child: VideoViewerPage(
|
child: NativeVideoViewerPage(
|
||||||
key: ValueKey(asset),
|
key: ValueKey(asset),
|
||||||
asset: asset,
|
asset: asset,
|
||||||
showDownloadingIndicator: false,
|
|
||||||
placeholder: SizedBox.expand(
|
placeholder: SizedBox.expand(
|
||||||
child: ImmichImage(
|
child: ImmichImage(
|
||||||
asset,
|
asset,
|
||||||
|
@ -1024,6 +1024,15 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.4"
|
version: "1.0.4"
|
||||||
|
native_video_player:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "."
|
||||||
|
ref: "feat/headers"
|
||||||
|
resolved-ref: "568c76e1552791f06dcf44b45d3373cad12913ed"
|
||||||
|
url: "https://github.com/immich-app/native_video_player"
|
||||||
|
source: git
|
||||||
|
version: "1.3.1"
|
||||||
nested:
|
nested:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -56,8 +56,15 @@ dependencies:
|
|||||||
thumbhash: 0.1.0+1
|
thumbhash: 0.1.0+1
|
||||||
async: ^2.11.0
|
async: ^2.11.0
|
||||||
dynamic_color: ^1.7.0 #package to apply system theme
|
dynamic_color: ^1.7.0 #package to apply system theme
|
||||||
|
|
||||||
|
native_video_player:
|
||||||
|
git:
|
||||||
|
url: https://github.com/immich-app/native_video_player
|
||||||
|
ref: feat/headers
|
||||||
|
|
||||||
background_downloader: ^8.5.5
|
background_downloader: ^8.5.5
|
||||||
|
|
||||||
|
|
||||||
#image editing packages
|
#image editing packages
|
||||||
crop_image: ^1.0.13
|
crop_image: ^1.0.13
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user