|
1 | 1 | package io.nekohasekai.sagernet.ui |
2 | 2 |
|
3 | 3 | import android.annotation.SuppressLint |
| 4 | +import android.app.Activity |
4 | 5 | import android.content.Intent |
5 | 6 | import android.graphics.Color |
6 | 7 | import android.os.Bundle |
@@ -117,6 +118,12 @@ import java.util.concurrent.atomic.AtomicInteger |
117 | 118 | import java.util.zip.ZipInputStream |
118 | 119 | import android.content.DialogInterface |
119 | 120 | 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 |
120 | 127 |
|
121 | 128 | class ConfigurationFragment @JvmOverloads constructor( |
122 | 129 | val select: Boolean = false, val selectedItem: ProxyEntity? = null, val titleRes: Int = 0 |
@@ -343,6 +350,45 @@ class ConfigurationFragment @JvmOverloads constructor( |
343 | 350 | } |
344 | 351 | } |
345 | 352 |
|
| 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 | + |
346 | 392 | suspend fun import(proxies: List<AbstractBean>) { |
347 | 393 | val targetId = DataStore.selectedGroupForImport() |
348 | 394 | for (proxy in proxies) { |
@@ -1014,6 +1060,7 @@ class ConfigurationFragment @JvmOverloads constructor( |
1014 | 1060 | } |
1015 | 1061 | } |
1016 | 1062 |
|
| 1063 | + |
1017 | 1064 | override suspend fun groupRemoved(groupId: Long) { |
1018 | 1065 | val index = groupList.indexOfFirst { it.id == groupId } |
1019 | 1066 | if (index == -1) return |
@@ -1770,21 +1817,114 @@ class ConfigurationFragment @JvmOverloads constructor( |
1770 | 1817 | searchView.onActionViewCollapsed() |
1771 | 1818 | searchView.clearFocus() |
1772 | 1819 | } |
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 | + |
1774 | 1859 | private fun setupBannerLayoutController() { |
1775 | 1860 | 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 | + } |
1777 | 1914 |
|
1778 | 1915 | if (bannerLayoutListener == null) { |
1779 | 1916 | bannerLayoutListener = object : OnPreferenceDataStoreChangeListener { |
1780 | 1917 | override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String) { |
1781 | 1918 | if (key == "show_banner_layout") { |
1782 | 1919 | val show = DataStore.showBannerLayout |
1783 | | - |
| 1920 | + |
1784 | 1921 | if (!isAdded) return |
1785 | 1922 |
|
1786 | 1923 | requireActivity().runOnUiThread { |
1787 | 1924 | linear?.visibility = if (show) View.VISIBLE else View.GONE |
| 1925 | + if (show) { |
| 1926 | + loadSavedBanner() |
| 1927 | + } |
1788 | 1928 | } |
1789 | 1929 | } |
1790 | 1930 | } |
|
0 commit comments