Initial import of f7cloud-mobile native Android app.

Kotlin/Compose client for F7cloud (mail, files, talk, calendar, contacts, tasks, support).
Current version: 0.5.113 (build 121).
This commit is contained in:
F7cloud Mobile
2026-07-07 12:05:18 +03:00
commit fd17df80a8
1789 changed files with 246889 additions and 0 deletions
@@ -0,0 +1,320 @@
/**
* F7cloud APK: skip "Check devices" and join incoming calls with camera/mic off.
* Requires sessionStorage f7_apk_direct_join=1 (set from Android on push accept).
*/
(function () {
'use strict';
var LOG = 'F7TalkJoin';
var LEFT_PREFIX = 'f7_apk_left_';
var STYLE_ID = 'f7-talk-hide-media-style';
function log(msg) {
try { console.debug('[' + LOG + '] ' + msg); } catch (e) { /* ignore */ }
}
function roomToken() {
var m = window.location.pathname.match(/\/call\/([A-Za-z0-9]+)/);
return m ? m[1] : null;
}
function leftKey() {
var t = roomToken();
return t ? (LEFT_PREFIX + t) : null;
}
function markLeft() {
try {
var k = leftKey();
if (k) {
sessionStorage.setItem(k, String(Date.now()));
}
sessionStorage.removeItem('f7_apk_direct_join');
document.documentElement.removeAttribute('data-f7-direct-join');
log('marked left');
} catch (e) { /* ignore */ }
}
function leftRecently() {
try {
var k = leftKey();
if (!k) {
return false;
}
var ts = parseInt(sessionStorage.getItem(k) || '0', 10);
return ts > 0 && (Date.now() - ts) < (10 * 60 * 1000);
} catch (e) {
return false;
}
}
function isDirectJoin() {
if (leftRecently()) {
return false;
}
try {
return sessionStorage.getItem('f7_apk_direct_join') === '1';
} catch (e) {
return false;
}
}
function stripDirectCallHash() {
try {
if (window.location.hash === '#direct-call') {
history.replaceState(history.state, '', window.location.pathname + window.location.search);
log('stripped #direct-call hash');
}
} catch (e) { /* ignore */ }
}
function prepareStorage() {
stripDirectCallHash();
try {
localStorage.setItem('showMediaSettings', 'false');
document.documentElement.setAttribute('data-f7-direct-join', '1');
} catch (e) { /* ignore */ }
var token = roomToken();
if (token) {
try {
localStorage.setItem('videoDisabled_' + token, 'true');
localStorage.removeItem('audioDisabled_' + token);
} catch (e2) { /* ignore */ }
}
}
function hideMediaDialogs() {
var roots = document.querySelectorAll('.media-settings');
for (var i = 0; i < roots.length; i++) {
var el = roots[i];
var modal = el.closest('.modal-wrapper')
|| el.closest('.modal-container')
|| el.closest('[role="dialog"]')
|| el;
modal.style.setProperty('display', 'none', 'important');
modal.style.setProperty('visibility', 'hidden', 'important');
modal.style.setProperty('opacity', '0', 'important');
modal.style.setProperty('pointer-events', 'none', 'important');
}
}
function injectHideStyle() {
if (document.getElementById(STYLE_ID)) {
return;
}
var style = document.createElement('style');
style.id = STYLE_ID;
style.textContent = ''
+ 'html[data-f7-direct-join="1"] .modal-wrapper,'
+ 'html[data-f7-direct-join="1"] .modal-container,'
+ 'html[data-f7-direct-join="1"] .media-settings,'
+ 'html[data-f7-direct-join="1"] .modal-wrapper .media-settings'
+ '{display:none!important;visibility:hidden!important;opacity:0!important;pointer-events:none!important;}';
(document.head || document.documentElement).appendChild(style);
hideMediaDialogs();
}
function eventBus() {
if (window._nc_event_bus) {
return window._nc_event_bus;
}
if (window.OC && window.OC._eventBus) {
return window.OC._eventBus;
}
return null;
}
function emitEvent(name, arg) {
var bus = eventBus();
if (!bus || typeof bus.emit !== 'function') {
return false;
}
try {
bus.emit(name, arg === undefined ? '' : arg);
return true;
} catch (e) {
return false;
}
}
function labelMatchesMediaToggle(label) {
return label.indexOf('camera') !== -1 || label.indexOf('video') !== -1
|| label.indexOf('камер') !== -1 || label.indexOf('видео') !== -1
|| label.indexOf('mute video') !== -1 || label.indexOf('turn off') !== -1;
}
function disableCameraInMediaSettings() {
var root = document.querySelector('.media-settings');
if (!root) {
return;
}
var toggles = root.querySelector('.media-settings__toggles');
var scope = toggles || root;
var buttons = scope.querySelectorAll('button');
for (var i = 0; i < buttons.length; i++) {
var b = buttons[i];
var label = ((b.getAttribute('aria-label') || '') + ' ' + (b.getAttribute('title') || '')).toLowerCase();
if (!labelMatchesMediaToggle(label)) {
continue;
}
var pressed = b.getAttribute('aria-pressed');
if (pressed === 'true' || pressed === null) {
b.click();
log('toggled camera off: ' + label);
}
}
}
function clickJoinButtons() {
var root = document.querySelector('.media-settings');
if (root) {
var inDialog = root.querySelectorAll('button.action-button, button.primary, button[class*="join"]');
for (var d = 0; d < inDialog.length; d++) {
var db = inDialog[d];
if (db.disabled || db.offsetParent === null) {
continue;
}
var dt = ((db.getAttribute('aria-label') || '') + ' ' + (db.textContent || '')).toLowerCase();
if (dt.indexOf('join') !== -1 || dt.indexOf('присоедин') !== -1
|| dt.indexOf('answer') !== -1 || dt.indexOf('начать') !== -1
|| dt.indexOf('apply') !== -1 || dt.indexOf('примен') !== -1
|| dt.indexOf('save') !== -1 || dt.indexOf('сохран') !== -1
|| dt.indexOf('готово') !== -1) {
db.click();
log('clicked in-dialog: ' + dt);
return true;
}
}
}
var btn = document.querySelector('button.join-call');
if (btn && !btn.disabled && btn.offsetParent !== null) {
btn.click();
log('clicked button.join-call');
return true;
}
var buttons = document.querySelectorAll('button.action-button, button.primary');
for (var i = 0; i < buttons.length; i++) {
var b = buttons[i];
if (b.disabled || b.offsetParent === null) {
continue;
}
var t = ((b.getAttribute('aria-label') || '') + ' ' + (b.textContent || '')).toLowerCase();
if (t.indexOf('join') !== -1 || t.indexOf('присоедин') !== -1
|| t.indexOf('answer') !== -1 || t.indexOf('ответ') !== -1
|| t.indexOf('принять') !== -1 || t.indexOf('начать') !== -1) {
b.click();
log('clicked: ' + t);
return true;
}
}
return false;
}
function forceJoin() {
if (!isDirectJoin()) {
return false;
}
prepareStorage();
injectHideStyle();
disableCameraInMediaSettings();
emitEvent('talk:media-settings:hide');
if (clickJoinButtons()) {
try { sessionStorage.removeItem('f7_apk_direct_join'); } catch (e) { /* ignore */ }
setTimeout(function () {
document.documentElement.removeAttribute('data-f7-direct-join');
}, 5000);
return true;
}
return false;
}
function installLeaveHook() {
if (window.__f7TalkLeaveHook) {
return;
}
window.__f7TalkLeaveHook = true;
document.addEventListener('click', function (ev) {
var el = ev.target;
if (!el) {
return;
}
for (var i = 0; i < 5 && el; i++) {
if (el.tagName && el.tagName.toLowerCase() === 'button') {
break;
}
el = el.parentElement;
}
if (!el || !el.tagName || el.tagName.toLowerCase() !== 'button') {
return;
}
var txt = ((el.getAttribute('aria-label') || '') + ' ' + (el.textContent || '')).toLowerCase();
if (txt.indexOf('leave') !== -1 || txt.indexOf('hang up') !== -1 || txt.indexOf('end') !== -1
|| txt.indexOf('выйти') !== -1 || txt.indexOf('покин') !== -1 || txt.indexOf('заверш') !== -1) {
markLeft();
}
}, true);
}
function installBusHook() {
if (window.__f7TalkBusHook) {
return;
}
var bus = eventBus();
if (!bus || typeof bus.on !== 'function') {
return;
}
window.__f7TalkBusHook = true;
bus.on('talk:media-settings:show', function () {
if (!isDirectJoin()) {
return;
}
log('block media-settings');
prepareStorage();
injectHideStyle();
setTimeout(function () {
disableCameraInMediaSettings();
emitEvent('talk:media-settings:hide');
forceJoin();
}, 50);
});
}
var tries = 0;
function tick() {
if (!isDirectJoin()) {
return;
}
installLeaveHook();
installBusHook();
injectHideStyle();
tries++;
if (forceJoin() || tries >= 100) {
return;
}
setTimeout(tick, 200);
}
function start() {
prepareStorage();
if (!isDirectJoin()) {
return;
}
if (!roomToken()) {
return;
}
tries = 0;
installLeaveHook();
installBusHook();
tick();
}
window.addEventListener('hashchange', start);
if (document.readyState === 'complete' || document.readyState === 'interactive') {
start();
} else {
document.addEventListener('DOMContentLoaded', start);
window.addEventListener('load', start);
}
})();