immich/mobile/lib/providers/cast.provider.dart
Thomas f79c8cf1c1
feat(mobile): consolidate video controls (#26673)
Videos have recently been changed to support zooming, but this can make
the controls in the centre of the screen unergonomic as they will either
stay in the centre when dismissing, or stick to the video when zooming.
Neither is great. We should align the behaviour with other apps which
has the play/pause toggle at the bottom of the screen with the seeker
bar instead.

Co-authored-by: Alex <alex.tran1502@gmail.com>
2026-03-10 10:55:31 -05:00

124 lines
3.3 KiB
Dart

import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/entities/asset.entity.dart' as old_asset_entity;
import 'package:immich_mobile/models/cast/cast_manager_state.dart';
import 'package:immich_mobile/services/gcast.service.dart';
final castProvider = StateNotifierProvider<CastNotifier, CastManagerState>(
(ref) => CastNotifier(ref.watch(gCastServiceProvider)),
);
class CastNotifier extends StateNotifier<CastManagerState> {
// more cast providers can be added here (ie Fcast)
final GCastService _gCastService;
List<(String, CastDestinationType, dynamic)> discovered = List.empty();
CastNotifier(this._gCastService)
: super(
const CastManagerState(
isCasting: false,
currentTime: Duration.zero,
duration: Duration.zero,
receiverName: '',
castState: CastState.idle,
),
) {
_gCastService.onConnectionState = _onConnectionState;
_gCastService.onCurrentTime = _onCurrentTime;
_gCastService.onDuration = _onDuration;
_gCastService.onReceiverName = _onReceiverName;
_gCastService.onCastState = _onCastState;
}
void _onConnectionState(bool isCasting) {
state = state.copyWith(isCasting: isCasting);
}
void _onCurrentTime(Duration currentTime) {
state = state.copyWith(currentTime: currentTime);
}
void _onDuration(Duration duration) {
state = state.copyWith(duration: duration);
}
void _onReceiverName(String receiverName) {
state = state.copyWith(receiverName: receiverName);
}
void _onCastState(CastState castState) {
state = state.copyWith(castState: castState);
}
void loadMedia(RemoteAsset asset, bool reload) {
_gCastService.loadMedia(asset, reload);
}
// TODO: remove this when we migrate to new timeline
void loadMediaOld(old_asset_entity.Asset asset, bool reload) {
final remoteAsset = RemoteAsset(
id: asset.remoteId.toString(),
name: asset.name,
ownerId: asset.ownerId.toString(),
checksum: asset.checksum,
type: asset.type == old_asset_entity.AssetType.image
? AssetType.image
: asset.type == old_asset_entity.AssetType.video
? AssetType.video
: AssetType.other,
createdAt: asset.fileCreatedAt,
updatedAt: asset.updatedAt,
isEdited: false,
);
_gCastService.loadMedia(remoteAsset, reload);
}
Future<void> connect(CastDestinationType type, dynamic device) async {
switch (type) {
case CastDestinationType.googleCast:
await _gCastService.connect(device);
break;
}
}
Future<List<(String, CastDestinationType, dynamic)>> getDevices() async {
if (discovered.isEmpty) {
discovered = await _gCastService.getDevices();
}
return discovered;
}
void toggle() {
switch (state.castState) {
case CastState.playing:
pause();
case CastState.paused:
play();
default:
}
}
void play() {
_gCastService.play();
}
void pause() {
_gCastService.pause();
}
void seekTo(Duration position) {
_gCastService.seekTo(position);
}
void stop() {
_gCastService.stop();
}
Future<void> disconnect() async {
await _gCastService.disconnect();
}
}