bee_dynamic_launcher 1.1.0 copy "bee_dynamic_launcher: ^1.1.0" to clipboard
bee_dynamic_launcher: ^1.1.0 copied to clipboard

Dynamic launcher icons for Flutter: Android activity-alias switching and iOS alternate app icons, with a Dart API and optional CLI to sync native projects from a JSON catalog.

example/lib/main.dart

import 'package:bee_dynamic_launcher/bee_dynamic_launcher.dart';
import 'package:bee_dynamic_launcher_example/app/app_theme.dart';
import 'package:bee_dynamic_launcher_example/widgets/catalog_error_view.dart';
import 'package:bee_dynamic_launcher_example/widgets/catalog_loading_view.dart';
import 'package:bee_dynamic_launcher_example/widgets/launcher_preview_card.dart';
import 'package:bee_dynamic_launcher_example/widgets/launcher_variants_section.dart';
import 'package:flutter/material.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const BeeLauncherExampleApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Bee Dynamic Launcher',
      debugShowCheckedModeBanner: false,
      themeMode: ThemeMode.system,
      theme: ExampleAppTheme.light(),
      darkTheme: ExampleAppTheme.dark(),
      home: const LauncherSettingsPage(),
    );
  }
}

class LauncherSettingsPage extends StatefulWidget {
  const LauncherSettingsPage({super.key});

  @override
  State<LauncherSettingsPage> createState() => _LauncherSettingsPageState();
}

class _LauncherSettingsPageState extends State<LauncherSettingsPage> {
  static const LauncherVariantResolvedColors _defaultBrandColors =
      LauncherVariantResolvedColors(
        baseColor: Color(0xFF2563EB),
        secondaryColor: Color(0xFF14B8A6),
        backgroundColor: Color(0xFFF4F5F7),
        surfaceColor: Color(0xFFFFFFFF),
  );

  bool _loading = true;
  String? _errorMessage;
  List<LauncherVariantEntry> _entries = const [];
  String? _currentVariantId;
  String? _busyVariantId;
  LauncherVariantResolvedColors? _activeColors;

  @override
  void initState() {
    super.initState();
    _load();
  }

  Future<void> _load() async {
    setState(() {
      _loading = true;
      _errorMessage = null;
    });
    try {
      await BeeDynamicLauncher.initializeFromCatalog();
      final current = await BeeDynamicLauncher.getCurrentVariant();
      final resolvedCurrentId =
          current ?? LauncherCatalog.instance.primaryVariantId;
      final colors = BeeDynamicLauncher.styleColorsForVariant(
        resolvedCurrentId,
        defaultColors: _defaultBrandColors,
      );
      if (!mounted) {
        return;
      }
      setState(() {
        _entries = LauncherCatalog.instance.variants;
        _currentVariantId = resolvedCurrentId;
        _activeColors = colors;
        _loading = false;
      });
    } catch (e) {
      if (!mounted) {
        return;
      }
      setState(() {
        _errorMessage = '$e';
        _loading = false;
      });
    }
  }

