From e85f273684a5971f060fe591a25b39c6ee3964f1 Mon Sep 17 00:00:00 2001
From: "Jona T. Feucht" <14951074+jonafeucht@users.noreply.github.com>
Date: Fri, 5 Jun 2026 13:58:28 +0200
Subject: [PATCH 01/15] chore: remove redundant folders
---
CHANGELOG.md | 2 +
example/pubspec.lock | 2 +-
ios/.gitignore | 38 -------------------
ios/Assets/.gitkeep | 0
.../SensitiveContentAnalysisPlugin.swift | 1 -
ios/sensitive_content_analysis.podspec | 1 -
.../SensitiveContentAnalysisPlugin.swift | 1 -
macos/sensitive_content_analysis.podspec | 1 -
8 files changed, 3 insertions(+), 43 deletions(-)
delete mode 100644 ios/.gitignore
delete mode 100644 ios/Assets/.gitkeep
delete mode 120000 ios/Classes/SensitiveContentAnalysisPlugin.swift
delete mode 120000 ios/sensitive_content_analysis.podspec
delete mode 120000 macos/Classes/SensitiveContentAnalysisPlugin.swift
delete mode 120000 macos/sensitive_content_analysis.podspec
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2a8c35c..a5c11c9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,5 @@
+## 2.0.2
+- Fix: Data Race & Main Thread Violation
## 2.0.0
- Support for Swift Package Manager.
diff --git a/example/pubspec.lock b/example/pubspec.lock
index 2d3b29e..23c1d50 100644
--- a/example/pubspec.lock
+++ b/example/pubspec.lock
@@ -445,7 +445,7 @@ packages:
path: ".."
relative: true
source: path
- version: "2.0.1"
+ version: "2.0.2"
sky_engine:
dependency: transitive
description: flutter
diff --git a/ios/.gitignore b/ios/.gitignore
deleted file mode 100644
index 0c88507..0000000
--- a/ios/.gitignore
+++ /dev/null
@@ -1,38 +0,0 @@
-.idea/
-.vagrant/
-.sconsign.dblite
-.svn/
-
-.DS_Store
-*.swp
-profile
-
-DerivedData/
-build/
-GeneratedPluginRegistrant.h
-GeneratedPluginRegistrant.m
-
-.generated/
-
-*.pbxuser
-*.mode1v3
-*.mode2v3
-*.perspectivev3
-
-!default.pbxuser
-!default.mode1v3
-!default.mode2v3
-!default.perspectivev3
-
-xcuserdata
-
-*.moved-aside
-
-*.pyc
-*sync/
-Icon?
-.tags*
-
-/Flutter/Generated.xcconfig
-/Flutter/ephemeral/
-/Flutter/flutter_export_environment.sh
\ No newline at end of file
diff --git a/ios/Assets/.gitkeep b/ios/Assets/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/ios/Classes/SensitiveContentAnalysisPlugin.swift b/ios/Classes/SensitiveContentAnalysisPlugin.swift
deleted file mode 120000
index 2369ff7..0000000
--- a/ios/Classes/SensitiveContentAnalysisPlugin.swift
+++ /dev/null
@@ -1 +0,0 @@
-../../darwin/sensitive_content_analysis/Sources/sensitive_content_analysis/SensitiveContentAnalysisPlugin.swift
\ No newline at end of file
diff --git a/ios/sensitive_content_analysis.podspec b/ios/sensitive_content_analysis.podspec
deleted file mode 120000
index 3889939..0000000
--- a/ios/sensitive_content_analysis.podspec
+++ /dev/null
@@ -1 +0,0 @@
-../darwin/sensitive_content_analysis/sensitive_content_analysis.podspec
\ No newline at end of file
diff --git a/macos/Classes/SensitiveContentAnalysisPlugin.swift b/macos/Classes/SensitiveContentAnalysisPlugin.swift
deleted file mode 120000
index 2369ff7..0000000
--- a/macos/Classes/SensitiveContentAnalysisPlugin.swift
+++ /dev/null
@@ -1 +0,0 @@
-../../darwin/sensitive_content_analysis/Sources/sensitive_content_analysis/SensitiveContentAnalysisPlugin.swift
\ No newline at end of file
diff --git a/macos/sensitive_content_analysis.podspec b/macos/sensitive_content_analysis.podspec
deleted file mode 120000
index 3889939..0000000
--- a/macos/sensitive_content_analysis.podspec
+++ /dev/null
@@ -1 +0,0 @@
-../darwin/sensitive_content_analysis/sensitive_content_analysis.podspec
\ No newline at end of file
From 50db7d659216393ff912eaca1829cd1f4e99e6a4 Mon Sep 17 00:00:00 2001
From: "Jona T. Feucht" <14951074+jonafeucht@users.noreply.github.com>
Date: Fri, 5 Jun 2026 16:36:42 +0200
Subject: [PATCH 02/15] fix(darwin): add backwards compatibility for cocoapods
---
CHANGELOG.md | 4 +++
.../sensitive_content_analysis.podspec | 3 +-
.../xcschemes/xcschememanagement.plist | 14 ++++++++++
.../sensitive_content_analysis/Package.swift | 11 ++++----
.../SensitiveContentAnalysisPlugin.h | 4 ---
example/ios/Podfile.lock | 26 +++++++++++++++++
example/ios/Runner.xcodeproj/project.pbxproj | 28 -------------------
example/pubspec.yaml | 5 +++-
pubspec.yaml | 4 +--
9 files changed, 57 insertions(+), 42 deletions(-)
rename darwin/{sensitive_content_analysis => }/sensitive_content_analysis.podspec (89%)
create mode 100644 darwin/sensitive_content_analysis/.swiftpm/xcode/xcuserdata/jonafeucht.xcuserdatad/xcschemes/xcschememanagement.plist
delete mode 100644 darwin/sensitive_content_analysis/Sources/sensitive_content_analysis/SensitiveContentAnalysisPlugin.h
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a5c11c9..5b00a69 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
+## 2.0.3
+- Fix: Add backwards compatibility for cocoapods.
+
## 2.0.2
- Fix: Data Race & Main Thread Violation
+
## 2.0.0
- Support for Swift Package Manager.
diff --git a/darwin/sensitive_content_analysis/sensitive_content_analysis.podspec b/darwin/sensitive_content_analysis.podspec
similarity index 89%
rename from darwin/sensitive_content_analysis/sensitive_content_analysis.podspec
rename to darwin/sensitive_content_analysis.podspec
index ed02976..7ea18a0 100644
--- a/darwin/sensitive_content_analysis/sensitive_content_analysis.podspec
+++ b/darwin/sensitive_content_analysis.podspec
@@ -14,8 +14,7 @@ A new Flutter plugin project supporting Sensitive Content Analysis.
s.author = { 'Your Company' => 'email@example.com' }
s.source = { :path => '.' }
- s.source_files = 'Sources/sensitive_content_analysis/**/*'
- s.public_header_files = 'Sources/sensitive_content_analysis/**/*.h'
+ s.source_files = 'sensitive_content_analysis/Sources/sensitive_content_analysis/**/*'
s.swift_version = '5.5'
s.ios.deployment_target = '13.0'
diff --git a/darwin/sensitive_content_analysis/.swiftpm/xcode/xcuserdata/jonafeucht.xcuserdatad/xcschemes/xcschememanagement.plist b/darwin/sensitive_content_analysis/.swiftpm/xcode/xcuserdata/jonafeucht.xcuserdatad/xcschemes/xcschememanagement.plist
new file mode 100644
index 0000000..5ca3b98
--- /dev/null
+++ b/darwin/sensitive_content_analysis/.swiftpm/xcode/xcuserdata/jonafeucht.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -0,0 +1,14 @@
+
+
+
+
+ SchemeUserState
+
+ sensitive-content-analysis.xcscheme_^#shared#^_
+
+ orderHint
+ 9
+
+
+
+
diff --git a/darwin/sensitive_content_analysis/Package.swift b/darwin/sensitive_content_analysis/Package.swift
index c142b34..2c28c85 100644
--- a/darwin/sensitive_content_analysis/Package.swift
+++ b/darwin/sensitive_content_analysis/Package.swift
@@ -6,7 +6,7 @@ let package = Package(
name: "sensitive_content_analysis",
platforms: [
.iOS("13.0"),
- .macOS("12.0")
+ .macOS("12.0"),
],
products: [
.library(
@@ -14,20 +14,21 @@ let package = Package(
)
],
dependencies: [
- .package(name: "FlutterFramework", path: "../FlutterFramework"),
+ .package(path: "../FlutterFramework")
],
targets: [
.target(
name: "sensitive_content_analysis",
dependencies: [
- .product(name: "FlutterFramework", package: "FlutterFramework"),
+ .product(name: "FlutterFramework", package: "FlutterFramework")
],
+ path: ".",
resources: [],
linkerSettings: [
.linkedFramework("Flutter", .when(platforms: [.iOS])),
.linkedFramework("FlutterMacOS", .when(platforms: [.macOS])),
- .linkedFramework("SensitiveContentAnalysis", .when(platforms: [.iOS, .macOS]))
+ .linkedFramework("SensitiveContentAnalysis", .when(platforms: [.iOS, .macOS])),
]
)
]
-)
\ No newline at end of file
+)
diff --git a/darwin/sensitive_content_analysis/Sources/sensitive_content_analysis/SensitiveContentAnalysisPlugin.h b/darwin/sensitive_content_analysis/Sources/sensitive_content_analysis/SensitiveContentAnalysisPlugin.h
deleted file mode 100644
index 49d4ac6..0000000
--- a/darwin/sensitive_content_analysis/Sources/sensitive_content_analysis/SensitiveContentAnalysisPlugin.h
+++ /dev/null
@@ -1,4 +0,0 @@
-#import
-
-@interface SensitiveContentAnalysisPlugin : NSObject
-@end
\ No newline at end of file
diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock
index c4ae4f7..0052c97 100644
--- a/example/ios/Podfile.lock
+++ b/example/ios/Podfile.lock
@@ -1,22 +1,48 @@
PODS:
+ - file_picker (0.0.1):
+ - Flutter
+ - FlutterMacOS
- Flutter (1.0.0)
+ - image_picker_ios (0.0.1):
+ - Flutter
+ - integration_test (0.0.1):
+ - Flutter
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
+ - sensitive_content_analysis (0.0.1):
+ - Flutter
+ - FlutterMacOS
DEPENDENCIES:
+ - file_picker (from `.symlinks/plugins/file_picker/darwin`)
- Flutter (from `Flutter`)
+ - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
+ - integration_test (from `.symlinks/plugins/integration_test/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
+ - sensitive_content_analysis (from `.symlinks/plugins/sensitive_content_analysis/darwin`)
EXTERNAL SOURCES:
+ file_picker:
+ :path: ".symlinks/plugins/file_picker/darwin"
Flutter:
:path: Flutter
+ image_picker_ios:
+ :path: ".symlinks/plugins/image_picker_ios/ios"
+ integration_test:
+ :path: ".symlinks/plugins/integration_test/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
+ sensitive_content_analysis:
+ :path: ".symlinks/plugins/sensitive_content_analysis/darwin"
SPEC CHECKSUMS:
+ file_picker: 70164d9778c42c47218d6cd79ce435de0856b11a
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
+ image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
+ integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
path_provider_foundation: 608fcb11be570ce83519b076ab6a1fffe2474f05
+ sensitive_content_analysis: ed23804e800acb5554078f1584f24da9c6184b87
PODFILE CHECKSUM: 9c46fd01abff66081b39f5fa5767b3f1d0b11d76
diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj
index 71dbb3c..9238175 100644
--- a/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/example/ios/Runner.xcodeproj/project.pbxproj
@@ -11,7 +11,6 @@
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
- 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
@@ -55,9 +54,6 @@
6655F3E930AD6D7B22732ABA /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
- 784666492D4C4C64000A1A5F /* FlutterFramework */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterFramework; path = Flutter/ephemeral/Packages/.packages/FlutterFramework; sourceTree = ""; };
- 78DABEA22ED26510000E7860 /* sensitive_content_analysis */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = sensitive_content_analysis; path = ../../ios/sensitive_content_analysis; sourceTree = ""; };
- 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
7EB008A721187624F840D95F /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; };
830E9F5EECBEBE4BE89C5025 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; };
@@ -85,7 +81,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
BA71F0ED51A027C7FB44BBF6 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -126,9 +121,6 @@
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
- 78DABEA22ED26510000E7860 /* sensitive_content_analysis */,
- 784666492D4C4C64000A1A5F /* FlutterFramework */,
- 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
@@ -214,9 +206,6 @@
dependencies = (
);
name = Runner;
- packageProductDependencies = (
- 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
- );
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
@@ -250,9 +239,6 @@
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
- packageReferences = (
- 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */,
- );
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
@@ -741,20 +727,6 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
-
-/* Begin XCLocalSwiftPackageReference section */
- 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = {
- isa = XCLocalSwiftPackageReference;
- relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
- };
-/* End XCLocalSwiftPackageReference section */
-
-/* Begin XCSwiftPackageProductDependency section */
- 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
- isa = XCSwiftPackageProductDependency;
- productName = FlutterGeneratedPluginSwiftPackage;
- };
-/* End XCSwiftPackageProductDependency section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
diff --git a/example/pubspec.yaml b/example/pubspec.yaml
index ddf81ce..e7d33c4 100644
--- a/example/pubspec.yaml
+++ b/example/pubspec.yaml
@@ -26,4 +26,7 @@ dev_dependencies:
flutter_lints: ^6.0.0
flutter:
- uses-material-design: true
\ No newline at end of file
+ uses-material-design: true
+
+ config:
+ enable-swift-package-manager: false
\ No newline at end of file
diff --git a/pubspec.yaml b/pubspec.yaml
index 9771e5d..2bb61f1 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,11 +1,11 @@
name: sensitive_content_analysis
description: "Dart package for detecting and alerting users to nudity in images and videos before displaying them, utilizing the SensitiveContentAnalysis Framework by Apple."
-version: 2.0.2
+version: 2.0.3
homepage: https://github.com/doppeltilde/sensitive_content_analysis
environment:
sdk: '>=3.1.0 <4.0.0'
- flutter: '>=3.3.0'
+ flutter: '>=3.41.0'
dependencies:
flutter:
From 457aee8789a52fd6db62ea3d7a5fb7231f2cf544 Mon Sep 17 00:00:00 2001
From: "Jona T. Feucht" <14951074+jonafeucht@users.noreply.github.com>
Date: Sat, 6 Jun 2026 08:43:29 +0200
Subject: [PATCH 03/15] chore: formatting
---
.../sensitive_content_analysis/Package.swift | 56 +--
.../SensitiveContentAnalysisPlugin.swift | 454 +++++++++---------
2 files changed, 255 insertions(+), 255 deletions(-)
diff --git a/darwin/sensitive_content_analysis/Package.swift b/darwin/sensitive_content_analysis/Package.swift
index 2c28c85..0055371 100644
--- a/darwin/sensitive_content_analysis/Package.swift
+++ b/darwin/sensitive_content_analysis/Package.swift
@@ -3,32 +3,32 @@
import PackageDescription
let package = Package(
- name: "sensitive_content_analysis",
- platforms: [
- .iOS("13.0"),
- .macOS("12.0"),
- ],
- products: [
- .library(
- name: "sensitive-content-analysis", targets: ["sensitive_content_analysis"]
- )
- ],
- dependencies: [
- .package(path: "../FlutterFramework")
- ],
- targets: [
- .target(
- name: "sensitive_content_analysis",
- dependencies: [
- .product(name: "FlutterFramework", package: "FlutterFramework")
- ],
- path: ".",
- resources: [],
- linkerSettings: [
- .linkedFramework("Flutter", .when(platforms: [.iOS])),
- .linkedFramework("FlutterMacOS", .when(platforms: [.macOS])),
- .linkedFramework("SensitiveContentAnalysis", .when(platforms: [.iOS, .macOS])),
- ]
- )
- ]
+ name: "sensitive_content_analysis",
+ platforms: [
+ .iOS("13.0"),
+ .macOS("12.0"),
+ ],
+ products: [
+ .library(
+ name: "sensitive-content-analysis", targets: ["sensitive_content_analysis"]
+ )
+ ],
+ dependencies: [
+ .package(path: "../FlutterFramework")
+ ],
+ targets: [
+ .target(
+ name: "sensitive_content_analysis",
+ dependencies: [
+ .product(name: "FlutterFramework", package: "FlutterFramework")
+ ],
+ path: ".",
+ resources: [],
+ linkerSettings: [
+ .linkedFramework("Flutter", .when(platforms: [.iOS])),
+ .linkedFramework("FlutterMacOS", .when(platforms: [.macOS])),
+ .linkedFramework("SensitiveContentAnalysis", .when(platforms: [.iOS, .macOS])),
+ ]
+ )
+ ]
)
diff --git a/darwin/sensitive_content_analysis/Sources/sensitive_content_analysis/SensitiveContentAnalysisPlugin.swift b/darwin/sensitive_content_analysis/Sources/sensitive_content_analysis/SensitiveContentAnalysisPlugin.swift
index 6963c45..6f7d3f2 100644
--- a/darwin/sensitive_content_analysis/Sources/sensitive_content_analysis/SensitiveContentAnalysisPlugin.swift
+++ b/darwin/sensitive_content_analysis/Sources/sensitive_content_analysis/SensitiveContentAnalysisPlugin.swift
@@ -1,257 +1,257 @@
#if canImport(SensitiveContentAnalysis)
- import SensitiveContentAnalysis
+ import SensitiveContentAnalysis
#endif
#if os(iOS)
- import Flutter
- import UIKit
+ import Flutter
+ import UIKit
#else
- import FlutterMacOS
- import AppKit
+ import FlutterMacOS
+ import AppKit
#endif
@MainActor
@objc public class SensitiveContentAnalysisPlugin: NSObject, FlutterPlugin {
- public static func register(with registrar: FlutterPluginRegistrar) {
- #if os(iOS)
- let messenger = registrar.messenger()
- #else
- let messenger = registrar.messenger
- #endif
+ public static func register(with registrar: FlutterPluginRegistrar) {
+ #if os(iOS)
+ let messenger = registrar.messenger()
+ #else
+ let messenger = registrar.messenger
+ #endif
- let channel = FlutterMethodChannel(
- name: "sensitive_content_analysis",
- binaryMessenger: messenger
- )
-
- let instance = SensitiveContentAnalysisPlugin()
- registrar.addMethodCallDelegate(instance, channel: channel)
- }
+ let channel = FlutterMethodChannel(
+ name: "sensitive_content_analysis",
+ binaryMessenger: messenger
+ )
- private var _analyzer: Any?
+ let instance = SensitiveContentAnalysisPlugin()
+ registrar.addMethodCallDelegate(instance, channel: channel)
+ }
- @available(iOS 17.0, macOS 14.0, *)
- private var analyzer: SCSensitivityAnalyzer {
- if let existing = _analyzer as? SCSensitivityAnalyzer {
- return existing
- }
+ private var _analyzer: Any?
- let newAnalyzer = SCSensitivityAnalyzer()
- _analyzer = newAnalyzer
- return newAnalyzer
+ @available(iOS 17.0, macOS 14.0, *)
+ private var analyzer: SCSensitivityAnalyzer {
+ if let existing = _analyzer as? SCSensitivityAnalyzer {
+ return existing
}
- private func cgImage(from data: Data) -> CGImage? {
- #if os(iOS)
- return UIImage(data: data)?.cgImage
- #elseif os(macOS)
- guard let nsImage = NSImage(data: data) else { return nil }
- var imageRect = CGRect(
- x: 0, y: 0, width: nsImage.size.width, height: nsImage.size.height)
- return nsImage.cgImage(
- forProposedRect: &imageRect,
- context: nil,
- hints: nil
+ let newAnalyzer = SCSensitivityAnalyzer()
+ _analyzer = newAnalyzer
+ return newAnalyzer
+ }
+
+ private func cgImage(from data: Data) -> CGImage? {
+ #if os(iOS)
+ return UIImage(data: data)?.cgImage
+ #elseif os(macOS)
+ guard let nsImage = NSImage(data: data) else { return nil }
+ var imageRect = CGRect(
+ x: 0, y: 0, width: nsImage.size.width, height: nsImage.size.height)
+ return nsImage.cgImage(
+ forProposedRect: &imageRect,
+ context: nil,
+ hints: nil
+ )
+ #endif
+ }
+
+ @available(iOS 17.0, macOS 14.0, *)
+ private func analyzeImage(
+ image: FlutterStandardTypedData,
+ result: @escaping FlutterResult
+ ) {
+ let analyzer = self.analyzer
+
+ Task(priority: .userInitiated) {
+ guard analyzer.analysisPolicy != .disabled else {
+ await MainActor.run { result(nil) }
+ return
+ }
+
+ guard let cgImage = cgImage(from: image.data) else {
+ await MainActor.run { result(nil) }
+ return
+ }
+
+ do {
+ let analysisResult = try await analyzer.analyzeImage(cgImage)
+
+ await MainActor.run {
+ result(analysisResult.isSensitive)
+ }
+ } catch {
+ await MainActor.run {
+ result(
+ FlutterError(
+ code: "analysis_error",
+ message: "Failed to analyze image",
+ details: error.localizedDescription
)
- #endif
- }
-
- @available(iOS 17.0, macOS 14.0, *)
- private func analyzeImage(
- image: FlutterStandardTypedData,
- result: @escaping FlutterResult
- ) {
- let analyzer = self.analyzer
-
- Task(priority: .userInitiated) {
- guard analyzer.analysisPolicy != .disabled else {
- await MainActor.run { result(nil) }
- return
- }
-
- guard let cgImage = cgImage(from: image.data) else {
- await MainActor.run { result(nil) }
- return
- }
-
- do {
- let analysisResult = try await analyzer.analyzeImage(cgImage)
-
- await MainActor.run {
- result(analysisResult.isSensitive)
- }
- } catch {
- await MainActor.run {
- result(
- FlutterError(
- code: "analysis_error",
- message: "Failed to analyze image",
- details: error.localizedDescription
- )
- )
- }
- }
+ )
}
+ }
}
-
- @available(iOS 17.0, macOS 14.0, *)
- private func analyzeVideo(
- at fileURL: URL,
- result: @escaping FlutterResult
- ) {
- let analyzer = self.analyzer
-
- Task(priority: .userInitiated) {
- guard analyzer.analysisPolicy != .disabled else {
- await MainActor.run { result(nil) }
- return
- }
-
- do {
- let handler = analyzer.videoAnalysis(forFileAt: fileURL)
- let analysisResult = try await handler.hasSensitiveContent()
-
- await MainActor.run {
- result(analysisResult.isSensitive)
- }
- } catch {
- await MainActor.run {
- result(
- FlutterError(
- code: "analysis_error",
- message: "Failed to analyze video",
- details: error.localizedDescription
- )
- )
- }
- }
+ }
+
+ @available(iOS 17.0, macOS 14.0, *)
+ private func analyzeVideo(
+ at fileURL: URL,
+ result: @escaping FlutterResult
+ ) {
+ let analyzer = self.analyzer
+
+ Task(priority: .userInitiated) {
+ guard analyzer.analysisPolicy != .disabled else {
+ await MainActor.run { result(nil) }
+ return
+ }
+
+ do {
+ let handler = analyzer.videoAnalysis(forFileAt: fileURL)
+ let analysisResult = try await handler.hasSensitiveContent()
+
+ await MainActor.run {
+ result(analysisResult.isSensitive)
}
- }
-
- @available(iOS 17.0, macOS 14.0, *)
- private func analyzeNetworkImage(
- at url: URL,
- result: @escaping FlutterResult
- ) {
- let analyzer = self.analyzer
-
- Task(priority: .userInitiated) {
- guard analyzer.analysisPolicy != .disabled else {
- await MainActor.run { result(nil) }
- return
- }
-
- do {
- let (data, _) = try await URLSession.shared.data(from: url)
-
- guard let cgImage = cgImage(from: data) else {
- await MainActor.run { result(nil) }
- return
- }
-
- let analysisResult = try await analyzer.analyzeImage(cgImage)
-
- await MainActor.run {
- result(analysisResult.isSensitive)
- }
- } catch {
- await MainActor.run {
- result(
- FlutterError(
- code: "analysis_error",
- message: "Failed to analyze network image",
- details: error.localizedDescription
- )
- )
- }
- }
+ } catch {
+ await MainActor.run {
+ result(
+ FlutterError(
+ code: "analysis_error",
+ message: "Failed to analyze video",
+ details: error.localizedDescription
+ )
+ )
}
+ }
}
+ }
+
+ @available(iOS 17.0, macOS 14.0, *)
+ private func analyzeNetworkImage(
+ at url: URL,
+ result: @escaping FlutterResult
+ ) {
+ let analyzer = self.analyzer
+
+ Task(priority: .userInitiated) {
+ guard analyzer.analysisPolicy != .disabled else {
+ await MainActor.run { result(nil) }
+ return
+ }
+
+ do {
+ let (data, _) = try await URLSession.shared.data(from: url)
+
+ guard let cgImage = cgImage(from: data) else {
+ await MainActor.run { result(nil) }
+ return
+ }
- @available(iOS 17.0, macOS 14.0, *)
- private func checkPolicy(result: @escaping FlutterResult) {
- switch analyzer.analysisPolicy {
- case .disabled:
- result(0)
- case .simpleInterventions:
- result(1)
- case .descriptiveInterventions:
- result(2)
- @unknown default:
- result(
- FlutterError(
- code: "unknown_policy",
- message: "Unrecognized AnalysisPolicy value",
- details: nil
- )
+ let analysisResult = try await analyzer.analyzeImage(cgImage)
+
+ await MainActor.run {
+ result(analysisResult.isSensitive)
+ }
+ } catch {
+ await MainActor.run {
+ result(
+ FlutterError(
+ code: "analysis_error",
+ message: "Failed to analyze network image",
+ details: error.localizedDescription
)
+ )
}
+ }
+ }
+ }
+
+ @available(iOS 17.0, macOS 14.0, *)
+ private func checkPolicy(result: @escaping FlutterResult) {
+ switch analyzer.analysisPolicy {
+ case .disabled:
+ result(0)
+ case .simpleInterventions:
+ result(1)
+ case .descriptiveInterventions:
+ result(2)
+ @unknown default:
+ result(
+ FlutterError(
+ code: "unknown_policy",
+ message: "Unrecognized AnalysisPolicy value",
+ details: nil
+ )
+ )
+ }
+ }
+
+ public func handle(
+ _ call: FlutterMethodCall,
+ result: @escaping FlutterResult
+ ) {
+ guard #available(iOS 17.0, macOS 14.0, *) else {
+ result(FlutterMethodNotImplemented)
+ return
}
- public func handle(
- _ call: FlutterMethodCall,
- result: @escaping FlutterResult
- ) {
- guard #available(iOS 17.0, macOS 14.0, *) else {
- result(FlutterMethodNotImplemented)
- return
- }
+ switch call.method {
- switch call.method {
-
- case "analyzeImage":
- guard let image = call.arguments as? FlutterStandardTypedData else {
- result(
- FlutterError(
- code: "invalid_arguments",
- message: "Expected FlutterStandardTypedData for image",
- details: nil
- )
- )
- return
- }
- analyzeImage(image: image, result: result)
-
- case "analyzeVideo":
- guard
- let args = call.arguments as? [String: Any],
- let urlString = args["url"] as? String,
- let url = URL(string: urlString)
- else {
- result(
- FlutterError(
- code: "invalid_arguments",
- message: "Expected a valid 'url' string argument",
- details: nil
- )
- )
- return
- }
- analyzeVideo(at: url, result: result)
-
- case "analyzeNetworkImage":
- guard
- let args = call.arguments as? [String: Any],
- let urlString = args["url"] as? String,
- let url = URL(string: urlString)
- else {
- result(
- FlutterError(
- code: "invalid_arguments",
- message: "Expected a valid 'url' string argument",
- details: nil
- )
- )
- return
- }
- analyzeNetworkImage(at: url, result: result)
-
- case "checkPolicy":
- checkPolicy(result: result)
-
- default:
- result(FlutterMethodNotImplemented)
- }
+ case "analyzeImage":
+ guard let image = call.arguments as? FlutterStandardTypedData else {
+ result(
+ FlutterError(
+ code: "invalid_arguments",
+ message: "Expected FlutterStandardTypedData for image",
+ details: nil
+ )
+ )
+ return
+ }
+ analyzeImage(image: image, result: result)
+
+ case "analyzeVideo":
+ guard
+ let args = call.arguments as? [String: Any],
+ let urlString = args["url"] as? String,
+ let url = URL(string: urlString)
+ else {
+ result(
+ FlutterError(
+ code: "invalid_arguments",
+ message: "Expected a valid 'url' string argument",
+ details: nil
+ )
+ )
+ return
+ }
+ analyzeVideo(at: url, result: result)
+
+ case "analyzeNetworkImage":
+ guard
+ let args = call.arguments as? [String: Any],
+ let urlString = args["url"] as? String,
+ let url = URL(string: urlString)
+ else {
+ result(
+ FlutterError(
+ code: "invalid_arguments",
+ message: "Expected a valid 'url' string argument",
+ details: nil
+ )
+ )
+ return
+ }
+ analyzeNetworkImage(at: url, result: result)
+
+ case "checkPolicy":
+ checkPolicy(result: result)
+
+ default:
+ result(FlutterMethodNotImplemented)
}
+ }
}
From 791d9450a25f09409e0ca00769e7d0224b3c48a0 Mon Sep 17 00:00:00 2001
From: "Jona T. Feucht" <14951074+jonafeucht@users.noreply.github.com>
Date: Sat, 6 Jun 2026 15:05:34 +0200
Subject: [PATCH 04/15] fix
---
.../xcschemes/xcschememanagement.plist | 2 +-
.../SensitiveContentAnalysisPlugin.swift | 46 ++++++++++---------
example/macos/Podfile.lock | 12 -----
.../macos/Runner.xcodeproj/project.pbxproj | 34 ++++++++++++--
.../xcshareddata/xcschemes/Runner.xcscheme | 18 ++++++++
example/pubspec.lock | 4 +-
example/pubspec.yaml | 4 +-
7 files changed, 78 insertions(+), 42 deletions(-)
diff --git a/darwin/sensitive_content_analysis/.swiftpm/xcode/xcuserdata/jonafeucht.xcuserdatad/xcschemes/xcschememanagement.plist b/darwin/sensitive_content_analysis/.swiftpm/xcode/xcuserdata/jonafeucht.xcuserdatad/xcschemes/xcschememanagement.plist
index 5ca3b98..53c5146 100644
--- a/darwin/sensitive_content_analysis/.swiftpm/xcode/xcuserdata/jonafeucht.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/darwin/sensitive_content_analysis/.swiftpm/xcode/xcuserdata/jonafeucht.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -7,7 +7,7 @@
sensitive-content-analysis.xcscheme_^#shared#^_
orderHint
- 9
+ 10
diff --git a/darwin/sensitive_content_analysis/Sources/sensitive_content_analysis/SensitiveContentAnalysisPlugin.swift b/darwin/sensitive_content_analysis/Sources/sensitive_content_analysis/SensitiveContentAnalysisPlugin.swift
index 6f7d3f2..d175023 100644
--- a/darwin/sensitive_content_analysis/Sources/sensitive_content_analysis/SensitiveContentAnalysisPlugin.swift
+++ b/darwin/sensitive_content_analysis/Sources/sensitive_content_analysis/SensitiveContentAnalysisPlugin.swift
@@ -10,8 +10,7 @@
import AppKit
#endif
-@MainActor
-@objc public class SensitiveContentAnalysisPlugin: NSObject, FlutterPlugin {
+public class SensitiveContentAnalysisPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
#if os(iOS)
@@ -62,9 +61,9 @@
image: FlutterStandardTypedData,
result: @escaping FlutterResult
) {
- let analyzer = self.analyzer
-
Task(priority: .userInitiated) {
+ let analyzer = await MainActor.run { self.analyzer }
+
guard analyzer.analysisPolicy != .disabled else {
await MainActor.run { result(nil) }
return
@@ -100,9 +99,9 @@
at fileURL: URL,
result: @escaping FlutterResult
) {
- let analyzer = self.analyzer
-
Task(priority: .userInitiated) {
+ let analyzer = await MainActor.run { self.analyzer }
+
guard analyzer.analysisPolicy != .disabled else {
await MainActor.run { result(nil) }
return
@@ -134,9 +133,9 @@
at url: URL,
result: @escaping FlutterResult
) {
- let analyzer = self.analyzer
-
Task(priority: .userInitiated) {
+ let analyzer = await MainActor.run { self.analyzer }
+
guard analyzer.analysisPolicy != .disabled else {
await MainActor.run { result(nil) }
return
@@ -171,21 +170,24 @@
@available(iOS 17.0, macOS 14.0, *)
private func checkPolicy(result: @escaping FlutterResult) {
- switch analyzer.analysisPolicy {
- case .disabled:
- result(0)
- case .simpleInterventions:
- result(1)
- case .descriptiveInterventions:
- result(2)
- @unknown default:
- result(
- FlutterError(
- code: "unknown_policy",
- message: "Unrecognized AnalysisPolicy value",
- details: nil
+ Task { @MainActor in
+ let analyzer = self.analyzer
+ switch analyzer.analysisPolicy {
+ case .disabled:
+ result(0)
+ case .simpleInterventions:
+ result(1)
+ case .descriptiveInterventions:
+ result(2)
+ @unknown default:
+ result(
+ FlutterError(
+ code: "unknown_policy",
+ message: "Unrecognized AnalysisPolicy value",
+ details: nil
+ )
)
- )
+ }
}
}
diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock
index 68938d9..bbea909 100644
--- a/example/macos/Podfile.lock
+++ b/example/macos/Podfile.lock
@@ -1,34 +1,22 @@
PODS:
- - file_selector_macos (0.0.1):
- - FlutterMacOS
- FlutterMacOS (1.0.0)
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- - sensitive_content_analysis (0.0.1):
- - FlutterMacOS
DEPENDENCIES:
- - file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`)
- FlutterMacOS (from `Flutter/ephemeral`)
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
- - sensitive_content_analysis (from `Flutter/ephemeral/.symlinks/plugins/sensitive_content_analysis/macos`)
EXTERNAL SOURCES:
- file_selector_macos:
- :path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos
FlutterMacOS:
:path: Flutter/ephemeral
path_provider_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
- sensitive_content_analysis:
- :path: Flutter/ephemeral/.symlinks/plugins/sensitive_content_analysis/macos
SPEC CHECKSUMS:
- file_selector_macos: 660f7672ee62dad6df1bb0cbdb90a10ce3f946df
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
path_provider_foundation: 608fcb11be570ce83519b076ab6a1fffe2474f05
- sensitive_content_analysis: 00182a342b1e6ee570373d73573f7bc9ee4fe690
PODFILE CHECKSUM: 6acf97521436d16fc31cd5e1a02000905acdb3ae
diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj
index 0e13e25..49be747 100644
--- a/example/macos/Runner.xcodeproj/project.pbxproj
+++ b/example/macos/Runner.xcodeproj/project.pbxproj
@@ -28,6 +28,7 @@
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
5F8E2129349DD2ADB44CA281 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C4D5C9BE33EC2BDEF637609 /* Pods_Runner.framework */; };
+ 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
78FA66B7C2A45709C4128F28 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D8F111450786C92DF977678C /* Pods_RunnerTests.framework */; };
/* End PBXBuildFile section */
@@ -83,6 +84,9 @@
4C4D5C9BE33EC2BDEF637609 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
669FDA171898C867DBAF4BE7 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
68098A31A007FACF677C042E /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
+ 784666492D4C4C64000A1A5F /* FlutterFramework */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterFramework; path = ephemeral/Packages/.packages/FlutterFramework; sourceTree = ""; };
+ 78DABEA22ED26510000E7860 /* sensitive_content_analysis */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = sensitive_content_analysis; path = ../../../darwin/sensitive_content_analysis; sourceTree = ""; };
+ 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; };
7F890A81D151036D48996D76 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; };
@@ -103,6 +107,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
5F8E2129349DD2ADB44CA281 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -164,6 +169,9 @@
33CEB47122A05771004F2AC0 /* Flutter */ = {
isa = PBXGroup;
children = (
+ 78DABEA22ED26510000E7860 /* sensitive_content_analysis */,
+ 784666492D4C4C64000A1A5F /* FlutterFramework */,
+ 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,
@@ -247,6 +255,9 @@
33CC11202044C79F0003C045 /* PBXTargetDependency */,
);
name = Runner;
+ packageProductDependencies = (
+ 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
+ );
productName = Runner;
productReference = 33CC10ED2044A3C60003C045 /* sensitive_content_analysis_example.app */;
productType = "com.apple.product-type.application";
@@ -290,6 +301,9 @@
Base,
);
mainGroup = 33CC10E42044A3C60003C045;
+ packageReferences = (
+ 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */,
+ );
productRefGroup = 33CC10EE2044A3C60003C045 /* Products */;
projectDirPath = "";
projectRoot = "";
@@ -576,7 +590,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
- MACOSX_DEPLOYMENT_TARGET = 14.0;
+ MACOSX_DEPLOYMENT_TARGET = 14.6;
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};
@@ -705,7 +719,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
- MACOSX_DEPLOYMENT_TARGET = 14.0;
+ MACOSX_DEPLOYMENT_TARGET = 14.6;
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@@ -728,7 +742,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
- MACOSX_DEPLOYMENT_TARGET = 14.0;
+ MACOSX_DEPLOYMENT_TARGET = 14.6;
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};
@@ -794,6 +808,20 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
+
+/* Begin XCLocalSwiftPackageReference section */
+ 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = {
+ isa = XCLocalSwiftPackageReference;
+ relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
+ };
+/* End XCLocalSwiftPackageReference section */
+
+/* Begin XCSwiftPackageProductDependency section */
+ 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = FlutterGeneratedPluginSwiftPackage;
+ };
+/* End XCSwiftPackageProductDependency section */
};
rootObject = 33CC10E52044A3C60003C045 /* Project object */;
}
diff --git a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index 4b53e01..71448bf 100644
--- a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -5,6 +5,24 @@
+
+
+
+
+
+
+
+
+
+
=3.11.0 <4.0.0"
- flutter: ">=3.38.0"
+ flutter: ">=3.41.0"
diff --git a/example/pubspec.yaml b/example/pubspec.yaml
index e7d33c4..3724744 100644
--- a/example/pubspec.yaml
+++ b/example/pubspec.yaml
@@ -28,5 +28,5 @@ dev_dependencies:
flutter:
uses-material-design: true
- config:
- enable-swift-package-manager: false
\ No newline at end of file
+ # config:
+ # enable-swift-package-manager: false
\ No newline at end of file
From 21c4a786f5ba319df888d723bdd3062fbcd7d508 Mon Sep 17 00:00:00 2001
From: "Jona T. Feucht" <14951074+jonafeucht@users.noreply.github.com>
Date: Tue, 9 Jun 2026 09:24:10 +0200
Subject: [PATCH 05/15] Release 2.1.0
---
.github/workflows/test.yml | 42 ++
CHANGELOG.md | 11 +
README.md | 96 ++--
.../SensitiveContentAnalysisPlugin.swift | 72 ++-
example/ios/Flutter/Debug.xcconfig | 1 -
example/ios/Flutter/Release.xcconfig | 1 -
example/ios/Podfile | 44 --
example/ios/Podfile.lock | 49 --
example/ios/Runner.xcodeproj/project.pbxproj | 123 +---
example/lib/home_feed.dart | 253 +++++++++
example/lib/main.dart | 149 ++++-
example/macos/Flutter/Flutter-Debug.xcconfig | 1 -
.../macos/Flutter/Flutter-Release.xcconfig | 1 -
.../Flutter/GeneratedPluginRegistrant.swift | 4 +-
example/macos/Podfile | 43 --
example/macos/Podfile.lock | 23 -
.../macos/Runner.xcodeproj/project.pbxproj | 95 ----
example/pubspec.lock | 528 ++++++++++++++++--
example/pubspec.yaml | 9 +-
example/test/widget_test.dart | 271 ++++++++-
example/test/widget_test.mocks.dart | 80 +++
lib/sensitive_content_analysis.dart | 54 +-
pubspec.yaml | 2 +-
23 files changed, 1431 insertions(+), 521 deletions(-)
create mode 100644 .github/workflows/test.yml
delete mode 100644 example/ios/Podfile
delete mode 100644 example/ios/Podfile.lock
create mode 100644 example/lib/home_feed.dart
delete mode 100644 example/macos/Podfile
delete mode 100644 example/macos/Podfile.lock
create mode 100644 example/test/widget_test.mocks.dart
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..ef5b46d
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,42 @@
+name: Flutter CI
+
+on:
+ push:
+ branches: ["main", "master", "dev"]
+ pull_request:
+ branches: ["main", "master", "dev"]
+
+jobs:
+ test:
+ name: Unit & Widget Tests
+ runs-on: macos-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up Flutter
+ uses: subosito/flutter-action@v2
+ with:
+ channel: "stable"
+ cache: true
+
+ - name: Install dependencies
+ run: flutter pub get
+ working-directory: example
+
+ - name: Generate mocks (build_runner)
+ run: dart run build_runner build --delete-conflicting-outputs
+ working-directory: example
+
+ - name: Verify formatting
+ run: dart format --output=none --set-exit-if-changed .
+ working-directory: example
+
+ - name: Analyze code (dart analyze)
+ run: flutter analyze
+ working-directory: example
+
+ - name: Run tests with coverage
+ run: flutter test --coverage
+ working-directory: example
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5b00a69..9d006e9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,14 @@
+## 2.1.0
+### Breaking
+- `analyzeImage`, `analyzeNetworkImage`, and `analyzeVideo` now return `SensitivityAnalysisResult?` instead of `bool?`.
+
+### Added
+- `SensitivityAnalysisResult` with `isSensitive` (bool) and `detectedTypes` (List).
+- `detectedTypes` returns detected content categories (e.g. `sexuallyExplicit`, `goreOrViolence`) on iOS 27.0+ / macOS 27.0+. Empty list on older OS versions.
+
+### Fix
+- Added a queue to `analyzeNetworkImage` preventing SCA from crashing and returning `null`.
+
## 2.0.3
- Fix: Add backwards compatibility for cocoapods.
diff --git a/README.md b/README.md
index 1844db0..87e2373 100644
--- a/README.md
+++ b/README.md
@@ -93,50 +93,41 @@ if (policy != null) {
#### File Image:
```dart
- try {
- final sca = SensitiveContentAnalysis();
- final ImagePicker picker = ImagePicker();
+ try {
+ final ImagePicker picker = ImagePicker();
- // Pick an image.
- final XFile? image = await picker.pickImage(source: ImageSource.gallery);
+ // Pick an image.
+ final XFile? image = await picker.pickImage(source: ImageSource.gallery);
- if (image != null) {
+ if (image != null) {
Uint8List imageData = await image.readAsBytes();
// Analyze the image for sensitive content.
- final bool? isSensitive = await sca.analyzeImage(imageData);
- if (isSensitive != null) {
- return isSensitive;
- } else {
- debugPrint("Enable ”Sensitive Content Warning” in Settings -> Privacy & Security.");
- return null;
- }
+ SensitivityAnalysisResult? isSensitive =
+ await sca.analyzeImage(imageData);
+ _showResultDialog(
+ "Analysis Result", "SENSITIVE: ${isSensitive?.isSensitive}");
+ }
+ } catch (e) {
+ _showResultDialog("Error", e.toString());
}
- } catch (e) {
- return null;
- }
```
#### Network Image:
```dart
-final String? analyzeUrl = "https://docs-assets.developer.apple.com/published/517e263450/rendered2x-1685188934.png";
-
try {
- final sca = SensitiveContentAnalysis();
-
- if (analyzeUrl != null) {
- final bool? isSensitive = await sca.analyzeNetworkImage(url: analyzeUrl);
- if (isSensitive != null) {
- return isSensitive;
- } else {
- debugPrint("Enable ”Sensitive Content Warning” in Settings -> Privacy & Security.");
- return null;
- }
+ const url =
+ "https://docs-assets.developer.apple.com/published/517e263450/rendered2x-1685188934.png";
+
+ // Analyze the image for sensitive content.
+ SensitivityAnalysisResult? isSensitive =
+ await sca.analyzeNetworkImage(url: url);
+ _showResultDialog(
+ "Analysis Result", "SENSITIVE: ${isSensitive?.isSensitive}");
+ } catch (e) {
+ _showResultDialog("Error", e.toString());
}
- } catch (e) {
- return null;
- }
```
### Analyze Video
@@ -144,7 +135,6 @@ final String? analyzeUrl = "https://docs-assets.developer.apple.com/published/51
#### Network Video:
```dart
- Future analyzeNetworkVideo() async {
try {
Dio dio = Dio();
Directory tempDir = await getTemporaryDirectory();
@@ -155,38 +145,50 @@ final String? analyzeUrl = "https://docs-assets.developer.apple.com/published/51
final response = await dio.download(url, file.path);
if (response.statusCode == 200) {
- bool? isSensitive = await sca.analyzeVideo(url: file.path);
- debugPrint("SENSITIVE: $isSensitive");
+ SensitivityAnalysisResult? isSensitive =
+ await sca.analyzeVideo(url: file.path);
+ _showResultDialog(
+ "Analysis Result", "SENSITIVE: ${isSensitive?.isSensitive}");
await file.delete();
}
} catch (e) {
- debugPrint(e.toString());
+ _showResultDialog("Error", e.toString());
}
- }
```
#### Local Video:
```dart
- Future analyzeLocalVideo() async {
try {
- const XTypeGroup typeGroup = XTypeGroup(
- label: 'video',
- extensions: ['mp4', 'mkv', 'avi', 'mov'],
+ FilePickerResult? selectedFile = await FilePicker.pickFiles(
+ allowMultiple: false,
+ type: FileType.video,
);
- final XFile? selectedFile =
- await openFile(acceptedTypeGroups: [typeGroup]);
-
if (selectedFile != null) {
- bool? isSensitive = await sca.analyzeVideo(url: selectedFile.path);
- debugPrint("SENSITIVE: $isSensitive");
+ SensitivityAnalysisResult? isSensitive =
+ await sca.analyzeVideo(url: selectedFile.files.first.path!);
+ _showResultDialog(
+ "Analysis Result", "SENSITIVE: ${isSensitive?.isSensitive}");
}
} catch (e) {
- debugPrint(e.toString());
+ _showResultDialog("Error", e.toString());
}
- }
```
+### Result
+
+All `analyze` methods return a `SensitivityAnalysisResult` object:
+
+```dart
+class SensitivityAnalysisResult {
+ final bool isSensitive;
+ final List detectedTypes; // Available on iOS 27.0+ / macOS 27.0+
+}
+```
+
+- `isSensitive` — whether the content was flagged as sensitive
+- `detectedTypes` — list of detected content categories, e.g. `"sexuallyExplicit"` or `"goreOrViolence"`. Empty on devices running below iOS 27.0 / macOS 27.0.
+
---
### Caveats
diff --git a/darwin/sensitive_content_analysis/Sources/sensitive_content_analysis/SensitiveContentAnalysisPlugin.swift b/darwin/sensitive_content_analysis/Sources/sensitive_content_analysis/SensitiveContentAnalysisPlugin.swift
index d175023..47dde2e 100644
--- a/darwin/sensitive_content_analysis/Sources/sensitive_content_analysis/SensitiveContentAnalysisPlugin.swift
+++ b/darwin/sensitive_content_analysis/Sources/sensitive_content_analysis/SensitiveContentAnalysisPlugin.swift
@@ -1,3 +1,5 @@
+import ImageIO
+
#if canImport(SensitiveContentAnalysis)
import SensitiveContentAnalysis
#endif
@@ -42,20 +44,60 @@ public class SensitiveContentAnalysisPlugin: NSObject, FlutterPlugin {
}
private func cgImage(from data: Data) -> CGImage? {
- #if os(iOS)
- return UIImage(data: data)?.cgImage
- #elseif os(macOS)
- guard let nsImage = NSImage(data: data) else { return nil }
- var imageRect = CGRect(
- x: 0, y: 0, width: nsImage.size.width, height: nsImage.size.height)
- return nsImage.cgImage(
- forProposedRect: &imageRect,
- context: nil,
- hints: nil
- )
+ guard let source = CGImageSourceCreateWithData(data as CFData, nil) else { return nil }
+ return CGImageSourceCreateImageAtIndex(source, 0, nil)
+ }
+
+ @available(iOS 17.0, macOS 14.0, *)
+ private func formatAnalysisResult(_ analysisResult: SCSensitivityAnalysis) -> [String: Any] {
+ var detectedTypesArray: [String] = []
+
+ #if compiler(>=6.4)
+ if #available(iOS 27.0, macOS 27.0, *) {
+ if analysisResult.isSensitive {
+ if analysisResult.detectedTypes.contains(.sexuallyExplicit) {
+ detectedTypesArray.append("sexuallyExplicit")
+ }
+ if analysisResult.detectedTypes.contains(.goreOrViolence) {
+ detectedTypesArray.append("goreOrViolence")
+ }
+ }
+ }
#endif
+
+ return [
+ "isSensitive": analysisResult.isSensitive,
+ "detectedTypes": detectedTypesArray,
+ ]
}
+ private actor AnalysisQueue {
+ private var running = 0
+ private let maxConcurrent = 3
+ private var waiters: [CheckedContinuation] = []
+
+ func acquire() async {
+ if running < maxConcurrent {
+ running += 1
+ return
+ }
+ await withCheckedContinuation { continuation in
+ waiters.append(continuation)
+ }
+ }
+
+ func release() {
+ if !waiters.isEmpty {
+ let next = waiters.removeFirst()
+ next.resume()
+ } else {
+ running = max(0, running - 1)
+ }
+ }
+ }
+
+ private let queue = AnalysisQueue()
+
@available(iOS 17.0, macOS 14.0, *)
private func analyzeImage(
image: FlutterStandardTypedData,
@@ -78,7 +120,7 @@ public class SensitiveContentAnalysisPlugin: NSObject, FlutterPlugin {
let analysisResult = try await analyzer.analyzeImage(cgImage)
await MainActor.run {
- result(analysisResult.isSensitive)
+ result(self.formatAnalysisResult(analysisResult))
}
} catch {
await MainActor.run {
@@ -112,7 +154,7 @@ public class SensitiveContentAnalysisPlugin: NSObject, FlutterPlugin {
let analysisResult = try await handler.hasSensitiveContent()
await MainActor.run {
- result(analysisResult.isSensitive)
+ result(self.formatAnalysisResult(analysisResult))
}
} catch {
await MainActor.run {
@@ -134,6 +176,8 @@ public class SensitiveContentAnalysisPlugin: NSObject, FlutterPlugin {
result: @escaping FlutterResult
) {
Task(priority: .userInitiated) {
+ await queue.acquire()
+ defer { Task { await queue.release() } }
let analyzer = await MainActor.run { self.analyzer }
guard analyzer.analysisPolicy != .disabled else {
@@ -152,7 +196,7 @@ public class SensitiveContentAnalysisPlugin: NSObject, FlutterPlugin {
let analysisResult = try await analyzer.analyzeImage(cgImage)
await MainActor.run {
- result(analysisResult.isSensitive)
+ result(self.formatAnalysisResult(analysisResult))
}
} catch {
await MainActor.run {
diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig
index ec97fc6..592ceee 100644
--- a/example/ios/Flutter/Debug.xcconfig
+++ b/example/ios/Flutter/Debug.xcconfig
@@ -1,2 +1 @@
-#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig
index c4855bf..592ceee 100644
--- a/example/ios/Flutter/Release.xcconfig
+++ b/example/ios/Flutter/Release.xcconfig
@@ -1,2 +1 @@
-#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
diff --git a/example/ios/Podfile b/example/ios/Podfile
deleted file mode 100644
index 4871635..0000000
--- a/example/ios/Podfile
+++ /dev/null
@@ -1,44 +0,0 @@
-# Uncomment this line to define a global platform for your project
-platform :ios, '15.0'
-
-# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
-ENV['COCOAPODS_DISABLE_STATS'] = 'true'
-
-project 'Runner', {
- 'Debug' => :debug,
- 'Profile' => :release,
- 'Release' => :release,
-}
-
-def flutter_root
- generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
- unless File.exist?(generated_xcode_build_settings_path)
- raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
- end
-
- File.foreach(generated_xcode_build_settings_path) do |line|
- matches = line.match(/FLUTTER_ROOT\=(.*)/)
- return matches[1].strip if matches
- end
- raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
-end
-
-require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
-
-flutter_ios_podfile_setup
-
-target 'Runner' do
- use_frameworks!
- use_modular_headers!
-
- flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
- target 'RunnerTests' do
- inherit! :search_paths
- end
-end
-
-post_install do |installer|
- installer.pods_project.targets.each do |target|
- flutter_additional_ios_build_settings(target)
- end
-end
diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock
deleted file mode 100644
index 0052c97..0000000
--- a/example/ios/Podfile.lock
+++ /dev/null
@@ -1,49 +0,0 @@
-PODS:
- - file_picker (0.0.1):
- - Flutter
- - FlutterMacOS
- - Flutter (1.0.0)
- - image_picker_ios (0.0.1):
- - Flutter
- - integration_test (0.0.1):
- - Flutter
- - path_provider_foundation (0.0.1):
- - Flutter
- - FlutterMacOS
- - sensitive_content_analysis (0.0.1):
- - Flutter
- - FlutterMacOS
-
-DEPENDENCIES:
- - file_picker (from `.symlinks/plugins/file_picker/darwin`)
- - Flutter (from `Flutter`)
- - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- - integration_test (from `.symlinks/plugins/integration_test/ios`)
- - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- - sensitive_content_analysis (from `.symlinks/plugins/sensitive_content_analysis/darwin`)
-
-EXTERNAL SOURCES:
- file_picker:
- :path: ".symlinks/plugins/file_picker/darwin"
- Flutter:
- :path: Flutter
- image_picker_ios:
- :path: ".symlinks/plugins/image_picker_ios/ios"
- integration_test:
- :path: ".symlinks/plugins/integration_test/ios"
- path_provider_foundation:
- :path: ".symlinks/plugins/path_provider_foundation/darwin"
- sensitive_content_analysis:
- :path: ".symlinks/plugins/sensitive_content_analysis/darwin"
-
-SPEC CHECKSUMS:
- file_picker: 70164d9778c42c47218d6cd79ce435de0856b11a
- Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
- image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
- integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
- path_provider_foundation: 608fcb11be570ce83519b076ab6a1fffe2474f05
- sensitive_content_analysis: ed23804e800acb5554078f1584f24da9c6184b87
-
-PODFILE CHECKSUM: 9c46fd01abff66081b39f5fa5767b3f1d0b11d76
-
-COCOAPODS: 1.16.2
diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj
index 9238175..b8e980c 100644
--- a/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/example/ios/Runner.xcodeproj/project.pbxproj
@@ -11,11 +11,10 @@
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
+ 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
- B52200AC350B45639D4A8255 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 240C9B7B164EFAC11FD12018 /* Pods_RunnerTests.framework */; };
- BA71F0ED51A027C7FB44BBF6 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 244CED211C858B5AA8AC3E14 /* Pods_Runner.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -44,20 +43,15 @@
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
- 240C9B7B164EFAC11FD12018 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 244CED211C858B5AA8AC3E14 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
- 418085546E28C88484BE7028 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
- 56D9EEAC50ABB2CA2200B2F0 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; };
- 6655F3E930AD6D7B22732ABA /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ 784666492D4C4C64000A1A5F /* FlutterFramework */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterFramework; path = Flutter/ephemeral/Packages/.packages/FlutterFramework; sourceTree = ""; };
+ 78DABEA22ED26510000E7860 /* sensitive_content_analysis */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = sensitive_content_analysis; path = ../../darwin/sensitive_content_analysis; sourceTree = ""; };
+ 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
- 7EB008A721187624F840D95F /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; };
- 830E9F5EECBEBE4BE89C5025 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; };
- 8BDFD6F2399343487621EC2E /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -73,7 +67,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- B52200AC350B45639D4A8255 /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -81,7 +74,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- BA71F0ED51A027C7FB44BBF6 /* Pods_Runner.framework in Frameworks */,
+ 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -96,24 +89,9 @@
path = RunnerTests;
sourceTree = "";
};
- 3BE7B3DD1495D20D149F214B /* Frameworks */ = {
- isa = PBXGroup;
- children = (
- 244CED211C858B5AA8AC3E14 /* Pods_Runner.framework */,
- 240C9B7B164EFAC11FD12018 /* Pods_RunnerTests.framework */,
- );
- name = Frameworks;
- sourceTree = "";
- };
87FE9446974C5F6AE975833C /* Pods */ = {
isa = PBXGroup;
children = (
- 418085546E28C88484BE7028 /* Pods-Runner.debug.xcconfig */,
- 8BDFD6F2399343487621EC2E /* Pods-Runner.release.xcconfig */,
- 6655F3E930AD6D7B22732ABA /* Pods-Runner.profile.xcconfig */,
- 56D9EEAC50ABB2CA2200B2F0 /* Pods-RunnerTests.debug.xcconfig */,
- 7EB008A721187624F840D95F /* Pods-RunnerTests.release.xcconfig */,
- 830E9F5EECBEBE4BE89C5025 /* Pods-RunnerTests.profile.xcconfig */,
);
path = Pods;
sourceTree = "";
@@ -121,6 +99,9 @@
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
+ 78DABEA22ED26510000E7860 /* sensitive_content_analysis */,
+ 784666492D4C4C64000A1A5F /* FlutterFramework */,
+ 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
@@ -137,7 +118,6 @@
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
87FE9446974C5F6AE975833C /* Pods */,
- 3BE7B3DD1495D20D149F214B /* Frameworks */,
);
sourceTree = "";
};
@@ -173,7 +153,6 @@
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
- 677C4B0E13348E208FD78788 /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
05509ADE118F2DF4F289742C /* Frameworks */,
@@ -192,20 +171,21 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
- 34FB7EF5107F14C31A66B3ED /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
- F7356EA80015DDD1F19A10D5 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
+ packageProductDependencies = (
+ 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
+ );
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
@@ -239,6 +219,9 @@
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
+ packageReferences = (
+ 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */,
+ );
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
@@ -271,28 +254,6 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
- 34FB7EF5107F14C31A66B3ED /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputFileListPaths = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputFileListPaths = (
- );
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@@ -309,28 +270,6 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
- 677C4B0E13348E208FD78788 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputFileListPaths = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputFileListPaths = (
- );
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@@ -346,23 +285,6 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
- F7356EA80015DDD1F19A10D5 /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
- );
- name = "[CP] Embed Pods Frameworks";
- outputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -489,7 +411,6 @@
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 56D9EEAC50ABB2CA2200B2F0 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@@ -507,7 +428,6 @@
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 7EB008A721187624F840D95F /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@@ -523,7 +443,6 @@
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 830E9F5EECBEBE4BE89C5025 /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@@ -727,6 +646,20 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
+
+/* Begin XCLocalSwiftPackageReference section */
+ 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = {
+ isa = XCLocalSwiftPackageReference;
+ relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
+ };
+/* End XCLocalSwiftPackageReference section */
+
+/* Begin XCSwiftPackageProductDependency section */
+ 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = FlutterGeneratedPluginSwiftPackage;
+ };
+/* End XCSwiftPackageProductDependency section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
diff --git a/example/lib/home_feed.dart b/example/lib/home_feed.dart
new file mode 100644
index 0000000..8e7bf93
--- /dev/null
+++ b/example/lib/home_feed.dart
@@ -0,0 +1,253 @@
+import 'dart:convert';
+import 'dart:ui';
+import 'package:cached_network_image/cached_network_image.dart';
+import 'package:flutter/material.dart';
+// ignore: depend_on_referenced_packages
+import 'package:http/http.dart' as http;
+import 'package:sensitive_content_analysis/sensitive_content_analysis.dart';
+import 'package:sensitive_content_analysis_example/main.dart';
+
+class FeedScreen extends StatelessWidget {
+ const FeedScreen({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text(
+ 'NSFW Feed',
+ style: TextStyle(fontWeight: FontWeight.bold),
+ ),
+ ),
+ body: AuthenticatedAlbumFeed(),
+ );
+ }
+}
+
+class AuthenticatedAlbumFeed extends StatefulWidget {
+ const AuthenticatedAlbumFeed({super.key});
+
+ @override
+ State createState() => _AuthenticatedAlbumFeedState();
+}
+
+class _AuthenticatedAlbumFeedState extends State {
+ late Future> _albumImagesFuture;
+
+ @override
+ void initState() {
+ super.initState();
+ _albumImagesFuture = fetchAlbumImages();
+ }
+
+ Future> fetchAlbumImages() async {
+ try {
+ final Uri url = Uri.parse(
+ 'https://api.waifu.im/images?isNsfw=All&orderBy=Random&page=1&pageSize=30');
+
+ final response = await http.get(url);
+
+ if (response.statusCode == 200) {
+ final Map data = jsonDecode(response.body);
+ return data['items'] ?? [];
+ } else {
+ debugPrint('API Error: ${response.reasonPhrase}');
+ }
+ } catch (e) {
+ debugPrint('Network error fetching album: $e');
+ }
+ return [];
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return FutureBuilder>(
+ future: _albumImagesFuture,
+ builder: (context, snapshot) {
+ if (snapshot.connectionState == ConnectionState.waiting) {
+ return const Center(child: CircularProgressIndicator());
+ }
+
+ if (snapshot.hasError || !snapshot.hasData || snapshot.data!.isEmpty) {
+ return const Center(
+ child: Text('No images found or configuration invalid.'));
+ }
+
+ final imagesList = snapshot.data!;
+
+ return Align(
+ alignment: Alignment.topCenter,
+ child: Container(
+ constraints: const BoxConstraints(maxWidth: 500),
+ child: ListView.builder(
+ itemCount: imagesList.length,
+ itemBuilder: (context, index) {
+ final imageItem = imagesList[index];
+ return AlbumFeedPost(imageData: imageItem, index: index);
+ },
+ ),
+ ),
+ );
+ },
+ );
+ }
+}
+
+class AlbumFeedPost extends StatefulWidget {
+ final Map imageData;
+ final int index;
+ const AlbumFeedPost(
+ {super.key, required this.imageData, required this.index});
+
+ // Fixed type signature to accept dynamic map values
+ static final Map> _analysisCache = {};
+
+ @override
+ State createState() => _AlbumFeedPostState();
+}
+
+class _AlbumFeedPostState extends State {
+ bool _isNsfw = false;
+ bool _isBlurred = false;
+ bool _hasLoaded = false;
+
+ @override
+ void initState() {
+ super.initState();
+ analyzeImage();
+ }
+
+ Future analyzeImage() async {
+ final String? url = widget.imageData["url"];
+ if (url == null || url.isEmpty) {
+ if (mounted) setState(() => _hasLoaded = true);
+ return;
+ }
+
+ if (AlbumFeedPost._analysisCache.containsKey(url)) {
+ debugPrint("Fetching $url from cache.");
+ final cachedData = AlbumFeedPost._analysisCache[url]!;
+ if (mounted) {
+ setState(() {
+ _isNsfw = cachedData['isNsfw'] ?? false;
+ _isBlurred = cachedData['isBlurred'] ?? false;
+ _hasLoaded = true;
+ });
+ }
+ return;
+ }
+
+ try {
+ SensitivityAnalysisResult? isSensitive =
+ await sca.analyzeNetworkImage(url: url);
+
+ debugPrint(isSensitive?.isSensitive.toString());
+ debugPrint(isSensitive?.detectedTypes.toString());
+
+ final bool isNsfw = isSensitive?.isSensitive ?? false;
+
+ AlbumFeedPost._analysisCache[url] = {
+ 'isNsfw': isNsfw,
+ 'isBlurred': isNsfw,
+ };
+
+ if (mounted) {
+ setState(() {
+ _isNsfw = isNsfw;
+ _isBlurred = isNsfw;
+ });
+ }
+ } catch (e, st) {
+ debugPrint(e.toString());
+ debugPrint(st.toString());
+
+ AlbumFeedPost._analysisCache[url] = {
+ 'isNsfw': false,
+ 'isBlurred': false,
+ };
+ } finally {
+ if (mounted) setState(() => _hasLoaded = true);
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final String imageUrl = widget.imageData['url'] ?? '';
+
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
+ child: Row(
+ children: [
+ const CircleAvatar(radius: 16, backgroundColor: Colors.indigo),
+ const SizedBox(width: 10),
+ Text('creator',
+ style: const TextStyle(fontWeight: FontWeight.bold)),
+ ],
+ ),
+ ),
+ GestureDetector(
+ onTap: () {
+ if (!_isNsfw) return;
+ setState(() => _isBlurred = !_isBlurred);
+ },
+ child: Stack(
+ alignment: Alignment.center,
+ children: [
+ _hasLoaded
+ ? AspectRatio(
+ aspectRatio: 1,
+ child: CachedNetworkImage(
+ imageUrl: imageUrl,
+ width: double.infinity,
+ height: 440,
+ fit: BoxFit.cover,
+ ))
+ : Center(child: CircularProgressIndicator()),
+ if (_isBlurred) ...[
+ Positioned.fill(
+ child: ClipRect(
+ child: BackdropFilter(
+ filter: ImageFilter.blur(sigmaX: 100, sigmaY: 100),
+ child:
+ Container(color: Colors.black.withValues(alpha: 0.5)),
+ ),
+ ),
+ ),
+ Positioned.fill(
+ child: Container(
+ color: Colors.black.withValues(alpha: 0.4),
+ child: const Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Icon(Icons.lock_outline, color: Colors.white, size: 40),
+ SizedBox(height: 10),
+ Text(
+ 'Image Content Restricted\nTap to show the image',
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ color: Colors.white, fontWeight: FontWeight.bold),
+ )
+ ],
+ ),
+ ),
+ )
+ ]
+ ],
+ ),
+ ),
+ Row(
+ children: [
+ IconButton(
+ icon: const Icon(Icons.favorite_border), onPressed: () {}),
+ IconButton(
+ icon: const Icon(Icons.bookmark_border), onPressed: () {}),
+ ],
+ ),
+ const SizedBox(height: 12),
+ ],
+ );
+ }
+}
diff --git a/example/lib/main.dart b/example/lib/main.dart
index 70c7575..5bf1c57 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -7,8 +7,12 @@ import 'package:image_picker/image_picker.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sensitive_content_analysis/sensitive_content_analysis.dart';
import 'package:path/path.dart' as p;
+import 'package:sensitive_content_analysis_example/home_feed.dart';
+
+late final SensitiveContentAnalysis sca;
void main() {
+ sca = SensitiveContentAnalysis();
runApp(MaterialApp(theme: ThemeData.dark(), home: MyApp()));
}
@@ -20,8 +24,6 @@ class MyApp extends StatefulWidget {
}
class _MyAppState extends State {
- final sca = SensitiveContentAnalysis();
-
Future analyzeImage() async {
try {
final ImagePicker picker = ImagePicker();
@@ -33,8 +35,10 @@ class _MyAppState extends State {
Uint8List imageData = await image.readAsBytes();
// Analyze the image for sensitive content.
- bool? isSensitive = await sca.analyzeImage(imageData);
- _showResultDialog("Analysis Result", "SENSITIVE: $isSensitive");
+ SensitivityAnalysisResult? isSensitive =
+ await sca.analyzeImage(imageData);
+ _showResultDialog(
+ "Analysis Result", "SENSITIVE: ${isSensitive?.isSensitive}");
}
} catch (e) {
_showResultDialog("Error", e.toString());
@@ -47,8 +51,10 @@ class _MyAppState extends State {
"https://docs-assets.developer.apple.com/published/517e263450/rendered2x-1685188934.png";
// Analyze the image for sensitive content.
- bool? isSensitive = await sca.analyzeNetworkImage(url: url);
- _showResultDialog("Analysis Result", "SENSITIVE: $isSensitive");
+ SensitivityAnalysisResult? isSensitive =
+ await sca.analyzeNetworkImage(url: url);
+ _showResultDialog(
+ "Analysis Result", "SENSITIVE: ${isSensitive?.isSensitive}");
} catch (e) {
_showResultDialog("Error", e.toString());
}
@@ -65,8 +71,10 @@ class _MyAppState extends State {
final response = await dio.download(url, file.path);
if (response.statusCode == 200) {
- bool? isSensitive = await sca.analyzeVideo(url: file.path);
- _showResultDialog("Analysis Result", "SENSITIVE: $isSensitive");
+ SensitivityAnalysisResult? isSensitive =
+ await sca.analyzeVideo(url: file.path);
+ _showResultDialog(
+ "Analysis Result", "SENSITIVE: ${isSensitive?.isSensitive}");
await file.delete();
}
} catch (e) {
@@ -81,9 +89,10 @@ class _MyAppState extends State {
type: FileType.video,
);
if (selectedFile != null) {
- bool? isSensitive =
+ SensitivityAnalysisResult? isSensitive =
await sca.analyzeVideo(url: selectedFile.files.first.path!);
- _showResultDialog("Analysis Result", "SENSITIVE: $isSensitive");
+ _showResultDialog(
+ "Analysis Result", "SENSITIVE: ${isSensitive?.isSensitive}");
}
} catch (e) {
_showResultDialog("Error", e.toString());
@@ -129,6 +138,7 @@ class _MyAppState extends State {
onPressed: () async => await checkPolicy(),
child: const Text("Check Policy."),
),
+ warningWidget(),
]),
),
);
@@ -151,4 +161,123 @@ class _MyAppState extends State {
},
);
}
+
+ TextButton warningWidget() {
+ return TextButton(
+ onPressed: () {
+ showDialog(
+ context: context,
+ barrierDismissible: false,
+ builder: (BuildContext context) {
+ return Dialog(
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(24.0),
+ ),
+ elevation: 10,
+ backgroundColor: Theme.of(context).cardColor,
+ child: Padding(
+ padding: const EdgeInsets.all(24.0),
+ child: Column(
+ mainAxisSize: MainAxisSize.min, // Wrap content tightly
+ children: [
+ // 18+ Icon Header
+ Container(
+ padding: const EdgeInsets.all(16),
+ decoration: BoxDecoration(
+ color: Colors.red.withOpacity(0.1),
+ shape: BoxShape.circle,
+ ),
+ child: const Text(
+ "18+",
+ style: TextStyle(
+ fontSize: 28,
+ fontWeight: FontWeight.bold,
+ color: Colors.redAccent,
+ ),
+ ),
+ ),
+ const SizedBox(height: 20),
+
+ // Title
+ const Text(
+ "Age Verification Required",
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ fontSize: 20,
+ fontWeight: FontWeight.bold,
+ letterSpacing: 0.5,
+ ),
+ ),
+ const SizedBox(height: 12),
+
+ // Content Description
+ Text(
+ "The home feed contains content intended for mature audiences. Please verify that you are 18 years or older to proceed.",
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ fontSize: 14,
+ color: Colors.grey[600],
+ height: 1.4,
+ ),
+ ),
+ const SizedBox(height: 28),
+
+ // Actions (Stacked for a cleaner, full-bleed button look)
+ SizedBox(
+ width: double.infinity,
+ height: 48,
+ child: ElevatedButton(
+ style: ElevatedButton.styleFrom(
+ backgroundColor: Colors.redAccent,
+ foregroundColor: Colors.white,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(12),
+ ),
+ elevation: 0,
+ ),
+ onPressed: () {
+ Navigator.pop(context); // Close dialog
+ Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (context) => const FeedScreen(),
+ ),
+ );
+ },
+ child: const Text(
+ "I am 18 or older",
+ style: TextStyle(
+ fontSize: 16, fontWeight: FontWeight.w600),
+ ),
+ ),
+ ),
+ const SizedBox(height: 8),
+ SizedBox(
+ width: double.infinity,
+ height: 44,
+ child: TextButton(
+ style: TextButton.styleFrom(
+ foregroundColor: Colors.grey[600],
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(12),
+ ),
+ ),
+ onPressed: () => Navigator.pop(context), // Close dialog
+ child: const Text(
+ "Go Back",
+ style: TextStyle(
+ fontSize: 15, fontWeight: FontWeight.w500),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ },
+ );
+ },
+ child: const Text("Home feed example."),
+ );
+ }
}
diff --git a/example/macos/Flutter/Flutter-Debug.xcconfig b/example/macos/Flutter/Flutter-Debug.xcconfig
index 4b81f9b..c2efd0b 100644
--- a/example/macos/Flutter/Flutter-Debug.xcconfig
+++ b/example/macos/Flutter/Flutter-Debug.xcconfig
@@ -1,2 +1 @@
-#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "ephemeral/Flutter-Generated.xcconfig"
diff --git a/example/macos/Flutter/Flutter-Release.xcconfig b/example/macos/Flutter/Flutter-Release.xcconfig
index 5caa9d1..c2efd0b 100644
--- a/example/macos/Flutter/Flutter-Release.xcconfig
+++ b/example/macos/Flutter/Flutter-Release.xcconfig
@@ -1,2 +1 @@
-#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "ephemeral/Flutter-Generated.xcconfig"
diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift
index 6a9f476..37a3275 100644
--- a/example/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -7,12 +7,12 @@ import Foundation
import file_picker
import file_selector_macos
-import path_provider_foundation
import sensitive_content_analysis
+import sqflite_darwin
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
- PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SensitiveContentAnalysisPlugin.register(with: registry.registrar(forPlugin: "SensitiveContentAnalysisPlugin"))
+ SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
}
diff --git a/example/macos/Podfile b/example/macos/Podfile
deleted file mode 100644
index 1f811a9..0000000
--- a/example/macos/Podfile
+++ /dev/null
@@ -1,43 +0,0 @@
-platform :osx, '14.0'
-
-# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
-ENV['COCOAPODS_DISABLE_STATS'] = 'true'
-
-project 'Runner', {
- 'Debug' => :debug,
- 'Profile' => :release,
- 'Release' => :release,
-}
-
-def flutter_root
- generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
- unless File.exist?(generated_xcode_build_settings_path)
- raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
- end
-
- File.foreach(generated_xcode_build_settings_path) do |line|
- matches = line.match(/FLUTTER_ROOT\=(.*)/)
- return matches[1].strip if matches
- end
- raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
-end
-
-require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
-
-flutter_macos_podfile_setup
-
-target 'Runner' do
- use_frameworks!
- use_modular_headers!
-
- flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
- target 'RunnerTests' do
- inherit! :search_paths
- end
-end
-
-post_install do |installer|
- installer.pods_project.targets.each do |target|
- flutter_additional_macos_build_settings(target)
- end
-end
diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock
deleted file mode 100644
index bbea909..0000000
--- a/example/macos/Podfile.lock
+++ /dev/null
@@ -1,23 +0,0 @@
-PODS:
- - FlutterMacOS (1.0.0)
- - path_provider_foundation (0.0.1):
- - Flutter
- - FlutterMacOS
-
-DEPENDENCIES:
- - FlutterMacOS (from `Flutter/ephemeral`)
- - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
-
-EXTERNAL SOURCES:
- FlutterMacOS:
- :path: Flutter/ephemeral
- path_provider_foundation:
- :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
-
-SPEC CHECKSUMS:
- FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
- path_provider_foundation: 608fcb11be570ce83519b076ab6a1fffe2474f05
-
-PODFILE CHECKSUM: 6acf97521436d16fc31cd5e1a02000905acdb3ae
-
-COCOAPODS: 1.16.2
diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj
index 49be747..215a4c5 100644
--- a/example/macos/Runner.xcodeproj/project.pbxproj
+++ b/example/macos/Runner.xcodeproj/project.pbxproj
@@ -27,9 +27,7 @@
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
- 5F8E2129349DD2ADB44CA281 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C4D5C9BE33EC2BDEF637609 /* Pods_Runner.framework */; };
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
- 78FA66B7C2A45709C4128F28 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D8F111450786C92DF977678C /* Pods_RunnerTests.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -63,8 +61,6 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
- 1CDB1DEC3823320EA5F2E111 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; };
- 2DA548C834AAAB4734956DDF /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; };
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; };
@@ -81,17 +77,11 @@
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; };
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; };
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; };
- 4C4D5C9BE33EC2BDEF637609 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 669FDA171898C867DBAF4BE7 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
- 68098A31A007FACF677C042E /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
784666492D4C4C64000A1A5F /* FlutterFramework */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterFramework; path = ephemeral/Packages/.packages/FlutterFramework; sourceTree = ""; };
78DABEA22ED26510000E7860 /* sensitive_content_analysis */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = sensitive_content_analysis; path = ../../../darwin/sensitive_content_analysis; sourceTree = ""; };
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; };
- 7F890A81D151036D48996D76 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; };
- D8F111450786C92DF977678C /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- E4519D8E12DBAC847FE32D5E /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -99,7 +89,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 78FA66B7C2A45709C4128F28 /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -108,7 +97,6 @@
buildActionMask = 2147483647;
files = (
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
- 5F8E2129349DD2ADB44CA281 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -141,7 +129,6 @@
33CEB47122A05771004F2AC0 /* Flutter */,
331C80D6294CF71000263BE5 /* RunnerTests */,
33CC10EE2044A3C60003C045 /* Products */,
- D73912EC22F37F3D000D13A0 /* Frameworks */,
FE0095FE3310AF736E67F430 /* Pods */,
);
sourceTree = "";
@@ -193,24 +180,9 @@
path = Runner;
sourceTree = "";
};
- D73912EC22F37F3D000D13A0 /* Frameworks */ = {
- isa = PBXGroup;
- children = (
- 4C4D5C9BE33EC2BDEF637609 /* Pods_Runner.framework */,
- D8F111450786C92DF977678C /* Pods_RunnerTests.framework */,
- );
- name = Frameworks;
- sourceTree = "";
- };
FE0095FE3310AF736E67F430 /* Pods */ = {
isa = PBXGroup;
children = (
- 669FDA171898C867DBAF4BE7 /* Pods-Runner.debug.xcconfig */,
- 68098A31A007FACF677C042E /* Pods-Runner.release.xcconfig */,
- 2DA548C834AAAB4734956DDF /* Pods-Runner.profile.xcconfig */,
- 1CDB1DEC3823320EA5F2E111 /* Pods-RunnerTests.debug.xcconfig */,
- E4519D8E12DBAC847FE32D5E /* Pods-RunnerTests.release.xcconfig */,
- 7F890A81D151036D48996D76 /* Pods-RunnerTests.profile.xcconfig */,
);
path = Pods;
sourceTree = "";
@@ -222,7 +194,6 @@
isa = PBXNativeTarget;
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
- 8ECA4C9ED57C573F7D3AD1F1 /* [CP] Check Pods Manifest.lock */,
331C80D1294CF70F00263BE5 /* Sources */,
331C80D2294CF70F00263BE5 /* Frameworks */,
331C80D3294CF70F00263BE5 /* Resources */,
@@ -241,13 +212,11 @@
isa = PBXNativeTarget;
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
- 200F0ECB42FD60E75A241A10 /* [CP] Check Pods Manifest.lock */,
33CC10E92044A3C60003C045 /* Sources */,
33CC10EA2044A3C60003C045 /* Frameworks */,
33CC10EB2044A3C60003C045 /* Resources */,
33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */,
- BAD8864925DB97FE97CBA973 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -335,28 +304,6 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
- 200F0ECB42FD60E75A241A10 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputFileListPaths = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputFileListPaths = (
- );
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
3399D490228B24CF009A79C7 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@@ -395,45 +342,6 @@
shellPath = /bin/sh;
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
};
- 8ECA4C9ED57C573F7D3AD1F1 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputFileListPaths = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputFileListPaths = (
- );
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
- BAD8864925DB97FE97CBA973 /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
- );
- name = "[CP] Embed Pods Frameworks";
- outputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -485,7 +393,6 @@
/* Begin XCBuildConfiguration section */
331C80DB294CF71000263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 1CDB1DEC3823320EA5F2E111 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
@@ -500,7 +407,6 @@
};
331C80DC294CF71000263BE5 /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = E4519D8E12DBAC847FE32D5E /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
@@ -515,7 +421,6 @@
};
331C80DD294CF71000263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 7F890A81D151036D48996D76 /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
diff --git a/example/pubspec.lock b/example/pubspec.lock
index 0e506f0..9bdafff 100644
--- a/example/pubspec.lock
+++ b/example/pubspec.lock
@@ -1,6 +1,22 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
+ _fe_analyzer_shared:
+ dependency: transitive
+ description:
+ name: _fe_analyzer_shared
+ sha256: cd6add6f846f35fb79f3c315296703c1a24f3cfd7f4739d91a74961c1c7e9f1b
+ url: "https://pub.dev"
+ source: hosted
+ version: "100.0.0"
+ analyzer:
+ dependency: transitive
+ description:
+ name: analyzer
+ sha256: "6ba98576948803398b69e3a444df24eacdbe12ed699c7014e120ea38552debbf"
+ url: "https://pub.dev"
+ source: hosted
+ version: "13.0.0"
args:
dependency: transitive
description:
@@ -13,18 +29,90 @@ packages:
dependency: transitive
description:
name: async
- sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
+ sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
url: "https://pub.dev"
source: hosted
- version: "2.11.0"
+ version: "2.13.1"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
- sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
+ sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
url: "https://pub.dev"
source: hosted
- version: "2.1.1"
+ version: "2.1.2"
+ build:
+ dependency: transitive
+ description:
+ name: build
+ sha256: a156715e7cd728130c592f30552575908aae5b100005fbc1f0fb16b3c03a3d10
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.0.6"
+ build_config:
+ dependency: transitive
+ description:
+ name: build_config
+ sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.0"
+ build_daemon:
+ dependency: transitive
+ description:
+ name: build_daemon
+ sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.1.1"
+ build_runner:
+ dependency: "direct dev"
+ description:
+ name: build_runner
+ sha256: "1523ce62448ebac2c15a8ba5fbad8acac169788658a7dd2a1c2d9c2a9318b9a6"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.15.0"
+ built_collection:
+ dependency: transitive
+ description:
+ name: built_collection
+ sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.1.1"
+ built_value:
+ dependency: transitive
+ description:
+ name: built_value
+ sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56"
+ url: "https://pub.dev"
+ source: hosted
+ version: "8.12.6"
+ cached_network_image:
+ dependency: "direct main"
+ description:
+ name: cached_network_image
+ sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.4.1"
+ cached_network_image_platform_interface:
+ dependency: transitive
+ description:
+ name: cached_network_image_platform_interface
+ sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.1.1"
+ cached_network_image_web:
+ dependency: transitive
+ description:
+ name: cached_network_image_web
+ sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.1"
characters:
dependency: transitive
description:
@@ -33,6 +121,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.1"
+ checked_yaml:
+ dependency: transitive
+ description:
+ name: checked_yaml
+ sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.4"
clock:
dependency: transitive
description:
@@ -41,6 +137,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.2"
+ code_assets:
+ dependency: transitive
+ description:
+ name: code_assets
+ sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.1"
+ code_builder:
+ dependency: transitive
+ description:
+ name: code_builder
+ sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.11.1"
collection:
dependency: transitive
description:
@@ -49,6 +161,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.19.1"
+ convert:
+ dependency: transitive
+ description:
+ name: convert
+ sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.2"
cross_file:
dependency: transitive
description:
@@ -57,6 +177,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.3.5+2"
+ crypto:
+ dependency: transitive
+ description:
+ name: crypto
+ sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.7"
cupertino_icons:
dependency: "direct main"
description:
@@ -65,14 +193,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.9"
+ dart_style:
+ dependency: transitive
+ description:
+ name: dart_style
+ sha256: "59d53ef8eaed9d288ed9767618e2b31c4fa0383a127db59d5eb2e737a7638a60"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.9"
dbus:
dependency: transitive
description:
name: dbus
- sha256: "792974a4007974fbc5c1b5433eb2330a9db3e368c3f906253af4c007d0f49a91"
+ sha256: "0ce9b0a839e6dee59a37a623d2fc26a35bbbe6404213e419b0d6411023d62645"
url: "https://pub.dev"
source: hosted
- version: "0.7.13"
+ version: "0.7.14"
dio:
dependency: "direct main"
description:
@@ -85,10 +221,10 @@ packages:
dependency: transitive
description:
name: dio_web_adapter
- sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
+ sha256: "2f9e64323a7c3c7ef69567d5c800424a11f8337b8b228bad02524c9fb3c1f340"
url: "https://pub.dev"
source: hosted
- version: "2.1.1"
+ version: "2.1.2"
fake_async:
dependency: transitive
description:
@@ -117,10 +253,10 @@ packages:
dependency: transitive
description:
name: file
- sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
+ sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.dev"
source: hosted
- version: "7.0.0"
+ version: "7.0.1"
file_picker:
dependency: "direct main"
description:
@@ -157,15 +293,31 @@ packages:
dependency: transitive
description:
name: file_selector_windows
- sha256: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0
+ sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd"
url: "https://pub.dev"
source: hosted
- version: "0.9.3+1"
+ version: "0.9.3+5"
+ fixnum:
+ dependency: transitive
+ description:
+ name: fixnum
+ sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.1"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
+ flutter_cache_manager:
+ dependency: transitive
+ description:
+ name: flutter_cache_manager
+ sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.4.1"
flutter_driver:
dependency: transitive
description: flutter
@@ -202,22 +354,54 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
+ glob:
+ dependency: transitive
+ description:
+ name: glob
+ sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.3"
+ graphs:
+ dependency: transitive
+ description:
+ name: graphs
+ sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.3.2"
+ hooks:
+ dependency: transitive
+ description:
+ name: hooks
+ sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.2"
http:
dependency: transitive
description:
name: http
- sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007
+ sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
url: "https://pub.dev"
source: hosted
- version: "1.5.0"
+ version: "1.6.0"
+ http_multi_server:
+ dependency: transitive
+ description:
+ name: http_multi_server
+ sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.2.2"
http_parser:
dependency: transitive
description:
name: http_parser
- sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
+ sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev"
source: hosted
- version: "4.0.2"
+ version: "4.1.2"
image_picker:
dependency: "direct main"
description:
@@ -230,26 +414,26 @@ packages:
dependency: transitive
description:
name: image_picker_android
- sha256: "8dfe08ea7fcf7467dbaf6889e72eebd5e0d6711caae201fdac780eb45232cd02"
+ sha256: "6f3a1995eafb000333174fae92202622033b0ee7fd917a6cd3730295264df84a"
url: "https://pub.dev"
source: hosted
- version: "0.8.13+3"
+ version: "0.8.13+19"
image_picker_for_web:
dependency: transitive
description:
name: image_picker_for_web
- sha256: "40c2a6a0da15556dc0f8e38a3246064a971a9f512386c3339b89f76db87269b6"
+ sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214"
url: "https://pub.dev"
source: hosted
- version: "3.1.0"
+ version: "3.1.1"
image_picker_ios:
dependency: transitive
description:
name: image_picker_ios
- sha256: eb06fe30bab4c4497bad449b66448f50edcc695f1c59408e78aa3a8059eb8f0e
+ sha256: b9c4a438a9ff4f60808c9cf0039b93a42bb6c2211ef6ebb647394b2b3fa84588
url: "https://pub.dev"
source: hosted
- version: "0.8.13"
+ version: "0.8.13+6"
image_picker_linux:
dependency: transitive
description:
@@ -262,18 +446,18 @@ packages:
dependency: transitive
description:
name: image_picker_macos
- sha256: d58cd9d67793d52beefd6585b12050af0a7663c0c2a6ece0fb110a35d6955e04
+ sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91"
url: "https://pub.dev"
source: hosted
- version: "0.2.2"
+ version: "0.2.2+1"
image_picker_platform_interface:
dependency: transitive
description:
name: image_picker_platform_interface
- sha256: "9f143b0dba3e459553209e20cc425c9801af48e6dfa4f01a0fcf927be3f41665"
+ sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c"
url: "https://pub.dev"
source: hosted
- version: "2.11.0"
+ version: "2.11.1"
image_picker_windows:
dependency: transitive
description:
@@ -287,6 +471,38 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
+ io:
+ dependency: transitive
+ description:
+ name: io
+ sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.5"
+ jni:
+ dependency: transitive
+ description:
+ name: jni
+ sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.0"
+ jni_flutter:
+ dependency: transitive
+ description:
+ name: jni_flutter
+ sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.1"
+ json_annotation:
+ dependency: transitive
+ description:
+ name: json_annotation
+ sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.12.0"
leak_tracker:
dependency: transitive
description:
@@ -319,6 +535,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.1.0"
+ logging:
+ dependency: transitive
+ description:
+ name: logging
+ sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.0"
matcher:
dependency: transitive
description:
@@ -347,12 +571,44 @@ packages:
dependency: transitive
description:
name: mime
- sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
+ sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
url: "https://pub.dev"
source: hosted
- version: "1.0.5"
- path:
+ version: "2.0.0"
+ mockito:
+ dependency: "direct dev"
+ description:
+ name: mockito
+ sha256: c8d040d754367108fbe482dcb79dd72b8fe60ac6727abd15b4783c5560297ee6
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.7.0"
+ objective_c:
+ dependency: transitive
+ description:
+ name: objective_c
+ sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed"
+ url: "https://pub.dev"
+ source: hosted
+ version: "9.4.1"
+ octo_image:
+ dependency: transitive
+ description:
+ name: octo_image
+ sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.0"
+ package_config:
dependency: transitive
+ description:
+ name: package_config
+ sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.0"
+ path:
+ dependency: "direct main"
description:
name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
@@ -371,18 +627,18 @@ packages:
dependency: transitive
description:
name: path_provider_android
- sha256: "993381400e94d18469750e5b9dcb8206f15bc09f9da86b9e44a9b0092a0066db"
+ sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd"
url: "https://pub.dev"
source: hosted
- version: "2.2.18"
+ version: "2.3.1"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
- sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
+ sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699"
url: "https://pub.dev"
source: hosted
- version: "2.3.2"
+ version: "2.6.0"
path_provider_linux:
dependency: transitive
description:
@@ -419,10 +675,10 @@ packages:
dependency: transitive
description:
name: platform
- sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
+ sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.dev"
source: hosted
- version: "3.1.4"
+ version: "3.1.6"
plugin_platform_interface:
dependency: transitive
description:
@@ -431,34 +687,138 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.8"
+ pool:
+ dependency: transitive
+ description:
+ name: pool
+ sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.5.2"
process:
dependency: transitive
description:
name: process
- sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32"
+ sha256: c6248e4526673988586e8c00bb22a49210c258dc91df5227d5da9748ecf79744
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.0.5"
+ pub_semver:
+ dependency: transitive
+ description:
+ name: pub_semver
+ sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.0"
+ pubspec_parse:
+ dependency: transitive
+ description:
+ name: pubspec_parse
+ sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.5.0"
+ record_use:
+ dependency: transitive
+ description:
+ name: record_use
+ sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed"
url: "https://pub.dev"
source: hosted
- version: "5.0.2"
+ version: "0.6.0"
+ rxdart:
+ dependency: transitive
+ description:
+ name: rxdart
+ sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.28.0"
sensitive_content_analysis:
dependency: "direct main"
description:
path: ".."
relative: true
source: path
- version: "2.0.3"
+ version: "2.1.0"
+ shelf:
+ dependency: transitive
+ description:
+ name: shelf
+ sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.4.2"
+ shelf_web_socket:
+ dependency: transitive
+ description:
+ name: shelf_web_socket
+ sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.0"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
+ source_gen:
+ dependency: transitive
+ description:
+ name: source_gen
+ sha256: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.2.3"
source_span:
dependency: transitive
description:
name: source_span
- sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
+ sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.10.2"
+ sqflite:
+ dependency: transitive
+ description:
+ name: sqflite
+ sha256: "58a799e6ac17dd32fbab93813d39ed835a75ccc0f8f85b8955fe318c6712b082"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.3"
+ sqflite_android:
+ dependency: transitive
+ description:
+ name: sqflite_android
+ sha256: d0548f9d7422a2dae99ec6f8b0a3074463b132d216fa5ba0d230eeefc901983b
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.3"
+ sqflite_common:
+ dependency: transitive
+ description:
+ name: sqflite_common
+ sha256: cce558075afe2a83f3fd7fc123acd6b090683e4f23910d44fbb31ecd7800b014
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.5.9"
+ sqflite_darwin:
+ dependency: transitive
+ description:
+ name: sqflite_darwin
+ sha256: "164a5d73ab87a134566057219988bafde837029a64264e61f1f04376ef3cfcd2"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.3"
+ sqflite_platform_interface:
+ dependency: transitive
+ description:
+ name: sqflite_platform_interface
+ sha256: f84939f84350d92d04416f8bc4dc52d3896aec7716cc9e80cf0146342139dc50
url: "https://pub.dev"
source: hosted
- version: "1.10.0"
+ version: "2.4.1"
stack_trace:
dependency: transitive
description:
@@ -475,14 +835,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
+ stream_transform:
+ dependency: transitive
+ description:
+ name: stream_transform
+ sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.1"
string_scanner:
dependency: transitive
description:
name: string_scanner
- sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
+ sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
url: "https://pub.dev"
source: hosted
- version: "1.2.0"
+ version: "1.4.1"
sync_http:
dependency: transitive
description:
@@ -491,14 +859,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.3.1"
+ synchronized:
+ dependency: transitive
+ description:
+ name: synchronized
+ sha256: "93b153dcb6a26dcddee6ca087dd634b53e38c10b5aa163e8e49501a776456153"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.4.1"
term_glyph:
dependency: transitive
description:
name: term_glyph
- sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
+ sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
url: "https://pub.dev"
source: hosted
- version: "1.2.1"
+ version: "1.2.2"
test_api:
dependency: transitive
description:
@@ -511,10 +887,18 @@ packages:
dependency: transitive
description:
name: typed_data
- sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
+ sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.4.0"
+ uuid:
+ dependency: transitive
+ description:
+ name: uuid
+ sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489"
url: "https://pub.dev"
source: hosted
- version: "1.3.2"
+ version: "4.5.3"
vector_math:
dependency: transitive
description:
@@ -527,10 +911,18 @@ packages:
dependency: transitive
description:
name: vm_service
- sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
+ sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360"
url: "https://pub.dev"
source: hosted
- version: "13.0.0"
+ version: "15.2.0"
+ watcher:
+ dependency: transitive
+ description:
+ name: watcher
+ sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.1"
web:
dependency: transitive
description:
@@ -539,14 +931,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.1"
+ web_socket:
+ dependency: transitive
+ description:
+ name: web_socket
+ sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.1"
+ web_socket_channel:
+ dependency: transitive
+ description:
+ name: web_socket_channel
+ sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.3"
webdriver:
dependency: transitive
description:
name: webdriver
- sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e"
+ sha256: "2f3a14ca026957870cfd9c635b83507e0e51d8091568e90129fbf805aba7cade"
url: "https://pub.dev"
source: hosted
- version: "3.0.3"
+ version: "3.1.0"
win32:
dependency: transitive
description:
@@ -559,18 +967,26 @@ packages:
dependency: transitive
description:
name: xdg_directories
- sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
+ sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
- version: "1.0.4"
+ version: "1.1.0"
xml:
dependency: transitive
description:
name: xml
- sha256: "67f0aff7be013d107995e9b75bf4e7f2c3ef2dfdb2c8e68024bba0a7fd5756a4"
+ sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
url: "https://pub.dev"
source: hosted
- version: "7.0.1"
+ version: "6.6.1"
+ yaml:
+ dependency: transitive
+ description:
+ name: yaml
+ sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.3"
sdks:
- dart: ">=3.11.0 <4.0.0"
- flutter: ">=3.41.0"
+ dart: ">=3.12.0 <4.0.0"
+ flutter: ">=3.44.0"
diff --git a/example/pubspec.yaml b/example/pubspec.yaml
index 3724744..ef07b4a 100644
--- a/example/pubspec.yaml
+++ b/example/pubspec.yaml
@@ -16,17 +16,18 @@ dependencies:
file_picker: 12.0.0-beta.3 #^11.0.2
path_provider: ^2.1.5
dio: ^5.9.2
+ path: ^1.9.1
+ cached_network_image: ^3.4.1
dev_dependencies:
integration_test:
sdk: flutter
flutter_test:
sdk: flutter
+ mockito: ^5.4.4
+ build_runner: ^2.4.9
flutter_lints: ^6.0.0
flutter:
- uses-material-design: true
-
- # config:
- # enable-swift-package-manager: false
\ No newline at end of file
+ uses-material-design: true
\ No newline at end of file
diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart
index 330c48c..82d5936 100644
--- a/example/test/widget_test.dart
+++ b/example/test/widget_test.dart
@@ -1,27 +1,260 @@
-// This is a basic Flutter widget test.
-//
-// To perform an interaction with a widget in your test, use the WidgetTester
-// utility in the flutter_test package. For example, you can send tap and scroll
-// gestures. You can also use WidgetTester to find child widgets in the widget
-// tree, read text, and verify that the values of widget properties are correct.
+import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+import 'package:sensitive_content_analysis/sensitive_content_analysis.dart';
-import 'package:sensitive_content_analysis_example/main.dart';
+import 'widget_test.mocks.dart';
+@GenerateMocks([SensitiveContentAnalysis])
void main() {
- testWidgets('Verify Platform version', (WidgetTester tester) async {
- // Build our app and trigger a frame.
- await tester.pumpWidget(const MyApp());
-
- // Verify that platform version is retrieved.
- expect(
- find.byWidgetPredicate(
- (Widget widget) => widget is Text &&
- widget.data!.startsWith('Running on:'),
- ),
- findsOneWidget,
- );
+ late MockSensitiveContentAnalysis mockSca;
+
+ setUp(() {
+ mockSca = MockSensitiveContentAnalysis();
+ });
+
+ group('SensitiveContentAnalysis - analyzeImage', () {
+ test('returns isSensitive=false for safe image bytes', () async {
+ final fakeBytes = Uint8List.fromList([0, 1, 2, 3]);
+ when(mockSca.analyzeImage(fakeBytes)).thenAnswer(
+ (_) async =>
+ SensitivityAnalysisResult(isSensitive: false, detectedTypes: []),
+ );
+
+ final result = await mockSca.analyzeImage(fakeBytes);
+
+ expect(result, isNotNull);
+ expect(result!.isSensitive, isFalse);
+ verify(mockSca.analyzeImage(fakeBytes)).called(1);
+ });
+
+ test('returns isSensitive=true for sensitive image bytes', () async {
+ final fakeBytes = Uint8List.fromList([255, 254, 253]);
+ when(mockSca.analyzeImage(fakeBytes)).thenAnswer(
+ (_) async =>
+ SensitivityAnalysisResult(isSensitive: true, detectedTypes: []),
+ );
+
+ final result = await mockSca.analyzeImage(fakeBytes);
+
+ expect(result, isNotNull);
+ expect(result!.isSensitive, isTrue);
+ });
+
+ test('returns null when analysis cannot be performed', () async {
+ final fakeBytes = Uint8List(0);
+ when(mockSca.analyzeImage(fakeBytes)).thenAnswer((_) async => null);
+
+ final result = await mockSca.analyzeImage(fakeBytes);
+
+ expect(result, isNull);
+ });
+
+ test('propagates exception on analysis failure', () async {
+ final fakeBytes = Uint8List.fromList([1, 2, 3]);
+ when(mockSca.analyzeImage(fakeBytes))
+ .thenThrow(Exception('Analysis failed'));
+
+ expect(() => mockSca.analyzeImage(fakeBytes), throwsException);
+ });
+ });
+
+ group('SensitiveContentAnalysis - analyzeNetworkImage', () {
+ const safeUrl =
+ 'https://docs-assets.developer.apple.com/published/517e263450/rendered2x-1685188934.png';
+
+ test('returns isSensitive=false for safe network image', () async {
+ when(mockSca.analyzeNetworkImage(url: safeUrl)).thenAnswer(
+ (_) async =>
+ SensitivityAnalysisResult(isSensitive: false, detectedTypes: []),
+ );
+
+ final result = await mockSca.analyzeNetworkImage(url: safeUrl);
+
+ expect(result, isNotNull);
+ expect(result!.isSensitive, isFalse);
+ verify(mockSca.analyzeNetworkImage(url: safeUrl)).called(1);
+ });
+
+ test('returns null for unreachable URL', () async {
+ const badUrl = 'https://invalid.example.com/image.png';
+ when(mockSca.analyzeNetworkImage(url: badUrl))
+ .thenAnswer((_) async => null);
+
+ final result = await mockSca.analyzeNetworkImage(url: badUrl);
+
+ expect(result, isNull);
+ });
+
+ test('throws on network error', () async {
+ const errorUrl = 'https://unreachable.example.com/img.jpg';
+ when(mockSca.analyzeNetworkImage(url: errorUrl))
+ .thenThrow(Exception('Network error'));
+
+ expect(
+ () => mockSca.analyzeNetworkImage(url: errorUrl),
+ throwsException,
+ );
+ });
+ });
+
+ group('SensitiveContentAnalysis - analyzeVideo', () {
+ const videoPath = '/tmp/test_video.mov';
+
+ test('returns isSensitive=false for safe video', () async {
+ when(mockSca.analyzeVideo(url: videoPath)).thenAnswer(
+ (_) async =>
+ SensitivityAnalysisResult(isSensitive: false, detectedTypes: []),
+ );
+
+ final result = await mockSca.analyzeVideo(url: videoPath);
+
+ expect(result, isNotNull);
+ expect(result!.isSensitive, isFalse);
+ verify(mockSca.analyzeVideo(url: videoPath)).called(1);
+ });
+
+ test('returns isSensitive=true for flagged video', () async {
+ when(mockSca.analyzeVideo(url: videoPath)).thenAnswer(
+ (_) async =>
+ SensitivityAnalysisResult(isSensitive: true, detectedTypes: []),
+ );
+
+ final result = await mockSca.analyzeVideo(url: videoPath);
+
+ expect(result!.isSensitive, isTrue);
+ });
+
+ test('throws on missing video file', () async {
+ const missingPath = '/tmp/nonexistent.mov';
+ when(mockSca.analyzeVideo(url: missingPath))
+ .thenThrow(Exception('File not found'));
+
+ expect(
+ () => mockSca.analyzeVideo(url: missingPath),
+ throwsException,
+ );
+ });
+ });
+
+ group('SensitiveContentAnalysis - checkPolicy', () {
+ test('returns policy code 0 (no restrictions)', () async {
+ when(mockSca.checkPolicy()).thenAnswer((_) async => 0);
+
+ final policy = await mockSca.checkPolicy();
+
+ expect(policy, equals(0));
+ verify(mockSca.checkPolicy()).called(1);
+ });
+
+ test('returns policy code 1 (restricted)', () async {
+ when(mockSca.checkPolicy()).thenAnswer((_) async => 1);
+
+ final policy = await mockSca.checkPolicy();
+
+ expect(policy, equals(1));
+ });
+
+ test('returns null when policy cannot be determined', () async {
+ when(mockSca.checkPolicy()).thenAnswer((_) async => null);
+
+ final policy = await mockSca.checkPolicy();
+
+ expect(policy, isNull);
+ });
+
+ test('throws on policy fetch failure', () async {
+ when(mockSca.checkPolicy()).thenThrow(Exception('Policy fetch failed'));
+
+ expect(() => mockSca.checkPolicy(), throwsException);
+ });
+ });
+
+ group('SensitivityAnalysisResult model', () {
+ test('constructs with isSensitive=true', () {
+ final result =
+ SensitivityAnalysisResult(isSensitive: true, detectedTypes: []);
+ expect(result.isSensitive, isTrue);
+ });
+
+ test('constructs with isSensitive=false', () {
+ final result =
+ SensitivityAnalysisResult(isSensitive: false, detectedTypes: []);
+ expect(result.isSensitive, isFalse);
+ });
+ });
+
+ group('UI - result dialog smoke test', () {
+ testWidgets('shows AlertDialog with title and message', (tester) async {
+ await tester.pumpWidget(
+ MaterialApp(
+ home: Builder(
+ builder: (context) => Scaffold(
+ body: TextButton(
+ onPressed: () => showDialog(
+ context: context,
+ builder: (_) => AlertDialog(
+ title: const Text('Analysis Result'),
+ content: const Text('SENSITIVE: false'),
+ actions: [
+ TextButton(
+ onPressed: () => Navigator.pop(context),
+ child: const Text('OK'),
+ ),
+ ],
+ ),
+ ),
+ child: const Text('Open Dialog'),
+ ),
+ ),
+ ),
+ ),
+ );
+
+ await tester.tap(find.text('Open Dialog'));
+ await tester.pumpAndSettle();
+
+ expect(find.text('Analysis Result'), findsOneWidget);
+ expect(find.text('SENSITIVE: false'), findsOneWidget);
+ expect(find.text('OK'), findsOneWidget);
+ });
+
+ testWidgets('dialog dismisses on OK tap', (tester) async {
+ await tester.pumpWidget(
+ MaterialApp(
+ home: Builder(
+ builder: (context) => Scaffold(
+ body: TextButton(
+ onPressed: () => showDialog(
+ context: context,
+ builder: (_) => AlertDialog(
+ title: const Text('Policy Check'),
+ content: const Text('Policy: 0'),
+ actions: [
+ TextButton(
+ onPressed: () => Navigator.pop(context),
+ child: const Text('OK'),
+ ),
+ ],
+ ),
+ ),
+ child: const Text('Open Dialog'),
+ ),
+ ),
+ ),
+ ),
+ );
+
+ await tester.tap(find.text('Open Dialog'));
+ await tester.pumpAndSettle();
+ expect(find.text('Policy: 0'), findsOneWidget);
+
+ await tester.tap(find.text('OK'));
+ await tester.pumpAndSettle();
+ expect(find.text('Policy: 0'), findsNothing);
+ });
});
}
diff --git a/example/test/widget_test.mocks.dart b/example/test/widget_test.mocks.dart
new file mode 100644
index 0000000..c28e8f0
--- /dev/null
+++ b/example/test/widget_test.mocks.dart
@@ -0,0 +1,80 @@
+// Mocks generated by Mockito 5.4.6 from annotations
+// in sensitive_content_analysis_example/test/widget_test.dart.
+// Do not manually edit this file.
+
+// ignore_for_file: no_leading_underscores_for_library_prefixes
+import 'dart:async' as _i3;
+import 'dart:typed_data' as _i4;
+
+import 'package:mockito/mockito.dart' as _i1;
+import 'package:sensitive_content_analysis/sensitive_content_analysis.dart'
+ as _i2;
+
+// ignore_for_file: type=lint
+// ignore_for_file: avoid_redundant_argument_values
+// ignore_for_file: avoid_setters_without_getters
+// ignore_for_file: comment_references
+// ignore_for_file: deprecated_member_use
+// ignore_for_file: deprecated_member_use_from_same_package
+// ignore_for_file: implementation_imports
+// ignore_for_file: invalid_use_of_visible_for_testing_member
+// ignore_for_file: must_be_immutable
+// ignore_for_file: prefer_const_constructors
+// ignore_for_file: unnecessary_parenthesis
+// ignore_for_file: camel_case_types
+// ignore_for_file: subtype_of_sealed_class
+// ignore_for_file: invalid_use_of_internal_member
+
+/// A class which mocks [SensitiveContentAnalysis].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockSensitiveContentAnalysis extends _i1.Mock
+ implements _i2.SensitiveContentAnalysis {
+ MockSensitiveContentAnalysis() {
+ _i1.throwOnMissingStub(this);
+ }
+
+ @override
+ _i3.Future<_i2.SensitivityAnalysisResult?> analyzeImage(
+ _i4.Uint8List? file) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #analyzeImage,
+ [file],
+ ),
+ returnValue: _i3.Future<_i2.SensitivityAnalysisResult?>.value(),
+ ) as _i3.Future<_i2.SensitivityAnalysisResult?>);
+
+ @override
+ _i3.Future<_i2.SensitivityAnalysisResult?> analyzeNetworkImage(
+ {required String? url}) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #analyzeNetworkImage,
+ [],
+ {#url: url},
+ ),
+ returnValue: _i3.Future<_i2.SensitivityAnalysisResult?>.value(),
+ ) as _i3.Future<_i2.SensitivityAnalysisResult?>);
+
+ @override
+ _i3.Future<_i2.SensitivityAnalysisResult?> analyzeVideo(
+ {required String? url}) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #analyzeVideo,
+ [],
+ {#url: url},
+ ),
+ returnValue: _i3.Future<_i2.SensitivityAnalysisResult?>.value(),
+ ) as _i3.Future<_i2.SensitivityAnalysisResult?>);
+
+ @override
+ _i3.Future checkPolicy() => (super.noSuchMethod(
+ Invocation.method(
+ #checkPolicy,
+ [],
+ ),
+ returnValue: _i3.Future.value(),
+ ) as _i3.Future);
+}
diff --git a/lib/sensitive_content_analysis.dart b/lib/sensitive_content_analysis.dart
index 2ac5331..05bc248 100644
--- a/lib/sensitive_content_analysis.dart
+++ b/lib/sensitive_content_analysis.dart
@@ -5,12 +5,13 @@ const methodChannel = MethodChannel('sensitive_content_analysis');
class SensitiveContentAnalysis {
/// Analyzes an image for sensitive content.
///
- /// Returns a `bool` indicating whether the image is sensitive.
- Future analyzeImage(Uint8List file) async {
+ /// Returns a [SensitivityAnalysisResult] with sensitivity status and detected content types.
+ Future analyzeImage(Uint8List file) async {
try {
- final dynamic isSensitive =
- await methodChannel.invokeMethod('analyzeImage', file);
- return isSensitive;
+ final Map