Skip to content
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
3 changes: 3 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ dependencies {
implementation(libs.sentry.android)
implementation(libs.androidx.graphics.shapes)

// AutoUpdater
implementation("com.github.CSAbhiOnline:AutoUpdater:1.0.1")

// Media3 for Live Wallpaper
implementation(libs.androidx.media3.exoplayer)
implementation(libs.androidx.media3.common)
Expand Down
8 changes: 8 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1030,6 +1030,14 @@
</intent-filter>
</receiver>

<receiver
android:name=".services.receivers.DownloadUpdateReceiver"
android:exported="false">
<intent-filter>
<action android:name="com.sameerasw.essentials.ACTION_DOWNLOAD_UPDATE" />
</intent-filter>
</receiver>

<receiver
android:name=".services.automation.receivers.TimeAutomationReceiver"
android:exported="false">
Expand Down
58 changes: 57 additions & 1 deletion app/src/main/java/com/sameerasw/essentials/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,23 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.graphics.BlendMode
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import androidx.compose.ui.graphics.toArgb
import com.airbnb.lottie.LottieProperty
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.LottieConstants
import com.airbnb.lottie.compose.animateLottieCompositionAsState
import com.airbnb.lottie.compose.rememberLottieComposition
import com.airbnb.lottie.compose.rememberLottieDynamicProperties
import com.airbnb.lottie.compose.rememberLottieDynamicProperty
import androidx.compose.material3.Badge
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.FloatingToolbarDefaults
import androidx.compose.material3.FloatingToolbarExitDirection.Companion.Bottom
Expand Down Expand Up @@ -226,6 +243,7 @@ class MainActivity : AppCompatActivity() {
var showUpdateSheet by remember { mutableStateOf(false) }
var showInstructionsSheet by remember { mutableStateOf(false) }
val updateInfo by viewModel.updateInfo
val isUpdateAvailable by viewModel.isUpdateAvailable

var showGitHubAuthSheet by remember { mutableStateOf(false) }
var showNewAutomationSheet by remember { mutableStateOf(false) }
Expand All @@ -250,7 +268,9 @@ class MainActivity : AppCompatActivity() {
if (!viewModel.isPostNotificationsEnabled.value) {
viewModel.requestNotificationPermission(this@MainActivity)
}
viewModel.checkForUpdates(context)
if (!viewModel.isUpdateAvailable.value) {
viewModel.checkForUpdates(context, manual = true)
}
updatesViewModel.loadTrackedRepos(context)
}

Expand Down Expand Up @@ -581,6 +601,42 @@ class MainActivity : AppCompatActivity() {
}
}

if (isUpdateAvailable && (currentTab == DIYTabs.ESSENTIALS || currentTab == DIYTabs.FREEZE)) {
Badge(
modifier = Modifier
.align(Alignment.TopEnd)
.offset(x = 6.dp, y = (-6).dp)
.size(28.dp),
containerColor = MaterialTheme.colorScheme.error,
contentColor = MaterialTheme.colorScheme.onError
) {
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.update_motion))
val progress by animateLottieCompositionAsState(
composition = composition,
iterations = LottieConstants.IterateForever
)
val onErrorColor = MaterialTheme.colorScheme.onError
val dynamicProperties = rememberLottieDynamicProperties(
rememberLottieDynamicProperty(
property = LottieProperty.COLOR_FILTER,
value = PorterDuffColorFilter(
onErrorColor.toArgb(),
PorterDuff.Mode.SRC_ATOP
),
keyPath = arrayOf("**")
)
)

LottieAnimation(
composition = composition,
progress = { progress },
dynamicProperties = dynamicProperties,
modifier = Modifier
.fillMaxSize()
.padding(2.dp)
)
}
}
}
}
)
Expand Down
147 changes: 78 additions & 69 deletions app/src/main/java/com/sameerasw/essentials/SettingsActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ fun SettingsContent(
var isPermissionsExpanded by remember { mutableStateOf(false) }
var showUpdateSheet by remember { mutableStateOf(false) }
val updateInfo by viewModel.updateInfo
val isUpdateAvailable by viewModel.isUpdateAvailable
val isAutoUpdateEnabled by viewModel.isAutoUpdateEnabled
val isUpdateNotificationEnabled by viewModel.isUpdateNotificationEnabled
val isPreReleaseCheckEnabled by viewModel.isPreReleaseCheckEnabled
Expand Down Expand Up @@ -418,6 +419,83 @@ fun SettingsContent(
)
}

// Updates Section
Text(
text = "Updates",
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(start = 16.dp, top = 16.dp, bottom = 8.dp),
color = MaterialTheme.colorScheme.onSurfaceVariant
)

