autoshot 1.4.0 copy "autoshot: ^1.4.0" to clipboard
autoshot: ^1.4.0 copied to clipboard

Automate App Store and Play Store screenshot generation across multiple devices, locales, and screens with web download and mobile file export.

example/lib/main.dart

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

import 'l10n/app_localizations.dart';

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

// ─── Autoshot entry point ─────────────────────────────────────────────────────

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

  @override
  Widget build(BuildContext context) {
    return Autoshot(
      config: AutoshotConfig(
        screens: [
          ScreenEntry.widget(name: 'home', builder: (_) => const HomeScreen()),
          ScreenEntry.widget(
            name: 'detail',
            builder: (_) => const DetailScreen(),
          ),
          ScreenEntry.widget(
            name: 'stats',
            builder: (_) => const StatsScreen(),
          ),
        ],
        locales: AppLocalizations.supportedLocales,
        devices: [
          Devices.ios.iPhone16ProMax,
          Devices.ios.iPad,
          Devices.android.samsungGalaxyS20,
        ],
        localizationsDelegates: AppLocalizations.localizationsDelegates,
        supportedLocales: AppLocalizations.supportedLocales,
        theme: ThemeData(
          colorSchemeSeed: const Color(0xFF6750A4),
          useMaterial3: true,
          brightness: Brightness.light,
        ),
        darkTheme: ThemeData(
          colorSchemeSeed: const Color(0xFF6750A4),
          useMaterial3: true,
          brightness: Brightness.dark,
        ),
      ),
      builder: (context) => const MemosApp(),
    );
  }
}

// ─── Root app ─────────────────────────────────────────────────────────────────

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      onGenerateTitle: (ctx) => AppLocalizations.of(ctx)?.appName ?? 'Memos',
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      supportedLocales: AppLocalizations.supportedLocales,
      locale: DevicePreview.locale(context),
      builder: DevicePreview.appBuilder,
      theme: ThemeData(
        colorSchemeSeed: const Color(0xFF6750A4),
        useMaterial3: true,
        brightness: Brightness.light,
      ),
      darkTheme: ThemeData(
        colorSchemeSeed: const Color(0xFF6750A4),
        useMaterial3: true,
        brightness: Brightness.dark,
      ),
      home: const HomeScreen(),
    );
  }
}

// ─── Home screen ──────────────────────────────────────────────────────────────

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

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;
    final theme = Theme.of(context);
    final cs = theme.colorScheme;

    final memos = [l10n.memo1, l10n.memo2, l10n.memo3, l10n.memo4];

    return Scaffold(
      backgroundColor: cs.surface,
      appBar: AppBar(
        title: Text(l10n.appName),
        centerTitle: false,
        actions: [
          IconButton(icon: const Icon(Icons.search), onPressed: () {}),
          const CircleAvatar(radius: 16, child: Icon(Icons.person, size: 18)),
          const SizedBox(width: 12),
        ],
      ),
      body: ListView(
        padding: const EdgeInsets.all(20),
        children: [
          // ── Greeting card ────────────────────────────────────────
          Card(
            color: cs.primaryContainer,
            elevation: 0,
            child: Padding(
              padding: const EdgeInsets.all(20),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    l10n.homeGreeting,
                    style: theme.textTheme.headlineSmall?.copyWith(
                      color: cs.onPrimaryContainer,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  const SizedBox(height: 6),
                  Text(
                    l10n.homeSummary,
                    style: theme.textTheme.bodyMedium?.copyWith(
                      color: cs.onPrimaryContainer.withValues(alpha: 0.8),
                    ),
                  ),
                ],
              ),
            ),
          ),

          const SizedBox(height: 20),

          Text(
            'Today',
            style: theme.textTheme.titleMedium?.copyWith(
              fontWeight: FontWeight.w600,
            ),
          ),
          const SizedBox(height: 10),

          // ── Memo list ────────────────────────────────────────────
          ...memos.asMap().entries.map((e) {
            return _MemoTile(text: e.value, done: e.key == 0);
          }),
        ],
      ),
      floatingActionButton: FloatingActionButton.extended(
        onPressed: () {},
        icon: const Icon(Icons.add),
        label: Text(l10n.cta),
      ),
    );
  }
}

class _MemoTile extends StatelessWidget {
  const _MemoTile({required this.text, this.done = false});

  final String text;
  final bool done;

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

    return Card(
      margin: const EdgeInsets.symmetric(vertical: 5),
      elevation: 0,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(12),
        side: BorderSide(color: cs.outlineVariant),
      ),
      child: ListTile(
        leading: CircleAvatar(
          radius: 14,
          backgroundColor: done ? cs.primary : cs.surfaceContainerHighest,
          child: Icon(
            done ? Icons.check : Icons.circle_outlined,
            size: 16,
            color: done ? cs.onPrimary : cs.onSurfaceVariant,
          ),
        ),
        title: Text(
          text,
          style: TextStyle(
            decoration: done ? TextDecoration.lineThrough : null,
            color: done ? theme.hintColor : cs.onSurface,
          ),
        ),
        trailing: Icon(Icons.chevron_right, color: cs.onSurfaceVariant),
        dense: true,
      ),
    );
  }
}

