android-widgets 0.1.6__tar.gz → 0.1.8__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.
Files changed (47) hide show
  1. android_widgets-0.1.8/PKG-INFO +239 -0
  2. android_widgets-0.1.8/README.md +217 -0
  3. {android_widgets-0.1.6 → android_widgets-0.1.8}/android_widgets/__init__.py +15 -1
  4. android_widgets-0.1.8/android_widgets/__main__.py +4 -0
  5. android_widgets-0.1.8/android_widgets/generate/__init__.py +32 -0
  6. android_widgets-0.1.8/android_widgets/generate/bindings.py +58 -0
  7. android_widgets-0.1.8/android_widgets/generate/cli.py +102 -0
  8. android_widgets-0.1.8/android_widgets/generate/drawables.py +25 -0
  9. android_widgets-0.1.8/android_widgets/generate/generator.py +103 -0
  10. android_widgets-0.1.8/android_widgets/generate/java.py +164 -0
  11. android_widgets-0.1.8/android_widgets/generate/layout.py +215 -0
  12. android_widgets-0.1.8/android_widgets/generate/manifest.py +161 -0
  13. android_widgets-0.1.8/android_widgets/generate/nodes.py +203 -0
  14. android_widgets-0.1.8/android_widgets/generate/provider.py +56 -0
  15. android_widgets-0.1.8/android_widgets/generate/values.py +19 -0
  16. android_widgets-0.1.8/android_widgets/generate/xml.py +72 -0
  17. {android_widgets-0.1.6 → android_widgets-0.1.8}/android_widgets/maker.py +4 -0
  18. android_widgets-0.1.8/android_widgets/runtime.py +139 -0
  19. android_widgets-0.1.8/android_widgets/templates/p4a_hook.py +33 -0
  20. android_widgets-0.1.8/android_widgets.egg-info/PKG-INFO +239 -0
  21. android_widgets-0.1.8/android_widgets.egg-info/SOURCES.txt +41 -0
  22. android_widgets-0.1.8/android_widgets.egg-info/entry_points.txt +2 -0
  23. android_widgets-0.1.8/android_widgets.egg-info/requires.txt +4 -0
  24. {android_widgets-0.1.6 → android_widgets-0.1.8}/android_widgets.egg-info/top_level.txt +1 -2
  25. {android_widgets-0.1.6 → android_widgets-0.1.8}/pyproject.toml +12 -3
  26. android_widgets-0.1.8/tests/sample.py +41 -0
  27. android_widgets-0.1.8/tests/test_cli.py +66 -0
  28. android_widgets-0.1.8/tests/test_generator.py +58 -0
  29. android_widgets-0.1.8/tests/test_generators.py +111 -0
  30. android_widgets-0.1.8/tests/test_java.py +125 -0
  31. android_widgets-0.1.8/tests/test_maker.py +51 -0
  32. android_widgets-0.1.8/tests/test_manifest.py +91 -0
  33. android_widgets-0.1.8/tests/test_naming.py +32 -0
  34. android_widgets-0.1.8/tests/test_nodes.py +45 -0
  35. android_widgets-0.1.8/tests/test_runtime.py +49 -0
  36. android_widgets-0.1.6/PKG-INFO +0 -154
  37. android_widgets-0.1.6/README.md +0 -136
  38. android_widgets-0.1.6/android_widgets.egg-info/PKG-INFO +0 -154
  39. android_widgets-0.1.6/android_widgets.egg-info/SOURCES.txt +0 -14
  40. android_widgets-0.1.6/venv/bin/activate_this.py +0 -36
  41. {android_widgets-0.1.6 → android_widgets-0.1.8}/android_widgets/aw_logging.py +0 -0
  42. {android_widgets-0.1.6 → android_widgets-0.1.8}/android_widgets/config.py +0 -0
  43. {android_widgets-0.1.6 → android_widgets-0.1.8}/android_widgets/facade.py +0 -0
  44. {android_widgets-0.1.6 → android_widgets-0.1.8}/android_widgets/java_classes.py +0 -0
  45. {android_widgets-0.1.6 → android_widgets-0.1.8}/android_widgets/tools.py +0 -0
  46. {android_widgets-0.1.6 → android_widgets-0.1.8}/android_widgets.egg-info/dependency_links.txt +0 -0
  47. {android_widgets-0.1.6 → android_widgets-0.1.8}/setup.cfg +0 -0
