diff --git a/kotlin/src/kawaii/packetik/sfx/SfxNative.kt b/kotlin/src/kawaii/packetik/sfx/SfxNative.kt new file mode 100644 index 00000000..49e0248b --- /dev/null +++ b/kotlin/src/kawaii/packetik/sfx/SfxNative.kt @@ -0,0 +1,369 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// +// Native builders for the SFX settings page. Host-only classes (UItem, +// SlideIntChooseView and Theme) are resolved through reflection so this source +// still compiles against the public Android SDK. + +package kawaii.packetik.sfx + +import android.content.Context +import android.graphics.Color +import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter +import android.util.TypedValue +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import android.widget.SeekBar +import android.widget.TextView +import java.lang.reflect.InvocationHandler +import java.lang.reflect.Method +import java.lang.reflect.Modifier +import java.lang.reflect.Proxy + +object SfxNative { + + private const val UITEM_CLASS = "org.telegram.ui.Components.UItem" + + @JvmStatic + fun createExpandable( + classLoader: ClassLoader, + id: Int, + text: String, + subtext: String, + checked: Boolean, + collapsed: Boolean, + switchClick: View.OnClickListener? + ): Any? { + return try { + val cls = Class.forName(UITEM_CLASS, true, classLoader) + val factory = findStatic(cls, "asExteraExpandableSwitch", 4) + val item = factory.invoke(null, id, text, subtext, switchClick) ?: return null + call(item, "setChecked", checked) + call(item, "setCollapsed", collapsed) + item + } catch (_: Throwable) { + null + } + } + + @JvmStatic + fun createChild( + classLoader: ClassLoader, + id: Int, + text: String, + checked: Boolean + ): Any? { + return try { + val cls = Class.forName(UITEM_CLASS, true, classLoader) + val factory = findStatic(cls, "asRoundCheckbox", 2) + val item = factory.invoke(null, id, text) ?: return null + call(item, "setChecked", checked) + call(item, "pad") + item + } catch (_: Throwable) { + null + } + } + + @JvmStatic + fun createVolumeSlider( + context: Context, + initial: Int, + title: String, + offLabel: String, + maximumLabel: String, + onChange: InvocationHandler? + ): View { + return VolumeSlider( + context, + initial.coerceIn(0, 100), + title, + offLabel, + maximumLabel, + onChange + ) + } + + private fun findStatic(cls: Class<*>, name: String, count: Int): Method { + return cls.methods.firstOrNull { + it.name == name && it.parameterCount == count && Modifier.isStatic(it.modifiers) + }?.apply { isAccessible = true } + ?: throw NoSuchMethodException("$name/$count on ${cls.name}") + } + + private fun findMethod(cls: Class<*>, name: String, count: Int): Method { + var current: Class<*>? = cls + while (current != null) { + current.declaredMethods.firstOrNull { + it.name == name && it.parameterCount == count + }?.let { + it.isAccessible = true + return it + } + current = current.superclass + } + throw NoSuchMethodException("$name/$count on ${cls.name}") + } + + private fun call(target: Any, name: String, vararg args: Any?): Any? { + return findMethod(target.javaClass, name, args.size).invoke(target, *args) + } + + private fun callbackMarker(value: Int) = value + + private class VolumeSlider( + context: Context, + initial: Int, + private val titleText: String, + private val offLabel: String, + private val maximumLabel: String, + private val onChange: InvocationHandler? + ) : LinearLayout(context) { + + private val hostLoader = context.classLoader + private val callbackMethod = SfxNative::class.java.getDeclaredMethod( + "callbackMarker", + Int::class.javaPrimitiveType + ).apply { isAccessible = true } + + init { + orientation = VERTICAL + setBackgroundColor(themeColor("key_windowBackgroundWhite", Color.WHITE)) + if (!buildMd3(context, initial)) { + buildSeekBar(context, initial) + } + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure( + MeasureSpec.makeMeasureSpec( + MeasureSpec.getSize(widthMeasureSpec), + MeasureSpec.EXACTLY + ), + MeasureSpec.makeMeasureSpec(dp(104), MeasureSpec.EXACTLY) + ) + } + + private fun buildMd3(context: Context, initial: Int): Boolean { + return try { + val slideClass = Class.forName( + "org.telegram.ui.Cells.SlideIntChooseView", + true, + hostLoader + ) + val optionsClass = Class.forName( + "org.telegram.ui.Cells.SlideIntChooseView\$Options", + true, + hostLoader + ) + val utilitiesClass = Class.forName( + "org.telegram.messenger.Utilities", + true, + hostLoader + ) + val plainInterface = Class.forName( + "org.telegram.messenger.Utilities\$CallbackReturn", + true, + hostLoader + ) + val formatInterface = Class.forName( + "org.telegram.messenger.Utilities\$Callback2Return", + true, + hostLoader + ) + val changeInterface = Class.forName( + "org.telegram.messenger.Utilities\$Callback", + true, + hostLoader + ) + + val plain = Proxy.newProxyInstance( + hostLoader, + arrayOf(plainInterface) + ) { _, method, args -> + if (method.name == "run") args?.getOrNull(0)?.toString() ?: "" + else defaultProxyValue(method.returnType) + } + val make = optionsClass.methods.firstOrNull { + it.name == "make" && it.parameterCount == 4 && + Modifier.isStatic(it.modifiers) + } ?: return false + val options = make.invoke(null, 0, 0, 100, plain) ?: return false + + val formatter = Proxy.newProxyInstance( + hostLoader, + arrayOf(formatInterface) + ) { _, method, args -> + if (method.name == "run") { + val type = (args?.getOrNull(0) as? Number)?.toInt() ?: 0 + val value = (args?.getOrNull(1) as? Number)?.toInt() ?: 0 + if (type == 0) volumeLabel(value) else "$value%" + } else { + defaultProxyValue(method.returnType) + } + } + options.javaClass.getDeclaredField("toString").apply { + isAccessible = true + set(options, formatter) + } + + val callback = Proxy.newProxyInstance( + hostLoader, + arrayOf(changeInterface) + ) { _, method, args -> + if (method.name == "run") { + notifyChanged((args?.getOrNull(0) as? Number)?.toInt() ?: initial) + } + defaultProxyValue(method.returnType) + } + + val ctor = slideClass.declaredConstructors.firstOrNull { + it.parameterCount == 2 + } ?: slideClass.declaredConstructors.firstOrNull { + it.parameterCount == 1 + } ?: return false + ctor.isAccessible = true + val slider = if (ctor.parameterCount == 2) { + ctor.newInstance(context, null) + } else { + ctor.newInstance(context) + } as? View ?: return false + + val setMethod = slideClass.methods.firstOrNull { + it.name == "set" && it.parameterCount == 3 + } ?: return false + setMethod.invoke(slider, initial, options, callback) + + setPadding(0, dp(6), 0, 0) + addView(makeText(titleText, 16f, "key_windowBackgroundWhiteBlackText"), + linearParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, 22, 0, 22, 0)) + addView(slider, LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)) + true + } catch (_: Throwable) { + false + } + } + + private fun buildSeekBar(context: Context, initial: Int) { + setPadding(dp(21), dp(8), dp(21), dp(8)) + gravity = Gravity.CENTER_VERTICAL + + val row = LinearLayout(context).apply { + orientation = HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + } + addView(row, linearParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, 0, 0, 0, 6)) + + row.addView( + makeText(titleText, 16f, "key_windowBackgroundWhiteBlackText"), + LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f) + ) + val value = makeText( + volumeLabel(initial), + 14f, + "key_windowBackgroundWhiteGrayText" + ) + row.addView(value, LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)) + + val seekBar = SeekBar(context).apply { + max = 100 + progress = initial + } + try { + val accent = themeColor("key_featuredStickers_addButton", Color.BLUE) + val filter = PorterDuffColorFilter(accent, PorterDuff.Mode.SRC_IN) + seekBar.progressDrawable?.colorFilter = filter + seekBar.thumb?.colorFilter = filter + } catch (_: Throwable) { + } + seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { + override fun onProgressChanged(bar: SeekBar?, progress: Int, fromUser: Boolean) { + value.text = volumeLabel(progress) + if (fromUser) notifyChanged(progress) + } + + override fun onStartTrackingTouch(bar: SeekBar?) = Unit + override fun onStopTrackingTouch(bar: SeekBar?) = Unit + }) + addView(seekBar, LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)) + } + + private fun notifyChanged(value: Int) { + try { + onChange?.invoke(this, callbackMethod, arrayOf(value.coerceIn(0, 100))) + } catch (_: Throwable) { + } + } + + private fun volumeLabel(value: Int): String { + return when (value) { + 0 -> offLabel + 100 -> maximumLabel + else -> "$value%" + } + } + + private fun makeText(textValue: String, size: Float, colorKey: String): TextView { + return TextView(context).apply { + text = textValue + setTextSize(TypedValue.COMPLEX_UNIT_DIP, size) + setTextColor(themeColor(colorKey, Color.DKGRAY)) + gravity = Gravity.CENTER_VERTICAL + } + } + + private fun themeColor(keyName: String, fallback: Int): Int { + return try { + val theme = Class.forName( + "org.telegram.ui.ActionBar.Theme", + true, + hostLoader + ) + val key = theme.getDeclaredField(keyName).apply { + isAccessible = true + }.get(null) + val method = theme.methods.firstOrNull { + it.name == "getColor" && it.parameterCount == 1 && + Modifier.isStatic(it.modifiers) + } ?: return fallback + (method.invoke(null, key) as? Number)?.toInt() ?: fallback + } catch (_: Throwable) { + fallback + } + } + + private fun linearParams( + width: Int, + height: Int, + left: Int, + top: Int, + right: Int, + bottom: Int + ): LayoutParams { + return LayoutParams(width, height).apply { + setMargins(dp(left), dp(top), dp(right), dp(bottom)) + } + } + + private fun dp(value: Int): Int { + return (value * resources.displayMetrics.density + 0.5f).toInt() + } + + private fun defaultProxyValue(type: Class<*>): Any? { + if (!type.isPrimitive || type == Void.TYPE) return null + return when (type) { + java.lang.Boolean.TYPE -> false + java.lang.Character.TYPE -> '\u0000' + java.lang.Byte.TYPE -> 0.toByte() + java.lang.Short.TYPE -> 0.toShort() + java.lang.Integer.TYPE -> 0 + java.lang.Long.TYPE -> 0L + java.lang.Float.TYPE -> 0f + java.lang.Double.TYPE -> 0.0 + else -> null + } + } + } +} diff --git a/packit/.elyxbuilder/stats.yml b/packit/.elyxbuilder/stats.yml index 503f8a34..c74ed0b7 100644 --- a/packit/.elyxbuilder/stats.yml +++ b/packit/.elyxbuilder/stats.yml @@ -1,4 +1,4 @@ -builds: 547 -compiledBuilds: 12 -failedBuilds: 8 +builds: 549 +compiledBuilds: 13 +failedBuilds: 9 uncompiledBuilds: 527 diff --git a/packit/dex/sfx.dex b/packit/dex/sfx.dex new file mode 100644 index 00000000..066fdbab Binary files /dev/null and b/packit/dex/sfx.dex differ diff --git a/packit/meta.yml b/packit/meta.yml index a0f17fd3..d41ef320 100644 --- a/packit/meta.yml +++ b/packit/meta.yml @@ -1,7 +1,7 @@ name: PackIt description: "{plugin_description} [shareui/packit-source](https://github.com/shareui/packit-source)" id: shareui_packit -version: "0.0.0-rc.651" +version: "0.0.0-rc.652" author: "@packitX" app_version: ">=12.8.1" sdk_version: ">=1.4.4.6" diff --git a/packit/src/SettingsActivity/SubSettings/sfx.py b/packit/src/SettingsActivity/SubSettings/sfx.py index 6f8e6581..aca2881e 100644 --- a/packit/src/SettingsActivity/SubSettings/sfx.py +++ b/packit/src/SettingsActivity/SubSettings/sfx.py @@ -1,27 +1,161 @@ # pyright: reportMissingImports=false # SPDX-License-Identifier: GPL-3.0-or-later -from ui.settings import Header, Divider -from elyx import strings +from packutil import logx +from ui.settings import Header, Divider, Custom +from elyx import strings, settings + +_SFX_CHILDREN = [ + ("sfx_install", "sfx_install", False), + ("sfx_copy_link", "sfx_copy_link", False), + ("sfx_search", "sfx_search", False), + ("sfx_clear_search", "sfx_clear_search", False), + ("sfx_achievement", "sfx_achievement", True), + ("sfx_available_updates", "sfx_available_updates", False), +] + + +def _item_id(key): + return hash(key) & 0x7FFFFFFF + + +def _reload(): + settings.set("_es_dummy", not settings.get("_es_dummy", False), reload_settings=True) + + +def _make_expandable(other_settings, ctx): + try: + from android_utils import OnClickListener + from ...dexLoader import sfxExpandableCreate + + checked_count = sum( + 1 for key, _, default in _SFX_CHILDREN if settings.get(key, default) + ) + + def switch_click(view): + new_value = not any( + settings.get(key, default) + for key, _, default in _SFX_CHILDREN + ) + for key, _, _ in _SFX_CHILDREN: + settings.set(key, new_value, reload_settings=False) + _reload() + + listener = OnClickListener(switch_click) + item = sfxExpandableCreate( + ctx, + _item_id("sfx_enabled"), + strings.sfx_header, + f"{checked_count}/{len(_SFX_CHILDREN)}", + checked_count > 0, + not other_settings._es_is_expanded("sfx_enabled"), + listener, + ) + if item is None: + from org.telegram.ui.Components import UItem + item = UItem.asExteraExpandableSwitch( + _item_id("sfx_enabled"), + strings.sfx_header, + f"{checked_count}/{len(_SFX_CHILDREN)}", + listener, + ) + item.setChecked(checked_count > 0) + item.setCollapsed(not other_settings._es_is_expanded("sfx_enabled")) + return Custom( + item=item, + on_click=lambda view: other_settings._es_toggle_and_reload("sfx_enabled"), + ) + except Exception as e: + logx(f"sfx: expandable create error: {e}", False) + return None + + +def _make_child(ctx, key, text, default): + try: + from ...dexLoader import sfxChildCreate + + item = sfxChildCreate( + ctx, + _item_id(key), + text, + settings.get(key, default), + ) + if item is None: + from org.telegram.ui.Components import UItem + item = UItem.asRoundCheckbox(_item_id(key), text) + item.setChecked(settings.get(key, default)) + item.pad() + + def on_click(view): + settings.set(key, not settings.get(key, default), reload_settings=False) + _reload() + + return Custom(item=item, on_click=on_click) + except Exception as e: + logx(f"sfx: child {key} create error: {e}", False) + return None + + +def _make_volume_slider(ctx): + try: + from java import dynamic_proxy + from java.lang.reflect import InvocationHandler + from ...dexLoader import sfxVolumeSliderCreate + + class _VolumeChange(dynamic_proxy(InvocationHandler)): + def invoke(self, proxy, method, args): + if args: + settings.set("sfx_volume", int(args[0]), reload_settings=False) + return None + + initial = int(settings.get("sfx_volume", 100)) + off_label = strings["sfx_volume_off"] + maximum_label = strings["sfx_volume_maximum"] + view = sfxVolumeSliderCreate( + ctx, + initial, + strings.sfx_volume, + off_label, + maximum_label, + _VolumeChange(), + ) + if view is None: + from ...ui.md3Slider import createMd3Slider + + def on_change(value): + settings.set("sfx_volume", int(value), reload_settings=False) + + def format_value(label_type, value): + if label_type != 0: + return f"{value}%" + if value == 0: + return off_label + if value == 100: + return maximum_label + return f"{value}%" + + slider = createMd3Slider( + ctx, 0, 100, initial, on_change, format_value + ) + view = slider.view if slider is not None else None + return Custom(view=view) if view is not None else None + except Exception as e: + logx(f"sfx: volume slider create error: {e}", False) + return None def build_sfx_page(other_settings, ctx): + expanded = other_settings._es_is_expanded("sfx_enabled") items = [ Header(text=strings.sfx_header), - other_settings._make_expandable_switch("sfx_enabled", strings.sfx_header, [ - ("sfx_install", False), - ("sfx_copy_link", False), - ("sfx_search", False), - ("sfx_clear_search", False), - ("sfx_achievement", True), - ("sfx_available_updates", False), - ]), - other_settings._make_es_child("sfx_install", strings.sfx_install, False) if other_settings._es_is_expanded("sfx_enabled") else None, - other_settings._make_es_child("sfx_copy_link", strings.sfx_copy_link, False) if other_settings._es_is_expanded("sfx_enabled") else None, - other_settings._make_es_child("sfx_search", strings.sfx_search, False) if other_settings._es_is_expanded("sfx_enabled") else None, - other_settings._make_es_child("sfx_clear_search", strings.sfx_clear_search, False) if other_settings._es_is_expanded("sfx_enabled") else None, - other_settings._make_es_child("sfx_achievement", strings.sfx_achievement, True) if other_settings._es_is_expanded("sfx_enabled") else None, - other_settings._make_es_child("sfx_available_updates", strings.sfx_available_updates, False) if other_settings._es_is_expanded("sfx_enabled") else None, - other_settings._build_sfx_volume_slider(ctx), + _make_expandable(other_settings, ctx), + *( + [ + _make_child(ctx, key, getattr(strings, label), default) + for key, label, default in _SFX_CHILDREN + ] + if expanded else [] + ), + _make_volume_slider(ctx), Divider(text=strings.sfx_header_desc), ] - return [item for item in items if item is not None] \ No newline at end of file + return [item for item in items if item is not None] diff --git a/packit/src/SettingsActivity/service/fastExpandableHook.py b/packit/src/SettingsActivity/service/fastExpandableHook.py index e023ce1d..5af0787c 100644 --- a/packit/src/SettingsActivity/service/fastExpandableHook.py +++ b/packit/src/SettingsActivity/service/fastExpandableHook.py @@ -16,6 +16,7 @@ ("sfx_search", False), ("sfx_clear_search", False), ("sfx_achievement", True), + ("sfx_available_updates", False), ], "inline_send_enabled": [ ("inline_send_name", True), @@ -159,4 +160,4 @@ def _original_call(param): return param.method.invoke(param.thisObject, param.args) except Exception as e: logx(f"fastExpandableHook: _original_call error: {e}", False) - return None \ No newline at end of file + return None diff --git a/packit/src/SettingsActivity/settings.py b/packit/src/SettingsActivity/settings.py index 4e82dc4f..c4c8093c 100644 --- a/packit/src/SettingsActivity/settings.py +++ b/packit/src/SettingsActivity/settings.py @@ -44,7 +44,7 @@ from dataclasses import dataclass, field from ..ui.FontPickerBottomSheet import showFontPicker from ..ui.FontManager import getSelectedFilename -from extera_utils.classes import Base, java_subclass, jMVELoverride, joverride +from extera_utils.classes import Base, java_subclass, joverride def _reload_plugin_settings(): try: @@ -1311,151 +1311,6 @@ def _buildSortMenuDesignToggle(context, key, default, on_change=None): return None -@java_subclass(LinearLayout) -class SfxVolumeSlider(Base): - # tall enough for the title row + the 75dp MD3 SlideIntChooseView; the - # SeekBar fallback centers itself vertically in the same height - onMeasure = jMVELoverride( - arguments=[("widthMeasureSpec", "int"), ("heightMeasureSpec", "int")], - code=""" - SUPER_onMeasure( - android.view.View$MeasureSpec.makeMeasureSpec( - android.view.View$MeasureSpec.getSize(widthMeasureSpec), - android.view.View$MeasureSpec.EXACTLY - ), - android.view.View$MeasureSpec.makeMeasureSpec( - org.telegram.messenger.AndroidUtilities.dp(104), - android.view.View$MeasureSpec.EXACTLY - ) - ); - return null; - """, - ) - - def on_post_init(self, context): - from android.view import Gravity - from elyx import settings as _s - dp = AndroidUtilities.dp - - self._s = _s - self._md3 = None - self.setOrientation(LinearLayout.VERTICAL) - self.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)) - - try: - initial = int(_s.get("sfx_volume", 100)) - except Exception: - initial = 100 - - def _on_change(v): - try: - _s.set("sfx_volume", int(v), reload_settings=False) - except Exception: - pass - - def _fmt(label_type, v): - # center label shows the pretty value, edges show plain percents - if label_type == 0: - return SfxVolumeSlider._volLabel(v) - return f"{v}%" - - try: - from ..ui.md3Slider import createMd3Slider - md3 = createMd3Slider(context, 0, 100, initial, _on_change, _fmt) - except Exception as e: - logx(f"SfxVolumeSlider: md3 import/create error: {e}", False) - md3 = None - - if md3 is not None: - self.setPadding(0, dp(6), 0, 0) - title = TextView(context) - title.setText(str(strings.sfx_volume)) - title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16) - title.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)) - self.addView(title, LayoutHelper.createLinear(-2, -2, 22, 0, 22, 0)) - # the MD3 cell is edge-to-edge with its own 22dp text insets - self.addView(md3.view, LayoutHelper.createLinear(-1, -2)) - self._md3 = md3 - return - - self._build_seekbar_fallback(context) - - def _build_seekbar_fallback(self, context): - from android.widget import SeekBar - from android.view import Gravity - from java import dynamic_proxy - _s = self._s - dp = AndroidUtilities.dp - - self.setPadding(dp(21), dp(8), dp(21), dp(8)) - self.setGravity(Gravity.CENTER_VERTICAL) - - labelRow = LinearLayout(context) - labelRow.setOrientation(LinearLayout.HORIZONTAL) - labelRow.setGravity(Gravity.CENTER_VERTICAL) - self.addView(labelRow, LayoutHelper.createLinear(-1, -2, 0, 0, 0, 6)) - - self._labelView = TextView(context) - self._labelView.setText(str(strings.sfx_volume)) - self._labelView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16) - self._labelView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)) - labelRow.addView(self._labelView, LayoutHelper.createLinear(0, -2, 1.0, Gravity.CENTER_VERTICAL)) - - self._valueView = TextView(context) - self._valueView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14) - self._valueView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText)) - labelRow.addView(self._valueView, LayoutHelper.createLinear(-2, -2, Gravity.CENTER_VERTICAL)) - - self._seekBar = SeekBar(context) - self._seekBar.setMax(100) - - try: - from android.graphics import PorterDuff, PorterDuffColorFilter - accent = Theme.getColor(Theme.key_featuredStickers_addButton) - self._seekBar.getProgressDrawable().setColorFilter(PorterDuffColorFilter(accent, PorterDuff.Mode.SRC_IN)) - self._seekBar.getThumb().setColorFilter(PorterDuffColorFilter(accent, PorterDuff.Mode.SRC_IN)) - except Exception: - pass - - valueView = self._valueView - - class _ChangeListener(dynamic_proxy(SeekBar.OnSeekBarChangeListener)): - def onProgressChanged(self_l, sb, progress, fromUser): - try: - _s.set("sfx_volume", progress, reload_settings=False) - valueView.setText(SfxVolumeSlider._volLabel(progress)) - except Exception: - pass - - def onStartTrackingTouch(self_l, sb): - pass - - def onStopTrackingTouch(self_l, sb): - pass - - self._seekBar.setOnSeekBarChangeListener(_ChangeListener()) - self.addView(self._seekBar, LayoutHelper.createLinear(-1, -2)) - - def bind(self): - try: - vol = int(self._s.get("sfx_volume", 100)) - except Exception: - vol = 100 - if self._md3 is not None: - self._md3.set_value(vol, False) - return - self._seekBar.setProgress(vol) - self._valueView.setText(SfxVolumeSlider._volLabel(vol)) - - @staticmethod - def _volLabel(v): - if v == 0: - return str(strings["sfx_volume_off"]) - if v == 100: - return str(strings["sfx_volume_maximum"]) - return f"{v}%" - - class OtherSettings: def __init__(self, chat_button=None, plugin=None): self.chat_button = chat_button @@ -1622,19 +1477,6 @@ def _build_hash_function_item(self, ctx): icon="msg_sendfile", ) - def _build_sfx_volume_slider(self, ctx): - # slider 0-100 for sfx_volume setting, shown as a separate row under sfx section - try: - if not ctx: - return None - slider = SfxVolumeSlider.new_instance(ctx) - slider.bind() - return Custom(view=slider.java) - except Exception as e: - logx(f"other: _build_sfx_volume_slider error: {e}", False) - return None - - def _open_main_menu_settings(self, view): try: from hook_utils import find_class @@ -2137,4 +1979,4 @@ def build(self): link_alias="file_settings" )) - return items \ No newline at end of file + return items diff --git a/packit/src/dexLoader.py b/packit/src/dexLoader.py index fc43efe9..2c8f33ca 100644 --- a/packit/src/dexLoader.py +++ b/packit/src/dexLoader.py @@ -139,6 +139,7 @@ def _ia(lst): _CATALOG_CLASS = "kawaii.packetik.catalog.CatalogChromeNative" +_SFX_CLASS = "kawaii.packetik.sfx.SfxNative" def catalogChromeCreate(context, main_bg, card_bg, card_pressed, text_color, @@ -203,6 +204,64 @@ def _i(v): return None +def sfxExpandableCreate(context, item_id, text, subtext, checked, collapsed, + switch_click): + try: + if context is None: + from org.telegram.messenger import ApplicationLoader + context = ApplicationLoader.applicationContext + cls = _loadClass("sfx", _SFX_CLASS, context) + if cls is None: + return None + from java import jint + return _callStatic( + cls, "createExpandable", + context.getClassLoader(), jint(int(item_id)), str(text), str(subtext), + bool(checked), bool(collapsed), switch_click, + ) + except Exception as e: + logx(f"dexLoader: sfxExpandableCreate error: {e}", False) + return None + + +def sfxChildCreate(context, item_id, text, checked): + try: + if context is None: + from org.telegram.messenger import ApplicationLoader + context = ApplicationLoader.applicationContext + cls = _loadClass("sfx", _SFX_CLASS, context) + if cls is None: + return None + from java import jint + return _callStatic( + cls, "createChild", + context.getClassLoader(), jint(int(item_id)), str(text), bool(checked), + ) + except Exception as e: + logx(f"dexLoader: sfxChildCreate error: {e}", False) + return None + + +def sfxVolumeSliderCreate(context, initial, title, off_label, maximum_label, + on_change): + try: + if context is None: + from org.telegram.messenger import ApplicationLoader + context = ApplicationLoader.applicationContext + cls = _loadClass("sfx", _SFX_CLASS, context) + if cls is None: + return None + from java import jint + return _callStatic( + cls, "createVolumeSlider", + context, jint(int(initial)), str(title), str(off_label), + str(maximum_label), on_change, + ) + except Exception as e: + logx(f"dexLoader: sfxVolumeSliderCreate error: {e}", False) + return None + + def openFileCancel(view): try: cls = _loaded.get("openfile") diff --git a/scripts/linux/kotlin-build.sh b/scripts/linux/kotlin-build.sh index 57cd99b2..dc12d571 100644 --- a/scripts/linux/kotlin-build.sh +++ b/scripts/linux/kotlin-build.sh @@ -26,6 +26,7 @@ PACKAGES=( "badges=kawaii.packetik.badges.BadgesNative" "openfile=kawaii.packetik.openfile.OpenFileNative" "catalog=kawaii.packetik.catalog.CatalogChromeNative" + "sfx=kawaii.packetik.sfx.SfxNative" ) die() { echo "error: $*" >&2; exit 1; }