rg_bridge 0.1.1 copy "rg_bridge: ^0.1.1" to clipboard
rg_bridge: ^0.1.1 copied to clipboard

PlatformAndroidiOS
unlisted

Hardware test bridge: device info + 14 headless test controllers + touchscreen widget. Native Android ships in-package (button interception, screen events, bluetooth/wifi, permissions); iOS is a neutral stub.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:rg_bridge/rg_bridge.dart';
import 'package:webview_flutter/webview_flutter.dart';

import 'pages/accelerometer_test_page.dart';
import 'pages/biometric_test_page.dart';
import 'pages/bluetooth_test_page.dart';
import 'pages/camera_test_page.dart';
import 'pages/device_info_page.dart';
import 'pages/flashlight_test_page.dart';
import 'pages/form_submission_page.dart';
import 'pages/gps_test_page.dart';
import 'pages/gyroscope_test_page.dart';
import 'pages/microphone_test_page.dart';
import 'pages/navigation_button_test_page.dart';
import 'pages/power_button_test_page.dart';
import 'pages/speaker_test_page.dart';
import 'pages/speech_test_page.dart';
import 'pages/touchscreen_test_page.dart';
import 'pages/vibration_test_page.dart';
import 'pages/volume_button_test_page.dart';
import 'pages/wifi_test_page.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  // Fullscreen: hide status bar + navigation bar (immersive sticky — swipe to show sementara)
  SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
  SystemChrome.setSystemUIOverlayStyle(
    const SystemUiOverlayStyle(
      statusBarColor: Colors.transparent,
      systemNavigationBarColor: Colors.transparent,
    ),
  );
  runApp(const ExampleApp());
}

/// Shared SDK instance for example screens that hit the backend (WebView, Form).
final RGBridge exampleSdk = RGBridge(
  baseUrl: 'https://rgstudio.usherops.com',
  clientId: 'rg_72mg5yD6CKBs5mUZaRAoJY6IJGs91wwc0T1QT2HL',
);

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SDK RG WebView Example',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const _MenuPage(),
    );
  }
}

class WebViewExamplePage extends StatefulWidget {
  const WebViewExamplePage({super.key, required this.sdk});

  final RGBridge sdk;

  @override
  State<WebViewExamplePage> createState() => _WebViewExamplePageState();
}

class _WebViewExamplePageState extends State<WebViewExamplePage> {
  WebViewController? _controller;
  bool _started = false;
  bool _isLoading = false;
  String? _error;
  final List<WebViewEvent> _events = [];
  WebViewEvent? get _lastEvent => _events.isEmpty ? null : _events.last;
  final List<({DateTime time, String level, String message})> _console = [];
  bool _showConsole = false;
  String? _sessionToken;

  final _typeCtrl = TextEditingController(text: 'pengecekan-laptop');
  final _conversationIdCtrl = TextEditingController();

  RGBridge get _sdk => widget.sdk;

  @override
  void initState() {
    super.initState();
    SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
  }

  @override
  void dispose() {
    _typeCtrl.dispose();
    _conversationIdCtrl.dispose();
    _sdk.webview.offAllEvents();
    super.dispose();
  }

  Future<void> _initWebView() async {
    final type = _typeCtrl.text.trim();
    if (type.isEmpty) {
      setState(() {
        _started = true;
        _error = 'type wajib diisi';
        _isLoading = false;
      });
      return;
    }

    final conversationId = _conversationIdCtrl.text.trim();
    final meta = conversationId.isEmpty
        ? null
        : <String, dynamic>{'conversation_id': conversationId};

    setState(() {
      _started = true;
      _isLoading = true;
      _error = null;
      _controller = null;
      _events.clear();
      _console.clear();
    });

    try {
      final initSession = await _sdk.webview.initSessionWebView(
        type: type,
        firstName: 'John',
        lastName: 'Doe',
        memberCode: '123456',
        meta: meta,
      );
      _sessionToken = initSession.sessionToken;
      for (final t in WebviewEventType.values) {
        _sdk.webview.onEvent(t, _handleEvent);
      }
      final controller = _sdk.webview.createController(
        onConsoleMessage: (msg) {
          if (!mounted) return;
          setState(
            () => _console.add((
              time: DateTime.now(),
              level: msg.level.name,
              message: msg.message,
            )),
          );
          debugPrint('[WebView:${msg.level.name}] ${msg.message}');
        },
      );
      await _sdk.webview.openWebView(
        controller: controller,
        url: initSession.webViewUrl,
      );
      if (mounted) {
        setState(() {
          _controller = controller;
          _isLoading = false;
        });
      }
    } catch (e) {
      if (mounted) {
        setState(() {
          _error = e.toString();
          _isLoading = false;
        });
      }
    }
  }

