sms_insights_flutter 0.0.1-dev.2 copy "sms_insights_flutter: ^0.0.1-dev.2" to clipboard
sms_insights_flutter: ^0.0.1-dev.2 copied to clipboard

PlatformAndroid

Flutter plugin wrapping the PayU SMS Insights Android SDK (core + optional logger). Reads transactional SMS for credit assessment with consent-based permission flow.

example/lib/main.dart

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:sms_insights_flutter/sms_insights_flutter.dart';

import 'client_credentials.dart';

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

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

  @override
  Widget build(BuildContext context) {
    const purple = Color(0xFF6200EE);
    return MaterialApp(
      title: 'SMS Insights Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        useMaterial3: false,
        primaryColor: purple,
        colorScheme: const ColorScheme.light(primary: purple),
        tabBarTheme: const TabBarThemeData(
          labelColor: purple,
          unselectedLabelColor: Colors.grey,
          indicatorColor: purple,
        ),
        inputDecorationTheme: const InputDecorationTheme(
          border: OutlineInputBorder(),
          contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 14),
        ),
      ),
      home: const HomeScreen(),
    );
  }
}

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

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen>
    with SingleTickerProviderStateMixin {
  static const _prefsClientEmail = 'CLIENT_EMAIL';
  static const _prefsAccessKey = 'ACCESS_KEY';
  static const _prefsCustomerId = 'CUSTOMER_ID';
  static const _prefsCompanyName = 'COMPANY_NAME';
  static const _prefsSalary = 'SALARY';

  late final TabController _tabController;

  final _clientEmailCtrl = TextEditingController();
  final _accessKeyCtrl = TextEditingController();
  final _customerIdCtrl = TextEditingController();
  final _customUrlCtrl = TextEditingController();
  final _incomeCompanyCtrl = TextEditingController();
  final _incomeSalaryCtrl = TextEditingController();
  final _deviceMatchPhoneCtrl = TextEditingController();

  SharedPreferences? _prefs;
  StreamSubscription<SdkEvent>? _sub;

  String _clientEmail = '';
  String _accessKey = '';
  String _customerId = '';
  String _customUrl = '';

  SdkEnvironmentKind _environment = SdkEnvironmentKind.staging;
  bool _suppressEnvironmentCredentialUpdate = true;
  bool _customUrlSet = false;

  bool _incomePredictionChecked = false;
  bool _incomePredictionSaved = false;
  bool _incomeInputsEditable = true;
  String? _savedIncomeCompany;
  int? _savedIncomeSalary;

  bool _deviceMatchSectionVisible = false;
  bool _deviceMatchByMobile = false;
  bool _deviceMatchSaved = false;
  bool _deviceMatchInputsEditable = true;

  bool _initEnabled = false;
  bool _postInitEnabled = false;
  bool _busy = false;
  bool _environmentEnabled = true;
  bool _incomePredictionEnabled = true;
  bool _credentialsEnabled = true;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 2, vsync: this);
    _sub = SmsSdkWrapper.instance.events.listen(_onEvent);
    _loadPreferences();
  }

  @override
  void dispose() {
    _sub?.cancel();
    _tabController.dispose();
    _clientEmailCtrl.dispose();
    _accessKeyCtrl.dispose();
    _customerIdCtrl.dispose();
    _customUrlCtrl.dispose();
    _incomeCompanyCtrl.dispose();
    _incomeSalaryCtrl.dispose();
    _deviceMatchPhoneCtrl.dispose();
    super.dispose();
  }

  Future<void> _loadPreferences() async {
    final prefs = await SharedPreferences.getInstance();
    _prefs = prefs;

    _customerId = prefs.getString(_prefsCustomerId) ?? '';
    _clientEmail =
        prefs.getString(_prefsClientEmail) ?? ClientCredentials.clientEmail;
    _accessKey = prefs.getString(_prefsAccessKey) ?? ClientCredentials.accessKey;

    final company = prefs.getString(_prefsCompanyName);
    final salaryStr = prefs.getString(_prefsSalary);
    if (company != null && salaryStr != null) {
      final salary = int.tryParse(salaryStr);
      if (salary != null) {
        _savedIncomeCompany = company;
        _savedIncomeSalary = salary;
        _incomeCompanyCtrl.text = company;
        _incomeSalaryCtrl.text = salaryStr;
      }
    }

    if (!mounted) return;
    setState(() {
      _clientEmailCtrl.text = _clientEmail;
      _accessKeyCtrl.text = _accessKey;
      _customerIdCtrl.text = _customerId;
      _suppressEnvironmentCredentialUpdate = false;
      _updateInitEnabled();
    });
  }

  void _onEvent(SdkEvent event) {
    if (event is InitSuccessEvent) {
      setState(() {
        _postInitEnabled = true;
        _initEnabled = false;
        _environmentEnabled = false;
        _incomePredictionEnabled = false;
        _credentialsEnabled = false;
      });
      _toast('Init Success');
    } else if (event is InitFailureEvent) {
      _toast('Init Failure (${event.code}): ${event.message}');
    } else if (event is SmsSyncSuccessEvent) {
      _toast(event.message);
    } else if (event is NoMessagesToSyncEvent) {
      _toast(event.message);
    } else if (event is UploadFailureEvent) {
      _toast('Upload failure (${event.errorCode}): ${event.message}');
    } else if (event is SmsPermissionNotAvailableEvent) {
      _toast('SMS permission not available: ${event.message}');
    } else if (event is InitNotCalledEvent) {
      _toast('Init not called: ${event.message}');
    } else if (event is ForgetUserFailureEvent) {
      _toast('${event.errorCode} ${event.message}');
    } else if (event is MatchDeviceFailureEvent) {
      _toast('Device match failure (${event.errorCode}): ${event.message}');
    } else if (event is RequestPermissionsEvent) {
      unawaited(_handlePermissionRequest(event));
    }
  }

  Future<void> _handlePermissionRequest(RequestPermissionsEvent event) async {
    try {
      final granted = await _requestPermissions(event.permissions);
      await SmsSdkWrapper.instance.grantPermissions(
        requestId: event.requestId,
        grantedPermissions: granted,
      );
    } catch (e) {
      _toast('Permission request failed: $e');
    }
  }

  void _updateInitEnabled() {
    final hasCredentials = _customerId.isNotEmpty &&
        _clientEmail.isNotEmpty &&
        _accessKey.isNotEmpty;
    final customOk =
        _environment != SdkEnvironmentKind.custom || _customUrlSet;
    _initEnabled = hasCredentials && customOk && !_postInitEnabled;
  }

  Future<void> _savePref(String key, String value) async {
    await _prefs?.setString(key, value);
  }

  void _onClientEmailChanged(String value) {
    _clientEmail = value.trim();
    _savePref(_prefsClientEmail, _clientEmail);
    setState(_updateInitEnabled);
  }

  void _onAccessKeyChanged(String value) {
    _accessKey = value.trim();
    _savePref(_prefsAccessKey, _accessKey);
    setState(_updateInitEnabled);
  }

  void _onCustomerIdChanged(String value) {
    _customerId = value.trim();
    _savePref(_prefsCustomerId, _customerId);
    setState(_updateInitEnabled);
  }

  void _onEnvironmentChanged(SdkEnvironmentKind? value) {
    if (value == null) return;
    setState(() {
      _environment = value;
      if (value == SdkEnvironmentKind.custom) {
        _customUrlSet = false;
        if (_customUrlCtrl.text.trim().isEmpty) {
          _customUrlCtrl.text = 'https://';
          _customUrlCtrl.selection = TextSelection.collapsed(
            offset: _customUrlCtrl.text.length,
          );
        }
        _updateInitEnabled();
      } else {
        _customUrlSet = true;
        if (!_suppressEnvironmentCredentialUpdate) {
          final creds = ClientCredentials.credentialsForEnvironment(value);
          if (creds.email.isNotEmpty) {
            _clientEmail = creds.email;
            _accessKey = creds.accessKey;
            _clientEmailCtrl.text = _clientEmail;
            _accessKeyCtrl.text = _accessKey;
            _savePref(_prefsClientEmail, _clientEmail);
            _savePref(_prefsAccessKey, _accessKey);
          }
        }
        _updateInitEnabled();
      }
    });
  }

  void _submitCustomUrl() {
    final input = _customUrlCtrl.text.trim();
    if (input.isEmpty) {
      _toast('URL should not be empty.');
      return;
    }
    setState(() {
      _customUrl = input;
      _customUrlSet = true;
      _updateInitEnabled();
    });
    _toast('URL set.');
  }

  void _onCustomUrlChanged(String value) {
    if (_environment == SdkEnvironmentKind.custom && !_customUrlSet) {
      setState(() {
        _customUrlSet = false;
        _updateInitEnabled();
      });
    }
  }

  void _updateIncomePredictionVisibility(bool visible) {
    setState(() {
      _incomePredictionChecked = visible;
      if (!visible) {
        _incomePredictionSaved = false;
        _savedIncomeCompany = null;
        _savedIncomeSalary = null;
        _incomeCompanyCtrl.clear();
        _incomeSalaryCtrl.clear();
        _setIncomeInputsEditable(true);
      }
    });
  }

  void _saveIncomePrediction() {
    final company = _incomeCompanyCtrl.text.trim();
    final salaryText = _incomeSalaryCtrl.text.trim();
    if (company.isEmpty || salaryText.isEmpty) {
      _toast('Please enter company name and salary.');
      return;
    }
    final salary = int.tryParse(salaryText);
    if (salary == null) {
      _toast('Please enter a valid salary.');
      return;
    }
    _savePref(_prefsCompanyName, company);
    _savePref(_prefsSalary, salary.toString());
    setState(() {
      _savedIncomeCompany = company;
      _savedIncomeSalary = salary;
      _incomePredictionSaved = true;
      _setIncomeInputsEditable(false);
    });
    _toast('Income prediction saved.');
  }

  void _resetIncomePrediction() {
    setState(() {
      _incomePredictionSaved = false;
      _savedIncomeCompany = null;
      _savedIncomeSalary = null;
      _incomeCompanyCtrl.clear();
      _incomeSalaryCtrl.clear();
      _setIncomeInputsEditable(true);
    });
  }

  void _setIncomeInputsEditable(bool editable) {
    _incomeInputsEditable = editable;
  }

  void _showDeviceMatchSection() {
    setState(() {
      _deviceMatchSectionVisible = true;
      _deviceMatchByMobile = false;
      _deviceMatchSaved = false;
      _deviceMatchPhoneCtrl.clear();
      _setDeviceMatchInputsEditable(true);
    });
  }

  void _setDeviceMatchInputsEditable(bool editable) {
    _deviceMatchInputsEditable = editable;
  }

  Future<void> _saveDeviceMatch() async {
    if (_deviceMatchByMobile && _deviceMatchPhoneCtrl.text.trim().isEmpty) {
      _toast('Please enter a mobile number');
      return;
    }
    setState(() => _busy = true);
    try {
      await _applyDeviceMatch(
        _deviceMatchByMobile ? _deviceMatchPhoneCtrl.text.trim() : null,
      );
      if (!mounted) return;
      setState(() {
        _deviceMatchSaved = true;
        _setDeviceMatchInputsEditable(false);
      });
      FocusScope.of(context).unfocus();
    } finally {
      if (mounted) setState(() => _busy = false);
    }
  }

  void _resetDeviceMatch() {
    setState(() {
      _deviceMatchByMobile = false;
      _deviceMatchPhoneCtrl.clear();
      _deviceMatchSaved = false;
      _setDeviceMatchInputsEditable(true);
    });
  }

  Future<void> _applyDeviceMatch(String? phoneNo) async {
    try {
      await SmsSdkWrapper.instance.setDeviceMatch(
        DeviceMatch(
          email: ClientCredentials.demoDeviceMatchEmail,
          name: ClientCredentials.demoDeviceMatchName,
          phoneNo: phoneNo?.isNotEmpty == true ? phoneNo : null,
        ),
      );
    } on SmsInsightsException catch (e) {
      _toast(e.message);
    }
  }

  Future<void> _checkPermissionAndInit() async {
    var status = await Permission.sms.status;
    if (!status.isGranted) {
      if (!mounted) return;
      final proceed = await showDialog<bool>(
        context: context,
        builder: (_) => AlertDialog(
          content: const Text(
            'This feature requires permission to access your device\'s SMS. '
            'Granting this permission enables interaction with the application '
            'more efficiently. If you decline, you will not be able to proceed further.',
          ),
          actions: [
            TextButton(
              onPressed: () async {
                await Permission.sms.request();
                if (mounted) Navigator.pop(context, true);
              },
              child: const Text('Grant Permission'),
            ),
            TextButton(
              onPressed: () => Navigator.pop(context, false),
              child: const Text('No Thanks'),
            ),
          ],
        ),
      );
      if (proceed != true) return;
      status = await Permission.sms.status;
      if (!status.isGranted) {
        _toast('SMS permission is required to initialise the SDK');
        return;
      }
    }
    await _initSdk();
  }

  SdkClientConfig _buildConfig() {
    CustomEnvironmentConfig? customEnv;
    if (_environment == SdkEnvironmentKind.custom) {
      customEnv = CustomEnvironmentConfig(
        testEmail: _clientEmail,
        accessKey: _accessKey,
        envUrl: _customUrl,
        signozUrl: _customUrl,
      );
    }

    IncomeEstimation? income;
    if (_incomePredictionChecked &&
        _incomePredictionSaved &&
        _savedIncomeSalary != null) {
      income = IncomeEstimation(
        salary: _savedIncomeSalary!.toDouble(),
        company: _savedIncomeCompany ?? '',
      );
    }

    return SdkClientConfig(
      clientEmail: _clientEmail,
      accessId: _accessKey,
      customerId: _customerId,
      upperLimit: 190,
      environment: _environment,
      customEnvironment: customEnv,
      incomePrediction: income,
    );
  }

  Future<void> _initSdk() async {
    setState(() => _busy = true);
    try {
      await SmsSdkWrapper.instance.init(_buildConfig());
    } on SmsInsightsException catch (e) {
      _toast('${e.code}: ${e.message}');
    } finally {
      if (mounted) setState(() => _busy = false);
    }
  }

  Future<void> _startSync() async {
    if (_deviceMatchSectionVisible && !_deviceMatchSaved) {
      if (_deviceMatchByMobile) {
        final phone = _deviceMatchPhoneCtrl.text.trim();
        if (phone.isEmpty) {
          _toast('Please enter a mobile number');
          return;
        }
        await _applyDeviceMatch(phone);
      } else {
        await _applyDeviceMatch(null);
      }
      setState(() {
        _deviceMatchSaved = true;
        _setDeviceMatchInputsEditable(false);
      });
    }

    setState(() => _busy = true);
    try {
      await SmsSdkWrapper.instance.startSync();
    } on SmsInsightsException catch (e) {
      _toast(e.message);
    } finally {
      if (mounted) setState(() => _busy = false);
    }
  }

  Future<void> _forgetUser() async {
    setState(() => _busy = true);
    try {
      await SmsSdkWrapper.instance.forgetUser();
      setState(() {
        _postInitEnabled = false;
        _environmentEnabled = true;
        _incomePredictionEnabled = true;
        _credentialsEnabled = true;
        _incomePredictionChecked = false;
        _incomePredictionSaved = false;
        _incomeInputsEditable = true;
        _savedIncomeCompany = null;
        _savedIncomeSalary = null;
        _incomeCompanyCtrl.clear();
        _incomeSalaryCtrl.clear();
        _deviceMatchSectionVisible = false;
        _deviceMatchByMobile = false;
        _deviceMatchSaved = false;
        _deviceMatchPhoneCtrl.clear();
        _setDeviceMatchInputsEditable(true);
        if (_environment == SdkEnvironmentKind.custom) {
          _customUrlSet = false;
          _customUrlCtrl.text =
              _customUrl.isNotEmpty ? _customUrl : 'https://';
          _customUrlCtrl.selection = TextSelection.collapsed(
            offset: _customUrlCtrl.text.length,
          );
        }
        _customUrl = '';
        _updateInitEnabled();
      });
      await _prefs?.remove(_prefsCompanyName);
      await _prefs?.remove(_prefsSalary);
    } on SmsInsightsException catch (e) {
      _toast(e.message);
    } finally {
      if (mounted) setState(() => _busy = false);
    }
  }

  Future<List<String>> _requestPermissions(List<String> requested) async {
    final mapping = <Permission, List<String>>{};
    for (final p in requested) {
      final handler = switch (p) {
        'android.permission.READ_SMS' => Permission.sms,
        'android.permission.ACCESS_FINE_LOCATION' => Permission.location,
        'android.permission.ACCESS_COARSE_LOCATION' => Permission.location,
        'android.permission.READ_PHONE_STATE' => Permission.phone,
        'android.permission.READ_PHONE_NUMBERS' => Permission.phone,
        _ => null,
      };
      if (handler != null) (mapping[handler] ??= []).add(p);
    }
    if (mapping.isEmpty) return [];
    final statuses = await mapping.keys.toList().request();
    return [
      for (final entry in statuses.entries)
        if (entry.value.isGranted) ...?mapping[entry.key],
    ];
  }

  void _toast(String msg) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(msg), duration: const Duration(seconds: 3)),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Column(
          children: [
            TabBar(
              controller: _tabController,
              tabs: const [
                Tab(text: 'ACCOUNT DETAILS'),
                Tab(text: 'SDK ACTIONS'),
              ],
            ),
            Expanded(
              child: TabBarView(
                controller: _tabController,
                children: [
                  _buildAccountDetailsTab(),
                  _buildSdkActionsTab(),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildAccountDetailsTab() {
    final isCustom = _environment == SdkEnvironmentKind.custom;
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          _OutlinedField(
            label: 'Client Email registered with PayU',
            controller: _clientEmailCtrl,
            enabled: _credentialsEnabled,
            keyboardType: TextInputType.emailAddress,
            onChanged: _onClientEmailChanged,
          ),
          const SizedBox(height: 12),
          _OutlinedField(
            label: 'Access Key provided by PayU',
            controller: _accessKeyCtrl,
            enabled: _credentialsEnabled,
            onChanged: _onAccessKeyChanged,
          ),
          const SizedBox(height: 12),
          _OutlinedField(
            label: 'Customer Id',
            controller: _customerIdCtrl,
            enabled: _credentialsEnabled,
            textInputAction: TextInputAction.done,
            onChanged: _onCustomerIdChanged,
            onSubmitted: (_) => FocusScope.of(context).unfocus(),
          ),
          const SizedBox(height: 16),
          Row(
            children: [
              const Text('Environment'),
              const Spacer(),
              DropdownButton<SdkEnvironmentKind>(
                value: _environment,
                underline: const SizedBox.shrink(),
                onChanged: _environmentEnabled ? _onEnvironmentChanged : null,
                items: const [
                  DropdownMenuItem(
                    value: SdkEnvironmentKind.staging,
                    child: Text('Staging'),
                  ),
                  DropdownMenuItem(
                    value: SdkEnvironmentKind.custom,
                    child: Text('Custom'),
                  ),
                  DropdownMenuItem(
                    value: SdkEnvironmentKind.production,
                    child: Text('Production'),
                  ),
                ],
              ),
            ],
          ),
          if (isCustom) ...[
            const SizedBox(height: 8),
            TextField(
              controller: _customUrlCtrl,
              enabled: _credentialsEnabled && !_customUrlSet,
              decoration: const InputDecoration(
                border: OutlineInputBorder(),
                contentPadding:
                    EdgeInsets.symmetric(horizontal: 12, vertical: 14),
              ),
              keyboardType: TextInputType.url,
              textInputAction: TextInputAction.done,
              onChanged: _onCustomUrlChanged,
              onSubmitted: (_) => _submitCustomUrl(),
            ),
            const SizedBox(height: 8),
            Center(
              child: ElevatedButton(
                onPressed: (_credentialsEnabled && !_customUrlSet)
                    ? _submitCustomUrl
                    : null,
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.grey.shade300,
                  foregroundColor: Colors.black87,
                ),
                child: const Text('Set URL'),
              ),
            ),
          ],
          const SizedBox(height: 16),
          CheckboxListTile(
            value: _incomePredictionChecked,
            onChanged: _incomePredictionEnabled
                ? (v) => _updateIncomePredictionVisibility(v ?? false)
                : null,
            title: const Text('Income Prediction (Optional)'),
            controlAffinity: ListTileControlAffinity.leading,
            contentPadding: EdgeInsets.zero,
          ),
          if (_incomePredictionChecked) ...[
            Row(
              children: [
                Expanded(
                  child: _OutlinedField(
                    label: 'Company name',
                    controller: _incomeCompanyCtrl,
                    enabled:
                        _incomePredictionEnabled && _incomeInputsEditable,
                  ),
                ),
                const SizedBox(width: 8),
                Expanded(
                  child: _OutlinedField(
                    label: 'Salary',
                    controller: _incomeSalaryCtrl,
                    enabled:
                        _incomePredictionEnabled && _incomeInputsEditable,
                    keyboardType: TextInputType.number,
                    inputFormatters: [FilteringTextInputFormatter.digitsOnly],
                    textInputAction: TextInputAction.done,
                    onSubmitted: (_) => _saveIncomePrediction(),
                  ),
                ),
              ],
            ),
            const SizedBox(height: 8),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
                  onPressed: (_incomePredictionEnabled && _incomeInputsEditable)
                      ? _saveIncomePrediction
                      : null,
                  style: ElevatedButton.styleFrom(
                    backgroundColor: Colors.grey.shade300,
                    foregroundColor: Colors.black87,
                  ),
                  child: const Text('Save'),
                ),
                const SizedBox(width: 12),
                ElevatedButton(
                  onPressed:
                      (_incomePredictionEnabled && !_incomeInputsEditable)
                          ? _resetIncomePrediction
                          : null,
                  style: ElevatedButton.styleFrom(
                    backgroundColor: Colors.grey.shade300,
                    foregroundColor: Colors.black87,
                  ),
                  child: const Text('Reset'),
                ),
              ],
            ),
          ],
        ],
      ),
    );
  }

  Widget _buildDeviceMatchSection() {
    return ClipRect(
      child: AnimatedSize(
        duration: const Duration(milliseconds: 200),
        curve: Curves.easeInOut,
        alignment: Alignment.topCenter,
        child: _deviceMatchSectionVisible
            ? Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  const SizedBox(height: 16),
                  CheckboxListTile(
                    value: _deviceMatchByMobile,
                    onChanged: _deviceMatchInputsEditable && !_busy
                        ? (v) {
                            setState(() {
                              _deviceMatchByMobile = v ?? false;
                              if (!_deviceMatchByMobile) {
                                _deviceMatchPhoneCtrl.clear();
                              }
                            });
                          }
                        : null,
                    title: const Text('Mobile no.'),
                    controlAffinity: ListTileControlAffinity.leading,
                    contentPadding: EdgeInsets.zero,
                  ),
                  ClipRect(
                    child: AnimatedSize(
                      duration: const Duration(milliseconds: 200),
                      curve: Curves.easeInOut,
                      alignment: Alignment.topCenter,
                      child: _deviceMatchByMobile
                          ? Column(
                              crossAxisAlignment: CrossAxisAlignment.stretch,
                              children: [
                                const SizedBox(height: 8),
                                _OutlinedField(
                                  label: 'Mobile number',
                                  controller: _deviceMatchPhoneCtrl,
                                  enabled: _deviceMatchInputsEditable,
                                  keyboardType: TextInputType.phone,
                                ),
                                const SizedBox(height: 8),
                                Row(
                                  mainAxisAlignment: MainAxisAlignment.center,
                                  children: [
                                    ElevatedButton(
                                      onPressed: _deviceMatchInputsEditable &&
                                              !_busy
                                          ? _saveDeviceMatch
                                          : null,
                                      style: ElevatedButton.styleFrom(
                                        backgroundColor: Colors.grey.shade300,
                                        foregroundColor: Colors.black87,
                                      ),
                                      child: const Text('Save'),
                                    ),
                                    const SizedBox(width: 12),
                                    ElevatedButton(
                                      onPressed: !_deviceMatchInputsEditable &&
                                              !_busy
                                          ? _resetDeviceMatch
                                          : null,
                                      style: ElevatedButton.styleFrom(
                                        backgroundColor: Colors.grey.shade300,
                                        foregroundColor: Colors.black87,
                                      ),
                                      child: const Text('Reset'),
                                    ),
                                  ],
                                ),
                              ],
                            )
                          : const SizedBox.shrink(),
                    ),
                  ),
                ],
              )
            : const SizedBox.shrink(),
      ),
    );
  }

  Widget _buildSdkActionsTab() {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(24),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          _SdkButton(
            label: 'INIT SDK',
            enabled: _initEnabled && !_busy,
            onPressed: _checkPermissionAndInit,
          ),
          const SizedBox(height: 16),
          _SdkButton(
            label: 'DEVICE MATCH (OPTIONAL)',
            enabled: _postInitEnabled && !_busy,
            onPressed: _showDeviceMatchSection,
          ),
          _buildDeviceMatchSection(),
          const SizedBox(height: 16),
          _SdkButton(
            label: 'START SYNC',
            enabled: _postInitEnabled && !_busy,
            onPressed: _startSync,
          ),
          const SizedBox(height: 16),
          _SdkButton(
            label: 'FORGET USER',
            enabled: _postInitEnabled && !_busy,
            onPressed: _forgetUser,
          ),
          if (_busy) ...[
            const SizedBox(height: 32),
            const Center(child: CircularProgressIndicator()),
          ],
        ],
      ),
    );
  }
}

