device_shield 0.1.0 copy "device_shield: ^0.1.0" to clipboard
device_shield: ^0.1.0 copied to clipboard

Runtime device-security checks for Flutter apps: root, jailbreak, emulator, debugger and mock-location detection, plus screen capture protection.

example/lib/main.dart

import 'dart:async';

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

void main() => runApp(const ExampleApp());

/// A one-screen demo of every device_shield feature.
class ExampleApp extends StatelessWidget {
  /// Creates the demo app.
  const ExampleApp({super.key});

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

/// Runs the checks and toggles the protections.
class HomePage extends StatefulWidget {
  /// Creates the home page.
  const HomePage({super.key});

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

class _HomePageState extends State<HomePage> {
  SecurityReport? _report;
  bool _checking = false;

  bool _screenshotProtection = false;
  bool _appSwitcherProtection = false;
  bool? _screenRecorded;
  final List<String> _events = [];
  final List<StreamSubscription<Object?>> _subscriptions = [];

  @override
  void initState() {
    super.initState();
    _subscriptions
      ..add(DeviceShield.screenshots.listen((_) => _log('Screenshot taken')))
      ..add(
        DeviceShield.screenRecordingChanges.listen((recording) {
          setState(() => _screenRecorded = recording);
          _log(
            recording ? 'Screen recording started' : 'Screen recording stopped',
          );
        }),
      );
    unawaited(_refreshRecording());
    unawaited(_runChecks());
  }

  @override
  void dispose() {
    for (final subscription in _subscriptions) {
      unawaited(subscription.cancel());
    }
    super.dispose();
  }

  Future<void> _runChecks() async {
    setState(() => _checking = true);
    final report = await DeviceShield.check();
    if (!mounted) return;
    setState(() {
      _report = report;
      _checking = false;
    });
  }

  Future<void> _refreshRecording() async {
    final recorded = await DeviceShield.isScreenRecorded();
    if (mounted) setState(() => _screenRecorded = recorded);
  }

  Future<void> _setProtection({
    required bool screenshot,
    required bool enabled,
  }) async {
    final result = screenshot
        ? await DeviceShield.setScreenshotProtection(enabled)
        : await DeviceShield.setAppSwitcherProtection(enabled);
    if (!mounted) return;
    if (result == ProtectionResult.applied) {
      setState(() {
        if (screenshot) {
          _screenshotProtection = enabled;
        } else {
          _appSwitcherProtection = enabled;
        }
      });
    }
    _log(
      '${screenshot ? 'Screenshot' : 'App-switcher'} protection '
      '${enabled ? 'on' : 'off'}: ${result.name}',
    );
  }

  void _log(String message) {
    if (!mounted) return;
    final time = TimeOfDay.now().format(context);
    setState(() => _events.insert(0, '$time  $message'));
  }

  @override
  Widget build(BuildContext context) {
    final report = _report;
    return Scaffold(
      appBar: AppBar(title: const Text('device_shield')),
      body: RefreshIndicator(
        onRefresh: _runChecks,
        child: ListView(
          padding: const EdgeInsets.all(16),
          children: [
            _SectionHeader(
              title: 'Device checks',
              trailing: _checking
                  ? const SizedBox.square(
                      dimension: 20,
                      child: CircularProgressIndicator(strokeWidth: 2),
                    )
                  : IconButton(
                      tooltip: 'Run checks again',
                      icon: const Icon(Icons.refresh),
                      onPressed: _runChecks,
                    ),
            ),
            if (report == null)
              const Padding(
                padding: EdgeInsets.all(24),
                child: Center(child: CircularProgressIndicator()),
              )
            else
              for (final result in report.all) _CheckTile(result),
            const SizedBox(height: 24),
            const _SectionHeader(title: 'Screen'),
            SwitchListTile(
              title: const Text('Screenshot protection'),
              subtitle: const Text('Blocks screenshots and recording'),
              value: _screenshotProtection,
              onChanged: (value) =>
                  _setProtection(screenshot: true, enabled: value),
            ),
            SwitchListTile(
              title: const Text('App-switcher protection'),
              subtitle: const Text('Hides content in the app switcher'),
              value: _appSwitcherProtection,
              onChanged: (value) =>
                  _setProtection(screenshot: false, enabled: value),
            ),
            ListTile(
              title: const Text('Screen being recorded'),
              trailing: Text(switch (_screenRecorded) {
                true => 'Yes',
                false => 'No',
                null => 'Unknown on this platform',
              }),
            ),
            const Card(
              child: ListTile(
                leading: Icon(Icons.account_balance),
                title: Text('Sample sensitive content'),
                subtitle: Text('Account •••• 4821 · Balance 12,450.00'),
              ),
            ),
            const SizedBox(height: 24),
            const _SectionHeader(title: 'Events'),
            if (_events.isEmpty)
              const ListTile(
                title: Text('Take a screenshot or start a screen recording.'),
              )
            else
              for (final event in _events.take(20))
                ListTile(dense: true, title: Text(event)),
          ],
        ),
      ),
    );
  }
}

class _SectionHeader extends StatelessWidget {
  const _SectionHeader({required this.title, this.trailing});

