Skip to main content

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

A pure-Dart Flutter recreation of the Samsung Now Bar with animated media, notification, routine, sports, and timer surfaces.

example/lib/main.dart

import 'dart:convert';

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

/// Starts the example application.
void main() {
  runApp(const NowBarExampleApp());
}

/// Root widget of the example application.
///
/// Installs the dark Now Bar theme on the [MaterialApp] so the app chrome and
/// the bar surfaces share one palette, then shows the demo page.
class NowBarExampleApp extends StatelessWidget {
  /// Creates the example application.
  const NowBarExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'NowBar Flutter Example',
      debugShowCheckedModeBanner: false,
      theme: NowBarTheme.buildThemeData(),
      home: const _DemoPage(),
    );
  }
}

/// Full-screen page that hosts the Now Bar deck.
///
/// The composition mirrors the upstream demo: a [NowBarTheme] wraps a
/// [Scaffold] whose body is a [SafeArea] with a bottom-aligned [Align], offset
/// 50 logical pixels from the bottom edge. A short heading fills the space
/// above the deck and explains the gesture the demo answers to.
class _DemoPage extends StatelessWidget {
  /// Creates the demo page.
  const _DemoPage();

  @override
  Widget build(BuildContext context) {
    return NowBarTheme.light(
      child: Scaffold(
        body: DecoratedBox(
          decoration: const BoxDecoration(gradient: _backdropGradient),
          child: SafeArea(
            child: Stack(
              fit: StackFit.expand,
              children: <Widget>[
                const Positioned.fill(child: _DemoHeading()),
                Padding(
                  padding: const EdgeInsets.only(bottom: 50),
                  child: Align(
                    alignment: Alignment.bottomCenter,
                    child: NowBarWidget(
                      dragDirection: NowBarDragController.dragVertically,
                      widgets: _demoComponents,
                    ),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

/// Heading shown above the bar deck.
class _DemoHeading extends StatelessWidget {
  /// Creates the demo heading.
  const _DemoHeading();

  @override
  Widget build(BuildContext context) {
    final ThemeData theme = Theme.of(context);
    final TextTheme textTheme = theme.textTheme;
    return Padding(
      padding: const EdgeInsets.fromLTRB(32, 56, 32, 0),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          Text(
            'NOW BAR',
            textAlign: TextAlign.center,
            style: textTheme.labelSmall?.copyWith(
              color: theme.colorScheme.primary,
              letterSpacing: 4,
            ),
          ),
          const SizedBox(height: 14),
          Text(
            'A glanceable card deck for Flutter',
            textAlign: TextAlign.center,
            style: textTheme.titleLarge,
          ),
          const SizedBox(height: 10),
          Text(
            'Drag the surface up or down to cycle through media, a timer, '
            'routines, a live score, and notifications.',
            textAlign: TextAlign.center,
            style: textTheme.bodyMedium?.copyWith(
              color: theme.colorScheme.onSurfaceVariant,
            ),
          ),
        ],
      ),
    );
  }
}

/// The rotation shown by the demo, in display order.
///
/// The order mirrors the upstream demo: media player, timer, routines, sports,
/// and two notification cards. Every surface is neutral and self-contained:
/// no brand names, logos, or bundled third-party artwork. The list lives at
/// top level so its identity is stable across rebuilds, which keeps the active
/// card from resetting when the page rebuilds.
final List<NowBarComponent> _demoComponents = <NowBarComponent>[
  NowBarComponent(
    // The media card is not dismissible, matching the upstream demo: the
    // rotation always keeps at least one persistent surface.
    dismissible: false,
    builder: () => MediaPlayerWidget(
      tracks: <MediaPlayerTrack>[
        MediaPlayerTrack(
          image: _coverViolet,
          title: 'Midnight Drive',
          artist: 'Neon Atlas',
        ),
        MediaPlayerTrack(
          image: _coverAmber,
          title: 'Golden Hour',
          artist: 'Wanderlight',
        ),
        MediaPlayerTrack(
          image: _coverTeal,
          title: 'Slow Current',
          artist: 'Tidal Bloom',
        ),
      ],
    ),
  ),
  NowBarComponent(builder: () => const TimerWidget(seconds: 65)),
  NowBarComponent(
    builder: () =>
        const RoutinesWidget(content: 'At work and 2 others running'),
  ),
  NowBarComponent(
    builder: () => SportsWidget(
      title: 'ICC Champions Trophy 2025 (Final)\nIND vs NZ',
      content: 'IND won by 4 wickets',
      backgroundImage: _sportsBackdrop,
      leading: const Icon(Icons.sports_cricket, color: Colors.white),
      trailing: const Icon(Icons.emoji_events, color: Colors.white),
    ),
  ),
  NowBarComponent(
    // Neutral stand-in for a professional-network notification: the gradient
    // echoes a blue social card without using any brand name or logo.
    builder: () => const NotificationWidget(
      icon: Icon(Icons.person_search),
      title: 'Weekly reach',
      content: 'You appeared in 54 searches last week.',
      colorController: NotificationColorController(
        backgroundColorGradient: LinearGradient(
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
          colors: <Color>[Color(0xFF0A66C2), Color(0xFFB3D5FA)],
        ),
      ),
    ),
  ),
  NowBarComponent(
    // Neutral stand-in for a delivery notification. The icon is tinted dark
    // because the leading edge of the gradient is a bright yellow.
    builder: () => const NotificationWidget(
      icon: Icon(Icons.local_shipping),
      title: 'Arriving today',
      content: 'Your order is out for delivery.',
      colorController: NotificationColorController(
        iconColor: Color(0xFF0B2A4A),
        backgroundColorGradient: LinearGradient(
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
          colors: <Color>[Color(0xFFFADC1E), Color(0xFF0D69B3)],
        ),
      ),
    ),
  ),
];

/// Full-screen backdrop: white lifting to a soft gray at the bottom, matching
/// the light stage the reference Now Bar sits on.
const LinearGradient _backdropGradient = LinearGradient(
  begin: Alignment.topCenter,
  end: Alignment.bottomCenter,
  colors: <Color>[Color(0xFFFFFFFF), Color(0xFFF2F2F4)],
);

// ---------------------------------------------------------------------------
// Embedded artwork
// ---------------------------------------------------------------------------
//
// The upstream demo draws album covers and notification logos from bundled
// drawables. This example ships no third-party imagery: the "artwork" here is
// four 32 x 32 diagonal gradient PNGs generated locally and embedded as
// base64. The media surface blurs its cover at an 8 logical pixel sigma and
// the sports surface blurs its backdrop at 40, which hides the low resolution
// completely.

/// Decodes one of the base64 PNGs below into a [MemoryImage].
///
/// Embedding the bytes keeps the example runnable with a stock `pubspec.yaml`
/// and no asset registration. Each provider is created once and reused:
/// [MemoryImage] uses instance identity as its cache key, so constructing a
/// new one on every frame would force the decoder to run again.
MemoryImage _embeddedPng(String base64Png) =>
    MemoryImage(base64Decode(base64Png));

/// 32 x 32 indigo-to-violet gradient, used as a demo track cover.
const String _coverVioletPng =
    'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAABOUlEQVR42rXMCUeDAQAG4PevJCnd'
    '10x3zczMrDIz61yttVat1Vpr5rO2Ws1nbbXWacp0mSkSiUQiEomMSCQSiUgkEvUr3ucHPMjLMBZk'
    'moqyzCXZfWU5/ZJcmzTfXlnoqC4eqy0dry/3yCSCXDqhqPArqwKqmhl1nahpmG2SRbTyqE4R0yuX'
    'DarVVnW8XbPR2Zjoat4yaXfMuqRFn7Ia9gdbDkDdh9oOQd2HO45A3R3GY1B3Z/cJqLur5xTU3d17'
    'BurusZyDugvWC1B378AlqLvPdgXqPmm/BnUPjNyAugdHb0HdRWca1D3kugN1D7vvQd3nPA+g7lHh'
    'EdQ95n0CdV/yPYO6r/hfQN3Xpl5B3ePTb6Du68F3UPeE+AHqvhn6BHXfDn+Buu9GvkHdk/M/oO6p'
    'hV9Q973Fv38TD9ZcOeUQwAAAAABJRU5ErkJggg==';

/// 32 x 32 plum-to-amber gradient, used as a demo track cover.
const String _coverAmberPng =
    'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAABNUlEQVR42rXMbUcDAQAH8P8nSSS9'
    'SBLZTte22kPXrtvzbtfdzphKmSmVZSplplSmJ2XKVKYyZUQiIhIRSUQiIhKJRPS6T/H/fYAf5BqL'
    'r9YarLOG6wW1QdAb28wmMdEsJlva+1ttgxZbSrCnRfuIzTHm6JhwdWY9zinJOSO7coo773fPBz2L'
    'EU9B7VrRpDVD2jC7iwnvdtJb6pN3B+TyUA+o+0FKAXWvpBVQ96NhH6h7ddQH6n4y7gd1P80EQN3P'
    'sgFQ9/PJIKj7xXQI1P1yNgTqfpULg7pf58Og7jdzEVD324UoqPvdUhTU/b6ggro/LEdB3R9XVVD3'
    'p/UYqPvzZgzU/aWogbq/bmmg7m+lXlD39x0d1P1jTwd1/ywboO5f+wao+/ehAer+U4mDuv8ex0Hd'
    '/6rmPzXtTzLm29QdAAAAAElFTkSuQmCC';

/// 32 x 32 teal-to-mint gradient, used as a demo track cover.
const String _coverTealPng =
    'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAABN0lEQVR42rXMaytDAQAG4PczIUQI'
    'IUTINMthG6dtZm0zp802s45La6G10FpoLbeWW8tda6G10FporZSUklJSSsr/8Sve5wc8KFAIhcqB'
    'IpWmWBgsUYulWl2ZaCjXGyuGTZUmc5XFWj1qq5GkWoe9zjleP+Fq8LobZU/TjLfZJ7f4p1vnZ9sC'
    'vvagv2NprjO00BUOdK8GFZHFnuiyciPUuxUGdVfFVkDd+3bXQN2F/Qioe388CuquPloHddecbIK6'
    'a8+3Qd2HEjFQdzG5A+quu9oDddenDkDdDek4qLvx9hDUfSRzDOpuyp6CupsfzkDdLbkLUHdrPgHq'
    'bntKgrqPPV+Cuksv16Du9tcUqLvjLQ3q7ny/AXV3fdyBurs/M6Dunq8sqPvk9z2ou/fnEdRd/s2B'
    'uk/95f8BpgauLacKYzwAAAAASUVORK5CYII=';

/// 32 x 32 deep-blue gradient, used as the sports backdrop.
const String _sportsBackdropPng =
    'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAABL0lEQVR42rXMiypDAQAG4P8RXHIX'
    'sxmzGbMZs7sdc2Z3czbXteZuDQ1rTWtYK0lSkqQkSUqS1JKkJA/mKf7vAT5UyazVcnuNwlmrFOq6'
    '3PUqsUE90ajxNWmDzf3hFl2kVS+1GWLtxlnZ8HyHKS43JxSWZKdtSelY7Xauq1ypHiGtdm9pxEyv'
    'Z1frzfb5c7pAfiBU0IeLhsghqPugVAJ1N0bLoO5D08eg7qaZE1D3kblTUHfzwhmouyV+DupuTVyA'
    'utuSl6Du9sUrUHfH8jWo++jKDai7a+0W1F3YuAN1H0vdg7q70w+g7uObj6Du4vYTqLsn8wzq7t15'
    'AXX37b2Cuvuzb6Dugdw7qHswXwF1D+1/gLqHC5+g7pPFL1D3qYNvUHfp6AfUPVr6BXWPlf/+AfuJ'
    'FD0E9R7kAAAAAElFTkSuQmCC';

/// Decoded violet track cover, created once for a stable cache key.
final MemoryImage _coverViolet = _embeddedPng(_coverVioletPng);

/// Decoded amber track cover, created once for a stable cache key.
final MemoryImage _coverAmber = _embeddedPng(_coverAmberPng);

/// Decoded teal track cover, created once for a stable cache key.
final MemoryImage _coverTeal = _embeddedPng(_coverTealPng);

/// Decoded sports backdrop, created once for a stable cache key.
final MemoryImage _sportsBackdrop = _embeddedPng(_sportsBackdropPng);
0
likes
0
points
534
downloads

Publisher

verified publisherwinwin.email

Weekly Downloads

A pure-Dart Flutter recreation of the Samsung Now Bar with animated media, notification, routine, sports, and timer surfaces.

Repository (GitHub)
View/report issues

Topics

#widget #ui #animation #now-bar #live-activity

License

unknown (license)

Dependencies

flutter

More

Packages that depend on nowbar_flutter