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 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,6 @@
1
+ package com.proofcite.overlay
2
+
3
+ object ProofCiteOverlayState {
4
+ @Volatile var visible: Boolean = false
5
+ @Volatile var listener: (() -> Unit)? = null
6
+ }
@@ -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,3 @@
1
+ import { registerPlugin } from '@capacitor/core';
2
+
3
+ export const ProofCiteOverlay = registerPlugin('ProofCiteOverlay');
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1,7 @@
1
+ 'use strict';
2
+
3
+ const core = require('@capacitor/core');
4
+
5
+ const ProofCiteOverlay = core.registerPlugin('ProofCiteOverlay');
6
+
7
+ exports.ProofCiteOverlay = ProofCiteOverlay;
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
+ }