diff --git a/README.md b/README.md index 0e940f9..0dbe456 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,19 @@ git submodule update --init --recursive ``` Add in the app's `settings.gradle`: -```groovy -include(":app", ":bohio") -project(":bohio").projectDir = file("bohio") +```kotlin +include(":app") +include(":bohio") + +// Activate For Release +//project(":bohio").projectDir = file("bohio") + +// Activate For Bohio Development (and locate your local source) +project(":bohio").projectDir = file("../git-mod_bohio") ``` Add in the app's `app/build.gradle`: -```groovy +```kotlin dependencies { implementation(project(":bohio")) } @@ -33,9 +39,9 @@ Make sure the app's theme extends `Theme.Rama.Base` in `themes.xml`. For F-Droid, add `submodules: true` to the relevant build entry in the app's metadata so `git submodule update --init --recursive` runs after checkout. Keep the `bohio` repo public and avoid rewriting history on any commit a published app version's submodule pointer references. -## Maintanence +## Maintenance Update submodule ```bash git submodule update --remote bohio -``` \ No newline at end of file +``` diff --git a/build.gradle b/build.gradle index 0e8d660..f01e2c9 100644 --- a/build.gradle +++ b/build.gradle @@ -29,9 +29,5 @@ android { } dependencies { - // 'api' so consuming apps get these transitively without redeclaring. - // CsActivity extends AppCompatActivity and uses OnBackPressedCallback - // (from androidx.activity, pulled in transitively by appcompat). api 'androidx.appcompat:appcompat:1.6.1' - api 'androidx.fragment:fragment-ktx:1.6.2' } diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml deleted file mode 100644 index bed3e93..0000000 --- a/src/main/AndroidManifest.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - diff --git a/src/main/java/com/rama/bohio/activity/BohioActivity.kt b/src/main/java/com/rama/bohio/activity/BohioActivity.kt new file mode 100644 index 0000000..deefd1b --- /dev/null +++ b/src/main/java/com/rama/bohio/activity/BohioActivity.kt @@ -0,0 +1,192 @@ +package com.rama.bohio.activity + +import android.content.Context +import android.content.SharedPreferences +import android.content.pm.ActivityInfo +import android.content.res.Configuration +import android.os.Build +import android.os.Bundle +import android.view.View +import android.view.WindowInsets +import android.view.WindowManager +import androidx.activity.ComponentActivity +import com.rama.bohio.managers.FontManager +import com.rama.bohio.managers.ThemeManager +import com.rama.bohio.objects.PrefKeys +import com.rama.bohio.util.Dimens.dpToPx +import com.rama.bohio.util.LocaleHelper + +abstract class BohioActivity : ComponentActivity() { + + private var lastKnownLanguage: String? = null + private var lastKnownTheme: String? = null + private var lastKnownUiScale: Float = -1f + + // Context wrapping (locale + UI scale) + + override fun attachBaseContext(newBase: Context) { + val localeContext = LocaleHelper.wrapContext(newBase) + + val scale = rawPrefs(localeContext).getFloat(PrefKeys.APP_UI_SCALE, 1f) + + val context = if (scale != 1f) { + val config = Configuration(localeContext.resources.configuration) + config.densityDpi = + (localeContext.resources.displayMetrics.densityDpi * scale).toInt() + localeContext.createConfigurationContext(config) + } else { + localeContext + } + + super.attachBaseContext(context) + } + + // Lifecycle + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val p = rawPrefs(this) + lastKnownLanguage = p.getString(PrefKeys.APP_LANGUAGE, "") + lastKnownTheme = p.getString(PrefKeys.APP_THEME_NAME, "") + lastKnownUiScale = p.getFloat(PrefKeys.APP_UI_SCALE, 1f) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + window.setDecorFitsSystemWindows(false) + } else { + @Suppress("DEPRECATION") + window.decorView.systemUiVisibility = + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or + View.SYSTEM_UI_FLAG_LAYOUT_STABLE + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + window.attributes.layoutInDisplayCutoutMode = + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES + } + } + + override fun onResume() { + super.onResume() + + val p = rawPrefs(this) + + val lang = p.getString(PrefKeys.APP_LANGUAGE, "") ?: "" + if (lang != lastKnownLanguage) { + lastKnownLanguage = lang + if (shouldRecreateOnSettingsChange()) { + recreate(); return + } + } + + val theme = p.getString(PrefKeys.APP_THEME_NAME, "") ?: "" + if (theme != lastKnownTheme) { + lastKnownTheme = theme + if (shouldRecreateOnSettingsChange()) { + recreate(); return + } + } + + val scale = p.getFloat(PrefKeys.APP_UI_SCALE, 1f) + if (scale != lastKnownUiScale) { + lastKnownUiScale = scale + if (shouldRecreateOnSettingsChange()) { + recreate(); return + } + } + + val preventRotation = p.getBoolean(PrefKeys.SYSTEM_PREVENT_ROTATION, false) + applyRotationLock(preventRotation) + + ThemeManager.applyTheme(this, contentRoot()) + } + + override fun onWindowFocusChanged(hasFocus: Boolean) { + super.onWindowFocusChanged(hasFocus) + if (hasFocus) applySystemBarVisibility() + } + + // Public API + + protected open fun shouldRecreateOnSettingsChange(): Boolean = true + + fun applyCurrentTheme(root: View? = null) { + ThemeManager.applyTheme(this, root ?: contentRoot()) + applyNavBarColor() + } + + fun refreshFont() { + FontManager.applyFont(this, contentRoot()) + } + + fun applyRotationLock(lock: Boolean) { + requestedOrientation = + if (lock) ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + else ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED + } + + protected fun applyEdgeToEdgePadding(root: View) { + val paddingInline = dpToPx(this, 16f) + val paddingBlock = dpToPx(this, 8f) + + root.setOnApplyWindowInsetsListener { view, insets -> + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + val sysBars = insets.getInsets( + WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout() + ) + val ime = insets.getInsets(WindowInsets.Type.ime()) + val bottomInset = if (insets.isVisible(WindowInsets.Type.ime())) ime.bottom + else sysBars.bottom + view.setPadding( + sysBars.left + paddingInline, + sysBars.top + paddingBlock, + sysBars.right + paddingInline, + bottomInset + paddingBlock, + ) + } else { + @Suppress("DEPRECATION") + view.setPadding( + insets.systemWindowInsetLeft + paddingInline, + insets.systemWindowInsetTop + paddingBlock, + insets.systemWindowInsetRight + paddingInline, + insets.systemWindowInsetBottom + paddingBlock, + ) + } + insets + } + } + + // Private helpers + + protected fun applyNavBarColor() { + val theme = rawPrefs(this).getString(PrefKeys.APP_THEME_NAME, "") ?: "" + val palette = ThemeManager.paletteFor(theme, this) + window.navigationBarColor = palette.bg_1 + } + + protected open fun isSystemBarVisible(): Boolean { + return rawPrefs(this) + .getBoolean(PrefKeys.SYSTEM_BAR_VISIBLE, true) + } + + private fun applySystemBarVisibility() { + if (isSystemBarVisible()) { + @Suppress("DEPRECATION") + window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE + } else { + @Suppress("DEPRECATION") + window.decorView.systemUiVisibility = + View.SYSTEM_UI_FLAG_FULLSCREEN or + View.SYSTEM_UI_FLAG_LAYOUT_STABLE or + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + } + contentRoot().requestApplyInsets() + } + + private fun contentRoot(): View = findViewById(android.R.id.content) + + private fun rawPrefs(context: Context): SharedPreferences = + context.getSharedPreferences("settings", Context.MODE_PRIVATE) +} \ No newline at end of file diff --git a/src/main/java/com/rama/bohio/dialogs/ColorPickerDialog.kt b/src/main/java/com/rama/bohio/dialogs/ColorPickerDialog.kt new file mode 100644 index 0000000..db06e08 --- /dev/null +++ b/src/main/java/com/rama/bohio/dialogs/ColorPickerDialog.kt @@ -0,0 +1,92 @@ +package com.rama.bohio.dialogs + +import android.app.Activity +import android.app.Dialog +import android.graphics.Color +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import android.view.ViewGroup.LayoutParams.WRAP_CONTENT +import android.widget.Button +import android.widget.EditText +import android.widget.Toast +import com.rama.bohio.R +import com.rama.bohio.managers.ThemeManager +import com.rama.bohio.widgets.HSVSquareView +import com.rama.bohio.widgets.HueStripView + +object ColorPickerDialog { + + fun show( + activity: Activity, + initialColor: Int, + onColorSelected: (Int) -> Unit + ) { + val dialog = Dialog(activity) + val view = LayoutInflater.from(activity).inflate(R.layout.wd_color_picker_dialog, null) + + dialog.setContentView(view) + dialog.window?.setLayout(MATCH_PARENT, WRAP_CONTENT) + + ThemeManager.applyTheme(activity, view) + + val preview = view.findViewById(R.id.preview) + val hexInput = view.findViewById(R.id.hex_input) + val applyButton = view.findViewById