Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 1.5.2

- iOS: add Swift Package Manager support
- Android: AGP 9 compatibility — apply `kotlin-android` only on AGP < 9 (AGP 9+ has built-in Kotlin), switch to new DSL (`minSdk`/`targetSdk`), drop unused `kotlinOptions` block

## 1.5.1

- Refactor: replace hack-based screenshot prevention with overlay implementation
Expand Down
22 changes: 8 additions & 14 deletions android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
buildscript {
ext.kotlin_version = '2.2.20'
}

plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
}

// AGP 9+ has built-in Kotlin support; older versions need the plugin explicitly
def agpMajor = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.split('\\.')[0].toInteger()
if (agpMajor < 9) {
apply plugin: 'kotlin-android'
}

rootProject.allprojects {
Expand All @@ -17,9 +18,6 @@ rootProject.allprojects {

group 'com.prongbang.screen_protector'

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

android {
namespace 'com.prongbang.screen_protector'
compileSdk 36
Expand All @@ -29,17 +27,13 @@ android {
targetCompatibility JavaVersion.VERSION_17
}

kotlinOptions {
jvmTarget = '17'
}

sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}

defaultConfig {
minSdkVersion 21
targetSdkVersion 36
minSdk 21
targetSdk 36
}

lintOptions {
Expand Down
23 changes: 23 additions & 0 deletions ios/screen_protector/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// swift-tools-version: 5.9
import PackageDescription

let package = Package(
name: "screen_protector",
platforms: [
.iOS(.v15)
],
products: [
.library(name: "screen-protector", targets: ["screen_protector"])
],
dependencies: [
.package(url: "https://github.com/prongbang/ScreenProtectorKit.git", exact: "1.5.1"),
],
targets: [
.target(
name: "screen_protector",
dependencies: [
.product(name: "ScreenProtectorKit", package: "ScreenProtectorKit")
]
)
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//
// FlutterRootViewResolver.swift
//
//
// Created by INTENIQUETIC on 18/1/2569 BE.
//

import Flutter
import UIKit
import ScreenProtectorKit

final class FlutterRootViewResolver: ScreenProtectorRootViewResolving {
func resolveRootView() -> UIView? {
guard Thread.isMainThread else {
log("resolveFlutterRootView: called off main thread")
return nil
}

guard let windowScene = UIApplication.shared.connectedScenes
.compactMap({ $0 as? UIWindowScene })
.first(where: { $0.activationState == .foregroundActive }) else {
log("resolveFlutterRootView: no foreground active UIWindowScene")
return nil
}

guard let flutterVC = windowScene.windows
.first(where: { $0.isKeyWindow })?
.rootViewController as? FlutterViewController else {
log("resolveFlutterRootView: FlutterViewController not found on key window")
return nil
}

return flutterVC.view
}

private func log(_ message: String) {
//print("[FlutterRootViewResolver]: \(message)")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
import Flutter
import UIKit
import ScreenProtectorKit

enum ScrennProtectorMethod: String {
case protectDataLeakageWithBlur
case protectDataLeakageWithBlurOff
case protectDataLeakageWithImage
case protectDataLeakageWithImageOff
case protectDataLeakageWithColor
case protectDataLeakageWithColorOff
case protectDataLeakageOff
case preventScreenshotOn
case preventScreenshotOff
case preventScreenRecordOn
case preventScreenRecordOff
case addListener
case removeListener
case isRecording
}

public class ScreenProtectorPlugin: NSObject, FlutterPlugin {
private static var channel: FlutterMethodChannel? = nil
private let protectKit: ScreenProtectorKit
private var protectMode: ScreenProtectorMode = .none
private var isPreventScreenshotEnabled = false

init(_ screenProtector: ScreenProtectorKit) {
self.protectKit = screenProtector
}

public static func register(with registrar: FlutterPluginRegistrar) {
ScreenProtectorPlugin.channel = FlutterMethodChannel(name: "screen_protector", binaryMessenger: registrar.messenger())

let kit = ScreenProtectorKit(window: ScreenProtectorPlugin.keyWindow())
kit.setRootViewResolver(FlutterRootViewResolver())
ScreenProtectorKit.initial(with: kit.window?.rootViewController?.view)
let instance = ScreenProtectorPlugin(kit)

registrar.addMethodCallDelegate(instance, channel: ScreenProtectorPlugin.channel!)
registrar.addApplicationDelegate(instance)
}

public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
if Thread.isMainThread {
handleFunc(call, result: result)
return
}
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.handleFunc(call, result: result)
}
}

public func applicationWillResignActive(_ application: UIApplication) {
updateWindowIfNeeded()
applyDataLeakageProtection()
}

public func applicationDidBecomeActive(_ application: UIApplication) {
updateWindowIfNeeded()
clearDataLeakageProtection()
}

deinit {
updateWindowIfNeeded()
protectKit.removeAllObserver()
protectKit.disablePreventScreenshot()
protectKit.disablePreventScreenRecording()
clearDataLeakageProtection()
}
}

private extension ScreenProtectorPlugin {
func handleFunc(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let args = call.arguments as? Dictionary<String, String>

switch ScrennProtectorMethod(rawValue: call.method) {
case .protectDataLeakageWithBlur:
setDataLeakageProtectMode(.blur)
result(true)
break
case .protectDataLeakageWithBlurOff:
if case .blur = protectMode {
protectMode = .none
}
protectKit.disableBlurScreen()
result(true)
break
case .protectDataLeakageWithImage:
setDataLeakageProtectMode(.image(name: args?["name"] ?? "LaunchImage"))
result(true)
break
case .protectDataLeakageWithImageOff:
if case .image = protectMode {
protectMode = .none
}
protectKit.disableImageScreen()
result(true)
break
case .protectDataLeakageWithColor:
guard let hexColor = args?["hexColor"] else {
result(false)
return
}
setDataLeakageProtectMode(.color(hex: hexColor))
result(true)
break
case .protectDataLeakageWithColorOff:
if case .color = protectMode {
protectMode = .none
}
protectKit.disableColorScreen()
result(true)
break
case .protectDataLeakageOff:
protectMode = .none
clearDataLeakageProtection()
result(true)
break
case .preventScreenshotOn:
isPreventScreenshotEnabled = true
updateWindowIfNeeded()
protectKit.enabledPreventScreenshot()
result(true)
break
case .preventScreenshotOff:
isPreventScreenshotEnabled = false
updateWindowIfNeeded()
protectKit.disablePreventScreenshot()
result(true)
break
case .preventScreenRecordOn:
updateWindowIfNeeded()
protectKit.enabledPreventScreenRecording()
result(true)
break
case .preventScreenRecordOff:
updateWindowIfNeeded()
protectKit.disablePreventScreenRecording()
result(true)
break
case .addListener:
protectKit.screenshotObserver { [weak channel = ScreenProtectorPlugin.channel] in
channel?.invokeMethod("onScreenshot", arguments: nil)
}
if #available(iOS 11.0, *) {
protectKit.screenRecordObserver { [weak channel = ScreenProtectorPlugin.channel] isCaptured in
channel?.invokeMethod("onScreenRecord", arguments: isCaptured)
}
}
result("listened")
break
case .removeListener:
protectKit.removeAllObserver()
result("removed")
break
case .isRecording:
if #available(iOS 11.0, *) {
result(protectKit.screenIsRecording())
} else {
result(false)
}
break
default:
result(false)
break
}
}

func updateWindowIfNeeded() {
if let window = Self.keyWindow() {
protectKit.window = window
}
}

func applyDataLeakageProtection() {
updateWindowIfNeeded()
clearDataLeakageProtection()
switch protectMode {
case .blur:
protectKit.enabledBlurScreen()
case let .image(name):
protectKit.enabledImageScreen(named: name)
case let .color(hex):
protectKit.enabledColorScreen(hexColor: hex)
case .none:
break
}
}

func clearDataLeakageProtection() {
protectKit.disableBlurScreen()
protectKit.disableImageScreen()
protectKit.disableColorScreen()
}

func setDataLeakageProtectMode(_ mode: ScreenProtectorMode) {
protectMode = mode
if UIApplication.shared.applicationState != .active {
applyDataLeakageProtection()
} else {
clearDataLeakageProtection()
}
}

static func keyWindow() -> UIWindow? {
if #available(iOS 13.0, *) {
return UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
.flatMap { $0.windows }
.first { $0.isKeyWindow }
}
return UIApplication.shared.keyWindow
}
}
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: screen_protector
description: Safe Data Leakage via Application Background Screenshot and Prevent Screenshot for Android and iOS.
version: 1.5.1
version: 1.5.2
homepage: https://github.com/prongbang/screen_protector

environment:
Expand Down
Loading