diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5b005ff --- /dev/null +++ b/Makefile @@ -0,0 +1,58 @@ +.PHONY: build clean install install-debug install-release test help + +# Default target +help: + @echo "Available targets:" + @echo " build - Build debug APK" + @echo " build-release - Build release APK" + @echo " clean - Clean build artifacts" + @echo " install - Install debug APK to connected device" + @echo " install-release- Install release APK to connected device" + @echo " uninstall - Uninstall app from connected device" + @echo " test - Run tests" + @echo " lint - Run lint checks" + @echo " assemble - Build all variants" + +# Build debug APK +build: + ./gradlew assembleDebug + +# Build release APK +build-release: + ./gradlew assembleRelease + +# Build all variants +assemble: + ./gradlew assemble + +# Clean build artifacts +clean: + ./gradlew clean + +# Install debug APK to connected device +install: build + ./gradlew installDebug + +# Install debug APK without building (if already built) +install-debug: + ./gradlew installDebug + +# Install release APK to connected device +install-release: build-release + ./gradlew installRelease + +# Uninstall app from device +uninstall: + ./gradlew uninstallAll + +# Run tests +test: + ./gradlew test + +# Run lint checks +lint: + ./gradlew lint + +# Run app on connected device +run: install + adb shell am start -n com.immichframe.immichframe/.MainActivity diff --git a/app/src/main/java/com/immichframe/immichframe/MainActivity.kt b/app/src/main/java/com/immichframe/immichframe/MainActivity.kt index ed2b99b..e25c5fc 100644 --- a/app/src/main/java/com/immichframe/immichframe/MainActivity.kt +++ b/app/src/main/java/com/immichframe/immichframe/MainActivity.kt @@ -25,10 +25,13 @@ import android.webkit.WebResourceRequest import android.webkit.WebSettings import android.webkit.WebView import android.webkit.WebViewClient +import android.text.InputType import android.widget.Button +import android.widget.EditText import android.widget.ImageView import android.widget.TextView import android.widget.Toast +import androidx.appcompat.app.AlertDialog import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate @@ -161,7 +164,7 @@ class MainActivity : AppCompatActivity() { onNextCommand = { runOnUiThread { nextAction() } }, onPreviousCommand = { runOnUiThread { previousAction() } }, onPauseCommand = { runOnUiThread { pauseAction() } }, - onSettingsCommand = { runOnUiThread { settingsAction() } }, + onSettingsCommand = { runOnUiThread { settingsActionFromRpc() } }, onBrightnessCommand = { brightness -> runOnUiThread { screenBrightnessAction(brightness) } }, ) rcpServer.start() @@ -700,11 +703,52 @@ class MainActivity : AppCompatActivity() { } private fun settingsAction() { + val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this) + val savedPassword = sharedPrefs.getString("settings_pincode", "") + + if (!savedPassword.isNullOrEmpty()) { + // Password is set, prompt for verification + promptForPasswordVerification(savedPassword) + } else { + // No password, open settings directly + openSettings() + } + } + + private fun settingsActionFromRpc() { + // RPC access bypasses password protection + openSettings() + } + + private fun openSettings() { val intent = Intent(this, SettingsActivity::class.java) stopImageTimer() settingsLauncher.launch(intent) } + private fun promptForPasswordVerification(correctPassword: String) { + val input = EditText(this) + input.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD + + AlertDialog.Builder(this) + .setTitle("Enter Pincode") + .setMessage("Enter your pincode to access settings:") + .setView(input) + .setPositiveButton("OK") { _, _ -> + val enteredPassword = input.text.toString() + if (enteredPassword == correctPassword) { + openSettings() + } else { + Toast.makeText(this, "Incorrect pincode", Toast.LENGTH_SHORT).show() + } + } + .setNegativeButton("Cancel") { dialog, _ -> + dialog.dismiss() + } + .setCancelable(false) + .show() + } + private fun screenBrightnessAction(brightness: Float) { val lp = window.attributes lp.screenBrightness = brightness diff --git a/app/src/main/java/com/immichframe/immichframe/SettingsFragment.kt b/app/src/main/java/com/immichframe/immichframe/SettingsFragment.kt index d2f52ad..1bbdc18 100644 --- a/app/src/main/java/com/immichframe/immichframe/SettingsFragment.kt +++ b/app/src/main/java/com/immichframe/immichframe/SettingsFragment.kt @@ -72,6 +72,44 @@ class SettingsFragment : PreferenceFragmentCompat() { true } + val chkSettingsPincode = findPreference("settingsPincode") + val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(requireContext()) + + // Update switch state based on whether password exists + val hasPassword = !sharedPrefs.getString("settings_pincode", "").isNullOrEmpty() + chkSettingsPincode?.isChecked = hasPassword + + chkSettingsPincode?.setOnPreferenceChangeListener { preference, newValue -> + val enabling = newValue as Boolean + + if (enabling) { + // Show confirmation dialog first + AlertDialog.Builder(requireContext()) + .setTitle("Confirm Action") + .setMessage( + "This will require a pincode to access the settings screen. " + + "The only way to reset is via RPC commands (or uninstall/reinstall).\n" + + "Are you sure?" + ) + .setPositiveButton("Yes") { _, _ -> + // Now prompt for password creation + promptForPasswordCreation(chkSettingsPincode) + } + .setNegativeButton("No") { dialog, _ -> + dialog.dismiss() + } + .show() + false // Don't change preference yet, wait for password creation + } else { + // Disabling - delete the password + sharedPrefs.edit() + .remove("settings_pincode") + .apply() + Toast.makeText(requireContext(), "Pincode protection removed", Toast.LENGTH_SHORT).show() + true + } + } + val btnClose = findPreference("closeSettings") btnClose?.setOnPreferenceClickListener { @@ -138,4 +176,35 @@ class SettingsFragment : PreferenceFragmentCompat() { } } } + + private fun promptForPasswordCreation(chkSettingsPincode: SwitchPreferenceCompat?) { + val input = android.widget.EditText(requireContext()) + input.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD + + AlertDialog.Builder(requireContext()) + .setTitle("Create Pincode") + .setMessage("Enter a pincode to protect settings access:") + .setView(input) + .setPositiveButton("Set") { _, _ -> + val password = input.text.toString() + if (password.isEmpty()) { + Toast.makeText(requireContext(), "Pincode cannot be empty", Toast.LENGTH_SHORT).show() + } else { + // Save the password + val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(requireContext()) + sharedPrefs.edit() + .putString("settings_pincode", password) + .apply() + + // Update the switch + chkSettingsPincode?.isChecked = true + + Toast.makeText(requireContext(), "Pincode protection enabled", Toast.LENGTH_SHORT).show() + } + } + .setNegativeButton("Cancel") { dialog, _ -> + dialog.dismiss() + } + .show() + } } \ No newline at end of file diff --git a/app/src/main/res/xml/settings_view.xml b/app/src/main/res/xml/settings_view.xml index 75112b5..a7dd03f 100644 --- a/app/src/main/res/xml/settings_view.xml +++ b/app/src/main/res/xml/settings_view.xml @@ -29,10 +29,17 @@ android:defaultValue="false" android:key="showCurrentDate" android:title="Show Current Date?" /> + + + + + android:title="Fully disable/lock access to these settings?" />