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

One Dart API for screen time, app usage, and app limits — Android and iOS, unified. Wraps Android UsageStatsManager and iOS FamilyControls / DeviceActivity / ManagedSettings behind a single, type-safe [...]

example/lib/main.dart

import 'dart:async';

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

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'screen_time_kit demo',
      theme: ThemeData(colorSchemeSeed: Colors.indigo, useMaterial3: true),
      home: const HomePage(),
    );
  }
}

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

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

class _HomePageState extends State<HomePage> {
  final _screenTime = ScreenTimeKit();

  PermissionStatus? _status;
  List<AppUsageInfo> _usage = const [];
  List<UsageSummary> _dailySummaries = const [];
  List<AppLimit> _limits = const [];
  String _log = '';
  bool _busy = false;

  StreamSubscription<AppLimitEvent>? _limitSub;

  @override
  void initState() {
    super.initState();
    _limitSub = _screenTime.onLimitReached.listen((event) {
      _appendLog(
        '⛔ Limit reached: ${event.packageName} (${event.limit.inMinutes}m)',
      );
    }, onError: (Object e) => _appendLog('Limit stream error: $e'));
    _refreshStatus();
  }

  @override
  void dispose() {
    _limitSub?.cancel();
    _screenTime.dispose();
    super.dispose();
  }

  void _appendLog(String line) {
    if (!mounted) return;
    setState(() => _log = '$line\n$_log');
  }

  /// Wraps an action with busy state + typed error reporting.
  Future<void> _run(String label, Future<void> Function() action) async {
    setState(() => _busy = true);
    try {
      await action();
      _appendLog('✓ $label');
    } on PermissionDeniedException catch (e) {
      _appendLog('🔒 $label — permission denied: ${e.message}');
    } on PlatformNotSupportedException catch (e) {
      _appendLog('🚫 $label — not supported: ${e.message}');
    } on EntitlementMissingException catch (e) {
      _appendLog('🍏 $label — entitlement missing: ${e.message}');
    } on ScreenTimeException catch (e) {
      _appendLog('⚠️ $label — ${e.message}');
    } finally {
      if (mounted) setState(() => _busy = false);
    }
  }

  Future<void> _refreshStatus() => _run('Check permission', () async {
    final status = await _screenTime.checkPermissionStatus();
    setState(() => _status = status);
  });

  Future<void> _requestPermission() => _run('Request permission', () async {
    final status = await _screenTime.requestPermission();
    setState(() => _status = status);
  });

  Future<void> _loadUsage() => _run('Load 24h usage', () async {
    final since = DateTime.now().subtract(const Duration(days: 1));
    final usage = await _screenTime.getAppUsage(since: since);
    usage.sort((a, b) => b.usageDuration.compareTo(a.usageDuration));
    setState(() => _usage = usage);
  });

  Future<void> _loadSummaries() => _run('Aggregate 7-day summary', () async {
    final since = DateTime.now().subtract(const Duration(days: 7));
    final summaries = await _screenTime.getDailySummaries(since: since);
    setState(() => _dailySummaries = summaries);
  });

  Future<void> _addSampleLimit() => _run('Set 1h Instagram limit', () async {
    await _screenTime.setAppLimit(
      'com.instagram.android',
      const Duration(hours: 1),
    );
    await _refreshLimits();
  });

  Future<void> _refreshLimits() => _run('Load active limits', () async {
    final limits = await _screenTime.getActiveLimits();
    setState(() => _limits = limits);
  });

  Future<void> _clearLimits() => _run('Clear all limits', () async {
    await _screenTime.clearAllLimits();
    await _refreshLimits();
  });

  Future<void> _startFocus() => _run('Start 25m focus mode', () async {
    await _screenTime.startFocusMode(
      apps: const ['com.instagram.android', 'com.zhiliaoapp.musically'],
      duration: const Duration(minutes: 25),
    );
  });

