securiti_consent_sdk 1.142.0-4rc copy "securiti_consent_sdk: ^1.142.0-4rc" to clipboard
securiti_consent_sdk: ^1.142.0-4rc copied to clipboard

A Flutter plugin for managing user consent preferences and compliance with privacy regulations. Integrates with Securiti's Consent Management Platform.

example/lib/main.dart

import 'package:securiti_consent_sdk/cmp_sdk_options.dart';
import 'package:securiti_consent_sdk/models/cmp_sdk_logger_level.dart';
import 'package:securiti_consent_sdk/models/consent_status.dart';
import 'package:securiti_consent_sdk/models/post_consents_request.dart';
import 'package:securiti_consent_sdk/models/app_permission.dart';
import 'package:securiti_consent_sdk/models/gcm_config.dart';
import 'package:securiti_consent_sdk/models/google_consent_type.dart';
import 'package:securiti_consent_sdk/models/purpose.dart';
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:convert';
import 'dart:developer' as developer;
import 'dart:io' show Platform;

import 'package:flutter/services.dart';
import 'package:securiti_consent_sdk/consent_sdk_plugin.dart';
import 'package:app_settings/app_settings.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_analytics/firebase_analytics.dart';
import 'permission_service.dart';

// Global navigation key for dialogs
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

