Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
d7f4152
version: Update
sameerasw Feb 18, 2026
9c15f78
fixup: Resolve file overwrite and formatting issues on transfer [2/2]
Mudit200408 Feb 28, 2026
3eccc1b
Merge pull request #85 from Mudit200408/main
sameerasw Feb 28, 2026
f540050
feat: Updated splash screen
sameerasw Feb 28, 2026
b3861a1
feat: Improved bottom toolbar from essentials
sameerasw Mar 1, 2026
0c8a332
feat: No top app bar
sameerasw Mar 1, 2026
4fa995e
feat: Tab content re-organization, remote and airsync
sameerasw Mar 1, 2026
835db13
feat: Minify and build optimization
sameerasw Mar 1, 2026
a594bc3
feat: media player in main tab
sameerasw Mar 1, 2026
b2ef651
fix: Out of sync QS tile to conneciton status
sameerasw Mar 4, 2026
746037d
fix: Media player stays even when disconnected
sameerasw Mar 4, 2026
39ba605
feat: Progressive blur in UI
sameerasw Mar 4, 2026
029a939
feat: Updated response remote view
sameerasw Mar 4, 2026
a2eda85
feat: Google sans flex
sameerasw Mar 4, 2026
15f8a34
fix: splash screen branding background colro fix
sameerasw Mar 4, 2026
38d9af7
feat: Remote lock screen
sameerasw Mar 4, 2026
28ced7a
feat: Screensaver
sameerasw Mar 4, 2026
a18265d
feat: Screensaver
sameerasw Mar 4, 2026
6e15308
feat: brightness changing
sameerasw Mar 4, 2026
6b30575
fix: Disable dev optimization
sameerasw Mar 7, 2026
9e971df
chore: AGP update and fixes
sameerasw Mar 7, 2026
734ac64
fix: Remote screen paddings
sameerasw Mar 7, 2026
6b68b4d
feat: Settings cattegorization - permissions, help and tiles section
sameerasw Mar 7, 2026
bca9373
feat: Power saving aware progressive blur and settings refactor
sameerasw Mar 7, 2026
2e487dd
feat: Pitch black theme
sameerasw Mar 7, 2026
3299610
feat: more settings re-organization and refactor
sameerasw Mar 7, 2026
9c66e00
feat: Moved icon sync to developer options
sameerasw Mar 7, 2026
7a023a5
feat: Sentry crash reporting
sameerasw Mar 7, 2026
f09ba16
feat: Onboarding
sameerasw Mar 7, 2026
bf7cb96
fix: Developer options styling
sameerasw Mar 7, 2026
59a7820
feat: Connection settings and QS tiles in setup
sameerasw Mar 7, 2026
405e9ee
feat: permissions in Onboarding
sameerasw Mar 7, 2026
311fdfe
feat: Setup updates
sameerasw Mar 7, 2026
be923d2
fix: splash crash from QS launch
sameerasw Mar 7, 2026
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ replay_pid*
app/release
local.properties
.vscode/launch.json
build/reports/problems/problems-report.html
37 changes: 29 additions & 8 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import org.gradle.api.JavaVersion.VERSION_11
import org.gradle.api.JavaVersion.VERSION_17
import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.google.ksp)
alias(libs.plugins.kotlin.compose)
kotlin("kapt")
}

