rnww-plugin-screen-pinning 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +57 -0
  2. package/lib/bridge/index.d.ts +9 -0
  3. package/lib/bridge/index.d.ts.map +1 -0
  4. package/lib/bridge/index.js +27 -0
  5. package/lib/bridge/screen-pinning-bridge.d.ts +36 -0
  6. package/lib/bridge/screen-pinning-bridge.d.ts.map +1 -0
  7. package/lib/bridge/screen-pinning-bridge.js +61 -0
  8. package/lib/types/bridge.d.ts +22 -0
  9. package/lib/types/bridge.d.ts.map +1 -0
  10. package/lib/types/bridge.js +5 -0
  11. package/lib/types/index.d.ts +7 -0
  12. package/lib/types/index.d.ts.map +1 -0
  13. package/lib/types/index.js +22 -0
  14. package/lib/types/platform.d.ts +10 -0
  15. package/lib/types/platform.d.ts.map +1 -0
  16. package/lib/types/platform.js +2 -0
  17. package/lib/types/screen-pinning-module.d.ts +36 -0
  18. package/lib/types/screen-pinning-module.d.ts.map +1 -0
  19. package/lib/types/screen-pinning-module.js +5 -0
  20. package/package.json +40 -0
  21. package/src/modules/android/.gradle/8.9/checksums/checksums.lock +0 -0
  22. package/src/modules/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
  23. package/src/modules/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
  24. package/src/modules/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
  25. package/src/modules/android/.gradle/8.9/gc.properties +0 -0
  26. package/src/modules/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  27. package/src/modules/android/.gradle/buildOutputCleanup/cache.properties +2 -0
  28. package/src/modules/android/.gradle/vcs-1/gc.properties +0 -0
  29. package/src/modules/android/build.gradle +64 -0
  30. package/src/modules/android/src/main/AndroidManifest.xml +10 -0
  31. package/src/modules/android/src/main/java/expo/modules/screenpinning/ScreenPinningModule.kt +103 -0
  32. package/src/modules/android/src/main/res/xml/file_provider_paths.xml +5 -0
  33. package/src/modules/expo-module.config.json +6 -0
  34. package/src/modules/index.ts +60 -0
