blinkui 0.1.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.
Files changed (36) hide show
  1. blinkui-0.1.0/PKG-INFO +7 -0
  2. blinkui-0.1.0/README.md +191 -0
  3. blinkui-0.1.0/blinkui/__init__.py +7 -0
  4. blinkui-0.1.0/blinkui/app.py +30 -0
  5. blinkui-0.1.0/blinkui/components/__init__.py +24 -0
  6. blinkui-0.1.0/blinkui/components/base.py +159 -0
  7. blinkui-0.1.0/blinkui/components/button.py +44 -0
  8. blinkui-0.1.0/blinkui/components/card.py +132 -0
  9. blinkui-0.1.0/blinkui/components/image.py +65 -0
  10. blinkui-0.1.0/blinkui/components/input.py +143 -0
  11. blinkui-0.1.0/blinkui/components/layout.py +93 -0
  12. blinkui-0.1.0/blinkui/components/navigation.py +135 -0
  13. blinkui-0.1.0/blinkui/components/text.py +66 -0
  14. blinkui-0.1.0/blinkui/http/__init__.py +17 -0
  15. blinkui-0.1.0/blinkui/http/client.py +269 -0
  16. blinkui-0.1.0/blinkui/http/response.py +54 -0
  17. blinkui-0.1.0/blinkui/router.py +117 -0
  18. blinkui-0.1.0/blinkui/screen.py +67 -0
  19. blinkui-0.1.0/blinkui/state.py +44 -0
  20. blinkui-0.1.0/blinkui/storage/__init__.py +3 -0
  21. blinkui-0.1.0/blinkui/storage/async_storage.py +264 -0
  22. blinkui-0.1.0/blinkui/theme.py +170 -0
  23. blinkui-0.1.0/blinkui.egg-info/PKG-INFO +7 -0
  24. blinkui-0.1.0/blinkui.egg-info/SOURCES.txt +34 -0
  25. blinkui-0.1.0/blinkui.egg-info/dependency_links.txt +1 -0
  26. blinkui-0.1.0/blinkui.egg-info/entry_points.txt +2 -0
  27. blinkui-0.1.0/blinkui.egg-info/top_level.txt +2 -0
  28. blinkui-0.1.0/cli/__init__.py +0 -0
  29. blinkui-0.1.0/cli/commands/__init__.py +6 -0
  30. blinkui-0.1.0/cli/commands/build.py +73 -0
  31. blinkui-0.1.0/cli/commands/install.py +82 -0
  32. blinkui-0.1.0/cli/commands/new.py +206 -0
  33. blinkui-0.1.0/cli/commands/run.py +119 -0
  34. blinkui-0.1.0/cli/main.py +78 -0
  35. blinkui-0.1.0/setup.cfg +4 -0
  36. blinkui-0.1.0/setup.py +14 -0
