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
9 changes: 8 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<queries>
<intent>
<action android:name="android.service.wallpaper.CROP_AND_SET_WALLPAPER" />
<data android:mimeType="image/*" />
</intent>
</queries>

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.SET_WALLPAPER" />
Expand Down Expand Up @@ -29,7 +36,7 @@
tools:node="remove" />

<provider
android:name="androidx.core.content.FileProvider"
android:name=".UttamFileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
Expand Down
42 changes: 0 additions & 42 deletions app/src/main/google-services.json

This file was deleted.

5 changes: 5 additions & 0 deletions app/src/main/kotlin/com/ratik/uttam/UttamFileProvider.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.ratik.uttam

import androidx.core.content.FileProvider

class UttamFileProvider : FileProvider()
2 changes: 1 addition & 1 deletion app/src/main/kotlin/com/ratik/uttam/data/dao/PhotoDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class PhotoDao @Inject constructor(private val sharedPreferences: SharedPreferen
editor.putString("photographerName", photo.photographer.name)
editor.putString("photographerUsername", photo.photographer.username)
editor.putString("photographerProfileUrl", photo.photographer.profileUrl)
editor.apply()
check(editor.commit()) { "Could not persist wallpaper details" }
}

fun getPhoto(): Photo? {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
package com.ratik.uttam.data.extensions

import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.flow.Flow

/**
* Implements callbacks for stages of flow collection that allows for maximising unit test code
* coverage.
*/
suspend inline fun <T : Any> Flow<T>.collectBy(
suspend fun <T : Any> Flow<T>.collectBy(
onStart: () -> Unit = {},
crossinline onEach: (T) -> Unit = { _ -> },
onEach: suspend (T) -> Unit = { _ -> },
onError: (Throwable) -> Unit = { _ -> },
) {
try {
onStart()
collect { item -> onEach(item) }
} catch (e: Exception) {
onError(e)
} catch (exception: CancellationException) {
throw exception
} catch (exception: Exception) {
onError(exception)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.ratik.uttam.data.storage

import android.app.WallpaperManager
import android.app.WallpaperManager.FLAG_SYSTEM
import android.content.Context
import com.ratik.uttam.core.DispatcherProvider
import com.ratik.uttam.domain.WallpaperSetter
import kotlinx.coroutines.withContext
import java.io.File
import javax.inject.Inject

internal class AndroidWallpaperSetter @Inject constructor(
context: Context,
private val dispatcherProvider: DispatcherProvider,
) : WallpaperSetter {
private val wallpaperManager = WallpaperManager.getInstance(context)

override suspend fun setHomeScreen(wallpaperPath: String): Result<Unit> =
withContext(dispatcherProvider.io) {
runCatching {
check(wallpaperManager.isWallpaperSupported) {
"Wallpapers are not supported for this user"
}
check(wallpaperManager.isSetWallpaperAllowed) {
"Setting wallpapers is disabled for this user"
}

val wallpaperFile = File(wallpaperPath)
check(wallpaperFile.isFile && wallpaperFile.length() > 0) {
"Wallpaper file is unavailable"
}

wallpaperFile.inputStream().buffered().use { inputStream ->
wallpaperManager.setStream(inputStream, null, true, FLAG_SYSTEM)
}
Unit
}
}
}
Original file line number Diff line number Diff line change
@@ -1,64 +1,77 @@
package com.ratik.uttam.data.storage

import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import com.ratik.uttam.R
import com.ratik.uttam.core.DispatcherProvider
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import java.io.File
import java.io.FileOutputStream
import java.net.HttpURLConnection
import java.net.URL
import java.io.IOException
import javax.inject.Inject

class WallpaperDownloader @Inject constructor(
private val dispatcherProvider: DispatcherProvider,
private val httpClient: OkHttpClient,
context: Context,
) {
private val appCacheFolder =
File(context.filesDir, context.getString(R.string.app_name).lowercase())

suspend fun downloadWallpaper(fileName: String, wallpaperUrl: String): String? {
return withContext(dispatcherProvider.io) {
val bitmap: Bitmap?
suspend fun downloadWallpaper(fileName: String, wallpaperUrl: String): String =
withContext(dispatcherProvider.io) {
ensureCacheFolderExists()

val destination = File(appCacheFolder, "$fileName.jpg")
val temporaryFile = File.createTempFile(fileName, ".tmp", appCacheFolder)
val request = Request.Builder().url(wallpaperUrl).build()

try {
val url = URL(wallpaperUrl)
val connection: HttpURLConnection = url.openConnection() as HttpURLConnection
connection.doInput = true
connection.connect()
val input = connection.inputStream
bitmap = BitmapFactory.decodeStream(input)
saveBitmapToInternalStorage(fileName, bitmap)
} catch (e: Exception) {
e.printStackTrace()
throw e
httpClient.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
throw IOException("Wallpaper download failed with HTTP ${response.code}")
}
val body = response.body ?: throw IOException("Wallpaper download returned no data")
body.byteStream().use { input ->
temporaryFile.outputStream().buffered().use { output -> input.copyTo(output) }
}
}

if (temporaryFile.length() == 0L) {
throw IOException("Wallpaper download returned an empty file")
}
if (!temporaryFile.renameTo(destination)) {
throw IOException("Could not finalize wallpaper download")
}

destination.absolutePath
} finally {
temporaryFile.delete()
}
}

fun deleteFiles(filePaths: Collection<String>) {
filePaths.forEach { filePath -> File(filePath).delete() }
}

fun clearCacheFolder() {
fun cleanStaleFilesExcept(retainedFilePaths: Set<String>) {
if (appCacheFolder.exists()) {
appCacheFolder.listFiles()?.forEach { file -> file.delete() }
val staleBefore = System.currentTimeMillis() - STALE_FILE_AGE_MILLIS
appCacheFolder.listFiles()
?.filter { file ->
file.absolutePath !in retainedFilePaths && file.lastModified() < staleBefore
}
?.forEach { file -> file.delete() }
}
}

private fun saveBitmapToInternalStorage(fileName: String, bitmap: Bitmap?): String? {
return bitmap?.let {
if (!appCacheFolder.exists()) {
appCacheFolder.mkdirs()
}
val file = File(appCacheFolder, "$fileName.jpg")
val outputStream: FileOutputStream
try {
outputStream = FileOutputStream(file)
it.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
outputStream.close()
file.absolutePath
} catch (e: Exception) {
e.printStackTrace()
throw e
}
private fun ensureCacheFolderExists() {
if (!appCacheFolder.exists() && !appCacheFolder.mkdirs()) {
throw IOException("Could not create wallpaper storage")
}
}

private companion object {
const val STALE_FILE_AGE_MILLIS = 4 * 60 * 60 * 1000L
}
}
9 changes: 4 additions & 5 deletions app/src/main/kotlin/com/ratik/uttam/di/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ package com.ratik.uttam.di
import android.app.Application
import android.content.Context
import android.content.SharedPreferences
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.ratik.uttam.R
import com.ratik.uttam.core.ErrorHandler
import com.ratik.uttam.core.ErrorHandlerImpl
import com.ratik.uttam.core.StringProvider
import com.ratik.uttam.core.StringProviderImpl
import com.ratik.uttam.util.NotificationHelper.Companion.CHANNEL_ID
import com.ratik.uttam.data.storage.AndroidWallpaperSetter
import com.ratik.uttam.domain.WallpaperSetter
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
Expand All @@ -37,9 +37,8 @@ object AppModule {
)

@Provides
fun provideNotificationCompatBuilder(context: Context): NotificationCompat.Builder {
return NotificationCompat.Builder(context, CHANNEL_ID)
}
internal fun provideWallpaperSetter(wallpaperSetter: AndroidWallpaperSetter): WallpaperSetter =
wallpaperSetter

@Provides
fun provideNotificationManagerCompat(context: Context): NotificationManagerCompat {
Expand Down
Loading
Loading