Skip to main content

bifold 0.1.0 copy "bifold: ^0.1.0" to clipboard
bifold: ^0.1.0 copied to clipboard

Fold awareness for the foldable iPhone: hinge angle and pose, reserved regions, fold-aware layout, a capture accessory for the outer display, a debug overlay and test fakes.

example/lib/main.dart

import 'package:bifold/bifold.dart';
import 'package:flutter/material.dart';

import 'pages/gallery_page.dart';
import 'pages/inspector_page.dart';
import 'pages/reader_page.dart';
import 'pages/studio_page.dart';

/// Content the system may show on the outer display during camera capture.
///
/// This runs in its own Flutter engine, so it is a separate entrypoint. The
/// pragma keeps it from being tree-shaken out of a release build.
@pragma('vm:entry-point')
void captureAccessoryMain() => runApp(const SubjectView());

void main() {
  // One scope at the root. Everything below it can read fold state.
  runApp(const BifoldScope(child: ExampleApp()));
}

class ExampleApp extends StatefulWidget {
  const ExampleApp({super.key});

  @override
  State<ExampleApp> createState() => _ExampleAppState();
}

class _ExampleAppState extends State<ExampleApp> {
  bool _overlay = true;
  bool _bridgeDisplayFeatures = true;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'bifold',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorSchemeSeed: const Color(0xFF3D5AFE),
        brightness: Brightness.dark,
        useMaterial3: true,
      ),
      builder: (context, child) {
        // Republish the fold as MediaQuery.displayFeatures, so Flutter's own
        // dialog positioning and any Android-foldable package see it too.
        Widget content = child!;
        if (_bridgeDisplayFeatures) {
          content = BifoldDisplayFeatures(child: content);
        }
        // The overlay wraps everything so it draws over the Scaffold as well,
        // including the area behind the app bar where a cutout sits.
        return BifoldDebugOverlay(enabled: _overlay, child: content);
      },
      home: HomePage(
        overlayEnabled: _overlay,
        bridgeEnabled: _bridgeDisplayFeatures,
        onToggleOverlay: () => setState(() => _overlay = !_overlay),
        onToggleBridge: () =>
            setState(() => _bridgeDisplayFeatures = !_bridgeDisplayFeatures),
      ),
    );
  }
}

/// Hosts the demos, with a live read-out of fold state above them.
class HomePage extends StatefulWidget {
  const HomePage({
    required this.overlayEnabled,
    required this.bridgeEnabled,
    required this.onToggleOverlay,
    required this.onToggleBridge,
    super.key,
  });

  final bool overlayEnabled;
  final bool bridgeEnabled;
  final VoidCallback onToggleOverlay;
  final VoidCallback onToggleBridge;

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  int _tab = 0;

  static const List<_Demo> _demos = <_Demo>[
    _Demo('Reader', Icons.menu_book_outlined, ReaderPage()),
    _Demo('Gallery', Icons.grid_view_outlined, GalleryPage()),
    _Demo('Studio', Icons.videocam_outlined, StudioPage()),
    _Demo('Inspector', Icons.science_outlined, InspectorPage()),
  ];

  @override
  Widget build(BuildContext context) {
    return BifoldScaffold(
      appBar: AppBar(
        title: const Text('bifold'),
        actions: <Widget>[
          IconButton(
            tooltip: widget.bridgeEnabled
                ? 'displayFeatures bridge on'
                : 'displayFeatures bridge off',
            onPressed: widget.onToggleBridge,
            icon: Icon(widget.bridgeEnabled ? Icons.link : Icons.link_off),
          ),
          IconButton(
            tooltip: widget.overlayEnabled ? 'Hide regions' : 'Show regions',
            onPressed: widget.onToggleOverlay,
            icon: Icon(widget.overlayEnabled ? Icons.grid_off : Icons.grid_on),
          ),
        ],
      ),
      selectedIndex: _tab,
      onDestinationSelected: (i) => setState(() => _tab = i),
      destinations: <BifoldDestination>[
        for (final demo in _demos)
          BifoldDestination(icon: Icon(demo.icon), label: demo.label),
      ],
      body: Column(
        children: <Widget>[
          const _LiveStatusBar(),
          Expanded(child: _demos[_tab].page),
        ],
      ),
    );
  }
}

