mirror of
https://github.com/sartoopjj/thefeed.git
synced 2026-05-19 06:54:34 +03:00
feat: implement app name presets and enhance password management UI
This commit is contained in:
@@ -27,23 +27,13 @@
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTask" />
|
||||
|
||||
<!-- Separate alias for launcher entry so it can be disabled
|
||||
when the user applies a custom icon/name shortcut -->
|
||||
<activity-alias
|
||||
android:name=".DefaultLauncher"
|
||||
android:targetActivity=".MainActivity"
|
||||
android:exported="true"
|
||||
android:enabled="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:label="@string/app_name">
|
||||
android:launchMode="singleTask"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -1,19 +1,8 @@
|
||||
package com.thefeed.android
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.util.Base64
|
||||
import android.webkit.JavascriptInterface
|
||||
import androidx.core.content.pm.ShortcutInfoCompat
|
||||
import androidx.core.content.pm.ShortcutManagerCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.security.MessageDigest
|
||||
|
||||
class AndroidBridge(private val activity: Activity) {
|
||||
@@ -27,94 +16,25 @@ class AndroidBridge(private val activity: Activity) {
|
||||
@JavascriptInterface
|
||||
fun isAndroid(): Boolean = true
|
||||
|
||||
/**
|
||||
* Change the app's launcher icon and name.
|
||||
* If iconBase64 is non-empty, disables the default launcher alias and
|
||||
* pins a new shortcut with the custom image — the result looks like
|
||||
* the app itself changed.
|
||||
* If iconBase64 is empty, only saves the custom name (used on lock screen).
|
||||
*/
|
||||
/** Set a preset display name for the app (shown on lock screen). */
|
||||
@JavascriptInterface
|
||||
fun setAppIdentity(name: String, iconBase64: String): Boolean {
|
||||
return try {
|
||||
prefs.edit().putString(PREF_CUSTOM_APP_NAME, name).apply()
|
||||
|
||||
if (iconBase64.isBlank()) return true // name-only change
|
||||
|
||||
val raw = if (iconBase64.contains(",")) iconBase64.substringAfter(",") else iconBase64
|
||||
val bytes = Base64.decode(raw, Base64.DEFAULT)
|
||||
val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size) ?: return false
|
||||
|
||||
val iconFile = File(activity.filesDir, "custom_icon.png")
|
||||
FileOutputStream(iconFile).use { out ->
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)
|
||||
}
|
||||
prefs.edit().putString(PREF_CUSTOM_ICON_PATH, iconFile.absolutePath).apply()
|
||||
|
||||
// Create pinned shortcut with custom icon/name
|
||||
val icon = IconCompat.createWithBitmap(bitmap)
|
||||
val shortcut = ShortcutInfoCompat.Builder(activity, "custom_launcher")
|
||||
.setShortLabel(name)
|
||||
.setLongLabel(name)
|
||||
.setIcon(icon)
|
||||
.setIntent(
|
||||
Intent(activity, MainActivity::class.java).apply {
|
||||
action = Intent.ACTION_MAIN
|
||||
}
|
||||
)
|
||||
.build()
|
||||
ShortcutManagerCompat.requestPinShortcut(activity, shortcut, null)
|
||||
|
||||
// Hide original launcher icon
|
||||
activity.packageManager.setComponentEnabledSetting(
|
||||
ComponentName(activity, "${activity.packageName}.DefaultLauncher"),
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||
PackageManager.DONT_KILL_APP
|
||||
)
|
||||
true
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
}
|
||||
fun setPresetName(name: String) {
|
||||
prefs.edit().putString(PREF_CUSTOM_APP_NAME, name).apply()
|
||||
}
|
||||
|
||||
/** Returns the custom display name, or empty string if using defaults. */
|
||||
/** Returns the preset display name, or empty string if using defaults. */
|
||||
@JavascriptInterface
|
||||
fun getCustomAppName(): String {
|
||||
fun getPresetName(): String {
|
||||
return prefs.getString(PREF_CUSTOM_APP_NAME, "") ?: ""
|
||||
}
|
||||
|
||||
/** Returns the display name for the app — custom name if set, otherwise "thefeed". */
|
||||
/** Returns the display name for the app — preset name if set, otherwise "thefeed". */
|
||||
@JavascriptInterface
|
||||
fun getAppDisplayName(): String {
|
||||
val custom = prefs.getString(PREF_CUSTOM_APP_NAME, null)
|
||||
return if (!custom.isNullOrBlank()) custom else "thefeed"
|
||||
}
|
||||
|
||||
/** Restore original app icon and name. */
|
||||
@JavascriptInterface
|
||||
fun resetAppIdentity() {
|
||||
prefs.edit()
|
||||
.remove(PREF_CUSTOM_APP_NAME)
|
||||
.remove(PREF_CUSTOM_ICON_PATH)
|
||||
.apply()
|
||||
val iconFile = File(activity.filesDir, "custom_icon.png")
|
||||
if (iconFile.exists()) iconFile.delete()
|
||||
|
||||
// Restore original launcher icon
|
||||
try {
|
||||
activity.packageManager.setComponentEnabledSetting(
|
||||
ComponentName(activity, "${activity.packageName}.DefaultLauncher"),
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||
PackageManager.DONT_KILL_APP
|
||||
)
|
||||
} catch (_: Exception) { }
|
||||
|
||||
// Remove dynamic shortcut
|
||||
try {
|
||||
ShortcutManagerCompat.removeDynamicShortcuts(activity, listOf("custom_launcher"))
|
||||
} catch (_: Exception) { }
|
||||
}
|
||||
|
||||
// ===== Language =====
|
||||
|
||||
@JavascriptInterface
|
||||
@@ -163,7 +83,6 @@ class AndroidBridge(private val activity: Activity) {
|
||||
|
||||
companion object {
|
||||
const val PREF_CUSTOM_APP_NAME = "custom_app_name"
|
||||
const val PREF_CUSTOM_ICON_PATH = "custom_icon_path"
|
||||
const val PREF_PASSWORD_HASH = "password_hash"
|
||||
const val PREF_LANG = "app_lang"
|
||||
}
|
||||
|
||||
@@ -67,11 +67,12 @@ class MainActivity : ComponentActivity() {
|
||||
controller.isAppearanceLightNavigationBars = false
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
// Apply top inset as padding so content isn't hidden behind the status bar
|
||||
// Apply insets so content isn't hidden behind the status bar or keyboard
|
||||
val rootView = findViewById<View>(android.R.id.content)
|
||||
ViewCompat.setOnApplyWindowInsetsListener(rootView) { v, insets ->
|
||||
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
v.setPadding(0, systemBars.top, 0, systemBars.bottom)
|
||||
val ime = insets.getInsets(WindowInsetsCompat.Type.ime())
|
||||
v.setPadding(0, systemBars.top, 0, maxOf(systemBars.bottom, ime.bottom))
|
||||
insets
|
||||
}
|
||||
// Trigger inset dispatch explicitly — required on some older Android versions
|
||||
@@ -98,6 +99,23 @@ class MainActivity : ComponentActivity() {
|
||||
return prefs.getString(AndroidBridge.PREF_PASSWORD_HASH, null) != null
|
||||
}
|
||||
|
||||
private fun resolvePresetName(key: String?, isPersian: Boolean): String {
|
||||
if (key == null) return getString(R.string.app_name)
|
||||
val presets = mapOf(
|
||||
"weather" to ("Weather" to "آب و هوا"),
|
||||
"calculator" to ("Calculator" to "ماشینحساب"),
|
||||
"calendar" to ("Calendar" to "تقویم"),
|
||||
"notes" to ("Notes" to "یادداشت"),
|
||||
"clock" to ("Clock" to "ساعت"),
|
||||
"camera" to ("Camera" to "دوربین"),
|
||||
"compass" to ("Compass" to "قطبنما"),
|
||||
"gallery" to ("Gallery" to "گالری"),
|
||||
"recorder" to ("Recorder" to "ضبط صدا"),
|
||||
)
|
||||
val pair = presets[key] ?: return key
|
||||
return if (isPersian) pair.second else pair.first
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun showLockScreen() {
|
||||
lockScreenVisible = true
|
||||
@@ -109,10 +127,11 @@ class MainActivity : ComponentActivity() {
|
||||
val lockError = findViewById<TextView>(R.id.lockError)
|
||||
|
||||
val prefs = getSharedPreferences(ThefeedService.PREFS_NAME, Context.MODE_PRIVATE)
|
||||
val appName = prefs.getString(AndroidBridge.PREF_CUSTOM_APP_NAME, null)
|
||||
?.takeIf { it.isNotBlank() } ?: getString(R.string.app_name)
|
||||
val lang = prefs.getString(AndroidBridge.PREF_LANG, "fa") ?: "fa"
|
||||
val isPersian = lang == "fa"
|
||||
val presetKey = prefs.getString(AndroidBridge.PREF_CUSTOM_APP_NAME, null)
|
||||
?.takeIf { it.isNotBlank() }
|
||||
val appName = resolvePresetName(presetKey, isPersian)
|
||||
|
||||
lockTitle.text = appName
|
||||
lockSubtitle.text = if (isPersian) "رمز عبور را وارد کنید" else "Enter password to unlock"
|
||||
|
||||
Reference in New Issue
Block a user