From b0eebecdb7ff0672669ad591a591fdfce5298257 Mon Sep 17 00:00:00 2001 From: Semper-Viventem Date: Wed, 1 Oct 2025 20:05:36 -0700 Subject: [PATCH 1/6] Update F-Droid metadata --- metadata/de/full_description.txt | 1 + metadata/de/short_description.txt | 2 +- metadata/en-US/full_description.txt | 1 + metadata/ru/full_description.txt | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/metadata/de/full_description.txt b/metadata/de/full_description.txt index 15840b5..876d966 100644 --- a/metadata/de/full_description.txt +++ b/metadata/de/full_description.txt @@ -11,6 +11,7 @@ Glücklicherweise implementieren viele moderne Geräte Datenschutzfunktionen in Ziel dieser App ist es, Ihnen Wissen und Kontrolle über die BLE-Geräte in Ihrer Umgebung zu geben. Zu verstehen, welche Geräte nachverfolgbare Informationen senden und welche datenschutzbewusst sind, ermöglicht es Ihnen, fundierte Entscheidungen darüber zu treffen, welche Geräte Sie täglich verwenden, tragen oder mit denen Sie interagieren. Im Allgemeinen kann die App: + * Bluetooth-Geräte in der Umgebung scannen, analysieren und verfolgen; * Flexible Filter für das Radar erstellen; * Tiefergehende Analyse der gescannten BLE-Geräte durchführen, einschließlich Daten aus verfügbaren GATT-Services; diff --git a/metadata/de/short_description.txt b/metadata/de/short_description.txt index 67ff084..06e1f3a 100644 --- a/metadata/de/short_description.txt +++ b/metadata/de/short_description.txt @@ -1 +1 @@ -Ein Tool, um BLE-Geräte in Ihrer Umgebung zu überwachen, zu analysieren und zu finden. \ No newline at end of file +BLE-Geräte finden, überwachen, und analysieren. \ No newline at end of file diff --git a/metadata/en-US/full_description.txt b/metadata/en-US/full_description.txt index f376512..85dc07d 100644 --- a/metadata/en-US/full_description.txt +++ b/metadata/en-US/full_description.txt @@ -11,6 +11,7 @@ Beyond analysis, BLE Radar can help protect you in real-time. The app can alert By making this app, the goal is to empower you with knowledge and control over the BLE devices in your environment. Understanding which devices are broadcasting trackable information and which are privacy-conscious allows you to make informed decisions about what you use, wear, and interact with daily. In general, the app is capable: + * Scan, analyze and track Bluetooth devices around; * Create flexible filters for the radar; * Deep analysis of the scanned BLE devices, getting data from the available GATT services; diff --git a/metadata/ru/full_description.txt b/metadata/ru/full_description.txt index 4a19400..cd8a528 100644 --- a/metadata/ru/full_description.txt +++ b/metadata/ru/full_description.txt @@ -11,6 +11,7 @@ Bluetooth Low Energy (BLE) — это широко используемый бе Цель приложения — дать вам знания и контроль над BLE-устройствами в вашей среде. Понимание того, какие устройства передают отслеживаемую информацию, а какие заботятся о приватности, позволяет принимать обоснованные решения о том, что вы используете, носите и с чем взаимодействуете ежедневно. В общем, приложение позволяет: + * Сканировать, анализировать и отслеживать Bluetooth-устройства вокруг; * Создавать гибкие фильтры для радара; * Глубоко анализировать сканированные BLE-устройства, получая данные из доступных GATT-сервисов; From 24704769d327916df05a3e76c190634c88c0b1d8 Mon Sep 17 00:00:00 2001 From: Constantine Date: Fri, 3 Oct 2025 19:04:51 -0700 Subject: [PATCH 2/6] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 17 ++++++++--------- .github/ISSUE_TEMPLATE/crash-report.md | 18 +++++++++++++----- .github/ISSUE_TEMPLATE/feature_request.md | 5 +++++ 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 03c8c3d..09c6b19 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,6 +7,11 @@ assignees: Semper-Viventem --- + + **Describe the bug** A clear and concise description of what the bug is. @@ -23,16 +28,10 @@ A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. -**Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - - Version [e.g. 22] - **Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - - Version [e.g. 22] + - Device: [e.g. Pixel 9] + - OS: [e.g. Android 16] + - App version [e.g. v0.31.0-beta] (find in the settings screen) **Additional context** Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/crash-report.md b/.github/ISSUE_TEMPLATE/crash-report.md index 0cd955a..f714d2f 100644 --- a/.github/ISSUE_TEMPLATE/crash-report.md +++ b/.github/ISSUE_TEMPLATE/crash-report.md @@ -7,17 +7,25 @@ assignees: Semper-Viventem --- + + **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' -4. App crashes +4. Crash **Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - App version [e.g. 22] + - Device: [e.g. Pixel 9] + - OS: [e.g. Android 16] + - App version [e.g. v0.31.0-beta] (find in the settings screen) + +**Additional context** +Add any other context about the problem here. **Crash log** -Please attach the crash log if you have one, (You can see the log in debug builds) +Grab crash log using ADB or check errors information in the Journal page diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 2c60655..669673c 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -7,6 +7,11 @@ assignees: Semper-Viventem --- + + **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] From 3fd8f275f062429a98985b77ae655525d4d6c3f2 Mon Sep 17 00:00:00 2001 From: Semper-Viventem Date: Fri, 21 Nov 2025 20:33:43 -0800 Subject: [PATCH 3/6] Update deps, optimize devices db galls --- app/build.gradle.kts | 4 +- .../java/f/cking/software/data/DataMappers.kt | 2 +- .../data/database/dao/AppleContactDao.kt | 3 ++ .../software/data/database/dao/DeviceDao.kt | 4 ++ .../software/data/repo/DevicesRepository.kt | 49 ++++++++++--------- .../interactor/ClearGarbageInteractor.kt | 1 + .../ui/devicelist/DeviceListViewModel.kt | 20 ++++---- gradle/libs.versions.toml | 10 ++-- 8 files changed, 51 insertions(+), 42 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index be12536..cd5a642 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -26,8 +26,8 @@ android { minSdk = 29 targetSdk = 36 - versionCode = 1708536378 - versionName = "0.31.1-beta" + versionCode = 1708536379 + versionName = "0.31.2-beta" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/java/f/cking/software/data/DataMappers.kt b/app/src/main/java/f/cking/software/data/DataMappers.kt index 7fb4eea..590afed 100644 --- a/app/src/main/java/f/cking/software/data/DataMappers.kt +++ b/app/src/main/java/f/cking/software/data/DataMappers.kt @@ -38,7 +38,7 @@ fun LocationEntity.toDomain(): LocationModel { return LocationModel(lat, lng, time) } -fun DeviceEntity.toDomain(appleAirDrop: AppleAirDrop?): DeviceData { +fun DeviceEntity.toDomain(appleAirDrop: AppleAirDrop? = null): DeviceData { return DeviceData( address = address, name = name, diff --git a/app/src/main/java/f/cking/software/data/database/dao/AppleContactDao.kt b/app/src/main/java/f/cking/software/data/database/dao/AppleContactDao.kt index bc39f66..b48ffde 100644 --- a/app/src/main/java/f/cking/software/data/database/dao/AppleContactDao.kt +++ b/app/src/main/java/f/cking/software/data/database/dao/AppleContactDao.kt @@ -29,4 +29,7 @@ interface AppleContactDao { @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertAll(contacts: List) + + @Query("DELETE FROM apple_contacts WHERE associated_address IN (:addresses)") + fun deleteAllByAddresses(addresses: List) } \ No newline at end of file diff --git a/app/src/main/java/f/cking/software/data/database/dao/DeviceDao.kt b/app/src/main/java/f/cking/software/data/database/dao/DeviceDao.kt index e803758..a90d4a6 100644 --- a/app/src/main/java/f/cking/software/data/database/dao/DeviceDao.kt +++ b/app/src/main/java/f/cking/software/data/database/dao/DeviceDao.kt @@ -5,6 +5,7 @@ import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query import f.cking.software.data.database.entity.DeviceEntity +import kotlinx.coroutines.flow.Flow @Dao interface DeviceDao { @@ -12,6 +13,9 @@ interface DeviceDao { @Query("SELECT * FROM device") fun getAll(): List + @Query("SELECT * FROM device") + fun observeAll(): Flow> + @Query("SELECT * FROM device ORDER BY last_detect_time_ms DESC LIMIT :limit OFFSET :offset") fun getPaginated(offset: Int, limit: Int): List diff --git a/app/src/main/java/f/cking/software/data/repo/DevicesRepository.kt b/app/src/main/java/f/cking/software/data/repo/DevicesRepository.kt index 0cb70cf..070baad 100644 --- a/app/src/main/java/f/cking/software/data/repo/DevicesRepository.kt +++ b/app/src/main/java/f/cking/software/data/repo/DevicesRepository.kt @@ -11,8 +11,10 @@ import f.cking.software.domain.toDomain import f.cking.software.splitToBatches import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -23,7 +25,8 @@ class DevicesRepository( private val deviceDao: DeviceDao = appDatabase.deviceDao() private val appleContactsDao = appDatabase.appleContactDao() private val lastBatch = MutableStateFlow(emptyList()) - private val allDevices = MutableStateFlow(emptyList()) + private val allDevices = deviceDao.observeAll() + .map { it.toDomainWithAirDrop() } suspend fun getDevices(): List { return withContext(Dispatchers.IO) { @@ -54,12 +57,8 @@ class DevicesRepository( lastBatch.value = emptyList() } - suspend fun observeAllDevices(): StateFlow> { - return allDevices.apply { - if (allDevices.value.isEmpty()) { - notifyListeners() - } - } + fun observeAllDevices(): Flow> { + return allDevices } suspend fun observeLastBatch(): StateFlow> { @@ -102,6 +101,17 @@ class DevicesRepository( } } + suspend fun clearUnAssociatedAirdrops() { + withContext(Dispatchers.IO) { + val allDevices = deviceDao.getAll().mapTo(mutableSetOf()) { it.address } + val allAidrops = appleContactsDao.getAll().map { it.associatedAddress } + + val unassotiatedAirdrops = allAidrops.filter { !allDevices.contains(it) } + + appleContactsDao.deleteAllByAddresses(unassotiatedAirdrops) + } + } + suspend fun getAllByAddresses(addresses: List): List { return withContext(Dispatchers.IO) { addresses.splitToBatches(DatabaseUtils.getMaxSQLVariablesNumber()).flatMap { @@ -149,14 +159,10 @@ class DevicesRepository( private suspend fun notifyListeners() { coroutineScope { - launch { + launch(Dispatchers.Default) { val data = getLastBatch() lastBatch.emit(data) } - launch { - val data = getDevices() - allDevices.emit(data) - } } } @@ -168,20 +174,15 @@ class DevicesRepository( } private suspend fun List.toDomainWithAirDrop(): List { - return withContext(Dispatchers.IO) { - - val allRelatedContacts = - splitToBatches(DatabaseUtils.getMaxSQLVariablesNumber()).flatMap { batch -> - appleContactsDao.getByAddresses(batch.map { it.address }) - } + return withContext(Dispatchers.Default) { + val allRelatedContacts = withContext(Dispatchers.IO) { + appleContactsDao.getAll().groupBy { it.associatedAddress } + } map { device -> - val airdrop = allRelatedContacts.asSequence() - .filter { it.associatedAddress == device.address } - .map { it.toDomain() } - .toList() - .takeIf { it.isNotEmpty() } - ?.let { AppleAirDrop(it) } + val airdrop = allRelatedContacts[device.address]?.let { + AppleAirDrop(it.map { it.toDomain() }) + } device.toDomain(airdrop) } diff --git a/app/src/main/java/f/cking/software/domain/interactor/ClearGarbageInteractor.kt b/app/src/main/java/f/cking/software/domain/interactor/ClearGarbageInteractor.kt index 23ce69a..fa0156f 100644 --- a/app/src/main/java/f/cking/software/domain/interactor/ClearGarbageInteractor.kt +++ b/app/src/main/java/f/cking/software/domain/interactor/ClearGarbageInteractor.kt @@ -23,6 +23,7 @@ class ClearGarbageInteractor( .toList() devicesRepository.deleteAllByAddress(devices) + devicesRepository.clearUnAssociatedAirdrops() locationRepository.removeDeviceLocationsByAddresses(devices) devices.count() } diff --git a/app/src/main/java/f/cking/software/ui/devicelist/DeviceListViewModel.kt b/app/src/main/java/f/cking/software/ui/devicelist/DeviceListViewModel.kt index 24e8f8c..6d19585 100644 --- a/app/src/main/java/f/cking/software/ui/devicelist/DeviceListViewModel.kt +++ b/app/src/main/java/f/cking/software/ui/devicelist/DeviceListViewModel.kt @@ -27,7 +27,6 @@ import f.cking.software.mapParallel import f.cking.software.service.BgScanService import f.cking.software.splitToBatches import f.cking.software.ui.ScreenNavigationCommands -import f.cking.software.ui.devicelist.DeviceListViewModel.ActiveScannerExpandedState.entries import f.cking.software.utils.navigation.Router import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -200,6 +199,7 @@ class DeviceListViewModel( @OptIn(ExperimentalCoroutinesApi::class) private fun observeAllDevices(): Job { + isLoading = true return viewModelScope.launch { combine( appliedFilter, @@ -207,17 +207,17 @@ class DeviceListViewModel( devicesRepository.observeAllDevices(), ) { filters, query, devices -> Triple(filters, query, devices) } .flatMapLatest { (filters, query, devices) -> - flow { - val result = withContext(Dispatchers.Default) { - isLoading = true - devices - .withFilters(filters, query) - .sortedWith(GENERAL_COMPARATOR) - .apply { showEnjoyTheAppIfNeeded() } + flow { + val result = withContext(Dispatchers.Default) { + isLoading = true + devices + .withFilters(filters, query) + .sortedWith(GENERAL_COMPARATOR) + .apply { showEnjoyTheAppIfNeeded() } + } + emit(result) } - emit(result) } - } .onStart { isLoading = true } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 60208ec..7335b98 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,15 +1,15 @@ [versions] # You also need to bump the version of ksp, anvil, compose -kotlin-general = "2.2.20" +kotlin-general = "2.2.21" kotlinx = "1.10.2" ksp = "2.2.20-2.0.3" anvil = "2.4.2" -android-gradle = "8.13.0" +android-gradle = "8.13.1" protobuf = "3.21.7" protobuf-gradle = "0.9.1" # Protobuf JVM library https://github.com/flipperdevices/flipperzero-protobuf-jvm protobuf-jvm = "0.12.0-0.3.0" -compose = "1.9.2" +compose = "1.9.5" compose-icons = "1.7.8" compose-wear = "1.5.2" compose-compiler = "1.5.6" @@ -23,7 +23,7 @@ wear = "1.2.0" wear-gms = "18.0.0" wear-interaction-phone = "1.1.0-alpha03" wear-interaction-remote = "1.0.0" -room = "2.8.1" +room = "2.8.4" dagger = "2.44" timber = "5.0.1" timber-treessence = "1.0.5" @@ -49,7 +49,7 @@ datastore = "1.1.2" # https://github.com/vsch/flexmark-java/issues/442 flexmark = "0.42.14" markdown = "0.1.5" -ktor = "3.3.0" +ktor = "3.3.2" apache-compress = "1.21" apache-codec = "1.15" countly = "22.06.0" From cf6517ecd8b2828f261c373b2caec2f6e9f44baf Mon Sep 17 00:00:00 2001 From: Semper-Viventem Date: Fri, 21 Nov 2025 21:07:55 -0800 Subject: [PATCH 4/6] Optimize journal performance --- .../software/data/database/dao/JournalDao.kt | 4 ++ .../data/database/dao/RadarProfileDao.kt | 7 +++ .../software/data/repo/JournalRepository.kt | 27 ++++----- .../data/repo/RadarProfilesRepository.kt | 33 ++++++----- .../software/ui/journal/JournalViewModel.kt | 57 ++++++++++++++----- 5 files changed, 80 insertions(+), 48 deletions(-) diff --git a/app/src/main/java/f/cking/software/data/database/dao/JournalDao.kt b/app/src/main/java/f/cking/software/data/database/dao/JournalDao.kt index 81883e9..bdbedb7 100644 --- a/app/src/main/java/f/cking/software/data/database/dao/JournalDao.kt +++ b/app/src/main/java/f/cking/software/data/database/dao/JournalDao.kt @@ -5,6 +5,7 @@ import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query import f.cking.software.data.database.entity.JournalEntryEntity +import kotlinx.coroutines.flow.Flow @Dao interface JournalDao { @@ -12,6 +13,9 @@ interface JournalDao { @Insert(onConflict = OnConflictStrategy.REPLACE) fun insert(journalEntryEntity: JournalEntryEntity) + @Query("SELECT * FROM journal") + fun observe(): Flow> + @Query("SELECT * FROM journal") fun getAll(): List diff --git a/app/src/main/java/f/cking/software/data/database/dao/RadarProfileDao.kt b/app/src/main/java/f/cking/software/data/database/dao/RadarProfileDao.kt index b324adb..2485432 100644 --- a/app/src/main/java/f/cking/software/data/database/dao/RadarProfileDao.kt +++ b/app/src/main/java/f/cking/software/data/database/dao/RadarProfileDao.kt @@ -7,6 +7,7 @@ import androidx.room.Query import f.cking.software.data.database.entity.LocationEntity import f.cking.software.data.database.entity.ProfileDetectEntity import f.cking.software.data.database.entity.RadarProfileEntity +import kotlinx.coroutines.flow.Flow @Dao interface RadarProfileDao { @@ -14,9 +15,15 @@ interface RadarProfileDao { @Query("SELECT * FROM radar_profile") fun getAll(): List + @Query("SELECT * FROM radar_profile") + fun observe(): Flow> + @Query("SELECT * FROM radar_profile WHERE id = :id") fun getById(id: Int): RadarProfileEntity? + @Query("SELECT * FROM radar_profile WHERE id IN (:ids)") + fun getAllById(ids: List): List + @Insert(onConflict = OnConflictStrategy.REPLACE) fun insert(radarProfile: RadarProfileEntity) diff --git a/app/src/main/java/f/cking/software/data/repo/JournalRepository.kt b/app/src/main/java/f/cking/software/data/repo/JournalRepository.kt index 1d4216c..3c2e24b 100644 --- a/app/src/main/java/f/cking/software/data/repo/JournalRepository.kt +++ b/app/src/main/java/f/cking/software/data/repo/JournalRepository.kt @@ -6,28 +6,26 @@ import f.cking.software.domain.toData import f.cking.software.domain.toDomain import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext -class JournalRepository( - private val database: AppDatabase, -) { +class JournalRepository(database: AppDatabase) { - val journalDao = database.journalDao() - private val journal = MutableStateFlow(emptyList()) - - suspend fun observe(): Flow> { - return journal.apply { - if (journal.value.isEmpty()) { - notifyListeners() + private val journalDao = database.journalDao() + private val journal = journalDao.observe() + .map { + withContext(Dispatchers.Default) { + it.map { it.toDomain() } } } + + fun observe(): Flow> { + return journal } suspend fun newEntry(journalEntry: JournalEntry) { withContext(Dispatchers.IO) { journalDao.insert(journalEntry.toData()) - notifyListeners() } } @@ -42,9 +40,4 @@ class JournalRepository( journalDao.getById(id)?.toDomain() } } - - private suspend fun notifyListeners() { - val data = getAllEntries() - journal.emit(data) - } } \ No newline at end of file diff --git a/app/src/main/java/f/cking/software/data/repo/RadarProfilesRepository.kt b/app/src/main/java/f/cking/software/data/repo/RadarProfilesRepository.kt index c5d38dc..192a5dc 100644 --- a/app/src/main/java/f/cking/software/data/repo/RadarProfilesRepository.kt +++ b/app/src/main/java/f/cking/software/data/repo/RadarProfilesRepository.kt @@ -7,8 +7,8 @@ import f.cking.software.domain.model.RadarProfile import f.cking.software.domain.toData import f.cking.software.domain.toDomain import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext class RadarProfilesRepository( @@ -16,15 +16,15 @@ class RadarProfilesRepository( ) { val dao = database.radarProfileDao() - private val allProfiles = MutableStateFlow(emptyList()) - - suspend fun observeAllProfiles(): StateFlow> { - return withContext(Dispatchers.IO) { - if (allProfiles.value.isEmpty()) { - notifyListeners() + private val allProfiles = dao.observe() + .map { + withContext(Dispatchers.Default) { + it.map { it.toDomain() } } - allProfiles } + + suspend fun observeAllProfiles(): Flow> { + return allProfiles } suspend fun getAllProfiles(): List { @@ -33,23 +33,27 @@ class RadarProfilesRepository( } } - suspend fun getById(id: Int): RadarProfile?{ + suspend fun getById(id: Int): RadarProfile? { return withContext(Dispatchers.IO) { dao.getById(id)?.toDomain() } } + suspend fun getAllByIds(ids: List): List { + return withContext(Dispatchers.IO) { + dao.getAllById(ids).map { it.toDomain() } + } + } + suspend fun saveProfile(profile: RadarProfile) { withContext(Dispatchers.IO) { dao.insert(profile.toData()) - notifyListeners() } } suspend fun deleteProfile(profileId: Int) { withContext(Dispatchers.IO) { dao.delete(profileId) - notifyListeners() } } @@ -70,9 +74,4 @@ class RadarProfilesRepository( dao.getProfileDetectLocations(profileId).map { it.toDomain() } } } - - private suspend fun notifyListeners() { - val data = getAllProfiles() - allProfiles.emit(data) - } } \ No newline at end of file diff --git a/app/src/main/java/f/cking/software/ui/journal/JournalViewModel.kt b/app/src/main/java/f/cking/software/ui/journal/JournalViewModel.kt index efa829d..7984ace 100644 --- a/app/src/main/java/f/cking/software/ui/journal/JournalViewModel.kt +++ b/app/src/main/java/f/cking/software/ui/journal/JournalViewModel.kt @@ -15,11 +15,16 @@ import f.cking.software.data.repo.DevicesRepository import f.cking.software.data.repo.JournalRepository import f.cking.software.data.repo.RadarProfilesRepository import f.cking.software.dateTimeStringFormat +import f.cking.software.domain.model.DeviceData import f.cking.software.domain.model.JournalEntry +import f.cking.software.domain.model.RadarProfile import f.cking.software.ui.ScreenNavigationCommands import f.cking.software.utils.navigation.Router +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import kotlin.math.min class JournalViewModel( @@ -53,18 +58,41 @@ class JournalViewModel( viewModelScope.launch { journalRepository.observe() .onStart { loading = true } - .collect { update -> + .map { loading = true - journal = update.sortedBy { it.timestamp }.reversed().map { map(it) } + mapJournalHistory(it) + } + .collect { update -> + journal = update loading = false } } } - private suspend fun map(from: JournalEntry): JournalEntryUiModel { + private suspend fun mapJournalHistory(history: List): List { + return withContext(Dispatchers.Default) { + val associatedAddresses = + history.flatMapTo(mutableSetOf()) { (it.report as? JournalEntry.Report.ProfileReport)?.deviceAddresses ?: emptyList() } + val associatedDevices = devicesRepository.getAllByAddresses(associatedAddresses.toList()).associateBy { it.address } + + val profileIds = history.mapNotNull { (it.report as? JournalEntry.Report.ProfileReport)?.profileId }.toSet() + val associatedProfiles = profileRepository.getAllByIds(profileIds.toList()).associateBy { it.id } + + history.asSequence() + .sortedByDescending { it.timestamp } + .map { map(it, associatedDevices, associatedProfiles) } + .toList() + } + } + + private fun map( + from: JournalEntry, + associatedDevices: Map, + associatedProfiles: Map, + ): JournalEntryUiModel { return when (from.report) { is JournalEntry.Report.Error -> mapReportError(from, from.report) - is JournalEntry.Report.ProfileReport -> mapReportProfile(from, from.report) + is JournalEntry.Report.ProfileReport -> mapReportProfile(from, from.report, associatedDevices, associatedProfiles) } } @@ -90,32 +118,33 @@ class JournalViewModel( ) } - private suspend fun mapReportProfile( + private fun mapReportProfile( journalEntry: JournalEntry, report: JournalEntry.Report.ProfileReport, + associatedDevices: Map, + associatedProfiles: Map, ): JournalEntryUiModel { + val profileName = associatedProfiles[report.profileId]?.name ?: context.getString(R.string.unknown_capital_case) return JournalEntryUiModel( dateTime = journalEntry.timestamp.formattedDate(), color = { MaterialTheme.colorScheme.surface }, colorForeground = { MaterialTheme.colorScheme.onSurface }, - title = context.getString(R.string.journal_profile_detected, getProfileName(report.profileId)), + title = context.getString(R.string.journal_profile_detected, profileName), subtitle = null, subtitleCollapsed = null, journalEntry = journalEntry, - items = mapListItems(report.deviceAddresses), + items = mapListItems(report.deviceAddresses, associatedDevices), ) } private fun Long.formattedDate() = dateTimeStringFormat("dd MMM yyyy, HH:mm") - private suspend fun getProfileName(id: Int): String { - return profileRepository.getById(id)?.name ?: context.getString(R.string.unknown_capital_case) - } - - private suspend fun mapListItems(addresses: List): List { - val matchedDevices = devicesRepository.getAllByAddresses(addresses) + private fun mapListItems( + addresses: List, + associatedDevices: Map, + ): List { return addresses.map { address -> - val device = matchedDevices.firstOrNull { it.address == address } + val device = associatedDevices[address] JournalEntryUiModel.ListItemUiModel( displayName = device?.buildDisplayName() ?: context.getString(R.string.journal_profile_removed, address), payload = device?.address, From cc1c2b338ba54501e158b805ee1f812cc3f1b875 Mon Sep 17 00:00:00 2001 From: Semper-Viventem Date: Fri, 21 Nov 2025 21:31:40 -0800 Subject: [PATCH 5/6] Add map disclaimer --- .../ui/devicedetails/DeviceDetailsScreen.kt | 23 +++++++++++++++++++ app/src/main/res/values/strings.xml | 2 ++ 2 files changed, 25 insertions(+) diff --git a/app/src/main/java/f/cking/software/ui/devicedetails/DeviceDetailsScreen.kt b/app/src/main/java/f/cking/software/ui/devicedetails/DeviceDetailsScreen.kt index 92673ae..6dfca64 100644 --- a/app/src/main/java/f/cking/software/ui/devicedetails/DeviceDetailsScreen.kt +++ b/app/src/main/java/f/cking/software/ui/devicedetails/DeviceDetailsScreen.kt @@ -20,6 +20,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.verticalScroll @@ -27,6 +28,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.outlined.Info import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ColorScheme @@ -92,6 +94,7 @@ import f.cking.software.utils.graphic.SignalData import f.cking.software.utils.graphic.SystemNavbarSpacer import f.cking.software.utils.graphic.TagChip import f.cking.software.utils.graphic.ThemedDialog +import f.cking.software.utils.graphic.infoDialog import kotlinx.coroutines.isActive import org.koin.androidx.compose.koinViewModel import org.koin.core.parameter.parametersOf @@ -756,6 +759,26 @@ object DeviceDetailsScreen { color = MaterialTheme.colorScheme.onSurface ) } + + val dialog = infoDialog( + title = stringResource(R.string.device_map_disclaimer_title), + content = stringResource(R.string.device_map_disclaimer_content) + ) + + IconButton( + modifier = Modifier.align(Alignment.BottomEnd), + onClick = { + dialog.show() + }, + ) { + Icon( + imageVector = Icons.Outlined.Info, + contentDescription = stringResource(R.string.device_map_disclaimer_title), + modifier = Modifier.size(24.dp) + .background(Color.Black.copy(alpha = 0.1f), shape = CircleShape), + tint = Color.DarkGray, + ) + } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 66df9a1..c9121ba 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -284,6 +284,8 @@ Disconnect Read Metadata + Map disclaimer + The map does not display the device’s actual location; it only records your smartphone’s coordinates at the moment of scanning. The location marker reflects where the device was detected, not where it truly is. The map is not designed to locate devices, but to show where they were previously observed and how their movement relates to your own. Device list From a33b2f77d088d2823e56108e0138a3cb8547e02b Mon Sep 17 00:00:00 2001 From: Semper-Viventem Date: Fri, 21 Nov 2025 22:12:40 -0800 Subject: [PATCH 6/6] Add database information --- .../software/data/database/AppDatabase.kt | 13 +++++-- .../software/data/database/dao/LocationDao.kt | 4 +++ .../data/helpers/BleFiltersProvider.kt | 2 +- .../software/data/repo/DevicesRepository.kt | 34 ++++++++++++------- .../interactor/ClearGarbageInteractor.kt | 2 +- .../interactor/GetAllDevicesInteractor.kt | 4 +-- .../interactor/GetDatabaseInfoInteractor.kt | 26 ++++++++++++++ .../domain/interactor/InteractorsModule.kt | 1 + .../domain/model/DatabaseInformation.kt | 7 ++++ .../main/java/f/cking/software/ui/UiModule.kt | 4 +-- .../ui/selectdevice/SelectDeviceViewModel.kt | 2 +- .../software/ui/settings/SettingsScreen.kt | 16 +++++++-- .../software/ui/settings/SettingsViewModel.kt | 5 +++ app/src/main/res/values-ru/strings.xml | 2 +- app/src/main/res/values/strings.xml | 6 +++- 15 files changed, 103 insertions(+), 25 deletions(-) create mode 100644 app/src/main/java/f/cking/software/domain/interactor/GetDatabaseInfoInteractor.kt create mode 100644 app/src/main/java/f/cking/software/domain/model/DatabaseInformation.kt diff --git a/app/src/main/java/f/cking/software/data/database/AppDatabase.kt b/app/src/main/java/f/cking/software/data/database/AppDatabase.kt index a0b2889..fa687fd 100644 --- a/app/src/main/java/f/cking/software/data/database/AppDatabase.kt +++ b/app/src/main/java/f/cking/software/data/database/AppDatabase.kt @@ -103,6 +103,13 @@ abstract class AppDatabase : RoomDatabase() { } } + suspend fun getDatabaseSize(context: Context): Long { + return withContext(Dispatchers.IO) { + val dbFile = File(context.getDatabasePath(openHelper.databaseName).toString()) + dbFile.length() + } + } + private fun testDatabase(name: String, context: Context) { val testDb = build(context, name) testDb.openHelper.writableDatabase.isDatabaseIntegrityOk @@ -232,10 +239,12 @@ abstract class AppDatabase : RoomDatabase() { ) """.trimIndent() ) - it.execSQL(""" + it.execSQL( + """ CREATE INDEX IF NOT EXISTS index_profile_detect_profile_id_trigger_time ON profile_detect(profile_id, trigger_time) - """.trimIndent()) + """.trimIndent() + ) } private fun migration( diff --git a/app/src/main/java/f/cking/software/data/database/dao/LocationDao.kt b/app/src/main/java/f/cking/software/data/database/dao/LocationDao.kt index 85864ce..db43aa9 100644 --- a/app/src/main/java/f/cking/software/data/database/dao/LocationDao.kt +++ b/app/src/main/java/f/cking/software/data/database/dao/LocationDao.kt @@ -6,6 +6,7 @@ import androidx.room.OnConflictStrategy import androidx.room.Query import f.cking.software.data.database.entity.DeviceToLocationEntity import f.cking.software.data.database.entity.LocationEntity +import kotlinx.coroutines.flow.Flow @Dao interface LocationDao { @@ -22,6 +23,9 @@ interface LocationDao { """) fun getAllLocationsByDeviceAddress(address: String, fromTime: Long = 0, toTime: Long = Long.MAX_VALUE): List + @Query("SELECT * FROM location") + fun observeAllLocations(): Flow> + @Insert(onConflict = OnConflictStrategy.REPLACE) fun saveLocation(locationEntity: LocationEntity) diff --git a/app/src/main/java/f/cking/software/data/helpers/BleFiltersProvider.kt b/app/src/main/java/f/cking/software/data/helpers/BleFiltersProvider.kt index ad4d65b..fb997b2 100644 --- a/app/src/main/java/f/cking/software/data/helpers/BleFiltersProvider.kt +++ b/app/src/main/java/f/cking/software/data/helpers/BleFiltersProvider.kt @@ -47,7 +47,7 @@ class BleFiltersProvider( suspend fun getKnownDevicesFilters(): List { return withContext(Dispatchers.Default) { - val allKnownDevices = getAllDevicesInteractor.execute() + val allKnownDevices = getAllDevicesInteractor.execute(withAirdropInfo = false) val lastSeenDevices = allKnownDevices .sortedByDescending { it.lastDetectTimeMs } diff --git a/app/src/main/java/f/cking/software/data/repo/DevicesRepository.kt b/app/src/main/java/f/cking/software/data/repo/DevicesRepository.kt index 070baad..6df1c44 100644 --- a/app/src/main/java/f/cking/software/data/repo/DevicesRepository.kt +++ b/app/src/main/java/f/cking/software/data/repo/DevicesRepository.kt @@ -26,17 +26,17 @@ class DevicesRepository( private val appleContactsDao = appDatabase.appleContactDao() private val lastBatch = MutableStateFlow(emptyList()) private val allDevices = deviceDao.observeAll() - .map { it.toDomainWithAirDrop() } + .map { it.toDomain(withAirdropInfo = true) } - suspend fun getDevices(): List { + suspend fun getDevices(withAirdropInfo: Boolean = false): List { return withContext(Dispatchers.IO) { - deviceDao.getAll().toDomainWithAirDrop() + deviceDao.getAll().toDomain(withAirdropInfo) } } suspend fun getPaginated(offset: Int, limit: Int): List { return withContext(Dispatchers.IO) { - deviceDao.getPaginated(offset, limit).toDomainWithAirDrop() + deviceDao.getPaginated(offset, limit).toDomain(withAirdropInfo = true) } } @@ -48,7 +48,7 @@ class DevicesRepository( emptyList() } else { val scanTime = lastDevice.lastDetectTimeMs - deviceDao.getByLastDetectTime(scanTime).toDomainWithAirDrop() + deviceDao.getByLastDetectTime(scanTime).toDomain(withAirdropInfo = true) } } } @@ -64,7 +64,7 @@ class DevicesRepository( suspend fun observeLastBatch(): StateFlow> { return lastBatch.apply { if (lastBatch.value.isEmpty()) { - notifyListeners() + notifyLastBatchListener() } } } @@ -73,14 +73,14 @@ class DevicesRepository( withContext(Dispatchers.IO) { saveDevices(devices) saveContacts(devices) - notifyListeners() + notifyLastBatchListener() } } suspend fun saveDevice(data: DeviceData) { withContext(Dispatchers.IO) { deviceDao.insert(data.toData()) - notifyListeners() + notifyLastBatchListener() } } @@ -88,7 +88,7 @@ class DevicesRepository( withContext(Dispatchers.IO) { val new = device.copy(lastFollowingDetectionTimeMs = detectionTime) deviceDao.insert(new.toData()) - notifyListeners() + notifyLastBatchListener() } } @@ -97,7 +97,7 @@ class DevicesRepository( addresses.splitToBatches(DatabaseUtils.getMaxSQLVariablesNumber()).forEach { addressesBatch -> deviceDao.deleteAllByAddress(addressesBatch) } - notifyListeners() + notifyLastBatchListener() } } @@ -115,7 +115,7 @@ class DevicesRepository( suspend fun getAllByAddresses(addresses: List): List { return withContext(Dispatchers.IO) { addresses.splitToBatches(DatabaseUtils.getMaxSQLVariablesNumber()).flatMap { - deviceDao.findAllByAddresses(addresses).toDomainWithAirDrop() + deviceDao.findAllByAddresses(addresses).toDomain(withAirdropInfo = true) } } } @@ -157,7 +157,7 @@ class DevicesRepository( } } - private suspend fun notifyListeners() { + private suspend fun notifyLastBatchListener() { coroutineScope { launch(Dispatchers.Default) { val data = getLastBatch() @@ -173,6 +173,16 @@ class DevicesRepository( } } + private suspend fun List.toDomain(withAirdropInfo: Boolean): List { + return withContext(Dispatchers.Default) { + if (withAirdropInfo) { + toDomainWithAirDrop() + } else { + map { it.toDomain() } + } + } + } + private suspend fun List.toDomainWithAirDrop(): List { return withContext(Dispatchers.Default) { val allRelatedContacts = withContext(Dispatchers.IO) { diff --git a/app/src/main/java/f/cking/software/domain/interactor/ClearGarbageInteractor.kt b/app/src/main/java/f/cking/software/domain/interactor/ClearGarbageInteractor.kt index fa0156f..ec9df0f 100644 --- a/app/src/main/java/f/cking/software/domain/interactor/ClearGarbageInteractor.kt +++ b/app/src/main/java/f/cking/software/domain/interactor/ClearGarbageInteractor.kt @@ -16,7 +16,7 @@ class ClearGarbageInteractor( suspend fun execute(): Int { return withContext(Dispatchers.Default) { - val devices = devicesRepository.getDevices() + val devices = devicesRepository.getDevices(withAirdropInfo = false) .asSequence() .filter { isGarbage(it) } .map { it.address } diff --git a/app/src/main/java/f/cking/software/domain/interactor/GetAllDevicesInteractor.kt b/app/src/main/java/f/cking/software/domain/interactor/GetAllDevicesInteractor.kt index 673a223..31e1949 100644 --- a/app/src/main/java/f/cking/software/domain/interactor/GetAllDevicesInteractor.kt +++ b/app/src/main/java/f/cking/software/domain/interactor/GetAllDevicesInteractor.kt @@ -7,7 +7,7 @@ class GetAllDevicesInteractor( private val devicesRepository: DevicesRepository, ) { - suspend fun execute(): List { - return devicesRepository.getDevices() + suspend fun execute(withAirdropInfo: Boolean = false): List { + return devicesRepository.getDevices(withAirdropInfo) } } \ No newline at end of file diff --git a/app/src/main/java/f/cking/software/domain/interactor/GetDatabaseInfoInteractor.kt b/app/src/main/java/f/cking/software/domain/interactor/GetDatabaseInfoInteractor.kt new file mode 100644 index 0000000..afc5c14 --- /dev/null +++ b/app/src/main/java/f/cking/software/domain/interactor/GetDatabaseInfoInteractor.kt @@ -0,0 +1,26 @@ +package f.cking.software.domain.interactor + +import f.cking.software.TheApp +import f.cking.software.data.database.AppDatabase +import f.cking.software.domain.model.DatabaseInformation +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map + +class GetDatabaseInfoInteractor( + private val database: AppDatabase, + private val application: TheApp, +) { + fun execute(): Flow { + return combine( + database.deviceDao().observeAll().map { it.size }, + database.locationDao().observeAllLocations().map { it.size } + ) { deviceCount, locationCount -> + DatabaseInformation( + sizeBytes = database.getDatabaseSize(application), + totalDevices = deviceCount, + totalGeotags = locationCount + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/f/cking/software/domain/interactor/InteractorsModule.kt b/app/src/main/java/f/cking/software/domain/interactor/InteractorsModule.kt index c7394e4..528cbd0 100644 --- a/app/src/main/java/f/cking/software/domain/interactor/InteractorsModule.kt +++ b/app/src/main/java/f/cking/software/domain/interactor/InteractorsModule.kt @@ -36,5 +36,6 @@ object InteractorsModule { factory { CheckBatchForRadarMatchesInteractor(get(), get(), get(), get(), get()) } factory { SaveOrMergeBatchInteractor(get(), get(), get(), get(), get(), get(), get()) } factory { FetchDeviceServiceInfo(get(), get()) } + factory { GetDatabaseInfoInteractor(get(), get()) } } } \ No newline at end of file diff --git a/app/src/main/java/f/cking/software/domain/model/DatabaseInformation.kt b/app/src/main/java/f/cking/software/domain/model/DatabaseInformation.kt new file mode 100644 index 0000000..006a065 --- /dev/null +++ b/app/src/main/java/f/cking/software/domain/model/DatabaseInformation.kt @@ -0,0 +1,7 @@ +package f.cking.software.domain.model + +data class DatabaseInformation( + val sizeBytes: Long, + val totalDevices: Int, + val totalGeotags: Int, +) \ No newline at end of file diff --git a/app/src/main/java/f/cking/software/ui/UiModule.kt b/app/src/main/java/f/cking/software/ui/UiModule.kt index a00cc7f..b4721a8 100644 --- a/app/src/main/java/f/cking/software/ui/UiModule.kt +++ b/app/src/main/java/f/cking/software/ui/UiModule.kt @@ -13,7 +13,7 @@ import f.cking.software.ui.selectmanufacturer.SelectManufacturerViewModel import f.cking.software.ui.settings.SettingsViewModel import f.cking.software.utils.navigation.Router import f.cking.software.utils.navigation.RouterImpl -import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.core.module.dsl.viewModel import org.koin.dsl.module object UiModule { @@ -22,7 +22,7 @@ object UiModule { single { get() } viewModel { MainViewModel(get(), get(), get(), get(), get(), get(), get()) } viewModel { DeviceListViewModel(get(), get(), get(), get(), get(), get(), get(), get(), get()) } - viewModel { SettingsViewModel(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) } + viewModel { SettingsViewModel(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) } viewModel { ProfilesListViewModel(get(), get()) } viewModel { ProfileDetailsViewModel(profileId = it[0], template = it[1], get(), get(), get(), get(), get()) } viewModel { SelectManufacturerViewModel(get()) } diff --git a/app/src/main/java/f/cking/software/ui/selectdevice/SelectDeviceViewModel.kt b/app/src/main/java/f/cking/software/ui/selectdevice/SelectDeviceViewModel.kt index eb9e3c3..f78a39f 100644 --- a/app/src/main/java/f/cking/software/ui/selectdevice/SelectDeviceViewModel.kt +++ b/app/src/main/java/f/cking/software/ui/selectdevice/SelectDeviceViewModel.kt @@ -38,7 +38,7 @@ class SelectDeviceViewModel( private fun refreshDevices() { viewModelScope.launch { loading = true - devices = devicesRepository.getDevices().asSequence() + devices = devicesRepository.getDevices(withAirdropInfo = false).asSequence() .filter { device -> searchStr.takeIf { it.isNotBlank() }?.let { searchStr -> (device.resolvedName?.contains(searchStr, true) ?: false) diff --git a/app/src/main/java/f/cking/software/ui/settings/SettingsScreen.kt b/app/src/main/java/f/cking/software/ui/settings/SettingsScreen.kt index df49352..eae0155 100644 --- a/app/src/main/java/f/cking/software/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/f/cking/software/ui/settings/SettingsScreen.kt @@ -1,5 +1,6 @@ package f.cking.software.ui.settings +import android.text.format.Formatter import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -21,6 +22,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle @@ -100,9 +102,19 @@ object SettingsScreen { @Composable private fun DatabaseBlock(viewModel: SettingsViewModel) { RoundedBox { - Text(text = stringResource(id = R.string.database_block_title), fontWeight = FontWeight.SemiBold) - Spacer(modifier = Modifier.height(4.dp)) + Text(text = stringResource(R.string.database_information), fontWeight = FontWeight.SemiBold) + Spacer(modifier = Modifier.height(8.dp)) + + val databaseInfo = viewModel.databaseInfo + if (databaseInfo != null) { + Text(text = stringResource(R.string.database_size, Formatter.formatFileSize(LocalContext.current, databaseInfo.sizeBytes))) + Text(text = stringResource(R.string.database_devices_count, databaseInfo.totalDevices.toString())) + Text(text = stringResource(R.string.database_locations_count, databaseInfo.totalGeotags.toString())) + Spacer(modifier = Modifier.height(12.dp)) + } + Text(text = stringResource(R.string.database_actions), fontWeight = FontWeight.SemiBold) + Spacer(modifier = Modifier.height(8.dp)) BackupDB(viewModel = viewModel) Spacer(modifier = Modifier.height(8.dp)) RestoreDB(viewModel = viewModel) diff --git a/app/src/main/java/f/cking/software/ui/settings/SettingsViewModel.kt b/app/src/main/java/f/cking/software/ui/settings/SettingsViewModel.kt index 607fc91..f3a3c26 100644 --- a/app/src/main/java/f/cking/software/ui/settings/SettingsViewModel.kt +++ b/app/src/main/java/f/cking/software/ui/settings/SettingsViewModel.kt @@ -11,6 +11,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import f.cking.software.BuildConfig import f.cking.software.R +import f.cking.software.collectAsState import f.cking.software.data.helpers.IntentHelper import f.cking.software.data.helpers.LocationProvider import f.cking.software.data.helpers.PermissionHelper @@ -19,6 +20,7 @@ import f.cking.software.data.repo.SettingsRepository import f.cking.software.domain.interactor.BackupDatabaseInteractor import f.cking.software.domain.interactor.ClearGarbageInteractor import f.cking.software.domain.interactor.CreateBackupFileInteractor +import f.cking.software.domain.interactor.GetDatabaseInfoInteractor import f.cking.software.domain.interactor.RestoreDatabaseInteractor import f.cking.software.domain.interactor.SaveReportInteractor import f.cking.software.domain.interactor.SelectBackupFileInteractor @@ -43,6 +45,7 @@ class SettingsViewModel( private val intentHelper: IntentHelper, private val permissionHelper: PermissionHelper, private val router: Router, + private val getDatabaseInfoInteractor: GetDatabaseInfoInteractor, ) : ViewModel() { var garbageRemovingInProgress: Boolean by mutableStateOf(false) @@ -55,6 +58,8 @@ class SettingsViewModel( var silentModeEnabled: Boolean by mutableStateOf(settingsRepository.getSilentMode()) var deepAnalysisEnabled: Boolean by mutableStateOf(settingsRepository.getEnableDeepAnalysis()) + val databaseInfo by getDatabaseInfoInteractor.execute().collectAsState(viewModelScope, null) + init { observeLocationData() observeSilentMode() diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index c488f3d..251e97b 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -81,7 +81,7 @@ Секретный кот Оффлайн режим Выключить любую сетевую коммуникацию от приложения. Может быть полезно чтобы экономить трафик или оставаться незаметным в сети. - Управление базой данных + Управление базой данных Настройки Оффлайн режим выключен. Вы можете включить его в настройках. Поиск производителя diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c9121ba..b6d1215 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -99,7 +99,11 @@ A secret cat photo Offline mode Turn off any network communications in the app. It might be helpful to save traffic. - Database actions + Database actions + Database information + Size: %s + Devices records: %s + Location records: %s App settings Offline mode is turned off. You can change it in the settings. Deep analysis