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
5 changed files with 6762 additions and 0 deletions
3
packages/halkyon_ui/example/.gitignore
vendored
Normal file
3
packages/halkyon_ui/example/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Flutter/Dart/Pub related
|
||||
.dart_tool/
|
||||
build/
|
||||
9
packages/halkyon_ui/example/analysis_options.yaml
Normal file
9
packages/halkyon_ui/example/analysis_options.yaml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# The workspace's lint rules (see ../../../analysis_options.yaml).
|
||||
include: ../../../analysis_options.yaml
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
- build/**
|
||||
|
||||
formatter:
|
||||
trailing_commas: preserve
|
||||
75
packages/halkyon_ui/example/lib/main.dart
Normal file
75
packages/halkyon_ui/example/lib/main.dart
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import 'package:halkyon_ui/halkyon_ui.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
|
||||
void main() {
|
||||
registerFontLicenses();
|
||||
runApp(const ExampleApp());
|
||||
}
|
||||
|
||||
/// An app set up as Halkyon's are: the shared theme in both brightnesses,
|
||||
/// the package's texts beside Material's (from material_ui, whose own
|
||||
/// delegates serve its widgets), and snack bars fitted to the window.
|
||||
class const ExampleApp({
|
||||
/// The language, the device's when null.
|
||||
final Locale? locale,
|
||||
super.key,
|
||||
}) extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) => MaterialApp(
|
||||
title: 'Halkyon Example',
|
||||
theme: appTheme(Brightness.light),
|
||||
darkTheme: appTheme(Brightness.dark),
|
||||
locale: locale,
|
||||
localizationsDelegates: const [
|
||||
HalkyonUiLocalizations.delegate,
|
||||
...GlobalMaterialLocalizations.delegates,
|
||||
],
|
||||
supportedLocales: HalkyonUiLocalizations.supportedLocales,
|
||||
builder: (context, child) => FittedSnackBars(child: child!),
|
||||
home: const _Home(),
|
||||
);
|
||||
}
|
||||
|
||||
class const _Home() extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
return TopGradient(
|
||||
background: scheme.surface,
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
appBar: AppBar(backgroundColor: Colors.transparent),
|
||||
body: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: readableContentMaxWidth,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const BrandWordmark(product: 'Example'),
|
||||
const SizedBox(height: 24),
|
||||
SectionCard(
|
||||
child: Builder(
|
||||
builder: (context) => FilledButton.tonal(
|
||||
onPressed: () => confirmDestructiveAction(
|
||||
context,
|
||||
title: 'Delete everything?',
|
||||
message: 'It can’t be undone.',
|
||||
confirmLabel: 'Delete',
|
||||
),
|
||||
child: const Text('Delete'),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
18
packages/halkyon_ui/example/pubspec.yaml
Normal file
18
packages/halkyon_ui/example/pubspec.yaml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
name: halkyon_ui_example
|
||||
description: "An app built on halkyon_ui, as Halkyon's apps are: shows how one sets it up, and checks what only an app can see (the package's font by the name an app finds it under, its licence)."
|
||||
publish_to: none
|
||||
resolution: workspace
|
||||
|
||||
environment:
|
||||
sdk: ^3.13.0
|
||||
flutter: ^3.44.4
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
halkyon_ui: ^0.1.0
|
||||
material_ui: ^1.5.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
99
packages/halkyon_ui/example/test/example_app_test.dart
Normal file
99
packages/halkyon_ui/example/test/example_app_test.dart
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:halkyon_ui/halkyon_ui.dart';
|
||||
import 'package:halkyon_ui_example/main.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
|
||||
/// How many pixels [text] inks in [family] at [weight]: heavier strokes ink
|
||||
/// more.
|
||||
Future<int> ink(String text, String family, FontWeight weight) async {
|
||||
final painter = TextPainter(
|
||||
text: TextSpan(
|
||||
text: text,
|
||||
style: TextStyle(
|
||||
fontFamily: family,
|
||||
fontSize: 40,
|
||||
fontWeight: weight,
|
||||
color: const Color(0xFF000000),
|
||||
),
|
||||
),
|
||||
textDirection: TextDirection.ltr,
|
||||
)..layout();
|
||||
final recorder = ui.PictureRecorder();
|
||||
painter.paint(Canvas(recorder), Offset.zero);
|
||||
final image = await recorder.endRecording().toImage(
|
||||
painter.width.ceil(),
|
||||
painter.height.ceil(),
|
||||
);
|
||||
final pixels = (await image.toByteData())!;
|
||||
var inked = 0;
|
||||
for (var alpha = 3; alpha < pixels.lengthInBytes; alpha += 4) {
|
||||
if (pixels.getUint8(alpha) > 128) {
|
||||
inked++;
|
||||
}
|
||||
}
|
||||
return inked;
|
||||
}
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
test("an app finds the package's Noto Sans under the theme's name, and "
|
||||
'draws every weight with it', () async {
|
||||
final manifest = jsonDecode(
|
||||
await rootBundle.loadString('FontManifest.json'),
|
||||
) as List<Object?>;
|
||||
final noto = manifest.cast<Map<String, Object?>>().singleWhere(
|
||||
(font) => font['family'] == appFontFamily,
|
||||
);
|
||||
final assets = [
|
||||
for (final font
|
||||
in (noto['fonts']! as List<Object?>).cast<Map<String, Object?>>())
|
||||
font['asset']! as String,
|
||||
];
|
||||
expect(
|
||||
assets,
|
||||
contains('packages/halkyon_ui/assets/fonts/NotoSans-Variable.ttf'),
|
||||
);
|
||||
|
||||
// Loaded as the engine loads an app's fonts as it starts: by the
|
||||
// manifest's family and files.
|
||||
final loader = FontLoader(appFontFamily);
|
||||
for (final asset in assets) {
|
||||
loader.addFont(rootBundle.load(asset));
|
||||
}
|
||||
await loader.load();
|
||||
const text = 'Ἀθῆναι Halkyon';
|
||||
final regular = await ink(text, appFontFamily, FontWeight.w400);
|
||||
final bold = await ink(text, appFontFamily, FontWeight.w700);
|
||||
expect(bold, greaterThan(regular * 1.2));
|
||||
});
|
||||
|
||||
test("Noto Sans's licence joins the app's licence list", () async {
|
||||
registerFontLicenses();
|
||||
|
||||
final notices = [
|
||||
await for (final entry in LicenseRegistry.licenses)
|
||||
if (entry.packages.contains('Noto Sans')) entry,
|
||||
];
|
||||
expect(notices, hasLength(1));
|
||||
expect(
|
||||
notices.single.paragraphs.map((paragraph) => paragraph.text).join(),
|
||||
contains('SIL OPEN FONT LICENSE'),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets("the app speaks German with the package's texts", (tester) async {
|
||||
await tester.pumpWidget(const ExampleApp(locale: Locale('de')));
|
||||
|
||||
expect(find.text('Halkyon'), findsOneWidget);
|
||||
expect(find.text('Example'), findsOneWidget);
|
||||
await tester.tap(find.text('Delete'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Abbrechen'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
Reference in a new issue