Compare commits

..

5 Commits

Author SHA1 Message Date
Will
22ab0b3a90 fix: casting documentation 2026-07-23 17:07:22 -05:00
Will
19e48f538d fix: version 2026-07-23 15:05:36 -05:00
Will
acb5d53ab9 fix: rust & protoc 2026-07-23 11:51:58 -05:00
Will
d9178e2036 chore: documentation 2026-07-23 11:01:07 -05:00
Will
22624cc134 feat: mobile fcast support 2026-07-23 10:28:24 -05:00
21 changed files with 738 additions and 1121 deletions

3
.gitattributes vendored
View File

@@ -15,9 +15,6 @@ mobile/ios/**/*.g.swift linguist-generated=true
mobile/lib/**/*.drift.dart -diff -merge
mobile/lib/**/*.drift.dart linguist-generated=true
mobile/lib/**/*.freezed.dart -diff -merge
mobile/lib/**/*.freezed.dart linguist-generated=true
mobile/drift_schemas/main/drift_schema_*.json -diff -merge
mobile/drift_schemas/main/drift_schema_*.json linguist-generated=true

View File

@@ -127,6 +127,15 @@ jobs:
with:
packages: ''
- name: Install Rust and protoc for cargokit
run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
"$HOME/.cargo/bin/rustup" target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android
sudo apt-get update
sudo apt-get install -y protobuf-compiler
echo "PROTOC=$(command -v protoc)" >> "$GITHUB_ENV"
- name: Get Packages
working-directory: ./mobile
run: flutter pub get
@@ -226,6 +235,11 @@ jobs:
github_token: ${{ steps.token.outputs.token }}
working_directory: ./mobile
- name: Install protoc
run: |
brew install protobuf
echo "PROTOC=$(brew --prefix)/bin/protoc" >> "$GITHUB_ENV"
- name: Install Flutter dependencies
working-directory: ./mobile
run: flutter pub get

View File