RoundedCardContainer {
IconToggleItem(
iconRes = R.drawable.rounded_mobile_check_24,
title = "Auto check for updates",
description = "Check for updates at app launch",
isChecked = isAutoUpdateEnabled,
onCheckedChange = { viewModel.setAutoUpdateEnabled(it, context) }
)
IconToggleItem(
iconRes = R.drawable.rounded_experiment_24,
title = context.getString(R.string.check_pre_releases_label),
description = context.getString(R.string.check_pre_releases_desc),
isChecked = isPreReleaseCheckEnabled,
onCheckedChange = { viewModel.setPreReleaseCheckEnabled(it, context) }
)
IconToggleItem(
iconRes = R.drawable.rounded_notifications_unread_24,
title = "Notify for new updates",
description = "Show a notification when an update is found",
isChecked = isUpdateNotificationEnabled,
onCheckedChange = { viewModel.setUpdateNotificationEnabled(it, context) }
)

Row(
modifier = Modifier
.fillMaxWidth()
.background(
MaterialTheme.colorScheme.surfaceBright,
shape = MaterialTheme.shapes.extraSmall
)
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
val buttonText = if (isUpdateAvailable && !updateInfo?.versionName.isNullOrEmpty()) {
stringResource(R.string.action_update_to_version, updateInfo?.versionName ?: "")
} else {
stringResource(R.string.action_check_for_updates)
}

val buttonIconRes = R.drawable.rounded_mobile_arrow_down_24

Button(
onClick = {
HapticUtil.performVirtualKeyHaptic(view)
viewModel.checkForUpdates(context, manual = true)
showUpdateSheet = true
},
modifier = Modifier
.fillMaxWidth()
.height(52.dp)
.padding(horizontal = 4.dp),
contentPadding = PaddingValues(vertical = 12.dp, horizontal = 16.dp)
) {
Icon(
painter = painterResource(id = buttonIconRes),
contentDescription = null,
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = buttonText,
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
}
}
}

Spacer(modifier = Modifier.height(8.dp))

// App Settings Section
Expand Down Expand Up @@ -879,75 +957,6 @@ fun SettingsContent(
}
}

// Updates Section
Text(
text = "Updates",
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(start = 16.dp, top = 16.dp, bottom = 8.dp),
color = MaterialTheme.colorScheme.onSurfaceVariant
)

RoundedCardContainer {
IconToggleItem(
iconRes = R.drawable.rounded_mobile_check_24,
title = "Auto check for updates",
description = "Check for updates at app launch",
isChecked = isAutoUpdateEnabled,
onCheckedChange = { viewModel.setAutoUpdateEnabled(it, context) }
)
IconToggleItem(
iconRes = R.drawable.rounded_experiment_24,
title = context.getString(R.string.check_pre_releases_label),
description = context.getString(R.string.check_pre_releases_desc),
isChecked = isPreReleaseCheckEnabled,
onCheckedChange = { viewModel.setPreReleaseCheckEnabled(it, context) }
)
IconToggleItem(
iconRes = R.drawable.rounded_notifications_unread_24,
title = "Notify for new updates",
description = "Show a notification when an update is found",
isChecked = isUpdateNotificationEnabled,
onCheckedChange = { viewModel.setUpdateNotificationEnabled(it, context) }
)

Row(
modifier = Modifier
.fillMaxWidth()
.background(
MaterialTheme.colorScheme.surfaceBright,
shape = MaterialTheme.shapes.extraSmall
)
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {

// Check for updates button
Button(
onClick = {
HapticUtil.performVirtualKeyHaptic(view)
viewModel.checkForUpdates(context, manual = true)
showUpdateSheet = true
},
modifier = Modifier
.fillMaxWidth()
.padding(6.dp)
) {
Icon(
painter = painterResource(id = R.drawable.rounded_mobile_arrow_down_24),
contentDescription = null,
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(8.dp))
Text(
"Check for updates",
fontWeight = androidx.compose.ui.text.font.FontWeight.Bold
)
}

}
}


Spacer(modifier = Modifier.height(16.dp))

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,44 @@
package com.sameerasw.essentials.data.repository

import android.content.Context
import com.google.gson.Gson
import com.sameerasw.essentials.domain.model.UpdateInfo
import com.sameerasw.essentials.utils.AutoUpdateManagerHelper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.net.HttpURLConnection
import java.net.URL

class UpdateRepository {

suspend fun checkForUpdates(
context: Context,
isPreReleaseCheckEnabled: Boolean,
currentVersion: String
): UpdateInfo? = withContext(Dispatchers.IO) {
try {
val autoUpdateHelper = AutoUpdateManagerHelper(context)
val updateFeatures = autoUpdateHelper.checkForUpdate("https://sameerasw.com/essentials-update.json")

if (updateFeatures != null && updateFeatures.latestversion.isNotEmpty()) {
val latestVersion = updateFeatures.latestversion
val hasUpdate = isNewerVersion(currentVersion, latestVersion)
return@withContext UpdateInfo(
versionName = latestVersion,
releaseNotes = updateFeatures.changelog,
downloadUrl = updateFeatures.apk_url,
releaseUrl = if (updateFeatures.changelog.startsWith("http")) updateFeatures.changelog else "https://github.com/sameerasw/essentials/releases",
isUpdateAvailable = hasUpdate
)
}
} catch (e: Exception) {
e.printStackTrace()
}

checkForUpdatesFromGitHub(isPreReleaseCheckEnabled, currentVersion)
}

private suspend fun checkForUpdatesFromGitHub(
isPreReleaseCheckEnabled: Boolean,
currentVersion: String
): UpdateInfo? = withContext(Dispatchers.IO) {
Expand All @@ -20,7 +50,7 @@ class UpdateRepository {
}

val url = URL(urlString)
val connection = url.openConnection() as java.net.HttpURLConnection
val connection = url.openConnection() as HttpURLConnection

if (connection.responseCode != 200) {
return@withContext null
Expand All @@ -32,7 +62,6 @@ class UpdateRepository {
val releases = Gson().fromJson(releaseData, Array<Any>::class.java)
.filterIsInstance<Map<String, Any>>()

// Find the true latest release using SemanticVersion comparison
releases.maxByOrNull { rel ->
val tagName = (rel["tag_name"] as? String)?.removePrefix("v") ?: "0.0.0"
SemanticVersion.parse(tagName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,7 @@ fun initPermissionRegistry() {

// Notification Snoozing feature
PermissionRegistry.register("WRITE_SECURE_SETTINGS", R.string.feat_notification_snoozing_title)

// Install unknown packages feature
PermissionRegistry.register("REQUEST_INSTALL_PACKAGES", R.string.tab_app_updates_title)
}
Loading