Source Code added

This commit is contained in:
Fr4nz D13trich 2026-02-02 15:06:40 +01:00
parent 800376eafd
commit 9efa9bc6dd
3912 changed files with 754770 additions and 2 deletions

View file

@ -0,0 +1,49 @@
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/services/asset.service.dart';
import 'package:logging/logging.dart';
import 'package:openapi/api.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'asset_people.provider.g.dart';
/// Maintains the list of people for an asset.
@riverpod
class AssetPeopleNotifier extends _$AssetPeopleNotifier {
final log = Logger('AssetPeopleNotifier');
@override
Future<List<PersonWithFacesResponseDto>> build(Asset asset) async {
if (!asset.isRemote) {
return [];
}
final list = await ref.watch(assetServiceProvider).getRemotePeopleOfAsset(asset.remoteId!);
if (list == null) {
return [];
}
// explicitly a sorted slice to make it deterministic
// named people will be at the beginning, and names are sorted
// ascendingly
list.sort((a, b) {
final aNotEmpty = a.name.isNotEmpty;
final bNotEmpty = b.name.isNotEmpty;
if (aNotEmpty && !bNotEmpty) {
return -1;
} else if (!aNotEmpty && bNotEmpty) {
return 1;
} else if (!aNotEmpty && !bNotEmpty) {
return 0;
}
return a.name.compareTo(b.name);
});
return list;
}
Future<void> refresh() async {
// invalidate the state this way we don't have to
// duplicate the code from build.
ref.invalidateSelf();
}
}

View file