android {
Expand All @@ -16,15 +16,29 @@ android {
applicationId = "com.sameerasw.airsync"
minSdk = 30
targetSdk = 36
versionCode = 22
versionName = "2.5.1"
versionCode = 23
versionName = "2.5.2"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
// optimized dev build
// debug {
// isMinifyEnabled = true
// isShrinkResources = true
// isDebuggable = false
//
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
// }
// end

release {
isMinifyEnabled = false
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
Expand All @@ -35,9 +49,11 @@ android {
sourceCompatibility = VERSION_11
targetCompatibility = VERSION_17
}
kotlinOptions {
jvmTarget = "17"
kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
}
}
buildFeatures {
compose = true
buildConfig = true
Expand Down Expand Up @@ -106,7 +122,7 @@ dependencies {
// Room database for call history
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.room.ktx)
kapt(libs.androidx.room.compiler)
ksp(libs.androidx.room.compiler)

// Phone number normalization
implementation(libs.libphonenumber)
Expand All @@ -121,6 +137,11 @@ dependencies {
// Google Play Review
implementation(libs.play.review)
implementation(libs.play.review.ktx)
implementation(libs.sentry.android)

// Coil for image and GIF loading
implementation("io.coil-kt:coil-compose:2.6.0")
implementation("io.coil-kt:coil-gif:2.6.0")

testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
Expand Down
13 changes: 12 additions & 1 deletion app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,15 @@

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
#-renamesourcefileattribute SourceFile

# Gson rules
-keep class com.google.gson.** { *; }
-keepattributes Signature
-keepattributes *Annotation*

# Domain models
-keep class com.sameerasw.airsync.domain.model.** { *; }

# Data Layer
-keep class com.sameerasw.airsync.data.** { *; }
5 changes: 5 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
<uses-permission android:name="com.sameerasw.permission.ESSENTIALS_AIRSYNC_BRIDGE" />

<application
android:name=".AirSyncApp"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
Expand All @@ -50,6 +51,10 @@
android:theme="@style/Theme.AirSync"
android:networkSecurityConfig="@xml/network_security_config"
tools:targetApi="36">

<!-- Disable Sentry auto-init as it's handled manually in AirSyncApp -->
<meta-data android:name="io.sentry.auto-init" android:value="false" />

<activity
android:name=".MainActivity"
android:exported="true"
Expand Down
26 changes: 26 additions & 0 deletions app/src/main/java/com/sameerasw/airsync/AirSyncApp.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.sameerasw.airsync

import android.app.Application
import com.sameerasw.airsync.data.local.DataStoreManager
import io.sentry.android.core.SentryAndroid
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking

class AirSyncApp : Application() {
override fun onCreate() {
super.onCreate()
initSentry()
}

private fun initSentry() {
val dataStoreManager = DataStoreManager.getInstance(this)
val isEnabled = runBlocking { dataStoreManager.getSentryReportingEnabled().first() }

if (!isEnabled) return

SentryAndroid.init(this) { options ->
options.dsn = "https://cb9b0ead9e88e0818269e773cb662141@o4510996760887296.ingest.de.sentry.io/4511002261389392"
options.isEnabled = true
}
}
}
154 changes: 35 additions & 119 deletions app/src/main/java/com/sameerasw/airsync/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,22 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material.icons.rounded.HelpOutline
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.LargeTopAppBar
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.material3.Surface
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
Expand All @@ -51,6 +52,7 @@ import com.sameerasw.airsync.data.local.DataStoreManager
import com.sameerasw.airsync.presentation.ui.activities.QRScannerActivity
import com.sameerasw.airsync.presentation.ui.screens.AirSyncMainScreen
import com.sameerasw.airsync.ui.theme.AirSyncTheme
import com.sameerasw.airsync.presentation.viewmodel.AirSyncViewModel
import com.sameerasw.airsync.utils.AdbMdnsDiscovery
import com.sameerasw.airsync.utils.ContentCaptureManager
import com.sameerasw.airsync.utils.DevicePreviewResolver
Expand Down Expand Up @@ -187,7 +189,11 @@ class MainActivity : ComponentActivity() {
splashScreen.setOnExitAnimationListener { splashScreenViewProvider ->
try {
val splashScreenView = splashScreenViewProvider.view
val splashIcon = splashScreenViewProvider.iconView
val splashIcon = try {
splashScreenViewProvider.iconView
} catch (e: Exception) {
null
}

// Retrieve last connected device in background while showing splash
var deviceIconRes: Int? = null
Expand Down Expand Up @@ -233,7 +239,7 @@ class MainActivity : ComponentActivity() {
fadeInIcon.doOnEnd {
// Hold on device icon for 0.5s, then start outro animation
try {
splashIcon.postDelayed({
splashScreenView.postDelayed({
startOutroAnimation(
splashScreenView,
splashIcon,
Expand Down Expand Up @@ -274,47 +280,28 @@ class MainActivity : ComponentActivity() {
fadeOutIcon.start()
} else {
// No device icon found, or splashIcon is null/not ImageView (OEM device compatibility)
when {
splashIcon == null -> {
Log.w(
"SplashScreen",
"iconView is null - OEM device detected, skipping crossfade"
)
}

deviceIconRes == null -> {
Log.d(
// Proceed directly to outro after a brief hold
try {
splashScreenView.postDelayed({
startOutroAnimation(
splashScreenView,
splashIcon,
splashScreenViewProvider
)
}, 500)
} catch (e: Exception) {
Log.e(
"MainActivity",
"No device icon resource, proceeding with app icon"
)
}

else -> {
Log.w(
"SplashScreen",
"iconView is not an ImageView - OEM device detected"
"Error scheduling outro with no icon: ${e.message}",
e
)
}
}

// Proceed directly to outro after a brief hold
try {
splashIcon?.postDelayed({
// Fallback: start outro immediately
startOutroAnimation(
splashScreenView,
splashIcon,
splashScreenViewProvider
)
}, 500)
} catch (e: Exception) {
Log.e(
"MainActivity",
"Error scheduling outro with no icon: ${e.message}",
e
)
// Fallback: start outro immediately
startOutroAnimation(splashScreenView, splashIcon, splashScreenViewProvider)
}
}
}
} catch (e: Exception) {
// Fallback for any unexpected exceptions during animation
Expand Down Expand Up @@ -376,91 +363,25 @@ class MainActivity : ComponentActivity() {
val isFromQrScan = data != null

setContent {
AirSyncTheme {
val viewModel: com.sameerasw.airsync.presentation.viewmodel.AirSyncViewModel =
androidx.lifecycle.viewmodel.compose.viewModel {
com.sameerasw.airsync.presentation.viewmodel.AirSyncViewModel.create(this@MainActivity)
}
val uiState by viewModel.uiState.collectAsState()

AirSyncTheme(pitchBlackTheme = uiState.isPitchBlackThemeEnabled) {
val navController = rememberNavController()
var showAboutDialog by remember { mutableStateOf(false) }
var showHelpSheet by remember { mutableStateOf(false) }
val scrollBehavior =
TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState())
var topBarTitle by remember { mutableStateOf("AirSync") }

Scaffold(
modifier = Modifier
.fillMaxSize()
.nestedScroll(scrollBehavior.nestedScrollConnection),
.fillMaxSize(),
containerColor = MaterialTheme.colorScheme.surfaceContainer,
contentWindowInsets = androidx.compose.foundation.layout.WindowInsets(
0,
0,
0,
0
),
topBar = {
LargeTopAppBar(
colors = TopAppBarDefaults.largeTopAppBarColors(
containerColor = MaterialTheme.colorScheme.surfaceContainer,
scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer,
),
modifier = Modifier.padding(horizontal = 8.dp),
title = {
Row(verticalAlignment = androidx.compose.ui.Alignment.CenterVertically) {
// Dynamic icon based on last connected device category
val ctx = androidx.compose.ui.platform.LocalContext.current
val ds = remember(ctx) { DataStoreManager(ctx) }
val lastDevice by ds.getLastConnectedDevice()
.collectAsState(initial = null)
val iconRes =
com.sameerasw.airsync.utils.DeviceIconResolver.getIconRes(
lastDevice
)
Image(
painter = painterResource(id = iconRes),
contentDescription = "AirSync Logo",
modifier = Modifier.size(32.dp),
contentScale = ContentScale.Fit,
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.primary)
)
Spacer(modifier = Modifier.width(8.dp))
Text(
topBarTitle,
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.primary,
maxLines = 1,
)
}
},
actions = {
IconButton(
onClick = { showHelpSheet = true },
colors = IconButtonDefaults.iconButtonColors(
containerColor = MaterialTheme.colorScheme.surfaceBright
),
modifier = Modifier.size(48.dp)
) {
Icon(
imageVector = androidx.compose.material.icons.Icons.Rounded.HelpOutline,
contentDescription = "Help",
modifier = Modifier.size(32.dp)
)
}
Spacer(modifier = Modifier.width(8.dp))
IconButton(
onClick = { showAboutDialog = true },
colors = IconButtonDefaults.iconButtonColors(
containerColor = MaterialTheme.colorScheme.surfaceBright
),
modifier = Modifier.size(48.dp)
) {
Icon(
painter = painterResource(id = R.drawable.rounded_info_24),
contentDescription = "About",
modifier = Modifier.size(32.dp)
)
}
},
scrollBehavior = scrollBehavior
)
}
) { innerPadding ->
NavHost(
navController = navController,
Expand All @@ -474,12 +395,7 @@ class MainActivity : ComponentActivity() {
showConnectionDialog = isFromQrScan,
pcName = pcName,
isPlus = isPlus,
symmetricKey = symmetricKey,
showAboutDialog = showAboutDialog,
onDismissAbout = { showAboutDialog = false },
showHelpSheet = showHelpSheet,
onDismissHelp = { showHelpSheet = false },
onTitleChange = { topBarTitle = it }
symmetricKey = symmetricKey
)
}
}
Expand Down
Loading