first_run_kit 1.1.3 copy "first_run_kit: ^1.1.3" to clipboard
first_run_kit: ^1.1.3 copied to clipboard

A production-ready Flutter package for first launch onboarding, permission flows, and safe first-run detection.

example/lib/main.dart

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

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

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

  @override
  Widget build(BuildContext context) {
    const brand = Color(0xFF0A7AFF);
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Astra Budget',
      theme: ThemeData(
        useMaterial3: true,
        colorScheme: ColorScheme.fromSeed(seedColor: brand),
        scaffoldBackgroundColor: const Color(0xFFF4F8FF),
      ),
      home: FirstRunWrapper(
        config: const FirstRunConfig(
          layout: FirstRunLayout.centered,
          primaryColor: brand,
          backgroundColor: Color(0xFFEFF5FF),
          surfaceColor: Colors.white,
          progressColor: brand,
          progressBackgroundColor: Color(0xFFD5E6FF),
          successColor: Color(0xFF0F9D58),
          warningColor: Color(0xFFF39C12),
          errorColor: Color(0xFFE53935),
          cardBorderRadius: 26,
        ),
        steps: [
          const OnboardingStep(
            title: 'Welcome to Astra Budget',
            description:
                'A modern personal finance app to plan, save, and grow steadily.',
            image: _HeroOrb(icon: Icons.auto_graph_rounded),
          ),
          const OnboardingStep(
            title: 'Money Clarity, Daily',
            description:
                'Visual insights and weekly goals help you stay in control.',
            image: _HeroOrb(icon: Icons.insights_rounded),
          ),
          const OnboardingStep(
            title: 'Smart Spending Categories',
            description:
                'Auto-categorize expenses to understand where your money goes.',
            image: _HeroOrb(icon: Icons.pie_chart_rounded),
          ),
          const OnboardingStep(
            title: 'Shared Budget Spaces',
            description:
                'Create shared spaces for family, roommates, or travel plans.',
            image: _HeroOrb(icon: Icons.groups_rounded),
          ),
          PermissionStep.camera(
            title: 'Scan Bills Instantly',
            description:
                'Enable camera access to scan receipts and pay bills faster.',
            rationale: 'You can disable it anytime from settings.',
          ),
          PermissionStep.microphone(
            title: 'Voice Notes & Smart Search',
            description: 'Enable microphone to log expenses faster by voice.',
            rationale: 'You can disable it anytime from settings.',
          ),
          PermissionStep.photos(
            title: 'Attach Receipts',
            description:
                'Allow photos access to attach bill screenshots and invoices.',
            rationale: 'You can disable it anytime from settings.',
          ),
          const CustomStep(widget: _GoLiveStep()),
        ],
        onFlowEvent: (event) {
          debugPrint(
            '[first_run_kit] ${event.type.name} step ${event.stepIndex + 1}/${event.totalSteps}',
          );
        },
        onFinish: (_) => const HomeScreen(),
      ),
    );
  }
}

class _HeroOrb extends StatelessWidget {
  const _HeroOrb({required this.icon});
  final IconData icon;

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 120,
      height: 120,
      decoration: BoxDecoration(
        gradient: const LinearGradient(
          colors: [Color(0xFF0A7AFF), Color(0xFF37B4FF)],
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
        ),
        borderRadius: BorderRadius.circular(36),
        boxShadow: const [
          BoxShadow(
            color: Color(0x330A7AFF),
            blurRadius: 24,
            offset: Offset(0, 10),
          ),
        ],
      ),
      child: Icon(icon, color: Colors.white, size: 58),
    );
  }
}

