diff --git a/README.md b/README.md index 7202b00..da813f0 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ Share image to WallpaperTunnel to set it as wallpaper. - Provide a simple method for setting images as wallpapers. - Allow adjustment of image brightness and blur intensity. +- Fill images with solid color or blurred images. +- Image Super-Resolution. (Beta) - Maintain a history of wallpapers set through this app. - Retrieve and modify the current wallpaper (static wallpapers only). diff --git a/app/build.gradle b/app/build.gradle index daca588..11a1ab2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,7 +12,7 @@ android { minSdkVersion 29 targetSdkVersion 34 versionCode 4 - versionName "2.5-alpha-240411" + versionName "3.0-alpha-240419" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildFeatures { @@ -59,11 +59,11 @@ android { universalApk false } } - task wrapper(type: Wrapper) { + tasks.register('wrapper', Wrapper) { gradleVersion = '8.0' } namespace 'com.maary.shareas' - applicationVariants.all { variant -> + applicationVariants.configureEach { variant -> variant.outputs.each { output -> def newName = output.outputFileName print(newName) @@ -92,14 +92,25 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - implementation 'androidx.core:core-ktx:1.12.0' - implementation 'androidx.core:core-ktx:1.12.0' + implementation 'androidx.core:core-ktx:1.13.0' implementation 'androidx.activity:activity:1.8.2' implementation "androidx.datastore:datastore-preferences:1.0.0" // optional - RxJava2 support implementation "androidx.datastore:datastore-preferences-rxjava2:1.0.0" // optional - RxJava3 support implementation "androidx.datastore:datastore-preferences-rxjava3:1.0.0" + implementation "androidx.fragment:fragment-ktx:1.6.2" + + implementation 'com.microsoft.onnxruntime:onnxruntime-android:1.17.3' + implementation 'com.microsoft.onnxruntime:onnxruntime-extensions-android:0.9.0' + + implementation("androidx.transition:transition:1.5.0-rc02") + implementation("androidx.transition:transition-ktx:1.5.0-rc02") + + implementation "androidx.fragment:fragment:1.7.0-rc02" + // Kotlin + implementation "androidx.fragment:fragment-ktx:1.7.0-rc02" + testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' @@ -114,9 +125,5 @@ dependencies { implementation 'com.github.bumptech.glide:glide:4.16.0' - //noinspection GradleCompatible - implementation 'com.android.support:palette-v7:28.0.0' - - - //TODO: implement monet for photo imported + implementation 'androidx.palette:palette:1.0.0' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 009822f..12e0bd9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -10,12 +10,9 @@ - - + - - + android:exported="true"/> + + @@ -48,11 +46,13 @@ android:exported="true"> + - + >() @@ -55,17 +55,6 @@ class HistoryActivityViewModel(application: Application) : AndroidViewModel(appl } } - fun getUriList(): ArrayList { - val uriList = ArrayList() - viewModelScope.launch { - val images = queryImages() - - for (image in images) uriList.add(image.contentUri) - - } - return uriList - } - @RequiresApi(Build.VERSION_CODES.R) fun deleteImage(image: MediaStoreImage) { viewModelScope.launch { @@ -215,7 +204,7 @@ class HistoryActivityViewModel(application: Application) : AndroidViewModel(appl Log.i(TAG, "Found ${cursor.count} images") while (cursor.moveToNext()) { - // Here we'll use the column indexs that we found above. + // Here we'll use the column index that we found above. val id = cursor.getLong(idColumn) val dateModified = Date(TimeUnit.SECONDS.toMillis(cursor.getLong(dateModifiedColumn))) @@ -292,14 +281,8 @@ class HistoryActivityViewModel(application: Application) : AndroidViewModel(appl * so it can be either updated or deleted. */ - val pendingIntent = MediaStore.createDeleteRequest(getApplication().contentResolver, arrayListOf(image.contentUri)) -// getApplication().startIntentSenderForResult(pendingIntent.intentSender, null, 0, 0, 0) + MediaStore.createDeleteRequest(getApplication().contentResolver, arrayListOf(image.contentUri)) -// getApplication().contentResolver.delete( -// image.contentUri, -// "${MediaStore.Images.Media._ID} = ?", -// arrayOf(image.id.toString()) -// ) } catch (securityException: SecurityException) { val recoverableSecurityException = securityException as? RecoverableSecurityException @@ -315,20 +298,6 @@ class HistoryActivityViewModel(application: Application) : AndroidViewModel(appl } } - /** - * Convenience method to convert a day/month/year date into a UNIX timestamp. - * - * We're suppressing the lint warning because we're not actually using the date formatter - * to format the date to display, just to specify a format to use to parse it, and so the - * locale warning doesn't apply. - */ - @Suppress("SameParameterValue") - @SuppressLint("SimpleDateFormat") - private fun dateToTimestamp(day: Int, month: Int, year: Int): Long = - SimpleDateFormat("dd.MM.yyyy").let { formatter -> - TimeUnit.MICROSECONDS.toSeconds(formatter.parse("$day.$month.$year")?.time ?: 0) - } - /** * Since we register a [ContentObserver], we want to unregister this when the `ViewModel` * is being released. diff --git a/app/src/main/java/com/maary/shareas/WallpaperViewModel.kt b/app/src/main/java/com/maary/shareas/WallpaperViewModel.kt new file mode 100644 index 0000000..9f9c0ac --- /dev/null +++ b/app/src/main/java/com/maary/shareas/WallpaperViewModel.kt @@ -0,0 +1,699 @@ +package com.maary.shareas + +import ai.onnxruntime.OrtEnvironment +import ai.onnxruntime.OrtSession +import ai.onnxruntime.extensions.OrtxPackage +import ai.onnxruntime.providers.NNAPIFlags +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.graphics.PorterDuff +import android.graphics.PorterDuffXfermode +import android.graphics.Rect +import android.graphics.RectF +import android.net.Uri +import android.util.Log +import androidx.core.content.FileProvider +import androidx.lifecycle.LiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.asLiveData +import androidx.lifecycle.viewModelScope +import androidx.palette.graphics.Palette +import com.hoko.blur.HokoBlur +import com.hoko.blur.task.AsyncBlurTask +import com.maary.shareas.data.ViewerBitmap +import com.maary.shareas.helper.SuperResPerformer +import com.maary.shareas.helper.Util +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream +import java.util.EnumSet +import kotlin.math.pow +import kotlin.random.Random + + +class WallpaperViewModel : ViewModel() { + init { + Log.v("WVM", "INIT") + } + + companion object { + const val HOME = 1 + const val LOCK = 0 + + const val TOP = 0 + const val BOTTOM = 1 + const val LEFT = 2 + const val RIGHT = 3 + const val CENTER = 4 + } + + private var bakBitmap = ViewerBitmap() + + private var bitmapRaw: Bitmap? = null + private var bitmapFit: Bitmap? = null + private var background: Bitmap? = null + + private var bitmap: Bitmap? = null + set(value) { + Log.v("WVM", "BITMAP SET") + field = value + bakBitmap.bitmapHome = value + bakBitmap.bitmapLock = value + _viewerState.value = bakBitmap + } + + private var inEditor = false + set(value) { + field = value + _inEditor.value = value + } + + var currentBitmap = HOME + set(value) { + field = value + _currentBitmapState.value = value + } + + var upscaleToggle = false + set(value) { + field = value + _upscaleToggleState.value = value + } + + + var primary: Int? = null + var secondary: Int? = null + var tertiary: Int? = null + + private val _currentBitmapState = MutableStateFlow(currentBitmap) + val currentBitmapState: StateFlow = _currentBitmapState.asStateFlow() + val currentBitmapStateLiveData: LiveData = _currentBitmapState.asLiveData() + + private val _viewerState = MutableStateFlow(ViewerBitmap()) + val viewerStateLiveData = _viewerState.asLiveData() + + private val _inEditor: MutableStateFlow = MutableStateFlow(inEditor) + val inEditorLiveData: LiveData = _inEditor.asLiveData() + + private val _upscaleProgressState = MutableStateFlow(0) + val upscaleProgressState = _upscaleProgressState.asStateFlow() + + private val _upscaleToggleState = MutableStateFlow(upscaleToggle) + val upscaleToggleState = _upscaleToggleState.asStateFlow() + + private val _primaryColorState = MutableStateFlow(Color.TRANSPARENT) + val primaryColorState = _primaryColorState.asStateFlow() + + private var ortEnv: OrtEnvironment = OrtEnvironment.getEnvironment() + private lateinit var ortSession: OrtSession + + + fun setBitmapRaw(value: Bitmap, context: Context) { + bitmapRaw = value + bitmapFit = fitBitmapToScreenAlt(value, context) + bitmap = fitBitmapToScreen(value, context) + val deviceBounds = Util.getDeviceBounds(context) + background = Bitmap.createBitmap(deviceBounds.x, deviceBounds.y, Bitmap.Config.ARGB_8888) + val colors = extractColorsFromPalette() + primary = adjustColor(colors[0]) + secondary = adjustColor(colors[1]) + tertiary = adjustColor(colors[2]) + _primaryColorState.value += 1 + } + private fun fitBitmapToScreen(value: Bitmap, context: Context): Bitmap { + val deviceBounds = Util.getDeviceBounds(context) + val deviceHeight = deviceBounds.y + val deviceWidth = deviceBounds.x + + //image ratio > device ratio? + val isVertical = Util.isVertical(deviceHeight, deviceWidth, value) + + //show image to imageview + val bitmapFullWidth = value.width + val bitmapFullHeight = value.height + val desiredWidth: Int + val desiredHeight: Int + + if (isVertical) { + desiredWidth = deviceWidth + val scale = deviceWidth.toFloat() / bitmapFullWidth + desiredHeight = (scale * bitmapFullHeight).toInt() + } else { + desiredHeight = deviceHeight + val scale = deviceHeight.toFloat() / bitmapFullHeight + desiredWidth = (scale * bitmapFullWidth).toInt() + } + + return Bitmap.createScaledBitmap(value, desiredWidth, desiredHeight, true) + } + + private fun fitBitmapToScreenAlt(value: Bitmap, context: Context): Bitmap { + val deviceBounds = Util.getDeviceBounds(context) + val deviceHeight = deviceBounds.y + val deviceWidth = deviceBounds.x + + //image ratio > device ratio? + val isVertical = Util.isVertical(deviceHeight, deviceWidth, value) + + //show image to imageview + val bitmapFullWidth = value.width + val bitmapFullHeight = value.height + val desiredWidth: Int + val desiredHeight: Int + + if (isVertical) { + desiredHeight = deviceHeight + val scale = deviceHeight.toFloat() / bitmapFullHeight + desiredWidth = (scale * bitmapFullWidth).toInt() + } else { + desiredWidth = deviceWidth + val scale = deviceWidth.toFloat() / bitmapFullWidth + desiredHeight = (scale * bitmapFullHeight).toInt() + } + + return Bitmap.createScaledBitmap(value, desiredWidth, desiredHeight, true) + } + + fun getBitmapHome(): Bitmap? { + return _viewerState.value.bitmapHome + } + + fun getBitmapLock(): Bitmap? { + return _viewerState.value.bitmapLock + } + + // 丢弃所有可能的修改 + fun restoreChanges() { + bakBitmap.bitmapLock = bitmap + bakBitmap.bitmapHome = bitmap + _viewerState.update { currentState -> + currentState.copy( + bitmapHome = bitmap, + bitmapLock = bitmap + ) + } + } + + private fun adjustColor(color: Int, threshold: Float = 0.5f): Int { + val red = Color.red(color) + val green = Color.green(color) + val blue = Color.blue(color) + val brightness = (0.299 * red + 0.587 * green + 0.114 * blue) / 255 + + return if (brightness > threshold) { + // 如果亮度超过阈值,则将颜色调暗 + val factor = 0.8f // 调暗因子,可以根据需要调整 + darkenColor(red, green, blue, factor) + } else if (brightness < (1 - threshold)) { + // 如果亮度低于 (1 - threshold),则将颜色调亮 + val factor = 1.2f // 调亮因子,可以根据需要调整 + lightenColor(red, green, blue, factor) + } else { + // 如果颜色过于偏白,随机添加一些颜色 + val offset = Random.nextInt(20, 50) // 随机偏移量 + val newRed = (red + offset).coerceIn(0, 255) + val newGreen = (green + offset).coerceIn(0, 255) + val newBlue = (blue + offset).coerceIn(0, 255) + Color.rgb(newRed, newGreen, newBlue) + } + } + + private fun darkenColor(red: Int, green: Int, blue: Int, factor: Float): Int { + val offset = Random.nextInt(-20, 20) // 随机偏移量 + val newRed = (red * factor + offset).coerceIn(0f, 255f) + val newGreen = (green * factor + offset).coerceIn(0f, 255f) + val newBlue = (blue * factor + offset).coerceIn(0f, 255f) + return Color.rgb(newRed.toInt(), newGreen.toInt(), newBlue.toInt()) + } + + private fun lightenColor(red: Int, green: Int, blue: Int, factor: Float): Int { + val offset = Random.nextInt(-20, 20) // 随机偏移量 + val newRed = (red * factor + offset).coerceIn(0f, 255f) + val newGreen = (green * factor + offset).coerceIn(0f, 255f) + val newBlue = (blue * factor + offset).coerceIn(0f, 255f) + return Color.rgb(newRed.toInt(), newGreen.toInt(), newBlue.toInt()) + } + + + + // 丢弃当前的修改 + fun abortEdit() { + _viewerState.update { current -> + current.copy( + bitmapHome = bakBitmap.bitmapHome, + bitmapLock = bakBitmap.bitmapLock + ) + } + } + + fun abortEditHome() { + _viewerState.update { current -> + current.copy( + bitmapHome = bakBitmap.bitmapHome + ) + } + } + + fun abortEditLock() { + _viewerState.update { current -> + current.copy( + bitmapLock = bakBitmap.bitmapLock + ) + } + } + + fun startEditing() { + inEditor = true + } + + fun finishEditing() { + inEditor = false + } + + fun getDisplayBitmap(): Bitmap? { + if (currentBitmap == HOME) { + return _viewerState.value.bitmapHome + } else if (currentBitmap == LOCK) { + return _viewerState.value.bitmapLock + } + return bitmap + } + + fun currentBitmapToggle() { + if (currentBitmap == HOME) { + currentBitmap = LOCK + } else if (currentBitmap == LOCK) { + currentBitmap = HOME + } + } + + fun getFabResource(): Int { + if (currentBitmap == HOME) { + return R.drawable.ic_vertical + } + if (currentBitmap == LOCK) { + return R.drawable.ic_lockscreen + } + return R.drawable.ic_vertical + } + + fun editBlur(context: Context, value: Float) { + HokoBlur.with(context) + .radius(value.toInt()) + .forceCopy(true) + .asyncBlur(bakBitmap.bitmapHome, object : AsyncBlurTask.Callback { + override fun onBlurSuccess(bitmap: Bitmap) { + _viewerState.update { current -> + current.copy(bitmapHome = bitmap) + } + HokoBlur.with(context) + .radius(value.toInt()) + .forceCopy(true) + .asyncBlur(bakBitmap.bitmapLock, object : AsyncBlurTask.Callback { + override fun onBlurSuccess(bitmap: Bitmap) { + _viewerState.update { current -> + current.copy(bitmapLock = bitmap) + } + } + + override fun onBlurFailed(error: Throwable) {} + }) + } + + override fun onBlurFailed(error: Throwable) {} + }) + } + + fun editBrightness(value: Float) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val homeA = Util.adjustBrightness(bakBitmap.bitmapHome, value) + val lockA = Util.adjustBrightness(bakBitmap.bitmapLock, value) + _viewerState.update { current -> + current.copy( + bitmapHome = homeA, + bitmapLock = lockA + ) + } + } + } + } + + fun saveEdit() { + bakBitmap = _viewerState.value + } + + fun getBitmapUri(context: Context, cacheDir: File): Uri? { + //---Save bitmap to external cache directory---// + //get cache directory + + val cachePath = File(cacheDir, "my_images/") + cachePath.mkdirs() + + //create png file + val file = File(cachePath, "Image_123.png") + val fileOutputStream: FileOutputStream + try { + fileOutputStream = FileOutputStream(file) + _viewerState.value.bitmapHome?.compress( + Bitmap.CompressFormat.PNG, + 100, + fileOutputStream + ) + fileOutputStream.flush() + fileOutputStream.close() + } catch (e: IOException) { + e.printStackTrace() + } + + return FileProvider.getUriForFile( + context, + context.packageName + ".provider", + file + ) + } + + private fun readModel(context: Context, modelID: Int): ByteArray { + Log.e("WVM", "READ MODEL") + return context.resources.openRawResource(modelID).readBytes() + } + + private fun performSuperResolution(ortSession: OrtSession, bitmap: Bitmap): Bitmap? { + val superResPerformer = SuperResPerformer() + Log.v("WVM", "STARTED") + + val result = superResPerformer.upscale(bitmapToInputStream(bitmap), ortEnv, ortSession) + return result.outputBitmap + } + + private fun bitmapToInputStream(bitmap: Bitmap?): InputStream { + val outputStream = ByteArrayOutputStream() + bitmap?.compress(Bitmap.CompressFormat.PNG, 100, outputStream) + return ByteArrayInputStream(outputStream.toByteArray()) + } + + private suspend fun process(tileBitmap: Bitmap): Bitmap? { + // 实现对图片的超分辨率处理 + return viewModelScope.async(Dispatchers.IO) { + performSuperResolution(ortSession, tileBitmap) + }.await() + } + + private fun scaleBitmapTo(originalBitmap: Bitmap, scale: Float): Bitmap { + val originalWidth = originalBitmap.width + val originalHeight = originalBitmap.height + val scaledWidth = (originalWidth * scale).toInt() + val scaledHeight = (originalHeight * scale).toInt() + return Bitmap.createScaledBitmap(originalBitmap, scaledWidth, scaledHeight, true) + } + + fun extractTopColorsFromBitmap(): List { + + val _bitmap = Bitmap.createScaledBitmap(bitmapRaw!!, 128, 128, true) + val colorMap = mutableMapOf() + + // 遍历图片的每个像素,并统计每种颜色的出现次数 + for (x in 0 until _bitmap.width) { + for (y in 0 until _bitmap.height) { + val pixel = _bitmap.getPixel(x, y) + val colorCount = colorMap.getOrDefault(pixel, 0) + colorMap[pixel] = colorCount + 1 + } + } + + // 按颜色出现次数排序,并取前五个颜色 + val sortedColors = colorMap.toList().sortedByDescending { it.second }.take(5) + return sortedColors.map { it.first } + } + + fun extractColorsFromPalette(): List { + val palette = Palette.from(bitmap!!).generate() + + // 获取所有的颜色 swatch,并根据 population 属性进行排序 + val sortedSwatches = palette.swatches.sortedByDescending { it.population } + + // 提取前五个颜色 + return sortedSwatches.take(3).map { it.rgb } + } + + fun paintColor(position: Int, color: Int, scale: Float = 1f) { + var left = 0 + var top = 0 + val bHeight = background!!.height + val bWidth = background!!.width + val _background = Bitmap.createBitmap(bWidth, bHeight, Bitmap.Config.ARGB_8888) + val _bitmapFit = scaleBitmapTo(bitmapFit!!, scale) + + val fHeight = _bitmapFit.height + val fWidth = _bitmapFit.width + when (position) { + TOP -> top = 0 + BOTTOM -> top = bHeight - fHeight + LEFT -> left = 0 + RIGHT -> left = bWidth - fWidth + CENTER -> { + left = (bWidth - fWidth) / 2 + top = (bHeight - fHeight) / 2 + } + } + + val canvas = Canvas(_background) + canvas.drawColor(color) + canvas.drawBitmap(_bitmapFit, left.toFloat(), top.toFloat(), null) + + _viewerState.update { current -> + current.copy( + bitmapHome = _background, + bitmapLock = _background + ) + } + } + + private fun addShadow(image: Bitmap, zoom: Float, offset: Int = 32): Bitmap { + val width = image.width + val height = image.height + + // 创建新的背景图片 + val _background = Bitmap.createBitmap(width + offset * 2, height + offset * 2, Bitmap.Config.ARGB_8888) + + if (_background.width > width/zoom || _background.height > height/zoom) return image + + val canvas = Canvas(_background) + + // 创建绘制阴影的 Paint 对象 + val shadowPaint = Paint().apply { + color = Color.BLACK + alpha = 255 + xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_ATOP) + } + + // 绘制阴影 + for (i in 0 until offset) { + val alpha = (255 * (1.3.pow(i) / 1.3.pow(offset))).toInt() // 根据当前距离计算透明度 + shadowPaint.alpha = alpha + val rect = RectF(i.toFloat(), i.toFloat(), (width + offset * 2 - i).toFloat(), (height + offset * 2 - i).toFloat()) + canvas.drawRect(rect, shadowPaint) + } + + // 将原始图片绘制在背景上 + canvas.drawBitmap(image, offset.toFloat(), offset.toFloat(), null) + + return _background + } + + fun paintBlur( + position: Int, + blur: Int, + context: Context, + scale: Float = 1f + ) { + var left = 0 + var top = 0 + + val bHeight = background!!.height + val bWidth = background!!.width + + val _bitmapFit = scaleBitmapTo(bitmapFit!!, scale) + val target = addShadow(_bitmapFit, scale) + + val canvasBack = Canvas(background!!) + val srcLeft = (bitmap!!.width - bWidth)/2 + val srcTop = (bitmap!!.height - bHeight)/2 + val srcRect = Rect( + srcLeft, + srcTop, + srcLeft + bWidth, + srcTop + bHeight) + + // 定义目标区域 + val destRect = Rect(0, 0, bWidth, bHeight) + + // 在 Canvas 上绘制裁剪后的图像 + canvasBack.drawBitmap(bitmap!!, srcRect, destRect, null) + + val fHeight = target.height + val fWidth = target.width + when (position) { + TOP -> top = 0 + BOTTOM -> top = bHeight - fHeight + LEFT -> left = 0 + RIGHT -> left = bWidth - fWidth + CENTER -> { + left = (bWidth - fWidth) / 2 + top = (bHeight - fHeight) / 2 + } + } + + HokoBlur.with(context) + .radius(blur) + .forceCopy(true) + .asyncBlur(background, object : AsyncBlurTask.Callback { + override fun onBlurSuccess(bitmap: Bitmap) { + val canvas = Canvas(bitmap) + canvas.drawBitmap(target, left.toFloat(), top.toFloat(), null) + _viewerState.update { current -> + current.copy( + bitmapHome = bitmap, + bitmapLock = bitmap + ) + } + } + + override fun onBlurFailed(error: Throwable) {} + }) + + } + + fun upscale(context: Context, modelName: String) { + Log.v("WVM", modelName) + + var model = R.raw.realesrgan_anime + var scale = 4 + val modelArray = context.resources.getStringArray(R.array.model_names) + when (modelName) { + modelArray[0] -> { + model = R.raw.realesrgan_x2plus + scale = 2 + } + + modelArray[1] -> { + model = R.raw.realesrgan_x4plus + scale = 4 + scaleBitmapTo(bitmapRaw!!, 0.5f) + } + + modelArray[2] -> { + model = R.raw.realesrgan_anime + scaleBitmapTo(bitmapRaw!!, 0.5f) + } + } + + val sessionOptions: OrtSession.SessionOptions = OrtSession.SessionOptions() + sessionOptions.registerCustomOpLibrary(OrtxPackage.getLibraryPath()) + sessionOptions.addNnapi( + EnumSet.of( + NNAPIFlags.USE_FP16, + NNAPIFlags.CPU_DISABLED + ) + ) + ortSession = ortEnv.createSession(readModel(context, model), sessionOptions) + + // 1. 输入是一个 bimap + val originalWidth = bitmapRaw!!.width + val originalHeight = bitmapRaw!!.height + + // 2. 将 bitmap 分割为多个 tile,记录 tile 的相对位置 + val tileSize = 256 + val numTilesX = (originalWidth + tileSize - 1) / tileSize + val numTilesY = (originalHeight + tileSize - 1) / tileSize + val sub = numTilesX * numTilesY + + // 存储处理后的 tile + val processedTiles = mutableListOf() + + val processedTilesDeferred = mutableListOf>() + + + viewModelScope.launch { + var numFinished = 0 + var progress = 0 + for (y in 0 until numTilesY) { + for (x in 0 until numTilesX) { + + if (!_upscaleToggleState.value) break + + val tileX = x * tileSize + val tileY = y * tileSize + val tileWidth = minOf(tileSize, originalWidth - tileX) + val tileHeight = minOf(tileSize, originalHeight - tileY) + + val tileBitmap = + Bitmap.createBitmap(bitmapRaw!!, tileX, tileY, tileWidth, tileHeight) + + val deferred = async { process(tileBitmap) } + processedTilesDeferred.add(deferred) + val processedTile = deferred.await() + + numFinished++ + Log.e("WVM", numFinished.toString()) + if (sub != 0) { + progress = (String.format("%.2f", (numFinished.toDouble() / sub.toDouble())) + .toDouble() * 100).toInt() + } + _upscaleProgressState.value = progress + + if (processedTile != null) { + processedTiles.add(processedTile) + } + + System.gc() + } + } + + val resultWidth = originalWidth * scale + val resultHeight = originalHeight * scale + var resultBitmap = Bitmap.createBitmap(resultWidth, resultHeight, bitmapRaw!!.config) + val canvas = Canvas(resultBitmap) + var currentX = 0 + var currentY = 0 + + for (processedTileDeferred in processedTilesDeferred) { + val processedTile = processedTileDeferred.await() + if (processedTile != null) { + canvas.drawBitmap(processedTile, currentX.toFloat(), currentY.toFloat(), null) + currentX += processedTile.width + if (currentX >= resultWidth) { + currentX = 0 + currentY += processedTile.height + } + } + } + + resultBitmap = fitBitmapToScreen(resultBitmap, context) + + _viewerState.update { current -> + current.copy( + bitmapHome = resultBitmap, + bitmapLock = resultBitmap + ) + } + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/maary/shareas/activity/HistoryActivity.kt b/app/src/main/java/com/maary/shareas/activity/HistoryActivity.kt index b90c177..5d584e3 100644 --- a/app/src/main/java/com/maary/shareas/activity/HistoryActivity.kt +++ b/app/src/main/java/com/maary/shareas/activity/HistoryActivity.kt @@ -2,14 +2,16 @@ package com.maary.shareas.activity import android.Manifest import android.app.Activity -import android.content.* +import android.content.Context +import android.content.DialogInterface +import android.content.Intent +import android.content.IntentSender import android.content.pm.PackageManager import android.content.res.Configuration import android.net.Uri import android.os.Build import android.os.Bundle import android.provider.MediaStore -import android.provider.Settings import android.util.TypedValue import android.view.LayoutInflater import android.view.View @@ -24,7 +26,8 @@ import androidx.appcompat.app.AppCompatActivity import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat -import androidx.core.view.* +import androidx.core.view.WindowCompat +import androidx.core.view.updatePadding import androidx.databinding.DataBindingUtil import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.ListAdapter @@ -33,11 +36,15 @@ import com.bumptech.glide.Glide import com.google.android.material.color.DynamicColors import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.maary.shareas.HistoryActivityViewModel -import com.maary.shareas.MediaStoreImage import com.maary.shareas.R -import com.maary.shareas.helper.Util +import com.maary.shareas.data.MediaStoreImage import com.maary.shareas.databinding.ActivityHistoryBinding -import kotlinx.coroutines.* +import com.maary.shareas.helper.Util +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext /** The request code for requesting [Manifest.permission.READ_EXTERNAL_STORAGE] permission. */ @@ -144,19 +151,9 @@ class HistoryActivity : AppCompatActivity(){ } if (!haveStoragePermission()) { -// binding.welcomeView.visibility = View.VISIBLE requestPermission() } else { showImages() -// if (galleryAdapter.itemCount == 0){ -// Log.v("WALLP", "0") -// binding.layoutNoHistory.visibility = View.VISIBLE -// binding.buttonClearAll.visibility = View.GONE -// }else { -// Log.v("WALLP", "1") -// binding.layoutNoHistory.visibility = View.INVISIBLE -// binding.buttonClearAll.visibility = View.VISIBLE -// } } } @@ -171,30 +168,6 @@ class HistoryActivity : AppCompatActivity(){ // If request is cancelled, the result arrays are empty. if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { showImages() - } else { - // If we weren't granted the permission, check to see if we should show - // rationale for the permission. - val showRationale = - ActivityCompat.shouldShowRequestPermissionRationale( - this, - Manifest.permission.READ_EXTERNAL_STORAGE - ) - - /** - * If we should show the rationale for requesting storage permission, then - * we'll show [ActivityMainBinding.permissionRationaleView] which does this. - * - * If `showRationale` is false, this means the user has not only denied - * the permission, but they've clicked "Don't ask again". In this case - * we send the user to the settings page for the app so they can grant - * the permission (Yay!) or uninstall the app. - */ - if (showRationale) { -// showNoAccess() - //TODO: do something - } else { - goToSettings() - } } return } @@ -209,7 +182,6 @@ class HistoryActivity : AppCompatActivity(){ } } - @OptIn(DelicateCoroutinesApi::class) private fun showImages() { viewModel.loadImages() @@ -222,23 +194,6 @@ class HistoryActivity : AppCompatActivity(){ } } - private fun openMediaStore() { - if (haveStoragePermission()) { - showImages() - } else { - requestPermission() - } - } - - private fun goToSettings() { - Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:$packageName")).apply { - addCategory(Intent.CATEGORY_DEFAULT) - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - }.also { intent -> - startActivity(intent) - } - } - /** * Convenience method to check if [Manifest.permission.READ_EXTERNAL_STORAGE] permission * has been granted to the app. @@ -280,7 +235,7 @@ class HistoryActivity : AppCompatActivity(){ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { val pendingIntent = MediaStore.createDeleteRequest(contentResolver, arrayListOf(image.contentUri)) - val intentSenderRequest = IntentSenderRequest.Builder(pendingIntent.intentSender).build() + IntentSenderRequest.Builder(pendingIntent.intentSender).build() startIntentSenderForResult(pendingIntent.intentSender, 42, null, 0, 0, 0, null) } else { application.contentResolver.delete( diff --git a/app/src/main/java/com/maary/shareas/activity/MainActivity.java b/app/src/main/java/com/maary/shareas/activity/MainActivity.java index dfdb467..87573c1 100644 --- a/app/src/main/java/com/maary/shareas/activity/MainActivity.java +++ b/app/src/main/java/com/maary/shareas/activity/MainActivity.java @@ -1,7 +1,5 @@ package com.maary.shareas.activity; -import static com.google.android.material.slider.LabelFormatter.LABEL_GONE; - import android.Manifest; import android.annotation.SuppressLint; import android.app.PendingIntent; @@ -12,59 +10,42 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.ColorStateList; -import android.content.res.Configuration; import android.graphics.Bitmap; -import android.graphics.Color; -import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Environment; +import android.os.Handler; +import android.os.Looper; import android.util.Log; -import android.view.Gravity; -import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.widget.HorizontalScrollView; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ScrollView; -import android.widget.TextView; - -import androidx.annotation.NonNull; + import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.content.res.AppCompatResources; import androidx.appcompat.view.menu.ActionMenuItemView; -import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.content.ContextCompat; -import androidx.core.content.FileProvider; import androidx.core.view.WindowCompat; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; +import androidx.lifecycle.ViewModelProvider; import androidx.palette.graphics.Palette; -import com.google.android.material.bottomappbar.BottomAppBar; -import com.google.android.material.chip.Chip; import com.google.android.material.color.DynamicColors; import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import com.google.android.material.floatingactionbutton.FloatingActionButton; -import com.google.android.material.slider.Slider; import com.google.android.material.snackbar.Snackbar; -import com.hoko.blur.HokoBlur; -import com.hoko.blur.task.AsyncBlurTask; -import com.maary.shareas.helper.PreferencesHelper; import com.maary.shareas.R; +import com.maary.shareas.WallpaperViewModel; +import com.maary.shareas.databinding.ActivityMainBinding; +import com.maary.shareas.fragment.EditorFragment; +import com.maary.shareas.helper.PreferencesHelper; import com.maary.shareas.helper.Util; import com.maary.shareas.helper.Util_Files; -import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.util.Objects; import java.util.concurrent.ExecutorService; @@ -73,476 +54,168 @@ public class MainActivity extends AppCompatActivity { static final int MENU_RESET = 0; - //请求权限 - Bitmap bitmap; - Bitmap processed; - Bitmap blurProcessed; - Bitmap brightnessProcessed; - Bitmap raw; Rect cord; - int blurBias = 0; - int brightnessBias = 0; MaterialAlertDialogBuilder builder; - Boolean applyEditToLock = true; - Boolean applyEditToHome = true; - Boolean isProcessed = false; - - Boolean currentImageViewIsHome = true; - int device_height, device_width; - Palette.Swatch vibrant; - Palette.Swatch darkVibrant; Palette.Swatch dominant; - Palette.Swatch muted ; - - Intent intent; - - Snackbar snackbarReturnHome; + Palette.Swatch muted; + private ActivityMainBinding binding; @SuppressLint("RestrictedApi") @RequiresApi(api = Build.VERSION_CODES.S) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); + binding = ActivityMainBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); WindowCompat.setDecorFitsSystemWindows(getWindow(), false); - PreferencesHelper preferencesHelper = new PreferencesHelper(this); - - device_height = preferencesHelper.getHeight(); - if (device_height == -1){ - Point deviceBounds = Util.getDeviceBounds(MainActivity.this); - device_height = deviceBounds.y; - device_width = deviceBounds.x; - - preferencesHelper.setWidthAndHeight(device_width, device_height); - } else { - device_width = preferencesHelper.getWidth(); - } - - intent = getIntent(); + Intent intent = getIntent(); String action = intent.getAction(); String type = intent.getType(); final WallpaperManager wallpaperManager = WallpaperManager.getInstance(getApplicationContext()); - try { - if (Intent.ACTION_SEND.equals(action) && type != null) { - if (type.startsWith("image/")) { - bitmap = Util.getBitmap(intent, MainActivity.this); - } + if (!Intent.ACTION_SEND.equals(action) || type == null) return; + if (!type.startsWith("image/")) return; + Bitmap bitmap = Util.getBitmap(intent, MainActivity.this); + if (bitmap == null) return; + + WallpaperViewModel viewModel = new ViewModelProvider(this).get(WallpaperViewModel.class); + + viewModel.setBitmapRaw(bitmap, this); + viewModel.getViewerStateLiveData().observe(this, state -> binding.mainView.setImageBitmap(Objects.requireNonNull(viewModel.getDisplayBitmap()))); + viewModel.getCurrentBitmapStateLiveData().observe(this, state -> { + binding.mainView.setImageBitmap(Objects.requireNonNull(viewModel.getDisplayBitmap())); + binding.fab.setImageResource(viewModel.getFabResource()); + }); + viewModel.getInEditorLiveData().observe(this, inEditor -> { + if (inEditor) { + binding.bottomAppBarContainer.setVisibility(View.INVISIBLE); + } else { + binding.bottomAppBarContainer.setVisibility(View.VISIBLE); } + }); - if (bitmap != null) { - //Parent layout - ConstraintLayout container = findViewById(R.id.container); - //parent layout of bottomAppBar - CoordinatorLayout bottomAppBarContainer = findViewById(R.id.bottomAppBarContainer); - //progressBar usd in wallpaper setting process - - BottomAppBar bottomAppBar = findViewById(R.id.bottomAppBar); - - FloatingActionButton fab = findViewById(R.id.fab); - //make image preview scrollable. parent of imageView. - ScrollView verticalScrollView = new ScrollView(this); - HorizontalScrollView horizontalScrollView = new HorizontalScrollView(this); - //preview image - ImageView imageView = new ImageView(this); - //image ratio > device ratio? - Boolean isVertical = Util.isVertical(device_height, device_width, bitmap); - - ExecutorService executorService = Executors.newSingleThreadExecutor(); - - //Show Image - imageView.setScaleType(ImageView.ScaleType.FIT_XY); - imageView.setAdjustViewBounds(true); - imageView.setId(View.generateViewId()); - - //show image to imageview - int bitmap_full_width = bitmap.getWidth(); - int bitmap_full_height = bitmap.getHeight(); - int desired_width; - int desired_height; - - LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT - ); - - if (isVertical) { - desired_width = device_width; - float scale = (float) device_width / bitmap_full_width; - desired_height = (int) (scale * bitmap_full_height); - - verticalScrollView.setFillViewport(true); - container.addView(verticalScrollView, layoutParams); - verticalScrollView.addView(imageView, layoutParams); - } else { - desired_height = device_height; - float scale = (float) device_height / bitmap_full_height; - desired_width = (int) (scale * bitmap_full_width); - - horizontalScrollView.setFillViewport(true); - container.addView(horizontalScrollView, layoutParams); - horizontalScrollView.addView(imageView, layoutParams); - } + ExecutorService executorService = Executors.newSingleThreadExecutor(); - bitmap = Bitmap.createScaledBitmap(bitmap, desired_width, desired_height, true); - processed = bitmap; - - raw = bitmap; - imageView.setImageBitmap(bitmap); - - imageView.setOnClickListener(v -> { - if (!isProcessed || applyEditToHome == applyEditToLock) { - if (currentImageViewIsHome) { - currentImageViewIsHome = false; - fab.setImageResource(R.drawable.ic_lockscreen); - } else { - currentImageViewIsHome = true; - fab.setImageResource(R.drawable.ic_vertical); - } - } - if (applyEditToHome) { - if (currentImageViewIsHome) { - imageView.setImageBitmap(raw); - currentImageViewIsHome = false; - fab.setImageResource(R.drawable.ic_lockscreen); - } else { - imageView.setImageBitmap(bitmap); - currentImageViewIsHome = true; - fab.setImageResource(R.drawable.ic_vertical); - } - } - if (applyEditToLock) { - if (currentImageViewIsHome) { - imageView.setImageBitmap(bitmap); - currentImageViewIsHome = false; - fab.setImageResource(R.drawable.ic_lockscreen); - } else { - imageView.setImageBitmap(raw); - currentImageViewIsHome = true; - fab.setImageResource(R.drawable.ic_vertical); - } - } - }); + binding.mainView.setOnImageClickListener(v -> viewModel.currentBitmapToggle()); - Palette.from(bitmap).generate(palette -> { - // Access the colors from the palette - assert palette != null; - vibrant = palette.getVibrantSwatch(); - muted = palette.getMutedSwatch(); - dominant = palette.getDominantSwatch(); - darkVibrant = palette.getDarkVibrantSwatch(); + Palette.from(bitmap).generate(palette -> { + // Access the colors from the palette + assert palette != null; + vibrant = palette.getVibrantSwatch(); + muted = palette.getMutedSwatch(); + dominant = palette.getDominantSwatch(); + if (vibrant != null) { + binding.fab.setBackgroundTintList(ColorStateList.valueOf(vibrant.getRgb())); + } else { + binding.fab.setBackgroundColor(palette.getVibrantColor(getColor(R.color.colorAccent))); + } - if (vibrant != null) { - fab.setBackgroundTintList(ColorStateList.valueOf(vibrant.getRgb())); + }); + + //setup the fab click listener + binding.fab.setOnClickListener(view -> { + cord = binding.mainView.getVisibleRect(); + binding.bottomAppBar.getMenu().getItem(MENU_RESET).setEnabled(true); + AlertDialog dialog = builder.create(); + dialog.show(); + }); + + binding.fab.setOnLongClickListener(view -> { + cord = null; + AlertDialog dialog = builder.create(); + dialog.show(); + return false; + }); + + //TODO:ADD zoom (if possible + + Snackbar snackbarReturnHome = Snackbar.make(binding.container, getString(R.string.wallpaper_setted), Snackbar.LENGTH_INDEFINITE) + .setAction(getString(R.string.gohome), v -> returnToHomeScreen()); + + WallpaperManager.OnColorsChangedListener wallpaperChangedListener = new WallpaperManager.OnColorsChangedListener() { + @Override + public void onColorsChanged(@Nullable WallpaperColors colors, int which) { + wallpaperManager.removeOnColorsChangedListener(this); + snackbarReturnHome.show(); + } + }; + + wallpaperManager.addOnColorsChangedListener(wallpaperChangedListener, new Handler(Looper.getMainLooper())); + + //set bottomAppBar menu item + //tap blur and brightness button will disable other menu item + binding.bottomAppBar.setOnMenuItemClickListener(item -> { + if (item.getItemId() == R.id.edit) { +// viewModel.startEditing(); + loadFragment(new EditorFragment()); + } else if (item.getItemId() == R.id.reset) { + viewModel.restoreChanges(); + } + return true; + }); + + ActionMenuItemView resetItem = binding.bottomAppBar.findViewById(R.id.reset); + resetItem.setOnLongClickListener(view -> { + Intent intent1 = new Intent(getApplicationContext(), HistoryActivity.class); + startActivity(intent1); + return true; + }); + + Context context = DynamicColors.wrapContextIfAvailable( + MainActivity.this + ); + + //setup AlertDialog builder + builder = new MaterialAlertDialogBuilder(context); + builder.setTitle(R.string.setAs); + + String[] options = { + getResources().getString(R.string.home), + getResources().getString(R.string.lockscreen), + getResources().getString(R.string.homeAndLockscreen), + getResources().getString(R.string.use_others)}; + + builder.setItems(options, (dialog, which) -> executorService.execute(() -> { + try { + if (new PreferencesHelper(this).getSettingsHistory()) { + //若已经选择保存选项,弹出「设置图片为」选项之前保存图片 + if (checkPermission()) { + Bitmap currentWallpaper = ((BitmapDrawable) Objects.requireNonNull(wallpaperManager.getDrawable())).getBitmap(); + Util_Files.saveWallpaper(currentWallpaper, this); } else { - fab.setBackgroundColor(palette.getVibrantColor(getColor(R.color.colorAccent))); + Snackbar.make(binding.container, R.string.no_permission, Snackbar.LENGTH_SHORT) + .show(); } + } - - }); - - //setup the fab click listener - fab.setOnClickListener(view -> { - if (isVertical) { - int start = verticalScrollView.getScrollY(); - cord = new Rect(0, start, device_width, start + device_height); - } else { - int start = horizontalScrollView.getScrollX(); - cord = new Rect(start, 0, start + device_width, device_height); - } - bottomAppBar.getMenu().getItem(MENU_RESET).setEnabled(true); - AlertDialog dialog = builder.create(); - dialog.show(); - - }); - - fab.setOnLongClickListener(view -> { - cord = null; - AlertDialog dialog = builder.create(); - dialog.show(); - return false; - }); - - //TODO:ADD zoom (if possible - - WallpaperManager.OnColorsChangedListener wallpaperChangedListener = new WallpaperManager.OnColorsChangedListener() { - @Override - public void onColorsChanged(@Nullable WallpaperColors colors, int which) { - wallpaperManager.removeOnColorsChangedListener(this); - snackbarReturnHome.show(); + switch (which) { + case 0 -> wallpaperManager.setBitmap(viewModel.getBitmapHome(), cord, true, WallpaperManager.FLAG_SYSTEM); + case 1 -> wallpaperManager.setBitmap(viewModel.getBitmapLock(), cord, true, WallpaperManager.FLAG_LOCK); + case 2 -> { + wallpaperManager.setBitmap(viewModel.getBitmapHome(), cord, true, WallpaperManager.FLAG_SYSTEM); + wallpaperManager.setBitmap(viewModel.getBitmapLock(), cord, true, WallpaperManager.FLAG_LOCK); } - }; - - snackbarReturnHome = Snackbar.make(container, getString(R.string.wallpaper_setted), Snackbar.LENGTH_INDEFINITE) - .setAction(getString(R.string.gohome), v -> returnToHomeScreen()); - - wallpaperManager.addOnColorsChangedListener(wallpaperChangedListener, null); - - //set bottomAppBar menu item - //tap blur and brightness button will disable other menu item - - bottomAppBar.setOnMenuItemClickListener(item -> { - - if (item.getItemId() == R.id.edit) { - bottomAppBarContainer.setVisibility(View.INVISIBLE); - AlertDialog dialog; - - dialog = createSliderDialog(); - Objects.requireNonNull(dialog.getWindow()) - .clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); - dialog.getWindow().setGravity(Gravity.BOTTOM); - dialog.setCancelable(false); - - // 获取 Drawable 对象 - Drawable drawable = AppCompatResources.getDrawable(this, R.drawable.dialog_background); - - assert drawable != null; - Drawable modifiedDrawable = Objects.requireNonNull(drawable.getConstantState()).newDrawable().mutate(); - // 复制 Drawable 对象,以便进行修改 - int currentNightMode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; - switch (currentNightMode) { - case Configuration.UI_MODE_NIGHT_NO: - modifiedDrawable.setTint(Color.WHITE); - break; - case Configuration.UI_MODE_NIGHT_YES: - modifiedDrawable.setTint(Color.BLACK); - break; - } - - dialog.getWindow().setBackgroundDrawable(modifiedDrawable); - dialog.show(); - - Slider sliderBlur = dialog.findViewById(R.id.dialog_slider_blur); - assert sliderBlur != null; - sliderBlur.setLabelBehavior(LABEL_GONE); - sliderBlur.setTickVisible(false); - - Slider sliderBrightness = dialog.findViewById(R.id.dialog_slider_brightness); - assert sliderBrightness != null; - sliderBrightness.setLabelBehavior(LABEL_GONE); - sliderBrightness.setTickVisible(false); - - Chip chipLock = dialog.findViewById(R.id.chip_apply_lock); - Chip chipHome = dialog.findViewById(R.id.chip_apply_home); - - assert chipLock != null; - assert chipHome != null; - - if (vibrant != null) { - sliderBlur.setThumbTintList(ColorStateList.valueOf(vibrant.getRgb())); - sliderBlur.setTrackActiveTintList(ColorStateList.valueOf(vibrant.getRgb())); - sliderBrightness.setThumbTintList(ColorStateList.valueOf(vibrant.getRgb())); - sliderBrightness.setTrackActiveTintList(ColorStateList.valueOf(vibrant.getRgb())); - } - - chipLock.setChecked(applyEditToLock); - chipHome.setChecked(applyEditToHome); - - chipLock.setOnCheckedChangeListener((buttonView, isChecked) -> applyEditToLock = isChecked); - chipHome.setOnCheckedChangeListener((buttonView, isChecked) -> applyEditToHome = isChecked); - - sliderBlur.setValueFrom(0); - sliderBlur.setValueTo(30); - sliderBlur.setStepSize(1); - sliderBlur.setValue(blurBias); - - sliderBrightness.setValueFrom(-50); - sliderBrightness.setValueTo(50); - sliderBrightness.setStepSize(1); - sliderBrightness.setValue(brightnessBias); - - dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(view -> { - bitmap = processed; - isProcessed = true; - bottomAppBarContainer.setVisibility(View.VISIBLE); - bottomAppBar.getMenu().getItem(MENU_RESET).setEnabled(true); - blurBias = (int) sliderBlur.getValue(); - brightnessBias = (int) sliderBrightness.getValue(); - if (applyEditToHome == applyEditToLock) { - imageView.setImageBitmap(bitmap); - } else if (applyEditToHome) { - imageView.setImageBitmap(bitmap); - } else if (applyEditToLock) { - imageView.setImageBitmap(bitmap); - currentImageViewIsHome = false; - fab.setImageResource(R.drawable.ic_lockscreen); - //todo: set fab icon - } - - dialog.dismiss(); - }); - - dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(view -> { - sliderBlur.setValue(0.0f); - sliderBrightness.setValue(0.0f); - bitmap = raw; - isProcessed = false; - applyEditToLock = applyEditToHome = true; - chipHome.setChecked(true); - chipLock.setChecked(applyEditToLock); - imageView.setImageBitmap(raw); - Log.v("WALLP", "SET RAW NEUTRAL"); - - }); - - dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener(view -> { - bitmap = raw; - blurBias = 0; - brightnessBias = 0; - isProcessed = false; - applyEditToLock = applyEditToHome = true; - chipHome.setChecked(true); - chipLock.setChecked(applyEditToLock); - imageView.setImageBitmap(raw); - Log.v("WALLP", "SET RAW NEGATIVE"); - bottomAppBarContainer.setVisibility(View.VISIBLE); - dialog.dismiss(); - }); - - sliderBlur.addOnChangeListener((slider1, value, fromUser) -> { - Bitmap toProcess = bitmap; - if (brightnessProcessed != null) { - toProcess = brightnessProcessed; - } - HokoBlur.with(getApplicationContext()) - .radius((int) value) - .sampleFactor(1.0f) - .forceCopy(true) - .asyncBlur(toProcess, new AsyncBlurTask.Callback() { - @Override - public void onBlurSuccess(Bitmap bitmap) { - Log.v("WALLP", "BLURRRRRRRRRRRING"); - processed = bitmap; - blurProcessed = bitmap; - imageView.setImageBitmap(bitmap); - if (value == 0.0f && sliderBrightness.getValue() == 0.0f) { - imageView.setImageBitmap(bitmap); - Log.v("WALLP", "SET RAW IN BLUR"); - } - } - - @Override - public void onBlurFailed(Throwable error) { - - } - }); - - }); - sliderBrightness.addOnChangeListener((slider1, value, fromUser) -> { - Bitmap toProcess = bitmap; - if (blurProcessed != null) { - toProcess = blurProcessed; - } - Bitmap finalToProcess = toProcess; - new Thread(() -> { - processed = Util.adjustBrightness(finalToProcess, (int) value); - brightnessProcessed = processed; - Log.v("WALLP", "BRIGHTTTTTTTTTTING"); - - runOnUiThread(() -> { - imageView.setImageBitmap(processed); - if (value == 0.0f && sliderBlur.getValue() == 0.0f) { - imageView.setImageBitmap(bitmap); - Log.v("WALLP", "SET RAW IN BRIG"); - } - }); - }).start(); - - }); - } else if (item.getItemId() == R.id.reset) { - bitmap = raw; - blurBias = 0; - brightnessBias = 0; - cord = null; - isProcessed = false; - applyEditToLock = applyEditToHome = true; - imageView.setImageBitmap(bitmap); - if (isVertical) { - verticalScrollView.post(() -> verticalScrollView.scrollTo(0, 0)); - } else { - horizontalScrollView.post(() -> horizontalScrollView.scrollTo(0, 0)); - } - } - return true; - }); - - ActionMenuItemView resetItem = bottomAppBar.findViewById(R.id.reset); - resetItem.setOnLongClickListener(view -> { - Intent intent1 = new Intent(getApplicationContext(), HistoryActivity.class); - startActivity(intent1); - return true; - }); - - Context context = DynamicColors.wrapContextIfAvailable( - MainActivity.this - ); - - //setup AlertDialog builder - builder = new MaterialAlertDialogBuilder(context); - builder.setTitle(R.string.setAs); - - String[] options = { - getResources().getString(R.string.home), - getResources().getString(R.string.lockscreen), - getResources().getString(R.string.homeAndLockscreen), - getResources().getString(R.string.use_others)}; - - builder.setItems(options, (dialog, which) -> executorService.execute(() -> { - try { - if (new PreferencesHelper(this).getSettingsHistory()){ - //若已经选择保存选项,弹出「设置图片为」选项之前保存图片 - if (checkPermission()) { - Bitmap currentWallpaper = ((BitmapDrawable) wallpaperManager.getDrawable()).getBitmap(); - Util_Files.saveWallpaper(currentWallpaper, this); - }else { - Snackbar.make(container, R.string.no_permission, Snackbar.LENGTH_SHORT) - .show(); - } - } - - switch (which) { - case 0 -> { - wallpaperManager.setBitmap(bitmap, cord, true, WallpaperManager.FLAG_SYSTEM); - } - case 1 -> { - wallpaperManager.setBitmap(bitmap, cord, true, WallpaperManager.FLAG_LOCK); - } - case 2 -> { - if (applyEditToLock) { - wallpaperManager.setBitmap(bitmap, cord, true, WallpaperManager.FLAG_LOCK); - } else { - wallpaperManager.setBitmap(raw, cord, true, WallpaperManager.FLAG_LOCK); - } - if (applyEditToHome) { - wallpaperManager.setBitmap(bitmap, cord, true, WallpaperManager.FLAG_SYSTEM); - } else { - wallpaperManager.setBitmap(raw, cord, true, WallpaperManager.FLAG_SYSTEM); - } - } - //应对定制 Rom(如 Color OS)可能存在的魔改导致 "WallpaperManager.FLAG_LOCK | WallpaperManager.FLAG_SYSTEM" 参数失效的情况。 - case 3 -> { - shareBitmap(bitmap, getApplicationContext()); - } - default -> throw new IllegalStateException("Unexpected value: " + which); - } - } catch (IOException e) { - e.printStackTrace(); + case 3 -> { + Uri myImageFileUri = viewModel.getBitmapUri(getApplicationContext(), Objects.requireNonNull(getExternalCacheDir())); + shareUri(getApplicationContext(), myImageFileUri); } + default -> throw new IllegalStateException("Unexpected value: " + which); + } + } catch (IOException e) { + Log.e("ERROR", e.toString()); + } - })); + })); + + binding.bottomAppBarContainer.bringToFront(); - bottomAppBarContainer.bringToFront(); - } - } catch (IOException e) { - throw new RuntimeException(e); - } } - private Boolean checkPermission(){ + private Boolean checkPermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { Boolean hasStoragePermission = ContextCompat.checkSelfPermission( getApplicationContext(), @@ -563,47 +236,27 @@ private void returnToHomeScreen() { startActivity(intent); } - //用于亮度/模糊的 Slider Bar 对话框 - @SuppressLint("InflateParams") - private AlertDialog createSliderDialog() { - MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);//, R.style.TransparentDialogTheme); - LayoutInflater inflater = this.getLayoutInflater(); - - builder.setView(inflater.inflate(R.layout.layout_dialog_adjustment, null)) - .setPositiveButton(R.string.save, null) - .setNeutralButton(R.string.reset, null) - .setNegativeButton(R.string.cancel, null); - - return builder.create(); - } - - - - private void shareBitmap(@NonNull Bitmap bitmap, Context context) { - //---Save bitmap to external cache directory---// - //get cache directory - File cachePath = new File(getExternalCacheDir(), "my_images/"); - cachePath.mkdirs(); - - //create png file - File file = new File(cachePath, "Image_123.png"); - FileOutputStream fileOutputStream; - try { - fileOutputStream = new FileOutputStream(file); - bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream); - fileOutputStream.flush(); - fileOutputStream.close(); + public static class ShareReceiver extends BroadcastReceiver { - } catch (IOException e) { - e.printStackTrace(); + @Override + public void onReceive(Context context, Intent intent) { + Intent local = new Intent(); + local.setAction("done"); + context.sendBroadcast(local); } + } - //---Share File---// - //get file uri - Uri myImageFileUri = FileProvider.getUriForFile(this, getApplicationContext().getPackageName() + ".provider", file); + private void loadFragment(Fragment fragment) { + FragmentManager fragmentManager = getSupportFragmentManager(); + FragmentTransaction transaction = fragmentManager.beginTransaction(); + transaction.replace(R.id.editor_container, fragment); + transaction.addToBackStack(null); // 可选,用于返回栈管理 + transaction.commit(); + } + private void shareUri(Context context, Uri uri) { Intent sendIntent = new Intent(Intent.ACTION_ATTACH_DATA); - sendIntent.setDataAndType(myImageFileUri, "image/*"); + sendIntent.setDataAndType(uri, "image/*"); sendIntent.putExtra("mimeType", "image/*"); sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); @@ -617,14 +270,4 @@ private void shareBitmap(@NonNull Bitmap bitmap, Context context) { , pendingIntent.getIntentSender() )); } - - public static class ShareReceiver extends BroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - Intent local = new Intent(); - local.setAction("done"); - context.sendBroadcast(local); - } - } } diff --git a/app/src/main/java/com/maary/shareas/MediaStoreImage.kt b/app/src/main/java/com/maary/shareas/data/MediaStoreImage.kt similarity index 95% rename from app/src/main/java/com/maary/shareas/MediaStoreImage.kt rename to app/src/main/java/com/maary/shareas/data/MediaStoreImage.kt index 867a0dd..43b9bfb 100644 --- a/app/src/main/java/com/maary/shareas/MediaStoreImage.kt +++ b/app/src/main/java/com/maary/shareas/data/MediaStoreImage.kt @@ -1,4 +1,4 @@ -package com.maary.shareas +package com.maary.shareas.data import android.net.Uri import androidx.recyclerview.widget.DiffUtil diff --git a/app/src/main/java/com/maary/shareas/data/ViewerBitmap.kt b/app/src/main/java/com/maary/shareas/data/ViewerBitmap.kt new file mode 100644 index 0000000..f1cef4c --- /dev/null +++ b/app/src/main/java/com/maary/shareas/data/ViewerBitmap.kt @@ -0,0 +1,8 @@ +package com.maary.shareas.data + +import android.graphics.Bitmap + +data class ViewerBitmap( + var bitmapHome: Bitmap? = null, + var bitmapLock: Bitmap? = null +) \ No newline at end of file diff --git a/app/src/main/java/com/maary/shareas/fragment/EditorFragment.kt b/app/src/main/java/com/maary/shareas/fragment/EditorFragment.kt new file mode 100644 index 0000000..ab6630d --- /dev/null +++ b/app/src/main/java/com/maary/shareas/fragment/EditorFragment.kt @@ -0,0 +1,201 @@ +package com.maary.shareas.fragment + +import android.content.Context +import android.content.res.ColorStateList +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.activity.OnBackPressedCallback +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.activityViewModels +import androidx.fragment.app.commitNow +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import com.maary.shareas.WallpaperViewModel +import com.maary.shareas.databinding.FragmentEditorBinding +import com.maary.shareas.fragment.editor.BlurFragment +import com.maary.shareas.fragment.editor.BrightnessFragment +import com.maary.shareas.fragment.editor.PaintFragment +import com.maary.shareas.fragment.editor.UpscaleFragment +import kotlinx.coroutines.launch + +class EditorFragment : Fragment() { + + private val viewModel: WallpaperViewModel by activityViewModels() + private var _binding: FragmentEditorBinding? = null + private val binding get() = _binding!! + + private lateinit var onBackPressedCallback: OnBackPressedCallback + + private var isSaved = false + + override fun onAttach(context: Context) { + super.onAttach(context) + viewModel.startEditing() + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.currentBitmapState.collect { state -> + if (state == WallpaperViewModel.HOME) { + binding.appbarToggleGroup.check(binding.appbarButtonHome.id) + } + if (state == WallpaperViewModel.LOCK) { + binding.appbarToggleGroup.check(binding.appbarButtonLock.id) + } + } + } + } + + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.upscaleProgressState.collect { state -> + if (state == 100) { + binding.editorButtonApply.visibility = View.VISIBLE + } + } + } + } + + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.primaryColorState.collect { + if (viewModel.primary != null) { + val colorValue = viewModel.tertiary!! + val colorStateList = ColorStateList.valueOf(colorValue) + binding.editorButtonBlur.backgroundTintList = colorStateList + binding.editorButtonBrightness.backgroundTintList = colorStateList + binding.editorButtonFill.backgroundTintList = colorStateList + binding.editorButtonUpscale.backgroundTintList = colorStateList + binding.appbarButtonCancel.setBackgroundColor(viewModel.secondary!!) + binding.appbarButtonConfirm.setBackgroundColor(viewModel.secondary!!) + binding.editorButtonApply.setTextColor(viewModel.primary!!) + binding.editorButtonAbort.setTextColor(viewModel.primary!!) + binding.chipApplyHome.backgroundTintList = colorStateList + binding.chipApplyLock.backgroundTintList = colorStateList + } + } + } + } + + onBackPressedCallback = object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + if (!isSaved) viewModel.restoreChanges() + isEnabled = false + activity?.onBackPressedDispatcher?.onBackPressed() + } + } + requireActivity().onBackPressedDispatcher.addCallback(this, onBackPressedCallback) + + } + + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentEditorBinding.inflate(inflater, container, false) + ViewCompat.setOnApplyWindowInsetsListener(binding.main) { _, insets -> + val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) + binding.editorButtons.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) + insets + } + + val chipHome = binding.chipApplyHome + val chipLock = binding.chipApplyLock + + binding.appbarButtonHome.setOnClickListener { + viewModel.currentBitmap = WallpaperViewModel.HOME + } + + binding.appbarButtonLock.setOnClickListener { + viewModel.currentBitmap = WallpaperViewModel.LOCK + } + + binding.appbarButtonCancel.setOnClickListener { + viewModel.restoreChanges() + isSaved = false + if (binding.editorCard.visibility == View.VISIBLE) { + requireActivity().onBackPressedDispatcher.onBackPressed() + } + activity?.onBackPressedDispatcher?.onBackPressed() + } + + binding.appbarButtonConfirm.setOnClickListener { + viewModel.saveEdit() + isSaved = true + if (binding.editorCard.visibility == View.VISIBLE) { + requireActivity().onBackPressedDispatcher.onBackPressed() + } + activity?.onBackPressedDispatcher?.onBackPressed() + } + + binding.editorButtonApply.setOnClickListener { + if (!chipHome.isChecked) viewModel.abortEditHome() + if (!chipLock.isChecked) viewModel.abortEditLock() + viewModel.saveEdit() + requireActivity().onBackPressedDispatcher.onBackPressed() + } + + binding.editorButtonAbort.setOnClickListener { + viewModel.abortEdit() + requireActivity().onBackPressedDispatcher.onBackPressed() + } + + binding.editorButtonBlur.setOnClickListener { + loadFragment(BlurFragment()) + } + + binding.editorButtonBrightness.setOnClickListener { + loadFragment(BrightnessFragment()) + } + + binding.editorButtonFill.setOnClickListener { + loadFragment(PaintFragment()) + } + + binding.editorButtonUpscale.setOnClickListener { + loadFragment(UpscaleFragment()) + } + + // Inflate the layout for this fragment + return binding.root + } + + override fun onDetach() { + super.onDetach() + viewModel.finishEditing() + onBackPressedCallback.remove() + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + private fun loadFragment(fragment: Fragment) { + viewModel.abortEdit() + val fragmentManager: FragmentManager = childFragmentManager + fragmentManager.commitNow { + replace(binding.editorFragmentContainer.id, fragment) + } + if (fragment is UpscaleFragment) { + binding.editorButtonApply.visibility = View.INVISIBLE + binding.chipApplyHome.isClickable = false + binding.chipApplyLock.isClickable = false + } else { + binding.editorButtonApply.visibility = View.VISIBLE + binding.chipApplyHome.isClickable = true + binding.chipApplyLock.isClickable = true + } + binding.editorCard.visibility = View.VISIBLE + } +} \ No newline at end of file diff --git a/app/src/main/java/com/maary/shareas/fragment/WelcomeFinishFragment.kt b/app/src/main/java/com/maary/shareas/fragment/WelcomeFinishFragment.kt index 80cab0c..c8ede6d 100644 --- a/app/src/main/java/com/maary/shareas/fragment/WelcomeFinishFragment.kt +++ b/app/src/main/java/com/maary/shareas/fragment/WelcomeFinishFragment.kt @@ -20,27 +20,7 @@ import com.maary.shareas.activity.StartActivity import com.maary.shareas.databinding.FragmentWelcomeFinishBinding import kotlinx.coroutines.launch -// TODO: Rename parameter arguments, choose names that match -// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER -private const val ARG_PARAM1 = "param1" -private const val ARG_PARAM2 = "param2" - -/** - * A simple [Fragment] subclass. - * Use the [WelcomeFinishFragment.newInstance] factory method to - * create an instance of this fragment. - */ class WelcomeFinishFragment : Fragment() { - // TODO: Rename and change types of parameters - private var param1: String? = null - private var param2: String? = null - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - arguments?.let { - param1 = it.getString(ARG_PARAM1) - param2 = it.getString(ARG_PARAM2) - } - } private var _binding: FragmentWelcomeFinishBinding? = null // This property is only valid between onCreateView and @@ -85,24 +65,4 @@ class WelcomeFinishFragment : Fragment() { super.onDestroyView() _binding = null } - - companion object { - /** - * Use this factory method to create a new instance of - * this fragment using the provided parameters. - * - * @param param1 Parameter 1. - * @param param2 Parameter 2. - * @return A new instance of fragment WelcomeFinishFragment. - */ - // TODO: Rename and change types and number of parameters - @JvmStatic - fun newInstance(param1: String, param2: String) = - WelcomeFinishFragment().apply { - arguments = Bundle().apply { - putString(ARG_PARAM1, param1) - putString(ARG_PARAM2, param2) - } - } - } } \ No newline at end of file diff --git a/app/src/main/java/com/maary/shareas/fragment/WelcomeHistoryFragment.kt b/app/src/main/java/com/maary/shareas/fragment/WelcomeHistoryFragment.kt index 017aaf0..8f83dee 100644 --- a/app/src/main/java/com/maary/shareas/fragment/WelcomeHistoryFragment.kt +++ b/app/src/main/java/com/maary/shareas/fragment/WelcomeHistoryFragment.kt @@ -33,28 +33,7 @@ import com.maary.shareas.databinding.FragmentWelcomeHistoryBinding import kotlinx.coroutines.launch import java.util.concurrent.Executor -// TODO: Rename parameter arguments, choose names that match -// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER -private const val ARG_PARAM1 = "param1" -private const val ARG_PARAM2 = "param2" - -/** - * A simple [Fragment] subclass. - * Use the [WelcomeHistoryFragment.newInstance] factory method to - * create an instance of this fragment. - */ class WelcomeHistoryFragment : Fragment() { - // TODO: Rename and change types of parameters - private var param1: String? = null - private var param2: String? = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - arguments?.let { - param1 = it.getString(ARG_PARAM1) - param2 = it.getString(ARG_PARAM2) - } - } private var _binding: FragmentWelcomeHistoryBinding? = null // This property is only valid between onCreateView and @@ -199,26 +178,6 @@ class WelcomeHistoryFragment : Fragment() { _binding = null } - companion object { - /** - * Use this factory method to create a new instance of - * this fragment using the provided parameters. - * - * @param param1 Parameter 1. - * @param param2 Parameter 2. - * @return A new instance of fragment WelcomeHistoryFragment. - */ - // TODO: Rename and change types and number of parameters - @JvmStatic - fun newInstance(param1: String, param2: String) = - WelcomeHistoryFragment().apply { - arguments = Bundle().apply { - putString(ARG_PARAM1, param1) - putString(ARG_PARAM2, param2) - } - } - } - private fun checkPermission(): Boolean{ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { val hasStoragePermission = ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_MEDIA_IMAGES) == PackageManager.PERMISSION_GRANTED diff --git a/app/src/main/java/com/maary/shareas/fragment/WelcomeSystemFragment.kt b/app/src/main/java/com/maary/shareas/fragment/WelcomeSystemFragment.kt index a4f8184..6a8317a 100644 --- a/app/src/main/java/com/maary/shareas/fragment/WelcomeSystemFragment.kt +++ b/app/src/main/java/com/maary/shareas/fragment/WelcomeSystemFragment.kt @@ -20,28 +20,8 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton import com.maary.shareas.R import com.maary.shareas.databinding.FragmentWelcomeSystemBinding -// TODO: Rename parameter arguments, choose names that match -// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER -private const val ARG_PARAM1 = "param1" -private const val ARG_PARAM2 = "param2" - -/** - * A simple [Fragment] subclass. - * Use the [WelcomeSystemFragment.newInstance] factory method to - * create an instance of this fragment. - */ class WelcomeSystemFragment : Fragment() { - // TODO: Rename and change types of parameters - private var param1: String? = null - private var param2: String? = null - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - arguments?.let { - param1 = it.getString(ARG_PARAM1) - param2 = it.getString(ARG_PARAM2) - } - } private var _binding: FragmentWelcomeSystemBinding? = null // This property is only valid between onCreateView and @@ -105,24 +85,4 @@ class WelcomeSystemFragment : Fragment() { binding.textWelcomeSystemPermission1.helperText = getText(R.string.permission_got) } } - - companion object { - /** - * Use this factory method to create a new instance of - * this fragment using the provided parameters. - * - * @param param1 Parameter 1. - * @param param2 Parameter 2. - * @return A new instance of fragment WelcomeSystemFragment. - */ - // TODO: Rename and change types and number of parameters - @JvmStatic - fun newInstance(param1: String, param2: String) = - WelcomeSystemFragment().apply { - arguments = Bundle().apply { - putString(ARG_PARAM1, param1) - putString(ARG_PARAM2, param2) - } - } - } } \ No newline at end of file diff --git a/app/src/main/java/com/maary/shareas/fragment/editor/BlurFragment.kt b/app/src/main/java/com/maary/shareas/fragment/editor/BlurFragment.kt new file mode 100644 index 0000000..46ad2a8 --- /dev/null +++ b/app/src/main/java/com/maary/shareas/fragment/editor/BlurFragment.kt @@ -0,0 +1,83 @@ +package com.maary.shareas.fragment.editor + +import android.content.res.ColorStateList +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.activity.OnBackPressedCallback +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import com.google.android.material.slider.Slider +import com.maary.shareas.WallpaperViewModel +import com.maary.shareas.databinding.FragmentBlurBinding +import kotlinx.coroutines.launch + +class BlurFragment : Fragment() { + + private val viewModel: WallpaperViewModel by activityViewModels() + private var _binding: FragmentBlurBinding? = null + private val binding get() = _binding!! + private lateinit var onBackPressedCallback: OnBackPressedCallback + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + onBackPressedCallback = object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + viewModel.abortEdit() + requireParentFragment().childFragmentManager.beginTransaction().remove(this@BlurFragment).commit() + requireParentFragment().childFragmentManager.popBackStack() + // 获取包含当前 Fragment 的布局 + val containingLayout = requireView().parent.parent.parent as? View + // 如果布局不为空,则隐藏布局 + containingLayout?.visibility = View.INVISIBLE + isEnabled = false + } + } + requireActivity().onBackPressedDispatcher.addCallback(this, onBackPressedCallback) + + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.primaryColorState.collect { + if (viewModel.primary != null) { + val primaryValue = viewModel.primary!! + val primaryStateList = ColorStateList.valueOf(primaryValue) + val secondaryValue = viewModel.secondary!! + val secondaryStateList = ColorStateList.valueOf(secondaryValue) + binding.adjustmentSlider.thumbTintList = primaryStateList + binding.adjustmentSlider.trackActiveTintList = secondaryStateList + } + } + } + } + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View { + // Inflate the layout for this fragment + _binding = FragmentBlurBinding.inflate(inflater, container, false) + val sliderBlur = binding.adjustmentSlider + + sliderBlur.valueFrom = 0f + sliderBlur.valueTo = 25f + sliderBlur.stepSize = 1f + sliderBlur.addOnChangeListener(Slider.OnChangeListener { _: Slider?, value: Float, _: Boolean -> + viewModel.editBlur(requireActivity(), value) + }) + return binding.root + } + + override fun onDetach() { + super.onDetach() + onBackPressedCallback.remove() + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} \ No newline at end of file diff --git a/app/src/main/java/com/maary/shareas/fragment/editor/BrightnessFragment.kt b/app/src/main/java/com/maary/shareas/fragment/editor/BrightnessFragment.kt new file mode 100644 index 0000000..39e2184 --- /dev/null +++ b/app/src/main/java/com/maary/shareas/fragment/editor/BrightnessFragment.kt @@ -0,0 +1,88 @@ +package com.maary.shareas.fragment.editor + +import android.content.res.ColorStateList +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.activity.OnBackPressedCallback +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import com.google.android.material.slider.Slider +import com.maary.shareas.WallpaperViewModel +import com.maary.shareas.databinding.FragmentBrightnessBinding +import kotlinx.coroutines.launch + +class BrightnessFragment : Fragment() { + + private val viewModel: WallpaperViewModel by activityViewModels() + private var _binding: FragmentBrightnessBinding? = null + private val binding get() = _binding!! + private lateinit var onBackPressedCallback: OnBackPressedCallback + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + onBackPressedCallback = object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + viewModel.abortEdit() + requireParentFragment().childFragmentManager.beginTransaction().remove(this@BrightnessFragment).commit() + requireParentFragment().childFragmentManager.popBackStack() + // 获取包含当前 Fragment 的布局 + val containingLayout = requireView().parent.parent.parent as? View + // 如果布局不为空,则隐藏布局 + containingLayout?.visibility = View.INVISIBLE + isEnabled = false + } + } + requireActivity().onBackPressedDispatcher.addCallback(this, onBackPressedCallback) + + + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.primaryColorState.collect { + if (viewModel.primary != null) { + val primaryValue = viewModel.primary!! + val primaryStateList = ColorStateList.valueOf(primaryValue) + val secondaryValue = viewModel.secondary!! + val secondaryStateList = ColorStateList.valueOf(secondaryValue) + binding.adjustmentSlider.thumbTintList = primaryStateList + binding.adjustmentSlider.trackActiveTintList = secondaryStateList + } + } + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentBrightnessBinding.inflate(inflater, container, false) + + val sliderBrightness = binding.adjustmentSlider + sliderBrightness.valueFrom = -50f + sliderBrightness.valueTo = 50f + sliderBrightness.stepSize = 1f + sliderBrightness.value = 0f + + sliderBrightness.addOnChangeListener(Slider.OnChangeListener { _, value, _ -> + viewModel.editBrightness(value) + }) + + return binding.root + } + + override fun onDetach() { + super.onDetach() + onBackPressedCallback.remove() + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} \ No newline at end of file diff --git a/app/src/main/java/com/maary/shareas/fragment/editor/PaintFragment.kt b/app/src/main/java/com/maary/shareas/fragment/editor/PaintFragment.kt new file mode 100644 index 0000000..15823b1 --- /dev/null +++ b/app/src/main/java/com/maary/shareas/fragment/editor/PaintFragment.kt @@ -0,0 +1,219 @@ +package com.maary.shareas.fragment.editor + +import android.content.res.ColorStateList +import android.graphics.Color +import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.EditorInfo +import android.view.inputmethod.InputMethodManager +import androidx.activity.OnBackPressedCallback +import androidx.core.content.ContextCompat.getSystemService +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import com.google.android.material.button.MaterialButton +import com.maary.shareas.R +import com.maary.shareas.WallpaperViewModel +import com.maary.shareas.databinding.FragmentPaintBinding +import kotlinx.coroutines.launch + +class PaintFragment : Fragment() { + + private var _binding: FragmentPaintBinding? = null + private val binding get() = _binding!! + private val viewModel: WallpaperViewModel by activityViewModels() + + private var position = 4 //viewModel.CENTER + private lateinit var onBackPressedCallback: OnBackPressedCallback + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + onBackPressedCallback = object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + viewModel.abortEdit() + requireParentFragment().childFragmentManager.beginTransaction().remove(this@PaintFragment).commit() + requireParentFragment().childFragmentManager.popBackStack() + // 获取包含当前 Fragment 的布局 + val containingLayout = requireView().parent.parent.parent as? View + // 如果布局不为空,则隐藏布局 + containingLayout?.visibility = View.INVISIBLE + isEnabled = false + } + } + requireActivity().onBackPressedDispatcher.addCallback(this, onBackPressedCallback) + + + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.primaryColorState.collect { + if (viewModel.primary != null) { + val colorValue = viewModel.primary!! + val colorStateList = ColorStateList.valueOf(colorValue) + binding.buttonPaint.iconTint = colorStateList + } + } + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentPaintBinding.inflate(inflater, container, false) + + var paintType = 0 + + binding.buttonAlignCenter.isChecked = true + + binding.hexEditText.addTextChangedListener(object: TextWatcher { + override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { + } + + override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { + } + + override fun afterTextChanged(p0: Editable?) { + if (p0?.length == 6){ + binding.hexEditText.onEditorAction(EditorInfo.IME_ACTION_DONE) + paintType = binding.buttonColorCustom.id + checkButton(paintType) + binding.buttonColorCustom.setBackgroundColor(Color.parseColor("#${binding.hexEditText.text}")) + } + } + }) + + binding.buttonsAlignment.addOnButtonCheckedListener { _, checkId, isChecked -> + if (isChecked) { + when (checkId) { + R.id.button_align_left -> position = WallpaperViewModel.LEFT + R.id.button_align_top -> position = WallpaperViewModel.TOP + R.id.button_align_center -> position = WallpaperViewModel.CENTER + R.id.button_align_bottom -> position = WallpaperViewModel.BOTTOM + R.id.button_align_right -> position = WallpaperViewModel.RIGHT + } + } + + } + + val colors = viewModel.extractTopColorsFromBitmap() + binding.buttonColor1.setBackgroundColor(colors[0]) + binding.buttonColor2.setBackgroundColor(colors[1]) + binding.buttonColor3.setBackgroundColor(colors[2]) + binding.buttonColor4.setBackgroundColor(colors[3]) + binding.buttonColor5.setBackgroundColor(colors[4]) + + binding.buttonColorCustom.setOnClickListener { + if (binding.hexEditText.text.isNullOrEmpty()) { + binding.hexEditText.requestFocus() + // 显示键盘 + val imm = getSystemService(requireContext(), InputMethodManager::class.java) as InputMethodManager + imm.showSoftInput(binding.hexEditText, InputMethodManager.SHOW_IMPLICIT) + } else { + paintType = it.id + setZoom() + } + } + + binding.buttonColor1.setOnClickListener { + paintType = it.id + setZoom() + checkButton(it.id) + } + binding.buttonColor2.setOnClickListener { + paintType = it.id + setZoom() + checkButton(it.id) + } + binding.buttonColor3.setOnClickListener { + paintType = it.id + setZoom() + checkButton(it.id) + } + binding.buttonColor4.setOnClickListener { + paintType = it.id + setZoom() + checkButton(it.id) + } + binding.buttonColor5.setOnClickListener { + paintType = it.id + setZoom() + checkButton(it.id) + } + + binding.buttonBlur.setOnClickListener { + paintType = it.id + if (binding.scaleEditText.text.isNullOrEmpty()) { + binding.scaleEditText.setText( + resources.getStringArray(R.array.zoom_scales)[3], + false + ) + } + checkButton(it.id) + } + + binding.buttonPaint.setOnClickListener { + val zoom = binding.scaleEditText.text.toString().toFloat() + when (paintType) { + R.id.button_color1 -> viewModel.paintColor(position, colors[0], zoom) + R.id.button_color2 -> viewModel.paintColor(position, colors[1], zoom) + R.id.button_color3 -> viewModel.paintColor(position, colors[2], zoom) + R.id.button_color4 -> viewModel.paintColor(position, colors[3], zoom) + R.id.button_color5 -> viewModel.paintColor(position, colors[4], zoom) + R.id.button_color_custom -> + viewModel.paintColor(position, Color.parseColor("#${binding.hexEditText.text}"), zoom) + R.id.button_blur -> viewModel.paintBlur(position, 16, requireContext(), zoom) + + } + } + + // Inflate the layout for this fragment + return binding.root + } + + private fun checkButton(buttonId: Int) { + val buttons = mutableListOf( + R.id.button_blur, + R.id.button_color1, + R.id.button_color2, + R.id.button_color3, + R.id.button_color4, + R.id.button_color5, + R.id.button_color_custom) + + for (button in buttons) { + if (button == buttonId) { + binding.root.findViewById(button).setIconResource(R.drawable.ic_done) + } else { + if (button == R.id.button_blur) { + binding.root.findViewById(button).setIconResource(R.drawable.ic_blur_16) + } else { + binding.root.findViewById(button).icon = null + } + } + } + } + + override fun onDetach() { + super.onDetach() + onBackPressedCallback.remove() + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + private fun setZoom() { + if (binding.scaleEditText.text.isNullOrEmpty()) { + binding.scaleEditText.setText(resources.getStringArray(R.array.zoom_scales)[5], false) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/maary/shareas/fragment/editor/UpscaleFragment.kt b/app/src/main/java/com/maary/shareas/fragment/editor/UpscaleFragment.kt new file mode 100644 index 0000000..dbb6f61 --- /dev/null +++ b/app/src/main/java/com/maary/shareas/fragment/editor/UpscaleFragment.kt @@ -0,0 +1,116 @@ +package com.maary.shareas.fragment.editor + +import android.content.res.ColorStateList +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.activity.OnBackPressedCallback +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import com.maary.shareas.R +import com.maary.shareas.WallpaperViewModel +import com.maary.shareas.databinding.FragmentUpscaleBinding +import kotlinx.coroutines.launch + +class UpscaleFragment : Fragment() { + + private val viewModel: WallpaperViewModel by activityViewModels() + private var _binding: FragmentUpscaleBinding? = null + private val binding get() = _binding!! + + private lateinit var onBackPressedCallback: OnBackPressedCallback + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + onBackPressedCallback = object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + viewModel.abortEdit() + requireParentFragment().childFragmentManager.beginTransaction().remove(this@UpscaleFragment).commit() + requireParentFragment().childFragmentManager.popBackStack() + // 获取包含当前 Fragment 的布局 + val containingLayout = requireView().parent.parent.parent as? View + // 如果布局不为空,则隐藏布局 + containingLayout?.visibility = View.INVISIBLE + isEnabled = false + } + } + requireActivity().onBackPressedDispatcher.addCallback(this, onBackPressedCallback) + + + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.upscaleProgressState.collect { state -> + binding.progressUpscale.setProgressCompat(state, true) + if (state == 100) { + viewModel.upscaleToggle = !viewModel.upscaleToggle + } + } + } + } + + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.upscaleToggleState.collect { state -> + when (state) { + true -> { + binding.buttonUpscaleToggle.setIconResource(R.drawable.ic_action_close) + binding.progressUpscale.isIndeterminate = true + viewModel.upscale(requireContext(), binding.menuChooseModelTextview.text.toString()) + } + false -> { + binding.buttonUpscaleToggle.setIconResource(R.drawable.ic_play) + binding.progressUpscale.setProgressCompat(0, true) + } + } + } + } + } + + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.primaryColorState.collect { + if (viewModel.primary != null) { + val primaryValue = viewModel.primary!! + val primaryStateList = ColorStateList.valueOf(primaryValue) + val tertiaryValue = viewModel.tertiary!! + val tertiaryStateList = ColorStateList.valueOf(tertiaryValue) + binding.buttonUpscaleToggle.iconTint = primaryStateList + binding.betaIcon.iconTint = tertiaryStateList + binding.betaIcon.setTextColor(tertiaryValue) + } + } + } + } + + } + + override fun onDetach() { + super.onDetach() + onBackPressedCallback.remove() + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentUpscaleBinding.inflate(inflater, container, false) + + binding.menuChooseModelTextview.setText(resources.getStringArray(R.array.model_names)[2], false) + + binding.buttonUpscaleToggle.setOnClickListener { + viewModel.upscaleToggle = !viewModel.upscaleToggle + } + + return binding.root + } +} \ No newline at end of file diff --git a/app/src/main/java/com/maary/shareas/helper/SuperResPerformer.kt b/app/src/main/java/com/maary/shareas/helper/SuperResPerformer.kt new file mode 100644 index 0000000..492268c --- /dev/null +++ b/app/src/main/java/com/maary/shareas/helper/SuperResPerformer.kt @@ -0,0 +1,60 @@ +package com.maary.shareas.helper + +import ai.onnxruntime.OnnxJavaType +import ai.onnxruntime.OnnxTensor +import ai.onnxruntime.OrtEnvironment +import ai.onnxruntime.OrtSession +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.util.Log +import java.io.InputStream +import java.nio.ByteBuffer +import java.util.Collections + +internal data class Result( + var outputBitmap: Bitmap? = null +) + +internal class SuperResPerformer { + + fun upscale(inputStream: InputStream, ortEnv: OrtEnvironment, ortSession: OrtSession): Result { + val result = Result() + + // Step 1: convert image into byte array (raw image bytes) + val rawImageBytes = inputStream.readBytes() + + // Step 2: get the shape of the byte array and make ort tensor + val shape = longArrayOf(rawImageBytes.size.toLong()) + + val inputTensor = OnnxTensor.createTensor( + ortEnv, + ByteBuffer.wrap(rawImageBytes), + shape, + OnnxJavaType.UINT8 + ) + + inputTensor.use { + // Step 3: call ort inferenceSession run + Log.v("WVM", "RUN") + + val output = ortSession.run(Collections.singletonMap("image", inputTensor)) + Log.v("WVM", "RUN FINISHED") + + + // Step 4: output analysis + output.use { + val rawOutput = (output?.get(0)?.value) as ByteArray + val outputImageBitmap = + byteArrayToBitmap(rawOutput) + + // Step 5: set output result + result.outputBitmap = outputImageBitmap + } + } + return result + } + + private fun byteArrayToBitmap(data: ByteArray): Bitmap { + return BitmapFactory.decodeByteArray(data, 0, data.size) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/maary/shareas/helper/Util.java b/app/src/main/java/com/maary/shareas/helper/Util.java index 7fbec3e..a21c43f 100644 --- a/app/src/main/java/com/maary/shareas/helper/Util.java +++ b/app/src/main/java/com/maary/shareas/helper/Util.java @@ -8,47 +8,60 @@ import android.graphics.Canvas; import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; -import android.graphics.ImageDecoder; import android.graphics.Paint; import android.graphics.Point; import android.net.Uri; import android.os.Build; -import android.provider.MediaStore; import android.util.DisplayMetrics; import android.view.WindowMetrics; -import java.io.IOException; +import java.io.FileNotFoundException; import java.io.InputStream; public class Util { - public static Bitmap getBitmap(Intent intent, Context context) throws IOException { + public static Bitmap getBitmap(Intent intent, Context context) { Uri imageUri = intent.getParcelableExtra(Intent.EXTRA_STREAM); if (imageUri != null) { - InputStream inputStream = context.getContentResolver().openInputStream(imageUri); + InputStream inputStream; + try { + inputStream = context.getContentResolver().openInputStream(imageUri); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } return BitmapFactory.decodeStream(inputStream); } else return null; } public static Point getDeviceBounds(Context context) { - WindowMetrics windowMetrics; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - windowMetrics = ((Activity)context).getWindowManager().getMaximumWindowMetrics(); - return new Point(windowMetrics.getBounds().width(), windowMetrics.getBounds().height()); - }else { - DisplayMetrics displayMetrics = new DisplayMetrics(); - ((Activity)context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); - int height = displayMetrics.heightPixels; - int width = displayMetrics.widthPixels; - return new Point(width, height); + PreferencesHelper preferencesHelper = new PreferencesHelper(context); + int device_height, device_width; + + device_height = preferencesHelper.getHeight(); + if (device_height == -1){ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + WindowMetrics windowMetrics = ((Activity)context).getWindowManager().getMaximumWindowMetrics(); + device_height = windowMetrics.getBounds().height(); + device_width = windowMetrics.getBounds().width(); + }else { + DisplayMetrics displayMetrics = new DisplayMetrics(); + ((Activity)context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); + device_height = displayMetrics.heightPixels; + device_width = displayMetrics.widthPixels; + } + preferencesHelper.setWidthAndHeight(device_width, device_height); + } else { + device_width = preferencesHelper.getWidth(); } + return new Point(device_width, device_height); + } - public static Boolean isVertical(int dheight, int dwidth, Bitmap bitmap) { + public static Boolean isVertical(int dHeight, int dWidth, Bitmap bitmap) { int bitmap_full_width = bitmap.getWidth(); int bitmap_full_height = bitmap.getHeight(); - double device_scale = (double) dheight / dwidth; + double device_scale = (double) dHeight / dWidth; double bitmap_scale = (double) bitmap_full_height / bitmap_full_width; return device_scale < bitmap_scale; diff --git a/app/src/main/java/com/maary/shareas/helper/Util_Files.java b/app/src/main/java/com/maary/shareas/helper/Util_Files.java index ea73885..81f8870 100644 --- a/app/src/main/java/com/maary/shareas/helper/Util_Files.java +++ b/app/src/main/java/com/maary/shareas/helper/Util_Files.java @@ -7,11 +7,13 @@ import android.net.Uri; import android.os.Environment; import android.provider.MediaStore; +import android.util.Log; + +import androidx.annotation.NonNull; import java.io.File; import java.io.IOException; import java.io.OutputStream; -import java.util.ArrayList; import java.util.Calendar; public class Util_Files { @@ -19,18 +21,7 @@ public class Util_Files { static String customDir = Environment.DIRECTORY_PICTURES + File.separator + "Wallpaper History"; public static void saveWallpaper(Bitmap bitmap, Activity activity){ - Calendar calendar = Calendar.getInstance(); - String fileName = "WLP_" + - (calendar.get(Calendar.YEAR) - 1900) + - calendar.get(Calendar.MONTH) + - calendar.get(Calendar.DAY_OF_MONTH) + - calendar.get(Calendar.HOUR_OF_DAY) + - calendar.get(Calendar.MINUTE) + - calendar.get(Calendar.MILLISECOND); - final ContentValues contentValues = new ContentValues(); - contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName); - contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/png"); - contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, customDir); + final ContentValues contentValues = getContentValues(); final ContentResolver contentResolver = activity.getContentResolver(); Uri uri; @@ -47,7 +38,24 @@ public static void saveWallpaper(Bitmap bitmap, Activity activity){ } outputStream.close(); } catch (IOException e) { - e.printStackTrace(); + Log.e("WPT", e.toString()); } } + + @NonNull + private static ContentValues getContentValues() { + Calendar calendar = Calendar.getInstance(); + String fileName = "WLP_" + + (calendar.get(Calendar.YEAR) - 1900) + + calendar.get(Calendar.MONTH) + + calendar.get(Calendar.DAY_OF_MONTH) + + calendar.get(Calendar.HOUR_OF_DAY) + + calendar.get(Calendar.MINUTE) + + calendar.get(Calendar.MILLISECOND); + final ContentValues contentValues = new ContentValues(); + contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName); + contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/png"); + contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, customDir); + return contentValues; + } } diff --git a/app/src/main/java/com/maary/shareas/view/ScrollableImageView.kt b/app/src/main/java/com/maary/shareas/view/ScrollableImageView.kt new file mode 100644 index 0000000..5a38365 --- /dev/null +++ b/app/src/main/java/com/maary/shareas/view/ScrollableImageView.kt @@ -0,0 +1,97 @@ +package com.maary.shareas.view + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Rect +import android.net.Uri +import android.util.AttributeSet +import android.util.Log +import android.widget.HorizontalScrollView +import android.widget.ImageView +import android.widget.ScrollView + +class ScrollableImageView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : ScrollView(context, attrs, defStyleAttr) { + + private lateinit var horizontalView: CustomHorizontalView + private lateinit var imageView: ImageView + + init { + initHorizontalView(context) + } + + private fun initHorizontalView(context: Context) { + horizontalView = CustomHorizontalView(context) + addView(horizontalView) + imageView = ImageView(context) + horizontalView.addView(imageView) + } + + fun setImageBitmap(bitmap: Bitmap) { + imageView.setImageBitmap(bitmap) + imageView.scaleType = ImageView.ScaleType.FIT_XY + } + + fun setImageUri(uri: Uri) { + imageView.setImageURI(uri) + imageView.scaleType = ImageView.ScaleType.FIT_XY + } + + fun setOnImageClickListener(listener: OnClickListener) { + imageView.setOnClickListener(listener) + } + + fun getVisibleBitmap(): Bitmap { + // 获取 ScrollView 和 HorizontalView 的滚动距离 + val scrollX = scrollX + val scrollY = scrollY + val horizontalScrollX = horizontalView.scrollX + val horizontalScrollY = horizontalView.scrollY + + // 计算可视部分的矩形区域 + val visibleRect = Rect(scrollX + horizontalScrollX, scrollY + horizontalScrollY, + scrollX + horizontalScrollX + width, scrollY + horizontalScrollY + height) + + // 创建与可视部分尺寸相同的 Bitmap + val bitmap = Bitmap.createBitmap(visibleRect.width(), visibleRect.height(), Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + + // 将 Canvas 移动到 ScrollView 和 HorizontalView 的滚动位置 + canvas.translate(-visibleRect.left.toFloat(), -visibleRect.top.toFloat()) + + // 绘制 ScrollView 和 HorizontalView 可视部分的内容 + draw(canvas) + + return bitmap + } + + fun getVisibleRect(): Rect { + // 获取 ScrollView 和 HorizontalView 的滚动距离 + val scrollX = scrollX + val scrollY = scrollY + val horizontalScrollX = horizontalView.scrollX + val horizontalScrollY = horizontalView.scrollY + + // 计算可视部分的矩形区域 + return Rect(scrollX + horizontalScrollX, scrollY + horizontalScrollY, + scrollX + horizontalScrollX + width, scrollY + horizontalScrollY + height) + + } + + + inner class CustomHorizontalView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 + ) : HorizontalScrollView(context, attrs, defStyleAttr) { + + init { + isHorizontalScrollBarEnabled = false + } + + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_2x.xml b/app/src/main/res/drawable/ic_2x.xml new file mode 100644 index 0000000..36cf71b --- /dev/null +++ b/app/src/main/res/drawable/ic_2x.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_blur_16.xml b/app/src/main/res/drawable/ic_blur_16.xml new file mode 100644 index 0000000..cd1cacb --- /dev/null +++ b/app/src/main/res/drawable/ic_blur_16.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_bottom.xml b/app/src/main/res/drawable/ic_bottom.xml new file mode 100644 index 0000000..1ad74c1 --- /dev/null +++ b/app/src/main/res/drawable/ic_bottom.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_center.xml b/app/src/main/res/drawable/ic_center.xml new file mode 100644 index 0000000..fc78fa0 --- /dev/null +++ b/app/src/main/res/drawable/ic_center.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_fill.xml b/app/src/main/res/drawable/ic_fill.xml new file mode 100644 index 0000000..6d6b032 --- /dev/null +++ b/app/src/main/res/drawable/ic_fill.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_foreground.xml b/app/src/main/res/drawable/ic_foreground.xml new file mode 100644 index 0000000..a3be1b8 --- /dev/null +++ b/app/src/main/res/drawable/ic_foreground.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_lab.xml b/app/src/main/res/drawable/ic_lab.xml new file mode 100644 index 0000000..9c73dc3 --- /dev/null +++ b/app/src/main/res/drawable/ic_lab.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_left.xml b/app/src/main/res/drawable/ic_left.xml new file mode 100644 index 0000000..18c979b --- /dev/null +++ b/app/src/main/res/drawable/ic_left.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_lock.xml b/app/src/main/res/drawable/ic_lock.xml new file mode 100644 index 0000000..34882f5 --- /dev/null +++ b/app/src/main/res/drawable/ic_lock.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_paint_action.xml b/app/src/main/res/drawable/ic_paint_action.xml new file mode 100644 index 0000000..392c51c --- /dev/null +++ b/app/src/main/res/drawable/ic_paint_action.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_paint_color.xml b/app/src/main/res/drawable/ic_paint_color.xml new file mode 100644 index 0000000..a3b9d80 --- /dev/null +++ b/app/src/main/res/drawable/ic_paint_color.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_play.xml b/app/src/main/res/drawable/ic_play.xml new file mode 100644 index 0000000..b592a28 --- /dev/null +++ b/app/src/main/res/drawable/ic_play.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_right.xml b/app/src/main/res/drawable/ic_right.xml new file mode 100644 index 0000000..9b0d9a4 --- /dev/null +++ b/app/src/main/res/drawable/ic_right.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_top.xml b/app/src/main/res/drawable/ic_top.xml new file mode 100644 index 0000000..478edc8 --- /dev/null +++ b/app/src/main/res/drawable/ic_top.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 0d6e5fc..76d0239 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,4 +1,5 @@ + + + - - - \ No newline at end of file + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_start.xml b/app/src/main/res/layout/activity_start.xml index 84c2779..19bcb49 100644 --- a/app/src/main/res/layout/activity_start.xml +++ b/app/src/main/res/layout/activity_start.xml @@ -3,6 +3,10 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" > + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_blur.xml b/app/src/main/res/layout/fragment_blur.xml new file mode 100644 index 0000000..a1ab59b --- /dev/null +++ b/app/src/main/res/layout/fragment_blur.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_brightness.xml b/app/src/main/res/layout/fragment_brightness.xml new file mode 100644 index 0000000..5a5a3c6 --- /dev/null +++ b/app/src/main/res/layout/fragment_brightness.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_editor.xml b/app/src/main/res/layout/fragment_editor.xml new file mode 100644 index 0000000..b6743f3 --- /dev/null +++ b/app/src/main/res/layout/fragment_editor.xml @@ -0,0 +1,238 @@ + + + + + + + + + + + +