@@ -0,0 +1,239 @@
1
+ Metadata-Version: 2.4
2
+ Name: android-widgets
3
+ Version: 0.1.8
4
+ Summary: This would be used to auto gen XMLs for Android Home Screen widgets.
5
+ Author-email: Fabian <fector101@yahoo.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Fector101/kivy-androidwidgets
8
+ Project-URL: Documentation, https://github.com/Fector101/kivy-androidwidgets
9
+ Project-URL: Source, https://github.com/Fector101/kivy-androidwidgets
10
+ Project-URL: Tracker, https://github.com/Fector101/kivy-androidwidgets/issues
11
+ Keywords: kivy,android,widgets,hello-world,python-package
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Operating System :: Android
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ Requires-Dist: kivy>=2.0.0
20
+ Provides-Extra: test
21
+ Requires-Dist: pytest>=7; extra == "test"
22
+
23
+ Kivy-androidwidgets
24
+ ---
25
+ Generate Android home-screen widgets for Kivy / Buildozer apps from a pure-Python DSL — **no XML, no Java**. Widget definitions are compiled by a CLI pre-build step into `res/`, `src/` and a p4a hook that Buildozer compiles straight into the APK.
26
+
27
+ Complete sample: [working app](https://github.com/Fector101/wallpaper-carousel)
28
+
29
+ Quickstart
30
+ ---
31
+ ### 1. Install
32
+ ```bash
33
+ pip install kivy-androidwidgets
34
+ ```
35
+
36
+ ### 2. Describe your widget in Python
37
+
38
+ `widgets.py` (in your project root):
39
+
40
+ ```python
41
+ from android_widgets import HomeWidget, AWColumn, AWRow, AWText, AWImage, AWButton, AWProgressBar, Color
42
+
43
+ clock = HomeWidget(
44
+ name="Clock",
45
+ description="Shows the current time",
46
+ layout=AWColumn(
47
+ spacing=6,
48
+ children=[
49
+ AWText("12:00", id="time", size=28, bold=True),
50
+ AWText("Mon 4 Aug", id="date", size=12, color=Color("#888888")),
51
+ ],
52
+ ),
53
+ min_width_dp=110, # 2 cells
54
+ min_height_dp=110,
55
+ resize_mode="horizontal|vertical",
56
+ update_period_ms=1800000, # Android minimum: 30 minutes
57
+ tap_to_open=True, # whole widget opens the app on tap
58
+ background=Color("#FFFFFF"),
59
+ radius=12,
60
+ )
61
+ ```
62
+
63
+ ### 3. Generate the Android artifacts
64
+
65
+ ```bash
66
+ python -m android_widgets init # creates p4a/hook.py (one-time)
67
+ python -m android_widgets generate --spec buildozer.spec --module widgets.py
68
+ ```
69
+
70
+ This writes (gitignore them):
71
+ - `res/layout/<name>.xml`, `res/layout/<name>_preview.xml`
72
+ - `res/xml/<name>_provider.xml` — the `appwidget-provider` metadata
73
+ - `res/drawable/widget_bg_<name>.xml` — rounded background shapes
74
+ - `src/<package>/<ClassName>.java` — one `AppWidgetProvider` per widget
75
+ - `res/values/widgets.xml`, `p4a/widget_receivers.json`
76
+
77
+ `generate` reads `package.domain` + `package.name` from `buildozer.spec`. Use `--dry-run` to preview without writing.
78
+
79
+ ### 4. Wire it into `buildozer.spec`
80
+
81
+ ```ini
82
+ android.add_resources = res
83
+ android.add_src = src
84
+ p4a.hook = p4a/hook.py
85
+ ```
86
+
87
+ ### 5. Build
88
+ ```bash
89
+ buildozer android debug
90
+ ```
91
+
92
+ The generated `p4a/hook.py` (`after_apk_build`) injects the `<receiver>` entries into the dist's `AndroidManifest.xml` before aapt compiles it, so no manual manifest editing.
93
+
94
+ DSL Reference
95
+ ---
96
+ - `HomeWidget` — `name`, `description`, `layout`, `min_width_dp`, `min_height_dp`, `target_cells`, `max_cells`, `resize_mode`, `widget_category`, `update_period_ms`, `preview_image`, `preview_layout`, `tap_to_open`, `background` (`Color`), `radius`, `padding`
97
+ - `AWColumn`, `AWRow` — `spacing`, `children`
98
+ - `AWFrame` — single child, ideal for cards
99
+ - `AWText` — `text`, `size`, `color`, `bold`, `italic`, `align`, `max_lines`, `id`
100
+ - `AWImage` — `resource`, `path`, `scale_type`, `content_description`, `id`
101
+ - `AWButton` — `text`, `id`
102
+ - `AWProgressBar` — `style`, `max`, `id`
103
+
104
+ Layout children with an `id` become runtime handles: the generated Java loads images from `context.getFilesDir()/app/assets/...` for `AWImage(path=...)` at add-time. `tap_to_open=True` wires `@+id/widget_root` to a `PendingIntent` opening the app (with the API-23 `FLAG_IMMUTABLE` guard).
105
+
106
+ Runtime updates
107
+ ---
108
+ Add widgets to the home screen from Python at runtime (see `using-python-to-update-widget.md` for the manual version). `update_period_ms` cannot go below 1 800 000 ms on Android.
109
+
110
+ Widget & feature recipes: [examples.md](examples.md) (clock, battery meter, photo, button, card layouts, multiple widgets per app, and the runtime update API).
111
+
112
+ Tests
113
+ ---
114
+ ```bash
115
+ python3 -m unittest discover -s tests -v
116
+ ```
117
+
118
+ Manual fallback
119
+ ---
120
+ Prefer the DSL above. If you need full control, the original manual method still works: hand-write the layout/provider XML, the Java `AppWidgetProvider` and the manifest hook (see below).
121
+
122
+ ### Step 1: Design the layout
123
+ Store in `res/layout/simple_widget.xml`:
124
+ ```xml
125
+ <?xml version="1.0" encoding="utf-8"?>
126
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
127
+ android:orientation="vertical"
128
+ android:padding="10dp"
129
+ android:background="#FFFFFF"
130
+ android:gravity="center"
131
+ android:layout_width="wrap_content"
132
+ android:layout_height="wrap_content">
133
+
134
+ <TextView
135
+ android:id="@+id/widget_text"
136
+ android:text="Loading..."
137
+ android:textSize="18sp"
138
+ android:textColor="#000"
139
+ android:layout_width="wrap_content"
140
+ android:layout_height="wrap_content"/>
141
+
142
+ </LinearLayout>
143
+ ```
144
+ ### Step 2: Create the widget info XML
145
+ Path: `res/xml/widgetproviderinfo.xml`
146
+ ```xml
147
+ <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
148
+ android:minWidth="120dp"
149
+ android:minHeight="60dp"
150
+ android:updatePeriodMillis="1800000"
151
+ android:initialLayout="@layout/simple_widget"
152
+ android:previewImage="@drawable/ic_launcher_foreground"
153
+ android:resizeMode="horizontal|vertical"
154
+ android:widgetCategory="home_screen">
155
+ </appwidget-provider>
156
+ ```
157
+ Create the preview image at `res/drawable/ic_launcher_foreground.png`.
158
+
159
+ ### Step 3: Create an `AppWidgetProvider`
160
+ Path: `src/SimpleWidget.java`
161
+ ```java
162
+ package org.wally.waller; // Change here from buildozer.spec package.domain+package.name
163
+
164
+ import android.appwidget.AppWidgetManager;
165
+ import android.appwidget.AppWidgetProvider;
166
+ import android.content.Context;
167
+ import android.widget.RemoteViews;
168
+
169
+ import org.wally.waller.R; // Change here from buildozer.spec package.domain+package.name
170
+ import android.app.PendingIntent;
171
+ import android.content.Intent;
172
+
173
+ public class SimpleWidget extends AppWidgetProvider {
174
+
175
+ @Override
176
+ public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
177
+ for (int appWidgetId : appWidgetIds) {
178
+
179
+ RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.simple_widget);
180
+
181
+ // Example: Set text
182
+ views.setTextViewText(R.id.widget_text, "Hello Widget!");
183
+
184
+ // Update widget
185
+ appWidgetManager.updateAppWidget(appWidgetId, views);
186
+ }
187
+ }
188
+ }
189
+ ```
190
+
191
+ ### Step 4: Inject the receiver with a p4a hook
192
+ Path: `p4a/hook.py`
193
+ ```py
194
+ from pathlib import Path
195
+ from pythonforandroid.toolchain import ToolchainCL
196
+
197
+
198
+ def after_apk_build(toolchain: ToolchainCL):
199
+ manifest_file = Path(toolchain._dist.dist_dir) / "src" / "main" / "AndroidManifest.xml"
200
+ text = manifest_file.read_text(encoding="utf-8")
201
+
202
+ package = "org.wally.waller"
203
+ receiver_xml = f'''
204
+ <receiver android:name="{package}.SimpleWidget"
205
+ android:enabled="true"
206
+ android:exported="false">
207
+ <intent-filter>
208
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
209
+ </intent-filter>
210
+ <meta-data android:name="android.appwidget.provider"
211
+ android:resource="@xml/widgetproviderinfo" />
212
+ </receiver>
213
+ '''
214
+
215
+ if receiver_xml.strip() not in text:
216
+ if "</application>" in text:
217
+ text = text.replace("</application>", f"{receiver_xml}\n</application>")
218
+ print("Receiver added")
219
+ else:
220
+ print("Could not find </application> to insert receiver")
221
+ else:
222
+ print("Receiver already exists in manifest")
223
+
224
+ manifest_file.write_text(text, encoding="utf-8")
225
+ print("Successfully_101: Manifest update completed successfully!")
226
+ ```
227
+
228
+ ### Step 5: Wire into `buildozer.spec`
229
+ ```ini
230
+ android.add_resources = res
231
+ android.add_src = src
232
+ p4a.hook = p4a/hook.py
233
+ ```
234
+
235
+ Sample Image:
236
+
237
+ ![Rounded corners widget](https://raw.githubusercontent.com/Fector101/kivy-androidwidgets/main/imgs/not-rounded.jpg)
238
+
239
+ ### For more widget customisation check: [How to Customise.md](how-to-customise.md)
@@ -0,0 +1,217 @@
1
+ Kivy-androidwidgets
2
+ ---
3
+ Generate Android home-screen widgets for Kivy / Buildozer apps from a pure-Python DSL — **no XML, no Java**. Widget definitions are compiled by a CLI pre-build step into `res/`, `src/` and a p4a hook that Buildozer compiles straight into the APK.
4
+
5
+ Complete sample: [working app](https://github.com/Fector101/wallpaper-carousel)
6
+
7
+ Quickstart
8
+ ---
9
+ ### 1. Install
10
+ ```bash
11
+ pip install kivy-androidwidgets
12
+ ```
13
+
14
+ ### 2. Describe your widget in Python
15
+
16
+ `widgets.py` (in your project root):
17
+
18
+ ```python
19
+ from android_widgets import HomeWidget, AWColumn, AWRow, AWText, AWImage, AWButton, AWProgressBar, Color
20
+
21
+ clock = HomeWidget(
22
+ name="Clock",
23
+ description="Shows the current time",
24
+ layout=AWColumn(
25
+ spacing=6,
26
+ children=[
27
+ AWText("12:00", id="time", size=28, bold=True),
28
+ AWText("Mon 4 Aug", id="date", size=12, color=Color("#888888")),
29
+ ],
30
+ ),
31
+ min_width_dp=110, # 2 cells
32
+ min_height_dp=110,
33
+ resize_mode="horizontal|vertical",
34
+ update_period_ms=1800000, # Android minimum: 30 minutes
35
+ tap_to_open=True, # whole widget opens the app on tap
36
+ background=Color("#FFFFFF"),
37
+ radius=12,
38
+ )
39
+ ```
40
+
41
+ ### 3. Generate the Android artifacts
42
+
43
+ ```bash
44
+ python -m android_widgets init # creates p4a/hook.py (one-time)
45
+ python -m android_widgets generate --spec buildozer.spec --module widgets.py
46
+ ```
47
+
48
+ This writes (gitignore them):
49
+ - `res/layout/<name>.xml`, `res/layout/<name>_preview.xml`
50
+ - `res/xml/<name>_provider.xml` — the `appwidget-provider` metadata
51
+ - `res/drawable/widget_bg_<name>.xml` — rounded background shapes
52
+ - `src/<package>/<ClassName>.java` — one `AppWidgetProvider` per widget
53
+ - `res/values/widgets.xml`, `p4a/widget_receivers.json`
54
+
55
+ `generate` reads `package.domain` + `package.name` from `buildozer.spec`. Use `--dry-run` to preview without writing.
56
+
57
+ ### 4. Wire it into `buildozer.spec`
58
+
59
+ ```ini
60
+ android.add_resources = res
61
+ android.add_src = src
62
+ p4a.hook = p4a/hook.py
63
+ ```
64
+
65
+ ### 5. Build
66
+ ```bash
67
+ buildozer android debug
68
+ ```
69
+
70
+ The generated `p4a/hook.py` (`after_apk_build`) injects the `<receiver>` entries into the dist's `AndroidManifest.xml` before aapt compiles it, so no manual manifest editing.
71
+
72
+ DSL Reference
73
+ ---
74
+ - `HomeWidget` — `name`, `description`, `layout`, `min_width_dp`, `min_height_dp`, `target_cells`, `max_cells`, `resize_mode`, `widget_category`, `update_period_ms`, `preview_image`, `preview_layout`, `tap_to_open`, `background` (`Color`), `radius`, `padding`
75
+ - `AWColumn`, `AWRow` — `spacing`, `children`
76
+ - `AWFrame` — single child, ideal for cards
77
+ - `AWText` — `text`, `size`, `color`, `bold`, `italic`, `align`, `max_lines`, `id`
78
+ - `AWImage` — `resource`, `path`, `scale_type`, `content_description`, `id`
79
+ - `AWButton` — `text`, `id`
80
+ - `AWProgressBar` — `style`, `max`, `id`
81
+
82
+ Layout children with an `id` become runtime handles: the generated Java loads images from `context.getFilesDir()/app/assets/...` for `AWImage(path=...)` at add-time. `tap_to_open=True` wires `@+id/widget_root` to a `PendingIntent` opening the app (with the API-23 `FLAG_IMMUTABLE` guard).
83
+
84
+ Runtime updates
85
+ ---
86
+ Add widgets to the home screen from Python at runtime (see `using-python-to-update-widget.md` for the manual version). `update_period_ms` cannot go below 1 800 000 ms on Android.
87
+
88
+ Widget & feature recipes: [examples.md](examples.md) (clock, battery meter, photo, button, card layouts, multiple widgets per app, and the runtime update API).
89
+
90
+ Tests
91
+ ---
92
+ ```bash
93
+ python3 -m unittest discover -s tests -v
94
+ ```
95
+
96
+ Manual fallback
97
+ ---
98
+ Prefer the DSL above. If you need full control, the original manual method still works: hand-write the layout/provider XML, the Java `AppWidgetProvider` and the manifest hook (see below).
99
+
100
+ ### Step 1: Design the layout
101
+ Store in `res/layout/simple_widget.xml`:
102
+ ```xml
103
+ <?xml version="1.0" encoding="utf-8"?>
104
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
105
+ android:orientation="vertical"
106
+ android:padding="10dp"
107
+ android:background="#FFFFFF"
108
+ android:gravity="center"
109
+ android:layout_width="wrap_content"
110
+ android:layout_height="wrap_content">
111
+
112
+ <TextView
113
+ android:id="@+id/widget_text"
114
+ android:text="Loading..."
115
+ android:textSize="18sp"
116
+ android:textColor="#000"
117
+ android:layout_width="wrap_content"
118
+ android:layout_height="wrap_content"/>
119
+
120
+ </LinearLayout>
121
+ ```
122
+ ### Step 2: Create the widget info XML
123
+ Path: `res/xml/widgetproviderinfo.xml`
124
+ ```xml
125
+ <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
126
+ android:minWidth="120dp"
127
+ android:minHeight="60dp"
128
+ android:updatePeriodMillis="1800000"
129
+ android:initialLayout="@layout/simple_widget"
130
+ android:previewImage="@drawable/ic_launcher_foreground"
131
+ android:resizeMode="horizontal|vertical"
132
+ android:widgetCategory="home_screen">
133
+ </appwidget-provider>
134
+ ```
135
+ Create the preview image at `res/drawable/ic_launcher_foreground.png`.
136
+
137
+ ### Step 3: Create an `AppWidgetProvider`
138
+ Path: `src/SimpleWidget.java`
139
+ ```java
140
+ package org.wally.waller; // Change here from buildozer.spec package.domain+package.name
141
+
142
+ import android.appwidget.AppWidgetManager;
143
+ import android.appwidget.AppWidgetProvider;
144
+ import android.content.Context;
145
+ import android.widget.RemoteViews;
146
+
147
+ import org.wally.waller.R; // Change here from buildozer.spec package.domain+package.name
148
+ import android.app.PendingIntent;
149
+ import android.content.Intent;
150
+
151
+ public class SimpleWidget extends AppWidgetProvider {
152
+
153
+ @Override
154
+ public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
155
+ for (int appWidgetId : appWidgetIds) {
156
+
157
+ RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.simple_widget);
158
+
159
+ // Example: Set text
160
+ views.setTextViewText(R.id.widget_text, "Hello Widget!");
161
+
162
+ // Update widget
163
+ appWidgetManager.updateAppWidget(appWidgetId, views);
164
+ }
165
+ }
166
+ }
167
+ ```
168
+
169
+ ### Step 4: Inject the receiver with a p4a hook
170
+ Path: `p4a/hook.py`
171
+ ```py
172
+ from pathlib import Path
173
+ from pythonforandroid.toolchain import ToolchainCL
174
+
175
+
176
+ def after_apk_build(toolchain: ToolchainCL):
177
+ manifest_file = Path(toolchain._dist.dist_dir) / "src" / "main" / "AndroidManifest.xml"
178
+ text = manifest_file.read_text(encoding="utf-8")
179
+
180
+ package = "org.wally.waller"
181
+ receiver_xml = f'''
182
+ <receiver android:name="{package}.SimpleWidget"
183
+ android:enabled="true"
184
+ android:exported="false">
185
+ <intent-filter>
186
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
187
+ </intent-filter>
188
+ <meta-data android:name="android.appwidget.provider"
189
+ android:resource="@xml/widgetproviderinfo" />
190
+ </receiver>
191
+ '''
192
+
193
+ if receiver_xml.strip() not in text:
194
+ if "</application>" in text:
195
+ text = text.replace("</application>", f"{receiver_xml}\n</application>")
196
+ print("Receiver added")
197
+ else:
198
+ print("Could not find </application> to insert receiver")
199
+ else:
200
+ print("Receiver already exists in manifest")
201
+
202
+ manifest_file.write_text(text, encoding="utf-8")
203
+ print("Successfully_101: Manifest update completed successfully!")
204
+ ```
205
+
206
+ ### Step 5: Wire into `buildozer.spec`
207
+ ```ini
208
+ android.add_resources = res
209
+ android.add_src = src
210
+ p4a.hook = p4a/hook.py
211
+ ```
212
+
213
+ Sample Image:
214
+
215
+ ![Rounded corners widget](https://raw.githubusercontent.com/Fector101/kivy-androidwidgets/main/imgs/not-rounded.jpg)
216
+
217
+ ### For more widget customisation check: [How to Customise.md](how-to-customise.md)
@@ -12,7 +12,7 @@ from .config import get_python_activity_context
12
12
  try:
13
13
  from .java_classes import *
14
14
  except Exception as e:
15
- print("[android_widgets] Import Java classes Error:", e)
15
+ logger.debug(f"Import Java classes Error: {e}")
16
16
  from .facade import *
17
17
 
18
18
  RemoteViews_ = RemoteViews
@@ -20,6 +20,20 @@ except Exception as e:
20
20
 
21
21
  from .tools import SpecFile
22
22
 
23
+ from .generate.nodes import (
24
+ Color,
25
+ HomeWidget,
26
+ AWButton,
27
+ AWColumn,
28
+ AWFrame,
29
+ AWImage,
30
+ AWProgressBar,
31
+ AWRow,
32
+ AWText,
33
+ )
34
+
35
+ from .runtime import get_widget_data, save_widget_data, update_widget
36
+
23
37
  if is_platform_android():
24
38
  enable_logging()
25
39
 
@@ -0,0 +1,4 @@
1
+ from .generate.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ raise SystemExit(main())
@@ -0,0 +1,32 @@
1
+ """Build-time generators: turn the Python DSL into Android res/src artifacts."""
2
+
3
+ from .nodes import (
4
+ Color,
5
+ HomeWidget,
6
+ AWButton,
7
+ AWColumn,
8
+ AWFrame,
9
+ AWImage,
10
+ AWProgressBar,
11
+ AWRow,
12
+ AWText,
13
+ )
14
+ from .generator import collect_widgets, generate
15
+ from .manifest import inject_receivers, inject_widget_receivers, receivers_from_widgets
16
+
17
+ __all__ = [
18
+ "Color",
19
+ "HomeWidget",
20
+ "AWButton",
21
+ "AWColumn",
22
+ "AWFrame",
23
+ "AWImage",
24
+ "AWProgressBar",
25
+ "AWRow",
26
+ "AWText",
27
+ "collect_widgets",
28
+ "generate",
29
+ "inject_receivers",
30
+ "inject_widget_receivers",
31
+ "receivers_from_widgets",
32
+ ]
@@ -0,0 +1,58 @@
1
+ """Describe which layout nodes are runtime-updatable and their data keys.
2
+
3
+ Both the generated Java (reads the keys in onUpdate) and the Python runtime
4
+ (writes the keys before broadcasting an update) must agree on the key format,
5
+ so this module is shared and kept free of Android dependencies.
6
+ """
7
+
8
+ from dataclasses import dataclass
9
+ from typing import List, Optional
10
+
11
+ from . import nodes
12
+ from .xml import to_snake
13
+
14
+ PREFERENCES_NAME = "android_widgets"
15
+
16
+ TEXT = "text"
17
+ BUTTON = "button"
18
+ IMAGE = "image"
19
+ PROGRESS = "progress"
20
+
21
+
22
+ @dataclass
23
+ class DataBinding:
24
+ view_id: str
25
+ kind: str
26
+ default: str
27
+ max: Optional[int] = None
28
+
29
+ @property
30
+ def java_setter(self) -> str:
31
+ return {
32
+ TEXT: "setTextViewText",
33
+ BUTTON: "setTextViewText",
34
+ IMAGE: "setImageViewBitmap",
35
+ PROGRESS: "setProgressBar",
36
+ }[self.kind]
37
+
38
+
39
+ def data_bindings(home_widget: nodes.HomeWidget) -> List[DataBinding]:
40
+ """Collect runtime-updatable nodes (those with an id) from the tree."""
41
+ binds: List[DataBinding] = []
42
+ for node in home_widget.layout.iter_nodes():
43
+ if not node.id:
44
+ continue
45
+ if isinstance(node, nodes.AWText):
46
+ binds.append(DataBinding(node.id, TEXT, node.text))
47
+ elif isinstance(node, nodes.AWButton):
48
+ binds.append(DataBinding(node.id, BUTTON, node.text))
49
+ elif isinstance(node, nodes.AWProgressBar):
50
+ binds.append(DataBinding(node.id, PROGRESS, "0", node.max))
51
+ elif isinstance(node, nodes.AWImage) and node.path:
52
+ binds.append(DataBinding(node.id, IMAGE, node.path))
53
+ return binds
54
+
55
+
56
+ def data_key(widget_name: str, view_id: str) -> str:
57
+ """SharedPreferences key for a runtime-updatable field."""
58
+ return f"{to_snake(widget_name)}.{view_id}"
@@ -0,0 +1,102 @@
1
+ """Command line interface: `python -m android_widgets generate`."""
2
+
3
+ import argparse
4
+ import os
5
+ import sys
6
+
7
+ from ..tools import SpecFile
8
+ from .generator import collect_widgets, generate, load_widget_module
9
+
10
+
11
+ def _package_from_spec(spec_path: str) -> str:
12
+ spec = SpecFile(spec_path)
13
+ domain = spec.get("app", "package.domain")
14
+ name = spec.get("app", "package.name")
15
+ if not domain or not name:
16
+ raise SystemExit(
17
+ f"Error: buildozer.spec at {spec_path} is missing "
18
+ f"package.domain or package.name"
19
+ )
20
+ return f"{domain}.{name}"
21
+
22
+
23
+ def cmd_generate(args) -> int:
24
+ if not os.path.exists(args.spec):
25
+ raise SystemExit(f"Error: buildozer.spec not found at {args.spec}")
26
+ package = _package_from_spec(args.spec)
27
+
28
+ module_path = os.path.abspath(args.module)
29
+ if not os.path.exists(module_path):
30
+ raise SystemExit(f"Error: widget module not found at {module_path}")
31
+ module = load_widget_module(module_path, base_dir=args.out)
32
+ widgets = collect_widgets(module)
33
+ if not widgets:
34
+ print(f"Warning: no HomeWidget definitions found in {module_path}")
35
+
36
+ written = generate(widgets, package=package, out_dir=args.out, dry_run=args.dry_run)
37
+
38
+ mode = "Would generate" if args.dry_run else "Generated"
39
+ print(f"{mode} {len(written)} file(s) for {len(widgets)} widget(s):")
40
+ for path in written:
41
+ print(f" {path}")
42
+ if not args.dry_run and widgets:
43
+ print("\nNext: add these to buildozer.spec and run 'buildozer android debug':")
44
+ print(" android.add_resources = res")
45
+ print(" android.add_src = src")
46
+ print(" p4a.hook = p4a/hook.py")
47
+ return 0
48
+
49
+
50
+ def cmd_init(args) -> int:
51
+ import shutil
52
+
53
+ template = os.path.join(
54
+ os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
55
+ "templates",
56
+ "p4a_hook.py",
57
+ )
58
+ out_dir = os.path.join(args.out, "p4a")
59
+ os.makedirs(out_dir, exist_ok=True)
60
+ dest = os.path.join(out_dir, "hook.py")
61
+ if os.path.exists(dest) and not args.force:
62
+ print(f"Info: {dest} already exists (use --force to overwrite)")
63
+ return 0
64
+ shutil.copy(template, dest)
65
+ print(f"Created {dest}")
66
+ return 0
67
+
68
+
69
+ def build_parser() -> argparse.ArgumentParser:
70
+ parser = argparse.ArgumentParser(
71
+ prog="android-widgets",
72
+ description="Generate Android home screen widget resources for Kivy apps.",
73
+ )
74
+ sub = parser.add_subparsers(dest="command", required=True)
75
+
76
+ gen = sub.add_parser("generate", help="Generate res/ + src/ + p4a hook data")
77
+ gen.add_argument("--spec", default="buildozer.spec", help="path to buildozer.spec")
78
+ gen.add_argument(
79
+ "--module",
80
+ default="widgets.py",
81
+ help="Python module containing the HomeWidget definitions",
82
+ )
83
+ gen.add_argument("--out", default=".", help="project directory to write into")
84
+ gen.add_argument("--dry-run", action="store_true", help="print files without writing")
85
+ gen.set_defaults(func=cmd_generate)
86
+
87
+ init = sub.add_parser("init", help="Scaffold p4a/hook.py into the project")
88
+ init.add_argument("--out", default=".", help="project directory")
89
+ init.add_argument("--force", action="store_true", help="overwrite existing hook.py")
90
+ init.set_defaults(func=cmd_init)
91
+
92
+ return parser
93
+
94
+
95
+ def main(argv=None) -> int:
96
+ parser = build_parser()
97
+ args = parser.parse_args(argv)
98
+ return args.func(args)
99
+
100
+
101
+ if __name__ == "__main__":
102
+ sys.exit(main())