@ -0,0 +1,192 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'asset_people.provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$assetPeopleNotifierHash() =>
r'9835b180984a750c91e923e7b64dbda94f6d7574';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
abstract class _$AssetPeopleNotifier
extends
BuildlessAutoDisposeAsyncNotifier<List<PersonWithFacesResponseDto>> {
late final Asset asset;
FutureOr<List<PersonWithFacesResponseDto>> build(Asset asset);
}
/// Maintains the list of people for an asset.
///
/// Copied from [AssetPeopleNotifier].
@ProviderFor(AssetPeopleNotifier)
const assetPeopleNotifierProvider = AssetPeopleNotifierFamily();
/// Maintains the list of people for an asset.
///
/// Copied from [AssetPeopleNotifier].
class AssetPeopleNotifierFamily
extends Family<AsyncValue<List<PersonWithFacesResponseDto>>> {
/// Maintains the list of people for an asset.
///
/// Copied from [AssetPeopleNotifier].
const AssetPeopleNotifierFamily();
/// Maintains the list of people for an asset.
///
/// Copied from [AssetPeopleNotifier].
AssetPeopleNotifierProvider call(Asset asset) {
return AssetPeopleNotifierProvider(asset);
}
@override
AssetPeopleNotifierProvider getProviderOverride(
covariant AssetPeopleNotifierProvider provider,
) {
return call(provider.asset);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'assetPeopleNotifierProvider';
}
/// Maintains the list of people for an asset.
///
/// Copied from [AssetPeopleNotifier].
class AssetPeopleNotifierProvider
extends
AutoDisposeAsyncNotifierProviderImpl<
AssetPeopleNotifier,
List<PersonWithFacesResponseDto>
> {
/// Maintains the list of people for an asset.
///
/// Copied from [AssetPeopleNotifier].
AssetPeopleNotifierProvider(Asset asset)
: this._internal(
() => AssetPeopleNotifier()..asset = asset,
from: assetPeopleNotifierProvider,
name: r'assetPeopleNotifierProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$assetPeopleNotifierHash,
dependencies: AssetPeopleNotifierFamily._dependencies,
allTransitiveDependencies:
AssetPeopleNotifierFamily._allTransitiveDependencies,
asset: asset,
);
AssetPeopleNotifierProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.asset,
}) : super.internal();
final Asset asset;
@override
FutureOr<List<PersonWithFacesResponseDto>> runNotifierBuild(
covariant AssetPeopleNotifier notifier,
) {
return notifier.build(asset);
}
@override
Override overrideWith(AssetPeopleNotifier Function() create) {
return ProviderOverride(
origin: this,
override: AssetPeopleNotifierProvider._internal(
() => create()..asset = asset,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
asset: asset,
),
);
}
@override
AutoDisposeAsyncNotifierProviderElement<
AssetPeopleNotifier,
List<PersonWithFacesResponseDto>
>
createElement() {
return _AssetPeopleNotifierProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is AssetPeopleNotifierProvider && other.asset == asset;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, asset.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin AssetPeopleNotifierRef
on AutoDisposeAsyncNotifierProviderRef<List<PersonWithFacesResponseDto>> {
/// The parameter `asset` of this provider.
Asset get asset;
}
class _AssetPeopleNotifierProviderElement
extends
AutoDisposeAsyncNotifierProviderElement<
AssetPeopleNotifier,
List<PersonWithFacesResponseDto>
>
with AssetPeopleNotifierRef {
_AssetPeopleNotifierProviderElement(super.provider);
@override
Asset get asset => (origin as AssetPeopleNotifierProvider).asset;
}
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View file

@ -0,0 +1,42 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/services/asset.service.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'asset_stack.provider.g.dart';
class AssetStackNotifier extends StateNotifier<List<Asset>> {
final AssetService assetService;
final String _stackId;
AssetStackNotifier(this.assetService, this._stackId) : super([]) {
_fetchStack(_stackId);
}
void _fetchStack(String stackId) async {
if (!mounted) {
return;
}
final stack = await assetService.getStackAssets(stackId);
if (stack.isNotEmpty) {
state = stack;
}
}
void removeChild(int index) {
if (index < state.length) {
state.removeAt(index);
state = List<Asset>.from(state);
}
}
}
final assetStackStateProvider = StateNotifierProvider.autoDispose.family<AssetStackNotifier, List<Asset>, String>(
(ref, stackId) => AssetStackNotifier(ref.watch(assetServiceProvider), stackId),
);
@riverpod
int assetStackIndex(Ref _) {
return -1;
}

View file

@ -0,0 +1,27 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'asset_stack.provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$assetStackIndexHash() => r'086ddb782e3eb38b80d755666fe35be8fe7322d7';
/// See also [assetStackIndex].
@ProviderFor(assetStackIndex)
final assetStackIndexProvider = AutoDisposeProvider<int>.internal(
assetStackIndex,
name: r'assetStackIndexProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$assetStackIndexHash,
dependencies: null,
allTransitiveDependencies: null,
);
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef AssetStackIndexRef = AutoDisposeProviderRef<int>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View file

@ -0,0 +1,15 @@
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'current_asset.provider.g.dart';
@riverpod
class CurrentAsset extends _$CurrentAsset {
@override
Asset? build() => null;
void set(Asset? a) => state = a;
}
/// Mock class for testing
abstract class CurrentAssetInternal extends _$CurrentAsset {}

View file

@ -0,0 +1,26 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'current_asset.provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$currentAssetHash() => r'2def10ea594152c984ae2974d687ab6856d7bdd0';
/// See also [CurrentAsset].
@ProviderFor(CurrentAsset)
final currentAssetProvider =
AutoDisposeNotifierProvider<CurrentAsset, Asset?>.internal(
CurrentAsset.new,
name: r'currentAssetProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$currentAssetHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$CurrentAsset = AutoDisposeNotifier<Asset?>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View file

@ -0,0 +1,194 @@
import 'dart:async';
import 'package:background_downloader/background_downloader.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/models/download/download_state.model.dart';
import 'package:immich_mobile/models/download/livephotos_medatada.model.dart';
import 'package:immich_mobile/services/album.service.dart';
import 'package:immich_mobile/services/download.service.dart';
import 'package:immich_mobile/services/share.service.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
import 'package:immich_mobile/widgets/common/share_dialog.dart';
class DownloadStateNotifier extends StateNotifier<DownloadState> {
final DownloadService _downloadService;
final ShareService _shareService;
final AlbumService _albumService;
DownloadStateNotifier(this._downloadService, this._shareService, this._albumService)
: super(
const DownloadState(
downloadStatus: TaskStatus.complete,
showProgress: false,
taskProgress: <String, DownloadInfo>{},
),
) {
_downloadService.onImageDownloadStatus = _downloadImageCallback;
_downloadService.onVideoDownloadStatus = _downloadVideoCallback;
_downloadService.onLivePhotoDownloadStatus = _downloadLivePhotoCallback;
_downloadService.onTaskProgress = _taskProgressCallback;
}
void _updateDownloadStatus(String taskId, TaskStatus status) {
if (status == TaskStatus.canceled) {
return;
}
state = state.copyWith(
taskProgress: <String, DownloadInfo>{}
..addAll(state.taskProgress)
..addAll({
taskId: DownloadInfo(
progress: state.taskProgress[taskId]?.progress ?? 0,
fileName: state.taskProgress[taskId]?.fileName ?? '',
status: status,
),
}),
);
}
// Download live photo callback
void _downloadLivePhotoCallback(TaskStatusUpdate update) {
_updateDownloadStatus(update.task.taskId, update.status);
switch (update.status) {
case TaskStatus.complete:
if (update.task.metaData.isEmpty) {
return;
}
final livePhotosId = LivePhotosMetadata.fromJson(update.task.metaData).id;
_downloadService.saveLivePhotos(update.task, livePhotosId);
_onDownloadComplete(update.task.taskId);
break;
default:
break;
}
}
// Download image callback
void _downloadImageCallback(TaskStatusUpdate update) {
_updateDownloadStatus(update.task.taskId, update.status);
switch (update.status) {
case TaskStatus.complete:
_downloadService.saveImageWithPath(update.task);
_onDownloadComplete(update.task.taskId);
break;
default:
break;
}
}
// Download video callback
void _downloadVideoCallback(TaskStatusUpdate update) {
_updateDownloadStatus(update.task.taskId, update.status);
switch (update.status) {
case TaskStatus.complete:
_downloadService.saveVideo(update.task);
_onDownloadComplete(update.task.taskId);
break;
default:
break;
}
}
void _taskProgressCallback(TaskProgressUpdate update) {
// Ignore if the task is canceled or completed
if (update.progress == -2 || update.progress == -1) {
return;
}
state = state.copyWith(
showProgress: true,
taskProgress: <String, DownloadInfo>{}
..addAll(state.taskProgress)
..addAll({
update.task.taskId: DownloadInfo(
progress: update.progress,
fileName: update.task.filename,
status: TaskStatus.running,
),
}),
);
}
void _onDownloadComplete(String id) {
Future.delayed(const Duration(seconds: 2), () {
state = state.copyWith(
taskProgress: <String, DownloadInfo>{}
..addAll(state.taskProgress)
..remove(id),
);
if (state.taskProgress.isEmpty) {
state = state.copyWith(showProgress: false);
}
_albumService.refreshDeviceAlbums();
});
}
Future<List<bool>> downloadAllAsset(List<Asset> assets) async {
return await _downloadService.downloadAll(assets);
}
void downloadAsset(Asset asset) async {
await _downloadService.download(asset);
}
void cancelDownload(String id) async {
final isCanceled = await _downloadService.cancelDownload(id);
if (isCanceled) {
state = state.copyWith(
taskProgress: <String, DownloadInfo>{}
..addAll(state.taskProgress)
..remove(id),
);
}
if (state.taskProgress.isEmpty) {
state = state.copyWith(showProgress: false);
}
}
void shareAsset(Asset asset, BuildContext context) async {
unawaited(
showDialog(
context: context,
builder: (BuildContext buildContext) {
_shareService.shareAsset(asset, context).then((bool status) {
if (!status) {
ImmichToast.show(
context: context,
msg: 'image_viewer_page_state_provider_share_error'.tr(),
toastType: ToastType.error,
gravity: ToastGravity.BOTTOM,
);
}
buildContext.pop();
});
return const ShareDialog();
},
barrierDismissible: false,
useRootNavigator: false,
),
);
}
}
final downloadStateProvider = StateNotifierProvider<DownloadStateNotifier, DownloadState>(
((ref) => DownloadStateNotifier(
ref.watch(downloadServiceProvider),
ref.watch(shareServiceProvider),
ref.watch(albumServiceProvider),
)),
);

View file

@ -0,0 +1,22 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
/// Whether to display the video part of a motion photo
final isPlayingMotionVideoProvider = StateNotifierProvider<IsPlayingMotionVideo, bool>((ref) {
return IsPlayingMotionVideo(ref);
});
class IsPlayingMotionVideo extends StateNotifier<bool> {
IsPlayingMotionVideo(this.ref) : super(false);
final Ref ref;
bool get playing => state;
set playing(bool value) {
state = value;
}
void toggle() {
state = !state;
}
}

View file

@ -0,0 +1,19 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
enum RenderListStatusEnum { complete, empty, error, loading }
final renderListStatusProvider = StateNotifierProvider<RenderListStatus, RenderListStatusEnum>((ref) {
return RenderListStatus(ref);
});
class RenderListStatus extends StateNotifier<RenderListStatusEnum> {
RenderListStatus(this.ref) : super(RenderListStatusEnum.complete);
final Ref ref;
RenderListStatusEnum get status => state;
set status(RenderListStatusEnum value) {
state = value;
}
}

View file

@ -0,0 +1,9 @@
import 'package:flutter/material.dart';
final scrollToTopNotifierProvider = ScrollNotifier();
class ScrollNotifier with ChangeNotifier {
void scrollToTop() {
notifyListeners();
}
}

View file

@ -0,0 +1,14 @@
import 'package:flutter/material.dart';
final scrollToDateNotifierProvider = ScrollToDateNotifier(null);
class ScrollToDateNotifier extends ValueNotifier<DateTime?> {
ScrollToDateNotifier(super.value);
void scrollToDate(DateTime date) {
value = date;
// Manually notify listeners to trigger the scroll, even if the value hasn't changed
notifyListeners();
}
}

View file

@ -0,0 +1,101 @@
import 'dart:io';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/services/share_intent_service.dart';
import 'package:immich_mobile/services/foreground_upload.service.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as p;
final shareIntentUploadProvider = StateNotifierProvider<ShareIntentUploadStateNotifier, List<ShareIntentAttachment>>(
((ref) => ShareIntentUploadStateNotifier(
ref.watch(appRouterProvider),
ref.read(foregroundUploadServiceProvider),
ref.read(shareIntentServiceProvider),
)),
);
class ShareIntentUploadStateNotifier extends StateNotifier<List<ShareIntentAttachment>> {
final AppRouter router;
final ForegroundUploadService _foregroundUploadService;
final ShareIntentService _shareIntentService;
final Logger _logger = Logger('ShareIntentUploadStateNotifier');
ShareIntentUploadStateNotifier(this.router, this._foregroundUploadService, this._shareIntentService) : super([]);
void init() {
_shareIntentService.onSharedMedia = onSharedMedia;
_shareIntentService.init();
}
void onSharedMedia(List<ShareIntentAttachment> attachments) {
router.removeWhere((route) => route.name == "ShareIntentRoute");
clearAttachments();
addAttachments(attachments);
router.push(ShareIntentRoute(attachments: attachments));
}
void addAttachments(List<ShareIntentAttachment> attachments) {
if (attachments.isEmpty) {
return;
}
state = [...state, ...attachments];
}
void removeAttachment(ShareIntentAttachment attachment) {
final updatedState = state.where((element) => element != attachment).toList();
if (updatedState.length != state.length) {
state = updatedState;
}
}
void clearAttachments() {
if (state.isEmpty) {
return;
}
state = [];
}
Future<void> uploadAll(List<File> files) async {
for (final file in files) {
final fileId = p.hash(file.path).toString();
_updateStatus(fileId, UploadStatus.running);
}
await _foregroundUploadService.uploadShareIntent(
files,
onProgress: (fileId, bytes, totalBytes) {
final progress = totalBytes > 0 ? bytes / totalBytes : 0.0;
_updateProgress(fileId, progress);
},
onSuccess: (fileId) {
_updateStatus(fileId, UploadStatus.complete, progress: 1.0);
},
onError: (fileId, errorMessage) {
_logger.warning("Upload failed for file: $fileId, error: $errorMessage");
_updateStatus(fileId, UploadStatus.failed);
},
);
}
void _updateStatus(String fileId, UploadStatus status, {double? progress}) {
final id = int.parse(fileId);
state = [
for (final attachment in state)
if (attachment.id == id)
attachment.copyWith(status: status, uploadProgress: progress ?? attachment.uploadProgress)
else
attachment,
];
}
void _updateProgress(String fileId, double progress) {
final id = int.parse(fileId);
state = [
for (final attachment in state)
if (attachment.id == id) attachment.copyWith(uploadProgress: progress) else attachment,
];
}
}

View file

@ -0,0 +1,21 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
final showControlsProvider = StateNotifierProvider<ShowControls, bool>((ref) {
return ShowControls(ref);
});
class ShowControls extends StateNotifier<bool> {
ShowControls(this.ref) : super(true);
final Ref ref;
bool get show => state;
set show(bool value) {
state = value;
}
void toggle() {
state = !state;
}
}

View file

@ -0,0 +1,71 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider.dart';
class VideoPlaybackControls {
const VideoPlaybackControls({required this.position, required this.pause, this.restarted = false});
final Duration position;
final bool pause;
final bool restarted;
}
final videoPlayerControlsProvider = StateNotifierProvider<VideoPlayerControls, VideoPlaybackControls>((ref) {
return VideoPlayerControls(ref);
});
const videoPlayerControlsDefault = VideoPlaybackControls(position: Duration.zero, pause: false);
class VideoPlayerControls extends StateNotifier<VideoPlaybackControls> {
VideoPlayerControls(this.ref) : super(videoPlayerControlsDefault);
final Ref ref;
VideoPlaybackControls get value => state;
set value(VideoPlaybackControls value) {
state = value;
}
void reset() {
state = videoPlayerControlsDefault;
}
Duration get position => state.position;
bool get paused => state.pause;
set position(Duration value) {
if (state.position == value) {
return;
}
state = VideoPlaybackControls(position: value, pause: state.pause);
}
void pause() {
if (state.pause) {
return;
}
state = VideoPlaybackControls(position: state.position, pause: true);
}
void play() {
if (!state.pause) {
return;
}
state = VideoPlaybackControls(position: state.position, pause: false);
}
void togglePlay() {
state = VideoPlaybackControls(position: state.position, pause: !state.pause);
}
void restart() {
state = const VideoPlaybackControls(position: Duration.zero, pause: false, restarted: true);
ref.read(videoPlaybackValueProvider.notifier).value = ref
.read(videoPlaybackValueProvider.notifier)
.value
.copyWith(state: VideoPlaybackState.playing, position: Duration.zero);
}
}

View file

@ -0,0 +1,88 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:native_video_player/native_video_player.dart';
enum VideoPlaybackState { initializing, paused, playing, buffering, completed }
class VideoPlaybackValue {
/// The current position of the video
final Duration position;
/// The total duration of the video
final Duration duration;
/// The current state of the video playback
final VideoPlaybackState state;
/// The volume of the video
final double volume;
const VideoPlaybackValue({required this.position, required this.duration, required this.state, required this.volume});
factory VideoPlaybackValue.fromNativeController(NativeVideoPlayerController controller) {
final playbackInfo = controller.playbackInfo;
final videoInfo = controller.videoInfo;
if (playbackInfo == null || videoInfo == null) {
return videoPlaybackValueDefault;
}
final VideoPlaybackState status = switch (playbackInfo.status) {
PlaybackStatus.playing => VideoPlaybackState.playing,
PlaybackStatus.paused => VideoPlaybackState.paused,
PlaybackStatus.stopped => VideoPlaybackState.completed,
};
return VideoPlaybackValue(
position: Duration(milliseconds: playbackInfo.position),
duration: Duration(milliseconds: videoInfo.duration),
state: status,
volume: playbackInfo.volume,
);
}
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,
);
}
}
const VideoPlaybackValue videoPlaybackValueDefault = VideoPlaybackValue(
position: Duration.zero,
duration: Duration.zero,
state: VideoPlaybackState.initializing,
volume: 0.0,
);
final videoPlaybackValueProvider = StateNotifierProvider<VideoPlaybackValueState, VideoPlaybackValue>((ref) {
return VideoPlaybackValueState(ref);
});
class VideoPlaybackValueState extends StateNotifier<VideoPlaybackValue> {
VideoPlaybackValueState(this.ref) : super(videoPlaybackValueDefault);
final Ref ref;
VideoPlaybackValue get value => state;
set value(VideoPlaybackValue value) {
state = value;
}
set position(Duration value) {
if (state.position == value) return;
state = VideoPlaybackValue(position: value, duration: state.duration, state: state.state, volume: state.volume);
}
set status(VideoPlaybackState value) {
if (state.state == value) return;
state = VideoPlaybackValue(position: state.position, duration: state.duration, state: value, volume: state.volume);
}
void reset() {
state = videoPlaybackValueDefault;
}
}