  void _backToSetup() {
    _sdk.webview.offAllEvents();
    setState(() {
      _started = false;
      _isLoading = false;
      _error = null;
      _controller = null;
      _sessionToken = null;
      _events.clear();
      _console.clear();
      _showConsole = false;
    });
  }

  void _handleEvent(WebViewEvent event) {
    debugPrint('[SDKRG] ${event.type.name} ${event.data}');
    if (!mounted) return;
    setState(() => _events.add(event));
    final msg = switch (event.type) {
      WebviewEventType.formSubmitted =>
        'Form submitted: ${event.data['name'] ?? '-'}',
      WebviewEventType.formCancelled =>
        'Form cancelled: ${event.data['reason'] ?? '-'}',
      WebviewEventType.formError =>
        'Form error: ${event.data['message'] ?? event.data['code'] ?? '-'}',
      WebviewEventType.formSuccess =>
        'Form success: ${event.data['id'] ?? '-'}',
      WebviewEventType.imageCaptured =>
        'Image captured: ${event.data['imageId'] ?? '-'}',
    };
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(msg), behavior: SnackBarBehavior.floating),
    );
  }

  Future<void> _reload() async {
    if (_controller == null || _sessionToken == null) return;
    final session = await _sdk.webview.getSessionWebView(
      sessionToken: _sessionToken!,
    );
    await _sdk.webview.openWebView(
      controller: _controller!,
      url: session.webViewUrl,
    );
  }

  void _toggleConsole() => setState(() => _showConsole = !_showConsole);

  @override
  Widget build(BuildContext context) {
    SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);

    if (!_started) {
      return Scaffold(
        appBar: AppBar(title: const Text('WebView Demo')),
        body: ListView(
          padding: const EdgeInsets.all(16),
          children: [
            const Text(
              'Isi type (wajib) dan meta.conversation_id (opsional), '
              'lalu buka WebView.',
            ),
            const SizedBox(height: 16),
            TextField(
              controller: _typeCtrl,
              decoration: const InputDecoration(
                labelText: 'type',
                hintText: 'pengecekan-laptop',
                border: OutlineInputBorder(),
                isDense: true,
              ),
              style: const TextStyle(fontFamily: 'monospace', fontSize: 13),
            ),
            const SizedBox(height: 12),
            TextField(
              controller: _conversationIdCtrl,
              decoration: const InputDecoration(
                labelText: 'meta.conversation_id (opsional)',
                border: OutlineInputBorder(),
                isDense: true,
              ),
              style: const TextStyle(fontFamily: 'monospace', fontSize: 13),
            ),
            const SizedBox(height: 20),
            FilledButton.icon(
              onPressed: _initWebView,
              icon: const Icon(Icons.web),
              label: const Text('Buka WebView'),
            ),
          ],
        ),
      );
    }

    return Scaffold(
      backgroundColor: Colors.black,
      body: Stack(
        children: [
          // WebView / loading / error — full bleed
          Positioned.fill(child: _buildBody()),

          // Banner last event — overlay top (tanpa AppBar)
          if (_lastEvent != null)
            Positioned(
              top: 0,
              left: 0,
              right: 0,
              child: SafeArea(
                bottom: false,
                child: Container(
                  color: _colorFor(_lastEvent!.type),
                  padding: const EdgeInsets.symmetric(
                    horizontal: 12,
                    vertical: 8,
                  ),
                  child: Text(
                    '${_lastEvent!.type.name}: ${_lastEvent!.data}',
                    style: const TextStyle(color: Colors.white, fontSize: 11),
                    maxLines: 2,
                    overflow: TextOverflow.ellipsis,
                  ),
                ),
              ),
            ),

          // Event log — overlay bottom, bukan AppBar/bottomNavigationBar
          if (_events.isNotEmpty)
            Positioned(
              left: 0,
              right: 0,
              bottom: 0,
              child: SafeArea(
                top: false,
                child: Container(
                  height: 140,
                  decoration: BoxDecoration(
                    color: Theme.of(context).colorScheme.surfaceContainerHighest
                        .withValues(alpha: 0.97),
                    border: Border(
                      top: BorderSide(color: Theme.of(context).dividerColor),
                    ),
                  ),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.stretch,
                    children: [
                      Padding(
                        padding: const EdgeInsets.symmetric(
                          horizontal: 12,
                          vertical: 6,
                        ),
                        child: Row(
                          children: [
                            Expanded(
                              child: Text(
                                'Event log (${_events.length})',
                                style: Theme.of(context).textTheme.labelSmall,
                              ),
                            ),
                            InkWell(
                              onTap: () => setState(() => _events.clear()),
                              borderRadius: BorderRadius.circular(6),
                              child: const Padding(
                                padding: EdgeInsets.all(4),
                                child: Icon(Icons.close, size: 16),
                              ),
                            ),
                          ],
                        ),
                      ),
                      const Divider(height: 1),
                      Expanded(
                        child: ListView.separated(
                          padding: const EdgeInsets.symmetric(
                            horizontal: 12,
                            vertical: 4,
                          ),
                          itemCount: _events.length,
                          reverse: true,
                          separatorBuilder: (_, _) => const Divider(height: 1),
                          itemBuilder: (_, i) {
                            final e = _events[_events.length - 1 - i];
                            return Padding(
                              padding: const EdgeInsets.symmetric(vertical: 4),
                              child: Row(
                                children: [
                                  _Badge(type: e.type),
                                  const SizedBox(width: 8),
                                  Expanded(
                                    child: Text(
                                      e.data.toString(),
                                      style: const TextStyle(fontSize: 11),
                                      maxLines: 2,
                                      overflow: TextOverflow.ellipsis,
                                    ),
                                  ),
                                ],
                              ),
                            );
                          },
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            ),

          // JS Console overlay — developer console untuk WebView
          if (_showConsole)
            Positioned(
              left: 0,
              right: 0,
              top: 0,
              bottom: 0,
              child: _ConsoleOverlay(
                entries: _console,
                onClose: _toggleConsole,
                onClear: () => setState(() => _console.clear()),
                onCopy: (text) {
                  Clipboard.setData(ClipboardData(text: text));
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(content: Text('Copied to clipboard')),
                  );
                },
              ),
            ),

          // Floating controls — ganti AppBar actions, tetap tanpa topbar
          Positioned(
            right: 12,
            bottom: (_events.isNotEmpty ? 152 : 0) + (_showConsole ? 0 : 0),
            child: SafeArea(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  FloatingActionButton.small(
                    heroTag: 'setup',
                    onPressed: _backToSetup,
                    tooltip: 'Ubah type / conversation_id',
                    child: const Icon(Icons.edit),
                  ),
                  const SizedBox(height: 8),
                  FloatingActionButton.small(
                    heroTag: 'reload',
                    onPressed: _controller == null ? null : _reload,
                    tooltip: 'Reload',
                    child: const Icon(Icons.refresh),
                  ),
                  const SizedBox(height: 8),
                  FloatingActionButton.small(
                    heroTag: 'console',
                    onPressed: _toggleConsole,
                    tooltip: 'Developer console (${_console.length})',
                    backgroundColor: _showConsole
                        ? const Color(0xFFB3261E)
                        : null,
                    child: Badge(
                      isLabelVisible: _console.isNotEmpty,
                      label: Text('${_console.length}'),
                      child: Icon(
                        _showConsole ? Icons.close : Icons.bug_report,
                      ),
                    ),
                  ),
                  if (_events.isNotEmpty) const SizedBox(height: 8),
                  if (_events.isNotEmpty)
                    FloatingActionButton.small(
                      heroTag: 'clear',
                      onPressed: () => setState(() => _events.clear()),
                      tooltip: 'Clear log',
                      child: const Icon(Icons.delete_sweep),
                    ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildBody() {
    if (_error != null) {
      return Container(
        color: Colors.white,
        child: Center(
          child: Padding(
            padding: const EdgeInsets.all(24),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                const Icon(Icons.error_outline, size: 48, color: Colors.red),
                const SizedBox(height: 12),
                Text(_error!, textAlign: TextAlign.center),
                const SizedBox(height: 16),
                FilledButton(
                  onPressed: () {
                    _sdk.webview.offAllEvents();
                    _initWebView();
                  },
                  child: const Text('Coba lagi'),
                ),
                const SizedBox(height: 8),
                TextButton(
                  onPressed: _backToSetup,
                  child: const Text('Ubah input'),
                ),
              ],
            ),
          ),
        ),
      );
    }
    if (_isLoading || _controller == null) {
      return const ColoredBox(
        color: Colors.white,
        child: Center(child: CircularProgressIndicator()),
      );
    }
    return WebViewWidget(controller: _controller!);
  }

  Color _colorFor(WebviewEventType t) => switch (t) {
    WebviewEventType.formSubmitted => const Color(0xFF6750A4),
    WebviewEventType.formCancelled => const Color(0xFF79747E),
    WebviewEventType.formError => const Color(0xFFB3261E),
    WebviewEventType.formSuccess => const Color(0xFF2E7D32),
    WebviewEventType.imageCaptured => const Color(0xFF00639A),
  };
}

class _ConsoleOverlay extends StatelessWidget {
  final List<({DateTime time, String level, String message})> entries;
  final VoidCallback onClose;
  final VoidCallback onClear;
  final void Function(String text) onCopy;

  const _ConsoleOverlay({
    required this.entries,
    required this.onClose,
    required this.onClear,
    required this.onCopy,
  });

  Color _levelColor(String level) => switch (level) {
    'error' => const Color(0xFFB3261E),
    'warning' => const Color(0xFFE65100),
    _ => const Color(0xFF2E7D32),
  };

  @override
  Widget build(BuildContext context) {
    return Container(
      color: const Color(0xFF0F1115).withValues(alpha: 0.96),
      child: SafeArea(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Container(
              color: const Color(0xFF1E1E1E),
              padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
              child: Row(
                children: [
                  const Icon(Icons.terminal, size: 16, color: Colors.white70),
                  const SizedBox(width: 8),
                  Expanded(
                    child: Text(
                      'WebView Console (${entries.length})',
                      style: const TextStyle(
                        color: Colors.white,
                        fontSize: 13,
                        fontWeight: FontWeight.w600,
                      ),
                    ),
                  ),
                  TextButton(
                    onPressed: entries.isEmpty ? null : onClear,
                    child: const Text(
                      'Clear',
                      style: TextStyle(color: Colors.white70),
                    ),
                  ),
                  IconButton(
                    onPressed: onClose,
                    icon: const Icon(
                      Icons.close,
                      size: 18,
                      color: Colors.white,
                    ),
                    tooltip: 'Close console',
                  ),
                ],
              ),
            ),
            const Divider(height: 1, color: Color(0xFF333333)),
            if (entries.isEmpty)
              const Expanded(
                child: Center(
                  child: Text(
                    '(console kosong — console.log dari JS akan muncul di sini)',
                    style: TextStyle(color: Color(0xFF888888), fontSize: 12),
                  ),
                ),
              )
            else
              Expanded(
                child: ListView.separated(
                  padding: const EdgeInsets.all(8),
                  reverse: true,
                  itemCount: entries.length,
                  separatorBuilder: (_, _) =>
                      const Divider(height: 1, color: Color(0xFF222222)),
                  itemBuilder: (_, i) {
                    final e = entries[entries.length - 1 - i];
                    final time =
                        '${e.time.hour.toString().padLeft(2, '0')}:${e.time.minute.toString().padLeft(2, '0')}:${e.time.second.toString().padLeft(2, '0')}';
                    return InkWell(
                      onTap: () => onCopy(e.message),
                      child: Padding(
                        padding: const EdgeInsets.symmetric(
                          vertical: 6,
                          horizontal: 4,
                        ),
                        child: Row(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Container(
                              padding: const EdgeInsets.symmetric(
                                horizontal: 5,
                                vertical: 2,
                              ),
                              decoration: BoxDecoration(
                                color: _levelColor(e.level),
                                borderRadius: BorderRadius.circular(4),
                              ),
                              child: Text(
                                e.level,
                                style: const TextStyle(
                                  color: Colors.white,
                                  fontSize: 9,
                                  fontWeight: FontWeight.w700,
                                ),
                              ),
                            ),
                            const SizedBox(width: 6),
                            Text(
                              time,
                              style: const TextStyle(
                                color: Color(0xFF888888),
                                fontSize: 10,
                                fontFamily: 'monospace',
                              ),
                            ),
                            const SizedBox(width: 6),
                            Expanded(
                              child: SelectableText(
                                e.message,
                                style: const TextStyle(
                                  color: Color(0xFFD4D4D4),
                                  fontSize: 11,
                                  fontFamily: 'monospace',
                                ),
                              ),
                            ),
                          ],
                        ),
                      ),
                    );
                  },
                ),
              ),
            Container(
              color: const Color(0xFF1E1E1E),
              padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
              child: const Text(
                'Tap baris untuk copy • console.error/warn/log dari JS + [JS] bridge error tampil di sini',
                style: TextStyle(color: Color(0xFF888888), fontSize: 10),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class _Badge extends StatelessWidget {
  final WebviewEventType type;
  const _Badge({required this.type});

  @override
  Widget build(BuildContext context) {
    final color = switch (type) {
      WebviewEventType.formSubmitted => const Color(0xFF6750A4),
      WebviewEventType.formCancelled => const Color(0xFF79747E),
      WebviewEventType.formError => const Color(0xFFB3261E),
      WebviewEventType.formSuccess => const Color(0xFF2E7D32),
      WebviewEventType.imageCaptured => const Color(0xFF00639A),
    };
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 3),
      decoration: BoxDecoration(
        color: color,
        borderRadius: BorderRadius.circular(6),
      ),
      child: Text(
        type.name,
        style: const TextStyle(
          color: Colors.white,
          fontSize: 10,
          fontWeight: FontWeight.w700,
        ),
      ),
    );
  }
}

// ── Hardware test examples (rg_bridge device addon) ────────────────
// Tiap test punya file sendiri di lib/pages/ + scaffold di lib/widgets/.

/// Menu utama example: demo WebView + 16 test hardware + device info.
class _MenuPage extends StatelessWidget {
  const _MenuPage();

  void _go(BuildContext context, Widget page) {
    Navigator.of(context).push(MaterialPageRoute(builder: (_) => page));
  }

  @override
  Widget build(BuildContext context) {
    final groups =
        <
          String,
          List<({IconData icon, String title, String subtitle, Widget page})>
        >{
          'WebView': [
            (
              icon: Icons.web,
              title: 'WebView Demo',
              subtitle: 'initSession + event bridge',
              page: WebViewExamplePage(sdk: exampleSdk),
            ),
          ],
          'Form': [
            (
              icon: Icons.assignment,
              title: 'Form Submission',
              subtitle: 'initFormSubmission → submitForm',
              page: FormSubmissionPage(sdk: exampleSdk),
            ),
          ],
          'Info Perangkat': [
            (
              icon: Icons.smartphone,
              title: 'Device Info',
              subtitle: 'getDeviceInfo + battery/memory/storage',
              page: const DeviceInfoPage(),
            ),
          ],
          'Sensor Gerak': [
            (
              icon: Icons.touch_app,
              title: 'Touchscreen Test',
              subtitle: 'sdk.device.checkTouchscreen()',
              page: const TouchscreenTestPage(),
            ),
            (
              icon: Icons.screen_rotation,
              title: 'Accelerometer Test',
              subtitle: 'sdk.device.checkAccelerometer()',
              page: const AccelerometerTestPage(),
            ),
            (
              icon: Icons.rotate_right,
              title: 'Gyroscope Test',
              subtitle: 'sdk.device.checkGyroscope()',
              page: const GyroscopeTestPage(),
            ),
          ],
          'Biometrik': [
            (
              icon: Icons.fingerprint,
              title: 'Biometric Test',
              subtitle: 'sdk.device.checkBiometric()',
              page: const BiometricTestPage(),
            ),
          ],
          'Konektivitas & Lokasi': [
            (
              icon: Icons.bluetooth,
              title: 'Bluetooth Test',
              subtitle: 'sdk.device.checkBluetooth()',
              page: const BluetoothTestPage(),
            ),
            (
              icon: Icons.wifi,
              title: 'WiFi Test',
              subtitle: 'sdk.device.checkWifi()',
              page: const WifiTestPage(),
            ),
            (
              icon: Icons.gps_fixed,
              title: 'GPS Test',
              subtitle: 'sdk.device.checkGps()',
              page: const GpsTestPage(),
            ),
          ],
          'Media': [
            (
              icon: Icons.camera_alt,
              title: 'Camera Test',
              subtitle: 'sdk.device.checkCamera()',
              page: const CameraTestPage(),
            ),
            (
              icon: Icons.volume_up,
              title: 'Speaker Test',
              subtitle: 'sdk.device.checkSpeaker()',
              page: const SpeakerTestPage(),
            ),
            (
              icon: Icons.vibration,
              title: 'Vibration Test',
              subtitle: 'sdk.device.checkVibration()',
              page: const VibrationTestPage(),
            ),
            (
              icon: Icons.record_voice_over,
              title: 'Speech Test',
              subtitle: 'sdk.device.checkSpeech()',
              page: const SpeechTestPage(),
            ),
            (
              icon: Icons.mic,
              title: 'Microphone Test',
              subtitle: 'sdk.device.checkMicrophone()',
              page: const MicrophoneTestPage(),
            ),
            (
              icon: Icons.flashlight_on,
              title: 'Flashlight Test',
              subtitle: 'sdk.device.checkFlashlight()',
              page: const FlashlightTestPage(),
            ),
          ],
          'Tombol': [
            (
              icon: Icons.smart_button,
              title: 'Navigation Button Test',
              subtitle: 'sdk.device.checkNavigationButton()',
              page: const NavigationButtonTestPage(),
            ),
            (
              icon: Icons.power_settings_new,
              title: 'Power Button Test',
              subtitle: 'sdk.device.checkPower()',
              page: const PowerButtonTestPage(),
            ),
            (
              icon: Icons.volume_up_outlined,
              title: 'Volume Button Test',
              subtitle: 'sdk.device.checkVolumeButton()',
              page: const VolumeButtonTestPage(),
            ),
          ],
        };
    return Scaffold(
      appBar: AppBar(title: const Text('rg_bridge example')),
      body: ListView(
        padding: const EdgeInsets.all(12),
        children: [
          for (final entry in groups.entries) ...[
            Padding(
              padding: const EdgeInsets.fromLTRB(4, 12, 4, 4),
              child: Text(
                entry.key,
                style: Theme.of(
                  context,
                ).textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w700),
              ),
            ),
            for (final item in entry.value)
              Card(
                child: ListTile(
                  leading: Icon(item.icon),
                  title: Text(item.title),
                  subtitle: Text(item.subtitle),
                  trailing: const Icon(Icons.chevron_right),
                  onTap: () => _go(context, item.page),
                ),
              ),
          ],
        ],
      ),
    );
  }
}
1
likes
130
points
112
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Hardware test bridge: device info + 14 headless test controllers + touchscreen widget. Native Android ships in-package (button interception, screen events, bluetooth/wifi, permissions); iOS is a neutral stub.

License

MIT (license)

Dependencies

audioplayers, camera, connectivity_plus, cryptography, device_info_plus, flutter, flutter_blue_plus, geolocator, http, local_auth, network_info_plus, package_info_plus, pointycastle, sensors_plus, speech_to_text, torch_light, webview_flutter, webview_flutter_android, webview_flutter_wkwebview

More

Packages that depend on rg_bridge

Packages that implement rg_bridge