code review changes from @shenlong-tanwen

This commit is contained in:
bwees 2025-05-28 10:25:10 -05:00
parent 9b42e1b561
commit f999c77079
No known key found for this signature in database
9 changed files with 85 additions and 87 deletions

View File

@ -5,8 +5,6 @@ abstract interface class ICastDestinationService {
Future<bool> initialize(); Future<bool> initialize();
CastDestinationType getType(); CastDestinationType getType();
bool isAvailable();
void Function(bool)? onConnectionState; void Function(bool)? onConnectionState;
void Function(Duration)? onCurrentTime; void Function(Duration)? onCurrentTime;
@ -15,12 +13,14 @@ abstract interface class ICastDestinationService {
void Function(String)? onReceiverName; void Function(String)? onReceiverName;
void Function(CastState)? onCastState; void Function(CastState)? onCastState;
Future<void> connect(dynamic device);
void loadMedia(Asset asset, bool reload); void loadMedia(Asset asset, bool reload);
void play(); void play();
void pause(); void pause();
void seekTo(Duration position); void seekTo(Duration position);
void disconnect(); Future<void> disconnect();
Future<List<(String, CastDestinationType, dynamic)>> getDevices(); Future<List<(String, CastDestinationType, dynamic)>> getDevices();
} }

View File

