diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..b75dee8 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,16 @@ +* text=auto eol=lf + +*.bat text eol=crlf +*.cmd text eol=crlf +*.ps1 text eol=crlf + +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.webp binary +*.ico binary +*.keystore binary +*.jks binary +*.zip binary +*.jar binary \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8853ba0..dd04359 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,7 +2,12 @@ - + + + + + + + + + + + diff --git a/app/src/main/java/mba/vm/onhit/Constant.kt b/app/src/main/java/mba/vm/onhit/Constant.kt index 41da603..fb89a7e 100644 --- a/app/src/main/java/mba/vm/onhit/Constant.kt +++ b/app/src/main/java/mba/vm/onhit/Constant.kt @@ -13,6 +13,8 @@ class Constant { const val PREF_FIXED_UID = "pref_fixed_uid" const val PREF_FIXED_UID_VALUE = "pref_fixed_uid_value" const val PREF_RANDOM_UID_LEN = "pref_random_uid_len" + + const val PREF_BACKGROUND_URI = "pref_background_uri" const val MAX_OF_BROADCAST_SIZE = 1048576 const val GITHUB_URL = "https://github.com/0penPublic/onHit" diff --git a/app/src/main/java/mba/vm/onhit/core/ConfigManager.kt b/app/src/main/java/mba/vm/onhit/core/ConfigManager.kt index de022c2..1033977 100644 --- a/app/src/main/java/mba/vm/onhit/core/ConfigManager.kt +++ b/app/src/main/java/mba/vm/onhit/core/ConfigManager.kt @@ -10,6 +10,7 @@ import mba.vm.onhit.Constant.Companion.SHARED_PREFERENCES_CHOSEN_FOLDER import mba.vm.onhit.Constant.Companion.SHARED_PREFERENCES_NAME import mba.vm.onhit.utils.HexUtils import java.security.SecureRandom +import mba.vm.onhit.Constant.Companion.PREF_BACKGROUND_URI object ConfigManager { fun getRootUri(context: Context): Uri? { @@ -47,11 +48,27 @@ object ConfigManager { .getString(PREF_RANDOM_UID_LEN, "4") ?: "4" } + fun setRandomUidLen(context: Context, len: String) { context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE) .edit().putString(PREF_RANDOM_UID_LEN, len).apply() } + + fun getBackgroundUri(context: Context): Uri? { + val value = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE) + .getString(PREF_BACKGROUND_URI, null) + return if (value.isNullOrEmpty()) null else Uri.parse(value) + } + + fun setBackgroundUri(context: Context, uri: Uri?) { + context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE) + .edit().apply { + if (uri == null) remove(PREF_BACKGROUND_URI) + else putString(PREF_BACKGROUND_URI, uri.toString()) + }.apply() + } + fun getUid(context: Context): ByteArray { if (isFixedUid(context)) { val hex = getFixedUidValue(context) diff --git a/app/src/main/java/mba/vm/onhit/helper/DialogHelper.kt b/app/src/main/java/mba/vm/onhit/helper/DialogHelper.kt index d4266c8..d3f3b32 100644 --- a/app/src/main/java/mba/vm/onhit/helper/DialogHelper.kt +++ b/app/src/main/java/mba/vm/onhit/helper/DialogHelper.kt @@ -113,7 +113,14 @@ object DialogHelper { return dialog } - fun showSettingsSheet(context: Context, onChangeDir: () -> Unit) { + fun showSettingsSheet( + context: Context, + onChangeDir: () -> Unit, + onChangeBackground: () -> Unit, + onClearBackground: () -> Unit + ) + + { val dialog = createBottomDialog(context, R.layout.bottom_sheet_settings) val btnChangeDir = dialog.findViewById(R.id.btn_change_dir) @@ -122,6 +129,34 @@ object DialogHelper { val uidConfigSummary = dialog.findViewById(R.id.tv_uid_config_summary) val etUidConfig = dialog.findViewById(R.id.et_uid_config) + val btnChangeBackground = dialog.findViewById(R.id.btn_change_background) + val btnClearBackground = dialog.findViewById(R.id.btn_clear_background) + val backgroundSummary = dialog.findViewById(R.id.tv_background_summary) + + backgroundSummary.text = + if (ConfigManager.getBackgroundUri(context) == null) { + context.getString(R.string.settings_background_default) + } else { + context.getString(R.string.settings_background_custom) + } + + btnChangeBackground.setOnClickListener { + dialog.dismiss() + onChangeBackground() + } + + btnClearBackground.setOnClickListener { + + ConfigManager.setBackgroundUri(context, null) + + backgroundSummary.text = + context.getString(R.string.settings_background_default) + + dialog.dismiss() + + onClearBackground() + } + btnChangeDir.setOnClickListener { dialog.dismiss() onChangeDir() diff --git a/app/src/main/java/mba/vm/onhit/ui/MainActivity.kt b/app/src/main/java/mba/vm/onhit/ui/MainActivity.kt index e314041..a764f4c 100644 --- a/app/src/main/java/mba/vm/onhit/ui/MainActivity.kt +++ b/app/src/main/java/mba/vm/onhit/ui/MainActivity.kt @@ -17,7 +17,6 @@ import android.window.OnBackInvokedDispatcher import androidx.core.content.IntentCompat import androidx.core.view.WindowCompat import androidx.documentfile.provider.DocumentFile -import mba.vm.onhit.BuildConfig import mba.vm.onhit.Constant import mba.vm.onhit.Constant.Companion.MAX_OF_BROADCAST_SIZE import mba.vm.onhit.R @@ -49,10 +48,18 @@ class MainActivity : Activity() { private var pendingImportUri: Uri? = null + private val REQUEST_SELECT_BACKGROUND = 1002 + + private val REQUEST_CROP_BACKGROUND = 1003 + private var pendingCroppedBackgroundUri: Uri? = null + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) + + applyCustomBackground() + handleIntent(intent) WindowCompat.setDecorFitsSystemWindows(window, false) @@ -78,7 +85,7 @@ class MainActivity : Activity() { private fun handleIntent(intent: Intent?) { val className = intent?.component?.className ?: return - val appId = BuildConfig.APPLICATION_ID + val appId = packageName when (className) { "$appId.ImportHandler" -> { val uri = if (intent.action == Intent.ACTION_SEND) { @@ -177,7 +184,22 @@ class MainActivity : Activity() { if (pendingImportUri != null) { performImportSave() } else { - DialogHelper.showSettingsSheet(this) { requestSelectDirectory() } + DialogHelper.showSettingsSheet( + this, + + { + requestSelectDirectory() + }, + + { + requestSelectBackground() + }, + + { + ConfigManager.setBackgroundUri(this, null) + applyCustomBackground() + } + ) } } @@ -222,7 +244,6 @@ class MainActivity : Activity() { binding.tvAppTitle.visibility = View.GONE binding.etSearch.visibility = View.VISIBLE binding.etSearch.requestFocus() - binding.etSearch.requestFocus() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { binding.etSearch.windowInsetsController?.show(WindowInsets.Type.ime()) } else { @@ -436,6 +457,24 @@ class MainActivity : Activity() { Toast.makeText(this, R.string.toast_storage_unavailable, Toast.LENGTH_SHORT).show() } } + } else if (requestCode == REQUEST_SELECT_BACKGROUND && resultCode == RESULT_OK) { + data?.data?.let { uri -> + try { + contentResolver.takePersistableUriPermission( + uri, + Intent.FLAG_GRANT_READ_URI_PERMISSION + ) + } catch (_: Exception) { + } + + startCropBackground(uri) + } + + } else if (requestCode == REQUEST_CROP_BACKGROUND && resultCode == RESULT_OK) { + pendingCroppedBackgroundUri?.let { uri -> + ConfigManager.setBackgroundUri(this, uri) + applyCustomBackground() + } } } @@ -455,4 +494,115 @@ class MainActivity : Activity() { super.onDestroy() executor.shutdown() } + private fun requestSelectBackground() { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "image/*" + addFlags( + Intent.FLAG_GRANT_READ_URI_PERMISSION or + Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION + ) + } + + startActivityForResult(intent, REQUEST_SELECT_BACKGROUND) + } + + private fun startCropBackground(sourceUri: Uri) { + val outputFile = java.io.File(filesDir, "custom_background_cropped.jpg") + + if (!outputFile.exists()) { + outputFile.createNewFile() + } + + val outputUri = androidx.core.content.FileProvider.getUriForFile( + this, + "${packageName}.fileprovider", + outputFile + ) + + pendingCroppedBackgroundUri = outputUri + + val cropIntent = Intent("com.android.camera.action.CROP").apply { + setDataAndType(sourceUri, "image/*") + + putExtra("crop", "true") + putExtra("aspectX", 9) + putExtra("aspectY", 16) + putExtra("scale", true) + putExtra("return-data", false) + + putExtra(android.provider.MediaStore.EXTRA_OUTPUT, outputUri) + putExtra("outputFormat", android.graphics.Bitmap.CompressFormat.JPEG.toString()) + + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + + clipData = android.content.ClipData.newRawUri( + "cropped_background", + outputUri + ) + } + + val resInfoList = packageManager.queryIntentActivities( + cropIntent, + android.content.pm.PackageManager.MATCH_DEFAULT_ONLY + ) + + for (resolveInfo in resInfoList) { + val packageName = resolveInfo.activityInfo.packageName + + grantUriPermission( + packageName, + sourceUri, + Intent.FLAG_GRANT_READ_URI_PERMISSION + ) + + grantUriPermission( + packageName, + outputUri, + Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION + ) + } + + try { + startActivityForResult(cropIntent, REQUEST_CROP_BACKGROUND) + } catch (_: Exception) { + Toast.makeText(this, R.string.unknown_error, Toast.LENGTH_SHORT).show() + + ConfigManager.setBackgroundUri(this, sourceUri) + applyCustomBackground() + } + } + private fun applyCustomBackground() { + val uri = ConfigManager.getBackgroundUri(this) + + if (uri == null) { + binding.ivCustomBackground.setImageDrawable(null) + binding.ivCustomBackground.visibility = View.GONE + binding.vBackgroundScrim.visibility = View.GONE + + binding.topBar.setBackgroundColor( + android.graphics.Color.TRANSPARENT + ) + + return + } + + try { + binding.ivCustomBackground.setImageDrawable(null) + binding.ivCustomBackground.setImageURI(uri) + + binding.ivCustomBackground.visibility = View.VISIBLE + binding.vBackgroundScrim.visibility = View.VISIBLE + + binding.topBar.setBackgroundColor( + getColor(R.color.custom_panel_background) + ) + + } catch (_: Exception) { + Toast.makeText(this, R.string.unknown_error, Toast.LENGTH_SHORT).show() + ConfigManager.setBackgroundUri(this, null) + applyCustomBackground() + } + } } diff --git a/app/src/main/res/drawable-night/bg_file_item.xml b/app/src/main/res/drawable-night/bg_file_item.xml new file mode 100644 index 0000000..7d9a7c9 --- /dev/null +++ b/app/src/main/res/drawable-night/bg_file_item.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/baseline_delete_24.xml b/app/src/main/res/drawable/baseline_delete_24.xml new file mode 100644 index 0000000..16e8b1b --- /dev/null +++ b/app/src/main/res/drawable/baseline_delete_24.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_file_item.xml b/app/src/main/res/drawable/bg_file_item.xml new file mode 100644 index 0000000..801f480 --- /dev/null +++ b/app/src/main/res/drawable/bg_file_item.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 969eb5d..628c752 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -6,18 +6,36 @@ android:layout_height="match_parent" android:fitsSystemWindows="true"> + + + + + android:orientation="vertical" + android:background="@android:color/transparent"> + android:paddingTop="18dp"> + android:text="@string/app_name" + android:textColor="?android:attr/textColorPrimary" + android:textSize="24sp" + android:textStyle="bold" /> + android:tint="?android:attr/textColorPrimary" /> + android:tint="?android:attr/textColorPrimary" /> + android:scrollbars="none"> + android:layout_height="match_parent" + android:background="@android:color/transparent"> @@ -139,7 +159,7 @@ android:background="@drawable/bg_circle_icon" android:src="@drawable/baseline_settings_24" android:tint="?android:attr/textColorPrimary" - android:backgroundTint="?android:attr/colorBackground" + android:backgroundTint="@color/custom_fab_background" android:elevation="6dp" android:contentDescription="@string/setting" /> diff --git a/app/src/main/res/layout/bottom_sheet_settings.xml b/app/src/main/res/layout/bottom_sheet_settings.xml index d04f294..607c4c8 100644 --- a/app/src/main/res/layout/bottom_sheet_settings.xml +++ b/app/src/main/res/layout/bottom_sheet_settings.xml @@ -107,6 +107,78 @@ android:inputType="" tools:ignore="LabelFor" /> + + + + + + + + + + + + + + + + + + + + + + android:paddingTop="8dp" + android:paddingBottom="8dp" + android:background="@drawable/bg_file_item" + android:foreground="?android:attr/selectableItemBackground"> - + \ No newline at end of file diff --git a/app/src/main/res/values-night/custom_background_colors.xml b/app/src/main/res/values-night/custom_background_colors.xml new file mode 100644 index 0000000..2a4a5a0 --- /dev/null +++ b/app/src/main/res/values-night/custom_background_colors.xml @@ -0,0 +1,16 @@ + + + + + #11000000 + + + #44000000 + + + #00000000 + + + #66000000 + + \ No newline at end of file diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 80ad915..dc4e837 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -51,4 +51,10 @@ 已导入: %s 导入失败: %s 将 %s 保存至… + + 更换背景图片 + 清除背景图片 + 当前使用默认背景 + 当前使用自定义背景 + 未知错误 diff --git a/app/src/main/res/values/custom_background_colors.xml b/app/src/main/res/values/custom_background_colors.xml new file mode 100644 index 0000000..486a308 --- /dev/null +++ b/app/src/main/res/values/custom_background_colors.xml @@ -0,0 +1,7 @@ + + + #22FFFFFF + #AAFFFFFF + #66FFFFFF + #CCFFFFFF + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3b66e5e..e689f7b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -52,4 +52,10 @@ Import failed: %s Save %s to… + + Change background image + Clear background image + Using default background + Using custom background + unknown error diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml new file mode 100644 index 0000000..95149fc --- /dev/null +++ b/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/gradlew.bat b/gradlew.bat index aa5f10b..24c62d5 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,82 +1,82 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables, and ensure extensions are enabled -setlocal EnableExtensions - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -"%COMSPEC%" /c exit 1 - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -"%COMSPEC%" /c exit 1 - -:execute -@rem Setup the command line - - - -@rem Execute Gradle -@rem endlocal doesn't take effect until after the line is parsed and variables are expanded -@rem which allows us to clear the local environment before executing the java command -endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel - -:exitWithErrorLevel -@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts -"%COMSPEC%" /c exit %ERRORLEVEL% +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel + +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL%