init: rough companion app stub
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
class User {
|
||||
const User({required this.id, required this.username, required this.isAdmin});
|
||||
|
||||
final int id;
|
||||
final String username;
|
||||
final bool isAdmin;
|
||||
|
||||
factory User.fromJson(Map<String, dynamic> json) => User(
|
||||
id: json['id'] as int,
|
||||
username: json['username'] as String,
|
||||
isAdmin: json['is_admin'] as bool,
|
||||
);
|
||||
}
|
||||
|
||||
class TokenResponse {
|
||||
const TokenResponse({required this.token, required this.user});
|
||||
|
||||
final String token;
|
||||
final User user;
|
||||
|
||||
factory TokenResponse.fromJson(Map<String, dynamic> json) => TokenResponse(
|
||||
token: json['token'] as String,
|
||||
user: User.fromJson(json['user'] as Map<String, dynamic>),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import 'position.dart';
|
||||
|
||||
class Room {
|
||||
const Room({required this.id, required this.name, required this.polygon});
|
||||
|
||||
final String id;
|
||||
final String name;
|
||||
|
||||
/// Polygon vertices in normalised 0..1 coordinates.
|
||||
final List<Position> polygon;
|
||||
|
||||
Room copyWith({String? name, List<Position>? polygon}) =>
|
||||
Room(id: id, name: name ?? this.name, polygon: polygon ?? this.polygon);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'polygon': polygon.map((p) => p.toJson()).toList(),
|
||||
};
|
||||
|
||||
factory Room.fromJson(Map<String, dynamic> json) => Room(
|
||||
id: json['id'] as String,
|
||||
name: json['name'] as String,
|
||||
polygon: (json['polygon'] as List)
|
||||
.map((p) => Position.fromJson(p as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
class FloorPlan {
|
||||
const FloorPlan({
|
||||
required this.id,
|
||||
required this.floorId,
|
||||
required this.metersPerUnit,
|
||||
required this.rooms,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String floorId;
|
||||
|
||||
/// Scale: how many real-world meters one normalised unit represents.
|
||||
final double metersPerUnit;
|
||||
|
||||
final List<Room> rooms;
|
||||
|
||||
FloorPlan copyWith({double? metersPerUnit, List<Room>? rooms}) => FloorPlan(
|
||||
id: id,
|
||||
floorId: floorId,
|
||||
metersPerUnit: metersPerUnit ?? this.metersPerUnit,
|
||||
rooms: rooms ?? this.rooms,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'floor_id': floorId,
|
||||
'meters_per_unit': metersPerUnit,
|
||||
'rooms': rooms.map((r) => r.toJson()).toList(),
|
||||
};
|
||||
|
||||
factory FloorPlan.fromJson(Map<String, dynamic> json) => FloorPlan(
|
||||
id: json['id'] as String,
|
||||
floorId: json['floor_id'] as String,
|
||||
metersPerUnit: (json['meters_per_unit'] as num).toDouble(),
|
||||
rooms: (json['rooms'] as List)
|
||||
.map((r) => Room.fromJson(r as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
enum FloorPlanMode { view, edit }
|
||||
@@ -0,0 +1,13 @@
|
||||
enum OnboardingStatus {
|
||||
/// No admin user exists yet.
|
||||
notStarted,
|
||||
|
||||
/// Admin user created, floor plan not yet saved.
|
||||
awaitingFloorPlan,
|
||||
|
||||
/// Floor plan saved, no sensors enrolled yet.
|
||||
awaitingFirstSensor,
|
||||
|
||||
/// At least one sensor enrolled; onboarding considered complete.
|
||||
complete,
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/// Single particle in a particle filter cloud snapshot.
|
||||
class Particle {
|
||||
const Particle({required this.x, required this.y, required this.weight});
|
||||
|
||||
/// Normalised 0..1 coordinates (same space as [Position]).
|
||||
final double x;
|
||||
final double y;
|
||||
|
||||
/// Unnormalised likelihood weight.
|
||||
final double weight;
|
||||
|
||||
factory Particle.fromJson(Map<String, dynamic> json) => Particle(
|
||||
x: (json['x'] as num).toDouble(),
|
||||
y: (json['y'] as num).toDouble(),
|
||||
weight: (json['weight'] as num).toDouble(),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Normalised coordinates: x and y are in the range 0..1 relative to the floor plan canvas.
|
||||
class Position {
|
||||
const Position({required this.x, required this.y});
|
||||
|
||||
final double x;
|
||||
final double y;
|
||||
|
||||
Position copyWith({double? x, double? y}) =>
|
||||
Position(x: x ?? this.x, y: y ?? this.y);
|
||||
|
||||
// Key names should match localiserd API fields.
|
||||
Map<String, dynamic> toJson() => {'x': x, 'y': y};
|
||||
|
||||
factory Position.fromJson(Map<String, dynamic> json) => Position(
|
||||
x: (json['x'] as num).toDouble(),
|
||||
y: (json['y'] as num).toDouble(),
|
||||
);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
other is Position && other.x == x && other.y == y;
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(x, y);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import 'position.dart';
|
||||
|
||||
enum SensorStatus { online, offline, provisioning }
|
||||
|
||||
class Sensor {
|
||||
const Sensor({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.floorId,
|
||||
required this.position,
|
||||
required this.status,
|
||||
this.lastSeen,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String name;
|
||||
final String floorId;
|
||||
final Position position;
|
||||
final SensorStatus status;
|
||||
final DateTime? lastSeen;
|
||||
|
||||
Sensor copyWith({
|
||||
String? name,
|
||||
Position? position,
|
||||
SensorStatus? status,
|
||||
DateTime? lastSeen,
|
||||
}) =>
|
||||
Sensor(
|
||||
id: id,
|
||||
name: name ?? this.name,
|
||||
floorId: floorId,
|
||||
position: position ?? this.position,
|
||||
status: status ?? this.status,
|
||||
lastSeen: lastSeen ?? this.lastSeen,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'floor_id': floorId,
|
||||
'position': position.toJson(),
|
||||
'status': status.name,
|
||||
'last_seen': lastSeen?.toIso8601String(),
|
||||
};
|
||||
|
||||
factory Sensor.fromJson(Map<String, dynamic> json) => Sensor(
|
||||
id: json['id'] as String,
|
||||
name: json['name'] as String,
|
||||
floorId: json['floor_id'] as String,
|
||||
position: Position.fromJson(json['position'] as Map<String, dynamic>),
|
||||
status: SensorStatus.values.byName(json['status'] as String),
|
||||
lastSeen: json['last_seen'] == null
|
||||
? null
|
||||
: DateTime.parse(json['last_seen'] as String),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
class ServerConfig {
|
||||
const ServerConfig({required this.host, required this.port});
|
||||
|
||||
final String host;
|
||||
final int port;
|
||||
|
||||
String get wsUrl => 'ws://$host:$port/socket/websocket';
|
||||
|
||||
ServerConfig copyWith({String? host, int? port}) =>
|
||||
ServerConfig(host: host ?? this.host, port: port ?? this.port);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
other is ServerConfig && other.host == host && other.port == port;
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(host, port);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import 'position.dart';
|
||||
|
||||
class Tag {
|
||||
const Tag({
|
||||
required this.id,
|
||||
required this.name,
|
||||
this.currentRoomId,
|
||||
this.lastPosition,
|
||||
this.lastSeen,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String name;
|
||||
final String? currentRoomId;
|
||||
final Position? lastPosition;
|
||||
final DateTime? lastSeen;
|
||||
|
||||
Tag copyWith({String? name, String? currentRoomId, Position? lastPosition, DateTime? lastSeen}) =>
|
||||
Tag(
|
||||
id: id,
|
||||
name: name ?? this.name,
|
||||
currentRoomId: currentRoomId ?? this.currentRoomId,
|
||||
lastPosition: lastPosition ?? this.lastPosition,
|
||||
lastSeen: lastSeen ?? this.lastSeen,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'current_room_id': currentRoomId,
|
||||
'last_position': lastPosition?.toJson(),
|
||||
'last_seen': lastSeen?.toIso8601String(),
|
||||
};
|
||||
|
||||
factory Tag.fromJson(Map<String, dynamic> json) => Tag(
|
||||
id: json['id'] as String,
|
||||
name: json['name'] as String,
|
||||
currentRoomId: json['current_room_id'] as String?,
|
||||
lastPosition: json['last_position'] == null
|
||||
? null
|
||||
: Position.fromJson(json['last_position'] as Map<String, dynamic>),
|
||||
lastSeen: json['last_seen'] == null
|
||||
? null
|
||||
: DateTime.parse(json['last_seen'] as String),
|
||||
);
|
||||
}
|
||||
|
||||
// Live position snapshot pushed from localiserd over Phoenix channel.
|
||||
class TagPosition {
|
||||
const TagPosition({required this.tagId, required this.position});
|
||||
|
||||
final String tagId;
|
||||
final Position position;
|
||||
}
|
||||
Reference in New Issue
Block a user