proofcite-capacitor-overlay 0.1.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 +27 -0
- package/android/build.gradle +31 -0
- package/android/src/main/AndroidManifest.xml +10 -0
- package/android/src/main/java/com/proofcite/overlay/ProofCiteOverlayPlugin.kt +69 -0
- package/android/src/main/java/com/proofcite/overlay/ProofCiteOverlayService.kt +95 -0
- package/android/src/main/java/com/proofcite/overlay/ProofCiteOverlayState.kt +6 -0
- package/dist/esm/index.d.ts +22 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/package.json +1 -0
- package/dist/plugin.cjs.js +7 -0
- package/package.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# proofcite-capacitor-overlay
|
|
2
|
+
|
|
3
|
+
Native Android Capacitor plugin for ProofCite's floating CHECK bubble.
|
|
4
|
+
|
|
5
|
+
## API
|
|
6
|
+
|
|
7
|
+
- `checkPermission()`
|
|
8
|
+
- `requestPermission()`
|
|
9
|
+
- `startOverlay()`
|
|
10
|
+
- `stopOverlay()`
|
|
11
|
+
- `getStatus()`
|
|
12
|
+
- event `checkPressed`
|
|
13
|
+
|
|
14
|
+
## Intended ProofCite flow
|
|
15
|
+
|
|
16
|
+
1. ProofCite opens visibly.
|
|
17
|
+
2. User starts Prüfmodus.
|
|
18
|
+
3. App requests `SYSTEM_ALERT_WINDOW` if needed.
|
|
19
|
+
4. App starts the overlay service.
|
|
20
|
+
5. User switches to Instagram/TikTok/etc.
|
|
21
|
+
6. Bubble stays visible and can be dragged.
|
|
22
|
+
7. Tap emits `checkPressed` through Capacitor.
|
|
23
|
+
8. ProofCite's JS handler triggers the already-existing screenshot/MediaProjection path and sends the image to the fact-check endpoint.
|
|
24
|
+
|
|
25
|
+
## Important
|
|
26
|
+
|
|
27
|
+
This package only draws the overlay and emits the click event. It deliberately does not own MediaProjection consent or the fact-check backend; those stay in the main ProofCite app.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
ext {
|
|
2
|
+
junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
buildscript {
|
|
6
|
+
repositories { google(); mavenCentral() }
|
|
7
|
+
dependencies {
|
|
8
|
+
classpath 'com.android.tools.build:gradle:8.7.2'
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
apply plugin: 'com.android.library'
|
|
13
|
+
|
|
14
|
+
android {
|
|
15
|
+
namespace "com.proofcite.overlay"
|
|
16
|
+
compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35
|
|
17
|
+
|
|
18
|
+
defaultConfig {
|
|
19
|
+
minSdk project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23
|
|
20
|
+
targetSdk project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35
|
|
21
|
+
versionCode 1
|
|
22
|
+
versionName "0.1.0"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
repositories { google(); mavenCentral() }
|
|
27
|
+
|
|
28
|
+
dependencies {
|
|
29
|
+
implementation project(':capacitor-android')
|
|
30
|
+
testImplementation "junit:junit:$junitVersion"
|
|
31
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
2
|
+
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
|
3
|
+
|
|
4
|
+
<application>
|
|
5
|
+
<service
|
|
6
|
+
android:name=".ProofCiteOverlayService"
|
|
7
|
+
android:exported="false"
|
|
8
|
+
android:stopWithTask="false" />
|
|
9
|
+
</application>
|
|
10
|
+
</manifest>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
package com.proofcite.overlay
|
|
2
|
+
|
|
3
|
+
import android.content.Intent
|
|
4
|
+
import android.net.Uri
|
|
5
|
+
import android.provider.Settings
|
|
6
|
+
import com.getcapacitor.JSObject
|
|
7
|
+
import com.getcapacitor.Plugin
|
|
8
|
+
import com.getcapacitor.PluginCall
|
|
9
|
+
import com.getcapacitor.PluginMethod
|
|
10
|
+
import com.getcapacitor.annotation.CapacitorPlugin
|
|
11
|
+
|
|
12
|
+
@CapacitorPlugin(name = "ProofCiteOverlay")
|
|
13
|
+
class ProofCiteOverlayPlugin : Plugin() {
|
|
14
|
+
|
|
15
|
+
override fun load() {
|
|
16
|
+
ProofCiteOverlayState.listener = {
|
|
17
|
+
notifyListeners("checkPressed", JSObject())
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
override fun handleOnDestroy() {
|
|
22
|
+
ProofCiteOverlayState.listener = null
|
|
23
|
+
super.handleOnDestroy()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@PluginMethod
|
|
27
|
+
fun checkPermission(call: PluginCall) {
|
|
28
|
+
val result = JSObject()
|
|
29
|
+
result.put("granted", Settings.canDrawOverlays(context))
|
|
30
|
+
call.resolve(result)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@PluginMethod
|
|
34
|
+
fun requestPermission(call: PluginCall) {
|
|
35
|
+
if (Settings.canDrawOverlays(context)) {
|
|
36
|
+
val result = JSObject(); result.put("granted", true); call.resolve(result); return
|
|
37
|
+
}
|
|
38
|
+
val intent = Intent(
|
|
39
|
+
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
|
|
40
|
+
Uri.parse("package:${context.packageName}")
|
|
41
|
+
).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) }
|
|
42
|
+
context.startActivity(intent)
|
|
43
|
+
val result = JSObject(); result.put("granted", false); call.resolve(result)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@PluginMethod
|
|
47
|
+
fun startOverlay(call: PluginCall) {
|
|
48
|
+
if (!Settings.canDrawOverlays(context)) {
|
|
49
|
+
call.reject("Overlay permission not granted")
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
context.startService(Intent(context, ProofCiteOverlayService::class.java))
|
|
53
|
+
call.resolve()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@PluginMethod
|
|
57
|
+
fun stopOverlay(call: PluginCall) {
|
|
58
|
+
context.stopService(Intent(context, ProofCiteOverlayService::class.java))
|
|
59
|
+
call.resolve()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@PluginMethod
|
|
63
|
+
fun getStatus(call: PluginCall) {
|
|
64
|
+
val result = JSObject()
|
|
65
|
+
result.put("visible", ProofCiteOverlayState.visible)
|
|
66
|
+
result.put("permissionGranted", Settings.canDrawOverlays(context))
|
|
67
|
+
call.resolve(result)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
package com.proofcite.overlay
|
|
2
|
+
|
|
3
|
+
import android.app.Service
|
|
4
|
+
import android.content.Intent
|
|
5
|
+
import android.graphics.Color
|
|
6
|
+
import android.graphics.PixelFormat
|
|
7
|
+
import android.graphics.Typeface
|
|
8
|
+
import android.os.IBinder
|
|
9
|
+
import android.view.Gravity
|
|
10
|
+
import android.view.MotionEvent
|
|
11
|
+
import android.view.View
|
|
12
|
+
import android.view.WindowManager
|
|
13
|
+
import android.widget.TextView
|
|
14
|
+
import kotlin.math.abs
|
|
15
|
+
|
|
16
|
+
class ProofCiteOverlayService : Service() {
|
|
17
|
+
private lateinit var windowManager: WindowManager
|
|
18
|
+
private var bubble: TextView? = null
|
|
19
|
+
|
|
20
|
+
override fun onCreate() {
|
|
21
|
+
super.onCreate()
|
|
22
|
+
windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
|
|
23
|
+
showBubble()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
override fun onBind(intent: Intent?): IBinder? = null
|
|
27
|
+
|
|
28
|
+
override fun onDestroy() {
|
|
29
|
+
bubble?.let { runCatching { windowManager.removeView(it) } }
|
|
30
|
+
bubble = null
|
|
31
|
+
ProofCiteOverlayState.visible = false
|
|
32
|
+
super.onDestroy()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private fun showBubble() {
|
|
36
|
+
if (bubble != null) return
|
|
37
|
+
|
|
38
|
+
val view = TextView(this).apply {
|
|
39
|
+
text = "CHECK"
|
|
40
|
+
setTextColor(Color.WHITE)
|
|
41
|
+
textSize = 14f
|
|
42
|
+
typeface = Typeface.DEFAULT_BOLD
|
|
43
|
+
gravity = Gravity.CENTER
|
|
44
|
+
setPadding(34, 22, 34, 22)
|
|
45
|
+
setBackgroundColor(Color.rgb(0, 174, 184))
|
|
46
|
+
elevation = 18f
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
val params = WindowManager.LayoutParams(
|
|
50
|
+
WindowManager.LayoutParams.WRAP_CONTENT,
|
|
51
|
+
WindowManager.LayoutParams.WRAP_CONTENT,
|
|
52
|
+
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
|
|
53
|
+
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
|
|
54
|
+
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
|
|
55
|
+
PixelFormat.TRANSLUCENT
|
|
56
|
+
).apply {
|
|
57
|
+
gravity = Gravity.TOP or Gravity.END
|
|
58
|
+
x = 24
|
|
59
|
+
y = 320
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
var downX = 0f
|
|
63
|
+
var downY = 0f
|
|
64
|
+
var startX = 0
|
|
65
|
+
var startY = 0
|
|
66
|
+
|
|
67
|
+
view.setOnTouchListener { _: View, event: MotionEvent ->
|
|
68
|
+
when (event.action) {
|
|
69
|
+
MotionEvent.ACTION_DOWN -> {
|
|
70
|
+
downX = event.rawX
|
|
71
|
+
downY = event.rawY
|
|
72
|
+
startX = params.x
|
|
73
|
+
startY = params.y
|
|
74
|
+
true
|
|
75
|
+
}
|
|
76
|
+
MotionEvent.ACTION_MOVE -> {
|
|
77
|
+
params.x = startX - (event.rawX - downX).toInt()
|
|
78
|
+
params.y = startY + (event.rawY - downY).toInt()
|
|
79
|
+
windowManager.updateViewLayout(view, params)
|
|
80
|
+
true
|
|
81
|
+
}
|
|
82
|
+
MotionEvent.ACTION_UP -> {
|
|
83
|
+
val moved = abs(event.rawX - downX) + abs(event.rawY - downY)
|
|
84
|
+
if (moved < 18f) ProofCiteOverlayState.listener?.invoke()
|
|
85
|
+
true
|
|
86
|
+
}
|
|
87
|
+
else -> false
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
windowManager.addView(view, params)
|
|
92
|
+
bubble = view
|
|
93
|
+
ProofCiteOverlayState.visible = true
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { PluginListenerHandle } from '@capacitor/core';
|
|
2
|
+
|
|
3
|
+
export interface OverlayPermissionResult {
|
|
4
|
+
granted: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface OverlayStatusResult {
|
|
8
|
+
visible: boolean;
|
|
9
|
+
permissionGranted: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ProofCiteOverlayPlugin {
|
|
13
|
+
checkPermission(): Promise<OverlayPermissionResult>;
|
|
14
|
+
requestPermission(): Promise<OverlayPermissionResult>;
|
|
15
|
+
startOverlay(): Promise<void>;
|
|
16
|
+
stopOverlay(): Promise<void>;
|
|
17
|
+
getStatus(): Promise<OverlayStatusResult>;
|
|
18
|
+
addListener(eventName: 'checkPressed', listenerFunc: () => void): Promise<PluginListenerHandle>;
|
|
19
|
+
removeAllListeners(): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export declare const ProofCiteOverlay: ProofCiteOverlayPlugin;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"module"}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "proofcite-capacitor-overlay",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Native Android floating CHECK overlay Capacitor plugin for ProofCite",
|
|
5
|
+
"main": "dist/plugin.cjs.js",
|
|
6
|
+
"module": "dist/esm/index.js",
|
|
7
|
+
"types": "dist/esm/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"android/",
|
|
10
|
+
"dist/",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc -p tsconfig.json",
|
|
15
|
+
"verify:android": "cd android && ./gradlew assembleDebug"
|
|
16
|
+
},
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"@capacitor/core": ">=7.0.0"
|
|
19
|
+
},
|
|
20
|
+
"capacitor": {
|
|
21
|
+
"android": {
|
|
22
|
+
"src": "android"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"license": "UNLICENSED",
|
|
26
|
+
"keywords": [
|
|
27
|
+
"capacitor",
|
|
28
|
+
"android",
|
|
29
|
+
"overlay",
|
|
30
|
+
"floating-button",
|
|
31
|
+
"proofcite"
|
|
32
|
+
]
|
|
33
|
+
}
|