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
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application>
<activity
android:name="ru.f7cloud.talk.activities.MainActivity"
tools:node="remove" />
</application>
</manifest>
@@ -0,0 +1,141 @@
/*
* F7cloud embed — sync F7cloud AuthSession into talk-android User DB.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.talk.f7cloud
import android.util.Log
import androidx.work.Data
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import autodagger.AutoInjector
import com.bluelinelabs.logansquare.LoganSquare
import ru.f7cloud.talk.api.NcApi
import ru.f7cloud.talk.application.F7cloudTalkApplication
import ru.f7cloud.talk.jobs.SignalingSettingsWorker
import ru.f7cloud.talk.users.UserManager
import ru.f7cloud.talk.utils.ApiUtils
import ru.f7cloud.talk.utils.bundle.BundleKeys
import javax.inject.Inject
@AutoInjector(F7cloudTalkApplication::class)
class F7TalkUserSync {
@Inject lateinit var userManager: UserManager
@Inject lateinit var ncApi: NcApi
fun ensureUser(
serverUrl: String,
username: String,
appPassword: String,
davUserId: String?,
): Long? {
val baseUrl = serverUrl.trimEnd('/')
val credentials = ApiUtils.getCredentials(username, appPassword)
if (credentials.isNullOrBlank()) {
Log.w(TAG, "Missing credentials for $username")
return null
}
val existingId = findExistingUserId(username, baseUrl)
if (existingId != null && hasCapabilities(existingId)) {
refreshTokenIfNeeded(existingId, appPassword)
userManager.setUserAsActive(userManager.getUserWithId(existingId).blockingGet()).blockingGet()
enqueueSignalingSettings(existingId)
Log.d(TAG, "User already synced: $username@$baseUrl")
return existingId
}
val capabilities = ncApi
.getCapabilities(credentials, ApiUtils.getUrlForCapabilities(baseUrl))
.blockingFirst()
val profile = ncApi
.getUserProfile(credentials, ApiUtils.getUrlForUserProfile(baseUrl))
.blockingFirst()
val profileData = profile.ocs?.data
val capsData = capabilities.ocs?.data
if (capsData?.capabilities == null || capsData.serverVersion == null) {
Log.w(TAG, "Capabilities response incomplete for $baseUrl")
return null
}
val displayName = profileData?.displayName?.takeIf { it.isNotBlank() }
?: profileData?.displayNameAlt?.takeIf { it.isNotBlank() }
?: username
val userId = profileData?.userId?.takeIf { it.isNotBlank() }
?: davUserId?.takeIf { it.isNotBlank() }
?: username
val user = userManager.storeProfile(
username,
UserManager.UserAttributes(
id = existingId,
serverUrl = baseUrl,
currentUser = true,
userId = userId,
token = appPassword,
displayName = displayName,
pushConfigurationState = null,
capabilities = LoganSquare.serialize(capsData.capabilities),
serverVersion = LoganSquare.serialize(capsData.serverVersion),
certificateAlias = null,
externalSignalingServer = null,
),
).blockingGet()
val internalId = user.id
if (internalId != null) {
enqueueSignalingSettings(internalId)
}
Log.d(TAG, "Synced talk user id=$internalId for $username@$baseUrl")
return internalId
}
private fun enqueueSignalingSettings(internalUserId: Long) {
val app = F7cloudTalkApplication.sharedApplication ?: return
val data = Data.Builder()
.putLong(BundleKeys.KEY_INTERNAL_USER_ID, internalUserId)
.build()
val work = OneTimeWorkRequest.Builder(SignalingSettingsWorker::class.java)
.setInputData(data)
.build()
WorkManager.getInstance(app).enqueue(work)
Log.d(TAG, "Enqueued SignalingSettingsWorker for user $internalUserId (HPB)")
}
private fun findExistingUserId(username: String, baseUrl: String): Long? =
userManager.users.blockingGet()
.firstOrNull { it.username == username && it.baseUrl == baseUrl && !it.scheduledForDeletion }
?.id
private fun hasCapabilities(userId: Long): Boolean {
val user = userManager.getUserWithId(userId).blockingGet()
return user.capabilities?.spreedCapability != null
}
private fun refreshTokenIfNeeded(userId: Long, token: String) {
val user = userManager.getUserWithId(userId).blockingGet()
if (user.token != token) {
user.token = token
userManager.saveUser(user).blockingGet()
}
}
companion object {
private const val TAG = "F7TalkUserSync"
@Volatile
private var instance: F7TalkUserSync? = null
fun get(): F7TalkUserSync {
val app = F7cloudTalkApplication.sharedApplication
?: error("F7cloudTalkApplication not initialized")
return instance ?: synchronized(this) {
instance ?: F7TalkUserSync().also {
app.componentApplication.inject(it)
instance = it
}
}
}
}
}
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- F7cloud embed: branding (signaling/HPB comes from server capabilities). -->
<resources>
<string name="nc_app_name">F7cloud Talk</string>
<string name="nc_app_product_name">F7cloud Talk</string>
<string name="nc_server_product_name">F7cloud</string>
<string name="nc_talk_login_scheme" translatable="false">f7</string>
<string name="nc_import_accounts_from" translatable="false"></string>
<string name="nc_import_account_type" translatable="false">f7cloud</string>
<string name="osm_geocoder_contact" translatable="false">android@f7cloud.ru</string>
<string name="nc_privacy_url" translatable="false">https://f7cloud.ru/</string>
<bool name="hide_provider">true</bool>
<bool name="multiaccount_support">false</bool>
</resources>