active_keyboard_language 0.0.1 copy "active_keyboard_language: ^0.0.1" to clipboard
active_keyboard_language: ^0.0.1 copied to clipboard

Detects and streams the active keyboard (IME) language on iOS and Android, with an RTL-aware Directionality helper widget.

example/lib/main.dart

import 'dart:async';

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

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

const _seedColor = Color(0xFF3F51B5);

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'keyboard_language',
      themeMode: ThemeMode.system,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: _seedColor),
        useMaterial3: true,
      ),
      darkTheme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: _seedColor,
          brightness: Brightness.dark,
        ),
        useMaterial3: true,
      ),
      home: const KeyboardLanguageDemo(),
    );
  }
}

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

  @override
  State<KeyboardLanguageDemo> createState() => _KeyboardLanguageDemoState();
}

class _LogEntry {
  _LogEntry(this.label, this.language, this.time);
  final String label;
  final String? language;
  final DateTime time;
}

class _KeyboardLanguageDemoState extends State<KeyboardLanguageDemo> {
  String? _currentLanguage;
  final List<_LogEntry> _log = [];
  final _textController = TextEditingController();
  StreamSubscription<String?>? _subscription;

  @override
  void initState() {
    super.initState();
    KeyboardLanguage.getCurrentLanguage().then((language) {
      if (!mounted) return;
      setState(() {
        _currentLanguage = language;
        _log.insert(0, _LogEntry('initial', language, DateTime.now()));
      });
    });
    _subscription = KeyboardLanguage.onLanguageChanged.listen((language) {
      if (!mounted) return;
      setState(() {
        _currentLanguage = language;
        _log.insert(0, _LogEntry('changed', language, DateTime.now()));
      });
    });
  }

  @override
  void dispose() {
    _subscription?.cancel();
    _textController.dispose();
    super.dispose();
  }

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

    return Scaffold(
      appBar: AppBar(
        title: const Text('keyboard_language'),
        centerTitle: false,
      ),
      body: SafeArea(
        child: ListView(
          padding: const EdgeInsets.fromLTRB(20, 16, 20, 24),
          children: [
            _LanguageStatusCard(language: _currentLanguage, isRtl: isRtl),
            const SizedBox(height: 24),
            Text(
              'Type below, then tap the globe key to switch keyboard language.',
              style: theme.textTheme.bodyMedium?.copyWith(
                color: theme.colorScheme.onSurfaceVariant,
              ),
            ),
            const SizedBox(height: 4),
            Text(
              'The field clears itself on every switch — mixing scripts in '
              'one field can otherwise land new characters in the wrong '
              'place, since Android resets the caret when the IME subtype '
              'changes.',
              style: theme.textTheme.bodySmall?.copyWith(
                color: theme.colorScheme.onSurfaceVariant,
              ),
            ),
            const SizedBox(height: 12),
            KeyboardAwareDirectionality(
              controller: _textController,
              child: TextField(
                controller: _textController,
                style: theme.textTheme.titleMedium,
                decoration: InputDecoration(
                  filled: true,
                  fillColor: theme.colorScheme.surfaceContainerHighest,
                  border: OutlineInputBorder(
                    borderRadius: BorderRadius.circular(14),
                    borderSide: BorderSide.none,
                  ),
                  contentPadding: const EdgeInsets.symmetric(
                    horizontal: 16,
                    vertical: 14,
                  ),
                  hintText: 'Type here…',
                ),
              ),
            ),
            const SizedBox(height: 28),
            Text('Event log', style: theme.textTheme.titleSmall),
            const SizedBox(height: 8),
            _EventLog(entries: _log),
          ],
        ),
      ),
    );
  }
}

class _LanguageStatusCard extends StatelessWidget {
  const _LanguageStatusCard({required this.language, required this.isRtl});

