Skip to content

Commit

Permalink
Started the migration to jetpack compose for the image optimizer, cor…
Browse files Browse the repository at this point in the history
…utines, etc
  • Loading branch information
D4rK7355608 committed Jul 11, 2024
1 parent dc1e51e commit ee7eecc
Show file tree
Hide file tree
Showing 16 changed files with 665 additions and 429 deletions.
2 changes: 1 addition & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ android {
applicationId = "com.d4rk.cleaner"
minSdk = 26
targetSdk = 34
versionCode = 92
versionCode = 93
versionName = "2.0.0"
archivesName = "${applicationId}-v${versionName}"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.d4rk.cleaner.data.model.ui.imageoptimizer

import com.d4rk.cleaner.R

enum class CompressionLevel(val stringRes: Int, val defaultPercentage: Int) {
LOW(R.string.low, 25),
MEDIUM(R.string.medium, 50),
HIGH(R.string.high, 75)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.d4rk.cleaner.data.model.ui.imageoptimizer

import android.net.Uri

data class ImageOptimizerState(
val selectedImageUri: Uri? = null,
val compressedImageUri: Uri? = null,
val isLoading: Boolean = false,
val quickCompressValue: Int = 50,
val fileSizeKB: Int = 0,
val manualWidth: Int = 0,
val manualHeight: Int = 0,
val manualQuality: Int = 50,
val currentTab: Int = 0
)
Original file line number Diff line number Diff line change
@@ -1,247 +1,40 @@
package com.d4rk.cleaner.ui.imageoptimizer.imageoptimizer

import android.content.Context
import android.graphics.Bitmap
import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Bundle
import android.os.Environment
import android.provider.DocumentsContract
import android.provider.MediaStore
import android.view.View
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import androidx.lifecycle.lifecycleScope
import com.bumptech.glide.Glide
import com.d4rk.cleaner.R
import com.d4rk.cleaner.adapters.ImageOptimizationPagerAdapter
import com.d4rk.cleaner.databinding.ActivityImageOptimizerBinding
import com.d4rk.cleaner.ui.imageoptimizer.imageoptimizer.tabs.FileSizeFragment
import com.d4rk.cleaner.ui.imageoptimizer.imageoptimizer.tabs.ManualModeFragment
import com.d4rk.cleaner.ui.imageoptimizer.imageoptimizer.tabs.QuickCompressFragment
import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.MobileAds
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.tabs.TabLayoutMediator
import id.zelory.compressor.Compressor
import id.zelory.compressor.constraint.format
import id.zelory.compressor.constraint.quality
import id.zelory.compressor.constraint.resolution
import id.zelory.compressor.constraint.size
import kotlinx.coroutines.Dispatchers
import com.d4rk.cleaner.ui.settings.display.theme.style.AppTheme
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File

class ImageOptimizerActivity : AppCompatActivity() {
private lateinit var binding: ActivityImageOptimizerBinding
private var actualImageFile: File? = null
private lateinit var viewModel: ImageOptimizerViewModel
private var compressedImageFile: File? = null
private var isCompressing = false
private val optimizedPicturesDirectory =
File(Environment.getExternalStorageDirectory(), "Pictures/Optimized Pictures").apply {
if (!exists()) {
mkdirs()
}
}
private val viewModel: ImageOptimizerViewModel by viewModels()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityImageOptimizerBinding.inflate(layoutInflater)
setContentView(binding.root)
val adapter = ImageOptimizationPagerAdapter(this)
binding.viewPager.adapter = adapter
binding.progressBar.alpha = 0f
MobileAds.initialize(this)
binding.adView.loadAd(AdRequest.Builder().build())
if (!intent.hasExtra("imageUri")) {
Snackbar.make(binding.root, getString(R.string.snack_no_image), Snackbar.LENGTH_SHORT)
.show()
finish()
return
}
val imageUri = Uri.parse(intent.getStringExtra("imageUri"))
Glide.with(this).load(imageUri).into(binding.imageView)
actualImageFile = getPath(this@ImageOptimizerActivity, imageUri)?.let { File(it) }
viewModel = ViewModelProvider(this)[ImageOptimizerViewModel::class.java]
viewModel.compressionLevelLiveData.observe(this) { compressionLevel ->
compressImageQuickCompress(actualImageFile, compressionLevel)
}
binding.buttonCompressImage.setOnClickListener {
when (binding.viewPager.currentItem) {
0 -> {
val quickCompressFragment =
supportFragmentManager.findFragmentByTag("f0") as? QuickCompressFragment
val compressionLevel = quickCompressFragment?.getCurrentCompressionLevel() ?: 50
compressImageQuickCompress(actualImageFile, compressionLevel)
}

1 -> {
val fileSizeFragment =
supportFragmentManager.findFragmentByTag("f1") as? FileSizeFragment
val targetSizeKB = fileSizeFragment?.getCurrentFileSizeKB() ?: -1
if (targetSizeKB > 0) {
compressImageByFileSize(actualImageFile, targetSizeKB)
} else {
val snackbar = Snackbar.make(
binding.root,
getString(R.string.snack_validate_file),
Snackbar.LENGTH_LONG
)
snackbar.setAction(android.R.string.ok) {
snackbar.dismiss()
}
snackbar.show()
}
}

2 -> {
val manualModeFragment =
supportFragmentManager.findFragmentByTag("f2") as? ManualModeFragment
val (width, height, quality) = manualModeFragment?.getCurrentCompressionSettings()
?: Triple(0, 0, 0)
compressImageManualMode(width, height, quality)
}
}
}
binding.viewPager.adapter = adapter
TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position ->
when (position) {
0 -> tab.text = getString(R.string.quick_compress)
1 -> tab.text = getString(R.string.file_size)
2 -> tab.text = getString(R.string.manual)
}
}.attach()
}

private fun compressImageManualMode(width: Int, height: Int, quality: Int) {
if (compressedImageFile != null || isCompressing) {
return
}
binding.progressBar.visibility = View.VISIBLE
lifecycleScope.launch(Dispatchers.IO) {
val compressedImageFile = withContext(Dispatchers.IO) {
if (actualImageFile != null) {
Compressor.compress(this@ImageOptimizerActivity, actualImageFile!!) {
resolution(width, height)
quality(quality)
}
} else {
null
}
}
withContext(Dispatchers.Main) {
updateImageView(compressedImageFile)
binding.progressBar.visibility = View.GONE
compressedImageFile?.let { saveImage(it) }
isCompressing = false
}
}
}

private fun compressImageByFileSize(imageFile: File?, targetSizeKB: Int) {
if (compressedImageFile != null || isCompressing) {
return
}
binding.progressBar.visibility = View.VISIBLE
lifecycleScope.launch(Dispatchers.IO) {
val compressedImageFile = withContext(Dispatchers.IO) {
if (imageFile != null) {
Compressor.compress(this@ImageOptimizerActivity, imageFile) {
format(Bitmap.CompressFormat.JPEG)
size((targetSizeKB * 1024).toLong())
}
} else {
null
}
}
withContext(Dispatchers.Main) {
updateImageView(compressedImageFile)
binding.progressBar.visibility = View.GONE
compressedImageFile?.let { saveImage(it) }
isCompressing = false
enableEdgeToEdge()
val selectedImageUriString = intent.getStringExtra("selectedImageUri")
if (!selectedImageUriString.isNullOrEmpty()) {
lifecycleScope.launch {
viewModel.onImageSelected(Uri.parse(selectedImageUriString))
}
}
}

private fun compressImageQuickCompress(actualImageFile: File?, compressionLevel: Int) {
if (compressedImageFile != null || isCompressing) {
return
}
binding.progressBar.visibility = View.VISIBLE
lifecycleScope.launch(Dispatchers.IO) {
val compressedImageFile = withContext(Dispatchers.IO) {
if (actualImageFile != null) {
Compressor.compress(this@ImageOptimizerActivity, actualImageFile) {
format(Bitmap.CompressFormat.JPEG)
quality(compressionLevel)
}
} else {
null
setContent {
AppTheme {
Surface(
modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background
) {
ImageOptimizerComposable(this@ImageOptimizerActivity, viewModel)
}
}
withContext(Dispatchers.Main) {
updateImageView(compressedImageFile)
binding.progressBar.visibility = View.GONE
compressedImageFile?.let { saveImage(it) }
isCompressing = false
}
}
}

private fun updateImageView(compressedImageFile: File?) {
compressedImageFile?.let {
val uri = Uri.fromFile(it)
Glide.with(this).load(uri).into(binding.imageView)
}
}

private fun saveImage(file: File) {
lifecycleScope.launch(Dispatchers.Main) {
optimizedPicturesDirectory.mkdirs()
val savedFile = withContext(Dispatchers.IO) {
val newFile = File(optimizedPicturesDirectory, "${System.currentTimeMillis()}.jpg")
file.copyTo(newFile, overwrite = true)
newFile
}
MediaScannerConnection.scanFile(
applicationContext,
arrayOf(savedFile.path),
arrayOf("image/jpeg"),
null
)
val snackbar = Snackbar.make(
binding.root,
getString(R.string.image_saved) + savedFile.path,
Snackbar.LENGTH_LONG
)
snackbar.setAction(android.R.string.ok) {
snackbar.dismiss()
}
snackbar.show()
}
}

private fun getPath(context: Context, uri: Uri): String? {
if (DocumentsContract.isDocumentUri(context, uri)) {
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(":")
val type = split[0]
if ("primary".equals(type, ignoreCase = true)) {
return Environment.getExternalStorageDirectory().toString() + "/" + split[1]
}
} else {
val projection = arrayOf(MediaStore.Images.Media.DATA)
val cursor = context.contentResolver.query(uri, projection, null, null, null)
cursor?.let {
val columnIndex = it.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
it.moveToFirst()
val path = it.getString(columnIndex)
it.close()
return path
}
}
return null
}
}
Loading

0 comments on commit ee7eecc

Please sign in to comment.