package/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # RNWW Plugin Screen Pinning
2
+
3
+ React Native와 WebView 간 화면 고정(Screen Pinning) 기능을 연결하는 플러그인입니다. (Android 전용)
4
+
5
+ ## 설치
6
+
7
+ ```bash
8
+ npm install rnww-plugin-screen-pinning
9
+ # or
10
+ yarn add rnww-plugin-screen-pinning
11
+ ```
12
+
13
+ ## 기능
14
+
15
+ - 앱 고정(Screen Pinning / Lock Task Mode) 시작 및 해제
16
+ - 앱 고정 상태 확인
17
+ - Android 전용 (API Level 21+)
18
+
19
+ ### 브릿지 핸들러
20
+
21
+ 다음 이벤트 핸들러가 등록됩니다:
22
+
23
+ - `getScreenPinning` - 앱 고정 상태 확인
24
+ - `startScreenPinning` - 앱 고정 시작
25
+ - `stopScreenPinning` - 앱 고정 해제
26
+
27
+ ### 사용법
28
+
29
+ #### 앱 고정 시작
30
+ ```javascript
31
+ bridge.send('startScreenPinning', {}, (response) => {
32
+ if (response.success) {
33
+ console.log('Screen pinning started');
34
+ }
35
+ });
36
+ ```
37
+
38
+ #### 앱 고정 해제
39
+ ```javascript
40
+ bridge.send('stopScreenPinning', {}, (response) => {
41
+ if (response.success) {
42
+ console.log('Screen pinning stopped');
43
+ }
44
+ });
45
+ ```
46
+
47
+ #### 앱 고정 상태 확인
48
+ ```javascript
49
+ bridge.send('getScreenPinning', {}, (response) => {
50
+ console.log('Is pinned:', response.isPinned);
51
+ console.log('Lock task mode state:', response.lockTaskModeState);
52
+ });
53
+ ```
54
+
55
+ ## 라이선스
56
+
57
+ MIT
@@ -0,0 +1,9 @@
1
+ /**
2
+ * 화면 고정 브릿지 메인 엔트리 포인트
3
+ *
4
+ * NPM 패키지로 사용 시 이 파일을 import하세요.
5
+ */
6
+ export { registerScreenPinningHandlers } from './screen-pinning-bridge';
7
+ export type { ScreenPinningBridgeConfig } from './screen-pinning-bridge';
8
+ export * from '../types';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bridge/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,6BAA6B,EAAE,MAAM,yBAAyB,CAAC;AACxE,YAAY,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AAGzE,cAAc,UAAU,CAAC"}
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ /**
3
+ * 화면 고정 브릿지 메인 엔트리 포인트
4
+ *
5
+ * NPM 패키지로 사용 시 이 파일을 import하세요.
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
19
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
20
+ };
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.registerScreenPinningHandlers = void 0;
23
+ // 메인 export (의존성 주입 방식)
24
+ var screen_pinning_bridge_1 = require("./screen-pinning-bridge");
25
+ Object.defineProperty(exports, "registerScreenPinningHandlers", { enumerable: true, get: function () { return screen_pinning_bridge_1.registerScreenPinningHandlers; } });
26
+ // 타입 정의
27
+ __exportStar(require("../types"), exports);
@@ -0,0 +1,36 @@
1
+ /**
2
+ * 앱 고정(Screen Pinning) 관련 핸들러 - Android 전용
3
+ */
4
+ import type { IBridge, IPlatform, IScreenPinningModule } from '../types';
5
+ /**
6
+ * 화면 고정 브릿지 설정
7
+ */
8
+ export interface ScreenPinningBridgeConfig {
9
+ /**
10
+ * 브릿지 구현체
11
+ */
12
+ bridge: IBridge;
13
+ /**
14
+ * 플랫폼 구현체
15
+ */
16
+ platform: IPlatform;
17
+ /**
18
+ * 화면 고정 모듈 인스턴스
19
+ * 이 패키지의 src/module/index.ts를 직접 전달
20
+ */
21
+ screenPinningModule: IScreenPinningModule;
22
+ /**
23
+ * 로거 (선택적)
24
+ */
25
+ logger?: {
26
+ log: (...args: any[]) => void;
27
+ warn: (...args: any[]) => void;
28
+ error: (...args: any[]) => void;
29
+ };
30
+ }
31
+ /**
32
+ * 화면 고정 브릿지 핸들러를 등록합니다
33
+ * @param config 브릿지 설정
34
+ */
35
+ export declare const registerScreenPinningHandlers: (config: ScreenPinningBridgeConfig) => void;
36
+ //# sourceMappingURL=screen-pinning-bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screen-pinning-bridge.d.ts","sourceRoot":"","sources":["../../src/bridge/screen-pinning-bridge.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACR,OAAO,EACP,SAAS,EACT,oBAAoB,EACvB,MAAM,UAAU,CAAC;AAElB;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACtC;;OAEG;IACH,MAAM,EAAE,OAAO,CAAC;IAEhB;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IAEpB;;;OAGG;IACH,mBAAmB,EAAE,oBAAoB,CAAC;IAE1C;;OAEG;IACH,MAAM,CAAC,EAAE;QACL,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;QAC9B,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;QAC/B,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;KACnC,CAAC;CACL;AAED;;;GAGG;AACH,eAAO,MAAM,6BAA6B,GAAI,QAAQ,yBAAyB,SAmD9E,CAAC"}
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ /**
3
+ * 앱 고정(Screen Pinning) 관련 핸들러 - Android 전용
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.registerScreenPinningHandlers = void 0;
7
+ /**
8
+ * 화면 고정 브릿지 핸들러를 등록합니다
9
+ * @param config 브릿지 설정
10
+ */
11
+ const registerScreenPinningHandlers = (config) => {
12
+ const { bridge, platform, screenPinningModule: ScreenPinning, logger = console } = config;
13
+ // Android만 지원
14
+ if (platform.OS !== 'android') {
15
+ logger.log('[Bridge] ScreenPinning handlers skipped (Android only)');
16
+ return;
17
+ }
18
+ // 앱 고정 상태 확인
19
+ bridge.registerHandler('getScreenPinning', async (_payload, respond) => {
20
+ try {
21
+ const status = await ScreenPinning.isScreenPinned();
22
+ respond({ success: true, ...status });
23
+ }
24
+ catch (error) {
25
+ respond({
26
+ success: false,
27
+ isPinned: false,
28
+ lockTaskModeState: 0,
29
+ error: error instanceof Error ? error.message : 'Failed to get screen pinning status'
30
+ });
31
+ }
32
+ });
33
+ // 앱 고정 시작
34
+ bridge.registerHandler('startScreenPinning', async (_payload, respond) => {
35
+ try {
36
+ const result = await ScreenPinning.startScreenPinning();
37
+ respond(result);
38
+ }
39
+ catch (error) {
40
+ respond({
41
+ success: false,
42
+ error: error instanceof Error ? error.message : 'Failed to start screen pinning'
43
+ });
44
+ }
45
+ });
46
+ // 앱 고정 해제
47
+ bridge.registerHandler('stopScreenPinning', async (_payload, respond) => {
48
+ try {
49
+ const result = await ScreenPinning.stopScreenPinning();
50
+ respond(result);
51
+ }
52
+ catch (error) {
53
+ respond({
54
+ success: false,
55
+ error: error instanceof Error ? error.message : 'Failed to stop screen pinning'
56
+ });
57
+ }
58
+ });
59
+ logger.log('[Bridge] ScreenPinning handlers registered');
60
+ };
61
+ exports.registerScreenPinningHandlers = registerScreenPinningHandlers;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * 브릿지 통신 추상화 인터페이스
3
+ */
4
+ /**
5
+ * 브릿지 통신 인터페이스
6
+ * 기존 프로젝트와의 호환성을 위해 타입을 느슨하게 설정
7
+ */
8
+ export interface IBridge {
9
+ /**
10
+ * 네이티브에서 호출할 수 있는 핸들러를 등록합니다
11
+ * @param eventName 이벤트 이름
12
+ * @param handler 핸들러 함수
13
+ */
14
+ registerHandler(eventName: string, handler: any): void;
15
+ /**
16
+ * 웹으로 메시지를 전송합니다
17
+ * @param eventName 이벤트 이름
18
+ * @param data 전송할 데이터
19
+ */
20
+ sendToWeb(eventName: string, data: any): void;
21
+ }
22
+ //# sourceMappingURL=bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../../src/types/bridge.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;GAGG;AACH,MAAM,WAAW,OAAO;IACtB;;;;OAIG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,GAAG,IAAI,CAAC;IAEvD;;;;OAIG;IACH,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,IAAI,CAAC;CAC/C"}
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ /**
3
+ * 브릿지 통신 추상화 인터페이스
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 타입 정의 모음
3
+ */
4
+ export * from './platform';
5
+ export * from './bridge';
6
+ export * from './screen-pinning-module';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,yBAAyB,CAAC"}
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ /**
3
+ * 타입 정의 모음
4
+ */
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
17
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
18
+ };
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ __exportStar(require("./platform"), exports);
21
+ __exportStar(require("./bridge"), exports);
22
+ __exportStar(require("./screen-pinning-module"), exports);
@@ -0,0 +1,10 @@
1
+ /**
2
+ * 플랫폼 추상화 인터페이스
3
+ */
4
+ export interface IPlatform {
5
+ /**
6
+ * 현재 실행 중인 플랫폼
7
+ */
8
+ OS: 'ios' | 'android' | 'windows' | 'macos' | 'web';
9
+ }
10
+ //# sourceMappingURL=platform.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../../src/types/platform.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB;;OAEG;IACH,EAAE,EAAE,KAAK,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,KAAK,CAAC;CACrD"}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,36 @@
1
+ /**
2
+ * 화면 고정 모듈 인터페이스
3
+ */
4
+ /**
5
+ * 화면 고정 상태
6
+ */
7
+ export interface ScreenPinningStatus {
8
+ /** 앱 고정 활성화 여부 */
9
+ isPinned: boolean;
10
+ /** Lock Task 모드 상태 (0: none, 1: pinned, 2: locked) */
11
+ lockTaskModeState: number;
12
+ }
13
+ /**
14
+ * 화면 고정 모듈 인터페이스
15
+ */
16
+ export interface IScreenPinningModule {
17
+ /**
18
+ * 앱 고정 상태 확인
19
+ */
20
+ isScreenPinned(): Promise<ScreenPinningStatus>;
21
+ /**
22
+ * 앱 고정 시작
23
+ */
24
+ startScreenPinning(): Promise<{
25
+ success: boolean;
26
+ error?: string;
27
+ }>;
28
+ /**
29
+ * 앱 고정 해제
30
+ */
31
+ stopScreenPinning(): Promise<{
32
+ success: boolean;
33
+ error?: string;
34
+ }>;
35
+ }
36
+ //# sourceMappingURL=screen-pinning-module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screen-pinning-module.d.ts","sourceRoot":"","sources":["../../src/types/screen-pinning-module.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,kBAAkB;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,sDAAsD;IACtD,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,cAAc,IAAI,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAE/C;;OAEG;IACH,kBAAkB,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAEpE;;OAEG;IACH,iBAAiB,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpE"}
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ /**
3
+ * 화면 고정 모듈 인터페이스
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "rnww-plugin-screen-pinning",
3
+ "version": "1.0.0",
4
+ "description": "React Native WebView Screen Pinning Plugin with Expo support (Android Only)",
5
+ "main": "lib/bridge/index.js",
6
+ "types": "lib/bridge/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "prepare": "npm run build",
10
+ "clean": "rimraf lib"
11
+ },
12
+ "keywords": [
13
+ "react-native",
14
+ "expo",
15
+ "screen-pinning",
16
+ "lock-task",
17
+ "kiosk-mode",
18
+ "android",
19
+ "webview",
20
+ "bridge",
21
+ "expo-module"
22
+ ],
23
+ "author": "",
24
+ "license": "MIT",
25
+ "files": [
26
+ "lib/",
27
+ "src/modules/",
28
+ "README.md"
29
+ ],
30
+ "peerDependencies": {
31
+ "expo": "*",
32
+ "expo-modules-core": "*"
33
+ },
34
+ "devDependencies": {
35
+ "@types/react": "^19.0.0",
36
+ "@types/react-native": "^0.73.0",
37
+ "rimraf": "^5.0.10",
38
+ "typescript": "^5.9.3"
39
+ }
40
+ }
File without changes
@@ -0,0 +1,2 @@
1
+ #Mon Dec 29 03:54:07 JST 2025
2
+ gradle.version=8.9
@@ -0,0 +1,64 @@
1
+ apply plugin: 'com.android.library'
2
+ apply plugin: 'kotlin-android'
3
+ apply plugin: 'maven-publish'
4
+
5
+ group = 'expo.modules.screenpinning'
6
+ version = '1.0.0'
7
+
8
+ buildscript {
9
+ def expoModulesCoreProject = rootProject.findProject(":expo-modules-core")
10
+ if (expoModulesCoreProject != null) {
11
+ def expoModulesCorePlugin = new File(expoModulesCoreProject.projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
12
+ if (expoModulesCorePlugin.exists()) {
13
+ apply from: expoModulesCorePlugin
14
+ applyKotlinExpoModulesCorePlugin()
15
+ }
16
+ }
17
+ }
18
+
19
+ afterEvaluate {
20
+ publishing {
21
+ publications {
22
+ release(MavenPublication) {
23
+ from components.release
24
+ }
25
+ }
26
+ repositories {
27
+ maven {
28
+ url = mavenLocal().url
29
+ }
30
+ }
31
+ }
32
+ }
33
+
34
+ android {
35
+ namespace "expo.modules.screenpinning"
36
+ compileSdkVersion safeExtGet("compileSdkVersion", 34)
37
+
38
+ defaultConfig {
39
+ minSdkVersion safeExtGet("minSdkVersion", 23)
40
+ targetSdkVersion safeExtGet("targetSdkVersion", 34)
41
+ }
42
+
43
+ compileOptions {
44
+ sourceCompatibility JavaVersion.VERSION_17
45
+ targetCompatibility JavaVersion.VERSION_17
46
+ }
47
+
48
+ kotlinOptions {
49
+ jvmTarget = JavaVersion.VERSION_17
50
+ }
51
+ }
52
+
53
+ repositories {
54
+ mavenCentral()
55
+ }
56
+
57
+ dependencies {
58
+ implementation project(':expo-modules-core')
59
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
60
+ }
61
+
62
+ def safeExtGet(prop, fallback) {
63
+ rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
64
+ }
@@ -0,0 +1,10 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ <!-- 카메라 권한 -->
3
+ <uses-permission android:name="android.permission.CAMERA"/>
4
+ <!-- 비디오 녹화 시 오디오 권한 -->
5
+ <uses-permission android:name="android.permission.RECORD_AUDIO"/>
6
+
7
+ <!-- 카메라 하드웨어 기능 (선택사항) -->
8
+ <uses-feature android:name="android.hardware.camera" android:required="false"/>
9
+ <uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
10
+ </manifest>
@@ -0,0 +1,103 @@
1
+ package expo.modules.screenpinning
2
+
3
+ import android.app.Activity
4
+ import android.app.ActivityManager
5
+ import android.content.Context
6
+ import android.os.Build
7
+ import expo.modules.kotlin.modules.Module
8
+ import expo.modules.kotlin.modules.ModuleDefinition
9
+ import expo.modules.kotlin.Promise
10
+
11
+ class ScreenPinningModule : Module() {
12
+ override fun definition() = ModuleDefinition {
13
+ Name("ScreenPinning")
14
+
15
+ // 앱 고정 상태 확인
16
+ AsyncFunction("isScreenPinned") { promise: Promise ->
17
+ try {
18
+ val activity = appContext.currentActivity
19
+ if (activity == null) {
20
+ promise.resolve(mapOf(
21
+ "isPinned" to false,
22
+ "lockTaskModeState" to 0
23
+ ))
24
+ return@AsyncFunction
25
+ }
26
+
27
+ val activityManager = activity.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
28
+ val lockTaskModeState = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
29
+ activityManager.lockTaskModeState
30
+ } else {
31
+ @Suppress("DEPRECATION")
32
+ if (activityManager.isInLockTaskMode) 1 else 0
33
+ }
34
+
35
+ // LOCK_TASK_MODE_NONE = 0, LOCK_TASK_MODE_PINNED = 1, LOCK_TASK_MODE_LOCKED = 2
36
+ promise.resolve(mapOf(
37
+ "isPinned" to (lockTaskModeState != ActivityManager.LOCK_TASK_MODE_NONE),
38
+ "lockTaskModeState" to lockTaskModeState
39
+ ))
40
+ } catch (e: Exception) {
41
+ promise.resolve(mapOf(
42
+ "isPinned" to false,
43
+ "lockTaskModeState" to 0,
44
+ "error" to e.message
45
+ ))
46
+ }
47
+ }
48
+
49
+ // 앱 고정 시작
50
+ AsyncFunction("startScreenPinning") { promise: Promise ->
51
+ try {
52
+ val activity = appContext.currentActivity
53
+ if (activity == null) {
54
+ promise.resolve(mapOf(
55
+ "success" to false,
56
+ "error" to "Activity not available"
57
+ ))
58
+ return@AsyncFunction
59
+ }
60
+
61
+ // startLockTask()는 사용자에게 확인 다이얼로그를 표시
62
+ // Device Owner 앱인 경우 다이얼로그 없이 바로 고정됨
63
+ activity.startLockTask()
64
+
65
+ promise.resolve(mapOf(
66
+ "success" to true
67
+ ))
68
+ } catch (e: Exception) {
69
+ promise.resolve(mapOf(
70
+ "success" to false,
71
+ "error" to e.message
72
+ ))
73
+ }
74
+ }
75
+
76
+ // 앱 고정 해제
77
+ AsyncFunction("stopScreenPinning") { promise: Promise ->
78
+ try {
79
+ val activity = appContext.currentActivity
80
+ if (activity == null) {
81
+ promise.resolve(mapOf(
82
+ "success" to false,
83
+ "error" to "Activity not available"
84
+ ))
85
+ return@AsyncFunction
86
+ }
87
+
88
+ // stopLockTask()는 Device Owner 앱에서만 프로그래밍 방식으로 해제 가능
89
+ // 일반 앱은 뒤로가기 + 최근 앱 버튼 동시 길게 누르기로 해제
90
+ activity.stopLockTask()
91
+
92
+ promise.resolve(mapOf(
93
+ "success" to true
94
+ ))
95
+ } catch (e: Exception) {
96
+ promise.resolve(mapOf(
97
+ "success" to false,
98
+ "error" to e.message
99
+ ))
100
+ }
101
+ }
102
+ }
103
+ }
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <paths xmlns:android="http://schemas.android.com/apk/res/android">
3
+ <!-- 크래시 로그 파일 공유를 위한 경로 -->
4
+ <external-files-path name="crash_logs" path="." />
5
+ </paths>
@@ -0,0 +1,6 @@
1
+ {
2
+ "platforms": ["android"],
3
+ "android": {
4
+ "modules": ["expo.modules.screenpinning.ScreenPinningModule"]
5
+ }
6
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Screen Pinning Module (Android Only)
3
+ * Android 앱 고정 (Screen Pinning / Lock Task Mode) 기능
4
+ */
5
+
6
+ import { requireNativeModule } from 'expo-modules-core';
7
+ import { Platform } from 'react-native';
8
+
9
+ // Android에서만 네이티브 모듈 로드
10
+ const ScreenPinningModule = Platform.OS === 'android'
11
+ ? requireNativeModule('ScreenPinning')
12
+ : null;
13
+
14
+ export interface ScreenPinningStatus {
15
+ /** 앱 고정 활성화 여부 */
16
+ isPinned: boolean;
17
+ /** Lock Task 모드 상태 (0: none, 1: pinned, 2: locked) */
18
+ lockTaskModeState: number;
19
+ }
20
+
21
+ /**
22
+ * 앱 고정 상태 확인
23
+ * @returns 앱 고정 상태 정보
24
+ */
25
+ export async function isScreenPinned(): Promise<ScreenPinningStatus> {
26
+ if (Platform.OS !== 'android' || !ScreenPinningModule) {
27
+ return { isPinned: false, lockTaskModeState: 0 };
28
+ }
29
+ return await ScreenPinningModule.isScreenPinned();
30
+ }
31
+
32
+ /**
33
+ * 앱 고정 시작
34
+ * 사용자에게 확인 다이얼로그가 표시됩니다.
35
+ * Device Owner 앱인 경우 다이얼로그 없이 바로 고정됩니다.
36
+ */
37
+ export async function startScreenPinning(): Promise<{ success: boolean; error?: string }> {
38
+ if (Platform.OS !== 'android' || !ScreenPinningModule) {
39
+ return { success: false, error: 'Only supported on Android' };
40
+ }
41
+ return await ScreenPinningModule.startScreenPinning();
42
+ }
43
+
44
+ /**
45
+ * 앱 고정 해제
46
+ * 일반 앱: 뒤로가기 + 최근 앱 버튼 동시 길게 누르기로 해제
47
+ * Device Owner 앱: 프로그래밍 방식으로 해제 가능
48
+ */
49
+ export async function stopScreenPinning(): Promise<{ success: boolean; error?: string }> {
50
+ if (Platform.OS !== 'android' || !ScreenPinningModule) {
51
+ return { success: false, error: 'Only supported on Android' };
52
+ }
53
+ return await ScreenPinningModule.stopScreenPinning();
54
+ }
55
+
56
+ export default {
57
+ isScreenPinned,
58
+ startScreenPinning,
59
+ stopScreenPinning,
60
+ };