  Future<void> _apply(LauncherVariantEntry entry) async {
    if (_busyVariantId != null || entry.id == _currentVariantId) {
      return;
    }
    setState(() => _busyVariantId = entry.id);
    try {
      final requestedColors = await BeeDynamicLauncher.applyVariantAndGetStyleColors(
        entry.id,
        defaultColors: _defaultBrandColors,
      );
      final current = await BeeDynamicLauncher.getCurrentVariant();
      final resolvedCurrentId = current ?? entry.id;
      final colors = resolvedCurrentId == entry.id
          ? requestedColors
          : BeeDynamicLauncher.styleColorsForVariant(
              resolvedCurrentId,
              defaultColors: _defaultBrandColors,
            );
      if (!mounted) {
        return;
      }
      setState(() {
        _currentVariantId = resolvedCurrentId;
        _activeColors = colors;
      });
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text('Home icon set to "${entry.displayName}"'),
          behavior: SnackBarBehavior.floating,
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
        ),
      );
    } catch (e) {
      if (!mounted) {
        return;
      }
      final scheme = Theme.of(context).colorScheme;
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text(
            'Could not apply: $e',
            style: TextStyle(color: scheme.onError),
          ),
          behavior: SnackBarBehavior.floating,
          backgroundColor: scheme.error,
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
        ),
      );
    } finally {
      if (mounted) {
        setState(() => _busyVariantId = null);
      }
    }
  }

  String _labelForId(String id) => LauncherCatalog.instance.displayNameFor(id);

  ColorScheme _resolvedColorScheme(ColorScheme base) {
    final colors = _activeColors;
    if (colors == null) {
      return base;
    }
    return base.copyWith(
      primary: colors.baseColor ?? base.primary,
      secondary: colors.secondaryColor ?? base.secondary,
      surface: colors.surfaceColor ?? base.surface,
    );
  }

  @override
  Widget build(BuildContext context) {
    if (_loading) {
      return const Scaffold(body: CatalogLoadingView());
    }
    if (_errorMessage != null) {
      return Scaffold(
        body: CatalogErrorView(
          message: _errorMessage!,
          onRetry: _load,
        ),
      );
    }

    final currentId = _currentVariantId;
    final currentLabel = currentId == null ? null : _labelForId(currentId);
    final baseTheme = Theme.of(context);
    final baseScheme = baseTheme.colorScheme;
    final scheme = _resolvedColorScheme(baseScheme);
    final themedContext = baseTheme.copyWith(colorScheme: scheme);
    final backgroundColor = _activeColors?.backgroundColor ??
        (scheme.brightness == Brightness.light
            ? const Color(0xFFF4F5F7)
            : Theme.of(context).scaffoldBackgroundColor);
    final panelColor = _activeColors?.surfaceColor ?? scheme.surface;

    return Theme(
      data: themedContext,
      child: Scaffold(
        appBar: AppBar(
          title: Text(
            'Launcher Variants',
            style: themedContext.textTheme.titleMedium?.copyWith(
                  fontWeight: FontWeight.w600,
                  fontSize: 15.5,
                  letterSpacing: -0.1,
                ),
          ),
          centerTitle: true,
          actions: [
            IconButton(
              tooltip: 'Reload catalog',
              onPressed: _loading ? null : _load,
              icon: const Icon(Icons.refresh_rounded),
            ),
          ],
        ),
        backgroundColor: backgroundColor,
        body: RefreshIndicator(
          onRefresh: _load,
          child: ListView(
            physics: const AlwaysScrollableScrollPhysics(),
            padding: const EdgeInsets.fromLTRB(16, 14, 16, 24),
            children: [
              if (currentId != null && currentLabel != null) ...[
                const _SectionTitle('Current Launcher'),
                const SizedBox(height: 8),
                Container(
                  decoration: BoxDecoration(
                    color: panelColor,
                    borderRadius: BorderRadius.circular(16),
                  ),
                  padding: const EdgeInsets.all(8),
                  child: LauncherPreviewCard(
                    title: currentLabel,
                    variantId: currentId,
                    activeIconAssetPath: launcherIconPreviewAssetPath(currentId),
                  ),
                ),
                const SizedBox(height: 14),
              ],
              const _SectionTitle('Icon Variants'),
              const SizedBox(height: 8),
              Container(
                decoration: BoxDecoration(
                  color: panelColor,
                  borderRadius: BorderRadius.circular(16),
                ),
                padding: const EdgeInsets.all(8),
                child: LauncherVariantsSection(
                  entries: _entries,
                  currentVariantId: currentId,
                  busyVariantId: _busyVariantId,
                  onApply: _apply,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class _SectionTitle extends StatelessWidget {
  const _SectionTitle(this.text);

  final String text;

  @override
  Widget build(BuildContext context) {
    final scheme = Theme.of(context).colorScheme;
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 2),
      child: Text(
        text,
        style: Theme.of(context).textTheme.labelMedium?.copyWith(
              color: scheme.onSurfaceVariant,
              fontSize: 13.5,
              fontWeight: FontWeight.w500,
              letterSpacing: -0.1,
            ),
      ),
    );
  }
}
5
likes
150
points
184
downloads
screenshot

Documentation

Documentation
API reference

Publisher

verified publisherorionbproject.web.id

Weekly Downloads

Dynamic launcher icons for Flutter: Android activity-alias switching and iOS alternate app icons, with a Dart API and optional CLI to sync native projects from a JSON catalog.

Repository (GitHub)
View/report issues
Contributing

Topics

#launcher-icon #app-icon #android #ios #utilities

License

MIT (license)

Dependencies

flutter, image, path

More

Packages that depend on bee_dynamic_launcher

Packages that implement bee_dynamic_launcher