// ─── Detail screen ────────────────────────────────────────────────────────────

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

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;
    final theme = Theme.of(context);
    final cs = theme.colorScheme;

    return Scaffold(
      appBar: AppBar(title: Text(l10n.detailTitle)),
      body: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // ── Illustration placeholder ──────────────────────────
            Container(
              width: double.infinity,
              height: 220,
              decoration: BoxDecoration(
                color: cs.primaryContainer,
                borderRadius: BorderRadius.circular(20),
              ),
              child: Icon(
                Icons.sticky_note_2_rounded,
                size: 96,
                color: cs.primary,
              ),
            ),

            const SizedBox(height: 32),

            Text(
              l10n.detailHeadline,
              style: theme.textTheme.headlineMedium?.copyWith(
                fontWeight: FontWeight.bold,
                color: cs.onSurface,
              ),
            ),
            const SizedBox(height: 12),
            Text(
              l10n.detailBody,
              style: theme.textTheme.bodyLarge?.copyWith(
                color: cs.onSurfaceVariant,
                height: 1.6,
              ),
            ),

            const Spacer(),

            SizedBox(
              width: double.infinity,
              child: FilledButton.icon(
                onPressed: () {},
                icon: const Icon(Icons.add),
                label: Text(l10n.cta),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

// ─── Stats screen ─────────────────────────────────────────────────────────────

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

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;
    final theme = Theme.of(context);
    final cs = theme.colorScheme;

    return Scaffold(
      appBar: AppBar(title: Text(l10n.statsTitle)),
      body: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // ── Stats grid ────────────────────────────────────────
            Row(
              children: [
                Expanded(
                  child: _StatCard(
                    label: l10n.statTotal,
                    value: '24',
                    icon: Icons.notes_rounded,
                    color: cs.primaryContainer,
                    onColor: cs.onPrimaryContainer,
                  ),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: _StatCard(
                    label: l10n.statWeek,
                    value: '7',
                    icon: Icons.calendar_today_rounded,
                    color: cs.secondaryContainer,
                    onColor: cs.onSecondaryContainer,
                  ),
                ),
              ],
            ),
            const SizedBox(height: 12),
            _StatCard(
              label: l10n.statTags,
              value: '12',
              icon: Icons.label_rounded,
              color: cs.tertiaryContainer,
              onColor: cs.onTertiaryContainer,
            ),

            const SizedBox(height: 28),

            // ── Activity bar chart ────────────────────────────────
            Text(
              'Weekly activity',
              style: theme.textTheme.titleMedium?.copyWith(
                fontWeight: FontWeight.w600,
              ),
            ),
            const SizedBox(height: 14),
            _ActivityChart(cs: cs),
          ],
        ),
      ),
    );
  }
}

class _StatCard extends StatelessWidget {
  const _StatCard({
    required this.label,
    required this.value,
    required this.icon,
    required this.color,
    required this.onColor,
  });

  final String label;
  final String value;
  final IconData icon;
  final Color color;
  final Color onColor;

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

    return Container(
      padding: const EdgeInsets.all(18),
      decoration: BoxDecoration(
        color: color,
        borderRadius: BorderRadius.circular(16),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Icon(icon, color: onColor, size: 28),
          const SizedBox(height: 12),
          Text(
            value,
            style: theme.textTheme.headlineMedium?.copyWith(
              color: onColor,
              fontWeight: FontWeight.bold,
            ),
          ),
          const SizedBox(height: 4),
          Text(
            label,
            style: theme.textTheme.bodySmall?.copyWith(
              color: onColor.withValues(alpha: 0.75),
            ),
          ),
        ],
      ),
    );
  }
}

class _ActivityChart extends StatelessWidget {
  const _ActivityChart({required this.cs});

  final ColorScheme cs;

  static const _days = ['M', 'T', 'W', 'T', 'F', 'S', 'S'];
  static const _values = [0.4, 0.7, 0.5, 1.0, 0.6, 0.3, 0.8];

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

    return SizedBox(
      height: 120,
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.end,
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: List.generate(_days.length, (i) {
          return Column(
            mainAxisAlignment: MainAxisAlignment.end,
            children: [
              AnimatedContainer(
                duration: const Duration(milliseconds: 400),
                curve: Curves.easeOut,
                width: 32,
                height: 90 * _values[i],
                decoration: BoxDecoration(
                  color: i == 3 ? cs.primary : cs.primaryContainer,
                  borderRadius: BorderRadius.circular(8),
                ),
              ),
              const SizedBox(height: 6),
              Text(
                _days[i],
                style: theme.textTheme.labelSmall?.copyWith(
                  color: cs.onSurfaceVariant,
                ),
              ),
            ],
          );
        }),
      ),
    );
  }
}
1
likes
150
points
219
downloads

Publisher

verified publisherandronasef.dev

Weekly Downloads

Automate App Store and Play Store screenshot generation across multiple devices, locales, and screens with web download and mobile file export.

Homepage

Documentation

API reference

License

MIT (license)

Dependencies

archive, device_preview, flutter, path_provider, provider

More

Packages that depend on autoshot