// Dialog service to safely show dialogs from anywhere
class DialogService {
  static Future<T?> showAlertDialog<T>({
    required String title,
    required String message,
    String? cancelText,
    String? confirmText,
  }) async {
    if (navigatorKey.currentContext == null) {
      developer.log("ERROR: No valid context for dialog!");
      return null;
    }

    return showDialog<T>(
      context: navigatorKey.currentContext!,
      builder: (context) => AlertDialog(
        title: Text(title),
        content: Text(message),
        actions: [
          if (cancelText != null)
            TextButton(
              onPressed: () => Navigator.pop(context, null),
              child: Text(cancelText),
            ),
          if (confirmText != null)
            TextButton(
              onPressed: () => Navigator.pop(context, true),
              child: Text(confirmText),
            ),
        ],
      ),
    );
  }
}

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Initialize Firebase
  // Note: Requires google-services.json (Android) and GoogleService-Info.plist (iOS)
  try {
    await Firebase.initializeApp();
    developer.log('Firebase initialized successfully');
  } catch (e) {
    developer.log('Firebase initialization failed: $e');
    developer.log(
        'Note: Add Firebase configuration files to run with Firebase Analytics');
  }

  runApp(const MyApp());
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String _platformVersion = 'Unknown';
  String _latestResult = 'No method called yet';
  static const EventChannel _eventChannel =
      EventChannel('ai.securiti.consent_sdk_plugin/isSDKReady');
  final _consentSdkPlugin = ConsentSdkPlugin();
  bool _isSDKReady = false;
  final ScrollController _scrollController = ScrollController();

  // Firebase Analytics instance (nullable for when Firebase is not configured)
  FirebaseAnalytics? _analytics;
  bool _firebaseConsentApplied = false;

  // List to store all app permissions from the SDK
  List<AppPermission> _appPermissions = [];

  // We no longer need these maps as we'll use the PermissionService directly

  @override
  void initState() {
    super.initState();
    initPlatformState();

    // Initialize Firebase Analytics if Firebase is available
    try {
      _analytics = FirebaseAnalytics.instance;
      developer.log('Firebase Analytics initialized successfully');
    } catch (e) {
      developer.log('Firebase Analytics not available: $e');
      developer.log(
          'Note: Add Firebase configuration files to enable Firebase Analytics');
    }

    String appURL = 'https://qa.securiti.xyz/';
    String cdnURL = 'https://cdn-qa.securiti.xyz/';
    String tenantID = 'b9153366-8d40-43b0-8009-e7c5f7342b6c';
    String appID = '765ed188-3ebd-4726-8245-f8a906f18b3a';
    String locationCode = 'AQ';

    if (Platform.isAndroid) {
      appURL = 'https://qa.securiti.xyz';
      cdnURL = 'https://cdn-qa.securiti.xyz/consent';
      appID = 'ab7b0b56-282b-4426-95e0-98418cf64736';
      locationCode = 'AQ';
    }

    CmpSDKOptions options = CmpSDKOptions(
      appURL: appURL,
      cdnURL: cdnURL,
      tenantID: tenantID,
      appID: appID,
      testingMode: true,
      loggerLevel: CmpSDKLoggerLevel.debug,
      consentsCheckInterval: 60,
      subjectId: 'flutterSubject',
      languageCode: 'en',
      locationCode: locationCode,
    );

    _consentSdkPlugin.setupSDK(options.toMap());
    _eventChannel.receiveBroadcastStream().listen((event) {
      setState(() {
        _isSDKReady = event as bool;
        if (_isSDKReady) {
          _consentSdkPlugin.presentConsentBanner();

          // Fetch permissions when SDK is ready
          _loadAppPermissions();
        }
      });
    });
  }

  // Load app permissions from the SDK
  Future<void> _loadAppPermissions() async {
    try {
      final permissions = await _consentSdkPlugin.getPermissions();
      setState(() {
        _appPermissions = permissions;
      });
      developer.log("Loaded ${permissions.length} app permissions");

      // Apply Google Consent Mode after permissions are loaded
      await _applyGoogleConsentMode();
    } catch (e) {
      developer.log("Error loading app permissions: $e");
    }
  }

  // Apply Google Consent Mode to Firebase Analytics
  Future<void> _applyGoogleConsentMode() async {
    try {
      // Check if Firebase Analytics is available
      if (_analytics == null) {
        developer.log(
            'Firebase Analytics not available, skipping Google Consent Mode application');
        return;
      }

      developer.log('Applying Google Consent Mode to Firebase Analytics...');

      // Get GCM consents from Securiti SDK
      Map<GoogleConsentType, ConsentStatus> gcmConsents =
          await _consentSdkPlugin.getGCMConsents();

      // Log consent statuses
      developer.log('Google Consent Mode statuses:');
      gcmConsents.forEach((type, status) {
        developer.log('  ${type.value}: ${status.value}');
      });

      // Convert to boolean values for Firebase
      bool analyticsGranted =
          gcmConsents[GoogleConsentType.analyticsStorage] ==
              ConsentStatus.granted;
      bool adStorageGranted =
          gcmConsents[GoogleConsentType.adStorage] ==
              ConsentStatus.granted;
      bool adUserDataGranted =
          gcmConsents[GoogleConsentType.adUserData] ==
              ConsentStatus.granted;
      bool adPersonalizationGranted =
          gcmConsents[GoogleConsentType.adPersonalization] ==
              ConsentStatus.granted;

      // Apply consent to Firebase Analytics
      await _analytics!.setConsent(
        analyticsStorageConsentGranted: analyticsGranted,
        adStorageConsentGranted: adStorageGranted,
        adUserDataConsentGranted: adUserDataGranted,
        adPersonalizationSignalsConsentGranted: adPersonalizationGranted,
      );

      setState(() {
        _firebaseConsentApplied = true;
      });

      developer.log(
          'Google Consent Mode applied to Firebase Analytics successfully');
      developer
          .log('Analytics Storage: ${analyticsGranted ? "GRANTED" : "DENIED"}');
      developer.log('Ad Storage: ${adStorageGranted ? "GRANTED" : "DENIED"}');
      developer
          .log('Ad User Data: ${adUserDataGranted ? "GRANTED" : "DENIED"}');
      developer.log(
          'Ad Personalization: ${adPersonalizationGranted ? "GRANTED" : "DENIED"}');

      // Update the result display
      setState(() {
        _latestResult = '''
Google Consent Mode Applied to Firebase Analytics:
  Analytics Storage: ${analyticsGranted ? "GRANTED" : "DENIED"}
  Ad Storage: ${adStorageGranted ? "GRANTED" : "DENIED"}
  Ad User Data: ${adUserDataGranted ? "GRANTED" : "DENIED"}
  Ad Personalization: ${adPersonalizationGranted ? "GRANTED" : "DENIED"}
''';
      });
    } catch (e) {
      developer.log('Error applying Google Consent Mode: $e');
      setState(() {
        _latestResult = 'Error applying Google Consent Mode: $e';
      });
    }
  }

  @override
  void dispose() {
    _scrollController.dispose();
    super.dispose();
  }

  // Platform messages are asynchronous, so we initialize in an async method.
  Future<void> initPlatformState() async {
    String platformVersion;
    // Platform messages may fail, so we use a try/catch PlatformException.
    // We also handle the message potentially returning null.
    try {
      platformVersion = await _consentSdkPlugin.getPlatformVersion() ??
          'Unknown platform version';
    } on PlatformException {
      platformVersion = 'Failed to get platform version.';
    }

    // If the widget was removed from the tree while the asynchronous platform
    // message was in flight, we want to discard the reply rather than calling
    // setState to update our non-existent appearance.
    if (!mounted) return;

    setState(() {
      _platformVersion = platformVersion;
    });
  }

  // Convert any Dart object to a JSON-formatted string efficiently
  String _formatJsonForDisplay(dynamic obj, {int depth = 0, int maxDepth = 3}) {
    // Limit recursion depth for performance
    if (depth > maxDepth) {
      return obj.toString();
    }

    String indent = '  ' * depth;
    String childIndent = '  ' * (depth + 1);

    if (obj == null) {
      return 'null';
    } else if (obj is Map) {
      if (obj.isEmpty) return '{}';

      List<String> pairs = [];
      obj.forEach((key, value) {
        String formattedValue =
            _formatJsonForDisplay(value, depth: depth + 1, maxDepth: maxDepth);
        pairs.add('$childIndent"$key": $formattedValue');
      });

      return '{\n${pairs.join(',\n')}\n$indent}';
    } else if (obj is List) {
      if (obj.isEmpty) return '[]';

      List<String> items = [];
      for (var item in obj) {
        items.add(
            '$childIndent${_formatJsonForDisplay(item, depth: depth + 1, maxDepth: maxDepth)}');
      }

      return '[\n${items.join(',\n')}\n$indent]';
    } else if (obj is String) {
      // Escape quotes and use proper JSON string formatting
      return '"${obj.replaceAll('"', '\\"')}"';
    } else if (obj is num || obj is bool) {
      // Numbers and booleans can be represented directly
      return obj.toString();
    } else {
      // Try to use toJson if available, or default to toString()
      try {
        if (obj is dynamic && obj.toJson != null) {
          return _formatJsonForDisplay(obj.toJson(),
              depth: depth, maxDepth: maxDepth);
        }
      } catch (_) {}

      // Last resort
      return '"${obj.toString().replaceAll('"', '\\"')}"';
    }
  }

  void _updateResult(String methodName, dynamic result) {
    // If it's a list, split it into smaller chunks for displaying
    if (result is List && result.length > 0) {
      // Special handling for complex collections
      List<Map<String, dynamic>> formattedItems = [];

      for (var i = 0; i < result.length; i++) {
        var item = result[i];

        // Create a simplified format with special handling for models
        if (item is dynamic && item.toJson != null) {
          try {
            formattedItems.add(item.toJson());
            continue;
          } catch (_) {}
        }

        if (item is Map) {
          formattedItems.add(Map<String, dynamic>.from(item));
        } else {
          // Wrap non-map items in a simple container
          formattedItems.add({'value': item.toString()});
        }
      }

      // Apply JSON formatting for the entire list
      final jsonOutput = _formatJsonForDisplay(formattedItems);

      setState(() {
        _latestResult =
            "$methodName result: (${result.length} items)\n$jsonOutput";
      });
    }
    // If result has toJson method, use that
    else if (result != null && result is dynamic) {
      try {
        if (result.toJson != null) {
          final jsonOutput = _formatJsonForDisplay(result.toJson());
          setState(() {
            _latestResult = "$methodName result:\n$jsonOutput";
          });
          return;
        }
      } catch (_) {}

      // Fall back to generic JSON formatter
      final jsonOutput = _formatJsonForDisplay(result);
      setState(() {
        _latestResult = "$methodName result:\n$jsonOutput";
      });
    } else {
      // For null or simple types
      setState(() {
        _latestResult = "$methodName result:\n${result ?? 'null'}";
      });
    }

    // No need to log the entire output which can be very large
    developer.log("$methodName executed successfully");
  }

  // Request a permission and handle the result using our custom PermissionService
  Future<void> _requestPermission(String permissionId) async {
    try {
      // First, check the current status of the permission
      PermissionStatus status =
          await PermissionService.checkPermissionStatus(permissionId);
      developer.log("Permission $permissionId status: $status");

      // Special handling for Media Library permission on iOS
      if (Platform.isIOS && permissionId == 'NSAppleMusicUsageDescription') {
        _updateResult('requestPermission',
            'iOS: Handling Media Library permission (status: $status)');

        // If status is not "notDetermined", go directly to settings
        if (status != PermissionStatus.notDetermined) {
          developer.log(
              "Media Library permission status is not 'notDetermined', going to settings");

          final shouldGoToSettings = await DialogService.showAlertDialog<bool>(
            title: 'Media Library Permission',
            message:
                'Media Library access requires manual enabling in device settings. Would you like to open settings now?',
            cancelText: 'Cancel',
            confirmText: 'Open Settings',
          );

          if (shouldGoToSettings == true) {
            await _openAppSettings();
          }
          return;
        }

        // If status is "notDetermined", try requesting the permission
        PermissionStatus result =
            await PermissionService.requestPermission(permissionId);
        developer.log("iOS Media Library permission request result: $result");

        if (result == PermissionStatus.granted) {
          _updateResult(
              'requestPermission', 'Media Library permission was granted');
        } else {
          _updateResult('requestPermission',
              'Media Library permission was not granted (result: $result)');

          // Media Library permission on iOS often requires going to settings
          final shouldGoToSettings = await DialogService.showAlertDialog<bool>(
            title: 'Media Library Permission',
            message:
                'Media Library access requires manual enabling in device settings. Would you like to open settings now?',
            cancelText: 'Cancel',
            confirmText: 'Open Settings',
          );

          if (shouldGoToSettings == true) {
            await _openAppSettings();
          }
        }
        return;
      }

      // For Android, always go directly to settings
      if (Platform.isAndroid) {
        _updateResult('requestPermission',
            'Android: Redirecting to settings for permission: $permissionId (status: $status)');

        final shouldGoToSettings = await DialogService.showAlertDialog<bool>(
          title: 'Permission Required',
          message:
              'Please enable ${PermissionService.getPermissionName(permissionId)} permission in your device settings.',
          cancelText: 'Cancel',
          confirmText: 'Open Settings',
        );

        if (shouldGoToSettings == true) {
          await _openAppSettings();
        }
        return;
      }

      // iOS (for permissions other than Media Library) - handle based on permission status
      if (status == PermissionStatus.granted) {
        // Already granted
        _updateResult(
            'requestPermission', 'Permission $permissionId is already granted');

        final shouldGoToSettings = await DialogService.showAlertDialog<bool>(
          title: 'Permission Granted',
          message:
              'This permission is already granted. Would you like to go to settings anyway?',
          cancelText: 'No',
          confirmText: 'Yes',
        );

        if (shouldGoToSettings == true) {
          await _openAppSettings();
        }
      } else if (status == PermissionStatus.notDetermined ||
          status == PermissionStatus.denied) {
        // Request permission
        _updateResult('requestPermission',
            'Requesting system permission: $permissionId (status: $status)');

        PermissionStatus result =
            await PermissionService.requestPermission(permissionId);
        developer.log("Permission request result: $result");

        if (result == PermissionStatus.granted) {
          _updateResult(
              'requestPermission', 'Permission $permissionId was granted');
        } else {
          _updateResult('requestPermission',
              'Permission $permissionId was not granted (result: $result)');

          // If permanently denied, offer to go to settings
          if (result == PermissionStatus.permanentlyDenied ||
              result == PermissionStatus.restricted) {
            final shouldGoToSettings =
                await DialogService.showAlertDialog<bool>(
              title: 'Permission Required',
              message:
                  'This permission was denied. You need to enable it manually in Settings.',
              cancelText: 'Cancel',
              confirmText: 'Open Settings',
            );

            if (shouldGoToSettings == true) {
              await _openAppSettings();
            }
          }
        }
      } else if (status == PermissionStatus.permanentlyDenied ||
          status == PermissionStatus.restricted) {
        // Need to go to settings
        _updateResult('requestPermission',
            'Permission $permissionId needs to be enabled in settings');

        final shouldGoToSettings = await DialogService.showAlertDialog<bool>(
          title: 'Permission Required',
          message: 'This permission needs to be enabled manually in Settings.',
          cancelText: 'Cancel',
          confirmText: 'Open Settings',
        );

        if (shouldGoToSettings == true) {
          await _openAppSettings();
        }
      }
    } catch (e) {
      _updateResult('requestPermission', 'Error in permission request: $e');
    }
  }

  // Open app settings page
  Future<void> _openAppSettings() async {
    try {
      if (Platform.isAndroid) {
        // For Android, use AppSettings.openAppSettings() without type parameter
        await AppSettings.openAppSettings();
      } else {
        // For iOS, try our custom implementation first
        bool opened = await PermissionService.openAppSettings();

        // Fall back to app_settings plugin if needed
        if (!opened) {
          await AppSettings.openAppSettings();
        }
      }

      _updateResult('openAppSettings', 'Opened app settings');
    } catch (e) {
      _updateResult('openAppSettings', 'Error opening app settings: $e');

      // If something goes wrong, try the most direct approach as a last resort
      if (Platform.isAndroid) {
        try {
          // Try our direct permission channel as a fallback
          await PermissionService.openAppSettings();
        } catch (fallbackError) {
          developer
              .log("Fallback settings approach also failed: $fallbackError");
        }
      }
    }
  }

  Future<void> _getBannerConfig() async {
    try {
      final config = await _consentSdkPlugin.getBannerConfig();
      developer.log("getBannerConfig result type: ${config.runtimeType}");

      if (config == null) {
        _updateResult('getBannerConfig', 'null - No banner config available');
        return;
      }

      // Use the complete BannerConfig model with toJson
      _updateResult('getBannerConfig', config);
    } catch (e) {
      _updateResult('getBannerConfig', 'Error: $e');
    }
  }

  Future<void> _getConsentByPurposeId() async {
    try {
      // First, get the list of purposes to find a valid purposeId
      final purposes = await _consentSdkPlugin.getPurposes();
      if (purposes.isEmpty) {
        _updateResult(
            'getConsentByPurposeId', 'No purposes available to check consent');
        return;
      }

      // Get the first purpose ID
      final purposeId = purposes.first.purposeId;
      final purposeName = purposes.first.purposeName?['en'] ?? 'Unknown';
      developer.log("Getting consent for purpose ID: $purposeId");

      final status = await _consentSdkPlugin.getConsent(purposeId ?? 1);
      developer.log("getConsentByPurposeId result type: ${status.runtimeType}");

      // Format result with full purpose details
      final result = {
        'purpose_id': purposeId,
        'purpose_name': purposeName,
        'consent_status': status.value,
        'enum_value': status.toString(),
        'purpose_details': purposes.first.toJson(),
      };

      _updateResult('getConsentByPurposeId', result);
    } catch (e) {
      _updateResult('getConsentByPurposeId', 'Error: $e');
    }
  }

  Future<void> _getConsentByPermissionId(String permissionId) async {
    try {
      // If we have a specific permissionId, use it directly
      if (permissionId.isNotEmpty) {
        final status = await _consentSdkPlugin.getConsent(permissionId);

        // Find the permission in our list to get more details if possible
        AppPermission? permission;
        try {
          permission =
              _appPermissions.firstWhere((p) => p.permissionId == permissionId);
        } catch (e) {
          permission = null;
        }

        final permissionName = permission?.name ?? 'Unknown';

        // Format result with permission details
        final result = {
          'permission_id': permissionId,
          'permission_name': permissionName,
          'consent_status': status.value,
          'enum_value': status.toString(),
        };

        _updateResult('getConsentByPermissionId', result);
        return;
      }

      // If no specific permission was provided, use the first one from the list
      if (_appPermissions.isNotEmpty) {
        final permission = _appPermissions.first;
        final permissionId = permission.permissionId;

        if (permissionId != null) {
          final status = await _consentSdkPlugin.getConsent(permissionId);

          // Format result with permission details
          final result = {
            'permission_id': permissionId,
            'permission_name': permission.name ?? 'Unknown',
            'consent_status': status.value,
            'enum_value': status.toString(),
            'permission_details': permission.toJson(),
          };

          _updateResult('getConsentByPermissionId', result);
          return;
        }
      }

      // If we get here, we need to fetch the permissions first
      final permissions = await _consentSdkPlugin.getPermissions();
      if (permissions.isEmpty) {
        _updateResult('getConsentByPermissionId',
            'No permissions available to check consent');
        return;
      }

      // Get the first permission ID or use a standard permission
      final permission = permissions.first;
      final id = permission.permissionId ??
          (Platform.isIOS
              ? "NSCameraUsageDescription"
              : "android.permission.CAMERA");
      final permissionName = permission.name ?? 'Unknown';

      final status = await _consentSdkPlugin.getConsent(id);

      // Format result with permission details
      final result = {
        'permission_id': id,
        'permission_name': permissionName,
        'consent_status': status.value,
        'enum_value': status.toString(),
        'permission_details': permission.toJson(),
      };

      _updateResult('getConsentByPermissionId', result);
    } catch (e) {
      _updateResult('getConsentByPermissionId', 'Error: $e');
    }
  }

  Future<void> _getPermissions() async {
    try {
      final permissions = await _consentSdkPlugin.getPermissions();

      // Update the cached permissions list
      setState(() {
        _appPermissions = permissions;
      });

      // Pass the full permissions list directly
      _updateResult('getPermissions', permissions);
    } catch (e) {
      _updateResult('getPermissions', 'Error: $e');
    }
  }

  Future<void> _getPurposes() async {
    try {
      final purposes = await _consentSdkPlugin.getPurposes();
      developer.log(
          "getPurposes result type: ${purposes.runtimeType}, isEmpty: ${purposes.isEmpty}");
      if (!purposes.isEmpty) {
        developer.log("First purpose type: ${purposes.first.runtimeType}");
      }

      // Pass the full purposes list directly
      _updateResult('getPurposes', purposes);
    } catch (e) {
      _updateResult('getPurposes', 'Error: $e');
    }
  }

  Future<void> _getSdksInPurpose() async {
    try {
      // First, get purposes to find a valid ID
      final purposes = await _consentSdkPlugin.getPurposes();
      if (purposes.isEmpty) {
        _updateResult(
            'getSdksInPurpose', 'No purposes available to check SDKs');
        return;
      }

      // Get the first purpose ID or use default 1
      final purposeId = purposes.first.purposeId ?? 1;
      developer.log("Getting SDKs for purpose ID: $purposeId");

      final sdks = await _consentSdkPlugin.getSdksInPurpose(purposeId);

      // Format the result with full SDK details
      final formattedResult = {
        'purpose_id': purposeId,
        'purpose_name': purposes.first.purposeName?['en'] ?? 'Unknown',
        'sdks_count': sdks.length,
        'sdks': sdks,
      };

      _updateResult('getSdksInPurpose', formattedResult);
    } catch (e) {
      _updateResult('getSdksInPurpose', 'Error: $e');
    }
  }

  Future<void> _getSettingsPrompt() async {
    try {
      final settingsPrompt = await _consentSdkPlugin.getSettingsPrompt();

      if (settingsPrompt == null) {
        _updateResult(
            'getSettingsPrompt', 'null - No settings prompt available');
        return;
      }

      // Settings prompt will be automatically formatted through toJson
      _updateResult('getSettingsPrompt', settingsPrompt);
    } catch (e) {
      _updateResult('getSettingsPrompt', 'Error: $e');
    }
  }

  Future<void> _setConsentForPurpose() async {
    try {
      // First, get the list of purposes to find a valid purposeId
      final purposes = await _consentSdkPlugin.getPurposes();
      if (purposes.isEmpty) {
        _updateResult(
            'setConsentForPurpose', 'No purposes available to set consent');
        return;
      }

      // Get the first purpose ID
      final purposeId = purposes.first.purposeId;
      developer.log("Setting consent for purpose ID: $purposeId");

      await _consentSdkPlugin.setConsent(purposeId ?? 1, ConsentStatus.granted);
      _updateResult('setConsentForPurpose',
          'Consent set to GRANTED for purpose ID: $purposeId');
    } catch (e) {
      _updateResult('setConsentForPurpose', 'Error: $e');
    }
  }

  Future<void> _setConsentForPermission(String permissionId) async {
    try {
      // Set consent for the permission in the SDK
      await _consentSdkPlugin.setConsent(permissionId, ConsentStatus.granted);
      _updateResult('setConsentForPermission',
          'Consent set to GRANTED for permission ID: $permissionId');

      // Request the actual OS permission
      await _requestPermission(permissionId);
    } catch (e) {
      _updateResult('setConsentForPermission', 'Error: $e');
    }
  }

  Future<void> _resetConsents() async {
    try {
      await _consentSdkPlugin.resetConsents();
      _updateResult('resetConsents', 'Consents reset successfully');
    } catch (e) {
      _updateResult('resetConsents', 'Error: $e');
    }
  }

  Future<void> _getAllConsents() async {
    try {
      // Get all purposes and their consent statuses
      final purposes = await _consentSdkPlugin.getPurposes();

      // Collect full purpose data with consent status
      List<Map<String, dynamic>> purposeData = [];
      // Limit to first 5 for performance
      int count = purposes.length > 5 ? 5 : purposes.length;

      for (var i = 0; i < count; i++) {
        var purpose = purposes[i];
        if (purpose.purposeId != null) {
          final consent =
              await _consentSdkPlugin.getConsent(purpose.purposeId!);
          final purposeName = purpose.purposeName?['en'] ?? 'Unknown';

          purposeData.add({
            'purpose_id': purpose.purposeId,
            'name': purposeName,
            'consent_status': consent.value,
            'purpose_data': purpose.toJson() // Include full purpose data
          });
        }
      }

      // Get all permissions and their consent statuses
      final permissions = await _consentSdkPlugin.getPermissions();
      List<Map<String, dynamic>> permissionData = [];

      // Limit to first 5 for performance
      count = permissions.length > 5 ? 5 : permissions.length;

      for (var i = 0; i < count; i++) {
        var permission = permissions[i];
        if (permission.permissionId != null) {
          final consent =
              await _consentSdkPlugin.getConsent(permission.permissionId!);
          final permissionName = permission.name ?? 'Unknown';

          permissionData.add({
            'permission_id': permission.permissionId,
            'name': permissionName,
            'consent_status': consent.value,
            'permission_data':
                permission.toJson() // Include full permission data
          });
        }
      }

      // Format result as JSON with all available details
      final result = {
        'total_purposes': purposes.length,
        'total_permissions': permissions.length,
        'purposes': purposeData,
        'permissions': permissionData,
      };

      _updateResult('getAllConsents', result);
    } catch (e) {
      _updateResult('getAllConsents', 'Error: $e');
    }
  }

  Future<void> _getGCMConsents() async {
    try {
      developer.log('Manually fetching GCM consents...');

      // Get GCM consents from Securiti SDK
      Map<GoogleConsentType, ConsentStatus> gcmConsents =
          await _consentSdkPlugin.getGCMConsents();

      // Format the result for display
      Map<String, dynamic> formattedResult = {
        'total_gcm_consents': gcmConsents.length,
        'consents': {},
      };

      gcmConsents.forEach((type, status) {
        formattedResult['consents'][type.value] = {
          'consent_status': status.value,
          'firebase_value': status == ConsentStatus.granted ? 'GRANTED' : 'DENIED',
        };
      });

      _updateResult('getGCMConsents', formattedResult);

      // Log individual consent statuses
      developer.log('Google Consent Mode statuses:');
      gcmConsents.forEach((type, status) {
        developer.log('  ${type.value}: ${status.value}');
      });
    } catch (e) {
      developer.log('Error fetching GCM consents: $e');
      _updateResult('getGCMConsents', 'Error: $e');
    }
  }

  Future<void> _getGCMConfig() async {
    try {
      developer.log('Fetching GCM config...');

      // Get GCM config from Securiti SDK
      GcmConfig? gcmConfig = await _consentSdkPlugin.getGCMConfig();

      if (gcmConfig == null) {
        _updateResult('getGCMConfig', 'null - No GCM config available');
        return;
      }

      // Format the result for display
      Map<String, dynamic> formattedResult = {
        'show_gcm_description': gcmConfig.showGcmDescription,
        'gcm_description': gcmConfig.gcmDescription,
      };

      // Add default mapping if available
      if (gcmConfig.defaultMapping != null) {
        formattedResult['default_mapping'] = {
          'analytics_storage': gcmConfig.defaultMapping!.analyticsStorage,
          'ad_storage': gcmConfig.defaultMapping!.adStorage,
          'ad_user_data': gcmConfig.defaultMapping!.adUserData,
          'ad_personalization': gcmConfig.defaultMapping!.adPersonalization,
        };
      }

      // Add region overrides if available
      if (gcmConfig.regionOverrides != null && gcmConfig.regionOverrides!.isNotEmpty) {
        Map<String, dynamic> regionOverridesFormatted = {};
        gcmConfig.regionOverrides!.forEach((region, mapping) {
          regionOverridesFormatted[region] = {
            'analytics_storage': mapping.analyticsStorage,
            'ad_storage': mapping.adStorage,
            'ad_user_data': mapping.adUserData,
            'ad_personalization': mapping.adPersonalization,
          };
        });
        formattedResult['region_overrides'] = regionOverridesFormatted;
      }

      _updateResult('getGCMConfig', formattedResult);
      developer.log('GCM config fetched successfully');
    } catch (e) {
      developer.log('Error fetching GCM config: $e');
      _updateResult('getGCMConfig', 'Error: $e');
    }
  }

  Widget _buildMethodButton(String label, VoidCallback onPressed) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 5),
      child: ElevatedButton(
        onPressed: _isSDKReady ? onPressed : null,
        child: Text(label),
      ),
    );
  }

  // Build permission list UI from AppPermissions
  Widget _buildPermissionsList() {
    if (_appPermissions.isEmpty) {
      return const Center(
        child: Text('No permissions available yet. SDK may still be loading.'),
      );
    }

    return ListView.builder(
      shrinkWrap: true,
      physics: const NeverScrollableScrollPhysics(),
      itemCount: _appPermissions.length,
      itemBuilder: (context, index) {
        final permission = _appPermissions[index];
        final permissionId = permission.permissionId ?? 'Unknown';
        final permissionName = permission.name ?? 'Unnamed Permission';

        // Extract description for the current language or use English as fallback
        String? description;
        if (permission.description != null) {
          description = permission.description!['en'] ?? // Try English first
              permission.description!.values.first; // Fallback to any language
        }

        return Card(
          margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
          child: ExpansionTile(
            title: Text(permissionName),
            subtitle: Text('ID: $permissionId'),
            children: [
              if (description != null)
                Padding(
                  padding:
                      const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
                  child: Text('Description: $description'),
                ),
              Padding(
                padding: const EdgeInsets.all(8.0),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: [
                    ElevatedButton(
                      onPressed: _isSDKReady
                          ? () => _getConsentByPermissionId(permissionId)
                          : null,
                      child: const Text('Check Consent'),
                    ),
                    ElevatedButton(
                      onPressed: _isSDKReady
                          ? () => _requestPermission(permissionId)
                          : null,
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.green,
                        foregroundColor: Colors.white,
                      ),
                      child: const Text('Request Permission'),
                    ),
                  ],
                ),
              ),
            ],
          ),
        );
      },
    );
  }

  Widget _buildSDKMethodsList() {
    return Expanded(
      child: SingleChildScrollView(
        controller: _scrollController,
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              // UI Methods
              const Padding(
                padding: EdgeInsets.only(top: 8, bottom: 8),
                child: Text('UI Methods',
                    style: TextStyle(fontWeight: FontWeight.bold)),
              ),
              _buildMethodButton('Present Consent Banner', () {
                _consentSdkPlugin.presentConsentBanner();
                _updateResult('presentConsentBanner', 'Banner presented');
              }),
              _buildMethodButton('Open Preference Center', () {
                _consentSdkPlugin.presentPreferenceCenter();
                _updateResult(
                    'presentPreferenceCenter', 'Preference center opened');
              }),

              // Permissions section (dynamically built from SDK data)
              const Padding(
                padding: EdgeInsets.only(top: 16, bottom: 8),
                child: Text('App Permissions',
                    style: TextStyle(fontWeight: FontWeight.bold)),
              ),
              _buildMethodButton('Refresh Permissions List', _getPermissions),
              _buildMethodButton('Open App Settings', _openAppSettings),
              _buildPermissionsList(),

              // Get Methods
              const Padding(
                padding: EdgeInsets.only(top: 16, bottom: 8),
                child: Text('Get Data Methods',
                    style: TextStyle(fontWeight: FontWeight.bold)),
              ),
              _buildMethodButton('Get Banner Config', _getBannerConfig),
              _buildMethodButton('Get Purposes', _getPurposes),
              _buildMethodButton('Get SDKs in Purpose', _getSdksInPurpose),
              _buildMethodButton('Get Settings Prompt', _getSettingsPrompt),

              // Consent Management Methods
              const Padding(
                padding: EdgeInsets.only(top: 16, bottom: 8),
                child: Text('Consent Management',
                    style: TextStyle(fontWeight: FontWeight.bold)),
              ),
              _buildMethodButton(
                  'Get Consent by Purpose ID', _getConsentByPurposeId),
              _buildMethodButton('Get All Consents', _getAllConsents),
              _buildMethodButton(
                  'Set Consent for Purpose', _setConsentForPurpose),
              _buildMethodButton('Reset All Consents', _resetConsents),

              // Google Consent Mode Methods
              const Padding(
                padding: EdgeInsets.only(top: 16, bottom: 8),
                child: Text('Google Consent Mode',
                    style: TextStyle(fontWeight: FontWeight.bold)),
              ),
              _buildMethodButton('Get GCM Consents', _getGCMConsents),
              _buildMethodButton('Get GCM Config', _getGCMConfig),
            ],
          ),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorKey: navigatorKey, // Use the global navigator key for dialogs
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Consent SDK Example'),
        ),
        body: Column(
          children: [
            Padding(
              padding: const EdgeInsets.all(16.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    'Securiti\'s SDK is ${_isSDKReady ? "ready" : "not ready"}',
                    style: TextStyle(
                      fontSize: 18,
                      fontWeight: FontWeight.bold,
                      color: _isSDKReady ? Colors.green : Colors.red,
                    ),
                  ),
                  const SizedBox(height: 10),
                  Text('Platform: $_platformVersion'),
                  Text('Permissions loaded: ${_appPermissions.length}'),
                  const Divider(),
                ],
              ),
            ),

            // List of SDK methods
            _buildSDKMethodsList(),

            // Results display with performance optimizations
            Container(
              height: 300, // Double the height to show more content
              margin: const EdgeInsets.all(16.0),
              padding: const EdgeInsets.all(8.0),
              decoration: BoxDecoration(
                border: Border.all(color: Colors.grey),
                borderRadius: BorderRadius.circular(8),
              ),
              // Use a ListView builder for optimal performance with large text
              child: Scrollbar(
                child: ListView.builder(
                  itemCount: 1,
                  itemBuilder: (context, index) {
                    return SelectableText(
                      _latestResult,
                      style: const TextStyle(
                        fontFamily: 'monospace',
                        fontSize: 11.0, // Smaller font for better performance
                        height: 1.2, // Tighter line height
                      ),
                    );
                  },
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
1
likes
145
points
878
downloads

Publisher

verified publishersecuriti.ai

Weekly Downloads

A Flutter plugin for managing user consent preferences and compliance with privacy regulations. Integrates with Securiti's Consent Management Platform.

Repository
View/report issues

Documentation

Documentation
API reference

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on securiti_consent_sdk

Packages that implement securiti_consent_sdk