Preserve Flutter mirror-os codebase
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
library mirror_protocol;
|
||||
|
||||
export 'src/messages.dart';
|
||||
export 'src/commands.dart';
|
||||
export 'src/state.dart';
|
||||
export 'src/dashboard_config.dart';
|
||||
@@ -0,0 +1,124 @@
|
||||
enum ScreenType {
|
||||
dashboard,
|
||||
workout,
|
||||
media,
|
||||
standby;
|
||||
|
||||
static ScreenType fromJson(String value) =>
|
||||
ScreenType.values.firstWhere((e) => e.name == value);
|
||||
|
||||
String toJson() => name;
|
||||
}
|
||||
|
||||
enum PlaybackAction {
|
||||
play,
|
||||
pause,
|
||||
resume,
|
||||
seek,
|
||||
skip,
|
||||
previous,
|
||||
stop,
|
||||
setVolume;
|
||||
|
||||
static PlaybackAction fromJson(String value) =>
|
||||
PlaybackAction.values.firstWhere((e) => e.name == value);
|
||||
|
||||
String toJson() => name;
|
||||
}
|
||||
|
||||
enum WorkoutAction {
|
||||
browse,
|
||||
setFilter,
|
||||
playVideo,
|
||||
playFolder,
|
||||
pauseWorkout,
|
||||
resumeWorkout,
|
||||
skipVideo,
|
||||
skipRest,
|
||||
previousVideo,
|
||||
stopSession;
|
||||
|
||||
static WorkoutAction fromJson(String value) =>
|
||||
WorkoutAction.values.firstWhere((e) => e.name == value);
|
||||
|
||||
String toJson() => name;
|
||||
}
|
||||
|
||||
enum MediaSource {
|
||||
workout,
|
||||
youtube,
|
||||
plex,
|
||||
local;
|
||||
|
||||
static MediaSource fromJson(String value) =>
|
||||
MediaSource.values.firstWhere((e) => e.name == value);
|
||||
|
||||
String toJson() => name;
|
||||
}
|
||||
|
||||
enum WorkoutPhase {
|
||||
idle,
|
||||
browsing,
|
||||
playing,
|
||||
resting,
|
||||
completed;
|
||||
|
||||
static WorkoutPhase fromJson(String value) =>
|
||||
WorkoutPhase.values.firstWhere((e) => e.name == value);
|
||||
|
||||
String toJson() => name;
|
||||
}
|
||||
|
||||
enum WorkoutFilter {
|
||||
all,
|
||||
unwatched,
|
||||
inProgress,
|
||||
completed;
|
||||
|
||||
static WorkoutFilter fromJson(String value) =>
|
||||
WorkoutFilter.values.firstWhere((e) => e.name == value);
|
||||
|
||||
String toJson() => name;
|
||||
}
|
||||
|
||||
enum ProgramAction {
|
||||
browse,
|
||||
playWorkout,
|
||||
skipExercise,
|
||||
previousExercise,
|
||||
pauseExercise,
|
||||
resumeExercise,
|
||||
stopProgram;
|
||||
|
||||
static ProgramAction fromJson(String value) =>
|
||||
ProgramAction.values.firstWhere((e) => e.name == value);
|
||||
|
||||
String toJson() => name;
|
||||
}
|
||||
|
||||
enum MediaAction {
|
||||
searchYouTube,
|
||||
playYouTube,
|
||||
browsePlex,
|
||||
playPlex,
|
||||
spotifyPlay,
|
||||
spotifyPause,
|
||||
spotifyNext,
|
||||
spotifyPrevious,
|
||||
spotifyTransfer;
|
||||
|
||||
static MediaAction fromJson(String value) =>
|
||||
MediaAction.values.firstWhere((e) => e.name == value);
|
||||
|
||||
String toJson() => name;
|
||||
}
|
||||
|
||||
enum DashboardAction {
|
||||
refresh,
|
||||
toggleWidget;
|
||||
|
||||
static DashboardAction fromJson(String value) =>
|
||||
DashboardAction.values.firstWhere((e) => e.name == value);
|
||||
|
||||
String toJson() => name;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
class DashboardWidgetConfig {
|
||||
final String id;
|
||||
final String type;
|
||||
final bool visible;
|
||||
final int position;
|
||||
final Map<String, dynamic> config;
|
||||
|
||||
const DashboardWidgetConfig({
|
||||
required this.id,
|
||||
required this.type,
|
||||
this.visible = true,
|
||||
required this.position,
|
||||
this.config = const {},
|
||||
});
|
||||
|
||||
DashboardWidgetConfig copyWith({
|
||||
String? id,
|
||||
String? type,
|
||||
bool? visible,
|
||||
int? position,
|
||||
Map<String, dynamic>? config,
|
||||
}) =>
|
||||
DashboardWidgetConfig(
|
||||
id: id ?? this.id,
|
||||
type: type ?? this.type,
|
||||
visible: visible ?? this.visible,
|
||||
position: position ?? this.position,
|
||||
config: config ?? this.config,
|
||||
);
|
||||
|
||||
factory DashboardWidgetConfig.fromJson(Map<String, dynamic> json) =>
|
||||
DashboardWidgetConfig(
|
||||
id: json['id'] as String,
|
||||
type: json['type'] as String,
|
||||
visible: json['visible'] as bool? ?? true,
|
||||
position: json['position'] as int,
|
||||
config: (json['config'] as Map<String, dynamic>?) ?? {},
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'type': type,
|
||||
'visible': visible,
|
||||
'position': position,
|
||||
'config': config,
|
||||
};
|
||||
}
|
||||
|
||||
class DashboardLayoutConfig {
|
||||
final List<DashboardWidgetConfig> widgets;
|
||||
|
||||
const DashboardLayoutConfig({required this.widgets});
|
||||
|
||||
factory DashboardLayoutConfig.defaultLayout() => DashboardLayoutConfig(
|
||||
widgets: [
|
||||
DashboardWidgetConfig(id: 'clock', type: 'clock', position: 0),
|
||||
DashboardWidgetConfig(id: 'weather', type: 'weather', position: 1),
|
||||
DashboardWidgetConfig(id: 'calendar', type: 'calendar', position: 2),
|
||||
DashboardWidgetConfig(
|
||||
id: 'ha_entities', type: 'ha_entities', position: 3),
|
||||
],
|
||||
);
|
||||
|
||||
factory DashboardLayoutConfig.fromJson(Map<String, dynamic> json) =>
|
||||
DashboardLayoutConfig(
|
||||
widgets: (json['widgets'] as List<dynamic>)
|
||||
.map((e) => DashboardWidgetConfig.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'widgets': widgets.map((e) => e.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
import 'dart:convert';
|
||||
import 'commands.dart';
|
||||
import 'dashboard_config.dart';
|
||||
import 'state.dart';
|
||||
|
||||
sealed class MirrorMessage {
|
||||
String get type;
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
String encode() => jsonEncode(toJson());
|
||||
|
||||
static MirrorMessage decode(String raw) {
|
||||
final json = jsonDecode(raw) as Map<String, dynamic>;
|
||||
return MirrorMessage.fromJson(json);
|
||||
}
|
||||
|
||||
static MirrorMessage fromJson(Map<String, dynamic> json) {
|
||||
final type = json['type'] as String;
|
||||
return switch (type) {
|
||||
'state_sync' => StateSync.fromJson(json),
|
||||
'state_patch' => StatePatch.fromJson(json),
|
||||
'navigate' => NavigateCommand.fromJson(json),
|
||||
'playback' => PlaybackCommand.fromJson(json),
|
||||
'workout' => WorkoutCommand.fromJson(json),
|
||||
'program' => ProgramCommand.fromJson(json),
|
||||
'media' => MediaCommand.fromJson(json),
|
||||
'event' => EventMessage.fromJson(json),
|
||||
'register' => RegisterMessage.fromJson(json),
|
||||
'dashboard_config_request' => DashboardConfigRequest.fromJson(json),
|
||||
'dashboard_config_update' => DashboardConfigUpdate.fromJson(json),
|
||||
'dashboard_config_response' => DashboardConfigResponse.fromJson(json),
|
||||
_ => throw FormatException('Unknown message type: $type'),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// --- Server → Client messages ---
|
||||
|
||||
class StateSync extends MirrorMessage {
|
||||
@override
|
||||
String get type => 'state_sync';
|
||||
final DisplayState state;
|
||||
|
||||
StateSync({required this.state});
|
||||
|
||||
static StateSync fromJson(Map<String, dynamic> json) =>
|
||||
StateSync(state: DisplayState.fromJson(json['state'] as Map<String, dynamic>));
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() => {'type': type, 'state': state.toJson()};
|
||||
}
|
||||
|
||||
class StatePatch extends MirrorMessage {
|
||||
@override
|
||||
String get type => 'state_patch';
|
||||
final Map<String, dynamic> patch;
|
||||
|
||||
StatePatch({required this.patch});
|
||||
|
||||
static StatePatch fromJson(Map<String, dynamic> json) =>
|
||||
StatePatch(patch: json['patch'] as Map<String, dynamic>);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() => {'type': type, 'patch': patch};
|
||||
}
|
||||
|
||||
class EventMessage extends MirrorMessage {
|
||||
@override
|
||||
String get type => 'event';
|
||||
final String event;
|
||||
final Map<String, dynamic>? data;
|
||||
|
||||
EventMessage({required this.event, this.data});
|
||||
|
||||
static EventMessage fromJson(Map<String, dynamic> json) => EventMessage(
|
||||
event: json['event'] as String,
|
||||
data: json['data'] as Map<String, dynamic>?,
|
||||
);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() => {'type': type, 'event': event, if (data != null) 'data': data};
|
||||
}
|
||||
|
||||
// --- Client → Server messages ---
|
||||
|
||||
class RegisterMessage extends MirrorMessage {
|
||||
@override
|
||||
String get type => 'register';
|
||||
final String role;
|
||||
|
||||
RegisterMessage({required this.role});
|
||||
|
||||
static RegisterMessage fromJson(Map<String, dynamic> json) =>
|
||||
RegisterMessage(role: json['role'] as String);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() => {'type': type, 'role': role};
|
||||
}
|
||||
|
||||
class NavigateCommand extends MirrorMessage {
|
||||
@override
|
||||
String get type => 'navigate';
|
||||
final ScreenType target;
|
||||
|
||||
NavigateCommand({required this.target});
|
||||
|
||||
static NavigateCommand fromJson(Map<String, dynamic> json) =>
|
||||
NavigateCommand(target: ScreenType.fromJson(json['target'] as String));
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() => {'type': type, 'target': target.toJson()};
|
||||
}
|
||||
|
||||
class PlaybackCommand extends MirrorMessage {
|
||||
@override
|
||||
String get type => 'playback';
|
||||
final PlaybackAction action;
|
||||
final dynamic value;
|
||||
|
||||
PlaybackCommand({required this.action, this.value});
|
||||
|
||||
static PlaybackCommand fromJson(Map<String, dynamic> json) => PlaybackCommand(
|
||||
action: PlaybackAction.fromJson(json['action'] as String),
|
||||
value: json['value'],
|
||||
);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() =>
|
||||
{'type': type, 'action': action.toJson(), if (value != null) 'value': value};
|
||||
}
|
||||
|
||||
class WorkoutCommand extends MirrorMessage {
|
||||
@override
|
||||
String get type => 'workout';
|
||||
final WorkoutAction action;
|
||||
final Map<String, dynamic>? payload;
|
||||
|
||||
WorkoutCommand({required this.action, this.payload});
|
||||
|
||||
static WorkoutCommand fromJson(Map<String, dynamic> json) => WorkoutCommand(
|
||||
action: WorkoutAction.fromJson(json['action'] as String),
|
||||
payload: json['payload'] as Map<String, dynamic>?,
|
||||
);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() =>
|
||||
{'type': type, 'action': action.toJson(), if (payload != null) 'payload': payload};
|
||||
}
|
||||
|
||||
class ProgramCommand extends MirrorMessage {
|
||||
@override
|
||||
String get type => 'program';
|
||||
final ProgramAction action;
|
||||
final Map<String, dynamic>? payload;
|
||||
|
||||
ProgramCommand({required this.action, this.payload});
|
||||
|
||||
static ProgramCommand fromJson(Map<String, dynamic> json) => ProgramCommand(
|
||||
action: ProgramAction.fromJson(json['action'] as String),
|
||||
payload: json['payload'] as Map<String, dynamic>?,
|
||||
);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() =>
|
||||
{'type': type, 'action': action.toJson(), if (payload != null) 'payload': payload};
|
||||
}
|
||||
|
||||
class MediaCommand extends MirrorMessage {
|
||||
@override
|
||||
String get type => 'media';
|
||||
final MediaAction action;
|
||||
final Map<String, dynamic>? payload;
|
||||
|
||||
MediaCommand({required this.action, this.payload});
|
||||
|
||||
static MediaCommand fromJson(Map<String, dynamic> json) => MediaCommand(
|
||||
action: MediaAction.fromJson(json['action'] as String),
|
||||
payload: json['payload'] as Map<String, dynamic>?,
|
||||
);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() =>
|
||||
{'type': type, 'action': action.toJson(), if (payload != null) 'payload': payload};
|
||||
}
|
||||
|
||||
// --- Dashboard config messages ---
|
||||
|
||||
class DashboardConfigRequest extends MirrorMessage {
|
||||
@override
|
||||
String get type => 'dashboard_config_request';
|
||||
|
||||
DashboardConfigRequest();
|
||||
|
||||
static DashboardConfigRequest fromJson(Map<String, dynamic> json) =>
|
||||
DashboardConfigRequest();
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() => {'type': type};
|
||||
}
|
||||
|
||||
class DashboardConfigUpdate extends MirrorMessage {
|
||||
@override
|
||||
String get type => 'dashboard_config_update';
|
||||
final DashboardLayoutConfig layout;
|
||||
|
||||
DashboardConfigUpdate({required this.layout});
|
||||
|
||||
static DashboardConfigUpdate fromJson(Map<String, dynamic> json) =>
|
||||
DashboardConfigUpdate(
|
||||
layout:
|
||||
DashboardLayoutConfig.fromJson(json['layout'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() => {'type': type, 'layout': layout.toJson()};
|
||||
}
|
||||
|
||||
class DashboardConfigResponse extends MirrorMessage {
|
||||
@override
|
||||
String get type => 'dashboard_config_response';
|
||||
final DashboardLayoutConfig layout;
|
||||
|
||||
DashboardConfigResponse({required this.layout});
|
||||
|
||||
static DashboardConfigResponse fromJson(Map<String, dynamic> json) =>
|
||||
DashboardConfigResponse(
|
||||
layout:
|
||||
DashboardLayoutConfig.fromJson(json['layout'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() => {'type': type, 'layout': layout.toJson()};
|
||||
}
|
||||
@@ -0,0 +1,554 @@
|
||||
import 'commands.dart';
|
||||
|
||||
class DisplayState {
|
||||
final ScreenType activeScreen;
|
||||
final PlaybackState? playback;
|
||||
final WorkoutSessionState? workout;
|
||||
final ProgramSessionState? program;
|
||||
final SpotifyState? spotify;
|
||||
final DashboardState dashboard;
|
||||
|
||||
const DisplayState({
|
||||
required this.activeScreen,
|
||||
this.playback,
|
||||
this.workout,
|
||||
this.program,
|
||||
this.spotify,
|
||||
required this.dashboard,
|
||||
});
|
||||
|
||||
DisplayState copyWith({
|
||||
ScreenType? activeScreen,
|
||||
PlaybackState? playback,
|
||||
bool clearPlayback = false,
|
||||
WorkoutSessionState? workout,
|
||||
bool clearWorkout = false,
|
||||
ProgramSessionState? program,
|
||||
bool clearProgram = false,
|
||||
SpotifyState? spotify,
|
||||
bool clearSpotify = false,
|
||||
DashboardState? dashboard,
|
||||
}) =>
|
||||
DisplayState(
|
||||
activeScreen: activeScreen ?? this.activeScreen,
|
||||
playback: clearPlayback ? null : (playback ?? this.playback),
|
||||
workout: clearWorkout ? null : (workout ?? this.workout),
|
||||
program: clearProgram ? null : (program ?? this.program),
|
||||
spotify: clearSpotify ? null : (spotify ?? this.spotify),
|
||||
dashboard: dashboard ?? this.dashboard,
|
||||
);
|
||||
|
||||
factory DisplayState.initial() => DisplayState(
|
||||
activeScreen: ScreenType.dashboard,
|
||||
dashboard: DashboardState(),
|
||||
);
|
||||
|
||||
factory DisplayState.fromJson(Map<String, dynamic> json) => DisplayState(
|
||||
activeScreen: ScreenType.fromJson(json['activeScreen'] as String),
|
||||
playback: json['playback'] != null
|
||||
? PlaybackState.fromJson(json['playback'] as Map<String, dynamic>)
|
||||
: null,
|
||||
workout: json['workout'] != null
|
||||
? WorkoutSessionState.fromJson(json['workout'] as Map<String, dynamic>)
|
||||
: null,
|
||||
program: json['program'] != null
|
||||
? ProgramSessionState.fromJson(json['program'] as Map<String, dynamic>)
|
||||
: null,
|
||||
spotify: json['spotify'] != null
|
||||
? SpotifyState.fromJson(json['spotify'] as Map<String, dynamic>)
|
||||
: null,
|
||||
dashboard: json['dashboard'] != null
|
||||
? DashboardState.fromJson(json['dashboard'] as Map<String, dynamic>)
|
||||
: DashboardState(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'activeScreen': activeScreen.toJson(),
|
||||
if (playback != null) 'playback': playback!.toJson(),
|
||||
if (workout != null) 'workout': workout!.toJson(),
|
||||
if (program != null) 'program': program!.toJson(),
|
||||
if (spotify != null) 'spotify': spotify!.toJson(),
|
||||
'dashboard': dashboard.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
class PlaybackState {
|
||||
final String mediaId;
|
||||
final String title;
|
||||
final MediaSource source;
|
||||
final Duration position;
|
||||
final Duration duration;
|
||||
final bool isPlaying;
|
||||
final double volume;
|
||||
|
||||
const PlaybackState({
|
||||
required this.mediaId,
|
||||
required this.title,
|
||||
required this.source,
|
||||
required this.position,
|
||||
required this.duration,
|
||||
required this.isPlaying,
|
||||
this.volume = 1.0,
|
||||
});
|
||||
|
||||
PlaybackState copyWith({
|
||||
String? mediaId,
|
||||
String? title,
|
||||
MediaSource? source,
|
||||
Duration? position,
|
||||
Duration? duration,
|
||||
bool? isPlaying,
|
||||
double? volume,
|
||||
}) =>
|
||||
PlaybackState(
|
||||
mediaId: mediaId ?? this.mediaId,
|
||||
title: title ?? this.title,
|
||||
source: source ?? this.source,
|
||||
position: position ?? this.position,
|
||||
duration: duration ?? this.duration,
|
||||
isPlaying: isPlaying ?? this.isPlaying,
|
||||
volume: volume ?? this.volume,
|
||||
);
|
||||
|
||||
factory PlaybackState.fromJson(Map<String, dynamic> json) => PlaybackState(
|
||||
mediaId: json['mediaId'] as String,
|
||||
title: json['title'] as String,
|
||||
source: MediaSource.fromJson(json['source'] as String),
|
||||
position: Duration(milliseconds: json['positionMs'] as int),
|
||||
duration: Duration(milliseconds: json['durationMs'] as int),
|
||||
isPlaying: json['isPlaying'] as bool,
|
||||
volume: (json['volume'] as num?)?.toDouble() ?? 1.0,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'mediaId': mediaId,
|
||||
'title': title,
|
||||
'source': source.toJson(),
|
||||
'positionMs': position.inMilliseconds,
|
||||
'durationMs': duration.inMilliseconds,
|
||||
'isPlaying': isPlaying,
|
||||
'volume': volume,
|
||||
};
|
||||
}
|
||||
|
||||
class WorkoutSessionState {
|
||||
final WorkoutPhase phase;
|
||||
final List<QueueItemState> queue;
|
||||
final int currentIndex;
|
||||
final int restRemaining;
|
||||
final String? routineName;
|
||||
final String? currentPath;
|
||||
final WorkoutFilter filter;
|
||||
final List<VideoState> videos;
|
||||
final List<String> folders;
|
||||
final WorkoutStats? stats;
|
||||
|
||||
const WorkoutSessionState({
|
||||
required this.phase,
|
||||
this.queue = const [],
|
||||
this.currentIndex = 0,
|
||||
this.restRemaining = 0,
|
||||
this.routineName,
|
||||
this.currentPath,
|
||||
this.filter = WorkoutFilter.all,
|
||||
this.videos = const [],
|
||||
this.folders = const [],
|
||||
this.stats,
|
||||
});
|
||||
|
||||
WorkoutSessionState copyWith({
|
||||
WorkoutPhase? phase,
|
||||
List<QueueItemState>? queue,
|
||||
int? currentIndex,
|
||||
int? restRemaining,
|
||||
String? routineName,
|
||||
String? currentPath,
|
||||
bool clearCurrentPath = false,
|
||||
WorkoutFilter? filter,
|
||||
List<VideoState>? videos,
|
||||
List<String>? folders,
|
||||
WorkoutStats? stats,
|
||||
}) =>
|
||||
WorkoutSessionState(
|
||||
phase: phase ?? this.phase,
|
||||
queue: queue ?? this.queue,
|
||||
currentIndex: currentIndex ?? this.currentIndex,
|
||||
restRemaining: restRemaining ?? this.restRemaining,
|
||||
routineName: routineName ?? this.routineName,
|
||||
currentPath: clearCurrentPath ? null : (currentPath ?? this.currentPath),
|
||||
filter: filter ?? this.filter,
|
||||
videos: videos ?? this.videos,
|
||||
folders: folders ?? this.folders,
|
||||
stats: stats ?? this.stats,
|
||||
);
|
||||
|
||||
factory WorkoutSessionState.fromJson(Map<String, dynamic> json) => WorkoutSessionState(
|
||||
phase: WorkoutPhase.fromJson(json['phase'] as String),
|
||||
queue: (json['queue'] as List<dynamic>?)
|
||||
?.map((e) => QueueItemState.fromJson(e as Map<String, dynamic>))
|
||||
.toList() ??
|
||||
[],
|
||||
currentIndex: json['currentIndex'] as int? ?? 0,
|
||||
restRemaining: json['restRemaining'] as int? ?? 0,
|
||||
routineName: json['routineName'] as String?,
|
||||
currentPath: json['currentPath'] as String?,
|
||||
filter: json['filter'] != null
|
||||
? WorkoutFilter.fromJson(json['filter'] as String)
|
||||
: WorkoutFilter.all,
|
||||
videos: (json['videos'] as List<dynamic>?)
|
||||
?.map((e) => VideoState.fromJson(e as Map<String, dynamic>))
|
||||
.toList() ??
|
||||
[],
|
||||
folders:
|
||||
(json['folders'] as List<dynamic>?)?.map((e) => e as String).toList() ?? [],
|
||||
stats: json['stats'] != null
|
||||
? WorkoutStats.fromJson(json['stats'] as Map<String, dynamic>)
|
||||
: null,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'phase': phase.toJson(),
|
||||
'queue': queue.map((e) => e.toJson()).toList(),
|
||||
'currentIndex': currentIndex,
|
||||
'restRemaining': restRemaining,
|
||||
if (routineName != null) 'routineName': routineName,
|
||||
if (currentPath != null) 'currentPath': currentPath,
|
||||
'filter': filter.toJson(),
|
||||
'videos': videos.map((e) => e.toJson()).toList(),
|
||||
'folders': folders,
|
||||
if (stats != null) 'stats': stats!.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
class QueueItemState {
|
||||
final String videoId;
|
||||
final String title;
|
||||
final String path;
|
||||
final int duration;
|
||||
final int trimStart;
|
||||
final int trimEnd;
|
||||
final int restSeconds;
|
||||
|
||||
const QueueItemState({
|
||||
required this.videoId,
|
||||
required this.title,
|
||||
required this.path,
|
||||
this.duration = 0,
|
||||
this.trimStart = 0,
|
||||
this.trimEnd = 0,
|
||||
this.restSeconds = 0,
|
||||
});
|
||||
|
||||
factory QueueItemState.fromJson(Map<String, dynamic> json) => QueueItemState(
|
||||
videoId: json['videoId'] as String,
|
||||
title: json['title'] as String,
|
||||
path: json['path'] as String,
|
||||
duration: json['duration'] as int? ?? 0,
|
||||
trimStart: json['trimStart'] as int? ?? 0,
|
||||
trimEnd: json['trimEnd'] as int? ?? 0,
|
||||
restSeconds: json['restSeconds'] as int? ?? 0,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'videoId': videoId,
|
||||
'title': title,
|
||||
'path': path,
|
||||
'duration': duration,
|
||||
'trimStart': trimStart,
|
||||
'trimEnd': trimEnd,
|
||||
'restSeconds': restSeconds,
|
||||
};
|
||||
}
|
||||
|
||||
class VideoState {
|
||||
final String id;
|
||||
final String title;
|
||||
final String path;
|
||||
final String? folder;
|
||||
final String? thumbnailPath;
|
||||
final int duration;
|
||||
final int trimStart;
|
||||
final int trimEnd;
|
||||
final int lastPosition;
|
||||
final int percentCompleted;
|
||||
final int restSeconds;
|
||||
|
||||
const VideoState({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.path,
|
||||
this.folder,
|
||||
this.thumbnailPath,
|
||||
this.duration = 0,
|
||||
this.trimStart = 0,
|
||||
this.trimEnd = 0,
|
||||
this.lastPosition = 0,
|
||||
this.percentCompleted = 0,
|
||||
this.restSeconds = 0,
|
||||
});
|
||||
|
||||
factory VideoState.fromJson(Map<String, dynamic> json) => VideoState(
|
||||
id: json['id'] as String,
|
||||
title: json['title'] as String,
|
||||
path: json['path'] as String,
|
||||
folder: json['folder'] as String?,
|
||||
thumbnailPath: json['thumbnailPath'] as String?,
|
||||
duration: json['duration'] as int? ?? 0,
|
||||
trimStart: json['trimStart'] as int? ?? 0,
|
||||
trimEnd: json['trimEnd'] as int? ?? 0,
|
||||
lastPosition: json['lastPosition'] as int? ?? 0,
|
||||
percentCompleted: json['percentCompleted'] as int? ?? 0,
|
||||
restSeconds: json['restSeconds'] as int? ?? 0,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'title': title,
|
||||
'path': path,
|
||||
if (folder != null) 'folder': folder,
|
||||
if (thumbnailPath != null) 'thumbnailPath': thumbnailPath,
|
||||
'duration': duration,
|
||||
'trimStart': trimStart,
|
||||
'trimEnd': trimEnd,
|
||||
'lastPosition': lastPosition,
|
||||
'percentCompleted': percentCompleted,
|
||||
'restSeconds': restSeconds,
|
||||
};
|
||||
}
|
||||
|
||||
class WorkoutStats {
|
||||
final int total;
|
||||
final int completed;
|
||||
final int inProgress;
|
||||
|
||||
const WorkoutStats({
|
||||
required this.total,
|
||||
required this.completed,
|
||||
required this.inProgress,
|
||||
});
|
||||
|
||||
factory WorkoutStats.fromJson(Map<String, dynamic> json) => WorkoutStats(
|
||||
total: json['total'] as int,
|
||||
completed: json['completed'] as int,
|
||||
inProgress: json['inProgress'] as int,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'total': total,
|
||||
'completed': completed,
|
||||
'inProgress': inProgress,
|
||||
};
|
||||
}
|
||||
|
||||
class DashboardState {
|
||||
final List<String> widgets;
|
||||
final WeatherInfo? weather;
|
||||
final List<CalendarEvent> calendarEvents;
|
||||
final List<Map<String, dynamic>> haEntities;
|
||||
|
||||
DashboardState({
|
||||
this.widgets = const ['clock', 'weather', 'calendar'],
|
||||
this.weather,
|
||||
this.calendarEvents = const [],
|
||||
this.haEntities = const [],
|
||||
});
|
||||
|
||||
factory DashboardState.fromJson(Map<String, dynamic> json) => DashboardState(
|
||||
widgets: (json['widgets'] as List<dynamic>?)?.map((e) => e as String).toList() ??
|
||||
['clock', 'weather', 'calendar'],
|
||||
weather: json['weather'] != null
|
||||
? WeatherInfo.fromJson(json['weather'] as Map<String, dynamic>)
|
||||
: null,
|
||||
calendarEvents: (json['calendarEvents'] as List<dynamic>?)
|
||||
?.map((e) => CalendarEvent.fromJson(e as Map<String, dynamic>))
|
||||
.toList() ??
|
||||
[],
|
||||
haEntities: (json['haEntities'] as List<dynamic>?)
|
||||
?.map((e) => e as Map<String, dynamic>)
|
||||
.toList() ??
|
||||
[],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'widgets': widgets,
|
||||
if (weather != null) 'weather': weather!.toJson(),
|
||||
'calendarEvents': calendarEvents.map((e) => e.toJson()).toList(),
|
||||
'haEntities': haEntities,
|
||||
};
|
||||
}
|
||||
|
||||
class WeatherInfo {
|
||||
final double temp;
|
||||
final String condition;
|
||||
final int? humidity;
|
||||
|
||||
const WeatherInfo({
|
||||
required this.temp,
|
||||
required this.condition,
|
||||
this.humidity,
|
||||
});
|
||||
|
||||
factory WeatherInfo.fromJson(Map<String, dynamic> json) => WeatherInfo(
|
||||
temp: (json['temp'] as num).toDouble(),
|
||||
condition: json['condition'] as String,
|
||||
humidity: json['humidity'] as int?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'temp': temp,
|
||||
'condition': condition,
|
||||
if (humidity != null) 'humidity': humidity,
|
||||
};
|
||||
}
|
||||
|
||||
class CalendarEvent {
|
||||
final String summary;
|
||||
final String startTime;
|
||||
final String endTime;
|
||||
|
||||
const CalendarEvent({
|
||||
required this.summary,
|
||||
required this.startTime,
|
||||
required this.endTime,
|
||||
});
|
||||
|
||||
factory CalendarEvent.fromJson(Map<String, dynamic> json) => CalendarEvent(
|
||||
summary: json['summary'] as String,
|
||||
startTime: json['startTime'] as String,
|
||||
endTime: json['endTime'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'summary': summary,
|
||||
'startTime': startTime,
|
||||
'endTime': endTime,
|
||||
};
|
||||
}
|
||||
|
||||
class ProgramSessionState {
|
||||
final String programId;
|
||||
final String programName;
|
||||
final String workoutName;
|
||||
final List<String> exercises;
|
||||
final int currentExerciseIndex;
|
||||
final String phase; // playing, resting, instruction, completed
|
||||
final int timerRemaining;
|
||||
final int currentSet;
|
||||
final int totalSets;
|
||||
|
||||
const ProgramSessionState({
|
||||
required this.programId,
|
||||
required this.programName,
|
||||
required this.workoutName,
|
||||
this.exercises = const [],
|
||||
this.currentExerciseIndex = 0,
|
||||
this.phase = 'playing',
|
||||
this.timerRemaining = 0,
|
||||
this.currentSet = 1,
|
||||
this.totalSets = 1,
|
||||
});
|
||||
|
||||
ProgramSessionState copyWith({
|
||||
String? programId,
|
||||
String? programName,
|
||||
String? workoutName,
|
||||
List<String>? exercises,
|
||||
int? currentExerciseIndex,
|
||||
String? phase,
|
||||
int? timerRemaining,
|
||||
int? currentSet,
|
||||
int? totalSets,
|
||||
}) =>
|
||||
ProgramSessionState(
|
||||
programId: programId ?? this.programId,
|
||||
programName: programName ?? this.programName,
|
||||
workoutName: workoutName ?? this.workoutName,
|
||||
exercises: exercises ?? this.exercises,
|
||||
currentExerciseIndex: currentExerciseIndex ?? this.currentExerciseIndex,
|
||||
phase: phase ?? this.phase,
|
||||
timerRemaining: timerRemaining ?? this.timerRemaining,
|
||||
currentSet: currentSet ?? this.currentSet,
|
||||
totalSets: totalSets ?? this.totalSets,
|
||||
);
|
||||
|
||||
factory ProgramSessionState.fromJson(Map<String, dynamic> json) =>
|
||||
ProgramSessionState(
|
||||
programId: json['programId'] as String,
|
||||
programName: json['programName'] as String,
|
||||
workoutName: json['workoutName'] as String,
|
||||
exercises: (json['exercises'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toList() ??
|
||||
[],
|
||||
currentExerciseIndex: json['currentExerciseIndex'] as int? ?? 0,
|
||||
phase: json['phase'] as String? ?? 'playing',
|
||||
timerRemaining: json['timerRemaining'] as int? ?? 0,
|
||||
currentSet: json['currentSet'] as int? ?? 1,
|
||||
totalSets: json['totalSets'] as int? ?? 1,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'programId': programId,
|
||||
'programName': programName,
|
||||
'workoutName': workoutName,
|
||||
'exercises': exercises,
|
||||
'currentExerciseIndex': currentExerciseIndex,
|
||||
'phase': phase,
|
||||
'timerRemaining': timerRemaining,
|
||||
'currentSet': currentSet,
|
||||
'totalSets': totalSets,
|
||||
};
|
||||
}
|
||||
|
||||
class SpotifyState {
|
||||
final bool isPlaying;
|
||||
final String? trackName;
|
||||
final String? artistName;
|
||||
final String? albumImageUrl;
|
||||
final int progressMs;
|
||||
final int durationMs;
|
||||
|
||||
const SpotifyState({
|
||||
this.isPlaying = false,
|
||||
this.trackName,
|
||||
this.artistName,
|
||||
this.albumImageUrl,
|
||||
this.progressMs = 0,
|
||||
this.durationMs = 0,
|
||||
});
|
||||
|
||||
SpotifyState copyWith({
|
||||
bool? isPlaying,
|
||||
String? trackName,
|
||||
String? artistName,
|
||||
String? albumImageUrl,
|
||||
int? progressMs,
|
||||
int? durationMs,
|
||||
}) =>
|
||||
SpotifyState(
|
||||
isPlaying: isPlaying ?? this.isPlaying,
|
||||
trackName: trackName ?? this.trackName,
|
||||
artistName: artistName ?? this.artistName,
|
||||
albumImageUrl: albumImageUrl ?? this.albumImageUrl,
|
||||
progressMs: progressMs ?? this.progressMs,
|
||||
durationMs: durationMs ?? this.durationMs,
|
||||
);
|
||||
|
||||
factory SpotifyState.fromJson(Map<String, dynamic> json) => SpotifyState(
|
||||
isPlaying: json['isPlaying'] as bool? ?? false,
|
||||
trackName: json['trackName'] as String?,
|
||||
artistName: json['artistName'] as String?,
|
||||
albumImageUrl: json['albumImageUrl'] as String?,
|
||||
progressMs: json['progressMs'] as int? ?? 0,
|
||||
durationMs: json['durationMs'] as int? ?? 0,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'isPlaying': isPlaying,
|
||||
if (trackName != null) 'trackName': trackName,
|
||||
if (artistName != null) 'artistName': artistName,
|
||||
if (albumImageUrl != null) 'albumImageUrl': albumImageUrl,
|
||||
'progressMs': progressMs,
|
||||
'durationMs': durationMs,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
name: mirror_protocol
|
||||
description: WebSocket message types and serialization for Mirror OS communication
|
||||
version: 0.1.0
|
||||
publish_to: none
|
||||
resolution: workspace
|
||||
|
||||
environment:
|
||||
sdk: ^3.5.0
|
||||
|
||||
dependencies:
|
||||
uuid: ^4.5.1
|
||||
Reference in New Issue
Block a user