tap2exit 1.3.0 copy "tap2exit: ^1.3.0" to clipboard
tap2exit: ^1.3.0 copied to clipboard

A Flutter plugin providing double-tap-to-exit functionality for Android with native Toast support and safe iOS handling.

example/lib/main.dart

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

void main() {
  runApp(const MyApp());
}

/// Example app demonstrating the tap2exit plugin.
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'tap2exit Example',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorSchemeSeed: Colors.deepPurple,
        useMaterial3: true,
        brightness: Brightness.light,
      ),
      darkTheme: ThemeData(
        colorSchemeSeed: Colors.deepPurple,
        useMaterial3: true,
        brightness: Brightness.dark,
      ),
      home: const HomePage(),
    );
  }
}

/// Home page with toggles to demonstrate all tap2exit features.
class HomePage extends StatefulWidget {
  const HomePage({super.key});

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

class _HomePageState extends State<HomePage> {
  bool _useToast = false;
  bool _useCustomCallback = false;
  double _durationSeconds = 2.0;
  String _message = 'Press back again to exit';
  ToastDuration _toastDuration = ToastDuration.short;
  ToastGravity _toastGravity = ToastGravity.bottom;
  late final TextEditingController _messageController;

  @override
  void initState() {
    super.initState();
    _messageController = TextEditingController(text: _message);
  }

  @override
  void dispose() {
    _messageController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final colorScheme = Theme.of(context).colorScheme;

    return Tap2Exit(
      message: _message,
      duration: Duration(milliseconds: (_durationSeconds * 1000).round()),
      useToast: _useToast,
      toastDuration: _toastDuration,
      toastGravity: _toastGravity,
      onBackFirstPress: _useCustomCallback
          ? (context) {
              ScaffoldMessenger.of(context)
                ..clearSnackBars()
                ..showSnackBar(
                  SnackBar(
                    content: Text(_message),
                    behavior: SnackBarBehavior.floating,
                    backgroundColor: colorScheme.tertiary,
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(12),
                    ),
                    margin: const EdgeInsets.symmetric(
                      horizontal: 16,
                      vertical: 12,
                    ),
                  ),
                );
            }
          : null,
      onFirstBackPress: () {
        debugPrint('[tap2exit] First back press detected');
      },
      onExit: () {
        debugPrint('[tap2exit] App is about to exit');
      },
      snackBarStyle: Tap2ExitSnackBarStyle(
        behavior: SnackBarBehavior.floating,
        backgroundColor: colorScheme.inverseSurface,
        textStyle: TextStyle(
          color: colorScheme.onInverseSurface,
          fontSize: 14,
        ),
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(12),
        ),
        margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
      ),
      child: Scaffold(
        appBar: AppBar(
          title: const Text('tap2exit Example'),
          centerTitle: true,
        ),
        body: ListView(
          padding: const EdgeInsets.all(24),
          children: [
            // ── Header ────────────────────────────────────
            Icon(
              Icons.exit_to_app_rounded,
              size: 64,
              color: colorScheme.primary,
            ),
            const SizedBox(height: 16),
            Text(
              'Double-Tap to Exit',
              textAlign: TextAlign.center,
              style: Theme.of(context).textTheme.headlineSmall?.copyWith(
                    fontWeight: FontWeight.bold,
                  ),
            ),
            const SizedBox(height: 8),
            Text(
              'Press the system back button to try it out.\n'
              'Customise the behaviour below.',
              textAlign: TextAlign.center,
              style: Theme.of(context).textTheme.bodyMedium?.copyWith(
                    color: colorScheme.onSurfaceVariant,
                  ),
            ),
            const SizedBox(height: 32),

            // ── Toast toggle ──────────────────────────────
            Card(
              child: SwitchListTile(
                title: const Text('Use native Toast'),
                subtitle:
                    const Text('Android only — falls back to SnackBar on iOS'),
                value: _useToast,
                onChanged: (value) => setState(() => _useToast = value),
              ),
            ),
            const SizedBox(height: 12),

            // ── Toast duration toggle ─────────────────────
            Card(
              child: SwitchListTile(
                title: const Text('Long toast duration'),
                subtitle: Text(
                  _toastDuration == ToastDuration.long
                      ? '≈ 3.5 seconds'
                      : '≈ 2 seconds (default)',
                ),
                value: _toastDuration == ToastDuration.long,
                onChanged: _useToast
                    ? (value) => setState(() => _toastDuration =
                        value ? ToastDuration.long : ToastDuration.short)
                    : null,
              ),
            ),
            const SizedBox(height: 12),

            // ── Toast gravity selector ────────────────────
            Card(
              child: ListTile(
                title: const Text('Toast gravity'),
                subtitle: const Text('Where the toast appears on screen'),
                trailing: DropdownButton<ToastGravity>(
                  value: _toastGravity,
                  onChanged: _useToast
                      ? (value) {
                          if (value != null) {
                            setState(() => _toastGravity = value);
                          }
                        }
                      : null,
                  items: ToastGravity.values
                      .map((g) => DropdownMenuItem(
                            value: g,
                            child: Text(g.name),
                          ))
                      .toList(),
                ),
              ),
            ),
            const SizedBox(height: 12),

            // ── Custom callback toggle ────────────────────
            Card(
              child: SwitchListTile(
                title: const Text('Custom first-press callback'),
                subtitle: const Text(
                  'Replaces Toast with a custom SnackBar via onBackFirstPress',
                ),
                value: _useCustomCallback,
                onChanged: (value) =>
                    setState(() => _useCustomCallback = value),
              ),
            ),
            const SizedBox(height: 12),

            // ── Duration slider ───────────────────────────
            Card(
              child: ListTile(
                title: const Text('Exit window duration'),
                subtitle: Slider(
                  value: _durationSeconds,
                  min: 1,
                  max: 5,
                  divisions: 8,
                  label: '${_durationSeconds.toStringAsFixed(1)}s',
                  onChanged: (value) =>
                      setState(() => _durationSeconds = value),
                ),
                trailing: Text('${_durationSeconds.toStringAsFixed(1)}s'),
              ),
            ),
            const SizedBox(height: 12),

            // ── Message input ─────────────────────────────
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: TextField(
                  decoration: const InputDecoration(
                    labelText: 'Exit message',
                    border: OutlineInputBorder(),
                  ),
                  controller: _messageController,
                  onChanged: (value) => _message = value,
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
0
likes
160
points
105
downloads

Documentation

API reference

Publisher

verified publisherjaberio.dev

Weekly Downloads

A Flutter plugin providing double-tap-to-exit functionality for Android with native Toast support and safe iOS handling.

Repository (GitHub)
View/report issues

Topics

#back-button #exit #navigation #android

License

MIT (license)

Dependencies

flutter

More

Packages that depend on tap2exit

Packages that implement tap2exit