Introduces shared seams for paginated views, D-pad reorder, media control routing, async singletons and the device method channel, then points the open-coded copies at them. Also removes unused models and duplicated provider/server plumbing, folds the twice-implemented artifact store in the server, and factors the repeated Flutter toolchain prologue in CI into a composite action.
80 lines
2.8 KiB
Dart
80 lines
2.8 KiB
Dart
import 'package:flutter/foundation.dart';
|
|
|
|
import '../database/app_database.dart';
|
|
import '../media/media_item.dart';
|
|
import '../utils/watch_state_notifier.dart';
|
|
|
|
@immutable
|
|
class WatchStateSnapshot {
|
|
final bool? isWatched;
|
|
final bool hasViewOffsetMs;
|
|
final int? viewOffsetMs;
|
|
|
|
const WatchStateSnapshot({this.isWatched, this.hasViewOffsetMs = false, this.viewOffsetMs});
|
|
|
|
bool get isEmpty => isWatched == null && !hasViewOffsetMs;
|
|
|
|
MediaItem apply(MediaItem item) {
|
|
var updated = item;
|
|
if (isWatched != null) {
|
|
updated = updated.withWatchedFlag(isWatched!);
|
|
}
|
|
if (hasViewOffsetMs) {
|
|
updated = updated.copyWith(viewOffsetMs: viewOffsetMs);
|
|
}
|
|
return updated;
|
|
}
|
|
|
|
@override
|
|
bool operator ==(Object other) =>
|
|
identical(this, other) ||
|
|
other is WatchStateSnapshot &&
|
|
other.isWatched == isWatched &&
|
|
other.hasViewOffsetMs == hasViewOffsetMs &&
|
|
other.viewOffsetMs == viewOffsetMs;
|
|
|
|
@override
|
|
int get hashCode => Object.hash(isWatched, hasViewOffsetMs, viewOffsetMs);
|
|
}
|
|
|
|
class WatchStateResolver {
|
|
const WatchStateResolver._();
|
|
|
|
static WatchStateSnapshot fromEvent(WatchStateEvent event) {
|
|
return switch (event.changeType) {
|
|
WatchStateChangeType.watched => const WatchStateSnapshot(isWatched: true, hasViewOffsetMs: true, viewOffsetMs: 0),
|
|
WatchStateChangeType.unwatched => const WatchStateSnapshot(
|
|
isWatched: false,
|
|
hasViewOffsetMs: true,
|
|
viewOffsetMs: 0,
|
|
),
|
|
WatchStateChangeType.progressUpdate =>
|
|
event.isNowWatched == true
|
|
? const WatchStateSnapshot(isWatched: true, hasViewOffsetMs: true, viewOffsetMs: 0)
|
|
: WatchStateSnapshot(hasViewOffsetMs: event.viewOffset != null, viewOffsetMs: event.viewOffset),
|
|
WatchStateChangeType.removedFromContinueWatching => const WatchStateSnapshot(),
|
|
};
|
|
}
|
|
|
|
static WatchStateSnapshot fromActions(Iterable<OfflineWatchProgressItem> actions) {
|
|
OfflineWatchProgressItem? latest;
|
|
|
|
for (final action in actions) {
|
|
if (action.actionType != 'watched' && action.actionType != 'unwatched' && action.actionType != 'progress') {
|
|
continue;
|
|
}
|
|
if (latest == null || action.updatedAt > latest.updatedAt) latest = action;
|
|
}
|
|
|
|
return switch (latest?.actionType) {
|
|
'watched' => const WatchStateSnapshot(isWatched: true, hasViewOffsetMs: true, viewOffsetMs: 0),
|
|
'unwatched' => const WatchStateSnapshot(isWatched: false, hasViewOffsetMs: true, viewOffsetMs: 0),
|
|
'progress' =>
|
|
latest!.shouldMarkWatched
|
|
? const WatchStateSnapshot(isWatched: true, hasViewOffsetMs: true, viewOffsetMs: 0)
|
|
: WatchStateSnapshot(hasViewOffsetMs: latest.viewOffset != null, viewOffsetMs: latest.viewOffset),
|
|
_ => const WatchStateSnapshot(),
|
|
};
|
|
}
|
|
}
|