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.
- package/README.md +57 -0
- package/lib/bridge/index.d.ts +9 -0
- package/lib/bridge/index.d.ts.map +1 -0
- package/lib/bridge/index.js +27 -0
- package/lib/bridge/screen-pinning-bridge.d.ts +36 -0
- package/lib/bridge/screen-pinning-bridge.d.ts.map +1 -0
- package/lib/bridge/screen-pinning-bridge.js +61 -0
- package/lib/types/bridge.d.ts +22 -0
- package/lib/types/bridge.d.ts.map +1 -0
- package/lib/types/bridge.js +5 -0
- package/lib/types/index.d.ts +7 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index.js +22 -0
- package/lib/types/platform.d.ts +10 -0
- package/lib/types/platform.d.ts.map +1 -0
- package/lib/types/platform.js +2 -0
- package/lib/types/screen-pinning-module.d.ts +36 -0
- package/lib/types/screen-pinning-module.d.ts.map +1 -0
- package/lib/types/screen-pinning-module.js +5 -0
- package/package.json +40 -0
- package/src/modules/android/.gradle/8.9/checksums/checksums.lock +0 -0
- package/src/modules/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
- package/src/modules/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
- package/src/modules/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
- package/src/modules/android/.gradle/8.9/gc.properties +0 -0
- package/src/modules/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/src/modules/android/.gradle/buildOutputCleanup/cache.properties +2 -0
- package/src/modules/android/.gradle/vcs-1/gc.properties +0 -0
- package/src/modules/android/build.gradle +64 -0
- package/src/modules/android/src/main/AndroidManifest.xml +10 -0
- package/src/modules/android/src/main/java/expo/modules/screenpinning/ScreenPinningModule.kt +103 -0
- package/src/modules/android/src/main/res/xml/file_provider_paths.xml +5 -0
- package/src/modules/expo-module.config.json +6 -0
- 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 @@
|
|
|
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 @@
|
|
|
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,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"}
|
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
|
+
}
|
|
Binary file
|
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
@@ -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,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
|
+
};
|