11package xyz.aprildown.timer.data.repositories
22
3+ import android.content.ContentResolver
34import android.content.Context
4- import android.graphics.BitmapFactory
55import android.webkit.MimeTypeMap
66import androidx.core.net.toUri
77import dagger.Reusable
@@ -21,8 +21,6 @@ import xyz.aprildown.timer.domain.entities.TimerInfo
2121import xyz.aprildown.timer.domain.entities.inferResourcesContentType
2222import xyz.aprildown.timer.domain.entities.toImageAction
2323import xyz.aprildown.timer.domain.repositories.TimerRepository
24- import xyz.aprildown.timer.domain.utils.Base64BitmapConverter.decodeBitmapByteArrayFromBase64
25- import xyz.aprildown.timer.domain.utils.Base64BitmapConverter.encodeToBase64
2624import xyz.aprildown.timer.domain.utils.ensureDirExistence
2725import xyz.aprildown.timer.domain.utils.ensureNewFile
2826import java.io.File
@@ -39,7 +37,9 @@ internal class TimerRepositoryImpl @Inject constructor(
3937 private val imageManager by lazy { TimerImageManager (context) }
4038
4139 override suspend fun items (): List <TimerEntity > {
42- return timerDao.getTimers().map { timerMapper.mapFrom(it).withImageCanonicalPath() }
40+ return timerDao.getTimers().map {
41+ timerMapper.mapFrom(it).withImageCanonicalPath()
42+ }
4343 }
4444
4545 override suspend fun item (id : Int ): TimerEntity ? {
@@ -50,7 +50,13 @@ internal class TimerRepositoryImpl @Inject constructor(
5050
5151 private fun TimerEntity.withImageCanonicalPath (): TimerEntity {
5252 return transformImageActions { data ->
53- ImageAction (data = data.convertImageDataToCanonicalPath(id))
53+ ImageAction (
54+ data = data.convertImageData(
55+ id = id,
56+ imageManager = imageManager,
57+ targetType = ResourceContentType .CanonicalPath ,
58+ ),
59+ )
5460 }
5561 }
5662
@@ -60,8 +66,13 @@ internal class TimerRepositoryImpl @Inject constructor(
6066 timerMapper.mapTo(
6167 item.transformImageActions {
6268 ImageAction (
63- data = imageManager.save(timerId = item.id, data = it)
64- .convertImageCanonicalPathToDataType(ResourceContentType .RelativePath )
69+ data = imageManager
70+ .save(id = item.id, data = it)
71+ .convertImageData(
72+ id = item.id,
73+ imageManager = imageManager,
74+ targetType = ResourceContentType .RelativePath ,
75+ ),
6576 )
6677 }
6778 )
@@ -75,11 +86,14 @@ internal class TimerRepositoryImpl @Inject constructor(
7586 }
7687
7788 override suspend fun save (item : TimerEntity ): Boolean {
78- val savedItem = imageManager.trackSaving(timerId = item.id) {
89+ val savedItem = imageManager.trackSaving(id = item.id) {
7990 item.transformImageActions {
8091 ImageAction (
81- data = save(data = it)
82- .convertImageCanonicalPathToDataType(ResourceContentType .RelativePath )
92+ data = save(data = it).convertImageData(
93+ id = item.id,
94+ imageManager = imageManager,
95+ targetType = ResourceContentType .RelativePath ,
96+ ),
8397 )
8498 }
8599 }
@@ -150,37 +164,27 @@ internal class TimerRepositoryImpl @Inject constructor(
150164 return timers.map { timer ->
151165 timer.transformImageActions { data ->
152166 ImageAction (
153- data = data.convertImageDataToCanonicalPath(timerId = timer.id)
154- .convertImageCanonicalPathToDataType(type = type),
167+ data = data.convertImageData(
168+ id = timer.id,
169+ imageManager = imageManager,
170+ targetType = type,
171+ )
155172 )
156173 }
157174 }
158175 }
159-
160- private fun String.convertImageDataToCanonicalPath (timerId : Int ): String {
161- return imageManager.save(timerId = timerId, data = this )
162- }
163-
164- private fun String.convertImageCanonicalPathToDataType (type : ResourceContentType ): String {
165- return when (type) {
166- ResourceContentType .CanonicalPath -> this
167- ResourceContentType .RelativePath -> File (this ).name
168- ResourceContentType .Uri -> error(" Unsupported" )
169- ResourceContentType .Base64 -> {
170- BitmapFactory .decodeFile(this ).encodeToBase64(quality = 90 )
171- }
172- }
173- }
174176}
175177
176- private class TimerImageManager (private val context : Context ) {
178+ private class TimerImageManager (context : Context ) {
177179 interface TrackingScope {
178180 /* *
179181 * [TimerImageManager.save]
180182 */
181183 fun save (data : String ): String
182184 }
183185
186+ val contentResolver: ContentResolver = context.contentResolver
187+
184188 private val imageFolder = File (context.filesDir, FOLDER )
185189
186190 fun getTimerFolder (timerId : Int ): File {
@@ -190,10 +194,10 @@ private class TimerImageManager(private val context: Context) {
190194 /* *
191195 * @return [ResourceContentType.RelativePath], [File.getCanonicalPath]
192196 */
193- fun save (timerId : Int , data : String ): String {
197+ fun save (id : Int , data : String ): String {
194198 imageFolder.ensureDirExistence()
195199
196- val timerFolder = getTimerFolder(timerId )
200+ val timerFolder = getTimerFolder(id )
197201 val imageFile: File
198202 when (data.inferResourcesContentType()) {
199203 ResourceContentType .CanonicalPath -> {
@@ -202,8 +206,8 @@ private class TimerImageManager(private val context: Context) {
202206 imageFile = existingFile
203207 } else {
204208 imageFile = File (
205- imageFolder .ensureDirExistence(),
206- " ${UUID .randomUUID ()} .${existingFile.extension} "
209+ timerFolder .ensureDirExistence(),
210+ " ${generateImageName ()} .${existingFile.extension} "
207211 ).ensureNewFile()
208212 imageFile.inputStream().use { inputStream ->
209213 existingFile.outputStream().use { outputStream ->
@@ -218,45 +222,40 @@ private class TimerImageManager(private val context: Context) {
218222 ResourceContentType .Uri -> {
219223 val uri = data.toUri()
220224 val extension = MimeTypeMap .getSingleton()
221- .getExtensionFromMimeType(context.contentResolver.getType(uri)) ? : " jpg"
222- imageFile =
223- File (timerFolder.ensureDirExistence(), " ${UUID .randomUUID()} .$extension " )
224- .ensureNewFile()
225- context.contentResolver.openInputStream(uri)?.use { inputStream ->
225+ .getExtensionFromMimeType(contentResolver.getType(uri)) ? : " jpg"
226+ imageFile = File (
227+ timerFolder.ensureDirExistence(),
228+ " ${generateImageName()} .$extension "
229+ ).ensureNewFile()
230+ contentResolver.openInputStream(uri)?.use { inputStream ->
226231 imageFile.outputStream().use { outputStream ->
227232 inputStream.copyTo(outputStream)
228233 }
229234 }
230- require(imageFile.exists() && imageFile.length() > 0L )
231235 imageFile.canonicalPath
232236 }
233- ResourceContentType .Base64 -> {
234- imageFile = File (timerFolder.ensureDirExistence(), " ${UUID .randomUUID()} .webp" )
235- .ensureNewFile()
236- val byteArray = data.decodeBitmapByteArrayFromBase64()
237- imageFile.outputStream().use {
238- it.write(byteArray)
239- it.flush()
240- }
241- }
242237 }
243238 require(imageFile.exists() && imageFile.length() > 0L )
244239 return imageFile.canonicalPath
245240 }
246241
247- fun <T > trackSaving (timerId : Int , block : TrackingScope .() -> T ): T {
242+ private fun generateImageName (): String {
243+ return UUID .randomUUID().toString()
244+ }
245+
246+ fun <T > trackSaving (id : Int , block : TrackingScope .() -> T ): T {
248247 val saved = mutableListOf<String >()
249248 val scope = object : TrackingScope {
250249 override fun save (data : String ): String {
251- val savedData = save(timerId = timerId , data = data)
250+ val savedData = save(id = id , data = data)
252251 saved + = savedData
253252 return savedData
254253 }
255254 }
256255 return try {
257256 block(scope)
258257 } finally {
259- val timerFolder = getTimerFolder(timerId )
258+ val timerFolder = getTimerFolder(id )
260259 if (timerFolder.exists()) {
261260 timerFolder.listFiles()?.toList()?.forEach { file ->
262261 if (file.canonicalPath !in saved) {
@@ -267,8 +266,8 @@ private class TimerImageManager(private val context: Context) {
267266 }
268267 }
269268
270- fun delete (timerId : Int ) {
271- val timerFolder = getTimerFolder(timerId )
269+ fun delete (id : Int ) {
270+ val timerFolder = getTimerFolder(id )
272271 if (timerFolder.exists()) {
273272 timerFolder.deleteRecursively()
274273 }
@@ -278,3 +277,41 @@ private class TimerImageManager(private val context: Context) {
278277 private const val FOLDER = " timer_images"
279278 }
280279}
280+
281+ private fun String.convertImageData (
282+ id : Int = TimerEntity .NULL_ID ,
283+ imageManager : TimerImageManager ,
284+ targetType : ResourceContentType ,
285+ ): String {
286+ fun String.save (): String {
287+ return imageManager.save(id = id, data = this )
288+ }
289+
290+ fun String.convert (targetType : ResourceContentType ): String {
291+ return convertImageData(id = id, imageManager = imageManager, targetType)
292+ }
293+
294+ return when (inferResourcesContentType()) {
295+ ResourceContentType .CanonicalPath -> {
296+ when (targetType) {
297+ ResourceContentType .CanonicalPath -> save()
298+ ResourceContentType .RelativePath -> File (this ).name
299+ ResourceContentType .Uri -> error(" Unable to convert CanonicalPath to Uri" )
300+ }
301+ }
302+ ResourceContentType .RelativePath -> {
303+ when (targetType) {
304+ ResourceContentType .CanonicalPath -> save()
305+ ResourceContentType .RelativePath -> return this
306+ ResourceContentType .Uri -> error(" Unable to convert RelativePath to Uri" )
307+ }
308+ }
309+ ResourceContentType .Uri -> {
310+ when (targetType) {
311+ ResourceContentType .CanonicalPath -> save()
312+ ResourceContentType .RelativePath -> save().convert(ResourceContentType .RelativePath )
313+ ResourceContentType .Uri -> return this
314+ }
315+ }
316+ }
317+ }
0 commit comments