Skip to content

Commit d573a01

Browse files
authored
Merge pull request #72 from sameerasw/develop
Develop - Connection redesign, Discovery, Notification priorities...
2 parents c4760b3 + 8799f13 commit d573a01

34 files changed

Lines changed: 2033 additions & 733 deletions

app/build.gradle.kts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ android {
1616
applicationId = "com.sameerasw.airsync"
1717
minSdk = 30
1818
targetSdk = 36
19-
versionCode = 20
20-
versionName = "2.4.0"
19+
versionCode = 21
20+
versionName = "2.5.0"
2121

2222
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
2323
}
@@ -40,6 +40,11 @@ android {
4040
}
4141
buildFeatures {
4242
compose = true
43+
buildConfig = true
44+
}
45+
46+
defaultConfig {
47+
buildConfigField("String", "MIN_MAC_APP_VERSION", "\"2.5.0\"")
4348
}
4449
}
4550

@@ -113,6 +118,10 @@ dependencies {
113118
// ML Kit barcode scanner (QR code only)
114119
implementation("com.google.mlkit:barcode-scanning:17.3.0")
115120

121+
// Google Play Review
122+
implementation(libs.play.review)
123+
implementation(libs.play.review.ktx)
124+
116125
testImplementation(libs.junit)
117126
androidTestImplementation(libs.androidx.junit)
118127
androidTestImplementation(libs.androidx.espresso.core)

app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,7 @@
5454
android:exported="true"
5555
android:label="@string/app_name"
5656
android:theme="@style/Theme.AirSync.Splash"
57-
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
58-
android:showWhenLocked="true"
59-
android:turnScreenOn="true">
57+
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden">
6058
<!-- Main launcher intent filter -->
6159
<intent-filter>
6260
<action android:name="android.intent.action.MAIN" />

app/src/main/java/com/sameerasw/airsync/data/local/DataStoreManager.kt

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,14 @@ class DataStoreManager(private val context: Context) {
6868
private val ESSENTIALS_CONNECTION_ENABLED = booleanPreferencesKey("essentials_connection_enabled")
6969

7070
private val DEFAULT_TAB = stringPreferencesKey("default_tab")
71+
private val FIRST_MAC_CONNECTION_TIME = longPreferencesKey("first_mac_connection_time")
72+
private val LAST_PROMPT_DISMISSED_VERSION = intPreferencesKey("last_prompt_dismissed_version")
73+
private val HAS_RATED_APP = booleanPreferencesKey("has_rated_app")
7174

7275

7376
// Call monitoring preferences
7477
private val CALL_SYNC_ENABLED = booleanPreferencesKey("call_sync_enabled")
78+
private val DEVICE_DISCOVERY_ENABLED = booleanPreferencesKey("device_discovery_enabled")
7579
private val LAST_CALL_SYNC_TIMESTAMP = longPreferencesKey("last_call_sync_timestamp")
7680
private val DEVICE_ID = stringPreferencesKey("device_id")
7781

@@ -284,6 +288,44 @@ class DataStoreManager(private val context: Context) {
284288
}
285289
}
286290

291+
suspend fun setFirstMacConnectionTime(time: Long) {
292+
context.dataStore.edit { prefs ->
293+
if (prefs[FIRST_MAC_CONNECTION_TIME] == null) {
294+
prefs[FIRST_MAC_CONNECTION_TIME] = time
295+
}
296+
}
297+
}
298+
299+
fun getFirstMacConnectionTime(): Flow<Long> {
300+
return context.dataStore.data.map { prefs ->
301+
prefs[FIRST_MAC_CONNECTION_TIME] ?: 0L
302+
}
303+
}
304+
305+
suspend fun setLastPromptDismissedVersion(version: Int) {
306+
context.dataStore.edit { prefs ->
307+
prefs[LAST_PROMPT_DISMISSED_VERSION] = version
308+
}
309+
}
310+
311+
fun getLastPromptDismissedVersion(): Flow<Int> {
312+
return context.dataStore.data.map { prefs ->
313+
prefs[LAST_PROMPT_DISMISSED_VERSION] ?: -1
314+
}
315+
}
316+
317+
suspend fun setHasRatedApp(hasRated: Boolean) {
318+
context.dataStore.edit { prefs ->
319+
prefs[HAS_RATED_APP] = hasRated
320+
}
321+
}
322+
323+
fun hasRatedApp(): Flow<Boolean> {
324+
return context.dataStore.data.map { prefs ->
325+
prefs[HAS_RATED_APP] ?: false
326+
}
327+
}
328+
287329
suspend fun saveLastConnectedDevice(device: ConnectedDevice) {
288330
context.dataStore.edit { preferences ->
289331
preferences[LAST_CONNECTED_PC_NAME] = device.name
@@ -774,6 +816,18 @@ class DataStoreManager(private val context: Context) {
774816
}
775817
}
776818

819+
suspend fun setDeviceDiscoveryEnabled(enabled: Boolean) {
820+
context.dataStore.edit { preferences ->
821+
preferences[DEVICE_DISCOVERY_ENABLED] = enabled
822+
}
823+
}
824+
825+
fun getDeviceDiscoveryEnabled(): Flow<Boolean> {
826+
return context.dataStore.data.map { preferences ->
827+
preferences[DEVICE_DISCOVERY_ENABLED] != false // Default to enabled
828+
}
829+
}
830+
777831
suspend fun setLastCallSyncTimestamp(timestamp: Long) {
778832
context.dataStore.edit { preferences ->
779833
preferences[LAST_CALL_SYNC_TIMESTAMP] = timestamp

app/src/main/java/com/sameerasw/airsync/data/repository/AirSyncRepositoryImpl.kt

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,4 +197,44 @@ class AirSyncRepositoryImpl(
197197
override fun getEssentialsConnectionEnabled(): Flow<Boolean> {
198198
return dataStoreManager.getEssentialsConnectionEnabled()
199199
}
200+
201+
override suspend fun setExpandNetworkingEnabled(enabled: Boolean) {
202+
dataStoreManager.setExpandNetworkingEnabled(enabled)
203+
}
204+
205+
override fun getExpandNetworkingEnabled(): Flow<Boolean> {
206+
return dataStoreManager.getExpandNetworkingEnabled()
207+
}
208+
209+
override suspend fun setDeviceDiscoveryEnabled(enabled: Boolean) {
210+
dataStoreManager.setDeviceDiscoveryEnabled(enabled)
211+
}
212+
213+
override fun getDeviceDiscoveryEnabled(): Flow<Boolean> {
214+
return dataStoreManager.getDeviceDiscoveryEnabled()
215+
}
216+
217+
override suspend fun setFirstMacConnectionTime(time: Long) {
218+
dataStoreManager.setFirstMacConnectionTime(time)
219+
}
220+
221+
override fun getFirstMacConnectionTime(): Flow<Long> {
222+
return dataStoreManager.getFirstMacConnectionTime()
223+
}
224+
225+
override suspend fun setLastPromptDismissedVersion(version: Int) {
226+
dataStoreManager.setLastPromptDismissedVersion(version)
227+
}
228+
229+
override fun getLastPromptDismissedVersion(): Flow<Int> {
230+
return dataStoreManager.getLastPromptDismissedVersion()
231+
}
232+
233+
override suspend fun setHasRatedApp(hasRated: Boolean) {
234+
dataStoreManager.setHasRatedApp(hasRated)
235+
}
236+
237+
override fun hasRatedApp(): Flow<Boolean> {
238+
return dataStoreManager.hasRatedApp()
239+
}
200240
}

app/src/main/java/com/sameerasw/airsync/domain/model/UiState.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,9 @@ data class UiState(
3737
val isClipboardHistoryEnabled: Boolean = true,
3838
val clipboardHistory: List<ClipboardEntry> = emptyList(),
3939
val defaultTab: String = "dynamic",
40-
val isEssentialsConnectionEnabled: Boolean = false
40+
val isEssentialsConnectionEnabled: Boolean = false,
41+
val activeIp: String? = null,
42+
val connectingDeviceId: String? = null,
43+
val isDeviceDiscoveryEnabled: Boolean = true,
44+
val shouldShowRatingPrompt: Boolean = false
4145
)

app/src/main/java/com/sameerasw/airsync/domain/repository/AirSyncRepository.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,20 @@ interface AirSyncRepository {
8686
// Essentials Bridge
8787
suspend fun setEssentialsConnectionEnabled(enabled: Boolean)
8888
fun getEssentialsConnectionEnabled(): Flow<Boolean>
89+
90+
// Expanded Networking
91+
suspend fun setExpandNetworkingEnabled(enabled: Boolean)
92+
fun getExpandNetworkingEnabled(): Flow<Boolean>
93+
94+
// Device discovery
95+
suspend fun setDeviceDiscoveryEnabled(enabled: Boolean)
96+
fun getDeviceDiscoveryEnabled(): Flow<Boolean>
97+
98+
// Rating card refined logic
99+
suspend fun setFirstMacConnectionTime(time: Long)
100+
fun getFirstMacConnectionTime(): Flow<Long>
101+
suspend fun setLastPromptDismissedVersion(version: Int)
102+
fun getLastPromptDismissedVersion(): Flow<Int>
103+
suspend fun setHasRatedApp(hasRated: Boolean)
104+
fun hasRatedApp(): Flow<Boolean>
89105
}

app/src/main/java/com/sameerasw/airsync/presentation/ui/components/SettingsView.kt

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ fun SettingsView(
7878
scope: CoroutineScope = androidx.compose.runtime.rememberCoroutineScope(),
7979
onSendMessage: (String) -> Unit = {},
8080
onExport: (String) -> Unit = {},
81-
onImport: () -> Unit = {}
81+
onImport: () -> Unit = {},
82+
onResetOnboarding: () -> Unit = {}
8283
) {
8384
val haptics = LocalHapticFeedback.current
8485

@@ -228,7 +229,9 @@ fun SettingsView(
228229
} catch (_: Exception) {
229230
emptyList()
230231
}
232+
val deviceId = com.sameerasw.airsync.utils.DeviceInfoUtil.getDeviceId(context)
231233
val message = com.sameerasw.airsync.utils.JsonUtil.createDeviceInfoJson(
234+
deviceId,
232235
deviceInfo.name,
233236
deviceInfo.localIp,
234237
uiState.port.toIntOrNull() ?: 6996,
@@ -240,12 +243,18 @@ fun SettingsView(
240243
onSendNotification = {
241244
val testNotification =
242245
com.sameerasw.airsync.utils.TestNotificationUtil.generateRandomNotification()
246+
247+
// Store ID for mock dismissal support
248+
com.sameerasw.airsync.utils.NotificationDismissalUtil.storeTestNotificationId(testNotification.id)
249+
243250
val message = com.sameerasw.airsync.utils.JsonUtil.createNotificationJson(
244251
testNotification.id,
245252
testNotification.title,
246253
testNotification.body,
247254
testNotification.appName,
248-
testNotification.packageName
255+
testNotification.packageName,
256+
testNotification.priority,
257+
testNotification.actions
249258
)
250259
onSendMessage(message)
251260
},
@@ -276,6 +285,9 @@ fun SettingsView(
276285
},
277286
onImportData = {
278287
onImport()
288+
},
289+
onResetOnboarding = {
290+
onResetOnboarding()
279291
}
280292
)
281293
}
@@ -287,7 +299,6 @@ fun SettingsView(
287299
viewModel.manualSyncAppIcons(context)
288300
},
289301
modifier = Modifier.fillMaxWidth(),
290-
shape = MaterialTheme.shapes.extraSmall,
291302
enabled = uiState.isConnected && !uiState.isIconSyncLoading
292303
) {
293304
if (uiState.isIconSyncLoading) {

app/src/main/java/com/sameerasw/airsync/presentation/ui/components/cards/ConnectionStatusCard.kt

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ import androidx.compose.foundation.layout.defaultMinSize
1010
import androidx.compose.foundation.layout.fillMaxSize
1111
import androidx.compose.foundation.layout.fillMaxWidth
1212
import androidx.compose.foundation.layout.padding
13+
import androidx.compose.foundation.layout.FlowRow
1314
import androidx.compose.foundation.shape.RoundedCornerShape
1415
import androidx.compose.material3.Card
1516
import androidx.compose.material3.CardDefaults
17+
import androidx.compose.material3.Surface
1618
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
1719
import androidx.compose.material3.Icon
1820
import androidx.compose.material3.LoadingIndicator
@@ -47,7 +49,7 @@ fun ConnectionStatusCard(
4749

4850
// Determine gradient color
4951
val gradientColor = when {
50-
isConnected -> Color(0xFF4CAF50) // Green
52+
isConnected -> MaterialTheme.colorScheme.primary
5153
isConnecting -> Color(0xFFFFC107) // Yellow
5254
else -> Color(0xFFF44336) // Red
5355
}
@@ -123,15 +125,27 @@ fun ConnectionStatusCard(
123125
}
124126
}
125127

126-
Row(
128+
FlowRow(
127129
modifier = Modifier.fillMaxWidth(),
128-
horizontalArrangement = Arrangement.SpaceBetween
130+
horizontalArrangement = Arrangement.spacedBy(8.dp),
131+
verticalArrangement = Arrangement.spacedBy(8.dp)
129132
) {
130-
Text(
131-
"${connectedDevice.ipAddress}:${connectedDevice.port}",
132-
style = MaterialTheme.typography.bodySmall,
133-
color = MaterialTheme.colorScheme.onSurfaceVariant
134-
)
133+
val ips = uiState.ipAddress.split(",").map { it.trim() }.filter { it.isNotEmpty() }
134+
ips.forEach { ip ->
135+
val isActive = ip == uiState.activeIp
136+
Surface(
137+
shape = RoundedCornerShape(12.dp),
138+
color = if (isActive) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surfaceVariant,
139+
modifier = Modifier.animateContentSize()
140+
) {
141+
Text(
142+
text = "$ip:${connectedDevice.port}",
143+
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
144+
style = MaterialTheme.typography.labelMedium,
145+
color = if (isActive) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onSurfaceVariant
146+
)
147+
}
148+
}
135149
}
136150
}
137151

app/src/main/java/com/sameerasw/airsync/presentation/ui/components/cards/DeveloperModeCard.kt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ fun DeveloperModeCard(
2929
onSendNotification: () -> Unit,
3030
onSendDeviceStatus: () -> Unit,
3131
onExportData: () -> Unit,
32-
onImportData: () -> Unit
32+
onImportData: () -> Unit,
33+
onResetOnboarding: () -> Unit
3334
) {
3435
val haptics = LocalHapticFeedback.current
3536

@@ -121,6 +122,17 @@ fun DeveloperModeCard(
121122
}
122123
}
123124

125+
Button(
126+
onClick = {
127+
HapticUtil.performClick(haptics)
128+
onResetOnboarding()
129+
},
130+
modifier = Modifier.fillMaxWidth(),
131+
enabled = !isLoading
132+
) {
133+
Text("Reset Onboarding")
134+
}
135+
124136
}
125137

126138
// Removed preview/response display to avoid crashes from large/encoded payloads

app/src/main/java/com/sameerasw/airsync/presentation/ui/components/cards/DeviceInfoCard.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ fun DeviceInfoCard(
2828
Text("My Android", style = MaterialTheme.typography.titleMedium)
2929
Spacer(modifier = Modifier.height(8.dp))
3030
Text("Local IP: $localIp", style = MaterialTheme.typography.bodyMedium)
31-
Text("Wake-up Ports: HTTP 8888, UDP 8889", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
3231

3332
Spacer(modifier = Modifier.height(8.dp))
3433
OutlinedTextField(

0 commit comments

Comments
 (0)