@@ -1,19 +1,23 @@
# Chromecast support
# Casting support
Immich supports the Google's Cast protocol so that photos and videos can be cast to devices such as a Chromecast and a Nest Hub. This feature is considered experimental and has several important limitations listed below. Currently, this feature is only supported by the web client, support on Android and iOS is planned for the future.
Immich supports both the FCast and Google Cast protocols, meaning photos and videos in Immich can be cast to devices such as a Chromecast, Nest Hub, or an FCast receiver. This feature is considered experimental and has several important limitations listed below. This feature is supported by the web client, as well as the mobile app via FCast.
## Enable Google Cast Support
## Mobile FCast support
Google Cast support is disabled by default. The web UI uses Google-provided scripts and must retrieve them from Google servers when the page loads. This is a privacy concern for some and is thus opt-in.
The mobile app also supports FCast for casting media to both FCast receivers and Google Cast devices such as a Google TV or Nest Hub. Devices are automatically discovered over your local network.
You can enable Google Cast support through `Account Settings > Features > Cast > Google Cast`
## Enable web support
Casting from the web client is currently only possible via Google Cast, so enabling web support means enabling Google Cast. Google Cast support is disabled by default, since it requires the web UI to load Google-provided scripts from Google's servers when the page loads. This is a privacy concern for some and is thus opt-in.
You can enable Google Cast support through `Account Settings > Features > Cast > Google Cast`.
<img src={require('./img/gcast-enable.webp').default} width="70%" title='Enable Google Cast Support' />
## Limitations
To use casting with Immich, there are a few prerequisites:
To use Google Cast with Immich, there are a few prerequisites:
1. Your instance must be accessed via an HTTPS connection in order for the casting menu to show.
2. Your instance must be publicly accessible via HTTPS and a DNS record for the server must be accessible via Google's DNS servers (`8.8.8.8` and `8.8.4.4`)
3. Videos must be in a format that is compatible with Google Cast. For more info, check out [Google's documentation](https://developers.google.com/cast/docs/media)
2. Your instance must be publicly accessible via HTTPS and a DNS record for the server must be accessible via Google's DNS servers (`8.8.8.8` and `8.8.4.4`).
3. Videos must be in a format that is compatible with Google Cast. For more info, check out [Google's documentation](https://developers.google.com/cast/docs/media).

View File

@@ -27,12 +27,30 @@ require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelpe
flutter_ios_podfile_setup
def ensure_fcast_sender_sdk_podspec
plugin_ios_dir = File.expand_path(File.join('.symlinks', 'plugins', 'fcast_sender_sdk', 'ios'), __dir__)
original_podspec = File.join(plugin_ios_dir, 'fcast_sender_sdk_flutter_plugin.podspec')
expected_podspec = File.join(plugin_ios_dir, 'fcast_sender_sdk.podspec')
return unless File.exist?(original_podspec)
contents = File.read(original_podspec)
fixed_contents = contents
.sub("s.name = 'fcast_sender_sdk_flutter_plugin'", "s.name = 'fcast_sender_sdk'")
.gsub('libfcast_sender_sdk_flutter_plugin.a', 'libfcast_sender_sdk.a')
return if File.exist?(expected_podspec) && File.read(expected_podspec) == fixed_contents
File.write(expected_podspec, fixed_contents)
end
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
ensure_fcast_sender_sdk_podspec
# share_handler addition start
target 'ShareExtension' do
inherit! :search_paths
@@ -49,7 +67,7 @@ post_install do |installer|
end
end
end
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)

View File

@@ -1,10 +1,9 @@
PODS:
- bonsoir_darwin (0.0.1):
- Flutter
- FlutterMacOS
- cupertino_http (0.0.1):
- Flutter
- FlutterMacOS
- fcast_sender_sdk (0.0.1):
- Flutter
- Flutter (1.0.0)
- flutter_local_notifications (0.0.1):
- Flutter
@@ -24,8 +23,8 @@ PODS:
- share_handler_ios_models (0.0.9)
DEPENDENCIES:
- bonsoir_darwin (from `.symlinks/plugins/bonsoir_darwin/darwin`)
- cupertino_http (from `.symlinks/plugins/cupertino_http/darwin`)
- fcast_sender_sdk (from `.symlinks/plugins/fcast_sender_sdk/ios`)
- Flutter (from `Flutter`)
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
@@ -35,10 +34,10 @@ DEPENDENCIES:
- share_handler_ios_models (from `.symlinks/plugins/share_handler_ios/ios/Models`)
EXTERNAL SOURCES:
bonsoir_darwin:
:path: ".symlinks/plugins/bonsoir_darwin/darwin"
cupertino_http:
:path: ".symlinks/plugins/cupertino_http/darwin"
fcast_sender_sdk:
:path: ".symlinks/plugins/fcast_sender_sdk/ios"
Flutter:
:path: Flutter
flutter_local_notifications:
@@ -55,8 +54,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/share_handler_ios/ios/Models"
SPEC CHECKSUMS:
bonsoir_darwin: 29c7ccf356646118844721f36e1de4b61f6cbd0e
cupertino_http: 94ac07f5ff090b8effa6c5e2c47871d48ab7c86c
fcast_sender_sdk: 8bf227a5cbcedaea2e580a633ff0f706f1e90328
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
flutter_local_notifications: ad39620c743ea4c15127860f4b5641649a988100
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
@@ -65,6 +64,6 @@ SPEC CHECKSUMS:
share_handler_ios: e2244e990f826b2c8eaa291ac3831569438ba0fb
share_handler_ios_models: fc638c9b4330dc7f082586c92aee9dfa0b87b871
PODFILE CHECKSUM: 3c43a700a4bffb4120bf696cad263aefd4bb3c8c
PODFILE CHECKSUM: 44895462563291c3e4328a856c6482a25165b507
COCOAPODS: 1.16.2
COCOAPODS: 1.17.0

View File

@@ -32,8 +32,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/maplibre/maplibre-gl-native-distribution.git",
"state" : {
"revision" : "60d9bb85c94ce6e7fc4406cd32529fd12bdb7809",
"version" : "6.14.0"
"revision" : "84a79bc375a301169390ac110c868f06c857b83f",
"version" : "6.27.0"
}
},
{

View File

@@ -140,8 +140,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-structured-queries",
"state" : {
"revision" : "dafddd843f10630aa6b5be47c1b6a59cc4ab6586",
"version" : "0.34.0"
"revision" : "8da8818fccd9959bd683934ddc62cf45bb65b3c8",
"version" : "0.31.1"
}
},
{

View File

@@ -127,6 +127,7 @@
<array>
<string>_googlecast._tcp</string>
<string>_CC1AD845._googlecast._tcp</string>
<string>_fcast._tcp</string>
</array>
<key>NSCameraUsageDescription</key>
<string>We need to access the camera to let you take beautiful video using this app</string>

View File

@@ -1,39 +1,30 @@
import 'package:freezed_annotation/freezed_annotation.dart';
class ExifInfo {
final int? assetId;
final int? fileSize;
final String? description;
final bool isFlipped;
final String? orientation;
final String? timeZone;
final DateTime? dateTimeOriginal;
final int? rating;
final int? width;
final int? height;
part 'exif.model.freezed.dart';
// GPS
final double? latitude;
final double? longitude;
final String? city;
final String? state;
final String? country;
@freezed
abstract class ExifInfo with _$ExifInfo {
const ExifInfo._();
const factory ExifInfo({
int? assetId,
int? fileSize,
String? description,
@Default(false) bool isFlipped,
String? orientation,
String? timeZone,
DateTime? dateTimeOriginal,
int? rating,
int? width,
int? height,
// GPS
double? latitude,
double? longitude,
String? city,
String? state,
String? country,
// Camera related
String? make,
String? model,
String? lens,
double? f,
double? mm,
int? iso,
double? exposureSeconds,
}) = _ExifInfo;
// Camera related
final String? make;
final String? model;
final String? lens;
final double? f;
final double? mm;
final int? iso;
final double? exposureSeconds;
bool get hasCoordinates => latitude != null && longitude != null && latitude != 0 && longitude != 0;
@@ -50,4 +41,162 @@ abstract class ExifInfo with _$ExifInfo {
String get fNumber => f == null ? "" : f!.toStringAsFixed(1);
String get focalLength => mm == null ? "" : mm!.toStringAsFixed(3);
const ExifInfo({
this.assetId,
this.fileSize,
this.description,
this.orientation,
this.timeZone,
this.dateTimeOriginal,
this.rating,
this.width,
this.height,
this.isFlipped = false,
this.latitude,
this.longitude,
this.city,
this.state,
this.country,
this.make,
this.model,
this.lens,
this.f,
this.mm,
this.iso,
this.exposureSeconds,
});
@override
bool operator ==(covariant ExifInfo other) {
if (identical(this, other)) {
return true;
}
return other.fileSize == fileSize &&
other.description == description &&
other.isFlipped == isFlipped &&
other.orientation == orientation &&
other.timeZone == timeZone &&
other.dateTimeOriginal == dateTimeOriginal &&
other.rating == rating &&
other.width == width &&
other.height == height &&
other.latitude == latitude &&
other.longitude == longitude &&
other.city == city &&
other.state == state &&
other.country == country &&
other.make == make &&
other.model == model &&
other.lens == lens &&
other.f == f &&
other.mm == mm &&
other.iso == iso &&
other.exposureSeconds == exposureSeconds &&
other.assetId == assetId;
}
@override
int get hashCode {
return fileSize.hashCode ^
description.hashCode ^
orientation.hashCode ^
isFlipped.hashCode ^
timeZone.hashCode ^
dateTimeOriginal.hashCode ^
rating.hashCode ^
width.hashCode ^
height.hashCode ^
latitude.hashCode ^
longitude.hashCode ^
city.hashCode ^
state.hashCode ^
country.hashCode ^
make.hashCode ^
model.hashCode ^
lens.hashCode ^
f.hashCode ^
mm.hashCode ^
iso.hashCode ^
exposureSeconds.hashCode ^
assetId.hashCode;
}
@override
String toString() {
return '''{
fileSize: ${fileSize ?? 'NA'},
description: ${description ?? 'NA'},
orientation: ${orientation ?? 'NA'},
isFlipped: $isFlipped,
timeZone: ${timeZone ?? 'NA'},
dateTimeOriginal: ${dateTimeOriginal ?? 'NA'},
rating: ${rating ?? 'NA'},
width: ${width ?? 'NA'},
height: ${height ?? 'NA'},
latitude: ${latitude ?? 'NA'},
longitude: ${longitude ?? 'NA'},
city: ${city ?? 'NA'},
state: ${state ?? 'NA'},
country: ${country ?? '<NA>'},
make: ${make ?? 'NA'},
model: ${model ?? 'NA'},
lens: ${lens ?? 'NA'},
f: ${f ?? 'NA'},
mm: ${mm ?? '<NA>'},
iso: ${iso ?? 'NA'},
exposureSeconds: ${exposureSeconds ?? 'NA'},
}''';
}
ExifInfo copyWith({
int? assetId,
int? fileSize,
String? description,
String? orientation,
String? timeZone,
DateTime? dateTimeOriginal,
int? rating,
int? width,
int? height,
double? latitude,
double? longitude,
String? city,
String? state,
String? country,
bool? isFlipped,
String? make,
String? model,
String? lens,
double? f,
double? mm,
int? iso,
double? exposureSeconds,
}) {
return ExifInfo(
assetId: assetId ?? this.assetId,
fileSize: fileSize ?? this.fileSize,
description: description ?? this.description,
orientation: orientation ?? this.orientation,
timeZone: timeZone ?? this.timeZone,
dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal,
rating: rating ?? this.rating,
width: width ?? this.width,
height: height ?? this.height,
isFlipped: isFlipped ?? this.isFlipped,
latitude: latitude ?? this.latitude,
longitude: longitude ?? this.longitude,
city: city ?? this.city,
state: state ?? this.state,
country: country ?? this.country,
make: make ?? this.make,
model: model ?? this.model,
lens: lens ?? this.lens,
f: f ?? this.f,
mm: mm ?? this.mm,
iso: iso ?? this.iso,
exposureSeconds: exposureSeconds ?? this.exposureSeconds,
);
}
}

View File

@@ -1,338 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// 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 'exif.model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$ExifInfo {
int? get assetId; int? get fileSize; String? get description; bool get isFlipped; String? get orientation; String? get timeZone; DateTime? get dateTimeOriginal; int? get rating; int? get width; int? get height;// GPS
double? get latitude; double? get longitude; String? get city; String? get state; String? get country;// Camera related
String? get make; String? get model; String? get lens; double? get f; double? get mm; int? get iso; double? get exposureSeconds;
/// Create a copy of ExifInfo
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$ExifInfoCopyWith<ExifInfo> get copyWith => _$ExifInfoCopyWithImpl<ExifInfo>(this as ExifInfo, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is ExifInfo&&(identical(other.assetId, assetId) || other.assetId == assetId)&&(identical(other.fileSize, fileSize) || other.fileSize == fileSize)&&(identical(other.description, description) || other.description == description)&&(identical(other.isFlipped, isFlipped) || other.isFlipped == isFlipped)&&(identical(other.orientation, orientation) || other.orientation == orientation)&&(identical(other.timeZone, timeZone) || other.timeZone == timeZone)&&(identical(other.dateTimeOriginal, dateTimeOriginal) || other.dateTimeOriginal == dateTimeOriginal)&&(identical(other.rating, rating) || other.rating == rating)&&(identical(other.width, width) || other.width == width)&&(identical(other.height, height) || other.height == height)&&(identical(other.latitude, latitude) || other.latitude == latitude)&&(identical(other.longitude, longitude) || other.longitude == longitude)&&(identical(other.city, city) || other.city == city)&&(identical(other.state, state) || other.state == state)&&(identical(other.country, country) || other.country == country)&&(identical(other.make, make) || other.make == make)&&(identical(other.model, model) || other.model == model)&&(identical(other.lens, lens) || other.lens == lens)&&(identical(other.f, f) || other.f == f)&&(identical(other.mm, mm) || other.mm == mm)&&(identical(other.iso, iso) || other.iso == iso)&&(identical(other.exposureSeconds, exposureSeconds) || other.exposureSeconds == exposureSeconds));
}
@override
int get hashCode => Object.hashAll([runtimeType,assetId,fileSize,description,isFlipped,orientation,timeZone,dateTimeOriginal,rating,width,height,latitude,longitude,city,state,country,make,model,lens,f,mm,iso,exposureSeconds]);
@override
String toString() {
return 'ExifInfo(assetId: $assetId, fileSize: $fileSize, description: $description, isFlipped: $isFlipped, orientation: $orientation, timeZone: $timeZone, dateTimeOriginal: $dateTimeOriginal, rating: $rating, width: $width, height: $height, latitude: $latitude, longitude: $longitude, city: $city, state: $state, country: $country, make: $make, model: $model, lens: $lens, f: $f, mm: $mm, iso: $iso, exposureSeconds: $exposureSeconds)';
}
}
/// @nodoc
abstract mixin class $ExifInfoCopyWith<$Res> {
factory $ExifInfoCopyWith(ExifInfo value, $Res Function(ExifInfo) _then) = _$ExifInfoCopyWithImpl;
@useResult
$Res call({
int? assetId, int? fileSize, String? description, bool isFlipped, String? orientation, String? timeZone, DateTime? dateTimeOriginal, int? rating, int? width, int? height, double? latitude, double? longitude, String? city, String? state, String? country, String? make, String? model, String? lens, double? f, double? mm, int? iso, double? exposureSeconds
});
}
/// @nodoc
class _$ExifInfoCopyWithImpl<$Res>
implements $ExifInfoCopyWith<$Res> {
_$ExifInfoCopyWithImpl(this._self, this._then);
final ExifInfo _self;
final $Res Function(ExifInfo) _then;
/// Create a copy of ExifInfo
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? assetId = freezed,Object? fileSize = freezed,Object? description = freezed,Object? isFlipped = null,Object? orientation = freezed,Object? timeZone = freezed,Object? dateTimeOriginal = freezed,Object? rating = freezed,Object? width = freezed,Object? height = freezed,Object? latitude = freezed,Object? longitude = freezed,Object? city = freezed,Object? state = freezed,Object? country = freezed,Object? make = freezed,Object? model = freezed,Object? lens = freezed,Object? f = freezed,Object? mm = freezed,Object? iso = freezed,Object? exposureSeconds = freezed,}) {
return _then(_self.copyWith(
assetId: freezed == assetId ? _self.assetId : assetId // ignore: cast_nullable_to_non_nullable
as int?,fileSize: freezed == fileSize ? _self.fileSize : fileSize // ignore: cast_nullable_to_non_nullable
as int?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String?,isFlipped: null == isFlipped ? _self.isFlipped : isFlipped // ignore: cast_nullable_to_non_nullable
as bool,orientation: freezed == orientation ? _self.orientation : orientation // ignore: cast_nullable_to_non_nullable
as String?,timeZone: freezed == timeZone ? _self.timeZone : timeZone // ignore: cast_nullable_to_non_nullable
as String?,dateTimeOriginal: freezed == dateTimeOriginal ? _self.dateTimeOriginal : dateTimeOriginal // ignore: cast_nullable_to_non_nullable
as DateTime?,rating: freezed == rating ? _self.rating : rating // ignore: cast_nullable_to_non_nullable
as int?,width: freezed == width ? _self.width : width // ignore: cast_nullable_to_non_nullable
as int?,height: freezed == height ? _self.height : height // ignore: cast_nullable_to_non_nullable
as int?,latitude: freezed == latitude ? _self.latitude : latitude // ignore: cast_nullable_to_non_nullable
as double?,longitude: freezed == longitude ? _self.longitude : longitude // ignore: cast_nullable_to_non_nullable
as double?,city: freezed == city ? _self.city : city // ignore: cast_nullable_to_non_nullable
as String?,state: freezed == state ? _self.state : state // ignore: cast_nullable_to_non_nullable
as String?,country: freezed == country ? _self.country : country // ignore: cast_nullable_to_non_nullable
as String?,make: freezed == make ? _self.make : make // ignore: cast_nullable_to_non_nullable
as String?,model: freezed == model ? _self.model : model // ignore: cast_nullable_to_non_nullable
as String?,lens: freezed == lens ? _self.lens : lens // ignore: cast_nullable_to_non_nullable
as String?,f: freezed == f ? _self.f : f // ignore: cast_nullable_to_non_nullable
as double?,mm: freezed == mm ? _self.mm : mm // ignore: cast_nullable_to_non_nullable
as double?,iso: freezed == iso ? _self.iso : iso // ignore: cast_nullable_to_non_nullable
as int?,exposureSeconds: freezed == exposureSeconds ? _self.exposureSeconds : exposureSeconds // ignore: cast_nullable_to_non_nullable
as double?,
));
}
}
/// Adds pattern-matching-related methods to [ExifInfo].
extension ExifInfoPatterns on ExifInfo {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _ExifInfo value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _ExifInfo() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _ExifInfo value) $default,){
final _that = this;
switch (_that) {
case _ExifInfo():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _ExifInfo value)? $default,){
final _that = this;
switch (_that) {
case _ExifInfo() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int? assetId, int? fileSize, String? description, bool isFlipped, String? orientation, String? timeZone, DateTime? dateTimeOriginal, int? rating, int? width, int? height, double? latitude, double? longitude, String? city, String? state, String? country, String? make, String? model, String? lens, double? f, double? mm, int? iso, double? exposureSeconds)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _ExifInfo() when $default != null:
return $default(_that.assetId,_that.fileSize,_that.description,_that.isFlipped,_that.orientation,_that.timeZone,_that.dateTimeOriginal,_that.rating,_that.width,_that.height,_that.latitude,_that.longitude,_that.city,_that.state,_that.country,_that.make,_that.model,_that.lens,_that.f,_that.mm,_that.iso,_that.exposureSeconds);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int? assetId, int? fileSize, String? description, bool isFlipped, String? orientation, String? timeZone, DateTime? dateTimeOriginal, int? rating, int? width, int? height, double? latitude, double? longitude, String? city, String? state, String? country, String? make, String? model, String? lens, double? f, double? mm, int? iso, double? exposureSeconds) $default,) {final _that = this;
switch (_that) {
case _ExifInfo():
return $default(_that.assetId,_that.fileSize,_that.description,_that.isFlipped,_that.orientation,_that.timeZone,_that.dateTimeOriginal,_that.rating,_that.width,_that.height,_that.latitude,_that.longitude,_that.city,_that.state,_that.country,_that.make,_that.model,_that.lens,_that.f,_that.mm,_that.iso,_that.exposureSeconds);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int? assetId, int? fileSize, String? description, bool isFlipped, String? orientation, String? timeZone, DateTime? dateTimeOriginal, int? rating, int? width, int? height, double? latitude, double? longitude, String? city, String? state, String? country, String? make, String? model, String? lens, double? f, double? mm, int? iso, double? exposureSeconds)? $default,) {final _that = this;
switch (_that) {
case _ExifInfo() when $default != null:
return $default(_that.assetId,_that.fileSize,_that.description,_that.isFlipped,_that.orientation,_that.timeZone,_that.dateTimeOriginal,_that.rating,_that.width,_that.height,_that.latitude,_that.longitude,_that.city,_that.state,_that.country,_that.make,_that.model,_that.lens,_that.f,_that.mm,_that.iso,_that.exposureSeconds);case _:
return null;
}
}
}
/// @nodoc
class _ExifInfo extends ExifInfo {
const _ExifInfo({this.assetId, this.fileSize, this.description, this.isFlipped = false, this.orientation, this.timeZone, this.dateTimeOriginal, this.rating, this.width, this.height, this.latitude, this.longitude, this.city, this.state, this.country, this.make, this.model, this.lens, this.f, this.mm, this.iso, this.exposureSeconds}): super._();
@override final int? assetId;
@override final int? fileSize;
@override final String? description;
@override@JsonKey() final bool isFlipped;
@override final String? orientation;
@override final String? timeZone;
@override final DateTime? dateTimeOriginal;
@override final int? rating;
@override final int? width;
@override final int? height;
// GPS
@override final double? latitude;
@override final double? longitude;
@override final String? city;
@override final String? state;
@override final String? country;
// Camera related
@override final String? make;
@override final String? model;
@override final String? lens;
@override final double? f;
@override final double? mm;
@override final int? iso;
@override final double? exposureSeconds;
/// Create a copy of ExifInfo
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$ExifInfoCopyWith<_ExifInfo> get copyWith => __$ExifInfoCopyWithImpl<_ExifInfo>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ExifInfo&&(identical(other.assetId, assetId) || other.assetId == assetId)&&(identical(other.fileSize, fileSize) || other.fileSize == fileSize)&&(identical(other.description, description) || other.description == description)&&(identical(other.isFlipped, isFlipped) || other.isFlipped == isFlipped)&&(identical(other.orientation, orientation) || other.orientation == orientation)&&(identical(other.timeZone, timeZone) || other.timeZone == timeZone)&&(identical(other.dateTimeOriginal, dateTimeOriginal) || other.dateTimeOriginal == dateTimeOriginal)&&(identical(other.rating, rating) || other.rating == rating)&&(identical(other.width, width) || other.width == width)&&(identical(other.height, height) || other.height == height)&&(identical(other.latitude, latitude) || other.latitude == latitude)&&(identical(other.longitude, longitude) || other.longitude == longitude)&&(identical(other.city, city) || other.city == city)&&(identical(other.state, state) || other.state == state)&&(identical(other.country, country) || other.country == country)&&(identical(other.make, make) || other.make == make)&&(identical(other.model, model) || other.model == model)&&(identical(other.lens, lens) || other.lens == lens)&&(identical(other.f, f) || other.f == f)&&(identical(other.mm, mm) || other.mm == mm)&&(identical(other.iso, iso) || other.iso == iso)&&(identical(other.exposureSeconds, exposureSeconds) || other.exposureSeconds == exposureSeconds));
}
@override
int get hashCode => Object.hashAll([runtimeType,assetId,fileSize,description,isFlipped,orientation,timeZone,dateTimeOriginal,rating,width,height,latitude,longitude,city,state,country,make,model,lens,f,mm,iso,exposureSeconds]);
@override
String toString() {
return 'ExifInfo(assetId: $assetId, fileSize: $fileSize, description: $description, isFlipped: $isFlipped, orientation: $orientation, timeZone: $timeZone, dateTimeOriginal: $dateTimeOriginal, rating: $rating, width: $width, height: $height, latitude: $latitude, longitude: $longitude, city: $city, state: $state, country: $country, make: $make, model: $model, lens: $lens, f: $f, mm: $mm, iso: $iso, exposureSeconds: $exposureSeconds)';
}
}
/// @nodoc
abstract mixin class _$ExifInfoCopyWith<$Res> implements $ExifInfoCopyWith<$Res> {
factory _$ExifInfoCopyWith(_ExifInfo value, $Res Function(_ExifInfo) _then) = __$ExifInfoCopyWithImpl;
@override @useResult
$Res call({
int? assetId, int? fileSize, String? description, bool isFlipped, String? orientation, String? timeZone, DateTime? dateTimeOriginal, int? rating, int? width, int? height, double? latitude, double? longitude, String? city, String? state, String? country, String? make, String? model, String? lens, double? f, double? mm, int? iso, double? exposureSeconds
});
}
/// @nodoc
class __$ExifInfoCopyWithImpl<$Res>
implements _$ExifInfoCopyWith<$Res> {
__$ExifInfoCopyWithImpl(this._self, this._then);
final _ExifInfo _self;
final $Res Function(_ExifInfo) _then;
/// Create a copy of ExifInfo
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? assetId = freezed,Object? fileSize = freezed,Object? description = freezed,Object? isFlipped = null,Object? orientation = freezed,Object? timeZone = freezed,Object? dateTimeOriginal = freezed,Object? rating = freezed,Object? width = freezed,Object? height = freezed,Object? latitude = freezed,Object? longitude = freezed,Object? city = freezed,Object? state = freezed,Object? country = freezed,Object? make = freezed,Object? model = freezed,Object? lens = freezed,Object? f = freezed,Object? mm = freezed,Object? iso = freezed,Object? exposureSeconds = freezed,}) {
return _then(_ExifInfo(
assetId: freezed == assetId ? _self.assetId : assetId // ignore: cast_nullable_to_non_nullable
as int?,fileSize: freezed == fileSize ? _self.fileSize : fileSize // ignore: cast_nullable_to_non_nullable
as int?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String?,isFlipped: null == isFlipped ? _self.isFlipped : isFlipped // ignore: cast_nullable_to_non_nullable
as bool,orientation: freezed == orientation ? _self.orientation : orientation // ignore: cast_nullable_to_non_nullable
as String?,timeZone: freezed == timeZone ? _self.timeZone : timeZone // ignore: cast_nullable_to_non_nullable
as String?,dateTimeOriginal: freezed == dateTimeOriginal ? _self.dateTimeOriginal : dateTimeOriginal // ignore: cast_nullable_to_non_nullable
as DateTime?,rating: freezed == rating ? _self.rating : rating // ignore: cast_nullable_to_non_nullable
as int?,width: freezed == width ? _self.width : width // ignore: cast_nullable_to_non_nullable
as int?,height: freezed == height ? _self.height : height // ignore: cast_nullable_to_non_nullable
as int?,latitude: freezed == latitude ? _self.latitude : latitude // ignore: cast_nullable_to_non_nullable
as double?,longitude: freezed == longitude ? _self.longitude : longitude // ignore: cast_nullable_to_non_nullable
as double?,city: freezed == city ? _self.city : city // ignore: cast_nullable_to_non_nullable
as String?,state: freezed == state ? _self.state : state // ignore: cast_nullable_to_non_nullable
as String?,country: freezed == country ? _self.country : country // ignore: cast_nullable_to_non_nullable
as String?,make: freezed == make ? _self.make : make // ignore: cast_nullable_to_non_nullable
as String?,model: freezed == model ? _self.model : model // ignore: cast_nullable_to_non_nullable
as String?,lens: freezed == lens ? _self.lens : lens // ignore: cast_nullable_to_non_nullable
as String?,f: freezed == f ? _self.f : f // ignore: cast_nullable_to_non_nullable
as double?,mm: freezed == mm ? _self.mm : mm // ignore: cast_nullable_to_non_nullable
as double?,iso: freezed == iso ? _self.iso : iso // ignore: cast_nullable_to_non_nullable
as int?,exposureSeconds: freezed == exposureSeconds ? _self.exposureSeconds : exposureSeconds // ignore: cast_nullable_to_non_nullable
as double?,
));
}
}
// dart format on

