import 'package:sync_relay_core/core.dart' show FeedMessage; // Everything that may become a toast, a sound, an attention request or an // activity-log line, as one sealed type. Code that has news emits an // event ([Notifier.notify]); only the notifier decides what the user sees // and hears. Pure Dart (no Flutter): it can move to core with the policy. sealed class AppEvent { const AppEvent(); } // ---------------------------------------------------------------- feed /// A server `feed` line (see PROTOCOL.md, `feed.action`); every one lands /// in the activity log. sealed class FeedEvent extends AppEvent { final FeedMessage feed; const FeedEvent(this.feed); String get actor => feed.actor; /// [feed] as its typed event. [selfId]: this member's id (people are /// matched by `actorId` / `targetId`, never by name); [readyCount]: /// members ready in the latest state (the pitch of a `ready` sound). factory FeedEvent.of(FeedMessage f, {String selfId = '', int readyCount = 0}) { bool isSelf(String id) => selfId.isNotEmpty && id == selfId; return switch (f.action) { 'join' => MemberJoined(f), 'leave' => MemberLeft(f), 'gone' => MemberGone(f), 'return' => MemberReturned(f), 'away' => MemberSatOut(f, atYou: isSelf(f.targetId) && !isSelf(f.actorId), autoSelf: isSelf(f.actorId) && f.targetId.isEmpty && f.reason.isNotEmpty), 'in' => MemberBack(f), 'ready' => ReadyChanged(f, ready: true, readyIndex: readyCount), 'unready' => ReadyChanged(f, ready: false), 'force-ready' => ForcedReady(f, ready: true), 'force-unready' => ForcedReady(f, ready: false), 'force-play' => ForcedTransport(f, play: true), 'force-hold' => ForcedTransport(f, play: false), 'swap' => MediaSwapped(f), 'seek' => Seeked(f), 'pause' => Paused(f), 'resume' => Resumed(f), 'finished' => Finished(f), 'chat' => ChatPosted(f, own: isSelf(f.actorId)), 'rename' => MemberRenamed(f), 'avatar' => AvatarChanged(f), 'queue-add' => QueueAdded(f), 'queue-remove' => QueueRemoved(f), 'queue-skip' => QueueSkipped(f), 'fetch-fail' => FetchFailed(f), _ => OtherFeed(f), }; } } class MemberJoined extends FeedEvent { const MemberJoined(super.feed); } /// Left for good (`reason` `bye`: the app closed or Leave; `grace`: gone /// too long). class MemberLeft extends FeedEvent { const MemberLeft(super.feed); } /// Lost signal: gone for a while, kept in its grace period. class MemberGone extends FeedEvent { const MemberGone(super.feed); } /// Back within its grace after a [MemberGone]. class MemberReturned extends FeedEvent { const MemberReturned(super.feed); } /// Someone sat out (`away`: the actor itself, or `target` sat out by the /// actor; `reason` `idle` / `minimised` for an automatic one); [atYou]: /// someone else sat this member out; [autoSelf]: this app's auto sit-out /// set this member away. class MemberSatOut extends FeedEvent { final bool atYou; final bool autoSelf; const MemberSatOut(super.feed, {this.atYou = false, this.autoSelf = false}); } /// Back from sitting out (`in`). class MemberBack extends FeedEvent { const MemberBack(super.feed); } class ReadyChanged extends FeedEvent { final bool ready; /// Members ready when it came (pitch of the ready sound). final int readyIndex; const ReadyChanged(super.feed, {required this.ready, this.readyIndex = 0}); } class ForcedReady extends FeedEvent { final bool ready; const ForcedReady(super.feed, {required this.ready}); } /// Force play / hold. class ForcedTransport extends FeedEvent { final bool play; const ForcedTransport(super.feed, {required this.play}); } class MediaSwapped extends FeedEvent { const MediaSwapped(super.feed); } class Seeked extends FeedEvent { const Seeked(super.feed); } class Paused extends FeedEvent { const Paused(super.feed); } class Resumed extends FeedEvent { const Resumed(super.feed); } class Finished extends FeedEvent { const Finished(super.feed); } /// A chat message (`reason` carries the text); [own]: this member sent it. class ChatPosted extends FeedEvent { final bool own; const ChatPosted(super.feed, {this.own = false}); } class MemberRenamed extends FeedEvent { const MemberRenamed(super.feed); } class AvatarChanged extends FeedEvent { const AvatarChanged(super.feed); } class QueueAdded extends FeedEvent { const QueueAdded(super.feed); } class QueueRemoved extends FeedEvent { const QueueRemoved(super.feed); } /// Up next skipped an item (`target` its title, `reason` why). class QueueSkipped extends FeedEvent { const QueueSkipped(super.feed); } /// The server could not fetch the current media. class FetchFailed extends FeedEvent { const FetchFailed(super.feed); } /// An action this app does not know (a newer server). class OtherFeed extends FeedEvent { const OtherFeed(super.feed); } // -------------------------------------------------------------- session /// Someone poked this member. class Nudged extends AppEvent { /// Their name; '' when unknown. final String name; const Nudged(this.name); } /// Another member's player cannot open the current media. class MemberCantPlay extends AppEvent { final String name; final String error; const MemberCantPlay(this.name, this.error); } /// This member passes the video on slower than it plays. class SlowSource extends AppEvent { final String text; const SlowSource(this.text); } /// This member started passing the video on through the server. class RelayStarted extends AppEvent { const RelayStarted(); } class RelayFailed extends AppEvent { final String error; const RelayFailed(this.error); } /// The connection to the server dropped and stayed down a few seconds /// (once per drop). class ConnectionLost extends AppEvent { const ConnectionLost(); } /// It came back after [ConnectionLost]. class ConnectionBack extends AppEvent { const ConnectionBack(); } /// Jellyfin progress reports fail (once per media). class JellyfinProgressFailed extends AppEvent { final bool signInExpired; const JellyfinProgressFailed({required this.signInExpired}); } // ------------------------------------------------------------------ app /// The player's shader message (a preset change, shaders turned off). class ShaderMessage extends AppEvent { final String text; const ShaderMessage(this.text); } /// This app added an item to Up next. class AddedToUpNext extends AppEvent { final String title; const AddedToUpNext(this.title); } /// Putting something up or adding it to Up next failed ([text]: why). class ActionFailed extends AppEvent { final String text; const ActionFailed(this.text); } /// The Schedule tab's request results and errors. class ScheduleResult extends AppEvent { final String text; const ScheduleResult(this.text); } /// "Mark a problem" done; [inSession]: in the server's session log. class ProblemMarked extends AppEvent { final bool inSession; const ProblemMarked({required this.inSession}); } /// Something went on the clipboard ([text]: the confirmation, e.g. /// "Link copied"). class Copied extends AppEvent { final String text; const Copied(this.text); } class CopyFailed extends AppEvent { const CopyFailed(); } /// A newer release is out (the update band shows it). class UpdateAvailable extends AppEvent { final String version; const UpdateAvailable(this.version); }