Halkyon's shared packages
A pub workspace of the Dart packages Halkyon's apps share: halkyon_ui, Halkyon's look (the theme, fonts, layout and shared widgets, with texts in English and German), halkyon_core's low-level primitives and halkyon_interop's cross-app contracts. Dart 3.13 with LeanCode's lints, configured once at the root; apps take a package by git, pinned to a signed commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
commit
80fa7b9628
4 changed files with 6762 additions and 0 deletions
13
packages/halkyon_interop/lib/halkyon_interop.dart
Normal file
13
packages/halkyon_interop/lib/halkyon_interop.dart
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/// Cross-app interoperability contracts for the Halkyon ecosystem.
|
||||
///
|
||||
/// This package is pure Dart and defines:
|
||||
/// - `TaskShape`: a minimal type for representing tasks across apps
|
||||
/// (e.g., a Halkyon Learn review session surfaced in Halkyon ToDo).
|
||||
/// - `HalkyonEvent`: a sealed class hierarchy for cross-app events.
|
||||
/// - `EventPublisher`: an interface for emitting events to other apps,
|
||||
/// with `DormantPublisher` as a no-op default for apps running standalone.
|
||||
library;
|
||||
|
||||
export 'src/event_publisher.dart';
|
||||
export 'src/events.dart';
|
||||
export 'src/task_shape.dart';
|
||||
24
packages/halkyon_interop/lib/src/event_publisher.dart
Normal file
24
packages/halkyon_interop/lib/src/event_publisher.dart
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import 'package:halkyon_interop/src/events.dart';
|
||||
|
||||
/// Publishes [HalkyonEvent]s to other Halkyon apps installed alongside this one.
|
||||
///
|
||||
/// The actual transport (platform channels, intents, App Group containers,
|
||||
/// etc.) is implementation-specific and lives in the app's composition root,
|
||||
/// not in this package.
|
||||
abstract interface class EventPublisher() {
|
||||
/// Publish an event. Implementations should be non-blocking and best-effort:
|
||||
/// failing to deliver to peer apps must not fail the originating action.
|
||||
Future<void> publish(HalkyonEvent event);
|
||||
}
|
||||
|
||||
/// A no-op [EventPublisher] for apps running in isolation.
|
||||
///
|
||||
/// When a Halkyon app starts and detects no peer apps to broadcast to (or
|
||||
/// when the user has disabled cross-app communication), inject this. All
|
||||
/// `publish` calls are silently dropped.
|
||||
class const DormantPublisher() implements EventPublisher {
|
||||
@override
|
||||
Future<void> publish(HalkyonEvent event) async {
|
||||
// Intentionally empty. Events are dropped when there are no peers.
|
||||
}
|
||||
}
|
||||
43
packages/halkyon_interop/lib/src/events.dart
Normal file
43
packages/halkyon_interop/lib/src/events.dart
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import 'package:meta/meta.dart';
|
||||
|
||||
/// Base type for events published across Halkyon apps.
|
||||
///
|
||||
/// Sealed: all subtypes must be declared in this file. Adding a new event
|
||||
/// type is a deliberate cross-app contract change, not something that should
|
||||
/// happen incidentally inside a feature.
|
||||
@immutable
|
||||
sealed class const HalkyonEvent({
|
||||
/// When the event occurred. Use the app's injected `Clock` to set this,
|
||||
/// not `DateTime.now()` directly, so tests can control time.
|
||||
required final DateTime occurredAt,
|
||||
});
|
||||
|
||||
/// Emitted when a Halkyon Learn review becomes due for the user.
|
||||
///
|
||||
/// Consuming apps (e.g., Halkyon ToDo) can subscribe to surface this as a
|
||||
/// task or notification.
|
||||
final class const ReviewDueEvent({
|
||||
required super.occurredAt,
|
||||
|
||||
/// Identifier of the studyset whose cards are due.
|
||||
required final String studysetId,
|
||||
|
||||
/// Number of cards in this studyset currently due for review.
|
||||
required final int dueCount,
|
||||
}) extends HalkyonEvent {
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is ReviewDueEvent &&
|
||||
runtimeType == other.runtimeType &&
|
||||
occurredAt == other.occurredAt &&
|
||||
studysetId == other.studysetId &&
|
||||
dueCount == other.dueCount;
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(occurredAt, studysetId, dueCount);
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'ReviewDueEvent(studyset=$studysetId, due=$dueCount, at=$occurredAt)';
|
||||
}
|
||||
53
packages/halkyon_interop/lib/src/task_shape.dart
Normal file
53
packages/halkyon_interop/lib/src/task_shape.dart
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import 'package:meta/meta.dart';
|
||||
|
||||
/// A minimal cross-app representation of a task.
|
||||
///
|
||||
/// This is the contract for surfacing one app's actionable items in another
|
||||
/// app's task views. Halkyon Learn might surface "review 12 cards in Spanish"
|
||||
/// as a [TaskShape] that Halkyon ToDo displays alongside native todos.
|
||||
///
|
||||
/// Kept deliberately minimal. Apps consuming a [TaskShape] should treat it
|
||||
/// as opaque metadata; deep-linking back to the originating app is via
|
||||
/// [deepLink], not by interpreting [sourceAppId] or [externalId].
|
||||
@immutable
|
||||
class const TaskShape({
|
||||
/// Identifier of the app that owns this task. Conventionally a reverse-DNS
|
||||
/// string like `eu.halkyon.learn`.
|
||||
required final String sourceAppId,
|
||||
|
||||
/// The originating app's stable identifier for this task. Opaque to
|
||||
/// consumers; used together with [sourceAppId] for deduplication.
|
||||
required final String externalId,
|
||||
|
||||
/// Human-readable title displayed in the consuming app's task list.
|
||||
required final String title,
|
||||
|
||||
/// A URI the consuming app can open to hand off back to the originating
|
||||
/// app. Format is app-specific (custom scheme or universal link).
|
||||
required final Uri deepLink,
|
||||
|
||||
/// When the task becomes actionable or overdue. Null means "no due date."
|
||||
final DateTime? dueAt,
|
||||
|
||||
/// Optional secondary line. Keep short — consumers may truncate.
|
||||
final String? subtitle,
|
||||
}) {
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is TaskShape &&
|
||||
runtimeType == other.runtimeType &&
|
||||
sourceAppId == other.sourceAppId &&
|
||||
externalId == other.externalId &&
|
||||
title == other.title &&
|
||||
subtitle == other.subtitle &&
|
||||
dueAt == other.dueAt &&
|
||||
deepLink == other.deepLink;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(sourceAppId, externalId, title, subtitle, dueAt, deepLink);
|
||||
|
||||
@override
|
||||
String toString() => 'TaskShape($sourceAppId/$externalId: $title)';
|
||||
}
|
||||
Reference in a new issue