View File

@@ -1,23 +1,128 @@
import 'package:freezed_annotation/freezed_annotation.dart';
class Ocr {
final String id;
final String assetId;
final double x1;
final double y1;
final double x2;
final double y2;
final double x3;
final double y3;
final double x4;
final double y4;
final double boxScore;
final double textScore;
final String text;
final bool isVisible;
part 'ocr.model.freezed.dart';
const Ocr({
required this.id,
required this.assetId,
required this.x1,
required this.y1,
required this.x2,
required this.y2,
required this.x3,
required this.y3,
required this.x4,
required this.y4,
required this.boxScore,
required this.textScore,
required this.text,
required this.isVisible,
});
@freezed
abstract class Ocr with _$Ocr {
const factory Ocr({
required String id,
required String assetId,
required double x1,
required double y1,
required double x2,
required double y2,
required double x3,
required double y3,
required double x4,
required double y4,
required double boxScore,
required double textScore,
required String text,
required bool isVisible,
}) = _Ocr;
Ocr copyWith({
String? id,
String? assetId,
double? x1,
double? y1,
double? x2,
double? y2,
double? x3,
double? y3,
double? x4,
double? y4,
double? boxScore,
double? textScore,
String? text,
bool? isVisible,
}) {
return Ocr(
id: id ?? this.id,
assetId: assetId ?? this.assetId,
x1: x1 ?? this.x1,
y1: y1 ?? this.y1,
x2: x2 ?? this.x2,
y2: y2 ?? this.y2,
x3: x3 ?? this.x3,
y3: y3 ?? this.y3,
x4: x4 ?? this.x4,
y4: y4 ?? this.y4,
boxScore: boxScore ?? this.boxScore,
textScore: textScore ?? this.textScore,
text: text ?? this.text,
isVisible: isVisible ?? this.isVisible,
);
}
@override
String toString() {
return '''Ocr {
id: $id,
assetId: $assetId,
x1: $x1,
y1: $y1,
x2: $x2,
y2: $y2,
x3: $x3,
y3: $y3,
x4: $x4,
y4: $y4,
boxScore: $boxScore,
textScore: $textScore,
text: $text,
isVisible: $isVisible
}''';
}
@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
return other is Ocr &&
other.id == id &&
other.assetId == assetId &&
other.x1 == x1 &&
other.y1 == y1 &&
other.x2 == x2 &&
other.y2 == y2 &&
other.x3 == x3 &&
other.y3 == y3 &&
other.x4 == x4 &&
other.y4 == y4 &&
other.boxScore == boxScore &&
other.textScore == textScore &&
other.text == text &&
other.isVisible == isVisible;
}
@override
int get hashCode {
return id.hashCode ^
assetId.hashCode ^
x1.hashCode ^
y1.hashCode ^
x2.hashCode ^
y2.hashCode ^
x3.hashCode ^
y3.hashCode ^
x4.hashCode ^
y4.hashCode ^
boxScore.hashCode ^
textScore.hashCode ^
text.hashCode ^
isVisible.hashCode;
}
}

