bee_dynamic_launcher 1.0.0 copy "bee_dynamic_launcher: ^1.0.0" to clipboard
bee_dynamic_launcher: ^1.0.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> {
  bool _loading = true;
  String? _errorMessage;
  List<LauncherVariantEntry> _entries = const [];
  String? _currentVariantId;
  String? _busyVariantId;

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

  Future<void> _load() async {
    setState(() {
      _loading = true;
      _errorMessage = null;
    });
    try {
      await LauncherCatalog.instance.loadFromBundle();
      await BeeDynamicLauncher.initializeFromCatalog();
      final current = await BeeDynamicLauncher.getCurrentVariant();
      if (!mounted) {
        return;
      }
      setState(() {
        _entries = LauncherCatalog.instance.variants;
        _currentVariantId = current ?? LauncherCatalog.instance.primaryVariantId;
        _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 {
      await BeeDynamicLauncher.applyVariant(entry.id);
      final current = await BeeDynamicLauncher.getCurrentVariant();
      if (!mounted) {
        return;
      }
      setState(() => _currentVariantId = current ?? entry.id);
      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);

  @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 scheme = Theme.of(context).colorScheme;

    return Scaffold(
      appBar: AppBar(
        title: Text('Launcher Variants', style: Theme.of(context).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: scheme.brightness == Brightness.light
          ? const Color(0xFFF4F5F7)
          : Theme.of(context).scaffoldBackgroundColor,
      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),
              LauncherPreviewCard(
                title: currentLabel,
                variantId: currentId,
                activeIconAssetPath: launcherIconPreviewAssetPath(currentId),
              ),
              const SizedBox(height: 14),
            ],
            const _SectionTitle('Icon Variants'),
            const SizedBox(height: 8),
            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,
            ),
      ),
    );
  }
}
2
likes
150
points
0
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