Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix some UI problems #107

Merged
merged 5 commits into from
Feb 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ android {

// This version code will be applied only for F-Droid builds, for other builds version code will be generated by gradle dynamically
// For some reason F-Droid requires version code to be hardcoded in the build.gradle file
versionCode = 1705859021
versionName = "0.22.6-beta"
versionCode = 1705859023
versionName = "0.22.7-beta"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ object BleScanErrorMapper {
fun map(errorCode: Int): String {
return when (errorCode) {
ScanCallback.SCAN_FAILED_ALREADY_STARTED -> "Scan already started"
ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED -> "Application registration failed"
ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED -> "Application registration failed. Possible solution is to restart the device."
ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED -> "Feature unsupported"
ScanCallback.SCAN_FAILED_INTERNAL_ERROR -> "Internal error"
ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES -> "Out of hardware resources"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ abstract class FilterChecker<T : RadarProfile.Filter>(
if (useCache() && cacheValue != null
&& System.currentTimeMillis() - cacheValue.time < powerModeHelper.powerMode(useCached = true).filterCacheExpirationTime
) {
// Timber.d("Cache hit for $key")
return cacheValue.value
}
val result = checkInternal(deviceData, filter)
Expand All @@ -39,6 +38,6 @@ abstract class FilterChecker<T : RadarProfile.Filter>(
)

companion object {
private const val MAX_CACHE_SIZE = 5000
private const val MAX_CACHE_SIZE = 5_000
}
}
95 changes: 48 additions & 47 deletions app/src/main/java/f/cking/software/service/BgScanService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.android.ext.android.inject
import timber.log.Timber
import java.util.concurrent.atomic.AtomicInteger


class BgScanService : Service() {
Expand All @@ -43,7 +44,7 @@ class BgScanService : Service() {
private val saveReportInteractor: SaveReportInteractor by inject()

private val handler = Handler(Looper.getMainLooper())
private var failureScanCounter: Int = 0
private var failureScanCounter: AtomicInteger = AtomicInteger(0)
private var locationDisabledWasReported: Boolean = false
private var bluetoothDisabledWasReported: Boolean = false
private val nextScanRunnable = Runnable {
Expand All @@ -68,12 +69,10 @@ class BgScanService : Service() {
}

private fun handleError(exception: Throwable) {
failureScanCounter++

reportError(exception)

if (failureScanCounter >= MAX_FAILURE_SCANS_TO_CLOSE) {
reportError(RuntimeException("Ble Scan service was stopped after $MAX_FAILURE_SCANS_TO_CLOSE errors"))
if (failureScanCounter.incrementAndGet() >= MAX_FAILURE_SCANS_TO_CLOSE) {
reportError(RuntimeException("Ble Scan service has been stopped after $MAX_FAILURE_SCANS_TO_CLOSE errors"))
stopSelf()
} else {
scheduleNextScan()
Expand Down Expand Up @@ -105,7 +104,7 @@ class BgScanService : Service() {
reportError(IllegalStateException("BLE Service is started but permissins are not granted"))
stopSelf()
},
onPermissionGranted = {
onPermissionGranted = {
locationProvider.startLocationFetching()
scan()
}
Expand Down Expand Up @@ -133,7 +132,7 @@ class BgScanService : Service() {
private fun scan() {
scope.launch {
try {
bleScannerHelper.scan(scanListener = bleListener,)
bleScannerHelper.scan(scanListener = bleListener)
} catch (e: BleScannerHelper.BluetoothIsNotInitialized) {
handleBleIsTurnedOffError()
notificationsHelper.updateNotification(
Expand All @@ -150,58 +149,60 @@ class BgScanService : Service() {

private fun handleScanResult(batch: List<BleScanDevice>) {
scope.launch {

val notificationContent: NotificationsHelper.ServiceNotificationContent =
if (batch.isEmpty() && !locationProvider.isLocationAvailable() && !locationDisabledWasReported) {
notificationsHelper.notifyLocationIsTurnedOff()
reportError(IllegalStateException("The BLE scanner did not return anything. This may happen if geolocation is turned off at the system level. Location access is required to work with BLE on Android."))
locationDisabledWasReported = true
NotificationsHelper.ServiceNotificationContent.LocationIsTurnedOff
} else if (batch.isEmpty() && !bleScannerHelper.isBluetoothEnabled()) {
handleBleIsTurnedOffError()
NotificationsHelper.ServiceNotificationContent.BluetoothIsTurnedOff
} else if (batch.isNotEmpty()) {
locationDisabledWasReported = false
bluetoothDisabledWasReported = false


try {
val analyseResult = analyseScanBatchInteractor.execute(batch)
withContext(Dispatchers.Default) {
saveScanBatchInteractor.execute(batch)
}

withContext(Dispatchers.Main) {
handleAnalysResult(analyseResult)
}

failureScanCounter = 0

if (analyseResult.knownDevicesCount > 0) {
NotificationsHelper.ServiceNotificationContent.KnownDevicesAround(analyseResult.knownDevicesCount)
} else {
NotificationsHelper.ServiceNotificationContent.TotalDevicesAround(batch.size)
}
} catch (exception: Throwable) {
handleError(exception)
NotificationsHelper.ServiceNotificationContent.NoDataYet
}
} else {
NotificationsHelper.ServiceNotificationContent.NoDataYet
}
val notificationContent: NotificationsHelper.ServiceNotificationContent = when {
batch.isEmpty() && !locationProvider.isLocationAvailable() && !locationDisabledWasReported -> handleLocationDisabled()
batch.isEmpty() && !bleScannerHelper.isBluetoothEnabled() -> handleBleIsTurnedOffError()
batch.isNotEmpty() -> handleNonEmptyBatch(batch)
else -> NotificationsHelper.ServiceNotificationContent.NoDataYet
}

notificationsHelper.updateNotification(notificationContent, createCloseServiceIntent(this@BgScanService))

scheduleNextScan()
}
}

private fun handleBleIsTurnedOffError() {
private fun handleLocationDisabled(): NotificationsHelper.ServiceNotificationContent {
notificationsHelper.notifyLocationIsTurnedOff()
reportError(IllegalStateException("The BLE scanner did not return anything. This may happen if geolocation is turned off at the system level. Location access is required to work with BLE on Android."))
locationDisabledWasReported = true
return NotificationsHelper.ServiceNotificationContent.LocationIsTurnedOff
}

private fun handleBleIsTurnedOffError(): NotificationsHelper.ServiceNotificationContent {
if (!bluetoothDisabledWasReported) {
notificationsHelper.notifyBluetoothIsTurnedOff()
reportError(BleScannerHelper.BluetoothIsNotInitialized())
bluetoothDisabledWasReported = true
}
return NotificationsHelper.ServiceNotificationContent.BluetoothIsTurnedOff
}

private suspend fun handleNonEmptyBatch(batch: List<BleScanDevice>): NotificationsHelper.ServiceNotificationContent {
locationDisabledWasReported = false
bluetoothDisabledWasReported = false

return try {
val analyseResult = analyseScanBatchInteractor.execute(batch)
withContext(Dispatchers.Default) {
saveScanBatchInteractor.execute(batch)
}

withContext(Dispatchers.Main) {
handleAnalysResult(analyseResult)
}

failureScanCounter.set(0)

if (analyseResult.knownDevicesCount > 0) {
NotificationsHelper.ServiceNotificationContent.KnownDevicesAround(analyseResult.knownDevicesCount)
} else {
NotificationsHelper.ServiceNotificationContent.TotalDevicesAround(batch.size)
}
} catch (exception: Throwable) {
handleError(exception)
NotificationsHelper.ServiceNotificationContent.NoDataYet
}
}

private fun handleProfileCheckingResult(profiles: List<CheckProfileDetectionInteractor.ProfileResult>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ object FilterScreen {
Spacer(modifier = Modifier.width(2.dp))
ClearIcon {
filter.toDate = null
filter.toDate = null
filter.toTime = null
}
}
}
Expand Down
32 changes: 16 additions & 16 deletions app/src/main/java/f/cking/software/ui/filter/FilterType.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@ package f.cking.software.ui.filter
import androidx.annotation.StringRes
import f.cking.software.R

enum class FilterType(@StringRes val displayNameRes: Int) {
NAME(R.string.filter_by_name),
ADDRESS(R.string.filter_by_address),
BY_FIRST_DETECTION(R.string.filter_by_first_detection_period),
BY_LAST_DETECTION(R.string.filter_by_last_detection_period),
BY_IS_FAVORITE(R.string.filter_by_is_favorite),
BY_MANUFACTURER(R.string.filter_by_manufacturer),
BY_MIN_DETECTION_TIME(R.string.filter_by_min_lost_period),
AIRDROP_CONTACT(R.string.filter_apple_airdrop_contact),
IS_FOLLOWING(R.string.filter_device_is_following_me),
BY_DEVICE_LOCATION(R.string.filter_device_location),
BY_USER_LOCATION(R.string.filter_user_location),
BY_TAG(R.string.filter_by_tag),
BY_LOGIC_ANY(R.string.filter_any_of),
BY_LOGIC_ALL(R.string.filter_all_of),
BY_LOGIC_NOT(R.string.filter_not),
enum class FilterType(@StringRes val displayNameRes: Int, @StringRes val displayDescription: Int) {
BY_LOGIC_ANY(R.string.filter_any_of, R.string.filter_any_of_description),
BY_LOGIC_ALL(R.string.filter_all_of, R.string.filter_all_of_description),
BY_LOGIC_NOT(R.string.filter_not, R.string.filter_not_description),
NAME(R.string.filter_by_name, R.string.filter_by_name_description),
ADDRESS(R.string.filter_by_address, R.string.filter_by_address_description),
BY_TAG(R.string.filter_by_tag, R.string.filter_by_tag_description),
BY_MIN_DETECTION_TIME(R.string.filter_by_min_lost_period, R.string.filter_by_min_lost_period_description),
BY_FIRST_DETECTION(R.string.filter_by_first_detection_period, R.string.filter_by_first_detection_period_description),
BY_LAST_DETECTION(R.string.filter_by_last_detection_period, R.string.filter_by_last_detection_period_description),
BY_IS_FAVORITE(R.string.filter_by_is_favorite, R.string.filter_by_is_favorite_description),
BY_MANUFACTURER(R.string.filter_by_manufacturer, R.string.filter_by_manufacturer_description),
IS_FOLLOWING(R.string.filter_device_is_following_me, R.string.filter_device_is_following_me_description),
BY_DEVICE_LOCATION(R.string.filter_device_location, R.string.filter_device_location_description),
BY_USER_LOCATION(R.string.filter_user_location, R.string.filter_user_location_description),
AIRDROP_CONTACT(R.string.filter_apple_airdrop_contact, R.string.filter_apple_airdrop_contact_description),
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package f.cking.software.ui.filter

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.MaterialTheme
Expand All @@ -11,6 +14,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.vanpra.composematerialdialogs.MaterialDialogState
Expand All @@ -27,7 +31,10 @@ object SelectFilterTypeScreen {
ThemedDialog(
dialogState = dialogState,
buttons = {
negativeButton(stringResource(R.string.cancel), textStyle = TextStyle(color = MaterialTheme.colorScheme.onSurface)) { dialogState.hide() }
negativeButton(
stringResource(R.string.cancel),
textStyle = TextStyle(color = MaterialTheme.colorScheme.onSurface)
) { dialogState.hide() }
}
) {
LazyColumn {
Expand All @@ -50,11 +57,22 @@ object SelectFilterTypeScreen {
.fillMaxWidth()
.clickable { onClickListener.invoke() }
) {
Text(
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
text = stringResource(item.displayNameRes),
fontSize = 18.sp
)
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp)
) {
Text(
text = stringResource(item.displayNameRes),
fontSize = 18.sp
)
Spacer(modifier = Modifier.height(2.dp))
Text(
text = stringResource(item.displayDescription),
fontSize = 14.sp,
fontWeight = FontWeight.Light,
)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ import org.osmdroid.views.MapView
import java.time.LocalDate
import java.time.LocalTime
import kotlin.math.abs
import kotlin.random.Random

@Composable
fun rememberDateDialog(
Expand Down Expand Up @@ -507,7 +508,7 @@ private val colorsDark = listOf(
@Composable
fun colorByHash(hash: Int): Color {
val colors = if (isSystemInDarkTheme()) colorsDark else colorsLight
return colors[abs(hash % colors.size)]
return colors[abs(Random(hash).nextInt() % colors.size)]
}

@Composable
Expand Down
15 changes: 15 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -97,20 +97,35 @@
<!-- Select filter -->
<string name="select_filter">Select filter</string>
<string name="filter_by_name">By name</string>
<string name="filter_by_name_description">Triggers when the device name matches</string>
<string name="filter_by_address">By address</string>
<string name="filter_by_address_description">Triggers when the device address matches</string>
<string name="filter_by_first_detection_period">By first detection period</string>
<string name="filter_by_first_detection_period_description">Triggers if the found device was first time detected in the selected period</string>
<string name="filter_by_last_detection_period">By last detection period</string>
<string name="filter_by_last_detection_period_description">Triggers if the found device was last time detected in the selected period</string>
<string name="filter_by_is_favorite">By is favorite</string>
<string name="filter_by_is_favorite_description">Triggers if the device is marked as a favorite</string>
<string name="filter_by_manufacturer">By manufacturer</string>
<string name="filter_by_manufacturer_description">Triggers when the device manufacturer matches</string>
<string name="filter_by_min_lost_period">By min lost period</string>
<string name="filter_by_min_lost_period_description">Triggers if the device was lost for the selected period. Might be helpful to avoid constant profile detection if the matched device is online for a long time</string>
<string name="filter_apple_airdrop_contact">Apple airdrop contact</string>
<string name="filter_apple_airdrop_contact_description">[DEPRECATED] Triggers when the device is an Apple device and the airdrop contact matches</string>
<string name="filter_device_is_following_me">Device is following me</string>
<string name="filter_device_is_following_me_description">Triggers if the device is following you during your move for a selected period of time.</string>
<string name="filter_any_of">Any of</string>
<string name="filter_any_of_description">Logical operator OR with group of filters inside</string>
<string name="filter_all_of">All of</string>
<string name="filter_all_of_description">Logical operator AND with group of filters inside</string>
<string name="filter_not">Not</string>
<string name="filter_not_description">Logical operator NOT</string>
<string name="filter_device_location">By device location</string>
<string name="filter_device_location_description">Triggers when the device has been previously seen in the particular location in the selected time period</string>
<string name="filter_user_location">By your location</string>
<string name="filter_user_location_description">Triggers if you are currently in a particular location. It might be helpful to avoid some filters triggering if you are at home or at the office</string>
<string name="filter_by_tag">By tag</string>
<string name="filter_by_tag_description">Triggers if the device has a selected tag</string>

<!-- Profiles list -->
<string name="create_new">Create new</string>
Expand Down
Loading