class _GoLiveStep extends StatelessWidget {
  const _GoLiveStep();

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: const [
            Icon(Icons.rocket_launch_rounded, size: 58),
            SizedBox(height: 12),
            Text(
              'Setup complete. Your finance cockpit is ready.',
              textAlign: TextAlign.center,
            ),
          ],
        ),
      ),
    );
  }
}

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Astra Budget'),
        actions: [
          IconButton(
            tooltip: 'Replay onboarding',
            onPressed: () async {
              await FirstRunManager().resetFirstRun();
              if (context.mounted) {
                Navigator.of(context).pushReplacement(
                  MaterialPageRoute<void>(builder: (_) => const ExampleApp()),
                );
              }
            },
            icon: const Icon(Icons.replay_rounded),
          ),
        ],
      ),
      body: Stack(
        children: [
          const _Atmosphere(),
          ListView(
            padding: const EdgeInsets.all(16),
            children: const [
              _HeaderCard(),
              SizedBox(height: 12),
              _ActionGrid(),
              SizedBox(height: 12),
              _InsightCard(),
            ],
          ),
        ],
      ),
    );
  }
}

class _Atmosphere extends StatelessWidget {
  const _Atmosphere();

  @override
  Widget build(BuildContext context) {
    return IgnorePointer(
      child: Align(
        alignment: Alignment.topRight,
        child: Container(
          width: 260,
          height: 260,
          decoration: const BoxDecoration(
            gradient: RadialGradient(
              colors: [Color(0x220A7AFF), Colors.transparent],
            ),
          ),
        ),
      ),
    );
  }
}

class _HeaderCard extends StatelessWidget {
  const _HeaderCard();

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(20),
      decoration: BoxDecoration(
        gradient: const LinearGradient(
          colors: [Color(0xFF0A7AFF), Color(0xFF3D8BFF)],
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
        ),
        borderRadius: BorderRadius.circular(22),
      ),
      child: const Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text('Total Balance', style: TextStyle(color: Colors.white70)),
          SizedBox(height: 6),
          Text(
            '\$12,420.85',
            style: TextStyle(
              color: Colors.white,
              fontWeight: FontWeight.w800,
              fontSize: 32,
            ),
          ),
          SizedBox(height: 6),
          Text('+8.4% this month', style: TextStyle(color: Colors.white)),
        ],
      ),
    );
  }
}

class _ActionGrid extends StatelessWidget {
  const _ActionGrid();

  @override
  Widget build(BuildContext context) {
    return const Row(
      children: [
        Expanded(child: _ActionCard(icon: Icons.send_rounded, label: 'Transfer')),
        SizedBox(width: 10),
        Expanded(child: _ActionCard(icon: Icons.account_tree_rounded, label: 'Budget')),
        SizedBox(width: 10),
        Expanded(child: _ActionCard(icon: Icons.savings_rounded, label: 'Savings')),
      ],
    );
  }
}

class _ActionCard extends StatelessWidget {
  const _ActionCard({required this.icon, required this.label});

  final IconData icon;
  final String label;

  @override
  Widget build(BuildContext context) {
    return Card(
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
      child: Padding(
        padding: const EdgeInsets.symmetric(vertical: 18),
        child: Column(
          children: [
            Icon(icon, size: 26),
            const SizedBox(height: 8),
            Text(label),
          ],
        ),
      ),
    );
  }
}

class _InsightCard extends StatelessWidget {
  const _InsightCard();

  @override
  Widget build(BuildContext context) {
    return Card(
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: const [
            Text('Weekly Insights', style: TextStyle(fontWeight: FontWeight.w700)),
            SizedBox(height: 10),
            ListTile(
              contentPadding: EdgeInsets.zero,
              leading: Icon(Icons.trending_up_rounded, color: Color(0xFF0F9D58)),
              title: Text('Food spending down 12%'),
              subtitle: Text('Great consistency this week'),
            ),
            Divider(height: 12),
            ListTile(
              contentPadding: EdgeInsets.zero,
              leading: Icon(Icons.calendar_month_rounded, color: Color(0xFF0A7AFF)),
              title: Text('Upcoming bill reminder'),
              subtitle: Text('Electricity bill due tomorrow'),
            ),
          ],
        ),
      ),
    );
  }
}
2
likes
160
points
335
downloads

Documentation

API reference

Publisher

verified publisherpratyushmishra.online

Weekly Downloads

A production-ready Flutter package for first launch onboarding, permission flows, and safe first-run detection.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter, permission_handler, shared_preferences

More

Packages that depend on first_run_kit