nowfycore 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,45 @@
1
+ Metadata-Version: 2.4
2
+ Name: nowfycore
3
+ Version: 1.0.0
4
+ Summary: Nowfy core runtime package (pure layer)
5
+ Author: AGeekApple
6
+ Requires-Python: >=3.9
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: requests
9
+ Requires-Dist: yt-dlp
10
+ Requires-Dist: ytmusicapi
11
+
12
+ # nowfycore
13
+
14
+ Pure Python core runtime for Nowfy.
15
+
16
+ ## Local build
17
+
18
+ ```bash
19
+ python -m build packages/nowfycore
20
+ ```
21
+
22
+ Artifacts:
23
+ - `packages/nowfycore/dist/nowfycore-1.0.0-py3-none-any.whl`
24
+ - `packages/nowfycore/dist/nowfycore-1.0.0.tar.gz`
25
+
26
+ ## Release helper
27
+
28
+ ```bash
29
+ python packages/nowfycore/scripts/release_nowfycore.py
30
+ python packages/nowfycore/scripts/release_nowfycore.py --upload --repository pypi
31
+ ```
32
+
33
+ Optional:
34
+ - `--repository testpypi`
35
+ - `--skip-existing`
36
+
37
+ ## Runtime usage in Nowfy plugin
38
+
39
+ `nowfy.plugin` uses:
40
+
41
+ ```python
42
+ __requirements__ = ["nowfycore>=1.0.0"]
43
+ ```
44
+
45
+ So only `nowfy.plugin` needs to be installed by the user; core runtime is resolved through requirements.
@@ -0,0 +1,34 @@
1
+ # nowfycore
2
+
3
+ Pure Python core runtime for Nowfy.
4
+
5
+ ## Local build
6
+
7
+ ```bash
8
+ python -m build packages/nowfycore
9
+ ```
10
+
11
+ Artifacts:
12
+ - `packages/nowfycore/dist/nowfycore-1.0.0-py3-none-any.whl`
13
+ - `packages/nowfycore/dist/nowfycore-1.0.0.tar.gz`
14
+
15
+ ## Release helper
16
+
17
+ ```bash
18
+ python packages/nowfycore/scripts/release_nowfycore.py
19
+ python packages/nowfycore/scripts/release_nowfycore.py --upload --repository pypi
20
+ ```
21
+
22
+ Optional:
23
+ - `--repository testpypi`
24
+ - `--skip-existing`
25
+
26
+ ## Runtime usage in Nowfy plugin
27
+
28
+ `nowfy.plugin` uses:
29
+
30
+ ```python
31
+ __requirements__ = ["nowfycore>=1.0.0"]
32
+ ```
33
+
34
+ So only `nowfy.plugin` needs to be installed by the user; core runtime is resolved through requirements.
@@ -0,0 +1,22 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "nowfycore"
7
+ version = "1.0.0"
8
+ description = "Nowfy core runtime package (pure layer)"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ authors = [{name = "AGeekApple"}]
12
+ dependencies = [
13
+ "requests",
14
+ "yt-dlp",
15
+ "ytmusicapi",
16
+ ]
17
+
18
+ [tool.setuptools]
19
+ package-dir = {"" = "src"}
20
+
21
+ [tool.setuptools.packages.find]
22
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,87 @@
1
+ from .api import get_nowfy_core_api, NowfyCoreAPI, NowfyCoreBackendMixin
2
+ from .language import SUPPORTED_LANGS, get_plugin_lang_code, get_plugin_lang_index, normalize_plugin_lang_index
3
+ from .state import CoreState
4
+ from .services import get_bio_feature_services
5
+ from .flags import is_addon_enabled, is_theme_enabled
6
+ from .bridge import invoke_action
7
+ from .corekeys import CORE_SETTINGS_KEYS, export_core_settings_from_plugin, import_core_settings_to_plugin
8
+ from .trackhub import append_track_hub_style_selector, build_track_hub_subfragment_items
9
+ from .panel import (
10
+ on_panel_drawer_toggle,
11
+ on_panel_disable_logs_toggle,
12
+ on_lab_nowplaying_pill_toggle,
13
+ on_lab_send_haptic_toggle,
14
+ )
15
+ from .cache import clear_cache_data
16
+ from .themes import inject_core_theme_multiselect
17
+ from .about import animate_opening_avatar, inject_about_support_rich_header
18
+ from .injections import (
19
+ inject_services_cards_custom,
20
+ inject_home_services_entry_custom,
21
+ inject_statsfm_profile_custom,
22
+ )
23
+ from .ui_controls import (
24
+ get_nowtab_control_style,
25
+ apply_nowtab_control_modern_ui,
26
+ update_nowtab_control_modern_state,
27
+ inject_track_hub_source_slide,
28
+ inject_nowtab_control_style_slide,
29
+ inject_vinify_logo_position_radios,
30
+ inject_vinify_cover_shape_slide,
31
+ inject_nowfy_pulse_style_slide,
32
+ inject_custom_cover_selector,
33
+ on_custom_cover_select,
34
+ on_custom_cover_toggle,
35
+ )
36
+ from .trackhub_ui import render_track_hub_box_cell, clear_track_hub_box_cell
37
+ from .tab_actions import send_tab_track_download, send_tab_music_preview
38
+ from .welcome import maybe_show_nowfy_welcome_sheet
39
+ from .translations import get_nowfy_translations
40
+
41
+ __all__ = [
42
+ "get_nowfy_core_api",
43
+ "NowfyCoreAPI",
44
+ "NowfyCoreBackendMixin",
45
+ "SUPPORTED_LANGS",
46
+ "get_plugin_lang_code",
47
+ "get_plugin_lang_index",
48
+ "normalize_plugin_lang_index",
49
+ "CoreState",
50
+ "get_bio_feature_services",
51
+ "is_addon_enabled",
52
+ "is_theme_enabled",
53
+ "invoke_action",
54
+ "CORE_SETTINGS_KEYS",
55
+ "export_core_settings_from_plugin",
56
+ "import_core_settings_to_plugin",
57
+ "append_track_hub_style_selector",
58
+ "build_track_hub_subfragment_items",
59
+ "on_panel_drawer_toggle",
60
+ "on_panel_disable_logs_toggle",
61
+ "on_lab_nowplaying_pill_toggle",
62
+ "on_lab_send_haptic_toggle",
63
+ "clear_cache_data",
64
+ "inject_core_theme_multiselect",
65
+ "animate_opening_avatar",
66
+ "inject_about_support_rich_header",
67
+ "inject_services_cards_custom",
68
+ "inject_home_services_entry_custom",
69
+ "inject_statsfm_profile_custom",
70
+ "get_nowtab_control_style",
71
+ "apply_nowtab_control_modern_ui",
72
+ "update_nowtab_control_modern_state",
73
+ "inject_track_hub_source_slide",
74
+ "inject_nowtab_control_style_slide",
75
+ "inject_vinify_logo_position_radios",
76
+ "inject_vinify_cover_shape_slide",
77
+ "inject_nowfy_pulse_style_slide",
78
+ "inject_custom_cover_selector",
79
+ "on_custom_cover_select",
80
+ "on_custom_cover_toggle",
81
+ "render_track_hub_box_cell",
82
+ "clear_track_hub_box_cell",
83
+ "send_tab_track_download",
84
+ "send_tab_music_preview",
85
+ "maybe_show_nowfy_welcome_sheet",
86
+ "get_nowfy_translations",
87
+ ]
@@ -0,0 +1,165 @@
1
+ def animate_opening_avatar(plugin, avatar_view, from_y_dp=20, duration_ms=620):
2
+ try:
3
+ if avatar_view is None:
4
+ return False
5
+ AndroidUtilities = None
6
+ try:
7
+ from hook_utils import find_class
8
+
9
+ AndroidUtilities = find_class("org.telegram.messenger.AndroidUtilities")
10
+ except Exception:
11
+ AndroidUtilities = None
12
+ try:
13
+ if AndroidUtilities is not None:
14
+ avatar_view.setCameraDistance(float(AndroidUtilities.dp(800)))
15
+ except Exception:
16
+ pass
17
+ try:
18
+ if AndroidUtilities is not None:
19
+ avatar_view.setTranslationY(float(AndroidUtilities.dp(int(from_y_dp or 20))))
20
+ except Exception:
21
+ pass
22
+ try:
23
+ avatar_view.setAlpha(0.0)
24
+ except Exception:
25
+ pass
26
+ try:
27
+ avatar_view.setRotationY(-120.0)
28
+ except Exception:
29
+ pass
30
+ try:
31
+ avatar_view.animate().cancel()
32
+ except Exception:
33
+ pass
34
+ try:
35
+ avatar_view.animate().translationY(0.0).alpha(1.0).rotationY(0.0).setDuration(int(duration_ms or 620)).start()
36
+ except Exception:
37
+ pass
38
+ return True
39
+ except Exception:
40
+ return False
41
+
42
+
43
+ def _build_support_project_rich_item(plugin, description_text=None):
44
+ try:
45
+ from org.telegram.messenger import ApplicationLoader
46
+ from org.telegram.ui.Components import UItem
47
+ from android.widget import LinearLayout, TextView
48
+ from android.view import Gravity
49
+ from android.util import TypedValue
50
+ from android.graphics.drawable import GradientDrawable
51
+
52
+ Theme = None
53
+ AndroidUtilities = None
54
+ get_last_fragment = None
55
+ tr_fn = None
56
+ try:
57
+ import nowfy as nowfy_mod
58
+
59
+ Theme = getattr(nowfy_mod, "Theme", None)
60
+ AndroidUtilities = getattr(nowfy_mod, "AndroidUtilities", None)
61
+ get_last_fragment = getattr(nowfy_mod, "get_last_fragment", None)
62
+ tr_fn = getattr(nowfy_mod, "tr", None)
63
+ except Exception:
64
+ pass
65
+
66
+ ctx = None
67
+ try:
68
+ frag = get_last_fragment() if callable(get_last_fragment) else None
69
+ ctx = frag.getParentActivity() if frag and frag.getParentActivity() else ApplicationLoader.applicationContext
70
+ except Exception:
71
+ ctx = ApplicationLoader.applicationContext
72
+ if ctx is None:
73
+ return None
74
+
75
+ rich = LinearLayout(ctx)
76
+ rich.setOrientation(LinearLayout.VERTICAL)
77
+ rich.setGravity(Gravity.START)
78
+ try:
79
+ if AndroidUtilities is not None:
80
+ rich.setPadding(AndroidUtilities.dp(14), AndroidUtilities.dp(14), AndroidUtilities.dp(14), AndroidUtilities.dp(14))
81
+ except Exception:
82
+ pass
83
+ try:
84
+ bg = GradientDrawable()
85
+ if AndroidUtilities is not None:
86
+ bg.setCornerRadius(AndroidUtilities.dp(16))
87
+ bg.setColor(0x162B3E5A)
88
+ rich.setBackground(bg)
89
+ except Exception:
90
+ pass
91
+
92
+ desc = TextView(ctx)
93
+ txt = str(description_text or "").strip()
94
+ if not txt:
95
+ try:
96
+ txt = str(plugin.tr("about_plugin_description") if hasattr(plugin, "tr") else "")
97
+ except Exception:
98
+ txt = ""
99
+ if not txt:
100
+ try:
101
+ txt = str(tr_fn("about_plugin_description") if callable(tr_fn) else "")
102
+ except Exception:
103
+ txt = ""
104
+ if not txt:
105
+ txt = "Support the project."
106
+ desc.setText(txt)
107
+ desc.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13)
108
+ desc.setGravity(Gravity.START)
109
+ try:
110
+ if Theme is not None:
111
+ desc.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText))
112
+ else:
113
+ desc.setTextColor(0xFFD2DCEB)
114
+ except Exception:
115
+ pass
116
+ rich.addView(desc)
117
+ return UItem.asCustom(rich)
118
+ except Exception:
119
+ return None
120
+
121
+
122
+ def inject_about_support_rich_header(plugin, activity, items):
123
+ try:
124
+ if activity is None or items is None:
125
+ return False
126
+ desc_txt = ""
127
+ try:
128
+ desc_txt = str(plugin.tr("about_plugin_description") if hasattr(plugin, "tr") else "")
129
+ except Exception:
130
+ desc_txt = ""
131
+
132
+ remove_idxs = []
133
+ for i in range(items.size()):
134
+ try:
135
+ it = items.get(i)
136
+ txt = ""
137
+ try:
138
+ if hasattr(plugin, "_get_uitem_text"):
139
+ txt = str(plugin._get_uitem_text(it) or "").strip()
140
+ except Exception:
141
+ txt = ""
142
+ if desc_txt and txt == desc_txt:
143
+ remove_idxs.append(i)
144
+ except Exception:
145
+ pass
146
+ for i in reversed(remove_idxs):
147
+ try:
148
+ items.remove(i)
149
+ except Exception:
150
+ pass
151
+
152
+ rich_item = _build_support_project_rich_item(plugin, desc_txt)
153
+ if rich_item is not None:
154
+ try:
155
+ rich_item.object2 = "__about_support_rich__"
156
+ except Exception:
157
+ pass
158
+ try:
159
+ items.add(0, rich_item)
160
+ except Exception:
161
+ items.add(rich_item)
162
+ return True
163
+ return False
164
+ except Exception:
165
+ return False
@@ -0,0 +1,268 @@
1
+ from .language import (
2
+ get_plugin_lang_code as _get_plugin_lang_code,
3
+ get_plugin_lang_index as _get_plugin_lang_index,
4
+ normalize_plugin_lang_index as _normalize_plugin_lang_index,
5
+ )
6
+ from .state import CoreState
7
+ from .services import get_bio_feature_services as _get_bio_feature_services
8
+ from .flags import is_addon_enabled as _is_addon_enabled, is_theme_enabled as _is_theme_enabled
9
+ from .bridge import invoke_action as _invoke_action
10
+ from .corekeys import (
11
+ export_core_settings_from_plugin as _export_core_settings_from_plugin,
12
+ import_core_settings_to_plugin as _import_core_settings_to_plugin,
13
+ )
14
+ from .trackhub import build_track_hub_subfragment_items as _build_track_hub_subfragment_items
15
+ from .themes import inject_core_theme_multiselect as _inject_core_theme_multiselect
16
+ from .panel import (
17
+ on_panel_drawer_toggle as _on_panel_drawer_toggle,
18
+ on_panel_disable_logs_toggle as _on_panel_disable_logs_toggle,
19
+ on_lab_nowplaying_pill_toggle as _on_lab_nowplaying_pill_toggle,
20
+ on_lab_send_haptic_toggle as _on_lab_send_haptic_toggle,
21
+ )
22
+ from .cache import clear_cache_data as _clear_cache_data
23
+ from .about import (
24
+ animate_opening_avatar as _animate_opening_avatar,
25
+ inject_about_support_rich_header as _inject_about_support_rich_header,
26
+ )
27
+ from .injections import (
28
+ inject_services_cards_custom as _inject_services_cards_custom,
29
+ inject_home_services_entry_custom as _inject_home_services_entry_custom,
30
+ inject_statsfm_profile_custom as _inject_statsfm_profile_custom,
31
+ )
32
+ from .ui_controls import (
33
+ get_nowtab_control_style as _get_nowtab_control_style,
34
+ apply_nowtab_control_modern_ui as _apply_nowtab_control_modern_ui,
35
+ update_nowtab_control_modern_state as _update_nowtab_control_modern_state,
36
+ inject_track_hub_source_slide as _inject_track_hub_source_slide,
37
+ inject_nowtab_control_style_slide as _inject_nowtab_control_style_slide,
38
+ inject_vinify_logo_position_radios as _inject_vinify_logo_position_radios,
39
+ inject_vinify_cover_shape_slide as _inject_vinify_cover_shape_slide,
40
+ inject_nowfy_pulse_style_slide as _inject_nowfy_pulse_style_slide,
41
+ inject_custom_cover_selector as _inject_custom_cover_selector,
42
+ on_custom_cover_select as _on_custom_cover_select,
43
+ on_custom_cover_toggle as _on_custom_cover_toggle,
44
+ )
45
+ from .trackhub_ui import (
46
+ render_track_hub_box_cell as _render_track_hub_box_cell,
47
+ clear_track_hub_box_cell as _clear_track_hub_box_cell,
48
+ )
49
+ from .tab_actions import (
50
+ send_tab_track_download as _send_tab_track_download,
51
+ send_tab_music_preview as _send_tab_music_preview,
52
+ )
53
+ from .welcome import maybe_show_nowfy_welcome_sheet as _maybe_show_nowfy_welcome_sheet
54
+
55
+
56
+ class NowfyCoreBackendMixin:
57
+ """Compatibility mixin kept for migration safety."""
58
+
59
+ pass
60
+
61
+
62
+ class NowfyCoreAPI:
63
+ """Core facade consumed by nowfy.plugin."""
64
+
65
+ def __init__(self):
66
+ self._state = CoreState()
67
+ self._runtime_plugin = None
68
+
69
+ def register_runtime_plugin(self, plugin):
70
+ self._runtime_plugin = plugin
71
+ return True
72
+
73
+ def is_core_active(self):
74
+ return True
75
+
76
+ def is_addon_enabled(self, plugin, addon_key, default_value=False):
77
+ return _is_addon_enabled(plugin, addon_key, default_value)
78
+
79
+ def are_translation_resources_ready(self):
80
+ plugin = self._runtime_plugin
81
+ if plugin is None:
82
+ return True
83
+ try:
84
+ tr_fn = getattr(plugin, "tr", None)
85
+ if not callable(tr_fn):
86
+ return True
87
+ probes = ("custom_command", "nowfy_panel_section_desc", "core_language_label")
88
+ hits = 0
89
+ for key in probes:
90
+ try:
91
+ val = str(tr_fn(key) or "").strip()
92
+ if val and val != key:
93
+ hits += 1
94
+ except Exception:
95
+ pass
96
+ return hits >= 2
97
+ except Exception:
98
+ return True
99
+
100
+ def ensure_translation_resources(self, silent=False):
101
+ plugin = self._runtime_plugin
102
+ if plugin is None:
103
+ return True
104
+ try:
105
+ if self.are_translation_resources_ready():
106
+ return True
107
+ # Best-effort hook if host exposes manual sync action.
108
+ sync_fn = getattr(plugin, "_run_sync_translations_action", None)
109
+ if callable(sync_fn):
110
+ try:
111
+ sync_fn()
112
+ except Exception:
113
+ pass
114
+ return self.are_translation_resources_ready()
115
+ except Exception:
116
+ return False
117
+
118
+ def maybe_show_nowfy_welcome_sheet(self, plugin):
119
+ return _maybe_show_nowfy_welcome_sheet(plugin)
120
+
121
+ def get_nowtab_control_style(self, plugin):
122
+ return _get_nowtab_control_style(plugin)
123
+
124
+ def apply_nowtab_control_modern_ui(self, plugin, context, controls_row, control_container, *args, **kwargs):
125
+ return _apply_nowtab_control_modern_ui(plugin, context, controls_row, control_container, *args, **kwargs)
126
+
127
+ def update_nowtab_control_modern_state(self, plugin, is_playing):
128
+ return _update_nowtab_control_modern_state(plugin, is_playing)
129
+
130
+ def send_tab_track_download(self, plugin):
131
+ return _send_tab_track_download(plugin)
132
+
133
+ def send_tab_music_preview(self, plugin):
134
+ return _send_tab_music_preview(plugin)
135
+
136
+ def get_track_hub_style(self, plugin):
137
+ try:
138
+ return int(plugin.get_setting("track_hub_style", 0) or 0)
139
+ except Exception:
140
+ return 0
141
+
142
+ def render_track_hub_box_cell(self, plugin, cell, title_text, subtitle_text, cover_url=""):
143
+ return _render_track_hub_box_cell(plugin, cell, title_text, subtitle_text, cover_url)
144
+
145
+ def clear_track_hub_box_cell(self, plugin, cell):
146
+ return _clear_track_hub_box_cell(plugin, cell)
147
+
148
+ def inject_track_hub_source_slide(self, plugin, activity, items):
149
+ return _inject_track_hub_source_slide(plugin, activity, items)
150
+
151
+ def inject_nowtab_control_style_slide(self, plugin, activity, items):
152
+ return _inject_nowtab_control_style_slide(plugin, activity, items)
153
+
154
+ def inject_vinify_logo_position_radios(self, plugin, activity, items):
155
+ return _inject_vinify_logo_position_radios(plugin, activity, items)
156
+
157
+ def inject_vinify_cover_shape_slide(self, plugin, activity, items):
158
+ return _inject_vinify_cover_shape_slide(plugin, activity, items)
159
+
160
+ def inject_nowfy_pulse_style_slide(self, plugin, activity, items):
161
+ return _inject_nowfy_pulse_style_slide(plugin, activity, items)
162
+
163
+ def inject_custom_cover_selector(self, plugin, activity, items):
164
+ return _inject_custom_cover_selector(plugin, activity, items)
165
+
166
+ def on_custom_cover_select(self, plugin, value):
167
+ return _on_custom_cover_select(plugin, value)
168
+
169
+ def on_custom_cover_toggle(self, plugin, enabled_value):
170
+ return _on_custom_cover_toggle(plugin, enabled_value)
171
+
172
+ def get_core_setting(self, key, default=None):
173
+ return self._state.get(str(key), default, plugin=self._runtime_plugin)
174
+
175
+ def set_core_setting(self, key, value):
176
+ return self._state.set(str(key), value, plugin=self._runtime_plugin)
177
+
178
+ def is_theme_enabled(self, plugin, theme_key, default_value=False):
179
+ raw = str(self.get_core_setting("core_enabled_optional_themes", "") or "").strip()
180
+ return _is_theme_enabled(plugin, theme_key, default_value, core_enabled_optional_themes=raw)
181
+
182
+ def inject_core_theme_multiselect(self, plugin, activity, items):
183
+ return _inject_core_theme_multiselect(plugin, activity, items)
184
+
185
+ def inject_statsfm_profile_custom(self, plugin, activity, items):
186
+ return _inject_statsfm_profile_custom(plugin, activity, items)
187
+
188
+ def inject_services_cards_custom(self, plugin, activity, items):
189
+ return _inject_services_cards_custom(plugin, activity, items)
190
+
191
+ def inject_home_services_entry_custom(self, plugin, activity, items):
192
+ return _inject_home_services_entry_custom(plugin, activity, items)
193
+
194
+ def get_plugin_lang_index(self, plugin):
195
+ return _get_plugin_lang_index(plugin, key="plugin_lang", default=0)
196
+
197
+ def normalize_plugin_lang_index(self, idx):
198
+ return _normalize_plugin_lang_index(idx)
199
+
200
+ def get_plugin_lang_code(self, idx):
201
+ return _get_plugin_lang_code(idx)
202
+
203
+ def on_panel_drawer_toggle(self, plugin, enabled):
204
+ return _on_panel_drawer_toggle(plugin, enabled)
205
+
206
+ def on_panel_disable_logs_toggle(self, plugin, enabled, *args, **kwargs):
207
+ a0 = args[0] if len(args) >= 1 else None
208
+ a1 = args[1] if len(args) >= 2 else None
209
+ a2 = args[2] if len(args) >= 3 else None
210
+ a3 = args[3] if len(args) >= 4 else None
211
+ return _on_panel_disable_logs_toggle(plugin, enabled, a0, a1, a2, a3)
212
+
213
+ def on_lab_nowplaying_pill_toggle(self, plugin, enabled):
214
+ return _on_lab_nowplaying_pill_toggle(plugin, enabled)
215
+
216
+ def on_lab_send_haptic_toggle(self, plugin, enabled):
217
+ return _on_lab_send_haptic_toggle(plugin, enabled)
218
+
219
+ def clear_cache_data(self, plugin):
220
+ return _clear_cache_data(plugin)
221
+
222
+ def get_bio_feature_services(self, plugin):
223
+ return _get_bio_feature_services(plugin)
224
+
225
+ def build_track_hub_subfragment_items(self, plugin):
226
+ return _build_track_hub_subfragment_items(plugin)
227
+
228
+ def invoke_core_action(self, action_name):
229
+ plugin = self._runtime_plugin
230
+ if plugin is None:
231
+ return False
232
+ return _invoke_action(plugin, action_name)
233
+
234
+ def animate_opening_avatar(self, plugin, avatar_view, from_y_dp=20, duration_ms=620):
235
+ return _animate_opening_avatar(plugin, avatar_view, from_y_dp, duration_ms)
236
+
237
+ def inject_about_support_rich_header(self, plugin, activity, items):
238
+ return _inject_about_support_rich_header(plugin, activity, items)
239
+
240
+ def export_core_settings(self):
241
+ data = self._state.export()
242
+ plugin = self._runtime_plugin
243
+ if plugin is not None:
244
+ try:
245
+ data.update(_export_core_settings_from_plugin(plugin))
246
+ except Exception:
247
+ pass
248
+ return data
249
+
250
+ def import_core_settings(self, data):
251
+ applied = self._state.import_settings(data, plugin=self._runtime_plugin)
252
+ plugin = self._runtime_plugin
253
+ if plugin is not None:
254
+ try:
255
+ _import_core_settings_to_plugin(plugin, data)
256
+ except Exception:
257
+ pass
258
+ return applied
259
+
260
+
261
+ _API_SINGLETON = None
262
+
263
+
264
+ def get_nowfy_core_api():
265
+ global _API_SINGLETON
266
+ if _API_SINGLETON is None:
267
+ _API_SINGLETON = NowfyCoreAPI()
268
+ return _API_SINGLETON
@@ -0,0 +1,13 @@
1
+ def invoke_action(plugin, action_name):
2
+ """Invoke a runtime method on host plugin during staged migration."""
3
+ try:
4
+ name = str(action_name or "").strip()
5
+ if not name:
6
+ return False
7
+ fn = getattr(plugin, name, None)
8
+ if not callable(fn):
9
+ return False
10
+ fn()
11
+ return True
12
+ except Exception:
13
+ return False
@@ -0,0 +1,21 @@
1
+ def clear_cache_data(plugin):
2
+ try:
3
+ for name in (
4
+ "_image_cache",
5
+ "_cache_timestamps",
6
+ "_enhanced_image_cache",
7
+ "_base_theme_cache",
8
+ "_apple_cache",
9
+ "_youtube_thumbnail_cache",
10
+ "_soundcloud_thumbnail_cache",
11
+ "_valid_thumbnail_url_cache",
12
+ ):
13
+ try:
14
+ obj = getattr(plugin, name, None)
15
+ if isinstance(obj, dict):
16
+ obj.clear()
17
+ except Exception:
18
+ pass
19
+ return True
20
+ except Exception:
21
+ return False