// Chanora PoC — Flutter side of the flutter_rust_bridge hello spike. // // Demonstrates both halves of the PoC exit criterion // (docs/architecture/proof-of-concept-plan.md §2): // 1) Flutter calls a Rust command and renders the typed result. // 2) Flutter subscribes to a Rust event stream and renders ticks live. // // This is PoC code, not product UI. The Chanora Design System (Material 3) // lives in docs/ui-ux/ and is owned by the product Flutter app, not here. import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_rust_bridge_hello/src/rust/api/simple.dart'; import 'package:flutter_rust_bridge_hello/src/rust/frb_generated.dart'; Future main() async { await RustLib.init(); runApp(const PocApp()); } class PocApp extends StatelessWidget { const PocApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Chanora PoC — FRB hello', theme: ThemeData( useMaterial3: true, colorSchemeSeed: const Color(0xFF3F51B5), ), home: const PocHome(), ); } } class PocHome extends StatefulWidget { const PocHome({super.key}); @override State createState() => _PocHomeState(); } class _PocHomeState extends State { final _nameCtl = TextEditingController(text: 'Chanora'); String _greetOutput = '(no call yet)'; bool _greetOk = true; StreamSubscription? _streamSub; final List _ticks = []; bool _streamRunning = false; @override void dispose() { _streamSub?.cancel(); _nameCtl.dispose(); super.dispose(); } void _callGreet() { try { final r = greet(name: _nameCtl.text); setState(() { _greetOk = true; _greetOutput = '${r.message}\n(elapsed: ${r.elapsedMicros} µs)'; }); } catch (e) { setState(() { _greetOk = false; _greetOutput = 'error: $e'; }); } } void _startStream() { _streamSub?.cancel(); setState(() { _ticks.clear(); _streamRunning = true; }); final stream = counterStream(count: 5, intervalMs: 400); _streamSub = stream.listen( (tick) => setState(() => _ticks.add(tick)), onDone: () => setState(() => _streamRunning = false), onError: (e) => setState(() { _streamRunning = false; _ticks.add(CounterTick(seq: BigInt.from(-1), note: 'error: $e')); }), ); } @override Widget build(BuildContext context) { final theme = Theme.of(context); return Scaffold( appBar: AppBar( title: const Text('Chanora PoC — flutter_rust_bridge hello'), ), body: Padding( padding: const EdgeInsets.all(16), child: ListView( children: [ Text('1. Command (Dart → Rust → typed Result)', style: theme.textTheme.titleMedium), const SizedBox(height: 8), Row( children: [ Expanded( child: TextField( controller: _nameCtl, decoration: const InputDecoration( labelText: 'name', border: OutlineInputBorder(), ), ), ), const SizedBox(width: 12), FilledButton(onPressed: _callGreet, child: const Text('greet')), ], ), const SizedBox(height: 8), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: _greetOk ? theme.colorScheme.surfaceContainerHighest : theme.colorScheme.errorContainer, borderRadius: BorderRadius.circular(8), ), child: Text(_greetOutput, style: const TextStyle(fontFamily: 'monospace')), ), const Divider(height: 32), Text('2. Event stream (Rust → Dart Stream)', style: theme.textTheme.titleMedium), const SizedBox(height: 8), Row( children: [ FilledButton( onPressed: _streamRunning ? null : _startStream, child: Text(_streamRunning ? 'streaming…' : 'start counter_stream'), ), const SizedBox(width: 12), Text('${_ticks.length} ticks received'), ], ), const SizedBox(height: 8), Container( constraints: const BoxConstraints(minHeight: 120), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: theme.colorScheme.surfaceContainerHighest, borderRadius: BorderRadius.circular(8), ), child: _ticks.isEmpty ? const Text('(no ticks yet)') : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ for (final t in _ticks) Text('seq=${t.seq} ${t.note}', style: const TextStyle(fontFamily: 'monospace')), ], ), ), const SizedBox(height: 24), Text( 'PoC exit criterion (docs/architecture/proof-of-concept-plan.md §2):\n' '"Flutter can call Rust and receive event stream data."', style: theme.textTheme.bodySmall, ), ], ), ), ); } }