@ -11,7 +11,7 @@ class CastManagerState {
final Duration currentTime; final Duration currentTime;
final Duration duration; final Duration duration;
CastManagerState({ const CastManagerState({
required this.isCasting, required this.isCasting,
required this.receiverName, required this.receiverName,
required this.castState, required this.castState,
@ -53,7 +53,8 @@ class CastManagerState {
receiverName: map['receiverName'] ?? '', receiverName: map['receiverName'] ?? '',
castState: map['castState'] ?? CastState.idle, castState: map['castState'] ?? CastState.idle,
currentTime: Duration(seconds: map['currentTime']?.toInt() ?? 0), currentTime: Duration(seconds: map['currentTime']?.toInt() ?? 0),
duration: Duration(seconds: map['duration']?.toInt() ?? 0),); duration: Duration(seconds: map['duration']?.toInt() ?? 0),
);
} }
String toJson() => json.encode(toMap()); String toJson() => json.encode(toMap());

View File

@ -8,7 +8,7 @@ class SessionCreateResponse {
final String token; final String token;
final String updatedAt; final String updatedAt;
SessionCreateResponse({ const SessionCreateResponse({
required this.createdAt, required this.createdAt,
required this.current, required this.current,
required this.deviceOS, required this.deviceOS,

View File

@ -61,7 +61,7 @@ class NativeVideoViewerPage extends HookConsumerWidget {
final log = Logger('NativeVideoViewerPage'); final log = Logger('NativeVideoViewerPage');
final cast = ref.watch(castProvider); final isCasting = ref.watch(castProvider.select((c) => c.isCasting));
Future<VideoSource?> createSource() async { Future<VideoSource?> createSource() async {
if (!context.mounted) { if (!context.mounted) {
@ -394,7 +394,7 @@ class NativeVideoViewerPage extends HookConsumerWidget {
// This remains under the video to avoid flickering // This remains under the video to avoid flickering
// For motion videos, this is the image portion of the asset // For motion videos, this is the image portion of the asset
Center(key: ValueKey(asset.id), child: image), Center(key: ValueKey(asset.id), child: image),
if (aspectRatio.value != null && !cast.isCasting) if (aspectRatio.value != null && !isCasting)
Visibility.maintain( Visibility.maintain(
key: ValueKey(asset), key: ValueKey(asset),
visible: isVisible.value, visible: isVisible.value,

View File

@ -1,5 +1,6 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/interfaces/cast_destination_service.interface.dart';
import 'package:immich_mobile/models/cast/cast_manager_state.dart'; import 'package:immich_mobile/models/cast/cast_manager_state.dart';
import 'package:immich_mobile/services/gcast.service.dart'; import 'package:immich_mobile/services/gcast.service.dart';
@ -8,13 +9,14 @@ final castProvider = StateNotifierProvider<CastNotifier, CastManagerState>(
); );
class CastNotifier extends StateNotifier<CastManagerState> { class CastNotifier extends StateNotifier<CastManagerState> {
final GCastService _gCastService; // more cast providers can be added here (ie Fcast)
final ICastDestinationService _gCastService;
List<(String, CastDestinationType, dynamic)> discovered = List.empty(); List<(String, CastDestinationType, dynamic)> discovered = List.empty();
CastNotifier(this._gCastService) CastNotifier(this._gCastService)
: super( : super(
CastManagerState( const CastManagerState(
isCasting: false, isCasting: false,
currentTime: Duration.zero, currentTime: Duration.zero,
duration: Duration.zero, duration: Duration.zero,

View File

@ -14,6 +14,7 @@ import 'package:immich_mobile/repositories/gcast.repository.dart';
import 'package:immich_mobile/repositories/sessions_api.repository.dart'; import 'package:immich_mobile/repositories/sessions_api.repository.dart';
import 'package:immich_mobile/utils/image_url_builder.dart'; import 'package:immich_mobile/utils/image_url_builder.dart';
import 'package:immich_mobile/utils/url_helper.dart'; import 'package:immich_mobile/utils/url_helper.dart';
// ignore: import_rule_openapi, we are only using the AssetMediaSize enum
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
final gCastServiceProvider = Provider( final gCastServiceProvider = Provider(
@ -34,7 +35,6 @@ class GCastService implements ICastDestinationService {
bool isConnected = false; bool isConnected = false;
int? _sessionId; int? _sessionId;
Timer? _mediaStatusPollingTimer; Timer? _mediaStatusPollingTimer;
CastState? castState;
@override @override
void Function(bool)? onConnectionState; void Function(bool)? onConnectionState;
@ -67,12 +67,16 @@ class GCastService implements ICastDestinationService {
} }
void _onCastMessageCallback(Map<String, dynamic> message) { void _onCastMessageCallback(Map<String, dynamic> message) {
final msgType = message['type']; switch (message['type']) {
case "MEDIA_STATUS":
_handleMediaStatus(message);
break;
}
}
if (msgType == "MEDIA_STATUS") { void _handleMediaStatus(Map<String, dynamic> message) {
final statusList = (message['status'] as List) final statusList =
.whereType<Map<String, dynamic>>() (message['status'] as List).whereType<Map<String, dynamic>>().toList();
.toList();
if (statusList.isEmpty) { if (statusList.isEmpty) {
return; return;
@ -117,7 +121,6 @@ class GCastService implements ICastDestinationService {
onCurrentTime?.call(currentTime); onCurrentTime?.call(currentTime);
} }
} }
}
Future<void> connect(CastDevice device) async { Future<void> connect(CastDevice device) async {
await _gCastRepository.connect(device); await _gCastRepository.connect(device);
@ -139,20 +142,11 @@ class GCastService implements ICastDestinationService {
} }
@override @override
void disconnect() { Future<void> disconnect() async {
_gCastRepository.disconnect(); await _gCastRepository.disconnect();
onReceiverName?.call(""); onReceiverName?.call("");
} }
@override
bool isAvailable() {
// check if server URL is https
final serverUrl = punycodeDecodeUrl(Store.tryGet(StoreKey.serverEndpoint));
return serverUrl?.startsWith("https://") ?? false;
}
bool isSessionValid() { bool isSessionValid() {
// check if we already have a session token // check if we already have a session token
// we should always have a expiration date // we should always have a expiration date
@ -220,6 +214,8 @@ class GCastService implements ICastDestinationService {
"autoplay": true, "autoplay": true,
}); });
currentAssetId = asset.remoteId;
// we need to poll for media status since the cast device does not // we need to poll for media status since the cast device does not
// send a message when the media is loaded for whatever reason // send a message when the media is loaded for whatever reason
// only do this on videos // only do this on videos

View File

@ -78,7 +78,9 @@ class CustomVideoPlayerControls extends HookConsumerWidget {
} }
ref.read(castProvider.notifier).loadMedia(asset, true); ref.read(castProvider.notifier).loadMedia(asset, true);
} }
} else { return;
}
if (state == VideoPlaybackState.playing) { if (state == VideoPlaybackState.playing) {
ref.read(videoPlayerControlsProvider.notifier).pause(); ref.read(videoPlayerControlsProvider.notifier).pause();
} else if (state == VideoPlaybackState.completed) { } else if (state == VideoPlaybackState.completed) {
@ -87,7 +89,6 @@ class CustomVideoPlayerControls extends HookConsumerWidget {
ref.read(videoPlayerControlsProvider.notifier).play(); ref.read(videoPlayerControlsProvider.notifier).play();
} }
} }
}
return GestureDetector( return GestureDetector(
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,

View File

@ -46,7 +46,7 @@ class TopControlAppBar extends HookConsumerWidget {
const double iconSize = 22.0; const double iconSize = 22.0;
final a = ref.watch(assetWatcher(asset)).value ?? asset; final a = ref.watch(assetWatcher(asset)).value ?? asset;
final album = ref.watch(currentAlbumProvider); final album = ref.watch(currentAlbumProvider);
final castManager = ref.watch(castProvider); final isCasting = ref.watch(castProvider.select((c) => c.isCasting));
final comments = album != null && final comments = album != null &&
album.remoteId != null && album.remoteId != null &&
asset.remoteId != null asset.remoteId != null
@ -181,9 +181,7 @@ class TopControlAppBar extends HookConsumerWidget {
); );
}, },
icon: Icon( icon: Icon(
castManager.isCasting isCasting ? Icons.cast_connected_rounded : Icons.cast_rounded,
? Icons.cast_connected_rounded
: Icons.cast_rounded,
size: 20.0, size: 20.0,
color: Colors.grey[200], color: Colors.grey[200],
), ),

View File

@ -71,16 +71,16 @@ class VideoPosition extends HookConsumerWidget {
ref ref
.read(castProvider.notifier) .read(castProvider.notifier)
.seekTo(seekToDuration); .seekTo(seekToDuration);
} else { return;
}
ref ref
.read(videoPlayerControlsProvider.notifier) .read(videoPlayerControlsProvider.notifier)
.position = seekToDuration.inSeconds.toDouble(); .position = seekToDuration.inSeconds.toDouble();
// This immediately updates the slider position without waiting for the video to update // This immediately updates the slider position without waiting for the video to update
ref ref.read(videoPlaybackValueProvider.notifier).position =
.read(videoPlaybackValueProvider.notifier) seekToDuration;
.position = seekToDuration;
}
}, },
), ),
), ),