page_curl_flip 0.2.2 copy "page_curl_flip: ^0.2.2" to clipboard
page_curl_flip: ^0.2.2 copied to clipboard

A realistic cylindrical page-curl animation for Flutter: a multi-page FlipBook, a CurlOverlay you drive yourself, and a PageCurlRoute transition. Zero dependencies, RTL-aware.

example/lib/main.dart

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

import 'ltr_book.dart';
import 'rtl_book.dart';
import 'shared_pages.dart';
import 'stores.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  // Marks and places are read back BEFORE the first frame, so the book opens
  // at the bookmark instead of jumping to it a moment later.
  await Persist.init();
  runApp(const ExampleApp());
}

/// Two books, named by reading direction: LTR ([LtrBook]) and RTL
/// ([RtlBook]) — one dressed head to toe, one close to the package's
/// defaults, so the two can be compared.
class ExampleApp extends StatelessWidget {
  const ExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'page_curl_flip example',
      theme: ThemeData(useMaterial3: true),
      home: const HomeScreen(),
    );
  }
}

/// The cover of the magazine: one block per edition, and the one place the
/// package's THIRD entry point is shown — tapping an edition peels the cover
/// away with [PageCurlRoute].
class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  void _open(BuildContext context, Widget book, {required bool mirror}) {
    Navigator.of(context).push(
      PageCurlRoute<void>(
        builder: (_) => book,
        // The cover itself is what curls away, so opening a book looks like
        // opening a book.
        coverChild: const _Cover(),
        pageColor: kStock,
        shine: 0.55,
        // Passed explicitly, NOT left to the ambient Directionality.
        //
        // This screen is LTR — the Arabic direction only exists inside the
        // Arabic book, which has not been built yet when the route starts.
        // So `Directionality.of(context)` here says left-to-right for BOTH
        // editions, and the Arabic cover peeled the same way as the English
        // one. The caller knows which book it is
        // opening; the route cannot.
        //
        // Closing follows automatically: the pop reverses this same curl.
        mirror: mirror,
      ),
    );
  }

  @override
  Widget build(BuildContext context) => Scaffold(
    backgroundColor: kStock,
    body: Stack(
      children: [
        const Positioned.fill(child: _Cover()),
        Positioned.fill(
          child: SafeArea(
            child: Padding(
              padding: const EdgeInsets.fromLTRB(26, 0, 26, 28),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Spacer(),
                  _Edition(
                    kicker: 'LTR',
                    title: 'ENGLISH\nEDITION',
                    line: 'Three pages. Every feature the package has.',
                    color: kTeal,
                    onTap: () => _open(context, const LtrBook(), mirror: false),
                  ),
                  const SizedBox(height: 14),
                  _Edition(
                    kicker: 'RTL',
                    title: 'الطبعة\nالعربية',
                    line: 'The same book, mirrored, plus a settings page.',
                    color: kPink,
                    textDirection: TextDirection.rtl,
                    onTap: () => _open(context, const RtlBook(), mirror: true),
                  ),
                ],
              ),
            ),
          ),
        ),
      ],
    ),
  );
}

/// The masthead and the paper it sits on. Reused as the curling cover, so the
/// page that peels away is the page you were just looking at.
class _Cover extends StatelessWidget {
  const _Cover();

  @override
  Widget build(BuildContext context) => Stack(
    clipBehavior: Clip.hardEdge,
    children: [
      const Positioned.fill(child: ColoredBox(color: kStock)),
      Positioned.fill(
        child: SafeArea(
          child: Padding(
            padding: const EdgeInsets.fromLTRB(26, 54, 26, 0),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  'PAGE\nCURL\nFLIP',
                  style: const TextStyle(
                    fontSize: 58,
                    height: 0.92,
                    letterSpacing: -2.6,
                    fontWeight: FontWeight.w900,
                    color: kPink,
                  ),
                ),
                const SizedBox(height: 14),
                Container(width: 64, height: 5, color: kTeal),
                const SizedBox(height: 14),
                const Text(
                  'A REALISTIC PAGE CURL FOR FLUTTER · ZERO DEPENDENCIES',
                  style: TextStyle(
                    fontSize: 10,
                    letterSpacing: 2.4,
                    height: 1.6,
                    fontWeight: FontWeight.w800,
                    color: kInkSoft,
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    ],
  );
}

/// One edition block. A colour block that runs the full measure, the way the
/// magazine's quotes do — not a card floating on a background.
class _Edition extends StatelessWidget {
  const _Edition({
    required this.kicker,
    required this.title,
    required this.line,
    required this.color,
    required this.onTap,
    this.textDirection,
  });

  final String kicker;
  final String title;
  final String line;
  final Color color;
  final VoidCallback onTap;
  final TextDirection? textDirection;

  @override
  Widget build(BuildContext context) {
    final block = Container(
      width: double.infinity,
      padding: const EdgeInsets.fromLTRB(22, 20, 22, 22),
      color: color,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            kicker,
            style: const TextStyle(
              fontSize: 10,
              letterSpacing: 3,
              fontWeight: FontWeight.w800,
              color: kOnColourDim,
            ),
          ),
          const SizedBox(height: 8),
          Text(
            title,
            style: const TextStyle(
              fontSize: 30,
              height: 1.0,
              letterSpacing: -1.0,
              fontWeight: FontWeight.w900,
              color: kOnColour,
            ),
          ),
          const SizedBox(height: 10),
          Text(
            line,
            style: const TextStyle(
              fontSize: 13,
              height: 1.5,
              color: kOnColourDim,
            ),
          ),
        ],
      ),
    );
    return Material(
      color: Colors.transparent,
      child: InkWell(
        onTap: onTap,
        splashColor: kOnColour.withValues(alpha: 0.14),
        child: textDirection == null
            ? block
            : Directionality(textDirection: textDirection!, child: block),
      ),
    );
  }
}
8
likes
160
points
294
downloads

Documentation

API reference

Publisher

verified publishervanisupriya.com

Weekly Downloads

A realistic cylindrical page-curl animation for Flutter: a multi-page FlipBook, a CurlOverlay you drive yourself, and a PageCurlRoute transition. Zero dependencies, RTL-aware.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter

More

Packages that depend on page_curl_flip