Skip to content

Commit 36cc20b

Browse files
Add com.micropythonos.nostr
Initial commit, not ready for release.
1 parent 7fd86da commit 36cc20b

File tree

14 files changed

+814
-0
lines changed

14 files changed

+814
-0
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"name": "Nostr",
3+
"publisher": "MicroPythonOS",
4+
"short_description": "Nostr",
5+
"long_description": "Notest and Other Stuff Transmitted by Relays",
6+
"icon_url": "https://apps.micropythonos.com/apps/com.micropythonos.nostr/icons/com.micropythonos.nostr_0.1.0_64x64.png",
7+
"download_url": "https://apps.micropythonos.com/apps/com.micropythonos.nostr/mpks/com.micropythonos.nostr_0.1.0.mpk",
8+
"fullname": "com.micropythonos.nostr",
9+
"version": "0.1.0",
10+
"category": "communication",
11+
"activities": [
12+
{
13+
"entrypoint": "assets/nostr.py",
14+
"classname": "Nostr",
15+
"intent_filters": [
16+
{
17+
"action": "main",
18+
"category": "launcher"
19+
}
20+
]
21+
}
22+
]
23+
}
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import time
2+
import random
3+
import lvgl as lv
4+
import mpos.ui
5+
6+
7+
class Confetti:
8+
"""Manages confetti animation with physics simulation."""
9+
10+
def __init__(self, screen, icon_path, asset_path, duration=10000):
11+
"""
12+
Initialize the Confetti system.
13+
14+
Args:
15+
screen: The LVGL screen/display object
16+
icon_path: Path to icon assets (e.g., "M:apps/com.lightningpiggy.displaywallet/res/mipmap-mdpi/")
17+
asset_path: Path to confetti assets (e.g., "M:apps/com.lightningpiggy.displaywallet/res/drawable-mdpi/")
18+
max_confetti: Maximum number of confetti pieces to display
19+
"""
20+
self.screen = screen
21+
self.icon_path = icon_path
22+
self.asset_path = asset_path
23+
self.duration = duration
24+
self.max_confetti = 21
25+
26+
# Physics constants
27+
self.GRAVITY = 100 # pixels/sec²
28+
29+
# Screen dimensions
30+
self.screen_width = screen.get_display().get_horizontal_resolution()
31+
self.screen_height = screen.get_display().get_vertical_resolution()
32+
33+
# State
34+
self.is_running = False
35+
self.last_time = time.ticks_ms()
36+
self.confetti_pieces = []
37+
self.confetti_images = []
38+
self.used_img_indices = set()
39+
40+
# Spawn control
41+
self.spawn_timer = 0
42+
self.spawn_interval = 0.15 # seconds
43+
self.animation_start = 0
44+
45+
46+
# Pre-create LVGL image objects
47+
self._init_images()
48+
49+
def _init_images(self):
50+
"""Pre-create LVGL image objects for confetti."""
51+
iconimages = 2
52+
for _ in range(iconimages):
53+
img = lv.image(lv.layer_top())
54+
img.set_src(f"{self.icon_path}icon_64x64.png")
55+
img.add_flag(lv.obj.FLAG.HIDDEN)
56+
self.confetti_images.append(img)
57+
58+
for i in range(self.max_confetti - iconimages):
59+
img = lv.image(lv.layer_top())
60+
img.set_src(f"{self.asset_path}confetti{random.randint(0, 4)}.png")
61+
img.add_flag(lv.obj.FLAG.HIDDEN)
62+
self.confetti_images.append(img)
63+
64+
def start(self):
65+
"""Start the confetti animation."""
66+
if self.is_running:
67+
return
68+
69+
self.is_running = True
70+
self.last_time = time.ticks_ms()
71+
self._clear_confetti()
72+
73+
# Staggered spawn control
74+
self.spawn_timer = 0
75+
self.animation_start = time.ticks_ms() / 1000.0
76+
77+
# Initial burst
78+
for _ in range(10):
79+
self._spawn_one()
80+
81+
# Register update callback
82+
mpos.ui.task_handler.add_event_cb(self._update_frame, 1)
83+
84+
# Stop spawning after 15 seconds
85+
lv.timer_create(self.stop, self.duration, None).set_repeat_count(1)
86+
87+
def stop(self, timer=None):
88+
"""Stop the confetti animation."""
89+
self.is_running = False
90+
91+
def _clear_confetti(self):
92+
"""Clear all confetti pieces from the screen."""
93+
for img in self.confetti_images:
94+
img.add_flag(lv.obj.FLAG.HIDDEN)
95+
self.confetti_pieces = []
96+
self.used_img_indices.clear()
97+
98+
def _update_frame(self, a, b):
99+
"""Update frame for confetti animation. Called by task handler."""
100+
current_time = time.ticks_ms()
101+
delta_time = time.ticks_diff(current_time, self.last_time) / 1000.0
102+
self.last_time = current_time
103+
104+
# === STAGGERED SPAWNING ===
105+
if self.is_running:
106+
self.spawn_timer += delta_time
107+
if self.spawn_timer >= self.spawn_interval:
108+
self.spawn_timer = 0
109+
for _ in range(random.randint(1, 2)):
110+
if len(self.confetti_pieces) < self.max_confetti:
111+
self._spawn_one()
112+
113+
# === UPDATE ALL PIECES ===
114+
new_pieces = []
115+
for piece in self.confetti_pieces:
116+
# Physics
117+
piece['age'] += delta_time
118+
piece['x'] += piece['vx'] * delta_time
119+
piece['y'] += piece['vy'] * delta_time
120+
piece['vy'] += self.GRAVITY * delta_time
121+
piece['rotation'] += piece['spin'] * delta_time
122+
piece['scale'] = max(0.3, 1.0 - (piece['age'] / piece['lifetime']) * 0.7)
123+
124+
# Render
125+
img = self.confetti_images[piece['img_idx']]
126+
img.remove_flag(lv.obj.FLAG.HIDDEN)
127+
img.set_pos(int(piece['x']), int(piece['y']))
128+
img.set_rotation(int(piece['rotation'] * 10))
129+
orig = img.get_width()
130+
if orig >= 64:
131+
img.set_scale(int(256 * piece['scale'] / 1.5))
132+
elif orig < 32:
133+
img.set_scale(int(256 * piece['scale'] * 1.5))
134+
else:
135+
img.set_scale(int(256 * piece['scale']))
136+
137+
# Death check
138+
dead = (
139+
piece['x'] < -60 or piece['x'] > self.screen_width + 60 or
140+
piece['y'] > self.screen_height + 60 or
141+
piece['age'] > piece['lifetime']
142+
)
143+
144+
if dead:
145+
img.add_flag(lv.obj.FLAG.HIDDEN)
146+
self.used_img_indices.discard(piece['img_idx'])
147+
else:
148+
new_pieces.append(piece)
149+
150+
self.confetti_pieces = new_pieces
151+
152+
# Full stop when empty and paused
153+
if not self.confetti_pieces and not self.is_running:
154+
print("Confetti finished")
155+
mpos.ui.task_handler.remove_event_cb(self._update_frame)
156+
157+
def _spawn_one(self):
158+
"""Spawn a single confetti piece."""
159+
if not self.is_running:
160+
return
161+
162+
# Find a free image slot
163+
for idx, img in enumerate(self.confetti_images):
164+
if img.has_flag(lv.obj.FLAG.HIDDEN) and idx not in self.used_img_indices:
165+
break
166+
else:
167+
return # No free slot
168+
169+
piece = {
170+
'img_idx': idx,
171+
'x': random.uniform(-50, self.screen_width + 50),
172+
'y': random.uniform(50, 100), # Start above screen
173+
'vx': random.uniform(-80, 80),
174+
'vy': random.uniform(-150, 0),
175+
'spin': random.uniform(-500, 500),
176+
'age': 0.0,
177+
'lifetime': random.uniform(5.0, 10.0), # Long enough to fill 10s
178+
'rotation': random.uniform(0, 360),
179+
'scale': 1.0
180+
}
181+
self.confetti_pieces.append(piece)
182+
self.used_img_indices.add(idx)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import lvgl as lv
2+
3+
from mpos import Activity, min_resolution
4+
5+
class FullscreenQR(Activity):
6+
# No __init__() so super.__init__() will be called automatically
7+
8+
def onCreate(self):
9+
receive_qr_data = self.getIntent().extras.get("receive_qr_data")
10+
qr_screen = lv.obj()
11+
qr_screen.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF)
12+
qr_screen.set_scroll_dir(lv.DIR.NONE)
13+
qr_screen.add_event_cb(lambda e: self.finish(),lv.EVENT.CLICKED,None)
14+
big_receive_qr = lv.qrcode(qr_screen)
15+
big_receive_qr.set_size(min_resolution())
16+
big_receive_qr.set_dark_color(lv.color_black())
17+
big_receive_qr.set_light_color(lv.color_white())
18+
big_receive_qr.center()
19+
big_receive_qr.set_style_border_color(lv.color_white(), 0)
20+
big_receive_qr.set_style_border_width(0, 0);
21+
big_receive_qr.update(receive_qr_data, len(receive_qr_data))
22+
self.setContentView(qr_screen)

0 commit comments

Comments
 (0)