feat: swipe gesture in android

This commit is contained in:
NoCrypt 2024-08-07 20:05:28 +07:00
parent a2739ca114
commit 7ac6fd678e
6 changed files with 212 additions and 3 deletions

View file

@ -10,6 +10,7 @@ android {
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies {
implementation project(':capacitor-community-file-opener')
implementation project(':capacitor-community-screen-brightness')
implementation project(':capacitor-app')
implementation project(':capacitor-browser')
implementation project(':capacitor-filesystem')
@ -17,6 +18,7 @@ dependencies {
implementation project(':capacitor-status-bar')
implementation project(':capacitor-nodejs')
implementation project(':capacitor-plugin-safe-area')
implementation project(':capacitor-volume-control')
}

View file

@ -5,6 +5,9 @@ project(':capacitor-android').projectDir = new File('../../node_modules/@capacit
include ':capacitor-community-file-opener'
project(':capacitor-community-file-opener').projectDir = new File('../../node_modules/@capacitor-community/file-opener/android')
include ':capacitor-community-screen-brightness'
project(':capacitor-community-screen-brightness').projectDir = new File('../../node_modules/@capacitor-community/screen-brightness/android')
include ':capacitor-app'
project(':capacitor-app').projectDir = new File('../../node_modules/@capacitor/app/android')
@ -25,3 +28,6 @@ project(':capacitor-nodejs').projectDir = new File('../../node_modules/capacitor
include ':capacitor-plugin-safe-area'
project(':capacitor-plugin-safe-area').projectDir = new File('../../node_modules/capacitor-plugin-safe-area/android')
include ':capacitor-volume-control'
project(':capacitor-volume-control').projectDir = new File('../../../volume-control/capacitor-volume-control/android')

View file

@ -27,10 +27,11 @@
},
"dependencies": {
"@capacitor-community/file-opener": "^6.0.0",
"@capacitor-community/screen-brightness": "^6.0.0",
"@capacitor/android": "^6.1.1",
"@capacitor/app": "^6.0.0",
"@capacitor/cli": "^6.1.1",
"@capacitor/browser": "^6.0.1",
"@capacitor/cli": "^6.1.1",
"@capacitor/core": "^6.1.1",
"@capacitor/filesystem": "^6.0.0",
"@capacitor/ios": "^6.1.1",
@ -38,6 +39,7 @@
"@capacitor/status-bar": "^6.0.0",
"capacitor-nodejs": "https://github.com/hampoelz/Capacitor-NodeJS/releases/download/v1.0.0-beta.7/capacitor-nodejs.tgz",
"capacitor-plugin-safe-area": "^3.0.3",
"capacitor-volume-control": "^0.0.1",
"common": "workspace:*",
"cordova-plugin-navigationbar-color": "^0.1.0",
"cordova-plugin-pip": "^0.0.2",

View file

@ -0,0 +1,173 @@
import { Capacitor } from '@capacitor/core';
import { ScreenBrightness } from '@capacitor-community/screen-brightness';
import { VolumeControl } from 'capacitor-volume-control';
export function swipeControls(node, props = { enabled: true, immersePlayer: () => {} }) {
if (!props.enabled) return;
const isNative = Capacitor.isNativePlatform();
let brightness = 50;
let volume = 50; // Start at middle volume (range 0-100)
let startX = 0;
let startY = 0;
let isDragging = false;
let activeControl = null;
let timeoutId;
let updateTimeoutId;
const indicators = document.createElement('div');
indicators.className = 'swipe-control-indicators';
indicators.style.cssText = `
position: absolute;
top: 75%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
gap: 20px;
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
color: white;
text-shadow: 1px 1px 2px black;
`;
indicators.innerHTML = `
<div class="indicator brightness">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"/>
<line x1="12" y1="1" x2="12" y2="3"/>
<line x1="12" y1="21" x2="12" y2="23"/>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
<line x1="1" y1="12" x2="3" y2="12"/>
<line x1="21" y1="12" x2="23" y2="12"/>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
</svg>
<span class="brightness-value">100%</span>
</div>
<div class="indicator volume">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/>
<path d="M15.54 8.46a5 5 0 0 1 0 7.07"/>
<path d="M19.07 4.93a10 10 0 0 1 0 14.14"/>
</svg>
<span class="volume-value">50%</span>
</div>
`;
node.style.position = 'relative';
node.appendChild(indicators);
const brightnessValue = indicators.querySelector('.brightness-value');
const volumeValue = indicators.querySelector('.volume-value');
function handleTouchStart(event) {
if (!props.enabled) return;
props.immersePlayer();
if (!isNative) return;
isDragging = true;
startX = event.touches[0].clientX;
startY = event.touches[0].clientY;
const rect = node.getBoundingClientRect();
activeControl = (event.touches[0].clientX - rect.left) < rect.width / 2 ? 'brightness' : 'volume';
}
function handleTouchMove(event) {
if (!props.enabled) return;
if (!isNative || !isDragging) return;
const currentY = event.touches[0].clientY;
const deltaY = startY - currentY;
const rect = node.getBoundingClientRect();
const sensitivity = 0.5 * (200 / rect.height);
if (activeControl === 'brightness') {
brightness = Math.max(0, Math.min(100, brightness + deltaY * sensitivity));
updateBrightness();
} else if (activeControl === 'volume') {
volume = Math.max(0, Math.min(100, volume + deltaY * sensitivity));
debouncedUpdateVolume();
}
showControlsIndicator();
startY = currentY;
}
function handleTouchEnd() {
if (!props.enabled) return;
isDragging = false;
activeControl = null;
}
async function updateBrightness() {
try {
await ScreenBrightness.setBrightness({ brightness: brightness / 100 });
brightnessValue.textContent = `${Math.round(brightness)}%`;
} catch (error) {
console.error('Error updating brightness:', error);
}
}
let lastVolume = 50;
const VOLUME_CHANGE_THRESHOLD = 1;
function debouncedUpdateVolume() {
clearTimeout(updateTimeoutId);
// Update UI immediately
volumeValue.textContent = `${Math.round(volume)}%`;
updateTimeoutId = setTimeout(async () => {
if (Math.abs(volume - lastVolume) >= VOLUME_CHANGE_THRESHOLD) {
try {
await VolumeControl.setVolume({ volume: Math.round(volume), showUI: false });
lastVolume = volume;
} catch (error) {
console.error('Error updating volume:', error);
// Retry logic
setTimeout(() => debouncedUpdateVolume(), 50);
}
}
}, 33);
}
function showControlsIndicator() {
indicators.style.opacity = '1';
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
indicators.style.opacity = '0';
props.immersePlayer();
}, 500);
}
node.addEventListener('touchstart', handleTouchStart);
node.addEventListener('touchmove', handleTouchMove, { passive: false });
node.addEventListener('touchend', handleTouchEnd);
async function init() {
if (isNative) {
try {
const { brightness: currentBrightness } = await ScreenBrightness.getBrightness();
const { volume: currentVolume } = await VolumeControl.getCurrentVolume();
brightness = currentBrightness * 100;
volume = currentVolume;
updateBrightness();
debouncedUpdateVolume();
} catch (error) {
console.error('Error initializing brightness and volume:', error);
}
}
}
init();
return {
destroy() {
node.removeEventListener('touchstart', handleTouchStart);
node.removeEventListener('touchmove', handleTouchMove);
node.removeEventListener('touchend', handleTouchEnd);
clearTimeout(timeoutId);
node.removeChild(indicators);
}
};
}

View file

@ -18,6 +18,7 @@
import { SUPPORTS } from '@/modules/support.js'
import 'rvfc-polyfill'
import IPC from '@/modules/ipc.js'
import { swipeControls } from '@/modules/swipecontrol';
const emit = createEventDispatcher()
@ -1027,6 +1028,7 @@
class:fitWidth
bind:this={container}
role='none'
use:swipeControls={{enabled: SUPPORTS.isAndroid, immersePlayer}}
on:pointermove={resetImmerse}
on:keypress={resetImmerse}
on:keydown={resetImmerse}

View file

@ -66,6 +66,9 @@ importers:
'@capacitor-community/file-opener':
specifier: ^6.0.0
version: 6.0.0(@capacitor/core@6.1.1)
'@capacitor-community/screen-brightness':
specifier: ^6.0.0
version: 6.0.0(@capacitor/core@6.1.1)
'@capacitor/android':
specifier: ^6.1.1
version: 6.1.1(@capacitor/core@6.1.1)
@ -99,6 +102,9 @@ importers:
capacitor-plugin-safe-area:
specifier: ^3.0.3
version: 3.0.3(@capacitor/core@6.1.1)
capacitor-volume-control:
specifier: ^0.0.1
version: 0.0.1(@capacitor/core@6.1.1)
common:
specifier: workspace:*
version: link:../common
@ -311,6 +317,11 @@ packages:
peerDependencies:
'@capacitor/core': ^6.0.0
'@capacitor-community/screen-brightness@6.0.0':
resolution: {integrity: sha512-8yU2Epwym7IKJ3Ae8LDlo6RDbZuo4x2B2M1oKT04kaVjWRxHzx6wETpzLJqrwix1NyqbXIx5TPPBpk0Kxmv45w==}
peerDependencies:
'@capacitor/core': ^6.0.0
'@capacitor/android@6.1.1':
resolution: {integrity: sha512-rhO/nH6NJizGV5KizoIoGxCmkos3HnyUzI9TNv8IVy/C8h6lPM5Gt9cxGclb6k0OBTtgv6iJVo654+m4kzv0Qg==}
peerDependencies:
@ -1633,6 +1644,11 @@ packages:
peerDependencies:
'@capacitor/core': ^6.0.0
capacitor-volume-control@0.0.1:
resolution: {integrity: sha512-AuD38P2my58LsYfsJMF7KmCrLfe9mRwo30KDSq1lUVGaWyCw5byJwQCywwsC/fijrd8q6dMZ+7c3krPqwnBLLA==}
peerDependencies:
'@capacitor/core': ^6.0.0
caseless@0.12.0:
resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==}
@ -5694,6 +5710,10 @@ snapshots:
dependencies:
'@capacitor/core': 6.1.1
'@capacitor-community/screen-brightness@6.0.0(@capacitor/core@6.1.1)':
dependencies:
'@capacitor/core': 6.1.1
'@capacitor/android@6.1.1(@capacitor/core@6.1.1)':
dependencies:
'@capacitor/core': 6.1.1
@ -7394,6 +7414,10 @@ snapshots:
dependencies:
'@capacitor/core': 6.1.1
capacitor-volume-control@0.0.1(@capacitor/core@6.1.1):
dependencies:
'@capacitor/core': 6.1.1
caseless@0.12.0: {}
chalk@2.4.2:
@ -7500,7 +7524,7 @@ snapshots:
dependencies:
'@jridgewell/sourcemap-codec': 1.4.15
'@types/estree': 1.0.5
acorn: 8.11.3
acorn: 8.12.0
estree-walker: 3.0.3
periscopic: 3.1.0
@ -11104,7 +11128,7 @@ snapshots:
'@jridgewell/sourcemap-codec': 1.4.15
'@jridgewell/trace-mapping': 0.3.25
'@types/estree': 1.0.5
acorn: 8.11.3
acorn: 8.12.0
aria-query: 5.3.0
axobject-query: 4.0.0
code-red: 1.0.4