From b5083c9d86c6be7ac551e61c7f50c2619de3ff9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Novotn=C3=BD?= <62177414+yardexx@users.noreply.github.com> Date: Tue, 30 Dec 2025 15:39:58 +0100 Subject: [PATCH 01/15] Refactor example app: Modernize UI and architecture --- .gemini/settings.json | 10 + .gitignore | 5 +- example/README.md | 155 ++++++++++++++- example/android/app/build.gradle | 2 +- example/lib/app/app.dart | 31 +++ example/lib/config/talsec_config.dart | 28 +++ example/lib/extensions.dart | 17 -- example/lib/main.dart | 154 ++------------- example/lib/models/security_check.dart | 47 +++++ example/lib/models/security_state.dart | 52 +++++ example/lib/models/setting_item.dart | 25 +++ .../lib/providers/external_id_provider.dart | 25 +++ .../screen_capture_notifier.dart | 0 .../providers/screen_capture_provider.dart | 9 + .../lib/providers/security_controller.dart | 184 ++++++++++++++++++ .../security_controller_provider.dart | 10 + example/lib/screens/security_screen.dart | 74 +++++++ .../lib/screens/suspicious_apps_screen.dart | 94 +++++++++ example/lib/theme/app_theme.dart | 56 ++++++ example/lib/threat_notifier.dart | 46 ----- example/lib/threat_state.dart | 27 --- example/lib/widgets/category_section.dart | 43 ++++ example/lib/widgets/list_item_card.dart | 43 ++++ example/lib/widgets/malware_alert_card.dart | 83 ++++++++ example/lib/widgets/malware_bottom_sheet.dart | 84 -------- example/lib/widgets/safety_icon.dart | 17 -- example/lib/widgets/section.dart | 34 ++++ example/lib/widgets/security_status_card.dart | 41 ++++ example/lib/widgets/settings_section.dart | 149 ++++++++++++++ example/lib/widgets/threat_listview.dart | 35 ---- example/lib/widgets/widgets.dart | 3 - example/pubspec.yaml | 3 + .../freerasp.build/EventProcessor.swiftdeps~ | Bin 0 -> 540 bytes .../SwiftFreeraspPlugin.swiftdeps~ | Bin 0 -> 548 bytes .../freerasp.build/TalsecHandlers.swiftdeps~ | Bin 0 -> 540 bytes 35 files changed, 1205 insertions(+), 381 deletions(-) create mode 100644 .gemini/settings.json create mode 100644 example/lib/app/app.dart create mode 100644 example/lib/config/talsec_config.dart delete mode 100644 example/lib/extensions.dart create mode 100644 example/lib/models/security_check.dart create mode 100644 example/lib/models/security_state.dart create mode 100644 example/lib/models/setting_item.dart create mode 100644 example/lib/providers/external_id_provider.dart rename example/lib/{ => providers}/screen_capture_notifier.dart (100%) create mode 100644 example/lib/providers/screen_capture_provider.dart create mode 100644 example/lib/providers/security_controller.dart create mode 100644 example/lib/providers/security_controller_provider.dart create mode 100644 example/lib/screens/security_screen.dart create mode 100644 example/lib/screens/suspicious_apps_screen.dart create mode 100644 example/lib/theme/app_theme.dart delete mode 100644 example/lib/threat_notifier.dart delete mode 100644 example/lib/threat_state.dart create mode 100644 example/lib/widgets/category_section.dart create mode 100644 example/lib/widgets/list_item_card.dart create mode 100644 example/lib/widgets/malware_alert_card.dart delete mode 100644 example/lib/widgets/malware_bottom_sheet.dart delete mode 100644 example/lib/widgets/safety_icon.dart create mode 100644 example/lib/widgets/section.dart create mode 100644 example/lib/widgets/security_status_card.dart create mode 100644 example/lib/widgets/settings_section.dart delete mode 100644 example/lib/widgets/threat_listview.dart delete mode 100644 example/lib/widgets/widgets.dart create mode 100644 ios/freerasp/.index-build/arm64-apple-macosx/debug/freerasp.build/EventProcessor.swiftdeps~ create mode 100644 ios/freerasp/.index-build/arm64-apple-macosx/debug/freerasp.build/SwiftFreeraspPlugin.swiftdeps~ create mode 100644 ios/freerasp/.index-build/arm64-apple-macosx/debug/freerasp.build/TalsecHandlers.swiftdeps~ diff --git a/.gemini/settings.json b/.gemini/settings.json new file mode 100644 index 0000000..3ae6a87 --- /dev/null +++ b/.gemini/settings.json @@ -0,0 +1,10 @@ +{ + "mcpServers": { + "dart": { + "command": "dart", + "args": [ + "mcp-server" + ] + } + } +} diff --git a/.gitignore b/.gitignore index 5d21bbc..2b2eed6 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,7 @@ build/ example/pubspec.lock # FVM Version Cache -.fvm/ \ No newline at end of file +.fvm/ + +# AI stuff +.gemini \ No newline at end of file diff --git a/example/README.md b/example/README.md index 4c71df7..33ee5bd 100644 --- a/example/README.md +++ b/example/README.md @@ -1,16 +1,153 @@ -# freerasp_example +# freeRASP Example App -Demonstrates how to use the freerasp plugin. +This example application demonstrates how to use the freeRASP plugin to protect your Flutter application from various security threats. + +## Features + +The example app showcases the following freeRASP capabilities: + +### Security Status Monitoring +- Real-time security status display showing overall device security +- Visual indicators for secure and compromised states +- Categorized threat detection (App Integrity, Device Security, Runtime Status) + +### Threat Detection +The app monitors and displays various security threats including: +- **App Integrity**: App signature verification, obfuscation, unofficial stores, simulator detection, device binding, multi-instance detection +- **Device Security**: Root/jailbreak detection, hooks, secure hardware availability, developer mode, debugger attachment, passcode protection, ADB status, VPN detection +- **Runtime Status**: Screenshot and screen recording detection + +### Screen Capture Blocking +- Toggle to enable/disable screen capture blocking +- Prevents screenshots and screen recordings when enabled +- Real-time status indicator + +### External ID Management +- Set a custom external ID to enrich security logs +- Useful for uniquely identifying devices in your backend +- Stored via `Talsec.instance.storeExternalId()` + +### Malware Detection +- Automatic detection of suspicious apps +- Alert cards showing detected malware +- Detailed view of suspicious applications with app icons and reasons + +## Project Structure + +``` +lib/ +├── app/ +│ └── app.dart # Main app widget with theme configuration +├── config/ +│ └── talsec_config.dart # Shared Talsec configuration +├── main.dart # App entry point with Talsec initialization +├── models/ +│ ├── security_check.dart # Security check model +│ ├── security_state.dart # Security state management +│ └── setting_item.dart # Settings item model +├── providers/ +│ ├── external_id_provider.dart # External ID state management +│ ├── screen_capture_notifier.dart # Screen capture blocking logic +│ ├── screen_capture_provider.dart # Screen capture provider +│ ├── security_controller.dart # Main security monitoring controller +│ └── security_controller_provider.dart # Security controller provider +├── screens/ +│ ├── security_screen.dart # Main security dashboard +│ └── suspicious_apps_screen.dart # Suspicious apps detail view +├── theme/ +│ └── app_theme.dart # App theme configuration +└── widgets/ + ├── category_section.dart # Threat category display + ├── list_item_card.dart # Reusable list item widget + ├── malware_alert_card.dart # Malware detection alert + ├── section.dart # Section wrapper widget + ├── security_status_card.dart # Security status display + └── settings_section.dart # Settings section widget +``` ## Getting Started -This project is a starting point for a Flutter application. +### Prerequisites + +- Flutter SDK (>=3.0.0) +- Dart SDK (>=3.0.0) + +### Setup + +1. Clone the repository and navigate to the example directory: + ```bash + cd example + ``` + +2. Install dependencies: + ```bash + flutter pub get + ``` + +3. Configure Talsec: + - Open `lib/config/talsec_config.dart` + - Update the configuration with your app's details: + - Android package name + - Signing certificate hashes + - Supported app stores + - iOS bundle IDs and team ID + - Watcher email address + +4. Run the app: + ```bash + flutter run + ``` + +## Configuration + +The Talsec configuration is centralized in `lib/config/talsec_config.dart`. You need to customize: + +- **Android Config**: + - `packageName`: Your app's package name + - `signingCertHashes`: SHA-256 hashes of your signing certificates + - `supportedStores`: List of supported app stores + - `malwareConfig`: Malware detection settings + +- **iOS Config**: + - `bundleIds`: Your app's bundle identifiers + - `teamId`: Your Apple Developer team ID + +- **General**: + - `watcherMail`: Email for security notifications + - `isProd`: Set to `true` for production, `false` for development + +## Usage Examples + +### Monitoring Security Threats + +The app automatically monitors threats through the `SecurityController`. Threats are detected via: +- Stream subscription to `Talsec.instance.onThreatDetected` +- Threat callback listeners for malware detection + +### Blocking Screen Capture + +```dart +// Toggle screen capture blocking +await ref.read(screenCaptureProvider.notifier).toggle(); +``` + +### Setting External ID + +```dart +// Set external ID for log enrichment +await ref.read(externalIdProvider.notifier).setExternalId('device-123'); +``` + +## Architecture -A few resources to get you started if this is your first Flutter project: +The app uses: +- **Riverpod** for state management +- **Material Design 3** with dynamic color theming +- **Provider pattern** for security monitoring +- **Reactive UI** that updates automatically when threats are detected -- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) +## Resources -For help getting started with Flutter, view our -[online documentation](https://flutter.dev/docs), which offers tutorials, -samples, guidance on mobile development, and a full API reference. +- [freeRASP Documentation](https://github.com/talsec/freerasp) +- [Flutter Documentation](https://flutter.dev/docs) +- [Riverpod Documentation](https://riverpod.dev) diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 3363e13..674e5e9 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -44,7 +44,7 @@ android { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.aheaditec.freerasp_example" // Talsec library needs higher version than default (16) - minSdkVersion 23 + minSdkVersion flutter.minSdkVersion targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/example/lib/app/app.dart b/example/lib/app/app.dart new file mode 100644 index 0000000..f56f469 --- /dev/null +++ b/example/lib/app/app.dart @@ -0,0 +1,31 @@ +// ignore_for_file: public_member_api_docs +import 'package:dynamic_color/dynamic_color.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:freerasp_example/screens/security_screen.dart'; +import 'package:freerasp_example/theme/app_theme.dart'; + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return ProviderScope( + child: DynamicColorBuilder( + builder: (lightDynamic, darkDynamic) { + final lightScheme = AppTheme.getScheme(lightDynamic, Brightness.light); + final darkScheme = AppTheme.getScheme(darkDynamic, Brightness.dark); + + return MaterialApp( + restorationScopeId: 'root', + title: 'My Secure App', + theme: AppTheme.create(lightScheme), + darkTheme: AppTheme.create(darkScheme), + home: const SecurityScreen(), + ); + }, + ), + ); + } +} + diff --git a/example/lib/config/talsec_config.dart b/example/lib/config/talsec_config.dart new file mode 100644 index 0000000..d2b8285 --- /dev/null +++ b/example/lib/config/talsec_config.dart @@ -0,0 +1,28 @@ +import 'package:freerasp/freerasp.dart'; + +/// Creates and returns the Talsec configuration for the example app. +/// +/// This configuration is used to initialize Talsec with Android and iOS settings. +TalsecConfig createTalsecConfig() { + return TalsecConfig( + androidConfig: AndroidConfig( + packageName: 'com.aheaditec.freeraspExample', + signingCertHashes: ['AKoRuyLMM91E7lX/Zqp3u4jMmd0A7hH/Iqozu0TMVd0='], + supportedStores: ['com.sec.android.app.samsungapps'], + malwareConfig: MalwareConfig( + blacklistedPackageNames: ['com.aheaditec.freeraspExample'], + suspiciousPermissions: [ + ['android.permission.CAMERA'], + ['android.permission.READ_SMS', 'android.permission.READ_CONTACTS'], + ], + ), + ), + iosConfig: IOSConfig( + bundleIds: ['com.aheaditec.freeraspExample'], + teamId: 'M8AK35...', + ), + watcherMail: 'your_mail@example.com', + isProd: true, + ); +} + diff --git a/example/lib/extensions.dart b/example/lib/extensions.dart deleted file mode 100644 index 0636a49..0000000 --- a/example/lib/extensions.dart +++ /dev/null @@ -1,17 +0,0 @@ -/// Extensions for the `String` class. -extension StringX on String { - /// Converts the first character of the string to uppercase. - /// - /// If the string is empty, returns an empty string. - /// - /// If the string has only one character, returns the uppercase version of - /// the character. - /// - /// Otherwise, returns the string with the first character converted to - /// uppercase. - String capitalize() { - if (isEmpty) return ''; - if (length == 1) return toUpperCase(); - return this[0].toUpperCase() + substring(1); - } -} diff --git a/example/lib/main.dart b/example/lib/main.dart index d143f20..5a05942 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,150 +1,22 @@ -// ignore_for_file: public_member_api_docs, avoid_redundant_argument_values - import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:freerasp/freerasp.dart'; -import 'package:freerasp_example/screen_capture_notifier.dart'; -import 'package:freerasp_example/threat_notifier.dart'; -import 'package:freerasp_example/threat_state.dart'; -import 'package:freerasp_example/widgets/widgets.dart'; - -/// Represents current state of the threats detectable by freeRASP -final threatProvider = - NotifierProvider.autoDispose(() { - return ThreatNotifier(); -}); - -final screenCaptureProvider = - AsyncNotifierProvider.autoDispose(() { - return ScreenCaptureNotifier(); -}); +import 'package:freerasp_example/app/app.dart'; +import 'package:freerasp_example/config/talsec_config.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); - /// Initialize Talsec config - await _initializeTalsec(); - - runApp(const ProviderScope(child: App())); -} - -/// Initialize Talsec configuration for Android and iOS -Future _initializeTalsec() async { - final config = TalsecConfig( - androidConfig: AndroidConfig( - packageName: 'com.aheaditec.freeraspExample', - signingCertHashes: ['AKoRuyLMM91E7lX/Zqp3u4jMmd0A7hH/Iqozu0TMVd0='], - supportedStores: ['com.sec.android.app.samsungapps'], - malwareConfig: MalwareConfig( - blacklistedPackageNames: ['com.aheaditec.freeraspExample'], - suspiciousPermissions: [ - ['android.permission.CAMERA'], - ['android.permission.READ_SMS', 'android.permission.READ_CONTACTS'], - ], - ), - ), - iosConfig: IOSConfig( - bundleIds: ['com.aheaditec.freeraspExample'], - teamId: 'M8AK35...', - ), - watcherMail: 'your_mail@example.com', - isProd: true, - ); - - await Talsec.instance.start(config); -} - -/// Example of how to use [Talsec.storeExternalId]. -Future testStoreExternalId(String data) async { - await Talsec.instance.storeExternalId(data); -} - -/// The root widget of the application -class App extends StatelessWidget { - const App({super.key}); - - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData(primarySwatch: Colors.blue), - home: const HomePage(), - ); + // Initialize Talsec configuration + try { + final config = createTalsecConfig(); + await Talsec.instance.start(config); + } catch (e) { + // Log error but allow app to continue + // In production, you might want to use a logging service or show an error dialog + debugPrint('Error initializing Talsec: $e'); + // Re-throw if you want the app to fail fast on initialization errors + // rethrow; } -} - -/// The home page that displays the threats and results -class HomePage extends ConsumerWidget { - const HomePage({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final threatState = ref.watch(threatProvider); - - // Listen for changes in the threatProvider and show the malware modal - ref.listen(threatProvider, (prev, next) { - if (prev?.detectedMalware != next.detectedMalware) { - _showMalwareBottomSheet(context, next.detectedMalware); - } - }); - - return Scaffold( - appBar: AppBar(title: const Text('freeRASP Demo')), - body: SafeArea( - child: Padding( - padding: const EdgeInsets.all(8), - child: Column( - children: [ - Text( - 'Threat Status', - style: Theme.of(context).textTheme.titleMedium, - ), - const SizedBox(height: 8), - ListTile( - title: const Text('Store External ID'), - trailing: IconButton( - icon: const Icon(Icons.refresh), - onPressed: () { - testStoreExternalId('testData'); - }, - ), - ), - ListTile( - title: const Text('Change Screen Capture'), - leading: SafetyIcon( - isDetected: !(ref.watch(screenCaptureProvider).value ?? true), - ), - trailing: IconButton( - icon: const Icon(Icons.refresh), - onPressed: () { - ref.read(screenCaptureProvider.notifier).toggle(); - }, - ), - ), - Expanded( - child: ThreatListView(threats: threatState.detectedThreats), - ), - ], - ), - ), - ), - ); - } -} -/// Extension method to show the malware bottom sheet -void _showMalwareBottomSheet( - BuildContext context, - List suspiciousApps, -) { - WidgetsBinding.instance.addPostFrameCallback((_) { - showModalBottomSheet( - context: context, - isDismissible: false, - enableDrag: false, - builder: (BuildContext context) => MalwareBottomSheet( - suspiciousApps: suspiciousApps, - ), - ); - }); + runApp(const MyApp()); } diff --git a/example/lib/models/security_check.dart b/example/lib/models/security_check.dart new file mode 100644 index 0000000..4003495 --- /dev/null +++ b/example/lib/models/security_check.dart @@ -0,0 +1,47 @@ +import 'package:freerasp/freerasp.dart'; + +/// Categories of threats. +enum ThreatCategory { + /// Threats related to the application's integrity and source. + appIntegrity, + + /// Threats related to the device's environment and security settings. + deviceSecurity, + + /// Threats related to user actions or runtime events. + runtimeStatus, +} + +/// Represents a security check with metadata. +class SecurityCheck { + /// Creates a [SecurityCheck]. + SecurityCheck({ + required this.threat, + required this.name, + required this.secureDescription, + required this.insecureDescription, + required this.category, + this.isSecure = true, + }); + + /// The specific threat being checked. + final Threat threat; + + /// Human-readable name of the threat. + final String name; + + /// Description shown when the check is secure. + final String secureDescription; + + /// Description shown when the check is insecure. + final String insecureDescription; + + /// The category this threat belongs to. + final ThreatCategory category; + + /// Whether the check is currently passing (secure). + bool isSecure; + + /// Returns the appropriate description based on security status. + String get description => isSecure ? secureDescription : insecureDescription; +} diff --git a/example/lib/models/security_state.dart b/example/lib/models/security_state.dart new file mode 100644 index 0000000..83cabe8 --- /dev/null +++ b/example/lib/models/security_state.dart @@ -0,0 +1,52 @@ +import 'package:freerasp/freerasp.dart'; +import 'package:freerasp_example/models/security_check.dart'; + +/// State class for security checks managed by Riverpod. +class SecurityState { + + /// Creates an initial state with all checks initialized. + factory SecurityState.initial(List checks) { + return SecurityState(checks: checks); + } + /// Creates a [SecurityState]. + SecurityState({ + required this.checks, + this.detectedMalware = const [], + }); + + /// List of all security checks. + final List checks; + + /// List of detected suspicious apps. + final List detectedMalware; + + /// Creates a copy of this state with updated checks. + SecurityState copyWith({ + List? checks, + List? detectedMalware, + }) { + return SecurityState( + checks: checks ?? this.checks, + detectedMalware: detectedMalware ?? this.detectedMalware, + ); + } + + /// Groups checks by category. + Map> get checksByCategory { + final map = >{}; + for (final check in checks) { + map.putIfAbsent(check.category, () => []).add(check); + } + return map; + } + + /// Returns true if all checks are secure. + bool get isAllSecure { + return checks.every((check) => check.isSecure); + } + + /// Returns true if any malware is detected. + bool get hasMalware { + return detectedMalware.isNotEmpty; + } +} diff --git a/example/lib/models/setting_item.dart b/example/lib/models/setting_item.dart new file mode 100644 index 0000000..47e1b8f --- /dev/null +++ b/example/lib/models/setting_item.dart @@ -0,0 +1,25 @@ +enum SettingType { + /// A simple informational setting + info, + + /// A setting with a switch toggle + switch_, + + /// A setting that can be edited (opens a dialog) + editable, +} + +class SettingItem { + const SettingItem({ + required this.name, + required this.description, + this.type = SettingType.info, + this.id, + }); + + final String name; + final String description; + final SettingType type; + final String? id; +} + diff --git a/example/lib/providers/external_id_provider.dart b/example/lib/providers/external_id_provider.dart new file mode 100644 index 0000000..d7a97cd --- /dev/null +++ b/example/lib/providers/external_id_provider.dart @@ -0,0 +1,25 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:freerasp/freerasp.dart'; + +class ExternalIdNotifier extends StateNotifier { + ExternalIdNotifier() : super(null); + + Future setExternalId(String id) async { + try { + await Talsec.instance.storeExternalId(id); + state = id; + } catch (e) { + // Handle error - you might want to show a snackbar + rethrow; + } + } + + void clearExternalId() { + state = null; + } +} + +final externalIdProvider = StateNotifierProvider( + (ref) => ExternalIdNotifier(), +); + diff --git a/example/lib/screen_capture_notifier.dart b/example/lib/providers/screen_capture_notifier.dart similarity index 100% rename from example/lib/screen_capture_notifier.dart rename to example/lib/providers/screen_capture_notifier.dart diff --git a/example/lib/providers/screen_capture_provider.dart b/example/lib/providers/screen_capture_provider.dart new file mode 100644 index 0000000..02545c5 --- /dev/null +++ b/example/lib/providers/screen_capture_provider.dart @@ -0,0 +1,9 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:freerasp_example/providers/screen_capture_notifier.dart'; + +/// Provider for screen capture blocking functionality +final screenCaptureProvider = + AsyncNotifierProvider.autoDispose(() { + return ScreenCaptureNotifier(); +}); + diff --git a/example/lib/providers/security_controller.dart b/example/lib/providers/security_controller.dart new file mode 100644 index 0000000..c1c288c --- /dev/null +++ b/example/lib/providers/security_controller.dart @@ -0,0 +1,184 @@ +import 'dart:async'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:freerasp/freerasp.dart'; +import 'package:freerasp_example/config/talsec_config.dart'; +import 'package:freerasp_example/models/security_check.dart'; +import 'package:freerasp_example/models/security_state.dart'; + +class SecurityController extends AutoDisposeNotifier { + StreamSubscription? _threatSubscription; + ThreatCallback? _threatCallback; + + @override + SecurityState build() { + final checks = _initChecks(); + // Start monitoring asynchronously + Future.microtask(() => _startMonitoring()); + + // Handle cleanup when provider is disposed + ref.onDispose(() { + _threatSubscription?.cancel(); + // ThreatCallback cleanup is handled automatically by Talsec + }); + + return SecurityState.initial(checks); + } + + List _initChecks() { + return [ + // App Integrity + SecurityCheck( + threat: Threat.appIntegrity, + name: 'App Integrity', + secureDescription: 'Application signature verified and intact.', + insecureDescription: 'Signature verification failed.', + category: ThreatCategory.appIntegrity, + ), + SecurityCheck( + threat: Threat.obfuscationIssues, + name: 'Obfuscation', + secureDescription: 'Application code is properly obfuscated.', + insecureDescription: 'Application code obfuscation disabled.', + category: ThreatCategory.appIntegrity, + ), + SecurityCheck( + threat: Threat.unofficialStore, + name: 'Unofficial Store', + secureDescription: 'Application installed from official store.', + insecureDescription: 'Application installed from unknown or unofficial source.', + category: ThreatCategory.appIntegrity, + ), + SecurityCheck( + threat: Threat.simulator, + name: 'Simulator', + secureDescription: 'Running on a real device.', + insecureDescription: 'Running on simulator or emulator.', + category: ThreatCategory.appIntegrity, + ), + SecurityCheck( + threat: Threat.deviceBinding, + name: 'Device Binding', + secureDescription: 'Application properly bound to device.', + insecureDescription: 'Device binding compromised.', + category: ThreatCategory.appIntegrity, + ), + SecurityCheck( + threat: Threat.multiInstance, + name: 'Multi Instance', + secureDescription: 'Single application instance running.', + insecureDescription: 'Multiple application instances detected.', + category: ThreatCategory.appIntegrity, + ), + + // Device Security + SecurityCheck( + threat: Threat.privilegedAccess, + name: 'Root / Jailbreak', + secureDescription: 'System is running securely (sandbox).', + insecureDescription: 'System security compromised.', + category: ThreatCategory.deviceSecurity, + ), + SecurityCheck( + threat: Threat.hooks, + name: 'Hooks', + secureDescription: 'No hooking frameworks detected.', + insecureDescription: 'Hooking framework detected.', + category: ThreatCategory.deviceSecurity, + ), + SecurityCheck( + threat: Threat.secureHardwareNotAvailable, + name: 'Secure Hardware', + secureDescription: 'Secure hardware available and functioning.', + insecureDescription: 'Secure hardware unavailable.', + category: ThreatCategory.deviceSecurity, + ), + SecurityCheck( + threat: Threat.devMode, + name: 'Developer Mode', + secureDescription: 'Developer options are disabled.', + insecureDescription: 'Developer mode is enabled.', + category: ThreatCategory.deviceSecurity, + ), + SecurityCheck( + threat: Threat.debug, + name: 'Debugger', + secureDescription: 'No debugger attached.', + insecureDescription: 'Debugger is attached.', + category: ThreatCategory.deviceSecurity, + ), + SecurityCheck( + threat: Threat.passcode, + name: 'Passcode', + secureDescription: 'Device is protected with a passcode.', + insecureDescription: 'Device is not password protected.', + category: ThreatCategory.deviceSecurity, + ), + SecurityCheck( + threat: Threat.adbEnabled, + name: 'ADB Enabled', + secureDescription: 'USB debugging (ADB) is disabled.', + insecureDescription: 'USB debugging (ADB) enabled.', + category: ThreatCategory.deviceSecurity, + ), + + SecurityCheck( + threat: Threat.systemVPN, + name: 'System VPN', + secureDescription: 'No VPN active.', + insecureDescription: 'VPN is active.', + category: ThreatCategory.deviceSecurity, + ), + SecurityCheck( + threat: Threat.screenshot, + name: 'Screenshot', + secureDescription: 'No screenshots detected.', + insecureDescription: 'Screenshot detected.', + category: ThreatCategory.runtimeStatus, + ), + SecurityCheck( + threat: Threat.screenRecording, + name: 'Screen Recording', + secureDescription: 'No screen recording detected.', + insecureDescription: 'Screen recording active.', + category: ThreatCategory.runtimeStatus, + ), + ]; + } + + Future _startMonitoring() async { + final config = createTalsecConfig(); + await Talsec.instance.start(config); + _threatSubscription = Talsec.instance.onThreatDetected.listen(_handleThreat); + + // Setup ThreatCallback for malware detection + _threatCallback = ThreatCallback( + onMalware: _handleMalware, + ); + Talsec.instance.attachListener(_threatCallback!); + } + + void _handleThreat(Threat type) { + final currentState = state; + final checks = List.from(currentState.checks); + final index = checks.indexWhere((c) => c.threat == type); + + if (index != -1 && checks[index].isSecure) { + // Create new SecurityCheck with insecure status, preserving all other properties + checks[index] = SecurityCheck( + threat: checks[index].threat, + name: checks[index].name, + secureDescription: checks[index].secureDescription, + insecureDescription: checks[index].insecureDescription, + category: checks[index].category, + isSecure: false, + ); + state = currentState.copyWith(checks: checks); + } + } + + void _handleMalware(List malware) { + final currentState = state; + final malwareList = malware.whereType().toList(); + state = currentState.copyWith(detectedMalware: malwareList); + } +} diff --git a/example/lib/providers/security_controller_provider.dart b/example/lib/providers/security_controller_provider.dart new file mode 100644 index 0000000..4ecac23 --- /dev/null +++ b/example/lib/providers/security_controller_provider.dart @@ -0,0 +1,10 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:freerasp_example/models/security_state.dart'; +import 'package:freerasp_example/providers/security_controller.dart'; + +/// Provider for the security controller that manages all security checks. +final securityControllerProvider = + NotifierProvider.autoDispose( + () => SecurityController(), +); + diff --git a/example/lib/screens/security_screen.dart b/example/lib/screens/security_screen.dart new file mode 100644 index 0000000..2d29e69 --- /dev/null +++ b/example/lib/screens/security_screen.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:freerasp_example/models/security_check.dart'; +import 'package:freerasp_example/models/setting_item.dart'; +import 'package:freerasp_example/providers/security_controller_provider.dart'; +import 'package:freerasp_example/widgets/category_section.dart'; +import 'package:freerasp_example/widgets/malware_alert_card.dart'; +import 'package:freerasp_example/widgets/section.dart'; +import 'package:freerasp_example/widgets/security_status_card.dart'; +import 'package:freerasp_example/widgets/settings_section.dart'; + +class SecurityScreen extends ConsumerWidget { + const SecurityScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final securityState = ref.watch(securityControllerProvider); + final checksByCategory = securityState.checksByCategory; + + // Define your settings items + const otherSettings = [ + SettingItem( + name: 'Block Screen Capture', + description: 'Prevent screenshots and screen recording', + type: SettingType.switch_, + id: 'screen_capture', + ), + SettingItem( + name: 'External ID', + description: 'Device identifier for log enrichment', + type: SettingType.editable, + id: 'external_id', + ), + ]; + + return Scaffold( + appBar: AppBar(), + body: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + const SecurityStatusCard(), + const MalwareAlertCard(), + Section( + sectionTitle: 'App Integrity', + child: CategorySection( + checks: checksByCategory[ThreatCategory.appIntegrity] ?? [], + ), + ), + Section( + sectionTitle: 'Device Security', + child: CategorySection( + checks: checksByCategory[ThreatCategory.deviceSecurity] ?? [], + ), + ), + Section( + sectionTitle: 'Runtime Status', + child: CategorySection( + checks: checksByCategory[ThreatCategory.runtimeStatus] ?? [], + ), + ), + Section( + sectionTitle: 'Other Settings', + child: SettingsSection(settings: otherSettings), + ), + ], + ), + ), + ), + ); + } +} diff --git a/example/lib/screens/suspicious_apps_screen.dart b/example/lib/screens/suspicious_apps_screen.dart new file mode 100644 index 0000000..9b2d7b4 --- /dev/null +++ b/example/lib/screens/suspicious_apps_screen.dart @@ -0,0 +1,94 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:freerasp/freerasp.dart'; +import 'package:freerasp_example/providers/security_controller_provider.dart'; + +class SuspiciousAppsScreen extends ConsumerWidget { + const SuspiciousAppsScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final securityState = ref.watch(securityControllerProvider); + final suspiciousApps = securityState.detectedMalware; + final textTheme = Theme.of(context).textTheme; + const radius = 16.0; + + return Scaffold( + appBar: AppBar( + title: const Text('Suspicious Apps'), + ), + body: suspiciousApps.isEmpty + ? Center( + child: Text( + 'No suspicious apps detected', + style: textTheme.bodyLarge, + ), + ) + : Padding( + padding: const EdgeInsets.all(16), + child: ListView.separated( + itemCount: suspiciousApps.length, + itemBuilder: (context, index) { + final app = suspiciousApps[index]; + final isFirst = index == 0; + final isLast = index == suspiciousApps.length - 1; + + return ListTile( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: isFirst ? const Radius.circular(radius) : Radius.zero, + bottom: isLast ? const Radius.circular(radius) : Radius.zero, + ), + ), + leading: Padding( + padding: const EdgeInsets.all(8), + child: FutureBuilder( + future: Talsec.instance.getAppIcon( + app.packageInfo.packageName, + ), + builder: (context, snapshot) { + Widget appIcon; + if (snapshot.hasData && snapshot.data != null) { + try { + appIcon = ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Image.memory( + base64.decode(snapshot.data!), + width: 32, + height: 32, + errorBuilder: (context, error, stackTrace) { + return const Icon( + Icons.android, + size: 32, + ); + }, + ), + ); + } catch (e) { + appIcon = const Icon( + Icons.android, + size: 32, + ); + } + } else { + appIcon = const Icon( + Icons.android, + size: 32, + ); + } + + return appIcon; + }, + ), + ), + title: Text(app.packageInfo.packageName), + subtitle: Text('Reason: ${app.reason}'), + ); + }, + separatorBuilder: (ctx, i) => const SizedBox(height: 2), + ), + ), + ); + } +} diff --git a/example/lib/theme/app_theme.dart b/example/lib/theme/app_theme.dart new file mode 100644 index 0000000..8dfd13e --- /dev/null +++ b/example/lib/theme/app_theme.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +class AppTheme { + // Default to Talsec company colours + static const _fallbackSeed = Color(0xFF8df6a2); + static const _fallbackSurface = Color(0xFF191B24); + + static ColorScheme getScheme( + ColorScheme? dynamicScheme, Brightness brightness) { + if (dynamicScheme != null) { + return ColorScheme.fromSeed( + seedColor: dynamicScheme.primary, + brightness: brightness, + ); + } + + return ColorScheme.fromSeed( + seedColor: _fallbackSeed, + brightness: brightness, + surface: _fallbackSurface, + ); + } + + static ThemeData create(ColorScheme scheme) { + return ThemeData( + useMaterial3: true, + colorScheme: scheme, + scaffoldBackgroundColor: scheme.surface, + + // Font setup + textTheme: GoogleFonts.openSansTextTheme(), + + // Component Styles + listTileTheme: ListTileThemeData( + tileColor: scheme.surfaceContainerHigh, + titleTextStyle: GoogleFonts.openSans( + fontSize: 16, + fontWeight: FontWeight.w600, + color: scheme.onSurface, + ), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(16)), + ), + ), + cardTheme: CardThemeData( + color: scheme.surfaceContainerHigh, + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + ), + ); + } +} + diff --git a/example/lib/threat_notifier.dart b/example/lib/threat_notifier.dart deleted file mode 100644 index d27cd21..0000000 --- a/example/lib/threat_notifier.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:freerasp/freerasp.dart'; -import 'package:freerasp_example/threat_state.dart'; - -/// Class responsible for setting up listeners to detected threats -class ThreatNotifier extends AutoDisposeNotifier { - @override - ThreatState build() { - _init(); - return ThreatState.initial(); - } - - void _init() { - final threatCallback = ThreatCallback( - onMalware: _updateMalware, - onHooks: () => _updateThreat(Threat.hooks), - onDebug: () => _updateThreat(Threat.debug), - onPasscode: () => _updateThreat(Threat.passcode), - onDeviceID: () => _updateThreat(Threat.deviceId), - onSimulator: () => _updateThreat(Threat.simulator), - onAppIntegrity: () => _updateThreat(Threat.appIntegrity), - onObfuscationIssues: () => _updateThreat(Threat.obfuscationIssues), - onDeviceBinding: () => _updateThreat(Threat.deviceBinding), - onUnofficialStore: () => _updateThreat(Threat.unofficialStore), - onPrivilegedAccess: () => _updateThreat(Threat.privilegedAccess), - onSecureHardwareNotAvailable: () => - _updateThreat(Threat.secureHardwareNotAvailable), - onSystemVPN: () => _updateThreat(Threat.systemVPN), - onDevMode: () => _updateThreat(Threat.devMode), - onADBEnabled: () => _updateThreat(Threat.adbEnabled), - onScreenshot: () => _updateThreat(Threat.screenshot), - onScreenRecording: () => _updateThreat(Threat.screenRecording), - onMultiInstance: () => _updateThreat(Threat.multiInstance), - ); - - Talsec.instance.attachListener(threatCallback); - } - - void _updateThreat(Threat threat) { - state = state.copyWith(detectedThreats: {...state.detectedThreats, threat}); - } - - void _updateMalware(List malware) { - state = state.copyWith(detectedMalware: malware.nonNulls.toList()); - } -} diff --git a/example/lib/threat_state.dart b/example/lib/threat_state.dart deleted file mode 100644 index 837c2a3..0000000 --- a/example/lib/threat_state.dart +++ /dev/null @@ -1,27 +0,0 @@ -// ignore_for_file: public_member_api_docs - -import 'package:freerasp/freerasp.dart'; - -class ThreatState { - factory ThreatState.initial() => - const ThreatState._(detectedThreats: {}, detectedMalware: []); - - const ThreatState._({ - required this.detectedThreats, - required this.detectedMalware, - }); - - final Set detectedThreats; - final List detectedMalware; - - ThreatState copyWith({ - Set? detectedThreats, - List? detectedMalware, - }) { - return ThreatState._( - detectedThreats: detectedThreats ?? this.detectedThreats, - detectedMalware: - detectedMalware?.nonNulls.toList() ?? this.detectedMalware, - ); - } -} diff --git a/example/lib/widgets/category_section.dart b/example/lib/widgets/category_section.dart new file mode 100644 index 0000000..b955806 --- /dev/null +++ b/example/lib/widgets/category_section.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:freerasp_example/models/security_check.dart'; +import 'package:freerasp_example/widgets/list_item_card.dart'; + +class CategorySection extends StatelessWidget { + const CategorySection({ + required this.checks, + super.key, + }); + + final List checks; + + @override + Widget build(BuildContext context) { + if (checks.isEmpty) { + return const SizedBox.shrink(); + } + + return ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: checks.length, + itemBuilder: (context, index) { + final item = checks[index]; + final isFirst = index == 0; + final isLast = index == checks.length - 1; + + return ListItemCard( + title: item.name, + subtitle: item.description, + leading: Icon( + item.isSecure ? Icons.check_circle : Icons.error, + color: item.isSecure ? Colors.green : Colors.amber, + ), + isFirst: isFirst, + isLast: isLast, + ); + }, + separatorBuilder: (ctx, i) => const SizedBox(height: 2), + ); + } +} + diff --git a/example/lib/widgets/list_item_card.dart b/example/lib/widgets/list_item_card.dart new file mode 100644 index 0000000..07c83d5 --- /dev/null +++ b/example/lib/widgets/list_item_card.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; + +class ListItemCard extends StatelessWidget { + const ListItemCard({ + required this.title, + required this.subtitle, + this.isFirst = false, + this.isLast = false, + this.leading, + this.trailing, + super.key, + }); + + final String title; + final String subtitle; + final Widget? leading; + final Widget? trailing; + final bool isFirst; + final bool isLast; + + @override + Widget build(BuildContext context) { + const radius = 16.0; + + return ListTile( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: isFirst ? const Radius.circular(radius) : Radius.zero, + bottom: isLast ? const Radius.circular(radius) : Radius.zero, + ), + ), + leading: leading != null + ? Padding( + padding: const EdgeInsets.all(8), + child: leading, + ) + : null, + title: Text(title), + subtitle: Text(subtitle), + trailing: trailing, + ); + } +} diff --git a/example/lib/widgets/malware_alert_card.dart b/example/lib/widgets/malware_alert_card.dart new file mode 100644 index 0000000..fb4267f --- /dev/null +++ b/example/lib/widgets/malware_alert_card.dart @@ -0,0 +1,83 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:freerasp_example/providers/security_controller_provider.dart'; +import 'package:freerasp_example/screens/suspicious_apps_screen.dart'; + +class MalwareAlertCard extends ConsumerStatefulWidget { + const MalwareAlertCard({super.key}); + + @override + ConsumerState createState() => _MalwareAlertCardState(); +} + +class _MalwareAlertCardState extends ConsumerState { + @override + Widget build(BuildContext context) { + final securityState = ref.watch(securityControllerProvider); + final hasMalware = securityState.hasMalware; + final malwareCount = securityState.detectedMalware.length; + final colors = Theme.of(context).colorScheme; + + if (!hasMalware) { + return const SizedBox.shrink(); + } + + return Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Malware security', + style: TextStyle( + fontSize: 14, + color: colors.onSurfaceVariant, + ), + ), + ], + ), + const SizedBox(height: 8), + Text( + 'You have security recommendations', + style: TextStyle( + fontSize: 22, + height: 1.2, + color: colors.onSurface, + fontWeight: FontWeight.w400, + ), + ), + const SizedBox(height: 8), + Text( + malwareCount == 1 + ? '$malwareCount suspicious app detected' + : '$malwareCount suspicious apps detected', + style: TextStyle( + color: colors.onSurfaceVariant, + fontSize: 14, + ), + ), + const SizedBox(height: 20), + SizedBox( + width: double.infinity, + height: 50, + child: FilledButton( + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const SuspiciousAppsScreen(), + ), + ); + }, + child: const Text('See suspicious apps'), + ), + ), + ], + ), + ), + ); + } +} diff --git a/example/lib/widgets/malware_bottom_sheet.dart b/example/lib/widgets/malware_bottom_sheet.dart deleted file mode 100644 index 04abf12..0000000 --- a/example/lib/widgets/malware_bottom_sheet.dart +++ /dev/null @@ -1,84 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/material.dart'; -import 'package:freerasp/freerasp.dart'; - -/// Bottom sheet widget that displays malware information -class MalwareBottomSheet extends StatelessWidget { - /// Represents malware information in the example app - const MalwareBottomSheet({ - required this.suspiciousApps, - super.key, - }); - - /// List of suspicious apps - final List suspiciousApps; - - @override - Widget build(BuildContext context) { - final textTheme = Theme.of(context).textTheme; - - return Container( - padding: const EdgeInsets.all(16), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text('Suspicious Apps', style: textTheme.titleMedium), - const SizedBox(height: 8), - SizedBox( - height: 240, - child: ListView.builder( - itemCount: suspiciousApps.length, - itemBuilder: (_, index) => - MalwareListTile(malware: suspiciousApps[index]), - ), - ), - const SizedBox(height: 16), - SizedBox( - width: double.infinity, - child: FilledButton( - onPressed: () => Navigator.pop(context), - child: const Text('Dismiss'), - ), - ), - ], - ), - ); - } -} - -/// List tile widget that displays malware information -class MalwareListTile extends StatelessWidget { - /// Represents malware information in the example app - const MalwareListTile({ - required this.malware, - super.key, - }); - - /// Malware information - final SuspiciousAppInfo malware; - - @override - Widget build(BuildContext context) { - return FutureBuilder( - future: Talsec.instance.getAppIcon(malware.packageInfo.packageName), - builder: (context, snapshot) { - Widget appIcon; - if (snapshot.data != null) { - appIcon = Image.memory(base64.decode(snapshot.data!)); - } else { - appIcon = const Icon( - Icons.error, - color: Colors.red, - ); - } - - return ListTile( - title: Text(malware.packageInfo.packageName), - subtitle: Text('Reason: ${malware.reason}'), - leading: appIcon, - ); - }, - ); - } -} diff --git a/example/lib/widgets/safety_icon.dart b/example/lib/widgets/safety_icon.dart deleted file mode 100644 index 2e00baf..0000000 --- a/example/lib/widgets/safety_icon.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:flutter/material.dart'; - -/// Class responsible for changing threat icon and style in the example app -class SafetyIcon extends StatelessWidget { - /// Represents security state icon in the example app - const SafetyIcon({required this.isDetected, super.key}); - - /// Determines whether given threat was detected - final bool isDetected; - - @override - Widget build(BuildContext context) { - return isDetected - ? const Icon(Icons.gpp_bad_outlined, color: Colors.red, size: 32) - : const Icon(Icons.gpp_good_outlined, color: Colors.green, size: 32); - } -} diff --git a/example/lib/widgets/section.dart b/example/lib/widgets/section.dart new file mode 100644 index 0000000..b46211f --- /dev/null +++ b/example/lib/widgets/section.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; + +class Section extends StatelessWidget { + const Section({ + required this.sectionTitle, + required this.child, + super.key, + }); + + final String sectionTitle; + final Widget child; + + @override + Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; + final textTheme = Theme.of(context).textTheme; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8, + children: [ + Padding( + padding: const EdgeInsets.only(left: 8), + child: Text( + sectionTitle, + style: textTheme.labelLarge?.copyWith(color: colors.primary), + ), + ), + child + ], + ); + } +} + diff --git a/example/lib/widgets/security_status_card.dart b/example/lib/widgets/security_status_card.dart new file mode 100644 index 0000000..af664cd --- /dev/null +++ b/example/lib/widgets/security_status_card.dart @@ -0,0 +1,41 @@ +import 'package:avatar_glow/avatar_glow.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:freerasp_example/providers/security_controller_provider.dart'; + +class SecurityStatusCard extends ConsumerWidget { + const SecurityStatusCard({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final securityState = ref.watch(securityControllerProvider); + final isSecure = securityState.isAllSecure; + + return ListTile( + contentPadding: const EdgeInsets.all(20), + leading: AvatarGlow( + glowColor: isSecure ? Colors.green : Colors.orange, + glowRadiusFactor: 0.4, + duration: const Duration(seconds: 1), + glowCount: 1, + child: Padding( + padding: const EdgeInsets.all(8), + child: Icon( + Icons.security, + color: isSecure ? Colors.green : Colors.orange, + size: 32, + ), + ), + ), + title: Text( + isSecure ? 'Device is Secure' : 'Device may be compromised', + ), + subtitle: Text( + isSecure + ? 'Checking security status...' + : 'See alerts for more details', + ), + ); + } +} + diff --git a/example/lib/widgets/settings_section.dart b/example/lib/widgets/settings_section.dart new file mode 100644 index 0000000..d825b3a --- /dev/null +++ b/example/lib/widgets/settings_section.dart @@ -0,0 +1,149 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:freerasp_example/models/setting_item.dart'; +import 'package:freerasp_example/providers/external_id_provider.dart'; +import 'package:freerasp_example/providers/screen_capture_provider.dart'; +import 'package:freerasp_example/widgets/list_item_card.dart'; + +class SettingsSection extends ConsumerWidget { + const SettingsSection({ + required this.settings, + super.key, + }); + + final List settings; + + @override + Widget build(BuildContext context, WidgetRef ref) { + if (settings.isEmpty) { + return const SizedBox.shrink(); + } + + return ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: settings.length, + itemBuilder: (context, index) { + final item = settings[index]; + final isFirst = index == 0; + final isLast = index == settings.length - 1; + + Widget? trailing; + String subtitle = item.description; + + if (item.type == SettingType.switch_) { + if (item.id == 'screen_capture') { + final screenCaptureState = ref.watch(screenCaptureProvider); + trailing = screenCaptureState.when( + data: (bool isBlocked) => Switch( + value: isBlocked, + onChanged: (bool value) async { + await ref.read(screenCaptureProvider.notifier).toggle(); + }, + ), + loading: () => const SizedBox( + width: 48, + height: 24, + child: Center( + child: SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator(strokeWidth: 2), + ), + ), + ), + error: (_, __) => const Icon(Icons.error), + ); + } + } else if (item.type == SettingType.editable) { + if (item.id == 'external_id') { + final externalId = ref.watch(externalIdProvider); + subtitle = externalId ?? 'Not set'; + trailing = const Icon(Icons.chevron_right); + } + } + + return GestureDetector( + onTap: item.type == SettingType.editable && item.id == 'external_id' + ? () => _showExternalIdDialog(context, ref, item) + : null, + child: ListItemCard( + title: item.name, + subtitle: subtitle, + isFirst: isFirst, + isLast: isLast, + trailing: trailing, + ), + ); + }, + separatorBuilder: (ctx, i) => const SizedBox(height: 2), + ); + } + + void _showExternalIdDialog( + BuildContext context, + WidgetRef ref, + SettingItem item, + ) { + final currentId = ref.read(externalIdProvider); + final controller = TextEditingController(text: currentId ?? ''); + + showDialog( + context: context, + builder: (dialogContext) => AlertDialog( + title: Text(item.name), + content: TextField( + controller: controller, + decoration: const InputDecoration( + labelText: 'External ID', + hintText: 'Enter device identifier', + border: OutlineInputBorder(), + ), + autofocus: true, + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(dialogContext).pop(), + child: const Text('Cancel'), + ), + FilledButton( + onPressed: () async { + final newId = controller.text.trim(); + if (newId.isNotEmpty) { + try { + await ref.read(externalIdProvider.notifier).setExternalId(newId); + if (dialogContext.mounted) { + Navigator.of(dialogContext).pop(); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('External ID saved successfully'), + duration: Duration(seconds: 2), + ), + ); + } + } catch (e) { + if (dialogContext.mounted) { + ScaffoldMessenger.of(dialogContext).showSnackBar( + SnackBar( + content: Text('Error saving external ID: $e'), + backgroundColor: Colors.red, + ), + ); + } + } + } else { + // Clear the external ID if empty + ref.read(externalIdProvider.notifier).clearExternalId(); + if (dialogContext.mounted) { + Navigator.of(dialogContext).pop(); + } + } + }, + child: const Text('Save'), + ), + ], + ), + ); + } +} + diff --git a/example/lib/widgets/threat_listview.dart b/example/lib/widgets/threat_listview.dart deleted file mode 100644 index 2342436..0000000 --- a/example/lib/widgets/threat_listview.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:freerasp/freerasp.dart'; -import 'package:freerasp_example/extensions.dart'; -import 'package:freerasp_example/widgets/widgets.dart'; - -/// ListView displaying all detected threats -class ThreatListView extends StatelessWidget { - /// Represents a list of detected threats - const ThreatListView({ - required this.threats, - super.key, - }); - - /// Set of detected threats - final Set threats; - - @override - Widget build(BuildContext context) { - return ListView.separated( - padding: const EdgeInsets.all(8), - itemCount: Threat.values.length, - itemBuilder: (context, index) { - final currentThreat = Threat.values[index]; - final isDetected = threats.contains(currentThreat); - - return ListTile( - title: Text(currentThreat.name.capitalize()), - subtitle: Text(isDetected ? 'Danger' : 'Safe'), - trailing: SafetyIcon(isDetected: isDetected), - ); - }, - separatorBuilder: (_, __) => const Divider(height: 1), - ); - } -} diff --git a/example/lib/widgets/widgets.dart b/example/lib/widgets/widgets.dart deleted file mode 100644 index 2342ae2..0000000 --- a/example/lib/widgets/widgets.dart +++ /dev/null @@ -1,3 +0,0 @@ -export 'malware_bottom_sheet.dart'; -export 'safety_icon.dart'; -export 'threat_listview.dart'; diff --git a/example/pubspec.yaml b/example/pubspec.yaml index b3d5fb5..783c16c 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -7,6 +7,8 @@ environment: sdk: ">=3.0.0 <4.0.0" dependencies: + avatar_glow: ^3.0.1 + dynamic_color: ^1.8.1 flutter: sdk: flutter flutter_riverpod: ^2.3.2 @@ -17,6 +19,7 @@ dependencies: # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: ../ + google_fonts: ^6.3.3 dev_dependencies: diff --git a/ios/freerasp/.index-build/arm64-apple-macosx/debug/freerasp.build/EventProcessor.swiftdeps~ b/ios/freerasp/.index-build/arm64-apple-macosx/debug/freerasp.build/EventProcessor.swiftdeps~ new file mode 100644 index 0000000000000000000000000000000000000000..f167f8f87f4a17ffb69de5a687a8a25f92baf8b9 GIT binary patch literal 540 zcma)&v2W8r7{$*AE$M;~61$NQ>g8hJxin5IWk}qlb3}w#ImhS9mE*H~jwxGZGG<`N z*nz=AN9dHHLv&@x(2+kU1D7B&vwS-J-h1EobiMvyXw(3}0Dx!4-E!rjnZhfG*5%<3 zg=ebXe$&NsXiXuS>Zu;yJ}UgXY|K1uK^4SvMZS4xsmM*)n8L<)MHUKc((ZjlRf%sr zw1SP5S6@JDUE+I)=IY^mlkm_2qDMvUOT5-pFDr2ctxDm=ZbzqI*H#^^w5&J%6Ts_F z0DRP)VQ{@P>FqT#&tUWN<MLOn5 zPTqHi0}^JOXH2HMhZV+?Jm(pSgxo=5M@hi1EPoQk0b$wLJ+|1UsWoOJA+Jcl{mI47 zZvXZ53w>sgi4m7lWcIdyz*D)m4`AmB1&-gM)Mfq&J#8Mfj_ChF3^f$%pgOj2!H}p069zKHo0VRnE0$0guO5gZb+T?xF1Fx9;D>7 z)@+bg%z4a`Xz|cmem6~dOuR5zh(z}SmtSjk*YjP%;!efX*z8iR!<;a=CN8(T?FFuX zaq|`@-ybymZrcm=c?p+CiR`+v3Sf1~0E||sX*;e%5BB$&$xUh=xMi0*oKxGh%PZ@@ I<76cG1W!n^%m4rY literal 0 HcmV?d00001 diff --git a/ios/freerasp/.index-build/arm64-apple-macosx/debug/freerasp.build/TalsecHandlers.swiftdeps~ b/ios/freerasp/.index-build/arm64-apple-macosx/debug/freerasp.build/TalsecHandlers.swiftdeps~ new file mode 100644 index 0000000000000000000000000000000000000000..c98219a639c7a1fd27fb83fb7b37662be2afcaf6 GIT binary patch literal 540 zcma)&KTq309EQ(Ffv{8viQPyQ;&QR?3{KJ^L*j<2BWj9Rd)Q}W`dlEKC^~qN76w#(ta(T!NaJ<)_p0{?V&zAD?uz5&#$gu&F&xXAT-kJcDSSAN-Mc zB+HFYEj)(C5Tc>F@_GM7;(vK%J7&fyO+?PY{je^VdbfK@*5xBzem5Tv1h);{qCm#FJG=^SG?6Dq1L6HT@UB-cJC| z)Mn`2Om+JE2I*g5^>XLKUu}1LtF)m#G`FCV+nPG9Bni3RxeEMD@AFgyairHwY98qa zVgW-IT^LJXTBb#HcL}T-r3(`^Ed)TRp#gF#RGM6}G!`N2M{z&SqHEITeICY1mq!`- z-s+qXKjl1SB3V5QKkQ{0Pe~Aq6(nX5d3<1;_kz$PEbZ28gDsXCUFODOKs Date: Fri, 2 Jan 2026 10:19:51 +0100 Subject: [PATCH 02/15] chore: update gitignore and remove settings.json --- .gemini/settings.json | 10 ---------- .gitignore | 2 +- 2 files changed, 1 insertion(+), 11 deletions(-) delete mode 100644 .gemini/settings.json diff --git a/.gemini/settings.json b/.gemini/settings.json deleted file mode 100644 index 3ae6a87..0000000 --- a/.gemini/settings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "mcpServers": { - "dart": { - "command": "dart", - "args": [ - "mcp-server" - ] - } - } -} diff --git a/.gitignore b/.gitignore index 2b2eed6..8cc6c4c 100644 --- a/.gitignore +++ b/.gitignore @@ -34,4 +34,4 @@ example/pubspec.lock .fvm/ # AI stuff -.gemini \ No newline at end of file +.gemini/ \ No newline at end of file From 07021fa9d34e9a0135170dc4cbaf3680e4c0455f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Novotn=C3=BD?= <62177414+yardexx@users.noreply.github.com> Date: Fri, 2 Jan 2026 10:20:12 +0100 Subject: [PATCH 03/15] refactor(example): restructure widgets and consolidate providers Refactor widgets into domain-specific folders (common, security, settings). Replace ListView with Column+Material layout for better performance and simplicity. Consolidate split provider files into single files. --- example/lib/models/setting_item.dart | 25 --- .../lib/providers/external_id_provider.dart | 2 - .../providers/screen_capture_notifier.dart | 18 --- .../providers/screen_capture_provider.dart | 17 +- .../security_controller_provider.dart | 10 -- ...controller.dart => security_provider.dart} | 68 ++++---- example/lib/screens/security_screen.dart | 61 +++---- .../lib/screens/suspicious_apps_screen.dart | 10 +- example/lib/widgets/category_section.dart | 43 ----- .../grouped_list_item.dart} | 20 +-- .../titled_section.dart} | 13 +- .../malware_card.dart} | 10 +- .../lib/widgets/security/security_group.dart | 38 +++++ example/lib/widgets/security/status_card.dart | 43 +++++ example/lib/widgets/security_status_card.dart | 41 ----- .../lib/widgets/settings/settings_group.dart | 27 ++++ .../settings/tiles/external_id_tile.dart | 93 +++++++++++ .../settings/tiles/screen_capture_tile.dart | 38 +++++ example/lib/widgets/settings_section.dart | 149 ------------------ 19 files changed, 343 insertions(+), 383 deletions(-) delete mode 100644 example/lib/models/setting_item.dart delete mode 100644 example/lib/providers/screen_capture_notifier.dart delete mode 100644 example/lib/providers/security_controller_provider.dart rename example/lib/providers/{security_controller.dart => security_provider.dart} (75%) delete mode 100644 example/lib/widgets/category_section.dart rename example/lib/widgets/{list_item_card.dart => common/grouped_list_item.dart} (57%) rename example/lib/widgets/{section.dart => common/titled_section.dart} (78%) rename example/lib/widgets/{malware_alert_card.dart => security/malware_card.dart} (87%) create mode 100644 example/lib/widgets/security/security_group.dart create mode 100644 example/lib/widgets/security/status_card.dart delete mode 100644 example/lib/widgets/security_status_card.dart create mode 100644 example/lib/widgets/settings/settings_group.dart create mode 100644 example/lib/widgets/settings/tiles/external_id_tile.dart create mode 100644 example/lib/widgets/settings/tiles/screen_capture_tile.dart delete mode 100644 example/lib/widgets/settings_section.dart diff --git a/example/lib/models/setting_item.dart b/example/lib/models/setting_item.dart deleted file mode 100644 index 47e1b8f..0000000 --- a/example/lib/models/setting_item.dart +++ /dev/null @@ -1,25 +0,0 @@ -enum SettingType { - /// A simple informational setting - info, - - /// A setting with a switch toggle - switch_, - - /// A setting that can be edited (opens a dialog) - editable, -} - -class SettingItem { - const SettingItem({ - required this.name, - required this.description, - this.type = SettingType.info, - this.id, - }); - - final String name; - final String description; - final SettingType type; - final String? id; -} - diff --git a/example/lib/providers/external_id_provider.dart b/example/lib/providers/external_id_provider.dart index d7a97cd..74ffdcd 100644 --- a/example/lib/providers/external_id_provider.dart +++ b/example/lib/providers/external_id_provider.dart @@ -9,7 +9,6 @@ class ExternalIdNotifier extends StateNotifier { await Talsec.instance.storeExternalId(id); state = id; } catch (e) { - // Handle error - you might want to show a snackbar rethrow; } } @@ -22,4 +21,3 @@ class ExternalIdNotifier extends StateNotifier { final externalIdProvider = StateNotifierProvider( (ref) => ExternalIdNotifier(), ); - diff --git a/example/lib/providers/screen_capture_notifier.dart b/example/lib/providers/screen_capture_notifier.dart deleted file mode 100644 index ba0cc0b..0000000 --- a/example/lib/providers/screen_capture_notifier.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:freerasp/freerasp.dart'; - -/// Class responsible for triggering screen capture blocking -class ScreenCaptureNotifier extends AutoDisposeAsyncNotifier { - @override - bool build() => false; - - /// Toggles screen capture blocking - /// - /// If screen capture is blocked, it will be unblocked and vice versa - Future toggle() async { - final isScreenCaptureBlocked = - await Talsec.instance.isScreenCaptureBlocked(); - await Talsec.instance.blockScreenCapture(enabled: !isScreenCaptureBlocked); - state = AsyncValue.data(!isScreenCaptureBlocked); - } -} diff --git a/example/lib/providers/screen_capture_provider.dart b/example/lib/providers/screen_capture_provider.dart index 02545c5..6cbfced 100644 --- a/example/lib/providers/screen_capture_provider.dart +++ b/example/lib/providers/screen_capture_provider.dart @@ -1,5 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:freerasp_example/providers/screen_capture_notifier.dart'; +import 'package:freerasp/freerasp.dart'; /// Provider for screen capture blocking functionality final screenCaptureProvider = @@ -7,3 +7,18 @@ final screenCaptureProvider = return ScreenCaptureNotifier(); }); +/// Class responsible for triggering screen capture blocking +class ScreenCaptureNotifier extends AutoDisposeAsyncNotifier { + @override + bool build() => false; + + /// Toggles screen capture blocking + /// + /// If screen capture is blocked, it will be unblocked and vice versa + Future toggle() async { + final isScreenCaptureBlocked = + await Talsec.instance.isScreenCaptureBlocked(); + await Talsec.instance.blockScreenCapture(enabled: !isScreenCaptureBlocked); + state = AsyncValue.data(!isScreenCaptureBlocked); + } +} \ No newline at end of file diff --git a/example/lib/providers/security_controller_provider.dart b/example/lib/providers/security_controller_provider.dart deleted file mode 100644 index 4ecac23..0000000 --- a/example/lib/providers/security_controller_provider.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:freerasp_example/models/security_state.dart'; -import 'package:freerasp_example/providers/security_controller.dart'; - -/// Provider for the security controller that manages all security checks. -final securityControllerProvider = - NotifierProvider.autoDispose( - () => SecurityController(), -); - diff --git a/example/lib/providers/security_controller.dart b/example/lib/providers/security_provider.dart similarity index 75% rename from example/lib/providers/security_controller.dart rename to example/lib/providers/security_provider.dart index c1c288c..a7810fc 100644 --- a/example/lib/providers/security_controller.dart +++ b/example/lib/providers/security_provider.dart @@ -1,26 +1,27 @@ import 'dart:async'; + import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:freerasp/freerasp.dart'; -import 'package:freerasp_example/config/talsec_config.dart'; import 'package:freerasp_example/models/security_check.dart'; import 'package:freerasp_example/models/security_state.dart'; -class SecurityController extends AutoDisposeNotifier { - StreamSubscription? _threatSubscription; - ThreatCallback? _threatCallback; +/// Provider for the security controller that manages all security checks. +final securityControllerProvider = + NotifierProvider.autoDispose( + SecurityController.new, +); +class SecurityController extends AutoDisposeNotifier { + /// Initializes the security controller and starts monitoring. @override SecurityState build() { final checks = _initChecks(); - // Start monitoring asynchronously - Future.microtask(() => _startMonitoring()); - - // Handle cleanup when provider is disposed + Future.microtask(_startListening); + ref.onDispose(() { - _threatSubscription?.cancel(); - // ThreatCallback cleanup is handled automatically by Talsec + Talsec.instance.detachListener(); }); - + return SecurityState.initial(checks); } @@ -45,7 +46,8 @@ class SecurityController extends AutoDisposeNotifier { threat: Threat.unofficialStore, name: 'Unofficial Store', secureDescription: 'Application installed from official store.', - insecureDescription: 'Application installed from unknown or unofficial source.', + insecureDescription: + 'Application installed from unknown or unofficial source.', category: ThreatCategory.appIntegrity, ), SecurityCheck( @@ -145,33 +147,39 @@ class SecurityController extends AutoDisposeNotifier { ]; } - Future _startMonitoring() async { - final config = createTalsecConfig(); - await Talsec.instance.start(config); - _threatSubscription = Talsec.instance.onThreatDetected.listen(_handleThreat); - - // Setup ThreatCallback for malware detection - _threatCallback = ThreatCallback( + Future _startListening() async { + final threatCallback = ThreatCallback( + onAppIntegrity: () => _handleThreat(Threat.appIntegrity), + onObfuscationIssues: () => _handleThreat(Threat.obfuscationIssues), + onUnofficialStore: () => _handleThreat(Threat.unofficialStore), + onPrivilegedAccess: () => _handleThreat(Threat.privilegedAccess), + onDeviceBinding: () => _handleThreat(Threat.deviceBinding), + onSimulator: () => _handleThreat(Threat.simulator), + onHooks: () => _handleThreat(Threat.hooks), + onDebug: () => _handleThreat(Threat.debug), + onScreenRecording: () => _handleThreat(Threat.screenRecording), + onScreenshot: () => _handleThreat(Threat.screenshot), + onSecureHardwareNotAvailable: () => + _handleThreat(Threat.secureHardwareNotAvailable), + onSystemVPN: () => _handleThreat(Threat.systemVPN), + onDeviceID: () => _handleThreat(Threat.deviceId), + onPasscode: () => _handleThreat(Threat.passcode), + onADBEnabled: () => _handleThreat(Threat.adbEnabled), + onDevMode: () => _handleThreat(Threat.devMode), + onMultiInstance: () => _handleThreat(Threat.multiInstance), onMalware: _handleMalware, ); - Talsec.instance.attachListener(_threatCallback!); + + Talsec.instance.attachListener(threatCallback); } void _handleThreat(Threat type) { final currentState = state; final checks = List.from(currentState.checks); final index = checks.indexWhere((c) => c.threat == type); - + if (index != -1 && checks[index].isSecure) { - // Create new SecurityCheck with insecure status, preserving all other properties - checks[index] = SecurityCheck( - threat: checks[index].threat, - name: checks[index].name, - secureDescription: checks[index].secureDescription, - insecureDescription: checks[index].insecureDescription, - category: checks[index].category, - isSecure: false, - ); + checks[index] = checks[index].copyWith(isSecure: false); state = currentState.copyWith(checks: checks); } } diff --git a/example/lib/screens/security_screen.dart b/example/lib/screens/security_screen.dart index 2d29e69..0217a6e 100644 --- a/example/lib/screens/security_screen.dart +++ b/example/lib/screens/security_screen.dart @@ -1,13 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:freerasp_example/models/security_check.dart'; -import 'package:freerasp_example/models/setting_item.dart'; -import 'package:freerasp_example/providers/security_controller_provider.dart'; -import 'package:freerasp_example/widgets/category_section.dart'; -import 'package:freerasp_example/widgets/malware_alert_card.dart'; -import 'package:freerasp_example/widgets/section.dart'; -import 'package:freerasp_example/widgets/security_status_card.dart'; -import 'package:freerasp_example/widgets/settings_section.dart'; +import 'package:freerasp_example/providers/security_provider.dart'; +import 'package:freerasp_example/widgets/common/titled_section.dart'; +import 'package:freerasp_example/widgets/security/malware_alert_card.dart'; +import 'package:freerasp_example/widgets/security/security_check_list.dart'; +import 'package:freerasp_example/widgets/security/security_status_card.dart'; +import 'package:freerasp_example/widgets/settings/settings_group.dart'; +import 'package:freerasp_example/widgets/settings/tiles/external_id_tile.dart'; +import 'package:freerasp_example/widgets/settings/tiles/screen_capture_tile.dart'; class SecurityScreen extends ConsumerWidget { const SecurityScreen({super.key}); @@ -17,22 +18,6 @@ class SecurityScreen extends ConsumerWidget { final securityState = ref.watch(securityControllerProvider); final checksByCategory = securityState.checksByCategory; - // Define your settings items - const otherSettings = [ - SettingItem( - name: 'Block Screen Capture', - description: 'Prevent screenshots and screen recording', - type: SettingType.switch_, - id: 'screen_capture', - ), - SettingItem( - name: 'External ID', - description: 'Device identifier for log enrichment', - type: SettingType.editable, - id: 'external_id', - ), - ]; - return Scaffold( appBar: AppBar(), body: SingleChildScrollView( @@ -40,30 +25,36 @@ class SecurityScreen extends ConsumerWidget { child: Padding( padding: const EdgeInsets.all(16), child: Column( + spacing: 12, children: [ const SecurityStatusCard(), const MalwareAlertCard(), - Section( - sectionTitle: 'App Integrity', - child: CategorySection( + TitledSection( + title: 'App Integrity', + child: SecurityCheckList( checks: checksByCategory[ThreatCategory.appIntegrity] ?? [], ), ), - Section( - sectionTitle: 'Device Security', - child: CategorySection( + TitledSection( + title: 'Device Security', + child: SecurityCheckList( checks: checksByCategory[ThreatCategory.deviceSecurity] ?? [], ), ), - Section( - sectionTitle: 'Runtime Status', - child: CategorySection( + TitledSection( + title: 'Runtime Status', + child: SecurityCheckList( checks: checksByCategory[ThreatCategory.runtimeStatus] ?? [], ), ), - Section( - sectionTitle: 'Other Settings', - child: SettingsSection(settings: otherSettings), + const TitledSection( + title: 'Other Settings', + child: SettingsGroup( + items: [ + ScreenCaptureTile(), + ExternalIdTile(), + ], + ), ), ], ), diff --git a/example/lib/screens/suspicious_apps_screen.dart b/example/lib/screens/suspicious_apps_screen.dart index 9b2d7b4..6b2b6b5 100644 --- a/example/lib/screens/suspicious_apps_screen.dart +++ b/example/lib/screens/suspicious_apps_screen.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:freerasp/freerasp.dart'; -import 'package:freerasp_example/providers/security_controller_provider.dart'; +import 'package:freerasp_example/providers/security_provider.dart'; class SuspiciousAppsScreen extends ConsumerWidget { const SuspiciousAppsScreen({super.key}); @@ -37,8 +37,12 @@ class SuspiciousAppsScreen extends ConsumerWidget { return ListTile( shape: RoundedRectangleBorder( borderRadius: BorderRadius.vertical( - top: isFirst ? const Radius.circular(radius) : Radius.zero, - bottom: isLast ? const Radius.circular(radius) : Radius.zero, + top: isFirst + ? const Radius.circular(radius) + : Radius.zero, + bottom: isLast + ? const Radius.circular(radius) + : Radius.zero, ), ), leading: Padding( diff --git a/example/lib/widgets/category_section.dart b/example/lib/widgets/category_section.dart deleted file mode 100644 index b955806..0000000 --- a/example/lib/widgets/category_section.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:freerasp_example/models/security_check.dart'; -import 'package:freerasp_example/widgets/list_item_card.dart'; - -class CategorySection extends StatelessWidget { - const CategorySection({ - required this.checks, - super.key, - }); - - final List checks; - - @override - Widget build(BuildContext context) { - if (checks.isEmpty) { - return const SizedBox.shrink(); - } - - return ListView.separated( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: checks.length, - itemBuilder: (context, index) { - final item = checks[index]; - final isFirst = index == 0; - final isLast = index == checks.length - 1; - - return ListItemCard( - title: item.name, - subtitle: item.description, - leading: Icon( - item.isSecure ? Icons.check_circle : Icons.error, - color: item.isSecure ? Colors.green : Colors.amber, - ), - isFirst: isFirst, - isLast: isLast, - ); - }, - separatorBuilder: (ctx, i) => const SizedBox(height: 2), - ); - } -} - diff --git a/example/lib/widgets/list_item_card.dart b/example/lib/widgets/common/grouped_list_item.dart similarity index 57% rename from example/lib/widgets/list_item_card.dart rename to example/lib/widgets/common/grouped_list_item.dart index 07c83d5..dbf6839 100644 --- a/example/lib/widgets/list_item_card.dart +++ b/example/lib/widgets/common/grouped_list_item.dart @@ -1,13 +1,12 @@ import 'package:flutter/material.dart'; -class ListItemCard extends StatelessWidget { - const ListItemCard({ +class GroupedListItem extends StatelessWidget { + const GroupedListItem({ required this.title, required this.subtitle, - this.isFirst = false, - this.isLast = false, this.leading, this.trailing, + this.onTap, super.key, }); @@ -15,20 +14,12 @@ class ListItemCard extends StatelessWidget { final String subtitle; final Widget? leading; final Widget? trailing; - final bool isFirst; - final bool isLast; + final VoidCallback? onTap; @override Widget build(BuildContext context) { - const radius = 16.0; - return ListTile( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.vertical( - top: isFirst ? const Radius.circular(radius) : Radius.zero, - bottom: isLast ? const Radius.circular(radius) : Radius.zero, - ), - ), + shape: const RoundedRectangleBorder(), leading: leading != null ? Padding( padding: const EdgeInsets.all(8), @@ -38,6 +29,7 @@ class ListItemCard extends StatelessWidget { title: Text(title), subtitle: Text(subtitle), trailing: trailing, + onTap: onTap, ); } } diff --git a/example/lib/widgets/section.dart b/example/lib/widgets/common/titled_section.dart similarity index 78% rename from example/lib/widgets/section.dart rename to example/lib/widgets/common/titled_section.dart index b46211f..2fd5570 100644 --- a/example/lib/widgets/section.dart +++ b/example/lib/widgets/common/titled_section.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; -class Section extends StatelessWidget { - const Section({ - required this.sectionTitle, +class TitledSection extends StatelessWidget { + const TitledSection({ + required this.title, required this.child, super.key, }); - final String sectionTitle; + final String title; final Widget child; @override @@ -22,13 +22,12 @@ class Section extends StatelessWidget { Padding( padding: const EdgeInsets.only(left: 8), child: Text( - sectionTitle, + title, style: textTheme.labelLarge?.copyWith(color: colors.primary), ), ), - child + child, ], ); } } - diff --git a/example/lib/widgets/malware_alert_card.dart b/example/lib/widgets/security/malware_card.dart similarity index 87% rename from example/lib/widgets/malware_alert_card.dart rename to example/lib/widgets/security/malware_card.dart index fb4267f..0286c99 100644 --- a/example/lib/widgets/malware_alert_card.dart +++ b/example/lib/widgets/security/malware_card.dart @@ -1,16 +1,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:freerasp_example/providers/security_controller_provider.dart'; +import 'package:freerasp_example/providers/security_provider.dart'; import 'package:freerasp_example/screens/suspicious_apps_screen.dart'; -class MalwareAlertCard extends ConsumerStatefulWidget { - const MalwareAlertCard({super.key}); +class MalwareCard extends ConsumerStatefulWidget { + const MalwareCard({super.key}); @override - ConsumerState createState() => _MalwareAlertCardState(); + ConsumerState createState() => _MalwareCardState(); } -class _MalwareAlertCardState extends ConsumerState { +class _MalwareCardState extends ConsumerState { @override Widget build(BuildContext context) { final securityState = ref.watch(securityControllerProvider); diff --git a/example/lib/widgets/security/security_group.dart b/example/lib/widgets/security/security_group.dart new file mode 100644 index 0000000..134522e --- /dev/null +++ b/example/lib/widgets/security/security_group.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:freerasp_example/models/security_check.dart'; +import 'package:freerasp_example/widgets/common/grouped_list_item.dart'; + +class SecurityGroup extends StatelessWidget { + const SecurityGroup({ + required this.checks, + super.key, + }); + + final List checks; + + @override + Widget build(BuildContext context) { + if (checks.isEmpty) { + return const SizedBox.shrink(); + } + + return Material( + type: MaterialType.transparency, + borderRadius: BorderRadius.circular(16), + clipBehavior: Clip.antiAlias, + child: Column( + spacing: 2, + children: checks.map((item) { + return GroupedListItem( + title: item.name, + subtitle: item.description, + leading: Icon( + item.isSecure ? Icons.check_circle : Icons.error, + color: item.isSecure ? Colors.green : Colors.amber, + ), + ); + }).toList(), + ), + ); + } +} diff --git a/example/lib/widgets/security/status_card.dart b/example/lib/widgets/security/status_card.dart new file mode 100644 index 0000000..1b5eca8 --- /dev/null +++ b/example/lib/widgets/security/status_card.dart @@ -0,0 +1,43 @@ +import 'package:avatar_glow/avatar_glow.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:freerasp_example/providers/security_provider.dart'; + +class StatusCard extends ConsumerWidget { + const StatusCard({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final securityState = ref.watch(securityControllerProvider); + final isSecure = securityState.isAllSecure; + + return Padding( + padding: const EdgeInsets.all(4), + child: ListTile( + contentPadding: const EdgeInsets.all(20), + leading: AvatarGlow( + glowColor: isSecure ? Colors.green : Colors.orange, + glowRadiusFactor: 0.4, + duration: const Duration(seconds: 1), + glowCount: 1, + child: Padding( + padding: const EdgeInsets.all(8), + child: Icon( + Icons.security, + color: isSecure ? Colors.green : Colors.orange, + size: 32, + ), + ), + ), + title: Text( + isSecure ? 'Device is Secure' : 'Device may be compromised', + ), + subtitle: Text( + isSecure + ? 'Checking security status...' + : 'See alerts for more details', + ), + ), + ); + } +} diff --git a/example/lib/widgets/security_status_card.dart b/example/lib/widgets/security_status_card.dart deleted file mode 100644 index af664cd..0000000 --- a/example/lib/widgets/security_status_card.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:avatar_glow/avatar_glow.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:freerasp_example/providers/security_controller_provider.dart'; - -class SecurityStatusCard extends ConsumerWidget { - const SecurityStatusCard({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final securityState = ref.watch(securityControllerProvider); - final isSecure = securityState.isAllSecure; - - return ListTile( - contentPadding: const EdgeInsets.all(20), - leading: AvatarGlow( - glowColor: isSecure ? Colors.green : Colors.orange, - glowRadiusFactor: 0.4, - duration: const Duration(seconds: 1), - glowCount: 1, - child: Padding( - padding: const EdgeInsets.all(8), - child: Icon( - Icons.security, - color: isSecure ? Colors.green : Colors.orange, - size: 32, - ), - ), - ), - title: Text( - isSecure ? 'Device is Secure' : 'Device may be compromised', - ), - subtitle: Text( - isSecure - ? 'Checking security status...' - : 'See alerts for more details', - ), - ); - } -} - diff --git a/example/lib/widgets/settings/settings_group.dart b/example/lib/widgets/settings/settings_group.dart new file mode 100644 index 0000000..44cef6d --- /dev/null +++ b/example/lib/widgets/settings/settings_group.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; + +class SettingsGroup extends StatelessWidget { + const SettingsGroup({ + required this.items, + super.key, + }); + + final List items; + + @override + Widget build(BuildContext context) { + if (items.isEmpty) { + return const SizedBox.shrink(); + } + + return Material( + type: MaterialType.transparency, + borderRadius: BorderRadius.circular(16), + clipBehavior: Clip.antiAlias, + child: Column( + spacing: 2, + children: items, + ), + ); + } +} diff --git a/example/lib/widgets/settings/tiles/external_id_tile.dart b/example/lib/widgets/settings/tiles/external_id_tile.dart new file mode 100644 index 0000000..e2c8231 --- /dev/null +++ b/example/lib/widgets/settings/tiles/external_id_tile.dart @@ -0,0 +1,93 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:freerasp_example/providers/external_id_provider.dart'; +import 'package:freerasp_example/widgets/common/grouped_list_item.dart'; + +class ExternalIdTile extends ConsumerWidget { + const ExternalIdTile({ + this.title = 'External ID', + super.key, + }); + + final String title; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final externalId = ref.watch(externalIdProvider); + final subtitle = externalId ?? 'Not set'; + + return GroupedListItem( + title: title, + subtitle: subtitle, + trailing: const Icon(Icons.chevron_right), + onTap: () => _showExternalIdDialog(context, ref), + ); + } + + void _showExternalIdDialog( + BuildContext context, + WidgetRef ref, + ) { + final currentId = ref.read(externalIdProvider); + final controller = TextEditingController(text: currentId ?? ''); + + showDialog( + context: context, + builder: (dialogContext) => AlertDialog( + title: Text(title), + content: TextField( + controller: controller, + decoration: const InputDecoration( + labelText: 'External ID', + hintText: 'Enter device identifier', + border: OutlineInputBorder(), + ), + autofocus: true, + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(dialogContext).pop(), + child: const Text('Cancel'), + ), + FilledButton( + onPressed: () async { + final newId = controller.text.trim(); + if (newId.isNotEmpty) { + try { + await ref + .read(externalIdProvider.notifier) + .setExternalId(newId); + if (dialogContext.mounted) { + Navigator.of(dialogContext).pop(); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('External ID saved successfully'), + duration: Duration(seconds: 2), + ), + ); + } + } catch (e) { + if (dialogContext.mounted) { + ScaffoldMessenger.of(dialogContext).showSnackBar( + SnackBar( + content: Text('Error saving external ID: $e'), + backgroundColor: Colors.red, + ), + ); + } + } + } else { + // Clear the external ID if empty + ref.read(externalIdProvider.notifier).clearExternalId(); + if (dialogContext.mounted) { + Navigator.of(dialogContext).pop(); + } + } + }, + child: const Text('Save'), + ), + ], + ), + ); + } +} diff --git a/example/lib/widgets/settings/tiles/screen_capture_tile.dart b/example/lib/widgets/settings/tiles/screen_capture_tile.dart new file mode 100644 index 0000000..8371def --- /dev/null +++ b/example/lib/widgets/settings/tiles/screen_capture_tile.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:freerasp_example/providers/screen_capture_provider.dart'; +import 'package:freerasp_example/widgets/common/grouped_list_item.dart'; + +class ScreenCaptureTile extends ConsumerWidget { + const ScreenCaptureTile({ + this.title = 'Block Screen Capture', + this.subtitle = 'Prevent screenshots and screen recording', + super.key, + }); + + final String title; + final String subtitle; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final screenCaptureState = ref.watch(screenCaptureProvider); + + final trailing = screenCaptureState.when( + data: (isBlocked) => Switch( + value: isBlocked, + onChanged: (_) => ref.read(screenCaptureProvider.notifier).toggle(), + ), + loading: () => const SizedBox.square( + dimension: 24, + child: CircularProgressIndicator(strokeWidth: 2), + ), + error: (_, __) => const Icon(Icons.error), + ); + + return GroupedListItem( + title: title, + subtitle: subtitle, + trailing: trailing, + ); + } +} diff --git a/example/lib/widgets/settings_section.dart b/example/lib/widgets/settings_section.dart deleted file mode 100644 index d825b3a..0000000 --- a/example/lib/widgets/settings_section.dart +++ /dev/null @@ -1,149 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:freerasp_example/models/setting_item.dart'; -import 'package:freerasp_example/providers/external_id_provider.dart'; -import 'package:freerasp_example/providers/screen_capture_provider.dart'; -import 'package:freerasp_example/widgets/list_item_card.dart'; - -class SettingsSection extends ConsumerWidget { - const SettingsSection({ - required this.settings, - super.key, - }); - - final List settings; - - @override - Widget build(BuildContext context, WidgetRef ref) { - if (settings.isEmpty) { - return const SizedBox.shrink(); - } - - return ListView.separated( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: settings.length, - itemBuilder: (context, index) { - final item = settings[index]; - final isFirst = index == 0; - final isLast = index == settings.length - 1; - - Widget? trailing; - String subtitle = item.description; - - if (item.type == SettingType.switch_) { - if (item.id == 'screen_capture') { - final screenCaptureState = ref.watch(screenCaptureProvider); - trailing = screenCaptureState.when( - data: (bool isBlocked) => Switch( - value: isBlocked, - onChanged: (bool value) async { - await ref.read(screenCaptureProvider.notifier).toggle(); - }, - ), - loading: () => const SizedBox( - width: 48, - height: 24, - child: Center( - child: SizedBox( - width: 16, - height: 16, - child: CircularProgressIndicator(strokeWidth: 2), - ), - ), - ), - error: (_, __) => const Icon(Icons.error), - ); - } - } else if (item.type == SettingType.editable) { - if (item.id == 'external_id') { - final externalId = ref.watch(externalIdProvider); - subtitle = externalId ?? 'Not set'; - trailing = const Icon(Icons.chevron_right); - } - } - - return GestureDetector( - onTap: item.type == SettingType.editable && item.id == 'external_id' - ? () => _showExternalIdDialog(context, ref, item) - : null, - child: ListItemCard( - title: item.name, - subtitle: subtitle, - isFirst: isFirst, - isLast: isLast, - trailing: trailing, - ), - ); - }, - separatorBuilder: (ctx, i) => const SizedBox(height: 2), - ); - } - - void _showExternalIdDialog( - BuildContext context, - WidgetRef ref, - SettingItem item, - ) { - final currentId = ref.read(externalIdProvider); - final controller = TextEditingController(text: currentId ?? ''); - - showDialog( - context: context, - builder: (dialogContext) => AlertDialog( - title: Text(item.name), - content: TextField( - controller: controller, - decoration: const InputDecoration( - labelText: 'External ID', - hintText: 'Enter device identifier', - border: OutlineInputBorder(), - ), - autofocus: true, - ), - actions: [ - TextButton( - onPressed: () => Navigator.of(dialogContext).pop(), - child: const Text('Cancel'), - ), - FilledButton( - onPressed: () async { - final newId = controller.text.trim(); - if (newId.isNotEmpty) { - try { - await ref.read(externalIdProvider.notifier).setExternalId(newId); - if (dialogContext.mounted) { - Navigator.of(dialogContext).pop(); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('External ID saved successfully'), - duration: Duration(seconds: 2), - ), - ); - } - } catch (e) { - if (dialogContext.mounted) { - ScaffoldMessenger.of(dialogContext).showSnackBar( - SnackBar( - content: Text('Error saving external ID: $e'), - backgroundColor: Colors.red, - ), - ); - } - } - } else { - // Clear the external ID if empty - ref.read(externalIdProvider.notifier).clearExternalId(); - if (dialogContext.mounted) { - Navigator.of(dialogContext).pop(); - } - } - }, - child: const Text('Save'), - ), - ], - ), - ); - } -} - From 8ac301d1bdb29f91e4df9967787592899553676b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Novotn=C3=BD?= <62177414+yardexx@users.noreply.github.com> Date: Fri, 2 Jan 2026 10:22:04 +0100 Subject: [PATCH 04/15] fix(example): restore correct widget filenames and class names Fix file and class naming mismatch caused by previous refactor. Add missing newline in screen_capture_provider.dart. --- example/lib/providers/screen_capture_provider.dart | 2 +- .../{malware_card.dart => malware_alert_card.dart} | 8 ++++---- .../{security_group.dart => security_check_list.dart} | 4 ++-- .../{status_card.dart => security_status_card.dart} | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) rename example/lib/widgets/security/{malware_card.dart => malware_alert_card.dart} (90%) rename example/lib/widgets/security/{security_group.dart => security_check_list.dart} (92%) rename example/lib/widgets/security/{status_card.dart => security_status_card.dart} (93%) diff --git a/example/lib/providers/screen_capture_provider.dart b/example/lib/providers/screen_capture_provider.dart index 6cbfced..0fa1f77 100644 --- a/example/lib/providers/screen_capture_provider.dart +++ b/example/lib/providers/screen_capture_provider.dart @@ -21,4 +21,4 @@ class ScreenCaptureNotifier extends AutoDisposeAsyncNotifier { await Talsec.instance.blockScreenCapture(enabled: !isScreenCaptureBlocked); state = AsyncValue.data(!isScreenCaptureBlocked); } -} \ No newline at end of file +} diff --git a/example/lib/widgets/security/malware_card.dart b/example/lib/widgets/security/malware_alert_card.dart similarity index 90% rename from example/lib/widgets/security/malware_card.dart rename to example/lib/widgets/security/malware_alert_card.dart index 0286c99..4ec3e7a 100644 --- a/example/lib/widgets/security/malware_card.dart +++ b/example/lib/widgets/security/malware_alert_card.dart @@ -3,14 +3,14 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:freerasp_example/providers/security_provider.dart'; import 'package:freerasp_example/screens/suspicious_apps_screen.dart'; -class MalwareCard extends ConsumerStatefulWidget { - const MalwareCard({super.key}); +class MalwareAlertCard extends ConsumerStatefulWidget { + const MalwareAlertCard({super.key}); @override - ConsumerState createState() => _MalwareCardState(); + ConsumerState createState() => _MalwareAlertCardState(); } -class _MalwareCardState extends ConsumerState { +class _MalwareAlertCardState extends ConsumerState { @override Widget build(BuildContext context) { final securityState = ref.watch(securityControllerProvider); diff --git a/example/lib/widgets/security/security_group.dart b/example/lib/widgets/security/security_check_list.dart similarity index 92% rename from example/lib/widgets/security/security_group.dart rename to example/lib/widgets/security/security_check_list.dart index 134522e..2b2d5bd 100644 --- a/example/lib/widgets/security/security_group.dart +++ b/example/lib/widgets/security/security_check_list.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:freerasp_example/models/security_check.dart'; import 'package:freerasp_example/widgets/common/grouped_list_item.dart'; -class SecurityGroup extends StatelessWidget { - const SecurityGroup({ +class SecurityCheckList extends StatelessWidget { + const SecurityCheckList({ required this.checks, super.key, }); diff --git a/example/lib/widgets/security/status_card.dart b/example/lib/widgets/security/security_status_card.dart similarity index 93% rename from example/lib/widgets/security/status_card.dart rename to example/lib/widgets/security/security_status_card.dart index 1b5eca8..ffc5e19 100644 --- a/example/lib/widgets/security/status_card.dart +++ b/example/lib/widgets/security/security_status_card.dart @@ -3,8 +3,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:freerasp_example/providers/security_provider.dart'; -class StatusCard extends ConsumerWidget { - const StatusCard({super.key}); +class SecurityStatusCard extends ConsumerWidget { + const SecurityStatusCard({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { From 118ec28425ce36ec75ac670eb051e75f44de5e3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Novotn=C3=BD?= <62177414+yardexx@users.noreply.github.com> Date: Fri, 2 Jan 2026 10:22:12 +0100 Subject: [PATCH 05/15] style(example): apply dart format and update analysis options --- analysis_options.yaml | 6 +++++- example/analysis_options.yaml | 8 ++++++++ example/lib/app/app.dart | 4 ++-- example/lib/config/talsec_config.dart | 2 -- example/lib/models/security_check.dart | 13 +++++++++++++ example/lib/models/security_state.dart | 10 +++++----- example/lib/theme/app_theme.dart | 5 +++-- 7 files changed, 36 insertions(+), 12 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 7dedb6c..933b5c1 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,5 +1,9 @@ include: package:very_good_analysis/analysis_options.yaml +linter: + rules: + public_member_api_docs: false + analyzer: exclude: - - '**/*.g.dart' \ No newline at end of file + - '**/*.g.dart' diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml index 9df80aa..1037f16 100644 --- a/example/analysis_options.yaml +++ b/example/analysis_options.yaml @@ -1 +1,9 @@ include: package:very_good_analysis/analysis_options.yaml + +linter: + rules: + public_member_api_docs: false + +analyzer: + exclude: + - '**/*.g.dart' \ No newline at end of file diff --git a/example/lib/app/app.dart b/example/lib/app/app.dart index f56f469..3b7d56a 100644 --- a/example/lib/app/app.dart +++ b/example/lib/app/app.dart @@ -13,7 +13,8 @@ class MyApp extends StatelessWidget { return ProviderScope( child: DynamicColorBuilder( builder: (lightDynamic, darkDynamic) { - final lightScheme = AppTheme.getScheme(lightDynamic, Brightness.light); + final lightScheme = + AppTheme.getScheme(lightDynamic, Brightness.light); final darkScheme = AppTheme.getScheme(darkDynamic, Brightness.dark); return MaterialApp( @@ -28,4 +29,3 @@ class MyApp extends StatelessWidget { ); } } - diff --git a/example/lib/config/talsec_config.dart b/example/lib/config/talsec_config.dart index d2b8285..de0d282 100644 --- a/example/lib/config/talsec_config.dart +++ b/example/lib/config/talsec_config.dart @@ -22,7 +22,5 @@ TalsecConfig createTalsecConfig() { teamId: 'M8AK35...', ), watcherMail: 'your_mail@example.com', - isProd: true, ); } - diff --git a/example/lib/models/security_check.dart b/example/lib/models/security_check.dart index 4003495..5247e03 100644 --- a/example/lib/models/security_check.dart +++ b/example/lib/models/security_check.dart @@ -44,4 +44,17 @@ class SecurityCheck { /// Returns the appropriate description based on security status. String get description => isSecure ? secureDescription : insecureDescription; + + SecurityCheck copyWith({ + bool? isSecure, + }) { + return SecurityCheck( + threat: threat, + name: name, + secureDescription: secureDescription, + insecureDescription: insecureDescription, + category: category, + isSecure: isSecure ?? this.isSecure, + ); + } } diff --git a/example/lib/models/security_state.dart b/example/lib/models/security_state.dart index 83cabe8..2a7f5b3 100644 --- a/example/lib/models/security_state.dart +++ b/example/lib/models/security_state.dart @@ -3,17 +3,17 @@ import 'package:freerasp_example/models/security_check.dart'; /// State class for security checks managed by Riverpod. class SecurityState { - - /// Creates an initial state with all checks initialized. - factory SecurityState.initial(List checks) { - return SecurityState(checks: checks); - } /// Creates a [SecurityState]. SecurityState({ required this.checks, this.detectedMalware = const [], }); + /// Creates an initial state with all checks initialized. + factory SecurityState.initial(List checks) { + return SecurityState(checks: checks); + } + /// List of all security checks. final List checks; diff --git a/example/lib/theme/app_theme.dart b/example/lib/theme/app_theme.dart index 8dfd13e..541167c 100644 --- a/example/lib/theme/app_theme.dart +++ b/example/lib/theme/app_theme.dart @@ -7,7 +7,9 @@ class AppTheme { static const _fallbackSurface = Color(0xFF191B24); static ColorScheme getScheme( - ColorScheme? dynamicScheme, Brightness brightness) { + ColorScheme? dynamicScheme, + Brightness brightness, + ) { if (dynamicScheme != null) { return ColorScheme.fromSeed( seedColor: dynamicScheme.primary, @@ -53,4 +55,3 @@ class AppTheme { ); } } - From 9dcc3b79475b41286106d103cad3303eeec25fa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Novotn=C3=BD?= <62177414+yardexx@users.noreply.github.com> Date: Fri, 2 Jan 2026 12:07:06 +0100 Subject: [PATCH 06/15] ci: upload generated apk as artifact --- .github/workflows/flutter-ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/flutter-ci.yml b/.github/workflows/flutter-ci.yml index 72246fd..129b385 100644 --- a/.github/workflows/flutter-ci.yml +++ b/.github/workflows/flutter-ci.yml @@ -84,6 +84,12 @@ jobs: working-directory: example run: flutter build apk --release + - name: 📤 Upload APK + uses: actions/upload-artifact@v4 + with: + name: app-release + path: example/build/app/outputs/flutter-apk/app-release.apk + build-ios: runs-on: macos-latest needs: test From 346872038fb3261609cd4fd245db4587ce1c6731 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Novotn=C3=BD?= <62177414+yardexx@users.noreply.github.com> Date: Fri, 2 Jan 2026 12:14:19 +0100 Subject: [PATCH 07/15] chore: update README --- example/README.md | 121 ++-------------------------------------------- 1 file changed, 5 insertions(+), 116 deletions(-) diff --git a/example/README.md b/example/README.md index 33ee5bd..954431c 100644 --- a/example/README.md +++ b/example/README.md @@ -1,69 +1,7 @@ # freeRASP Example App -This example application demonstrates how to use the freeRASP plugin to protect your Flutter application from various security threats. - -## Features - -The example app showcases the following freeRASP capabilities: - -### Security Status Monitoring -- Real-time security status display showing overall device security -- Visual indicators for secure and compromised states -- Categorized threat detection (App Integrity, Device Security, Runtime Status) - -### Threat Detection -The app monitors and displays various security threats including: -- **App Integrity**: App signature verification, obfuscation, unofficial stores, simulator detection, device binding, multi-instance detection -- **Device Security**: Root/jailbreak detection, hooks, secure hardware availability, developer mode, debugger attachment, passcode protection, ADB status, VPN detection -- **Runtime Status**: Screenshot and screen recording detection - -### Screen Capture Blocking -- Toggle to enable/disable screen capture blocking -- Prevents screenshots and screen recordings when enabled -- Real-time status indicator - -### External ID Management -- Set a custom external ID to enrich security logs -- Useful for uniquely identifying devices in your backend -- Stored via `Talsec.instance.storeExternalId()` - -### Malware Detection -- Automatic detection of suspicious apps -- Alert cards showing detected malware -- Detailed view of suspicious applications with app icons and reasons - -## Project Structure - -``` -lib/ -├── app/ -│ └── app.dart # Main app widget with theme configuration -├── config/ -│ └── talsec_config.dart # Shared Talsec configuration -├── main.dart # App entry point with Talsec initialization -├── models/ -│ ├── security_check.dart # Security check model -│ ├── security_state.dart # Security state management -│ └── setting_item.dart # Settings item model -├── providers/ -│ ├── external_id_provider.dart # External ID state management -│ ├── screen_capture_notifier.dart # Screen capture blocking logic -│ ├── screen_capture_provider.dart # Screen capture provider -│ ├── security_controller.dart # Main security monitoring controller -│ └── security_controller_provider.dart # Security controller provider -├── screens/ -│ ├── security_screen.dart # Main security dashboard -│ └── suspicious_apps_screen.dart # Suspicious apps detail view -├── theme/ -│ └── app_theme.dart # App theme configuration -└── widgets/ - ├── category_section.dart # Threat category display - ├── list_item_card.dart # Reusable list item widget - ├── malware_alert_card.dart # Malware detection alert - ├── section.dart # Section wrapper widget - ├── security_status_card.dart # Security status display - └── settings_section.dart # Settings section widget -``` +This example application demonstrates how to use the freeRASP plugin to protect your Flutter +application from various security threats. ## Getting Started @@ -84,67 +22,18 @@ lib/ flutter pub get ``` -3. Configure Talsec: - - Open `lib/config/talsec_config.dart` - - Update the configuration with your app's details: - - Android package name - - Signing certificate hashes - - Supported app stores - - iOS bundle IDs and team ID - - Watcher email address - -4. Run the app: +3. Run the app: ```bash flutter run ``` -## Configuration - -The Talsec configuration is centralized in `lib/config/talsec_config.dart`. You need to customize: - -- **Android Config**: - - `packageName`: Your app's package name - - `signingCertHashes`: SHA-256 hashes of your signing certificates - - `supportedStores`: List of supported app stores - - `malwareConfig`: Malware detection settings - -- **iOS Config**: - - `bundleIds`: Your app's bundle identifiers - - `teamId`: Your Apple Developer team ID - -- **General**: - - `watcherMail`: Email for security notifications - - `isProd`: Set to `true` for production, `false` for development - -## Usage Examples - -### Monitoring Security Threats - -The app automatically monitors threats through the `SecurityController`. Threats are detected via: -- Stream subscription to `Talsec.instance.onThreatDetected` -- Threat callback listeners for malware detection - -### Blocking Screen Capture - -```dart -// Toggle screen capture blocking -await ref.read(screenCaptureProvider.notifier).toggle(); -``` - -### Setting External ID - -```dart -// Set external ID for log enrichment -await ref.read(externalIdProvider.notifier).setExternalId('device-123'); -``` - ## Architecture The app uses: + +- **freeRASP** for security monitoring - **Riverpod** for state management - **Material Design 3** with dynamic color theming -- **Provider pattern** for security monitoring -- **Reactive UI** that updates automatically when threats are detected ## Resources From 78a3e5ce291360d3192b129051b7656a5343dece Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Novotn=C3=BD?= <62177414+yardexx@users.noreply.github.com> Date: Fri, 2 Jan 2026 12:14:49 +0100 Subject: [PATCH 08/15] ci: add artifact caching --- .github/workflows/flutter-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/flutter-ci.yml b/.github/workflows/flutter-ci.yml index 129b385..7c3fa2b 100644 --- a/.github/workflows/flutter-ci.yml +++ b/.github/workflows/flutter-ci.yml @@ -85,9 +85,9 @@ jobs: run: flutter build apk --release - name: 📤 Upload APK - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: - name: app-release + name: app-demo path: example/build/app/outputs/flutter-apk/app-release.apk build-ios: From ee1a7f6fd2a1c1066478a5782f2ed68bc579105e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Novotn=C3=BD?= <62177414+yardexx@users.noreply.github.com> Date: Fri, 2 Jan 2026 12:15:31 +0100 Subject: [PATCH 09/15] ci: raise actions versions --- .github/workflows/flutter-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/flutter-ci.yml b/.github/workflows/flutter-ci.yml index 7c3fa2b..7176f03 100644 --- a/.github/workflows/flutter-ci.yml +++ b/.github/workflows/flutter-ci.yml @@ -71,7 +71,7 @@ jobs: steps: - name: 📚 Git Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: 🐦 Setup Flutter uses: subosito/flutter-action@v2.16.0 @@ -96,7 +96,7 @@ jobs: steps: - name: 📚 Git Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: 🐦 Setup Flutter uses: subosito/flutter-action@v2.16.0 From ac97aea040115ef1b9714ec48191fd3729eeb08a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Novotn=C3=BD?= <62177414+yardexx@users.noreply.github.com> Date: Fri, 2 Jan 2026 13:44:55 +0100 Subject: [PATCH 10/15] style: update check descriptions --- example/lib/providers/security_provider.dart | 57 ++++++++++++++------ 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/example/lib/providers/security_provider.dart b/example/lib/providers/security_provider.dart index a7810fc..b7b12a7 100644 --- a/example/lib/providers/security_provider.dart +++ b/example/lib/providers/security_provider.dart @@ -31,36 +31,35 @@ class SecurityController extends AutoDisposeNotifier { SecurityCheck( threat: Threat.appIntegrity, name: 'App Integrity', - secureDescription: 'Application signature verified and intact.', - insecureDescription: 'Signature verification failed.', + secureDescription: 'Application signature is verified and intact.', + insecureDescription: 'Application signature verification failed.', category: ThreatCategory.appIntegrity, ), SecurityCheck( threat: Threat.obfuscationIssues, name: 'Obfuscation', secureDescription: 'Application code is properly obfuscated.', - insecureDescription: 'Application code obfuscation disabled.', + insecureDescription: 'Application code is not obfuscated.', category: ThreatCategory.appIntegrity, ), SecurityCheck( threat: Threat.unofficialStore, name: 'Unofficial Store', - secureDescription: 'Application installed from official store.', - insecureDescription: - 'Application installed from unknown or unofficial source.', + secureDescription: 'Application installed from an official store.', + insecureDescription: 'Application installed from an unknown source.', category: ThreatCategory.appIntegrity, ), SecurityCheck( threat: Threat.simulator, name: 'Simulator', secureDescription: 'Running on a real device.', - insecureDescription: 'Running on simulator or emulator.', + insecureDescription: 'Running on a simulator or emulator.', category: ThreatCategory.appIntegrity, ), SecurityCheck( threat: Threat.deviceBinding, name: 'Device Binding', - secureDescription: 'Application properly bound to device.', + secureDescription: 'Application properly bound to the device.', insecureDescription: 'Device binding compromised.', category: ThreatCategory.appIntegrity, ), @@ -76,8 +75,8 @@ class SecurityController extends AutoDisposeNotifier { SecurityCheck( threat: Threat.privilegedAccess, name: 'Root / Jailbreak', - secureDescription: 'System is running securely (sandbox).', - insecureDescription: 'System security compromised.', + secureDescription: 'System is running securely (standard environment).', + insecureDescription: 'Privileged access (Root/Jailbreak) detected.', category: ThreatCategory.deviceSecurity, ), SecurityCheck( @@ -90,7 +89,7 @@ class SecurityController extends AutoDisposeNotifier { SecurityCheck( threat: Threat.secureHardwareNotAvailable, name: 'Secure Hardware', - secureDescription: 'Secure hardware available and functioning.', + secureDescription: 'Secure hardware available.', insecureDescription: 'Secure hardware unavailable.', category: ThreatCategory.deviceSecurity, ), @@ -98,7 +97,7 @@ class SecurityController extends AutoDisposeNotifier { threat: Threat.devMode, name: 'Developer Mode', secureDescription: 'Developer options are disabled.', - insecureDescription: 'Developer mode is enabled.', + insecureDescription: 'Developer options are enabled.', category: ThreatCategory.deviceSecurity, ), SecurityCheck( @@ -112,14 +111,14 @@ class SecurityController extends AutoDisposeNotifier { threat: Threat.passcode, name: 'Passcode', secureDescription: 'Device is protected with a passcode.', - insecureDescription: 'Device is not password protected.', + insecureDescription: 'Device is not protected with a passcode.', category: ThreatCategory.deviceSecurity, ), SecurityCheck( threat: Threat.adbEnabled, name: 'ADB Enabled', secureDescription: 'USB debugging (ADB) is disabled.', - insecureDescription: 'USB debugging (ADB) enabled.', + insecureDescription: 'USB debugging (ADB) is enabled.', category: ThreatCategory.deviceSecurity, ), @@ -127,7 +126,7 @@ class SecurityController extends AutoDisposeNotifier { threat: Threat.systemVPN, name: 'System VPN', secureDescription: 'No VPN active.', - insecureDescription: 'VPN is active.', + insecureDescription: 'Network traffic is routed via VPN.', category: ThreatCategory.deviceSecurity, ), SecurityCheck( @@ -141,7 +140,28 @@ class SecurityController extends AutoDisposeNotifier { threat: Threat.screenRecording, name: 'Screen Recording', secureDescription: 'No screen recording detected.', - insecureDescription: 'Screen recording active.', + insecureDescription: 'Screen recording detected.', + category: ThreatCategory.runtimeStatus, + ), + SecurityCheck( + threat: Threat.locationSpoofing, + name: 'Location Spoofing', + secureDescription: 'Device location is valid.', + insecureDescription: 'Device location is being manipulated.', + category: ThreatCategory.runtimeStatus, + ), + SecurityCheck( + threat: Threat.timeSpoofing, + name: 'Time Spoofing', + secureDescription: 'Device time is correct.', + insecureDescription: 'Device time is out of sync.', + category: ThreatCategory.runtimeStatus, + ), + SecurityCheck( + threat: Threat.unsecureWiFi, + name: 'Unsecure Wi-Fi', + secureDescription: 'Connected to a secure Wi-Fi network.', + insecureDescription: 'Connected to an unsecure Wi-Fi network.', category: ThreatCategory.runtimeStatus, ), ]; @@ -167,10 +187,13 @@ class SecurityController extends AutoDisposeNotifier { onADBEnabled: () => _handleThreat(Threat.adbEnabled), onDevMode: () => _handleThreat(Threat.devMode), onMultiInstance: () => _handleThreat(Threat.multiInstance), + onUnsecureWiFi: () => _handleThreat(Threat.unsecureWiFi), + onTimeSpoofing: () => _handleThreat(Threat.timeSpoofing), + onLocationSpoofing: () => _handleThreat(Threat.locationSpoofing), onMalware: _handleMalware, ); - Talsec.instance.attachListener(threatCallback); + await Talsec.instance.attachListener(threatCallback); } void _handleThreat(Threat type) { From bcbc1387988215594ba0dce0fa57ad111a50322a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Novotn=C3=BD?= <62177414+yardexx@users.noreply.github.com> Date: Fri, 2 Jan 2026 13:45:48 +0100 Subject: [PATCH 11/15] chore: update CHANGELOG --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27bf515..405e30e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [7.3.1] - 2025-10-20 +- Android SDK version: 17.0.0 +- iOS SDK version: 6.13.0 + +### Flutter + +#### Changed +- Updated example application + ## [7.3.0] - 2025-10-20 - Android SDK version: 17.0.0 - iOS SDK version: 6.13.0 From 1a7f65f9e92c25da2eeb7d29f9646bcb55e4d9cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Novotn=C3=BD?= <62177414+yardexx@users.noreply.github.com> Date: Fri, 2 Jan 2026 13:45:55 +0100 Subject: [PATCH 12/15] chore: raise version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index c3d8a2f..5aaadce 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: freerasp description: Flutter library for improving app security and threat monitoring on Android and iOS mobile devices. Learn more about provided features on the freeRASP's homepage first. -version: 7.3.0 +version: 7.3.1 homepage: https://www.talsec.app/freerasp-in-app-protection-security-talsec repository: https://github.com/talsec/Free-RASP-Flutter From 5b823d2f3f501b54917761b01eeedd8e1876a225 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Novotn=C3=BD?= <62177414+yardexx@users.noreply.github.com> Date: Fri, 2 Jan 2026 13:55:06 +0100 Subject: [PATCH 13/15] style: formatting --- example/lib/config/talsec_config.dart | 3 ++- example/lib/main.dart | 13 ++----------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/example/lib/config/talsec_config.dart b/example/lib/config/talsec_config.dart index de0d282..e818645 100644 --- a/example/lib/config/talsec_config.dart +++ b/example/lib/config/talsec_config.dart @@ -2,7 +2,8 @@ import 'package:freerasp/freerasp.dart'; /// Creates and returns the Talsec configuration for the example app. /// -/// This configuration is used to initialize Talsec with Android and iOS settings. +/// This configuration is used to initialize Talsec with Android and iOS +/// settings. TalsecConfig createTalsecConfig() { return TalsecConfig( androidConfig: AndroidConfig( diff --git a/example/lib/main.dart b/example/lib/main.dart index 4b1bd29..ea8ce05 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -8,17 +8,8 @@ Future main() async { WidgetsFlutterBinding.ensureInitialized(); await Permission.locationWhenInUse.request(); - // Initialize Talsec configuration - try { - final config = createTalsecConfig(); - await Talsec.instance.start(config); - } catch (e) { - // Log error but allow app to continue - // In production, you might want to use a logging service or show an error dialog - debugPrint('Error initializing Talsec: $e'); - // Re-throw if you want the app to fail fast on initialization errors - // rethrow; - } + final config = createTalsecConfig(); + await Talsec.instance.start(config); runApp(const MyApp()); } From b356e142b5201e79fe572a4c6b9baa0289c925ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Novotn=C3=BD?= <62177414+yardexx@users.noreply.github.com> Date: Fri, 2 Jan 2026 14:08:10 +0100 Subject: [PATCH 14/15] chore(android): change package name --- example/android/app/build.gradle | 4 ++-- example/android/app/src/debug/AndroidManifest.xml | 2 +- example/android/app/src/main/AndroidManifest.xml | 2 +- .../talsec/freerasp/demo}/MainActivity.kt | 2 +- example/android/app/src/profile/AndroidManifest.xml | 2 +- example/lib/config/talsec_config.dart | 4 ++-- 6 files changed, 8 insertions(+), 8 deletions(-) rename example/android/app/src/main/kotlin/{com/aheaditec/freerasp_example => app/talsec/freerasp/demo}/MainActivity.kt (71%) diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 674e5e9..443e953 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -23,7 +23,7 @@ if (flutterVersionName == null) { } android { - namespace 'com.aheaditec.freerasp_example' + namespace 'app.talsec.freerasp.demo' compileSdkVersion 35 ndkVersion = "27.1.12297006" @@ -42,7 +42,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.aheaditec.freerasp_example" + applicationId "app.talsec.freerasp.demo" // Talsec library needs higher version than default (16) minSdkVersion flutter.minSdkVersion targetSdkVersion flutter.targetSdkVersion diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml index 5070f0f..585acdd 100644 --- a/example/android/app/src/debug/AndroidManifest.xml +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="app.talsec.freerasp.demo"> diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index f700a23..500a50d 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="app.talsec.freerasp.demo"> diff --git a/example/android/app/src/main/kotlin/com/aheaditec/freerasp_example/MainActivity.kt b/example/android/app/src/main/kotlin/app/talsec/freerasp/demo/MainActivity.kt similarity index 71% rename from example/android/app/src/main/kotlin/com/aheaditec/freerasp_example/MainActivity.kt rename to example/android/app/src/main/kotlin/app/talsec/freerasp/demo/MainActivity.kt index 8108737..d0082ac 100644 --- a/example/android/app/src/main/kotlin/com/aheaditec/freerasp_example/MainActivity.kt +++ b/example/android/app/src/main/kotlin/app/talsec/freerasp/demo/MainActivity.kt @@ -1,4 +1,4 @@ -package com.aheaditec.freerasp_example +package app.talsec.freerasp.demo import io.flutter.embedding.android.FlutterActivity diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml index 5070f0f..585acdd 100644 --- a/example/android/app/src/profile/AndroidManifest.xml +++ b/example/android/app/src/profile/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="app.talsec.freerasp.demo"> diff --git a/example/lib/config/talsec_config.dart b/example/lib/config/talsec_config.dart index e818645..cd0e1dc 100644 --- a/example/lib/config/talsec_config.dart +++ b/example/lib/config/talsec_config.dart @@ -7,11 +7,11 @@ import 'package:freerasp/freerasp.dart'; TalsecConfig createTalsecConfig() { return TalsecConfig( androidConfig: AndroidConfig( - packageName: 'com.aheaditec.freeraspExample', + packageName: 'app.talsec.freerasp.app', signingCertHashes: ['AKoRuyLMM91E7lX/Zqp3u4jMmd0A7hH/Iqozu0TMVd0='], supportedStores: ['com.sec.android.app.samsungapps'], malwareConfig: MalwareConfig( - blacklistedPackageNames: ['com.aheaditec.freeraspExample'], + blacklistedPackageNames: ['com.google.android.youtube'], suspiciousPermissions: [ ['android.permission.CAMERA'], ['android.permission.READ_SMS', 'android.permission.READ_CONTACTS'], From 7e5bea3a508261a0b8faf91726a424dbe92d7937 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Novotn=C3=BD?= <62177414+yardexx@users.noreply.github.com> Date: Fri, 2 Jan 2026 14:24:34 +0100 Subject: [PATCH 15/15] chore(android): change package name --- example/android/app/build.gradle | 4 ++-- example/android/app/src/debug/AndroidManifest.xml | 2 +- example/android/app/src/main/AndroidManifest.xml | 2 +- .../talsec/{freerasp/demo => demo/freerasp}/MainActivity.kt | 2 +- example/android/app/src/profile/AndroidManifest.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) rename example/android/app/src/main/kotlin/app/talsec/{freerasp/demo => demo/freerasp}/MainActivity.kt (74%) diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 443e953..f1c04f2 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -23,7 +23,7 @@ if (flutterVersionName == null) { } android { - namespace 'app.talsec.freerasp.demo' + namespace 'app.talsec.demo.freerasp' compileSdkVersion 35 ndkVersion = "27.1.12297006" @@ -42,7 +42,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "app.talsec.freerasp.demo" + applicationId "app.talsec.demo.freerasp" // Talsec library needs higher version than default (16) minSdkVersion flutter.minSdkVersion targetSdkVersion flutter.targetSdkVersion diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml index 585acdd..0f30652 100644 --- a/example/android/app/src/debug/AndroidManifest.xml +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="app.talsec.demo.freerasp"> diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 500a50d..d986779 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="app.talsec.demo.freerasp"> diff --git a/example/android/app/src/main/kotlin/app/talsec/freerasp/demo/MainActivity.kt b/example/android/app/src/main/kotlin/app/talsec/demo/freerasp/MainActivity.kt similarity index 74% rename from example/android/app/src/main/kotlin/app/talsec/freerasp/demo/MainActivity.kt rename to example/android/app/src/main/kotlin/app/talsec/demo/freerasp/MainActivity.kt index d0082ac..7fef11f 100644 --- a/example/android/app/src/main/kotlin/app/talsec/freerasp/demo/MainActivity.kt +++ b/example/android/app/src/main/kotlin/app/talsec/demo/freerasp/MainActivity.kt @@ -1,4 +1,4 @@ -package app.talsec.freerasp.demo +package app.talsec.demo.freerasp import io.flutter.embedding.android.FlutterActivity diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml index 585acdd..0f30652 100644 --- a/example/android/app/src/profile/AndroidManifest.xml +++ b/example/android/app/src/profile/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="app.talsec.demo.freerasp">