Skip to content

Commit ddeeeed

Browse files
authored
Merge pull request #88 from sameerasw/develop
Develop - Improved splash, UI without top toolbar, minified build, media player in main tab, progressive blur, fonts, remote lock, screenasver and brightness controls, agp update, onboarding, crash reporting and pitch black theme
2 parents 356b876 + be923d2 commit ddeeeed

83 files changed

Lines changed: 3575 additions & 1151 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,4 @@ replay_pid*
3434
app/release
3535
local.properties
3636
.vscode/launch.json
37+
build/reports/problems/problems-report.html

app/build.gradle.kts

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import org.gradle.api.JavaVersion.VERSION_11
22
import org.gradle.api.JavaVersion.VERSION_17
3+
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
34

45
plugins {
56
alias(libs.plugins.android.application)
6-
alias(libs.plugins.kotlin.android)
7+
alias(libs.plugins.google.ksp)
78
alias(libs.plugins.kotlin.compose)
8-
kotlin("kapt")
99
}
1010

1111
android {
@@ -16,15 +16,29 @@ android {
1616
applicationId = "com.sameerasw.airsync"
1717
minSdk = 30
1818
targetSdk = 36
19-
versionCode = 22
20-
versionName = "2.5.1"
19+
versionCode = 23
20+
versionName = "2.5.2"
2121

2222
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
2323
}
2424

2525
buildTypes {
26+
// optimized dev build
27+
// debug {
28+
// isMinifyEnabled = true
29+
// isShrinkResources = true
30+
// isDebuggable = false
31+
//
32+
// proguardFiles(
33+
// getDefaultProguardFile("proguard-android-optimize.txt"),
34+
// "proguard-rules.pro"
35+
// )
36+
// }
37+
// end
38+
2639
release {
27-
isMinifyEnabled = false
40+
isMinifyEnabled = true
41+
isShrinkResources = true
2842
proguardFiles(
2943
getDefaultProguardFile("proguard-android-optimize.txt"),
3044
"proguard-rules.pro"
@@ -35,9 +49,11 @@ android {
3549
sourceCompatibility = VERSION_11
3650
targetCompatibility = VERSION_17
3751
}
38-
kotlinOptions {
39-
jvmTarget = "17"
52+
kotlin {
53+
compilerOptions {
54+
jvmTarget.set(JvmTarget.JVM_17)
4055
}
56+
}
4157
buildFeatures {
4258
compose = true
4359
buildConfig = true
@@ -106,7 +122,7 @@ dependencies {
106122
// Room database for call history
107123
implementation(libs.androidx.room.runtime)
108124
implementation(libs.androidx.room.ktx)
109-
kapt(libs.androidx.room.compiler)
125+
ksp(libs.androidx.room.compiler)
110126

111127
// Phone number normalization
112128
implementation(libs.libphonenumber)
@@ -121,6 +137,11 @@ dependencies {
121137
// Google Play Review
122138
implementation(libs.play.review)
123139
implementation(libs.play.review.ktx)
140+
implementation(libs.sentry.android)
141+
142+
// Coil for image and GIF loading
143+
implementation("io.coil-kt:coil-compose:2.6.0")
144+
implementation("io.coil-kt:coil-gif:2.6.0")
124145

125146
testImplementation(libs.junit)
126147
androidTestImplementation(libs.androidx.junit)

app/proguard-rules.pro

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,15 @@
1818

1919
# If you keep the line number information, uncomment this to
2020
# hide the original source file name.
21-
#-renamesourcefileattribute SourceFile
21+
#-renamesourcefileattribute SourceFile
22+
23+
# Gson rules
24+
-keep class com.google.gson.** { *; }
25+
-keepattributes Signature
26+
-keepattributes *Annotation*
27+
28+
# Domain models
29+
-keep class com.sameerasw.airsync.domain.model.** { *; }
30+
31+
# Data Layer
32+
-keep class com.sameerasw.airsync.data.** { *; }

app/src/main/AndroidManifest.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
<uses-permission android:name="com.sameerasw.permission.ESSENTIALS_AIRSYNC_BRIDGE" />
4141

4242
<application
43+
android:name=".AirSyncApp"
4344
android:allowBackup="true"
4445
android:dataExtractionRules="@xml/data_extraction_rules"
4546
android:fullBackupContent="@xml/backup_rules"
@@ -50,6 +51,10 @@
5051
android:theme="@style/Theme.AirSync"
5152
android:networkSecurityConfig="@xml/network_security_config"
5253
tools:targetApi="36">
54+
55+
<!-- Disable Sentry auto-init as it's handled manually in AirSyncApp -->
56+
<meta-data android:name="io.sentry.auto-init" android:value="false" />
57+
5358
<activity
5459
android:name=".MainActivity"
5560
android:exported="true"
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.sameerasw.airsync
2+
3+
import android.app.Application
4+
import com.sameerasw.airsync.data.local.DataStoreManager
5+
import io.sentry.android.core.SentryAndroid
6+
import kotlinx.coroutines.flow.first
7+
import kotlinx.coroutines.runBlocking
8+
9+
class AirSyncApp : Application() {
10+
override fun onCreate() {
11+
super.onCreate()
12+
initSentry()
13+
}
14+
15+
private fun initSentry() {
16+
val dataStoreManager = DataStoreManager.getInstance(this)
17+
val isEnabled = runBlocking { dataStoreManager.getSentryReportingEnabled().first() }
18+
19+
if (!isEnabled) return
20+
21+
SentryAndroid.init(this) { options ->
22+
options.dsn = "https://cb9b0ead9e88e0818269e773cb662141@o4510996760887296.ingest.de.sentry.io/4511002261389392"
23+
options.isEnabled = true
24+
}
25+
}
26+
}

app/src/main/java/com/sameerasw/airsync/MainActivity.kt

Lines changed: 35 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,22 @@ import androidx.activity.result.contract.ActivityResultContracts
1515
import androidx.compose.foundation.Image
1616
import androidx.compose.foundation.layout.Row
1717
import androidx.compose.foundation.layout.Spacer
18+
import androidx.compose.foundation.layout.WindowInsets
1819
import androidx.compose.foundation.layout.fillMaxSize
1920
import androidx.compose.foundation.layout.padding
2021
import androidx.compose.foundation.layout.size
22+
import androidx.compose.foundation.layout.statusBars
2123
import androidx.compose.foundation.layout.width
24+
import androidx.compose.foundation.layout.windowInsetsPadding
2225
import androidx.compose.material.icons.rounded.HelpOutline
2326
import androidx.compose.material3.ExperimentalMaterial3Api
2427
import androidx.compose.material3.Icon
2528
import androidx.compose.material3.IconButton
2629
import androidx.compose.material3.IconButtonDefaults
27-
import androidx.compose.material3.LargeTopAppBar
2830
import androidx.compose.material3.MaterialTheme
2931
import androidx.compose.material3.Scaffold
3032
import androidx.compose.material3.Text
31-
import androidx.compose.material3.TopAppBarDefaults
32-
import androidx.compose.material3.rememberTopAppBarState
33+
import androidx.compose.material3.Surface
3334
import androidx.compose.runtime.LaunchedEffect
3435
import androidx.compose.runtime.collectAsState
3536
import androidx.compose.runtime.getValue
@@ -51,6 +52,7 @@ import com.sameerasw.airsync.data.local.DataStoreManager
5152
import com.sameerasw.airsync.presentation.ui.activities.QRScannerActivity
5253
import com.sameerasw.airsync.presentation.ui.screens.AirSyncMainScreen
5354
import com.sameerasw.airsync.ui.theme.AirSyncTheme
55+
import com.sameerasw.airsync.presentation.viewmodel.AirSyncViewModel
5456
import com.sameerasw.airsync.utils.AdbMdnsDiscovery
5557
import com.sameerasw.airsync.utils.ContentCaptureManager
5658
import com.sameerasw.airsync.utils.DevicePreviewResolver
@@ -187,7 +189,11 @@ class MainActivity : ComponentActivity() {
187189
splashScreen.setOnExitAnimationListener { splashScreenViewProvider ->
188190
try {
189191
val splashScreenView = splashScreenViewProvider.view
190-
val splashIcon = splashScreenViewProvider.iconView
192+
val splashIcon = try {
193+
splashScreenViewProvider.iconView
194+
} catch (e: Exception) {
195+
null
196+
}
191197

192198
// Retrieve last connected device in background while showing splash
193199
var deviceIconRes: Int? = null
@@ -233,7 +239,7 @@ class MainActivity : ComponentActivity() {
233239
fadeInIcon.doOnEnd {
234240
// Hold on device icon for 0.5s, then start outro animation
235241
try {
236-
splashIcon.postDelayed({
242+
splashScreenView.postDelayed({
237243
startOutroAnimation(
238244
splashScreenView,
239245
splashIcon,
@@ -274,47 +280,28 @@ class MainActivity : ComponentActivity() {
274280
fadeOutIcon.start()
275281
} else {
276282
// No device icon found, or splashIcon is null/not ImageView (OEM device compatibility)
277-
when {
278-
splashIcon == null -> {
279-
Log.w(
280-
"SplashScreen",
281-
"iconView is null - OEM device detected, skipping crossfade"
282-
)
283-
}
284-
285-
deviceIconRes == null -> {
286-
Log.d(
283+
// Proceed directly to outro after a brief hold
284+
try {
285+
splashScreenView.postDelayed({
286+
startOutroAnimation(
287+
splashScreenView,
288+
splashIcon,
289+
splashScreenViewProvider
290+
)
291+
}, 500)
292+
} catch (e: Exception) {
293+
Log.e(
287294
"MainActivity",
288-
"No device icon resource, proceeding with app icon"
289-
)
290-
}
291-
292-
else -> {
293-
Log.w(
294-
"SplashScreen",
295-
"iconView is not an ImageView - OEM device detected"
295+
"Error scheduling outro with no icon: ${e.message}",
296+
e
296297
)
297-
}
298-
}
299-
300-
// Proceed directly to outro after a brief hold
301-
try {
302-
splashIcon?.postDelayed({
298+
// Fallback: start outro immediately
303299
startOutroAnimation(
304300
splashScreenView,
305301
splashIcon,
306302
splashScreenViewProvider
307303
)
308-
}, 500)
309-
} catch (e: Exception) {
310-
Log.e(
311-
"MainActivity",
312-
"Error scheduling outro with no icon: ${e.message}",
313-
e
314-
)
315-
// Fallback: start outro immediately
316-
startOutroAnimation(splashScreenView, splashIcon, splashScreenViewProvider)
317-
}
304+
}
318305
}
319306
} catch (e: Exception) {
320307
// Fallback for any unexpected exceptions during animation
@@ -376,91 +363,25 @@ class MainActivity : ComponentActivity() {
376363
val isFromQrScan = data != null
377364

378365
setContent {
379-
AirSyncTheme {
366+
val viewModel: com.sameerasw.airsync.presentation.viewmodel.AirSyncViewModel =
367+
androidx.lifecycle.viewmodel.compose.viewModel {
368+
com.sameerasw.airsync.presentation.viewmodel.AirSyncViewModel.create(this@MainActivity)
369+
}
370+
val uiState by viewModel.uiState.collectAsState()
371+
372+
AirSyncTheme(pitchBlackTheme = uiState.isPitchBlackThemeEnabled) {
380373
val navController = rememberNavController()
381-
var showAboutDialog by remember { mutableStateOf(false) }
382-
var showHelpSheet by remember { mutableStateOf(false) }
383-
val scrollBehavior =
384-
TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState())
385-
var topBarTitle by remember { mutableStateOf("AirSync") }
386374

387375
Scaffold(
388376
modifier = Modifier
389-
.fillMaxSize()
390-
.nestedScroll(scrollBehavior.nestedScrollConnection),
377+
.fillMaxSize(),
391378
containerColor = MaterialTheme.colorScheme.surfaceContainer,
392379
contentWindowInsets = androidx.compose.foundation.layout.WindowInsets(
393380
0,
394381
0,
395382
0,
396383
0
397384
),
398-
topBar = {
399-
LargeTopAppBar(
400-
colors = TopAppBarDefaults.largeTopAppBarColors(
401-
containerColor = MaterialTheme.colorScheme.surfaceContainer,
402-
scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer,
403-
),
404-
modifier = Modifier.padding(horizontal = 8.dp),
405-
title = {
406-
Row(verticalAlignment = androidx.compose.ui.Alignment.CenterVertically) {
407-
// Dynamic icon based on last connected device category
408-
val ctx = androidx.compose.ui.platform.LocalContext.current
409-
val ds = remember(ctx) { DataStoreManager(ctx) }
410-
val lastDevice by ds.getLastConnectedDevice()
411-
.collectAsState(initial = null)
412-
val iconRes =
413-
com.sameerasw.airsync.utils.DeviceIconResolver.getIconRes(
414-
lastDevice
415-
)
416-
Image(
417-
painter = painterResource(id = iconRes),
418-
contentDescription = "AirSync Logo",
419-
modifier = Modifier.size(32.dp),
420-
contentScale = ContentScale.Fit,
421-
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.primary)
422-
)
423-
Spacer(modifier = Modifier.width(8.dp))
424-
Text(
425-
topBarTitle,
426-
style = MaterialTheme.typography.titleLarge,
427-
color = MaterialTheme.colorScheme.primary,
428-
maxLines = 1,
429-
)
430-
}
431-
},
432-
actions = {
433-
IconButton(
434-
onClick = { showHelpSheet = true },
435-
colors = IconButtonDefaults.iconButtonColors(
436-
containerColor = MaterialTheme.colorScheme.surfaceBright
437-
),
438-
modifier = Modifier.size(48.dp)
439-
) {
440-
Icon(
441-
imageVector = androidx.compose.material.icons.Icons.Rounded.HelpOutline,
442-
contentDescription = "Help",
443-
modifier = Modifier.size(32.dp)
444-
)
445-
}
446-
Spacer(modifier = Modifier.width(8.dp))
447-
IconButton(
448-
onClick = { showAboutDialog = true },
449-
colors = IconButtonDefaults.iconButtonColors(
450-
containerColor = MaterialTheme.colorScheme.surfaceBright
451-
),
452-
modifier = Modifier.size(48.dp)
453-
) {
454-
Icon(
455-
painter = painterResource(id = R.drawable.rounded_info_24),
456-
contentDescription = "About",
457-
modifier = Modifier.size(32.dp)
458-
)
459-
}
460-
},
461-
scrollBehavior = scrollBehavior
462-
)
463-
}
464385
) { innerPadding ->
465386
NavHost(
466387
navController = navController,
@@ -474,12 +395,7 @@ class MainActivity : ComponentActivity() {
474395
showConnectionDialog = isFromQrScan,
475396
pcName = pcName,
476397
isPlus = isPlus,
477-
symmetricKey = symmetricKey,
478-
showAboutDialog = showAboutDialog,
479-
onDismissAbout = { showAboutDialog = false },
480-
showHelpSheet = showHelpSheet,
481-
onDismissHelp = { showHelpSheet = false },
482-
onTitleChange = { topBarTitle = it }
398+
symmetricKey = symmetricKey
483399
)
484400
}
485401
}

0 commit comments

Comments
 (0)