blinkui-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,7 @@
1
+ Metadata-Version: 2.4
2
+ Name: blinkui
3
+ Version: 0.1.0
4
+ Summary: Build mobile apps in pure Python
5
+ Requires-Python: >=3.10
6
+ Dynamic: requires-python
7
+ Dynamic: summary
@@ -0,0 +1,191 @@
1
+ <div align="center">
2
+
3
+ # ⚡ BlinkUI
4
+
5
+ ### Build mobile apps in pure Python
6
+
7
+ [![Python](https://img.shields.io/badge/Python-3.10+-blue.svg)](https://python.org)
8
+ [![License](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
9
+ [![Platform](https://img.shields.io/badge/Platform-iOS%20%7C%20Android-lightgrey.svg)]()
10
+
11
+ </div>
12
+
13
+ ---
14
+
15
+ ## What is BlinkUI?
16
+
17
+ BlinkUI is a mobile app framework for Python developers. Write your app in pure Python and ship it to iOS and Android. No Swift, no Kotlin, no JavaScript.
18
+ ```python
19
+ from blinkui import Screen, state
20
+ from blinkui.components import VStack, Text, Button, Heading
21
+
22
+ class HomeScreen(Screen):
23
+ count = state(0)
24
+
25
+ def build(self):
26
+ return VStack(
27
+ Heading("Hello from BlinkUI"),
28
+ Text(f"Count: {self.count}").size(48).bold(),
29
+ Button("Tap Me").on_tap(self.increment),
30
+ ).spacing(16).padding(24)
31
+
32
+ def increment(self):
33
+ self.count += 1
34
+ ```
35
+
36
+ ---
37
+
38
+ ## Why BlinkUI?
39
+
40
+ | | BlinkUI | React Native | Flutter |
41
+ |---|---|---|---|
42
+ | Language | **Python** | JavaScript | Dart |
43
+ | Python ecosystem | **✅ Full access** | ❌ | ❌ |
44
+ | AI/ML libraries | **✅ pandas, numpy** | ❌ | ❌ |
45
+ | Native UI | ✅ | ✅ | ❌ |
46
+ | Hot reload | ✅ | ✅ | ✅ |
47
+
48
+ ---
49
+
50
+ ## Getting started
51
+ ```bash
52
+ pip install blinkui
53
+ blink new myapp
54
+ cd myapp
55
+ blink run
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Architecture
61
+
62
+ BlinkUI is built in two layers:
63
+
64
+ **C Runtime** — a fast native engine inspired by Facebook's Hermes:
65
+ - Node tree — every UI element represented in memory
66
+ - Reconciler — diffs component trees, patches only what changed
67
+ - Event system — catches native interactions, fires Python callbacks
68
+ - Animation engine — 60fps animations running entirely in C
69
+ - CPython host — embeds Python inside iOS and Android
70
+
71
+ **Python Framework** — what developers write in:
72
+ - `state()` — declare reactive variables, UI updates automatically
73
+ - `Screen` — base class for every screen
74
+ - `Router` — navigation stack with data passing
75
+ - `Theme` — iOS-inspired design system with dark mode
76
+ - HTTP client — async requests that never freeze the UI
77
+ - Async storage — persistent key-value storage
78
+
79
+ ---
80
+
81
+ ## Components
82
+ ```python
83
+ from blinkui.components import (
84
+ # Layout
85
+ VStack, HStack, ZStack, ScrollView, Spacer,
86
+
87
+ # Text
88
+ Text, Heading, Label, Badge,
89
+
90
+ # Input
91
+ Button, TextField, Toggle, Slider,
92
+
93
+ # Display
94
+ Card, Image, Avatar, Modal, BottomSheet,
95
+
96
+ # Navigation
97
+ TabBar, NavigationBar, Toast,
98
+ )
99
+ ```
100
+
101
+ ---
102
+
103
+ ## HTTP and AI backend
104
+
105
+ BlinkUI apps connect to Python backends via HTTP:
106
+ ```python
107
+ from blinkui.http import get, post, set_base_url, set_token
108
+
109
+ class DashboardScreen(Screen):
110
+ data = state(None)
111
+
112
+ async def on_mount(self):
113
+ response = await get("https://api.myapp.com/data")
114
+ if response.ok:
115
+ self.data = response.data
116
+ else:
117
+ self.error = response.error
118
+ ```
119
+
120
+ Your AI and ML code lives on the server — pandas, tensorflow, pytorch — and your BlinkUI app calls it via API. Mobile stays fast. Python stays powerful.
121
+
122
+ ---
123
+
124
+ ## Storage
125
+ ```python
126
+ from blinkui.storage import storage
127
+
128
+ # save
129
+ await storage.set("token", "abc123")
130
+
131
+ # get
132
+ token = await storage.get("token")
133
+
134
+ # delete
135
+ await storage.delete("token")
136
+ ```
137
+
138
+ ---
139
+
140
+ ## CLI
141
+ ```bash
142
+ blink new myapp # create a new app
143
+ blink run # run on simulator
144
+ blink run --hot # hot reload
145
+ blink install pandas # install packages
146
+ blink build ios # build for App Store
147
+ blink build android # build for Play Store
148
+ ```
149
+
150
+ ---
151
+
152
+ ## Project structure
153
+ ```
154
+ myapp/
155
+ ├── main.py # entry point
156
+ ├── blink.toml # project config
157
+ ├── screens/
158
+ │ ├── home.py
159
+ │ └── detail.py
160
+ ├── components/
161
+ └── assets/
162
+ ```
163
+
164
+ ---
165
+
166
+ ## Roadmap
167
+
168
+ - [x] C runtime — node tree, reconciler, events, animations
169
+ - [x] CPython embedding on mobile
170
+ - [x] Python framework — state, screen, router, theme
171
+ - [x] Component library
172
+ - [x] HTTP client
173
+ - [x] Async storage
174
+ - [x] CLI tool
175
+ - [ ] Android native bridge
176
+ - [ ] Hot reload on device
177
+ - [ ] PyPI publish
178
+ - [ ] iOS bridge
179
+ - [ ] Documentation site
180
+
181
+ ---
182
+
183
+ ## License
184
+
185
+ MIT — free to use, modify, and distribute.
186
+
187
+ ---
188
+
189
+ <div align="center">
190
+ Built with Python and C by the BlinkUI team
191
+ </div>
@@ -0,0 +1,7 @@
1
+ from .app import App
2
+ from .screen import Screen
3
+ from .state import state
4
+ from .router import Router
5
+ from .theme import Theme, get_theme, set_theme
6
+
7
+ __all__ = ["App", "Screen", "state", "Router", "Theme", "get_theme", "set_theme"]
@@ -0,0 +1,30 @@
1
+ from .screen import Screen
2
+
3
+
4
+ class App:
5
+ """
6
+ Entry point for every BlinkUI app.
7
+
8
+ Usage:
9
+ from blinkui import App
10
+ from screens.home import HomeScreen
11
+
12
+ App(entry=HomeScreen).run()
13
+ """
14
+
15
+ def __init__(self, entry, theme=None):
16
+ self._entry = entry
17
+ self._theme = theme
18
+ self._screen = None
19
+
20
+ def run(self):
21
+ print("[App] BlinkUI starting...")
22
+
23
+ # instantiate the entry screen
24
+ self._screen = self._entry()
25
+
26
+ # mount and render
27
+ self._screen.on_mount()
28
+ self._screen._render()
29
+
30
+ print("[App] App running")
@@ -0,0 +1,24 @@
1
+ from .layout import VStack, HStack, ZStack, ScrollView, Spacer, Divider
2
+ from .text import Text, Heading, Label, Badge
3
+ from .button import Button, IconButton
4
+ from .image import Image, Avatar
5
+ from .input import TextField, Toggle, Slider, Picker
6
+ from .card import Card, Skeleton, Modal, BottomSheet
7
+ from .navigation import TabBar, Tab, NavigationBar, Toast
8
+
9
+ __all__ = [
10
+ # layout
11
+ "VStack", "HStack", "ZStack", "ScrollView", "Spacer", "Divider",
12
+ # text
13
+ "Text", "Heading", "Label", "Badge",
14
+ # button
15
+ "Button", "IconButton",
16
+ # image
17
+ "Image", "Avatar",
18
+ # input
19
+ "TextField", "Toggle", "Slider", "Picker",
20
+ # card
21
+ "Card", "Skeleton", "Modal", "BottomSheet",
22
+ # navigation
23
+ "TabBar", "Tab", "NavigationBar", "Toast",
24
+ ]
@@ -0,0 +1,159 @@
1
+ # ─────────────────────────────────────────
2
+ # Base component
3
+ # Every BlinkUI component inherits from this
4
+ # ─────────────────────────────────────────
5
+
6
+ class Component:
7
+ """
8
+ Base class for all BlinkUI components.
9
+ Supports method chaining for styling.
10
+ """
11
+
12
+ def __init__(self, *children):
13
+ self._children = list(children)
14
+ self._font_size = 16
15
+ self._bold = False
16
+ self._italic = False
17
+ self._color = None
18
+ self._background = None
19
+ self._width = None
20
+ self._height = None
21
+ self._padding = [0, 0, 0, 0] # top right bottom left
22
+ self._margin = [0, 0, 0, 0]
23
+ self._corner_radius = 0
24
+ self._opacity = 1.0
25
+ self._visible = True
26
+ self._on_tap = None
27
+ self._on_change = None
28
+ self._spacing = 8
29
+ self._align = "start"
30
+ self._animation = None
31
+
32
+ # ── Styling methods — all return self for chaining ──
33
+
34
+ def size(self, font_size):
35
+ self._font_size = font_size
36
+ return self
37
+
38
+ def bold(self, value=True):
39
+ self._bold = value
40
+ return self
41
+
42
+ def italic(self, value=True):
43
+ self._italic = value
44
+ return self
45
+
46
+ def color(self, color):
47
+ self._color = color
48
+ return self
49
+
50
+ def background(self, color):
51
+ self._background = color
52
+ return self
53
+
54
+ def width(self, value):
55
+ self._width = value
56
+ return self
57
+
58
+ def height(self, value):
59
+ self._height = value
60
+ return self
61
+
62
+ def padding(self, top=0, right=None, bottom=None, left=None):
63
+ # support padding(16) for all sides
64
+ if right is None:
65
+ right = bottom = left = top
66
+ elif bottom is None:
67
+ bottom = top
68
+ left = right
69
+ self._padding = [top, right, bottom, left]
70
+ return self
71
+
72
+ def margin(self, top=0, right=None, bottom=None, left=None):
73
+ if right is None:
74
+ right = bottom = left = top
75
+ elif bottom is None:
76
+ bottom = top
77
+ left = right
78
+ self._margin = [top, right, bottom, left]
79
+ return self
80
+
81
+ def corner_radius(self, value):
82
+ self._corner_radius = value
83
+ return self
84
+
85
+ def round(self, value=999):
86
+ self._corner_radius = value
87
+ return self
88
+
89
+ def opacity(self, value):
90
+ self._opacity = value
91
+ return self
92
+
93
+ def visible(self, value=True):
94
+ self._visible = value
95
+ return self
96
+
97
+ def spacing(self, value):
98
+ self._spacing = value
99
+ return self
100
+
101
+ def align(self, value):
102
+ # "start" "center" "end"
103
+ self._align = value
104
+ return self
105
+
106
+ def center(self):
107
+ self._align = "center"
108
+ return self
109
+
110
+ # ── Event methods ──
111
+
112
+ def on_tap(self, callback):
113
+ self._on_tap = callback
114
+ return self
115
+
116
+ def on_change(self, callback):
117
+ self._on_change = callback
118
+ return self
119
+
120
+ # ── Animation ──
121
+
122
+ def animate(self, duration=300, curve="easeOut", **props):
123
+ self._animation = {
124
+ "duration": duration,
125
+ "curve": curve,
126
+ "props": props
127
+ }
128
+ return self
129
+
130
+ # ── Serialization ──
131
+ # Converts component tree to dict
132
+ # C runtime reads this dict to build native views
133
+
134
+ def to_dict(self):
135
+ return {
136
+ "type": self.__class__.__name__,
137
+ "font_size": self._font_size,
138
+ "bold": self._bold,
139
+ "italic": self._italic,
140
+ "color": self._color,
141
+ "background": self._background,
142
+ "width": self._width,
143
+ "height": self._height,
144
+ "padding": self._padding,
145
+ "margin": self._margin,
146
+ "corner_radius": self._corner_radius,
147
+ "opacity": self._opacity,
148
+ "visible": self._visible,
149
+ "spacing": self._spacing,
150
+ "align": self._align,
151
+ "animation": self._animation,
152
+ "children": [
153
+ c.to_dict() if isinstance(c, Component) else str(c)
154
+ for c in self._children
155
+ ]
156
+ }
157
+
158
+ def __repr__(self):
159
+ return f"<{self.__class__.__name__} children={len(self._children)}>"
@@ -0,0 +1,44 @@
1
+ from .base import Component
2
+
3
+
4
+ class Button(Component):
5
+ """
6
+ Tappable button.
7
+
8
+ Usage:
9
+ Button("Get Started").on_tap(self.handle_tap)
10
+ Button("Delete").color("#FF3B30").on_tap(self.delete)
11
+ """
12
+ def __init__(self, label=""):
13
+ super().__init__()
14
+ self._label = str(label)
15
+ self._background = "#007AFF" # iOS blue
16
+ self._color = "#FFFFFF"
17
+ self._font_size = 16
18
+ self._bold = True
19
+ self._corner_radius = 12
20
+ self._padding = [14, 20, 14, 20]
21
+
22
+ def to_dict(self):
23
+ d = super().to_dict()
24
+ d["label"] = self._label
25
+ return d
26
+
27
+
28
+ class IconButton(Component):
29
+ """
30
+ Button with just an icon.
31
+
32
+ Usage:
33
+ IconButton("heart").on_tap(self.like)
34
+ """
35
+ def __init__(self, icon=""):
36
+ super().__init__()
37
+ self._icon = icon
38
+ self._background = "transparent"
39
+ self._color = "#007AFF"
40
+
41
+ def to_dict(self):
42
+ d = super().to_dict()
43
+ d["icon"] = self._icon
44
+ return d
@@ -0,0 +1,132 @@
1
+ from .base import Component
2
+
3
+
4
+ class Card(Component):
5
+ """
6
+ Elevated surface for grouping content.
7
+ iOS style with subtle shadow and rounded corners.
8
+
9
+ Usage:
10
+ Card(
11
+ VStack(
12
+ Text("Title").bold(),
13
+ Text("Subtitle").color("#8E8E93")
14
+ )
15
+ ).padding(16)
16
+
17
+ Card(
18
+ Text("Simple card")
19
+ ).on_tap(self.handle_tap)
20
+ """
21
+
22
+ def __init__(self, *children):
23
+ super().__init__(*children)
24
+ self._background = "#FFFFFF"
25
+ self._corner_radius = 12
26
+ self._padding = [16, 16, 16, 16]
27
+ self._shadow = True
28
+ self._shadow_color = "rgba(0,0,0,0.08)"
29
+ self._shadow_radius = 8
30
+ self._shadow_offset = [0, 2]
31
+
32
+ def shadow(self, value=True):
33
+ self._shadow = value
34
+ return self
35
+
36
+ def shadow_color(self, color):
37
+ self._shadow_color = color
38
+ return self
39
+
40
+ def to_dict(self):
41
+ d = super().to_dict()
42
+ d["shadow"] = self._shadow
43
+ d["shadow_color"] = self._shadow_color
44
+ d["shadow_radius"] = self._shadow_radius
45
+ d["shadow_offset"] = self._shadow_offset
46
+ return d
47
+
48
+
49
+ class Skeleton(Component):
50
+ """
51
+ Loading placeholder that animates.
52
+ Shows while content is being fetched.
53
+
54
+ Usage:
55
+ Skeleton().width(200).height(20)
56
+ Skeleton().width(150).height(20)
57
+ """
58
+
59
+ def __init__(self):
60
+ super().__init__()
61
+ self._background = "#E5E5EA"
62
+ self._animated = True
63
+
64
+ def to_dict(self):
65
+ d = super().to_dict()
66
+ d["animated"] = self._animated
67
+ return d
68
+
69
+
70
+ class Modal(Component):
71
+ """
72
+ Full screen overlay modal.
73
+
74
+ Usage:
75
+ Modal(
76
+ VStack(
77
+ Heading("Are you sure?"),
78
+ Button("Confirm").on_tap(self.confirm),
79
+ Button("Cancel").on_tap(self.dismiss)
80
+ ),
81
+ visible=self.show_modal
82
+ )
83
+ """
84
+
85
+ def __init__(self, *children, visible=False):
86
+ super().__init__(*children)
87
+ self._modal_visible = visible
88
+ self._dismissable = True
89
+ self._background = "#FFFFFF"
90
+ self._corner_radius = 20
91
+
92
+ def dismissable(self, value=True):
93
+ self._dismissable = value
94
+ return self
95
+
96
+ def to_dict(self):
97
+ d = super().to_dict()
98
+ d["modal_visible"] = self._modal_visible
99
+ d["dismissable"] = self._dismissable
100
+ return d
101
+
102
+
103
+ class BottomSheet(Component):
104
+ """
105
+ Sheet that slides up from the bottom.
106
+
107
+ Usage:
108
+ BottomSheet(
109
+ VStack(
110
+ Text("Options"),
111
+ Button("Share").on_tap(self.share),
112
+ ),
113
+ visible=self.show_sheet
114
+ )
115
+ """
116
+
117
+ def __init__(self, *children, visible=False):
118
+ super().__init__(*children)
119
+ self._sheet_visible = visible
120
+ self._detents = ["medium", "large"] # snap points
121
+ self._background = "#FFFFFF"
122
+ self._corner_radius = 20
123
+
124
+ def detents(self, *values):
125
+ self._detents = list(values)
126
+ return self
127
+
128
+ def to_dict(self):
129
+ d = super().to_dict()
130
+ d["sheet_visible"] = self._sheet_visible
131
+ d["detents"] = self._detents
132
+ return d
@@ -0,0 +1,65 @@
1
+ from .base import Component
2
+
3
+
4
+ class Image(Component):
5
+ """
6
+ Displays an image from a path or URL.
7
+
8
+ Usage:
9
+ Image("avatar.png").size(80).round()
10
+ Image("https://example.com/photo.jpg").width(200).height(150)
11
+ """
12
+
13
+ def __init__(self, source=""):
14
+ super().__init__()
15
+ self._source = source
16
+ self._fit = "cover" # cover, contain, fill
17
+ self._alt = ""
18
+
19
+ def fit(self, mode):
20
+ # "cover" "contain" "fill"
21
+ self._fit = mode
22
+ return self
23
+
24
+ def alt(self, text):
25
+ self._alt = text
26
+ return self
27
+
28
+ def to_dict(self):
29
+ d = super().to_dict()
30
+ d["source"] = self._source
31
+ d["fit"] = self._fit
32
+ d["alt"] = self._alt
33
+ return d
34
+
35
+
36
+ class Avatar(Component):
37
+ """
38
+ Circular avatar image. Falls back to initials if no image.
39
+
40
+ Usage:
41
+ Avatar("photo.png").size(48)
42
+ Avatar(initials="JD").size(48).color("#007AFF")
43
+ """
44
+
45
+ def __init__(self, source="", initials=""):
46
+ super().__init__()
47
+ self._source = source
48
+ self._initials = initials
49
+ self._size_val = 40
50
+ self._background = "#007AFF"
51
+ self._color = "#FFFFFF"
52
+
53
+ def size(self, value):
54
+ self._size_val = value
55
+ self._width = value
56
+ self._height = value
57
+ self._corner_radius = value / 2
58
+ return self
59
+
60
+ def to_dict(self):
61
+ d = super().to_dict()
62
+ d["source"] = self._source
63
+ d["initials"] = self._initials
64
+ d["size"] = self._size_val
65
+ return d