/// Reads fold state itself, so the scaffold above it does not have to.
class _LiveStatusBar extends StatelessWidget {
  const _LiveStatusBar();

  @override
  Widget build(BuildContext context) => StatusBar(info: Bifold.of(context));
}

class _Demo {
  const _Demo(this.label, this.icon, this.page);
  final String label;
  final IconData icon;
  final Widget page;
}

/// Live read-out of everything `FoldInfo` exposes.
class StatusBar extends StatelessWidget {
  const StatusBar({required this.info, super.key});

  final FoldInfo info;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final degrees = info.hingeAngleDegrees;

    return Container(
      width: double.infinity,
      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
      color: theme.colorScheme.surfaceContainerHighest,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Wrap(
            spacing: 6,
            runSpacing: 6,
            children: <Widget>[
              Chip2(
                label: info.isFoldable ? 'foldable' : 'not foldable',
                highlight: info.isFoldable,
              ),
              Chip2(label: 'display: ${info.display.name}'),
              Chip2(label: 'pose: ${info.pose.name}'),
              if (degrees != null)
                Chip2(label: '${degrees.toStringAsFixed(0)}°', highlight: true),
              Chip2(
                label:
                    'size: ${info.horizontalSizeClass.name}/'
                    '${info.verticalSizeClass.name}',
              ),
              if (info.verticalBarEdge != VerticalBarEdge.unspecified)
                Chip2(label: 'bar: ${info.verticalBarEdge.name}'),
              Chip2(
                label:
                    '${info.regions.length} region'
                    '${info.regions.length == 1 ? '' : 's'}',
              ),
              Chip2(
                label: info.division == null
                    ? 'no active division'
                    : 'division active',
                highlight: info.division != null,
              ),
            ],
          ),
          if (!info.isFoldable) ...<Widget>[
            const SizedBox(height: 6),
            Text(
              'This device reports no fold. Every demo below still lays out '
              'and nothing throws.',
              style: theme.textTheme.bodySmall?.copyWith(
                color: theme.colorScheme.onSurfaceVariant,
              ),
            ),
          ],
        ],
      ),
    );
  }
}

/// A compact pill, small enough that a dozen fit on the outer display.
class Chip2 extends StatelessWidget {
  const Chip2({required this.label, this.highlight = false, super.key});

  final String label;
  final bool highlight;

  @override
  Widget build(BuildContext context) {
    final scheme = Theme.of(context).colorScheme;
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
      decoration: BoxDecoration(
        color: highlight ? scheme.primary : scheme.surface,
        borderRadius: BorderRadius.circular(999),
      ),
      child: Text(
        label,
        style: TextStyle(
          fontSize: 11,
          color: highlight ? scheme.onPrimary : scheme.onSurface,
        ),
      ),
    );
  }
}

/// What the person in front of the camera sees on the outer display.
///
/// A separate engine renders this, so it shares no state with the main app —
/// which is why it is a plain, self-contained widget.
class SubjectView extends StatelessWidget {
  const SubjectView({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: ColoredBox(
        color: Color(0xFF0B0B0F),
        child: Center(
          child: Padding(
            padding: EdgeInsets.all(24),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Icon(
                  Icons.fiber_manual_record,
                  color: Color(0xFFFF5252),
                  size: 40,
                ),
                SizedBox(height: 12),
                Text(
                  'You are on camera',
                  textAlign: TextAlign.center,
                  style: TextStyle(
                    color: Color(0xFFFFFFFF),
                    fontSize: 22,
                    fontWeight: FontWeight.w600,
                  ),
                ),
                SizedBox(height: 6),
                Text(
                  'Look here',
                  textAlign: TextAlign.center,
                  style: TextStyle(color: Color(0xFF9E9E9E), fontSize: 14),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}
0
likes
0
points
575
downloads

Publisher

verified publisheraayou.sh

Weekly Downloads

Fold awareness for the foldable iPhone: hinge angle and pose, reserved regions, fold-aware layout, a capture accessory for the outer display, a debug overlay and test fakes.

Repository (GitHub)
View/report issues

Topics

#foldable #ios #adaptive-layout #responsive #hinge

License

unknown (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on bifold

Packages that implement bifold