Skip to content

Commit 47b7d90

Browse files
add banner custom image
1 parent 5475495 commit 47b7d90

1 file changed

Lines changed: 143 additions & 3 deletions

File tree

app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt

Lines changed: 143 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.nekohasekai.sagernet.ui
22

33
import android.annotation.SuppressLint
4+
import android.app.Activity
45
import android.content.Intent
56
import android.graphics.Color
67
import android.os.Bundle
@@ -117,6 +118,12 @@ import java.util.concurrent.atomic.AtomicInteger
117118
import java.util.zip.ZipInputStream
118119
import android.content.DialogInterface
119120
import androidx.appcompat.app.AlertDialog
121+
import android.net.Uri
122+
import androidx.activity.result.PickVisualMediaRequest
123+
import com.bumptech.glide.Glide
124+
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
125+
import com.yalantis.ucrop.UCrop
126+
import java.io.File
120127

121128
class ConfigurationFragment @JvmOverloads constructor(
122129
val select: Boolean = false, val selectedItem: ProxyEntity? = null, val titleRes: Int = 0
@@ -343,6 +350,45 @@ class ConfigurationFragment @JvmOverloads constructor(
343350
}
344351
}
345352

353+
private val pickBannerImage =
354+
registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
355+
if (uri != null) {
356+
startCropActivity(uri)
357+
}
358+
}
359+
360+
private val cropBannerImage =
361+
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
362+
if (result.resultCode == Activity.RESULT_OK && result.data != null) {
363+
val resultUri = UCrop.getOutput(result.data!!)
364+
if (resultUri != null) {
365+
try {
366+
DataStore.configurationStore.putString("custom_banner_uri", resultUri.toString())
367+
368+
loadBannerImage(resultUri)
369+
snackbar(R.string.custom_banner_set).show()
370+
371+
} catch (e: SecurityException) {
372+
Logs.e("Failed to fetch persistent URI permission after crop", e)
373+
snackbar("Failed to save cropped banner image. Permission denied.").show()
374+
} catch (e: Exception) {
375+
Logs.e("Failed to load cropped banner", e)
376+
snackbar("Failed to load cropped banner image.").show()
377+
}
378+
} else {
379+
snackbar("Failed to get cropped image.").show()
380+
}
381+
} else if (result.resultCode == UCrop.RESULT_ERROR) {
382+
val cropError = UCrop.getError(result.data!!)
383+
if (cropError != null) {
384+
Logs.e("Cropping error: ", cropError)
385+
} else {
386+
Logs.e("Cropping error: Unknown error", Throwable("Unknown crop error"))
387+
}
388+
snackbar("An error occurred while cropping the image.").show()
389+
}
390+
}
391+
346392
suspend fun import(proxies: List<AbstractBean>) {
347393
val targetId = DataStore.selectedGroupForImport()
348394
for (proxy in proxies) {
@@ -1014,6 +1060,7 @@ class ConfigurationFragment @JvmOverloads constructor(
10141060
}
10151061
}
10161062

1063+
10171064
override suspend fun groupRemoved(groupId: Long) {
10181065
val index = groupList.indexOfFirst { it.id == groupId }
10191066
if (index == -1) return
@@ -1770,21 +1817,114 @@ class ConfigurationFragment @JvmOverloads constructor(
17701817
searchView.onActionViewCollapsed()
17711818
searchView.clearFocus()
17721819
}
1773-
1820+
1821+
private fun startCropActivity(sourceUri: Uri) {
1822+
val destinationFileName = "cropped_banner_${System.currentTimeMillis()}.jpg"
1823+
val destinationUri = Uri.fromFile(File(requireContext().cacheDir, destinationFileName))
1824+
1825+
val uCrop = UCrop.of(sourceUri, destinationUri)
1826+
.withAspectRatio(16F, 5F)
1827+
.withMaxResultSize(1920, 1080)
1828+
1829+
try {
1830+
val options = UCrop.Options()
1831+
1832+
options.setToolbarColor(requireContext().getColorAttr(R.attr.colorPrimary))
1833+
options.setStatusBarColor(requireContext().getColorAttr(R.attr.colorPrimary))
1834+
options.setActiveControlsWidgetColor(requireContext().getColorAttr(R.attr.colorSecondary))
1835+
options.setToolbarWidgetColor(requireContext().getColorAttr(R.attr.colorOnPrimary))
1836+
1837+
options.setDimmedLayerColor(Color.parseColor("#CCFFFFFF"))
1838+
options.setCircleDimmedLayer(false)
1839+
options.setShowCropGrid(true)
1840+
options.setFreeStyleCropEnabled(false)
1841+
uCrop.withOptions(options)
1842+
} catch (e: Exception) {
1843+
Logs.e("Failed to set UCrop theme", e)
1844+
}
1845+
1846+
cropBannerImage.launch(uCrop.getIntent(requireContext()))
1847+
}
1848+
1849+
private fun loadBannerImage(uri: Uri) {
1850+
val bannerImageView = view?.findViewById<ImageView>(R.id.img_banner_home)
1851+
bannerImageView?.let {
1852+
Glide.with(this)
1853+
.load(uri)
1854+
.transition(DrawableTransitionOptions.withCrossFade())
1855+
.into(it)
1856+
}
1857+
}
1858+
17741859
private fun setupBannerLayoutController() {
17751860
val linear = requireView().findViewById<View>(R.id.banner_home)
1776-
linear?.visibility = if (DataStore.showBannerLayout) View.VISIBLE else View.GONE
1861+
val bannerImageView = requireView().findViewById<ImageView>(R.id.img_banner_home)
1862+
1863+
fun loadSavedBanner() {
1864+
val savedUriString = DataStore.configurationStore.getString("custom_banner_uri", null)
1865+
if (!savedUriString.isNullOrEmpty()) {
1866+
try {
1867+
val savedUri = Uri.parse(savedUriString)
1868+
if (savedUri.scheme == "file" || requireContext().contentResolver.persistedUriPermissions
1869+
.any { it.uri == savedUri && it.isReadPermission }) {
1870+
loadBannerImage(savedUri)
1871+
} else {
1872+
Logs.w("Permission for URI banner lost: $savedUriString")
1873+
DataStore.configurationStore.putString("custom_banner_uri", null)
1874+
bannerImageView?.setImageResource(R.drawable.uwu_banner_home)
1875+
}
1876+
} catch (e: Exception) {
1877+
Logs.w("Failed to load saved banner", e)
1878+
DataStore.configurationStore.putString("custom_banner_uri", null)
1879+
bannerImageView?.setImageResource(R.drawable.uwu_banner_home)
1880+
}
1881+
} else {
1882+
bannerImageView?.setImageResource(R.drawable.uwu_banner_home)
1883+
}
1884+
}
1885+
1886+
if (DataStore.showBannerLayout) {
1887+
linear?.visibility = View.VISIBLE
1888+
loadSavedBanner()
1889+
} else {
1890+
linear?.visibility = View.GONE
1891+
}
1892+
1893+
linear?.setOnClickListener {
1894+
pickBannerImage.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
1895+
}
1896+
1897+
linear?.setOnLongClickListener {
1898+
if (!DataStore.configurationStore.getString("custom_banner_uri", null).isNullOrEmpty()) {
1899+
AlertDialog.Builder(requireContext())
1900+
.setTitle(R.string.delete_custom_banner_title)
1901+
.setMessage(R.string.delete_custom_banner_message)
1902+
.setPositiveButton(R.string.yes) { dialog: DialogInterface, which: Int ->
1903+
DataStore.configurationStore.putString("custom_banner_uri", null)
1904+
1905+
bannerImageView?.setImageResource(R.drawable.uwu_banner_home)
1906+
1907+
snackbar(R.string.custom_banner_removed).show()
1908+
}
1909+
.setNegativeButton(R.string.no, null)
1910+
.show()
1911+
}
1912+
true
1913+
}
17771914

17781915
if (bannerLayoutListener == null) {
17791916
bannerLayoutListener = object : OnPreferenceDataStoreChangeListener {
17801917
override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String) {
17811918
if (key == "show_banner_layout") {
17821919
val show = DataStore.showBannerLayout
1783-
1920+
17841921
if (!isAdded) return
17851922

17861923
requireActivity().runOnUiThread {
17871924
linear?.visibility = if (show) View.VISIBLE else View.GONE
1925+
if (show) {
1926+
loadSavedBanner()
1927+
}
17881928
}
17891929
}
17901930
}

0 commit comments

Comments
 (0)