View File

@@ -1,310 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// 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 'ocr.model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$Ocr {
String get id; String get assetId; double get x1; double get y1; double get x2; double get y2; double get x3; double get y3; double get x4; double get y4; double get boxScore; double get textScore; String get text; bool get isVisible;
/// Create a copy of Ocr
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$OcrCopyWith<Ocr> get copyWith => _$OcrCopyWithImpl<Ocr>(this as Ocr, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is Ocr&&(identical(other.id, id) || other.id == id)&&(identical(other.assetId, assetId) || other.assetId == assetId)&&(identical(other.x1, x1) || other.x1 == x1)&&(identical(other.y1, y1) || other.y1 == y1)&&(identical(other.x2, x2) || other.x2 == x2)&&(identical(other.y2, y2) || other.y2 == y2)&&(identical(other.x3, x3) || other.x3 == x3)&&(identical(other.y3, y3) || other.y3 == y3)&&(identical(other.x4, x4) || other.x4 == x4)&&(identical(other.y4, y4) || other.y4 == y4)&&(identical(other.boxScore, boxScore) || other.boxScore == boxScore)&&(identical(other.textScore, textScore) || other.textScore == textScore)&&(identical(other.text, text) || other.text == text)&&(identical(other.isVisible, isVisible) || other.isVisible == isVisible));
}
@override
int get hashCode => Object.hash(runtimeType,id,assetId,x1,y1,x2,y2,x3,y3,x4,y4,boxScore,textScore,text,isVisible);
@override
String toString() {
return 'Ocr(id: $id, assetId: $assetId, x1: $x1, y1: $y1, x2: $x2, y2: $y2, x3: $x3, y3: $y3, x4: $x4, y4: $y4, boxScore: $boxScore, textScore: $textScore, text: $text, isVisible: $isVisible)';
}
}
/// @nodoc
abstract mixin class $OcrCopyWith<$Res> {
factory $OcrCopyWith(Ocr value, $Res Function(Ocr) _then) = _$OcrCopyWithImpl;
@useResult
$Res call({
String id, String assetId, double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4, double boxScore, double textScore, String text, bool isVisible
});
}
/// @nodoc
class _$OcrCopyWithImpl<$Res>
implements $OcrCopyWith<$Res> {
_$OcrCopyWithImpl(this._self, this._then);
final Ocr _self;
final $Res Function(Ocr) _then;
/// Create a copy of Ocr
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? assetId = null,Object? x1 = null,Object? y1 = null,Object? x2 = null,Object? y2 = null,Object? x3 = null,Object? y3 = null,Object? x4 = null,Object? y4 = null,Object? boxScore = null,Object? textScore = null,Object? text = null,Object? isVisible = null,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,assetId: null == assetId ? _self.assetId : assetId // ignore: cast_nullable_to_non_nullable
as String,x1: null == x1 ? _self.x1 : x1 // ignore: cast_nullable_to_non_nullable
as double,y1: null == y1 ? _self.y1 : y1 // ignore: cast_nullable_to_non_nullable
as double,x2: null == x2 ? _self.x2 : x2 // ignore: cast_nullable_to_non_nullable
as double,y2: null == y2 ? _self.y2 : y2 // ignore: cast_nullable_to_non_nullable
as double,x3: null == x3 ? _self.x3 : x3 // ignore: cast_nullable_to_non_nullable
as double,y3: null == y3 ? _self.y3 : y3 // ignore: cast_nullable_to_non_nullable
as double,x4: null == x4 ? _self.x4 : x4 // ignore: cast_nullable_to_non_nullable
as double,y4: null == y4 ? _self.y4 : y4 // ignore: cast_nullable_to_non_nullable
as double,boxScore: null == boxScore ? _self.boxScore : boxScore // ignore: cast_nullable_to_non_nullable
as double,textScore: null == textScore ? _self.textScore : textScore // ignore: cast_nullable_to_non_nullable
as double,text: null == text ? _self.text : text // ignore: cast_nullable_to_non_nullable
as String,isVisible: null == isVisible ? _self.isVisible : isVisible // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// Adds pattern-matching-related methods to [Ocr].
extension OcrPatterns on Ocr {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _Ocr value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _Ocr() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _Ocr value) $default,){
final _that = this;
switch (_that) {
case _Ocr():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _Ocr value)? $default,){
final _that = this;
switch (_that) {
case _Ocr() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String assetId, double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4, double boxScore, double textScore, String text, bool isVisible)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _Ocr() when $default != null:
return $default(_that.id,_that.assetId,_that.x1,_that.y1,_that.x2,_that.y2,_that.x3,_that.y3,_that.x4,_that.y4,_that.boxScore,_that.textScore,_that.text,_that.isVisible);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String assetId, double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4, double boxScore, double textScore, String text, bool isVisible) $default,) {final _that = this;
switch (_that) {
case _Ocr():
return $default(_that.id,_that.assetId,_that.x1,_that.y1,_that.x2,_that.y2,_that.x3,_that.y3,_that.x4,_that.y4,_that.boxScore,_that.textScore,_that.text,_that.isVisible);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String assetId, double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4, double boxScore, double textScore, String text, bool isVisible)? $default,) {final _that = this;
switch (_that) {
case _Ocr() when $default != null:
return $default(_that.id,_that.assetId,_that.x1,_that.y1,_that.x2,_that.y2,_that.x3,_that.y3,_that.x4,_that.y4,_that.boxScore,_that.textScore,_that.text,_that.isVisible);case _:
return null;
}
}
}
/// @nodoc
class _Ocr implements Ocr {
const _Ocr({required this.id, required this.assetId, required this.x1, required this.y1, required this.x2, required this.y2, required this.x3, required this.y3, required this.x4, required this.y4, required this.boxScore, required this.textScore, required this.text, required this.isVisible});
@override final String id;
@override final String assetId;
@override final double x1;
@override final double y1;
@override final double x2;
@override final double y2;
@override final double x3;
@override final double y3;
@override final double x4;
@override final double y4;
@override final double boxScore;
@override final double textScore;
@override final String text;
@override final bool isVisible;
/// Create a copy of Ocr
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$OcrCopyWith<_Ocr> get copyWith => __$OcrCopyWithImpl<_Ocr>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Ocr&&(identical(other.id, id) || other.id == id)&&(identical(other.assetId, assetId) || other.assetId == assetId)&&(identical(other.x1, x1) || other.x1 == x1)&&(identical(other.y1, y1) || other.y1 == y1)&&(identical(other.x2, x2) || other.x2 == x2)&&(identical(other.y2, y2) || other.y2 == y2)&&(identical(other.x3, x3) || other.x3 == x3)&&(identical(other.y3, y3) || other.y3 == y3)&&(identical(other.x4, x4) || other.x4 == x4)&&(identical(other.y4, y4) || other.y4 == y4)&&(identical(other.boxScore, boxScore) || other.boxScore == boxScore)&&(identical(other.textScore, textScore) || other.textScore == textScore)&&(identical(other.text, text) || other.text == text)&&(identical(other.isVisible, isVisible) || other.isVisible == isVisible));
}
@override
int get hashCode => Object.hash(runtimeType,id,assetId,x1,y1,x2,y2,x3,y3,x4,y4,boxScore,textScore,text,isVisible);
@override
String toString() {
return 'Ocr(id: $id, assetId: $assetId, x1: $x1, y1: $y1, x2: $x2, y2: $y2, x3: $x3, y3: $y3, x4: $x4, y4: $y4, boxScore: $boxScore, textScore: $textScore, text: $text, isVisible: $isVisible)';
}
}
/// @nodoc
abstract mixin class _$OcrCopyWith<$Res> implements $OcrCopyWith<$Res> {
factory _$OcrCopyWith(_Ocr value, $Res Function(_Ocr) _then) = __$OcrCopyWithImpl;
@override @useResult
$Res call({
String id, String assetId, double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4, double boxScore, double textScore, String text, bool isVisible
});
}
/// @nodoc
class __$OcrCopyWithImpl<$Res>
implements _$OcrCopyWith<$Res> {
__$OcrCopyWithImpl(this._self, this._then);
final _Ocr _self;
final $Res Function(_Ocr) _then;
/// Create a copy of Ocr
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? assetId = null,Object? x1 = null,Object? y1 = null,Object? x2 = null,Object? y2 = null,Object? x3 = null,Object? y3 = null,Object? x4 = null,Object? y4 = null,Object? boxScore = null,Object? textScore = null,Object? text = null,Object? isVisible = null,}) {
return _then(_Ocr(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,assetId: null == assetId ? _self.assetId : assetId // ignore: cast_nullable_to_non_nullable
as String,x1: null == x1 ? _self.x1 : x1 // ignore: cast_nullable_to_non_nullable
as double,y1: null == y1 ? _self.y1 : y1 // ignore: cast_nullable_to_non_nullable
as double,x2: null == x2 ? _self.x2 : x2 // ignore: cast_nullable_to_non_nullable
as double,y2: null == y2 ? _self.y2 : y2 // ignore: cast_nullable_to_non_nullable
as double,x3: null == x3 ? _self.x3 : x3 // ignore: cast_nullable_to_non_nullable
as double,y3: null == y3 ? _self.y3 : y3 // ignore: cast_nullable_to_non_nullable
as double,x4: null == x4 ? _self.x4 : x4 // ignore: cast_nullable_to_non_nullable
as double,y4: null == y4 ? _self.y4 : y4 // ignore: cast_nullable_to_non_nullable
as double,boxScore: null == boxScore ? _self.boxScore : boxScore // ignore: cast_nullable_to_non_nullable
as double,textScore: null == textScore ? _self.textScore : textScore // ignore: cast_nullable_to_non_nullable
as double,text: null == text ? _self.text : text // ignore: cast_nullable_to_non_nullable
as String,isVisible: null == isVisible ? _self.isVisible : isVisible // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
// dart format on

View File

@@ -1,6 +1,6 @@
import 'dart:convert';
enum CastDestinationType { googleCast }
enum CastDestinationType { googleCast, fCast }
enum CastState { idle, playing, paused, buffering }

View File

@@ -1,19 +1,17 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/models/cast/cast_manager_state.dart';
import 'package:immich_mobile/services/gcast.service.dart';
import 'package:immich_mobile/services/cast.service.dart';
final castProvider = StateNotifierProvider<CastNotifier, CastManagerState>(
(ref) => CastNotifier(ref.watch(gCastServiceProvider)),
(ref) => CastNotifier(ref.watch(castServiceProvider)),
);
class CastNotifier extends StateNotifier<CastManagerState> {
// more cast providers can be added here (ie Fcast)
final GCastService _gCastService;
final CastService _castService;
List<(String, CastDestinationType, dynamic)> discovered = List.empty();
CastNotifier(this._gCastService)
CastNotifier(this._castService)
: super(
const CastManagerState(
isCasting: false,
@@ -23,11 +21,11 @@ class CastNotifier extends StateNotifier<CastManagerState> {
castState: CastState.idle,
),
) {
_gCastService.onConnectionState = _onConnectionState;
_gCastService.onCurrentTime = _onCurrentTime;
_gCastService.onDuration = _onDuration;
_gCastService.onReceiverName = _onReceiverName;
_gCastService.onCastState = _onCastState;
_castService.onConnectionState = _onConnectionState;
_castService.onCurrentTime = _onCurrentTime;
_castService.onDuration = _onDuration;
_castService.onReceiverName = _onReceiverName;
_castService.onCastState = _onCastState;
}
void _onConnectionState(bool isCasting) {
@@ -51,23 +49,15 @@ class CastNotifier extends StateNotifier<CastManagerState> {
}
void loadMedia(RemoteAsset asset, bool reload) {
_gCastService.loadMedia(asset, reload);
_castService.loadMedia(asset, reload);
}
Future<void> connect(CastDestinationType type, dynamic device) async {
switch (type) {
case CastDestinationType.googleCast:
await _gCastService.connect(device);
break;
}
Future<void> connect(dynamic device) async {
await _castService.connect(device);
}
Future<List<(String, CastDestinationType, dynamic)>> getDevices() async {
if (discovered.isEmpty) {
discovered = await _gCastService.getDevices();
}
return discovered;
Future<List<(String, CastDestinationType, dynamic)>> getDevices() {
return _castService.getDevices();
}
void toggle() {
@@ -81,22 +71,22 @@ class CastNotifier extends StateNotifier<CastManagerState> {
}
void play() {
_gCastService.play();
_castService.play();
}
void pause() {
_gCastService.pause();
_castService.pause();
}
void seekTo(Duration position) {
_gCastService.seekTo(position);
_castService.seekTo(position);
}
void stop() {
_gCastService.stop();
_castService.stop();
}
Future<void> disconnect() async {
await _gCastService.disconnect();
await _castService.disconnect();
}
}

View File

@@ -0,0 +1,99 @@
import 'dart:async';
import 'package:fcast_sender_sdk/fcast_sender_sdk.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
final castRepositoryProvider = Provider((_) => CastRepository());
class CastRepository {
CastContext? _castContext;
CastingDevice? _device;
void Function(DeviceConnectionState)? onConnectionState;
void Function(DeviceEvent)? onDeviceEvent;
final Map<(String, ProtocolType), (DeviceInfo, int?)> _discoveredDevices = {};
int _currentDeviceGeneration = 0;
Future<void>? _initialized;
Future<void> connect(DeviceInfo deviceInfo) async {
await _ensureInitialized();
_device?.disconnect();
final device = _castContext!.createDeviceFromInfo(info: deviceInfo);
_device = device;
final thisDeviceGeneration = ++_currentDeviceGeneration;
device.connect(
eventHandler: DeviceEventHandler(
onEvent: (event) {
if (thisDeviceGeneration != _currentDeviceGeneration) {
return;
}
if (event is DeviceEvent_ConnectionStateChanged) {
onConnectionState?.call(event.newState);
}
onDeviceEvent?.call(event);
},
),
reconnectIntervalMillis: 1000,
);
}
Future<void> disconnect() async {
final device = _device;
if (device == null) {
return;
}
_device = null;
_currentDeviceGeneration++;
if (device.isReady()) {
device.stopPlayback();
await Future.delayed(const Duration(milliseconds: 500));
}
device.disconnect();
onConnectionState?.call(const DeviceConnectionState.disconnected());
}
void loadMedia(LoadRequest request) => _device?.load(request: request);
void play() => _device?.resumePlayback();
void pause() => _device?.pausePlayback();
void stop() => _device?.stopPlayback();
void seekTo(Duration position) => _device?.seek(timeSeconds: position.inMilliseconds / 1000);
Future<List<(DeviceInfo, int?)>> listDestinations() async {
final isFirstScan = _initialized == null;
await _ensureInitialized();
if (isFirstScan) {
await Future.delayed(const Duration(seconds: 3));
}
return _discoveredDevices.values.toList(growable: false);
}
Future<void> _ensureInitialized() => _initialized ??= _initialize();
Future<void> _initialize() async {
await FCastSenderSdkLib.init();
_castContext = CastContext();
final discoverer = DeviceDiscoverer();
discoverer.eventStreamController.stream.listen((event) {
switch (event) {
case DiscoveryEventDeviceAdded(:final deviceInfo, :final gcastCaps) ||
DiscoveryEventDeviceUpdated(:final deviceInfo, :final gcastCaps):
_discoveredDevices[(deviceInfo.name, deviceInfo.protocol)] = (deviceInfo, gcastCaps);
case DiscoveryEventDeviceRemoved():
_discoveredDevices.removeWhere((key, _) => key.$1 == event.name);
}
});
await discoverer.init();
}
}

View File

@@ -1,68 +0,0 @@
import 'package:cast/device.dart';
import 'package:cast/session.dart';
import 'package:cast/session_manager.dart';
import 'package:cast/discovery_service.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
final gCastRepositoryProvider = Provider((_) {
return GCastRepository();
});
class GCastRepository {
CastSession? _castSession;
void Function(CastSessionState)? onCastStatus;
void Function(Map<String, dynamic>)? onCastMessage;
Map<String, dynamic>? _receiverStatus;
GCastRepository();
Future<void> connect(CastDevice device) async {
_castSession = await CastSessionManager().startSession(device);
_castSession?.stateStream.listen((state) {
onCastStatus?.call(state);
});
_castSession?.messageStream.listen((message) {
onCastMessage?.call(message);
if (message['type'] == 'RECEIVER_STATUS') {
_receiverStatus = message;
}
});
// open the default receiver
sendMessage(CastSession.kNamespaceReceiver, {'type': 'LAUNCH', 'appId': 'CC1AD845'});
}
Future<void> disconnect() async {
final sessionID = getSessionId();
sendMessage(CastSession.kNamespaceReceiver, {'type': "STOP", "sessionId": sessionID});
// wait 500ms to ensure the stop command is processed
await Future.delayed(const Duration(milliseconds: 500));
await _castSession?.close();
}
String? getSessionId() {
if (_receiverStatus == null) {
return null;
}
return _receiverStatus!['status']['applications'][0]['sessionId'];
}
void sendMessage(String namespace, Map<String, dynamic> message) {
if (_castSession == null) {
throw Exception("Cast session is not established");
}
_castSession!.sendMessage(namespace, message);
}
Future<List<CastDevice>> listDestinations() async {
return await CastDiscoveryService().search(timeout: const Duration(seconds: 3));
}
}

View File

@@ -0,0 +1,205 @@
import 'package:fcast_sender_sdk/fcast_sender_sdk.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/models/cast/cast_manager_state.dart';
import 'package:immich_mobile/models/sessions/session_create_response.model.dart';
import 'package:immich_mobile/repositories/asset_api.repository.dart';
import 'package:immich_mobile/repositories/cast.repository.dart';
import 'package:immich_mobile/repositories/sessions_api.repository.dart';
import 'package:immich_mobile/utils/image_url_builder.dart';
// ignore: import_rule_openapi, we are only using the AssetMediaSize enum
import 'package:openapi/api.dart';
final castServiceProvider = Provider(
(ref) => CastService(
ref.watch(castRepositoryProvider),
ref.watch(sessionsAPIRepositoryProvider),
ref.watch(assetApiRepositoryProvider),
),
);
class CastService {
final CastRepository _castRepository;
final SessionsAPIRepository _sessionsApiService;
final AssetApiRepository _assetApiRepository;
SessionCreateResponse? sessionKey;
String? currentAssetId;
bool isConnected = false;
void Function(bool)? onConnectionState;
void Function(Duration)? onCurrentTime;
void Function(Duration)? onDuration;
void Function(String)? onReceiverName;
void Function(CastState)? onCastState;
CastService(this._castRepository, this._sessionsApiService, this._assetApiRepository) {
_castRepository.onConnectionState = _onCastStatusCallback;
_castRepository.onDeviceEvent = _onDeviceEventCallback;
}
void _onCastStatusCallback(DeviceConnectionState state) {
if (state is DeviceConnectionState_Connected) {
onConnectionState?.call(true);
isConnected = true;
} else if (state is DeviceConnectionState_Disconnected) {
onConnectionState?.call(false);
isConnected = false;
onReceiverName?.call("");
currentAssetId = null;
}
}
void _onDeviceEventCallback(DeviceEvent event) {
switch (event) {
case DeviceEvent_PlaybackStateChanged():
_handlePlaybackState(event.newPlaybackState);
break;
case DeviceEvent_TimeChanged():
onCurrentTime?.call(Duration(milliseconds: (event.newTime * 1000).toInt()));
break;
case DeviceEvent_DurationChanged():
onDuration?.call(Duration(milliseconds: (event.newDuration * 1000).toInt()));
break;
default:
break;
}
}
void _handlePlaybackState(PlaybackState state) {
switch (state) {
case PlaybackState.playing:
onCastState?.call(CastState.playing);
break;
case PlaybackState.paused:
onCastState?.call(CastState.paused);
break;
case PlaybackState.buffering:
onCastState?.call(CastState.buffering);
break;
case PlaybackState.idle:
onCastState?.call(CastState.idle);
break;
}
}
Future<void> connect(dynamic device) async {
await _castRepository.connect(device);
onReceiverName?.call(device.name);
}
Future<void> disconnect() async {
onReceiverName?.call("");
currentAssetId = null;
await _castRepository.disconnect();
}
bool isSessionValid() {
// check if we already have a session token
// we should always have a expiration date
if (sessionKey == null || sessionKey?.expiresAt == null) {
return false;
}
final tokenExpiration = DateTime.parse(sessionKey!.expiresAt!);
// we want to make sure we have at least 10 seconds remaining in the session
// this is to account for network latency and other delays when sending the request
final bufferedExpiration = tokenExpiration.subtract(const Duration(seconds: 10));
return bufferedExpiration.isAfter(DateTime.now());
}
void loadMedia(RemoteAsset asset, bool reload) async {
if (!isConnected) {
return;
} else if (asset.id == currentAssetId && !reload) {
return;
}
// create a session key
if (!isSessionValid()) {
sessionKey = await _sessionsApiService.createSession(
"Cast",
"Cast",
duration: const Duration(minutes: 15).inSeconds,
);
}
final unauthenticatedUrl = asset.isVideo
? getPlaybackUrlForRemoteId(asset.id)
: getThumbnailUrlForRemoteId(asset.id, type: AssetMediaSize.fullsize);
final authenticatedURL = "$unauthenticatedUrl&sessionKey=${sessionKey?.token}";
// get image mime type
final mimeType = await _assetApiRepository.getAssetMIMEType(asset.id);
if (mimeType == null) {
return;
}
final request = asset.isVideo
? LoadRequest.video(contentType: mimeType, url: authenticatedURL, resumePosition: 0.0)
: LoadRequest.image(contentType: mimeType, url: authenticatedURL);
_castRepository.loadMedia(request);
currentAssetId = asset.id;
}
void play() {
_castRepository.play();
}
void pause() {
_castRepository.pause();
}
void seekTo(Duration position) {
_castRepository.seekTo(position);
}
void stop() {
_castRepository.stop();
currentAssetId = null;
}
// 0x01 is display capability bitmask
bool isDisplay(int ca) => (ca & 0x01) != 0;
Future<List<(String, CastDestinationType, dynamic)>> getDevices() async {
final dests = await _castRepository.listDestinations();
final fCastNames = dests
.where((dest) => dest.$1.protocol == ProtocolType.fCast)
.map((dest) => dest.$1.name)
.toSet();
return dests
.where((dest) {
final (device, gcastCaps) = dest;
if (device.protocol == ProtocolType.fCast) {
return true;
}
return isDisplay(gcastCaps ?? 0) && !fCastNames.contains(device.name);
})
.map((dest) {
final device = dest.$1;
final type = device.protocol == ProtocolType.fCast
? CastDestinationType.fCast
: CastDestinationType.googleCast;
return (device.name, type, device as dynamic);
})
.toList(growable: false);
}
}

View File

@@ -1,250 +0,0 @@
import 'dart:async';
import 'package:cast/session.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/models/cast/cast_manager_state.dart';
import 'package:immich_mobile/models/sessions/session_create_response.model.dart';
import 'package:immich_mobile/repositories/asset_api.repository.dart';
import 'package:immich_mobile/repositories/gcast.repository.dart';
import 'package:immich_mobile/repositories/sessions_api.repository.dart';
import 'package:immich_mobile/utils/image_url_builder.dart';
// ignore: import_rule_openapi, we are only using the AssetMediaSize enum
import 'package:openapi/api.dart';
final gCastServiceProvider = Provider(
(ref) => GCastService(
ref.watch(gCastRepositoryProvider),
ref.watch(sessionsAPIRepositoryProvider),
ref.watch(assetApiRepositoryProvider),
),
);
class GCastService {
final GCastRepository _gCastRepository;
final SessionsAPIRepository _sessionsApiService;
final AssetApiRepository _assetApiRepository;
SessionCreateResponse? sessionKey;
String? currentAssetId;
bool isConnected = false;
int? _sessionId;
Timer? _mediaStatusPollingTimer;
void Function(bool)? onConnectionState;
void Function(Duration)? onCurrentTime;
void Function(Duration)? onDuration;
void Function(String)? onReceiverName;
void Function(CastState)? onCastState;
GCastService(this._gCastRepository, this._sessionsApiService, this._assetApiRepository) {
_gCastRepository.onCastStatus = _onCastStatusCallback;
_gCastRepository.onCastMessage = _onCastMessageCallback;
}
void _onCastStatusCallback(CastSessionState state) {
if (state == CastSessionState.connected) {
onConnectionState?.call(true);
isConnected = true;
} else if (state == CastSessionState.closed) {
onConnectionState?.call(false);
isConnected = false;
onReceiverName?.call("");
currentAssetId = null;
}
}
void _onCastMessageCallback(Map<String, dynamic> message) {
switch (message['type']) {
case "MEDIA_STATUS":
_handleMediaStatus(message);
break;
}
}
void _handleMediaStatus(Map<String, dynamic> message) {
final statusList = (message['status'] as List).whereType<Map<String, dynamic>>().toList();
if (statusList.isEmpty) {
return;
}
final status = statusList[0];
switch (status['playerState']) {
case "PLAYING":
onCastState?.call(CastState.playing);
break;
case "PAUSED":
onCastState?.call(CastState.paused);
break;
case "BUFFERING":
onCastState?.call(CastState.buffering);
break;
case "IDLE":
onCastState?.call(CastState.idle);
// stop polling for media status if the video finished playing
if (status["idleReason"] == "FINISHED") {
_mediaStatusPollingTimer?.cancel();
}
break;
}
if (status["media"] != null && status["media"]["duration"] != null) {
final duration = Duration(milliseconds: (status["media"]["duration"] * 1000 ?? 0).toInt());
onDuration?.call(duration);
}
if (status["mediaSessionId"] != null) {
_sessionId = status["mediaSessionId"];
}
if (status["currentTime"] != null) {
final currentTime = Duration(milliseconds: (status["currentTime"] * 1000 ?? 0).toInt());
onCurrentTime?.call(currentTime);
}
}
Future<void> connect(dynamic device) async {
await _gCastRepository.connect(device);
onReceiverName?.call(device.extras["fn"] ?? "Google Cast");
}
CastDestinationType getType() {
return CastDestinationType.googleCast;
}
Future<bool> initialize() async {
// there is nothing blocking us from using Google Cast that we can check for
return true;
}
Future<void> disconnect() async {
onReceiverName?.call("");
currentAssetId = null;
await _gCastRepository.disconnect();
}
bool isSessionValid() {
// check if we already have a session token
// we should always have a expiration date
if (sessionKey == null || sessionKey?.expiresAt == null) {
return false;
}
final tokenExpiration = DateTime.parse(sessionKey!.expiresAt!);
// we want to make sure we have at least 10 seconds remaining in the session
// this is to account for network latency and other delays when sending the request
final bufferedExpiration = tokenExpiration.subtract(const Duration(seconds: 10));
return bufferedExpiration.isAfter(DateTime.now());
}
void loadMedia(RemoteAsset asset, bool reload) async {
if (!isConnected) {
return;
} else if (asset.id == currentAssetId && !reload) {
return;
}
// create a session key
if (!isSessionValid()) {
sessionKey = await _sessionsApiService.createSession(
"Cast",
"Google Cast",
duration: const Duration(minutes: 15).inSeconds,
);
}
final unauthenticatedUrl = asset.isVideo
? getPlaybackUrlForRemoteId(asset.id)
: getThumbnailUrlForRemoteId(asset.id, type: AssetMediaSize.fullsize);
final authenticatedURL = "$unauthenticatedUrl&sessionKey=${sessionKey?.token}";
// get image mime type
final mimeType = await _assetApiRepository.getAssetMIMEType(asset.id);
if (mimeType == null) {
return;
}
_gCastRepository.sendMessage(CastSession.kNamespaceMedia, {
"type": "LOAD",
"media": {
"contentId": authenticatedURL,
"streamType": "BUFFERED",
"contentType": mimeType,
"contentUrl": authenticatedURL,
},
"autoplay": true,
});
currentAssetId = asset.id;
// we need to poll for media status since the cast device does not
// send a message when the media is loaded for whatever reason
// only do this on videos
_mediaStatusPollingTimer?.cancel();
if (asset.isVideo) {
_mediaStatusPollingTimer = Timer.periodic(const Duration(milliseconds: 500), (timer) {
if (isConnected) {
_gCastRepository.sendMessage(CastSession.kNamespaceMedia, {
"type": "GET_STATUS",
"mediaSessionId": _sessionId,
});
} else {
timer.cancel();
}
});
}
}
void play() {
_gCastRepository.sendMessage(CastSession.kNamespaceMedia, {"type": "PLAY", "mediaSessionId": _sessionId});
}
void pause() {
_gCastRepository.sendMessage(CastSession.kNamespaceMedia, {"type": "PAUSE", "mediaSessionId": _sessionId});
}
void seekTo(Duration position) {
_gCastRepository.sendMessage(CastSession.kNamespaceMedia, {
"type": "SEEK",
"mediaSessionId": _sessionId,
"currentTime": position.inSeconds,
});
}
void stop() {
_gCastRepository.sendMessage(CastSession.kNamespaceMedia, {"type": "STOP", "mediaSessionId": _sessionId});
_mediaStatusPollingTimer?.cancel();
currentAssetId = null;
}
// 0x01 is display capability bitmask
bool isDisplay(int ca) => (ca & 0x01) != 0;
Future<List<(String, CastDestinationType, dynamic)>> getDevices() async {
final dests = await _gCastRepository.listDestinations();
return dests
.map((device) => (device.extras["fn"] ?? "Google Cast", CastDestinationType.googleCast, device))
.where((device) {
final caString = device.$3.extras["ca"];
final caNumber = int.tryParse(caString ?? "0") ?? 0;
return isDisplay(caNumber);
})
.toList(growable: false);
}
}

View File

@@ -69,7 +69,7 @@ class CastDialog extends ConsumerWidget {
child: Text(item, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16)).tr(),
);
} else {
final (deviceName, type, deviceObj) = item as (String, CastDestinationType, dynamic);
final (deviceName, _, deviceObj) = item as (String, CastDestinationType, dynamic);
return ListTile(
title: Text(
@@ -77,7 +77,7 @@ class CastDialog extends ConsumerWidget {
style: TextStyle(color: isCurrentDevice(deviceName) ? context.colorScheme.primary : null),
),
leading: Icon(
type == CastDestinationType.googleCast ? Icons.cast : Icons.cast_connected,
isCurrentDevice(deviceName) ? Icons.cast_connected : Icons.cast,
color: isCurrentDevice(deviceName) ? context.colorScheme.primary : null,
),
trailing: isCurrentDevice(deviceName)
@@ -95,7 +95,7 @@ class CastDialog extends ConsumerWidget {
}
if (!isCurrentDevice(deviceName)) {
unawaited(ref.read(castProvider.notifier).connect(type, deviceObj));
unawaited(ref.read(castProvider.notifier).connect(deviceObj));
}
},
);

View File

@@ -74,53 +74,53 @@ packages:
source: hosted
version: "9.5.5"
bonsoir:
dependency: "direct overridden"
dependency: transitive
description:
name: bonsoir
sha256: "2e2cf3be580deccad9a48dcaddddf90de092e74b7de2015ef58fb24e11d66496"
sha256: "1b112a966302a739d253c8dbc7ea4c1cb3eca6d8110dc59e36d76f7cf0b6edf2"
url: "https://pub.dev"
source: hosted
version: "5.1.11"
version: "6.1.0"
bonsoir_android:
dependency: transitive
description:
name: bonsoir_android
sha256: "9a65b6e50c5718c3f1a7ed6ff57ab9ed8ae990ff9c36d2b1ab3d1b90f28f7d1b"
sha256: bfa3ab7e2f65473cb369bce639dbdbdc75064848c01ac11b3c2bc3e16031ff3a
url: "https://pub.dev"
source: hosted
version: "5.1.6"
version: "6.0.2"
bonsoir_darwin:
dependency: transitive
description:
name: bonsoir_darwin
sha256: "2d25c70f0d09260be1c2ab583b80dd89cbbfd59997579dadf789c5af00c7b2e4"
sha256: d62fd62ed433aa09ec99f71f95dae53ffa0adf788c9051ff3d6b903463045d3c
url: "https://pub.dev"
source: hosted
version: "5.1.3"
version: "6.1.0"
bonsoir_linux:
dependency: transitive
description:
name: bonsoir_linux
sha256: f2639aded6e15943a9822de98a663a1056f37cbfd0a74d72c9eaa941965945c2
sha256: a49d5f328a197b27a3901b833f92c93366f2cd7085dcb495fa11ee6d9f2509a9
url: "https://pub.dev"
source: hosted
version: "5.1.3"
version: "6.0.3"
bonsoir_platform_interface:
dependency: transitive
description:
name: bonsoir_platform_interface
sha256: "08bb8b35d0198168b3bce87dbc718e4e510336cff1d97e43762e030c01636d45"
sha256: ba1cc30daaa172dfc76f88e4fee8d090674179439201997ba2b3bd9e1cca84c0
url: "https://pub.dev"
source: hosted
version: "5.1.3"
version: "6.1.0"
bonsoir_windows:
dependency: transitive
description:
name: bonsoir_windows
sha256: d4a0ca479d4f3679487a61f3174fb9fe1651e323c778b02dfa630490366be65d
sha256: "01aba2516b776eb1deb68845124dc0a41095da108276d4b307bf19c4c3e2d9b1"
url: "https://pub.dev"
source: hosted
version: "5.1.5"
version: "6.0.3"
boolean_selector:
dependency: transitive
description:
@@ -137,6 +137,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.0.6"
build_cli_annotations:
dependency: transitive
description:
name: build_cli_annotations
sha256: e563c2e01de8974566a1998410d3f6f03521788160a02503b0b1f1a46c7b3d95
url: "https://pub.dev"
source: hosted
version: "2.1.1"
build_config:
dependency: transitive
description:
@@ -177,14 +185,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "8.12.6"
cast:
dependency: "direct main"
description:
name: cast
sha256: de1856e1a31aa60a6fed627f827921f7ec6539c67c60d0c899e89646dcbe773e
url: "https://pub.dev"
source: hosted
version: "2.1.0"
characters:
dependency: transitive
description:
@@ -418,6 +418,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.3"
fcast_sender_sdk:
dependency: "direct main"
description:
name: fcast_sender_sdk
sha256: "4c4e0f51749a0930e26e42e6a3f456293c7f5f0ffed2ecd1e283c28de5e11b33"
url: "https://pub.dev"
source: hosted
version: "0.0.3"
ffi:
dependency: "direct main"
description:
@@ -569,6 +577,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.6.1"
flutter_rust_bridge:
dependency: transitive
description:
name: flutter_rust_bridge
sha256: "37ef40bc6f863652e865f0b2563ea07f0d3c58d8efad803cc01933a4b2ee067e"
url: "https://pub.dev"
source: hosted
version: "2.11.1"
flutter_secure_storage:
dependency: "direct main"
description:
@@ -667,16 +683,8 @@ packages:
url: "https://pub.dev"
source: hosted
version: "8.2.14"
freezed:
dependency: "direct dev"
description:
name: freezed
sha256: f23ea33b3863f119b58ed1b586e881a46bd28715ddcc4dbc33104524e3434131
url: "https://pub.dev"
source: hosted
version: "3.2.5"
freezed_annotation:
dependency: "direct main"
dependency: transitive
description:
name: freezed_annotation
sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8"
@@ -1414,14 +1422,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.0.5"
protobuf:
dependency: transitive
description:
name: protobuf
sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
pub_semver:
dependency: transitive
description:

View File

@@ -12,7 +12,6 @@ dependencies:
async: ^2.13.1
auto_route: ^11.1.0
background_downloader: ^9.5.4
cast: ^2.1.0
collection: ^1.19.1
connectivity_plus: ^7.0.0
crop_image: ^1.0.17
@@ -91,7 +90,7 @@ dependencies:
url: https://github.com/mertalev/http
ref: '549c24b0a4d3881a9a44b70f4873450d43c1c4af' # https://github.com/dart-lang/http/pull/1877
path: pkgs/ok_http/
freezed_annotation: ^3.1.0
fcast_sender_sdk: ^0.0.3
dev_dependencies:
auto_route_generator: ^10.5.0
@@ -105,7 +104,6 @@ dev_dependencies:
flutter_native_splash: ^2.4.7
flutter_test:
sdk: flutter
freezed: ^3.2.5
integration_test:
sdk: flutter
mocktail: ^1.0.5
@@ -115,7 +113,11 @@ dev_dependencies:
# cast 2.1.0 declares a loose bonsoir range but its code targets the 5.x API.
# Pin bonsoir to 5.x until cast releases a version compatible with bonsoir 6.x.
dependency_overrides:
bonsoir: ^5.1.11
bonsoir_darwin:
git:
url: https://github.com/Skyost/Bonsoir
ref: ce155549130e # fix(darwin): add missing Foundation import; still v6.1.0
path: packages/bonsoir_darwin
objective_c:
git:
url: https://github.com/mertalev/native