  final String? language;
  final bool isRtl;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    return Container(
      padding: const EdgeInsets.all(20),
      decoration: BoxDecoration(
        color: theme.colorScheme.primaryContainer,
        borderRadius: BorderRadius.circular(20),
      ),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  'CURRENT KEYBOARD LANGUAGE',
                  style: theme.textTheme.labelSmall?.copyWith(
                    color: theme.colorScheme.onPrimaryContainer.withValues(
                      alpha: 0.7,
                    ),
                    letterSpacing: 0.8,
                  ),
                ),
                const SizedBox(height: 6),
                Text(
                  language ?? 'unknown',
                  style: theme.textTheme.headlineMedium?.copyWith(
                    color: theme.colorScheme.onPrimaryContainer,
                    fontFamily: 'monospace',
                    fontWeight: FontWeight.w700,
                  ),
                ),
              ],
            ),
          ),
          _DirectionBadge(isRtl: isRtl),
        ],
      ),
    );
  }
}

class _DirectionBadge extends StatelessWidget {
  const _DirectionBadge({required this.isRtl});

  final bool isRtl;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final color = isRtl
        ? theme.colorScheme.tertiary
        : theme.colorScheme.outline;
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
      decoration: BoxDecoration(
        color: color.withValues(alpha: 0.15),
        borderRadius: BorderRadius.circular(999),
        border: Border.all(color: color.withValues(alpha: 0.4)),
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          Icon(
            isRtl
                ? Icons.format_textdirection_r_to_l
                : Icons.format_textdirection_l_to_r,
            size: 16,
            color: color,
          ),
          const SizedBox(width: 6),
          Text(
            isRtl ? 'RTL' : 'LTR',
            style: theme.textTheme.labelMedium?.copyWith(
              color: color,
              fontWeight: FontWeight.w700,
            ),
          ),
        ],
      ),
    );
  }
}

class _EventLog extends StatelessWidget {
  const _EventLog({required this.entries});

  final List<_LogEntry> entries;

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

    if (entries.isEmpty) {
      return Container(
        padding: const EdgeInsets.all(16),
        decoration: BoxDecoration(
          color: theme.colorScheme.surfaceContainerHighest,
          borderRadius: BorderRadius.circular(14),
        ),
        child: Text(
          'No events yet.',
          style: theme.textTheme.bodySmall?.copyWith(
            color: theme.colorScheme.onSurfaceVariant,
          ),
        ),
      );
    }

    return Container(
      decoration: BoxDecoration(
        color: theme.colorScheme.surfaceContainerHighest,
        borderRadius: BorderRadius.circular(14),
      ),
      clipBehavior: Clip.antiAlias,
      child: Column(
        children: [
          for (final (index, entry) in entries.indexed) ...[
            if (index > 0)
              Divider(height: 1, color: theme.colorScheme.outlineVariant),
            _EventLogRow(entry: entry, isLatest: index == 0),
          ],
        ],
      ),
    );
  }
}

class _EventLogRow extends StatelessWidget {
  const _EventLogRow({required this.entry, required this.isLatest});

  final _LogEntry entry;
  final bool isLatest;

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

    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
      child: Row(
        children: [
          Icon(
            changed ? Icons.swap_horiz : Icons.play_circle_outline,
            size: 18,
            color: isLatest
                ? theme.colorScheme.primary
                : theme.colorScheme.onSurfaceVariant,
          ),
          const SizedBox(width: 10),
          Expanded(
            child: Text(
              entry.language ?? 'unknown',
              style: theme.textTheme.bodyMedium?.copyWith(
                fontFamily: 'monospace',
                fontWeight: isLatest ? FontWeight.w700 : FontWeight.w500,
              ),
            ),
          ),
          Text(
            _formatTime(entry.time),
            style: theme.textTheme.bodySmall?.copyWith(
              color: theme.colorScheme.onSurfaceVariant,
            ),
          ),
        ],
      ),
    );
  }

  static String _formatTime(DateTime time) {
    String two(int n) => n.toString().padLeft(2, '0');
    return '${two(time.hour)}:${two(time.minute)}:${two(time.second)}';
  }
}
0
likes
160
points
53
downloads

Documentation

API reference

Publisher

verified publishercordeliaapps.com

Weekly Downloads

Detects and streams the active keyboard (IME) language on iOS and Android, with an RTL-aware Directionality helper widget.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on active_keyboard_language

Packages that implement active_keyboard_language