class _OutlinedField extends StatelessWidget {
  final String label;
  final TextEditingController controller;
  final bool enabled;
  final TextInputType? keyboardType;
  final TextInputAction? textInputAction;
  final ValueChanged<String>? onChanged;
  final ValueChanged<String>? onSubmitted;
  final List<TextInputFormatter>? inputFormatters;

  const _OutlinedField({
    required this.label,
    required this.controller,
    this.enabled = true,
    this.keyboardType,
    this.textInputAction,
    this.onChanged,
    this.onSubmitted,
    this.inputFormatters,
  });

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: controller,
      enabled: enabled,
      keyboardType: keyboardType,
      textInputAction: textInputAction,
      onChanged: onChanged,
      onSubmitted: onSubmitted,
      inputFormatters: inputFormatters,
      decoration: InputDecoration(
        labelText: label,
        border: const OutlineInputBorder(),
        contentPadding:
            const EdgeInsets.symmetric(horizontal: 12, vertical: 14),
      ),
    );
  }
}

class _SdkButton extends StatelessWidget {
  final String label;
  final bool enabled;
  final VoidCallback onPressed;

  const _SdkButton({
    required this.label,
    required this.enabled,
    required this.onPressed,
  });

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: enabled ? onPressed : null,
      style: ElevatedButton.styleFrom(
        backgroundColor: Colors.grey.shade300,
        disabledBackgroundColor: Colors.grey.shade200,
        foregroundColor: Colors.black87,
        disabledForegroundColor: Colors.grey.shade500,
        padding: const EdgeInsets.symmetric(vertical: 14),
        elevation: 2,
        textStyle: const TextStyle(
          fontSize: 14,
          fontWeight: FontWeight.w600,
          letterSpacing: 1.0,
        ),
      ),
      child: Text(label),
    );
  }
}
0
likes
140
points
7
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Flutter plugin wrapping the PayU SMS Insights Android SDK (core + optional logger). Reads transactional SMS for credit assessment with consent-based permission flow.

Topics

#sms #android #sdk #payu #credit

License

unknown (license)

Dependencies

flutter

More

Packages that depend on sms_insights_flutter

Packages that implement sms_insights_flutter