You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
375 lines
10 KiB
375 lines
10 KiB
/* |
|
* Copyright 2020 The Android Open Source Project |
|
* |
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
* you may not use this file except in compliance with the License. |
|
* You may obtain a copy of the License at |
|
* |
|
* http://www.apache.org/licenses/LICENSE-2.0 |
|
* |
|
* Unless required by applicable law or agreed to in writing, software |
|
* distributed under the License is distributed on an "AS IS" BASIS, |
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
* See the License for the specific language governing permissions and |
|
* limitations under the License. |
|
*/ |
|
package example.imageviewer.model |
|
|
|
import android.content.Context |
|
import android.graphics.* |
|
import android.os.Handler |
|
import android.os.Looper |
|
import androidx.compose.runtime.Composable |
|
import androidx.compose.runtime.remember |
|
import androidx.compose.runtime.MutableState |
|
import androidx.compose.runtime.mutableStateOf |
|
import example.imageviewer.core.FilterType |
|
import example.imageviewer.model.filtration.FiltersManager |
|
import example.imageviewer.utils.clearCache |
|
import example.imageviewer.utils.isInternetAvailable |
|
import example.imageviewer.view.showPopUpMessage |
|
import example.imageviewer.R |
|
import java.io.File |
|
import java.util.concurrent.ExecutorService |
|
import java.util.concurrent.Executors |
|
|
|
|
|
object ContentState { |
|
|
|
private lateinit var context: Context |
|
private lateinit var repository: ImageRepository |
|
private lateinit var uriRepository: String |
|
|
|
fun applyContent(context: Context, uriRepository: String): ContentState { |
|
if (this::uriRepository.isInitialized && this.uriRepository == uriRepository) { |
|
return this |
|
} |
|
|
|
this.context = context |
|
this.uriRepository = uriRepository |
|
repository = ImageRepository(uriRepository) |
|
appliedFilters = FiltersManager(context) |
|
isAppUIReady.value = false |
|
|
|
initData() |
|
|
|
return this |
|
} |
|
|
|
private val executor: ExecutorService by lazy { Executors.newFixedThreadPool(2) } |
|
|
|
private val handler: Handler by lazy { Handler(Looper.getMainLooper()) } |
|
|
|
fun getContext(): Context { |
|
return context |
|
} |
|
|
|
fun getOrientation(): Int { |
|
return context.resources.configuration.orientation |
|
} |
|
|
|
private val isAppUIReady = mutableStateOf(false) |
|
fun isContentReady(): Boolean { |
|
return isAppUIReady.value |
|
} |
|
|
|
fun getString(id: Int): String { |
|
return context.getString(id) |
|
} |
|
|
|
// drawable content |
|
private val mainImage = mutableStateOf(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)) |
|
private val currentImageIndex = mutableStateOf(0) |
|
private val miniatures = Miniatures() |
|
|
|
fun getMiniatures(): List<Picture> { |
|
return miniatures.getMiniatures() |
|
} |
|
|
|
fun getSelectedImage(): Bitmap { |
|
return mainImage.value |
|
} |
|
|
|
fun getSelectedImageName(): String { |
|
return MainImageWrapper.getName() |
|
} |
|
|
|
// filters managing |
|
private lateinit var appliedFilters: FiltersManager |
|
private val filterUIState: MutableMap<FilterType, MutableState<Boolean>> = LinkedHashMap() |
|
|
|
private fun toggleFilterState(filter: FilterType) { |
|
|
|
if (!filterUIState.containsKey(filter)) { |
|
filterUIState[filter] = mutableStateOf(true) |
|
} else { |
|
val value = filterUIState[filter]!!.value |
|
filterUIState[filter]!!.value = !value |
|
} |
|
} |
|
|
|
fun toggleFilter(filter: FilterType) { |
|
|
|
if (containsFilter(filter)) { |
|
removeFilter(filter) |
|
} else { |
|
addFilter(filter) |
|
} |
|
|
|
toggleFilterState(filter) |
|
|
|
var bitmap = MainImageWrapper.origin |
|
|
|
if (bitmap != null) { |
|
bitmap = appliedFilters.applyFilters(bitmap) |
|
MainImageWrapper.setImage(bitmap) |
|
mainImage.value = bitmap |
|
} |
|
} |
|
|
|
private fun addFilter(filter: FilterType) { |
|
appliedFilters.add(filter) |
|
MainImageWrapper.addFilter(filter) |
|
} |
|
|
|
private fun removeFilter(filter: FilterType) { |
|
appliedFilters.remove(filter) |
|
MainImageWrapper.removeFilter(filter) |
|
} |
|
|
|
private fun containsFilter(type: FilterType): Boolean { |
|
return appliedFilters.contains(type) |
|
} |
|
|
|
fun isFilterEnabled(type: FilterType): Boolean { |
|
if (!filterUIState.containsKey(type)) { |
|
filterUIState[type] = mutableStateOf(false) |
|
} |
|
return filterUIState[type]!!.value |
|
} |
|
|
|
private fun restoreFilters(): Bitmap { |
|
filterUIState.clear() |
|
appliedFilters.clear() |
|
return MainImageWrapper.restore() |
|
} |
|
|
|
fun restoreMainImage() { |
|
mainImage.value = restoreFilters() |
|
} |
|
|
|
// application content initialization |
|
// @Composable |
|
fun initData() { |
|
if (isAppUIReady.value) |
|
return |
|
|
|
val directory = context.cacheDir.absolutePath |
|
|
|
executor.execute { |
|
try { |
|
if (isInternetAvailable()) { |
|
val imageList = repository.get() |
|
|
|
if (imageList.isEmpty()) { |
|
handler.post { |
|
showPopUpMessage( |
|
getString(R.string.repo_invalid), |
|
context |
|
) |
|
isAppUIReady.value = true |
|
} |
|
return@execute |
|
} |
|
|
|
val pictureList = loadImages(directory, imageList) |
|
|
|
if (pictureList.isEmpty()) { |
|
handler.post { |
|
showPopUpMessage( |
|
getString(R.string.repo_empty), |
|
context |
|
) |
|
isAppUIReady.value = true |
|
} |
|
} else { |
|
val picture = loadFullImage(imageList[0]) |
|
|
|
handler.post { |
|
miniatures.setMiniatures(pictureList) |
|
|
|
if (isMainImageEmpty()) { |
|
wrapPictureIntoMainImage(picture) |
|
} else { |
|
appliedFilters.add(MainImageWrapper.getFilters()) |
|
mainImage.value = MainImageWrapper.getImage() |
|
currentImageIndex.value = MainImageWrapper.getId() |
|
} |
|
isAppUIReady.value = true |
|
} |
|
} |
|
} else { |
|
handler.post { |
|
showPopUpMessage( |
|
getString(R.string.no_internet), |
|
context |
|
) |
|
isAppUIReady.value = true |
|
} |
|
} |
|
} catch (e: Exception) { |
|
e.printStackTrace() |
|
} |
|
} |
|
} |
|
|
|
// preview/fullscreen image managing |
|
fun isMainImageEmpty(): Boolean { |
|
return MainImageWrapper.isEmpty() |
|
} |
|
|
|
fun setMainImage(picture: Picture) { |
|
if (MainImageWrapper.getId() == picture.id) |
|
return |
|
|
|
executor.execute { |
|
if (isInternetAvailable()) { |
|
|
|
val fullSizePicture = loadFullImage(picture.source) |
|
fullSizePicture.id = picture.id |
|
|
|
handler.post { |
|
wrapPictureIntoMainImage(fullSizePicture) |
|
} |
|
} else { |
|
handler.post { |
|
showPopUpMessage( |
|
"${getString(R.string.no_internet)}\n${getString(R.string.load_image_unavailable)}", |
|
context |
|
) |
|
wrapPictureIntoMainImage(picture) |
|
} |
|
} |
|
} |
|
} |
|
|
|
private fun wrapPictureIntoMainImage(picture: Picture) { |
|
MainImageWrapper.wrapPicture(picture) |
|
MainImageWrapper.saveOrigin() |
|
mainImage.value = picture.image |
|
currentImageIndex.value = picture.id |
|
} |
|
|
|
fun swipeNext() { |
|
if (currentImageIndex.value == miniatures.size() - 1) { |
|
showPopUpMessage( |
|
getString(R.string.last_image), |
|
context |
|
) |
|
return |
|
} |
|
|
|
restoreFilters() |
|
setMainImage(miniatures.get(++currentImageIndex.value)) |
|
} |
|
|
|
fun swipePrevious() { |
|
if (currentImageIndex.value == 0) { |
|
showPopUpMessage( |
|
getString(R.string.first_image), |
|
context |
|
) |
|
return |
|
} |
|
|
|
restoreFilters() |
|
setMainImage(miniatures.get(--currentImageIndex.value)) |
|
} |
|
|
|
fun refresh() { |
|
executor.execute { |
|
if (isInternetAvailable()) { |
|
handler.post { |
|
clearCache(context) |
|
miniatures.clear() |
|
isAppUIReady.value = false |
|
} |
|
} else { |
|
handler.post { |
|
showPopUpMessage( |
|
"${getString(R.string.no_internet)}\n${getString(R.string.refresh_unavailable)}", |
|
context |
|
) |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
private object MainImageWrapper { |
|
// origin image |
|
var origin: Bitmap? = null |
|
private set |
|
|
|
fun saveOrigin() { |
|
origin = copy(picture.value.image) |
|
} |
|
|
|
fun restore(): Bitmap { |
|
|
|
if (origin != null) { |
|
filtersSet.clear() |
|
picture.value.image = copy(origin!!) |
|
} |
|
|
|
return copy(picture.value.image) |
|
} |
|
|
|
// picture adapter |
|
private var picture = mutableStateOf( |
|
Picture(image = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)) |
|
) |
|
|
|
fun wrapPicture(picture: Picture) { |
|
this.picture.value = picture |
|
} |
|
|
|
fun setImage(bitmap: Bitmap) { |
|
picture.value.image = bitmap |
|
} |
|
|
|
fun isEmpty(): Boolean { |
|
return (picture.value.name == "") |
|
} |
|
|
|
fun getName(): String { |
|
return picture.value.name |
|
} |
|
|
|
fun getImage(): Bitmap { |
|
return picture.value.image |
|
} |
|
|
|
fun getId(): Int { |
|
return picture.value.id |
|
} |
|
|
|
// applied filters |
|
private var filtersSet: MutableSet<FilterType> = LinkedHashSet() |
|
|
|
fun addFilter(filter: FilterType) { |
|
filtersSet.add(filter) |
|
} |
|
|
|
fun removeFilter(filter: FilterType) { |
|
filtersSet.remove(filter) |
|
} |
|
|
|
fun getFilters(): Set<FilterType> { |
|
return filtersSet |
|
} |
|
|
|
private fun copy(bitmap: Bitmap) : Bitmap { |
|
return bitmap.copy(bitmap.config, false) |
|
} |
|
} |