  final String title;
  final Widget? trailing;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 8),
      child: Row(
        children: [
          Expanded(
            child: Text(title, style: Theme.of(context).textTheme.titleMedium),
          ),
          ?trailing,
        ],
      ),
    );
  }
}

class _CheckTile extends StatelessWidget {
  const _CheckTile(this.result);

  final CheckResult result;

  static const _titles = {
    CheckType.root: 'Root',
    CheckType.jailbreak: 'Jailbreak',
    CheckType.emulator: 'Emulator / simulator',
    CheckType.debugger: 'Debugger',
    CheckType.mockLocation: 'Mock location',
  };

  @override
  Widget build(BuildContext context) {
    final colors = Theme.of(context).colorScheme;
    final (icon, color, label) = switch (result.status) {
      CheckStatus.detected => (Icons.warning_amber, colors.error, 'Detected'),
      CheckStatus.clear => (
        Icons.check_circle_outline,
        colors.primary,
        'Clear',
      ),
      CheckStatus.notApplicable => (
        Icons.remove_circle_outline,
        colors.outline,
        'Not applicable',
      ),
      CheckStatus.failed => (Icons.error_outline, colors.tertiary, 'Failed'),
    };
    final details = [
      ?result.error,
      if (result case MockLocationResult(locationPermissionGranted: false))
        'No location permission: location signals skipped',
    ];

    return Card(
      child: Padding(
        padding: const EdgeInsets.all(12),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                Icon(icon, color: color),
                const SizedBox(width: 12),
                Expanded(child: Text(_titles[result.type]!)),
                Text(label, style: TextStyle(color: color)),
              ],
            ),
            if (result.signals.isNotEmpty) ...[
              const SizedBox(height: 8),
              Wrap(
                spacing: 6,
                runSpacing: 6,
                children: [
                  for (final signal in result.signals)
                    Chip(
                      visualDensity: VisualDensity.compact,
                      label: Text('${signal.id} · ${signal.strength.name}'),
                    ),
                ],
              ),
            ],
            for (final detail in details)
              Padding(
                padding: const EdgeInsets.only(top: 8),
                child: Text(
                  detail,
                  style: Theme.of(context).textTheme.bodySmall,
                ),
              ),
          ],
        ),
      ),
    );
  }
}
0
likes
160
points
48
downloads
screenshot

Documentation

Documentation
API reference

Publisher

verified publishergeekyants.com

Weekly Downloads

Runtime device-security checks for Flutter apps: root, jailbreak, emulator, debugger and mock-location detection, plus screen capture protection.

Homepage
Repository (GitHub)
View/report issues

Topics

#security #root-detection #jailbreak-detection #emulator-detection #screenshot

License

BSD-3-Clause (license)

Dependencies

flutter

More

Packages that depend on device_shield

Packages that implement device_shield