  Future<void> _stopFocus() =>
      _run('Stop focus mode', _screenTime.stopFocusMode);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('screen_time_kit'),
        bottom: _busy
            ? const PreferredSize(
                preferredSize: Size.fromHeight(2),
                child: LinearProgressIndicator(),
              )
            : null,
      ),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          _statusCard(),
          const SizedBox(height: 12),
          _section('1 · Permission', [
            _button('Check status', _refreshStatus),
            _button('Request / open settings', _requestPermission),
          ]),
          _section('2 · Usage stats', [_button('Load last 24h', _loadUsage)]),
          if (_usage.isNotEmpty) _usageList(),
          _section('3 · Summaries (pure Dart)', [
            _button('Aggregate 7 days', _loadSummaries),
          ]),
          if (_dailySummaries.isNotEmpty) _summaryList(),
          _section('4 · App limits', [
            _button('Add 1h IG limit', _addSampleLimit),
            _button('Refresh', _refreshLimits),
            _button('Clear all', _clearLimits),
          ]),
          if (_limits.isNotEmpty) _limitsList(),
          _section('5 · Focus mode', [
            _button('Start 25m', _startFocus),
            _button('Stop', _stopFocus),
          ]),
          const SizedBox(height: 16),
          _logCard(),
        ],
      ),
    );
  }

  Widget _statusCard() {
    final status = _status;
    final color = switch (status) {
      PermissionStatus.granted => Colors.green,
      null => Colors.grey,
      _ => Colors.orange,
    };
    return Card(
      color: color.withValues(alpha: 0.1),
      child: ListTile(
        leading: Icon(Icons.shield_outlined, color: color),
        title: const Text('Permission status'),
        subtitle: Text(status?.name ?? 'unknown'),
      ),
    );
  }

  Widget _section(String title, List<Widget> buttons) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 8),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(title, style: Theme.of(context).textTheme.titleMedium),
          const SizedBox(height: 6),
          Wrap(spacing: 8, runSpacing: 8, children: buttons),
        ],
      ),
    );
  }

  Widget _button(String label, Future<void> Function() onPressed) {
    return FilledButton.tonal(
      onPressed: _busy ? null : () => onPressed(),
      child: Text(label),
    );
  }

  Widget _usageList() {
    return Card(
      child: Column(
        children: [
          for (final app in _usage.take(15))
            ListTile(
              dense: true,
              title: Text(app.appName),
              subtitle: Text(app.packageName),
              trailing: Text('${app.usageDuration.inMinutes}m'),
            ),
        ],
      ),
    );
  }

  Widget _summaryList() {
    return Card(
      child: Column(
        children: [
          for (final s in _dailySummaries)
            ListTile(
              dense: true,
              title: Text('${s.start.year}-${s.start.month}-${s.start.day}'),
              subtitle: Text('${s.perApp.length} apps'),
              trailing: Text('${s.total.inMinutes}m'),
            ),
        ],
      ),
    );
  }

  Widget _limitsList() {
    return Card(
      child: Column(
        children: [
          for (final limit in _limits)
            ListTile(
              dense: true,
              leading: const Icon(Icons.timer_outlined),
              title: Text(limit.packageName),
              trailing: Text('${limit.limit.inMinutes}m/day'),
            ),
        ],
      ),
    );
  }

  Widget _logCard() {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(12),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('Log', style: Theme.of(context).textTheme.titleSmall),
            const SizedBox(height: 6),
            Text(
              _log.isEmpty ? 'Actions will appear here.' : _log,
              style: const TextStyle(fontFamily: 'monospace', fontSize: 12),
            ),
          ],
        ),
      ),
    );
  }
}
0
likes
140
points
15
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

One Dart API for screen time, app usage, and app limits — Android and iOS, unified. Wraps Android UsageStatsManager and iOS FamilyControls / DeviceActivity / ManagedSettings behind a single, type-safe interface.

Repository (GitHub)
View/report issues

Topics

#screen-time #usage-stats #parental-control #digital-wellbeing #family-controls

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on screen_time_kit

Packages that implement screen_time_kit