apkpy 0.2.2__tar.gz → 0.2.3__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.
- {apkpy-0.2.2 → apkpy-0.2.3}/PKG-INFO +1 -1
- {apkpy-0.2.2 → apkpy-0.2.3}/apkpy.egg-info/PKG-INFO +1 -1
- {apkpy-0.2.2 → apkpy-0.2.3}/apkpy.egg-info/SOURCES.txt +1 -0
- {apkpy-0.2.2 → apkpy-0.2.3}/apkpy_lib/cli.py +11 -3
- apkpy-0.2.3/apkpy_lib/core/__pycache__/compiler.cpython-313.pyc +0 -0
- apkpy-0.2.3/apkpy_lib/core/__pycache__/style_parser.cpython-313.pyc +0 -0
- {apkpy-0.2.2 → apkpy-0.2.3}/apkpy_lib/core/compiler.py +164 -46
- {apkpy-0.2.2 → apkpy-0.2.3}/apkpy_lib/ui.py +58 -20
- {apkpy-0.2.2 → apkpy-0.2.3}/setup.py +1 -1
- apkpy-0.2.2/apkpy_lib/core/__pycache__/compiler.cpython-313.pyc +0 -0
- {apkpy-0.2.2 → apkpy-0.2.3}/MANIFEST.in +0 -0
- {apkpy-0.2.2 → apkpy-0.2.3}/README.md +0 -0
- {apkpy-0.2.2 → apkpy-0.2.3}/apkpy.egg-info/dependency_links.txt +0 -0
- {apkpy-0.2.2 → apkpy-0.2.3}/apkpy.egg-info/entry_points.txt +0 -0
- {apkpy-0.2.2 → apkpy-0.2.3}/apkpy.egg-info/requires.txt +0 -0
- {apkpy-0.2.2 → apkpy-0.2.3}/apkpy.egg-info/top_level.txt +0 -0
- {apkpy-0.2.2 → apkpy-0.2.3}/apkpy_lib/__init__.py +0 -0
- {apkpy-0.2.2 → apkpy-0.2.3}/apkpy_lib/core/style_parser.py +0 -0
- {apkpy-0.2.2 → apkpy-0.2.3}/apkpy_lib/templates/android_project/app/src/main/AndroidManifest.xml +0 -0
- {apkpy-0.2.2 → apkpy-0.2.3}/apkpy_lib/templates/android_project/app/src/main/java/com/exemplo/MainActivity.java +0 -0
- {apkpy-0.2.2 → apkpy-0.2.3}/setup.cfg +0 -0
|
@@ -13,5 +13,6 @@ apkpy_lib/ui.py
|
|
|
13
13
|
apkpy_lib/core/compiler.py
|
|
14
14
|
apkpy_lib/core/style_parser.py
|
|
15
15
|
apkpy_lib/core/__pycache__/compiler.cpython-313.pyc
|
|
16
|
+
apkpy_lib/core/__pycache__/style_parser.cpython-313.pyc
|
|
16
17
|
apkpy_lib/templates/android_project/app/src/main/AndroidManifest.xml
|
|
17
18
|
apkpy_lib/templates/android_project/app/src/main/java/com/exemplo/MainActivity.java
|
|
@@ -80,9 +80,17 @@ def build():
|
|
|
80
80
|
else:
|
|
81
81
|
out = os.path.join(build_dir, rel_path)
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
if isinstance(content, tuple) and content[0] == "COPY_FILE":
|
|
84
|
+
source_path = content[1]
|
|
85
|
+
if os.path.exists(source_path):
|
|
86
|
+
shutil.copyfile(source_path, out)
|
|
87
|
+
click.echo(f" 🖼️ {os.path.basename(out)} (copiado de {source_path})")
|
|
88
|
+
else:
|
|
89
|
+
click.echo(f" ⚠️ Aviso: Imagem não encontrada: {source_path}")
|
|
90
|
+
else:
|
|
91
|
+
with open(out, "w", encoding="utf-8") as f:
|
|
92
|
+
f.write(content)
|
|
93
|
+
click.echo(f" ✅ {os.path.basename(out)}")
|
|
86
94
|
|
|
87
95
|
else:
|
|
88
96
|
# ── Fallback: Single-Screen (MainActivity) ────────────────────────────
|
|
Binary file
|
|
Binary file
|
|
@@ -12,12 +12,24 @@ def _parse_radius(style_dict):
|
|
|
12
12
|
except (ValueError, TypeError):
|
|
13
13
|
return 0
|
|
14
14
|
|
|
15
|
+
def _parse_border_width(style_dict):
|
|
16
|
+
raw = style_dict.get('border-width', '0')
|
|
17
|
+
cleaned = re.sub(r'[^0-9.]', '', str(raw))
|
|
18
|
+
try:
|
|
19
|
+
return int(float(cleaned))
|
|
20
|
+
except (ValueError, TypeError):
|
|
21
|
+
return 0
|
|
22
|
+
|
|
15
23
|
def _get_style_bg(style_dict):
|
|
16
24
|
return style_dict.get('background-color') or style_dict.get('backgroundcolor')
|
|
17
25
|
|
|
18
26
|
def _get_style_fg(style_dict):
|
|
19
27
|
return style_dict.get('color')
|
|
20
28
|
|
|
29
|
+
def _get_style_pressed_color(style_dict):
|
|
30
|
+
val = style_dict.get('pressed-color')
|
|
31
|
+
return _resolve_color(val) if val else None
|
|
32
|
+
|
|
21
33
|
def _get_style_border_color(style_dict):
|
|
22
34
|
val = style_dict.get('border-color')
|
|
23
35
|
if val and val.lower() == 'none':
|
|
@@ -36,22 +48,56 @@ def _resolve_color(c):
|
|
|
36
48
|
}
|
|
37
49
|
return mapping.get(c_lower, c)
|
|
38
50
|
|
|
39
|
-
def _generate_drawable_xml(bg_color, radius_px, border_color=None):
|
|
40
|
-
|
|
41
|
-
'<?xml version="1.0" encoding="utf-8"?>',
|
|
42
|
-
'<shape xmlns:android="http://schemas.android.com/apk/res/android">',
|
|
43
|
-
f' <solid android:color="{bg_color}" />',
|
|
44
|
-
f' <corners android:radius="{radius_px}dp" />'
|
|
45
|
-
]
|
|
51
|
+
def _generate_drawable_xml(bg_color, radius_px, border_color=None, border_width=0, pressed_color=None):
|
|
52
|
+
stroke_w = border_width if border_width > 0 else 1
|
|
46
53
|
|
|
54
|
+
stroke_xml = ""
|
|
47
55
|
if border_color is None:
|
|
48
|
-
|
|
56
|
+
stroke_xml = f' <stroke android:width="{stroke_w}dp" android:color="#808080" />'
|
|
49
57
|
elif border_color != 'none':
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
stroke_xml = f' <stroke android:width="{stroke_w}dp" android:color="{border_color}" />'
|
|
59
|
+
|
|
60
|
+
padding_xml = ' <padding android:left="10dp" android:top="5dp" android:right="10dp" android:bottom="5dp" />'
|
|
61
|
+
|
|
62
|
+
if pressed_color:
|
|
63
|
+
lines = [
|
|
64
|
+
'<?xml version="1.0" encoding="utf-8"?>',
|
|
65
|
+
'<selector xmlns:android="http://schemas.android.com/apk/res/android">',
|
|
66
|
+
' <item android:state_pressed="true">',
|
|
67
|
+
' <shape>',
|
|
68
|
+
f' <solid android:color="{pressed_color}" />',
|
|
69
|
+
f' <corners android:radius="{radius_px}dp" />'
|
|
70
|
+
]
|
|
71
|
+
if stroke_xml: lines.append(stroke_xml)
|
|
72
|
+
lines.append(padding_xml)
|
|
73
|
+
lines += [
|
|
74
|
+
' </shape>',
|
|
75
|
+
' </item>',
|
|
76
|
+
' <item>',
|
|
77
|
+
' <shape>',
|
|
78
|
+
f' <solid android:color="{bg_color}" />',
|
|
79
|
+
f' <corners android:radius="{radius_px}dp" />'
|
|
80
|
+
]
|
|
81
|
+
if stroke_xml: lines.append(stroke_xml)
|
|
82
|
+
lines.append(padding_xml)
|
|
83
|
+
lines += [
|
|
84
|
+
' </shape>',
|
|
85
|
+
' </item>',
|
|
86
|
+
'</selector>'
|
|
87
|
+
]
|
|
88
|
+
return "\n".join(lines)
|
|
89
|
+
else:
|
|
90
|
+
lines = [
|
|
91
|
+
'<?xml version="1.0" encoding="utf-8"?>',
|
|
92
|
+
'<shape xmlns:android="http://schemas.android.com/apk/res/android">',
|
|
93
|
+
f' <solid android:color="{bg_color}" />',
|
|
94
|
+
f' <corners android:radius="{radius_px}dp" />'
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
if stroke_xml: lines.append(stroke_xml[4:])
|
|
98
|
+
lines.append(padding_xml[4:])
|
|
99
|
+
lines.append('</shape>')
|
|
100
|
+
return "\n".join(lines)
|
|
55
101
|
|
|
56
102
|
|
|
57
103
|
# ─── Legacy Single-Screen Transpiler ──────────────────────────────────────────
|
|
@@ -86,8 +132,10 @@ class JavaTranspiler(ast.NodeVisitor):
|
|
|
86
132
|
bg = _get_style_bg(s)
|
|
87
133
|
color = _resolve_color(bg)
|
|
88
134
|
bc = _get_style_border_color(s)
|
|
135
|
+
bw = _parse_border_width(s)
|
|
136
|
+
pc = _get_style_pressed_color(s)
|
|
89
137
|
drawable_name = f"{button_id}_shape"
|
|
90
|
-
self.drawables[f"res/drawable/{drawable_name}.xml"] = _generate_drawable_xml(color, radius, border_color=bc)
|
|
138
|
+
self.drawables[f"res/drawable/{drawable_name}.xml"] = _generate_drawable_xml(color, radius, border_color=bc, border_width=bw, pressed_color=pc)
|
|
91
139
|
java_code += f'\n {var_name}.setBackgroundResource(R.drawable.{drawable_name});'
|
|
92
140
|
elif 'background-color' in s or 'backgroundcolor' in s:
|
|
93
141
|
bg = _get_style_bg(s)
|
|
@@ -122,8 +170,10 @@ class JavaTranspiler(ast.NodeVisitor):
|
|
|
122
170
|
bg = _get_style_bg(s)
|
|
123
171
|
color = _resolve_color(bg)
|
|
124
172
|
bc = _get_style_border_color(s)
|
|
173
|
+
bw = _parse_border_width(s)
|
|
174
|
+
pc = _get_style_pressed_color(s)
|
|
125
175
|
drawable_name = f"{input_id}_shape"
|
|
126
|
-
self.drawables[f"res/drawable/{drawable_name}.xml"] = _generate_drawable_xml(color, radius, border_color=bc)
|
|
176
|
+
self.drawables[f"res/drawable/{drawable_name}.xml"] = _generate_drawable_xml(color, radius, border_color=bc, border_width=bw, pressed_color=pc)
|
|
127
177
|
style_code += f'\n {var_name}.setBackgroundResource(R.drawable.{drawable_name});'
|
|
128
178
|
elif 'background-color' in s or 'backgroundcolor' in s:
|
|
129
179
|
bg = _get_style_bg(s)
|
|
@@ -207,6 +257,7 @@ class MultiScreenTranspiler(ast.NodeVisitor):
|
|
|
207
257
|
self.styles = styles or {}
|
|
208
258
|
# var_name (Python) -> screen_id
|
|
209
259
|
self.screens = {}
|
|
260
|
+
self.screen_bg_images = {}
|
|
210
261
|
# screen_id -> lista de dicts de componentes
|
|
211
262
|
self.screen_components = {}
|
|
212
263
|
# var_name -> dict do componente (para resolver on_click_navigate)
|
|
@@ -214,6 +265,7 @@ class MultiScreenTranspiler(ast.NodeVisitor):
|
|
|
214
265
|
# lista de {from_screen_id, button_py_var, to_screen_id}
|
|
215
266
|
self.navigations = []
|
|
216
267
|
self.start_screen_id = None
|
|
268
|
+
self.functions = {}
|
|
217
269
|
|
|
218
270
|
# ── Helpers ───────────────────────────────────────────────────────────────
|
|
219
271
|
|
|
@@ -245,6 +297,8 @@ class MultiScreenTranspiler(ast.NodeVisitor):
|
|
|
245
297
|
if comp_type == 'button':
|
|
246
298
|
comp['text'] = call.args[0].value if call.args else ''
|
|
247
299
|
comp['id'] = self._kw(call.keywords, 'id')
|
|
300
|
+
comp['command'] = self._kw(call.keywords, 'command')
|
|
301
|
+
comp['on_click'] = self._kw(call.keywords, 'on_click')
|
|
248
302
|
elif comp_type in ('inputs', 'input_field'):
|
|
249
303
|
comp['placeholder'] = call.args[0].value if call.args else ''
|
|
250
304
|
comp['id'] = self._kw(call.keywords, 'id')
|
|
@@ -266,6 +320,18 @@ class MultiScreenTranspiler(ast.NodeVisitor):
|
|
|
266
320
|
|
|
267
321
|
# ── Visitors ──────────────────────────────────────────────────────────────
|
|
268
322
|
|
|
323
|
+
def visit_FunctionDef(self, node):
|
|
324
|
+
commands = []
|
|
325
|
+
for stmt in node.body:
|
|
326
|
+
if isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Call):
|
|
327
|
+
call = stmt.value
|
|
328
|
+
func_id = getattr(call.func, 'id', getattr(call.func, 'attr', ''))
|
|
329
|
+
if func_id == 'toast':
|
|
330
|
+
msg = call.args[0].value if hasattr(call.args[0], 'value') else ""
|
|
331
|
+
commands.append(msg)
|
|
332
|
+
self.functions[node.name] = commands
|
|
333
|
+
self.generic_visit(node)
|
|
334
|
+
|
|
269
335
|
def visit_Assign(self, node):
|
|
270
336
|
if not node.targets or not isinstance(node.targets[0], ast.Name):
|
|
271
337
|
self.generic_visit(node)
|
|
@@ -283,7 +349,10 @@ class MultiScreenTranspiler(ast.NodeVisitor):
|
|
|
283
349
|
# login_screen = Screen(id="login")
|
|
284
350
|
if isinstance(func, ast.Name) and func.id == 'Screen':
|
|
285
351
|
screen_id = self._kw(value.keywords, 'id', var_name)
|
|
352
|
+
bg_image = self._kw(value.keywords, 'background_image')
|
|
286
353
|
self.screens[var_name] = screen_id
|
|
354
|
+
if bg_image:
|
|
355
|
+
self.screen_bg_images[screen_id] = bg_image
|
|
287
356
|
self.screen_components.setdefault(screen_id, [])
|
|
288
357
|
|
|
289
358
|
# btn = button("Entrar", screen=login_screen)
|
|
@@ -332,10 +401,11 @@ class MultiScreenTranspiler(ast.NodeVisitor):
|
|
|
332
401
|
# ─── Java / XML / Manifest generators ────────────────────────────────────────
|
|
333
402
|
|
|
334
403
|
def _generate_activity_java(screen_id, components, navigations, start_screen_id,
|
|
335
|
-
package="com.apkpy.app"):
|
|
404
|
+
package="com.apkpy.app", functions_map=None):
|
|
336
405
|
"""Gera o conteúdo completo de um ficheiro *Activity.java para um Screen."""
|
|
337
406
|
|
|
338
407
|
class_name = f"Screen_{screen_id}Activity"
|
|
408
|
+
functions_map = functions_map or {}
|
|
339
409
|
|
|
340
410
|
# Descobrir quais imports são necessários
|
|
341
411
|
types = {c.get('input_type', '') for c in components if c['comp_type'] in ('inputs', 'input_field')}
|
|
@@ -346,6 +416,14 @@ def _generate_activity_java(screen_id, components, navigations, start_screen_id,
|
|
|
346
416
|
has_seekbar = 'range' in types
|
|
347
417
|
has_radio = 'radio' in types
|
|
348
418
|
has_nav = any(n['from_screen_id'] == screen_id for n in navigations)
|
|
419
|
+
|
|
420
|
+
has_toast = False
|
|
421
|
+
for comp in components:
|
|
422
|
+
if comp['comp_type'] == 'button':
|
|
423
|
+
cmd = comp.get('command') or comp.get('on_click')
|
|
424
|
+
if cmd and cmd in functions_map and functions_map[cmd]:
|
|
425
|
+
has_toast = True
|
|
426
|
+
break
|
|
349
427
|
|
|
350
428
|
imports = [
|
|
351
429
|
f"import {package}.R;",
|
|
@@ -362,6 +440,7 @@ def _generate_activity_java(screen_id, components, navigations, start_screen_id,
|
|
|
362
440
|
if has_radio:
|
|
363
441
|
imports += ["import android.widget.RadioGroup;", "import android.widget.RadioButton;"]
|
|
364
442
|
if has_nav: imports.append("import android.content.Intent;")
|
|
443
|
+
if has_toast: imports.append("import android.widget.Toast;")
|
|
365
444
|
|
|
366
445
|
lines = []
|
|
367
446
|
lines.append(f"package {package};")
|
|
@@ -450,27 +529,36 @@ def _generate_activity_java(screen_id, components, navigations, start_screen_id,
|
|
|
450
529
|
lines.append(f" layout.addView({jv}Group);")
|
|
451
530
|
lines.append("")
|
|
452
531
|
|
|
453
|
-
# OnClickListeners
|
|
454
|
-
for
|
|
455
|
-
if
|
|
532
|
+
# OnClickListeners (navegação e custom functions combinados)
|
|
533
|
+
for comp in components:
|
|
534
|
+
if comp['comp_type'] != 'button':
|
|
456
535
|
continue
|
|
457
536
|
|
|
458
|
-
btn_py_var
|
|
459
|
-
|
|
537
|
+
btn_py_var = comp.get('_py_var')
|
|
538
|
+
btn_java_var = comp.get('_java_var')
|
|
539
|
+
cmd = comp.get('command') or comp.get('on_click')
|
|
460
540
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
btn_java_var = comp.get('_java_var')
|
|
541
|
+
nav_info = None
|
|
542
|
+
for n in navigations:
|
|
543
|
+
if n['from_screen_id'] == screen_id and n['button_py_var'] == btn_py_var:
|
|
544
|
+
nav_info = n
|
|
466
545
|
break
|
|
467
546
|
|
|
468
|
-
if
|
|
547
|
+
toast_msgs = functions_map.get(cmd, []) if cmd else []
|
|
548
|
+
|
|
549
|
+
if (nav_info or toast_msgs) and btn_java_var:
|
|
469
550
|
lines.append(f" {btn_java_var}.setOnClickListener(new View.OnClickListener() {{")
|
|
470
551
|
lines.append(f" @Override")
|
|
471
552
|
lines.append(f" public void onClick(View v) {{")
|
|
472
|
-
|
|
473
|
-
|
|
553
|
+
|
|
554
|
+
for msg in toast_msgs:
|
|
555
|
+
lines.append(f" Toast.makeText(getApplicationContext(), \"{msg}\", Toast.LENGTH_SHORT).show();")
|
|
556
|
+
|
|
557
|
+
if nav_info:
|
|
558
|
+
to_class = f"Screen_{nav_info['to_screen_id']}Activity"
|
|
559
|
+
lines.append(f" Intent intent = new Intent({class_name}.this, {to_class}.class);")
|
|
560
|
+
lines.append(f" startActivity(intent);")
|
|
561
|
+
|
|
474
562
|
lines.append(f" }}")
|
|
475
563
|
lines.append(f" }});")
|
|
476
564
|
lines.append("")
|
|
@@ -480,17 +568,36 @@ def _generate_activity_java(screen_id, components, navigations, start_screen_id,
|
|
|
480
568
|
return "\n".join(lines)
|
|
481
569
|
|
|
482
570
|
|
|
483
|
-
def _generate_layout_xml(screen_id, components):
|
|
571
|
+
def _generate_layout_xml(screen_id, components, has_bg=False):
|
|
484
572
|
"""Gera o conteúdo de um ficheiro activity_screen_<id>.xml."""
|
|
485
|
-
lines = [
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
573
|
+
lines = ['<?xml version="1.0" encoding="utf-8"?>']
|
|
574
|
+
|
|
575
|
+
if has_bg:
|
|
576
|
+
lines += [
|
|
577
|
+
'<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"',
|
|
578
|
+
' android:layout_width="match_parent"',
|
|
579
|
+
' android:layout_height="match_parent">',
|
|
580
|
+
f' <ImageView',
|
|
581
|
+
f' android:layout_width="match_parent"',
|
|
582
|
+
f' android:layout_height="match_parent"',
|
|
583
|
+
f' android:src="@drawable/screen_{screen_id}_bg"',
|
|
584
|
+
f' android:scaleType="centerCrop" />',
|
|
585
|
+
' <LinearLayout',
|
|
586
|
+
' android:layout_width="match_parent"',
|
|
587
|
+
' android:layout_height="match_parent"',
|
|
588
|
+
' android:orientation="vertical"',
|
|
589
|
+
' android:padding="16dp">',
|
|
590
|
+
'',
|
|
591
|
+
]
|
|
592
|
+
else:
|
|
593
|
+
lines += [
|
|
594
|
+
'<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"',
|
|
595
|
+
' android:layout_width="match_parent"',
|
|
596
|
+
' android:layout_height="match_parent"',
|
|
597
|
+
' android:orientation="vertical"',
|
|
598
|
+
' android:padding="16dp">',
|
|
599
|
+
'',
|
|
600
|
+
]
|
|
494
601
|
|
|
495
602
|
lbl_n = btn_n = inp_n = 0
|
|
496
603
|
|
|
@@ -511,7 +618,7 @@ def _generate_layout_xml(screen_id, components):
|
|
|
511
618
|
|
|
512
619
|
elif ctype == 'button':
|
|
513
620
|
btn_n += 1
|
|
514
|
-
attr_bg = f'
|
|
621
|
+
attr_bg = f'\n android:background="@drawable/{comp["_drawable"]}"' if comp.get('_drawable') else ''
|
|
515
622
|
lines += [
|
|
516
623
|
f' <Button',
|
|
517
624
|
f' android:id="@+id/btn{btn_n}"',
|
|
@@ -527,7 +634,7 @@ def _generate_layout_xml(screen_id, components):
|
|
|
527
634
|
placeholder = comp.get('placeholder', '')
|
|
528
635
|
|
|
529
636
|
if itype in ('text', 'search'):
|
|
530
|
-
attr_bg = f'
|
|
637
|
+
attr_bg = f'\n android:background="@drawable/{comp["_drawable"]}"' if comp.get('_drawable') else ''
|
|
531
638
|
lines += [
|
|
532
639
|
f' <EditText',
|
|
533
640
|
f' android:id="@+id/inp{inp_n}"',
|
|
@@ -537,7 +644,7 @@ def _generate_layout_xml(screen_id, components):
|
|
|
537
644
|
'',
|
|
538
645
|
]
|
|
539
646
|
elif itype == 'password':
|
|
540
|
-
attr_bg = f'
|
|
647
|
+
attr_bg = f'\n android:background="@drawable/{comp["_drawable"]}"' if comp.get('_drawable') else ''
|
|
541
648
|
lines += [
|
|
542
649
|
f' <EditText',
|
|
543
650
|
f' android:id="@+id/inp{inp_n}"',
|
|
@@ -581,7 +688,11 @@ def _generate_layout_xml(screen_id, components):
|
|
|
581
688
|
]
|
|
582
689
|
lines += [' </RadioGroup>', '']
|
|
583
690
|
|
|
584
|
-
|
|
691
|
+
if has_bg:
|
|
692
|
+
lines.append(' </LinearLayout>')
|
|
693
|
+
lines.append('</RelativeLayout>')
|
|
694
|
+
else:
|
|
695
|
+
lines.append('</LinearLayout>')
|
|
585
696
|
return "\n".join(lines)
|
|
586
697
|
|
|
587
698
|
|
|
@@ -668,20 +779,27 @@ def translate_python_to_android(python_code, package="com.apkpy.app"):
|
|
|
668
779
|
bg = _get_style_bg(styles[comp_id])
|
|
669
780
|
color = _resolve_color(bg)
|
|
670
781
|
bc = _get_style_border_color(styles[comp_id])
|
|
782
|
+
bw = _parse_border_width(styles[comp_id])
|
|
783
|
+
pc = _get_style_pressed_color(styles[comp_id])
|
|
671
784
|
drawable_name = f"{comp_id}_shape"
|
|
672
785
|
comp['_drawable'] = drawable_name
|
|
673
|
-
output_files[f"res/drawable/{drawable_name}.xml"] = _generate_drawable_xml(color, radius, border_color=bc)
|
|
786
|
+
output_files[f"res/drawable/{drawable_name}.xml"] = _generate_drawable_xml(color, radius, border_color=bc, border_width=bw, pressed_color=pc)
|
|
674
787
|
|
|
675
788
|
# Java Activity
|
|
676
789
|
java_content = _generate_activity_java(
|
|
677
|
-
sid, components, transpiler.navigations, start_screen_id, package
|
|
790
|
+
sid, components, transpiler.navigations, start_screen_id, package, transpiler.functions
|
|
678
791
|
)
|
|
679
792
|
output_files[f"java/Screen_{sid}Activity.java"] = java_content
|
|
680
793
|
|
|
681
794
|
# XML Layout
|
|
682
|
-
|
|
795
|
+
bg_image = transpiler.screen_bg_images.get(sid)
|
|
796
|
+
xml_content = _generate_layout_xml(sid, components, bool(bg_image))
|
|
683
797
|
output_files[f"res/layout/activity_screen_{sid}.xml"] = xml_content
|
|
684
798
|
|
|
799
|
+
if bg_image:
|
|
800
|
+
ext = bg_image.split('.')[-1]
|
|
801
|
+
output_files[f"res/drawable/screen_{sid}_bg.{ext}"] = ("COPY_FILE", bg_image)
|
|
802
|
+
|
|
685
803
|
# AndroidManifest
|
|
686
804
|
output_files["AndroidManifest.xml"] = _generate_manifest(
|
|
687
805
|
screen_ids, start_screen_id, package
|
|
@@ -35,6 +35,10 @@ def _get_style_fg(style_dict):
|
|
|
35
35
|
"""Lê a cor do texto."""
|
|
36
36
|
return style_dict.get('color')
|
|
37
37
|
|
|
38
|
+
def _get_style_pressed_color(style_dict):
|
|
39
|
+
"""Lê a cor de fundo ao pressionar o botão."""
|
|
40
|
+
return style_dict.get('pressed-color')
|
|
41
|
+
|
|
38
42
|
def _get_style_border_color(style_dict):
|
|
39
43
|
"""Lê a cor da borda, ou 'none'."""
|
|
40
44
|
val = style_dict.get('border-color')
|
|
@@ -51,6 +55,15 @@ def _parse_radius(style_dict):
|
|
|
51
55
|
except (ValueError, TypeError):
|
|
52
56
|
return 0
|
|
53
57
|
|
|
58
|
+
def _parse_border_width(style_dict):
|
|
59
|
+
"""Extrai a largura da borda (ex: '2px' -> 2)."""
|
|
60
|
+
raw = style_dict.get('border-width', '0')
|
|
61
|
+
cleaned = re.sub(r'[^0-9.]', '', str(raw))
|
|
62
|
+
try:
|
|
63
|
+
return int(float(cleaned))
|
|
64
|
+
except (ValueError, TypeError):
|
|
65
|
+
return 0
|
|
66
|
+
|
|
54
67
|
def _create_rounded_rect(canvas, x1, y1, x2, y2, r, **kwargs):
|
|
55
68
|
"""Desenha retângulo com cantos arredondados no Canvas."""
|
|
56
69
|
r = max(0, min(r, (x2 - x1) // 2, (y2 - y1) // 2))
|
|
@@ -92,8 +105,9 @@ class Screen:
|
|
|
92
105
|
run(start_screen=login)
|
|
93
106
|
"""
|
|
94
107
|
|
|
95
|
-
def __init__(self, id):
|
|
108
|
+
def __init__(self, id, background_image=None):
|
|
96
109
|
self.id = id
|
|
110
|
+
self.background_image = background_image
|
|
97
111
|
self.components = [] # lista de ComponentDef
|
|
98
112
|
|
|
99
113
|
def _add(self, comp_def):
|
|
@@ -155,11 +169,12 @@ class AppPreview:
|
|
|
155
169
|
lbl.pack(pady=4, fill="x")
|
|
156
170
|
comp.widget = lbl
|
|
157
171
|
|
|
158
|
-
def _draw_rounded_button(self, text, bg, fg, radius, command, nav_target, comp=None, border_color=None):
|
|
172
|
+
def _draw_rounded_button(self, text, bg, fg, radius, command, nav_target, comp=None, border_color=None, border_width=0, pressed_color=None):
|
|
159
173
|
fill_color = bg if bg else "#d9d9d9"
|
|
174
|
+
p_color = pressed_color if pressed_color else fill_color
|
|
160
175
|
text_color = fg if fg else "black"
|
|
161
176
|
outline_c = border_color if border_color else ""
|
|
162
|
-
outline_w = 1 if outline_c else 0
|
|
177
|
+
outline_w = border_width if border_width > 0 else (1 if outline_c else 0)
|
|
163
178
|
|
|
164
179
|
frame = tk.Frame(self.layout, bg=self.layout.cget("bg"))
|
|
165
180
|
frame.pack(pady=10, fill="x")
|
|
@@ -172,16 +187,22 @@ class AppPreview:
|
|
|
172
187
|
|
|
173
188
|
def _redraw(event, c=canvas, t=text, fc=fill_color, tc=text_color, r=radius, ol=outline_c, ow=outline_w):
|
|
174
189
|
c.delete("all")
|
|
175
|
-
w, h =
|
|
190
|
+
w, h = c.winfo_width(), c.winfo_height()
|
|
191
|
+
if w <= 1: w, h = event.width, event.height
|
|
176
192
|
_create_rounded_rect(c, 2, 2, w - 2, h - 2, r, fill=fc, outline=ol, width=ow)
|
|
177
193
|
c.create_text(w // 2, h // 2, text=t, fill=tc, font=("Arial", 12, "bold"))
|
|
178
194
|
|
|
179
|
-
def
|
|
180
|
-
|
|
181
|
-
|
|
195
|
+
def _on_press(event, c=canvas, t=text, fc=p_color, tc=text_color, r=radius, ol=outline_c, ow=outline_w):
|
|
196
|
+
_redraw(event, c, t, fc, tc, r, ol, ow)
|
|
197
|
+
|
|
198
|
+
def _on_release(event, c=canvas, t=text, fc=fill_color, tc=text_color, r=radius, ol=outline_c, ow=outline_w):
|
|
199
|
+
_redraw(event, c, t, fc, tc, r, ol, ow)
|
|
200
|
+
if command: command()
|
|
201
|
+
if nav_target: self.render_screen(nav_target)
|
|
182
202
|
|
|
183
203
|
canvas.bind("<Configure>", _redraw)
|
|
184
|
-
canvas.bind("<
|
|
204
|
+
canvas.bind("<ButtonPress-1>", _on_press)
|
|
205
|
+
canvas.bind("<ButtonRelease-1>", _on_release)
|
|
185
206
|
|
|
186
207
|
if comp is not None: comp.widget = canvas
|
|
187
208
|
return canvas
|
|
@@ -192,17 +213,20 @@ class AppPreview:
|
|
|
192
213
|
style = global_styles.get(comp_id, {}) if comp_id else {}
|
|
193
214
|
bg = _get_style_bg(style)
|
|
194
215
|
fg = _get_style_fg(style)
|
|
216
|
+
pressed_bg = _get_style_pressed_color(style)
|
|
195
217
|
radius = _parse_radius(style)
|
|
196
218
|
bc = _get_style_border_color(style)
|
|
219
|
+
bw = _parse_border_width(style)
|
|
197
220
|
user_cmd = comp.kwargs.get('command')
|
|
198
221
|
nav_target = comp.nav_target
|
|
199
222
|
|
|
200
223
|
if radius > 0:
|
|
201
|
-
self._draw_rounded_button(text, bg, fg, radius, user_cmd, nav_target, comp, bc)
|
|
224
|
+
self._draw_rounded_button(text, bg, fg, radius, user_cmd, nav_target, comp, bc, bw, pressed_color=pressed_bg)
|
|
202
225
|
return
|
|
203
226
|
|
|
204
227
|
tk_bg = bg if bg else "SystemButtonFace"
|
|
205
228
|
tk_fg = fg if fg else "black"
|
|
229
|
+
tk_abg = pressed_bg if pressed_bg else "SystemButtonHighlight"
|
|
206
230
|
|
|
207
231
|
def on_click(nt=nav_target, uc=user_cmd):
|
|
208
232
|
if uc: uc()
|
|
@@ -210,7 +234,7 @@ class AppPreview:
|
|
|
210
234
|
|
|
211
235
|
btn = tk.Button(
|
|
212
236
|
self.layout, text=text, font=("Arial", 12), command=on_click,
|
|
213
|
-
bg=tk_bg, fg=tk_fg, relief="raised", pady=5
|
|
237
|
+
bg=tk_bg, fg=tk_fg, activebackground=tk_abg, relief="raised", pady=5
|
|
214
238
|
)
|
|
215
239
|
btn.pack(pady=10, fill="x")
|
|
216
240
|
comp.widget = btn
|
|
@@ -225,7 +249,8 @@ class AppPreview:
|
|
|
225
249
|
bg=_get_style_bg(style),
|
|
226
250
|
fg=_get_style_fg(style),
|
|
227
251
|
radius=_parse_radius(style),
|
|
228
|
-
border_color=_get_style_border_color(style)
|
|
252
|
+
border_color=_get_style_border_color(style),
|
|
253
|
+
border_width=_parse_border_width(style)
|
|
229
254
|
)
|
|
230
255
|
|
|
231
256
|
# ── Legacy (sem Screen) ───────────────────────────────────────────────────
|
|
@@ -241,21 +266,22 @@ class AppPreview:
|
|
|
241
266
|
)
|
|
242
267
|
lbl.pack(pady=4, fill="x")
|
|
243
268
|
|
|
244
|
-
def add_button(self, text, command=None, bg=None, fg=None, radius=0, border_color=None):
|
|
269
|
+
def add_button(self, text, command=None, bg=None, fg=None, radius=0, border_color=None, border_width=0, pressed_color=None):
|
|
245
270
|
if radius > 0:
|
|
246
|
-
self._draw_rounded_button(text, bg, fg, radius, command, None, border_color=border_color)
|
|
271
|
+
self._draw_rounded_button(text, bg, fg, radius, command, None, border_color=border_color, border_width=border_width, pressed_color=pressed_color)
|
|
247
272
|
return
|
|
248
273
|
tk_bg = bg if bg else "SystemButtonFace"
|
|
249
274
|
tk_fg = fg if fg else "black"
|
|
275
|
+
tk_abg = pressed_color if pressed_color else "SystemButtonHighlight"
|
|
250
276
|
btn = tk.Button(
|
|
251
277
|
self.layout, text=text, font=("Arial", 12), command=command,
|
|
252
|
-
bg=tk_bg, fg=tk_fg, relief="raised", pady=5
|
|
278
|
+
bg=tk_bg, fg=tk_fg, activebackground=tk_abg, relief="raised", pady=5
|
|
253
279
|
)
|
|
254
280
|
if border_color and border_color != "":
|
|
255
|
-
btn.config(highlightbackground=border_color, highlightthickness=1)
|
|
281
|
+
btn.config(highlightbackground=border_color, highlightthickness=border_width if border_width > 0 else 1)
|
|
256
282
|
btn.pack(pady=10, fill="x")
|
|
257
283
|
|
|
258
|
-
def add_input(self, placeholder="", input_type="text", bg=None, fg=None, radius=0, border_color=None):
|
|
284
|
+
def add_input(self, placeholder="", input_type="text", bg=None, fg=None, radius=0, border_color=None, border_width=0):
|
|
259
285
|
"""Adiciona um campo de input consoante o tipo pedido."""
|
|
260
286
|
|
|
261
287
|
# ── TEXT / PASSWORD / SEARCH ──────────────────────────────────────────
|
|
@@ -268,7 +294,7 @@ class AppPreview:
|
|
|
268
294
|
show_char = "*" if input_type == "password" else ""
|
|
269
295
|
|
|
270
296
|
ol_color = border_color if border_color is not None else "grey"
|
|
271
|
-
ol_width = 1 if ol_color else 0
|
|
297
|
+
ol_width = border_width if border_width > 0 else 1 if ol_color else 0
|
|
272
298
|
|
|
273
299
|
if radius > 0:
|
|
274
300
|
canvas = tk.Canvas(
|
|
@@ -447,9 +473,11 @@ def button(text, id=None, command=None, screen=None):
|
|
|
447
473
|
style = global_styles.get(id, {})
|
|
448
474
|
bg = _get_style_bg(style)
|
|
449
475
|
fg = _get_style_fg(style)
|
|
476
|
+
pressed_bg = _get_style_pressed_color(style)
|
|
450
477
|
radius = _parse_radius(style)
|
|
451
478
|
bc = _get_style_border_color(style)
|
|
452
|
-
|
|
479
|
+
bw = _parse_border_width(style)
|
|
480
|
+
_app.add_button(text, command, bg, fg, radius=radius, border_color=bc, border_width=bw, pressed_color=pressed_bg)
|
|
453
481
|
return None
|
|
454
482
|
|
|
455
483
|
|
|
@@ -483,7 +511,8 @@ def inputs(placeholder="", id=None, type="text", screen=None):
|
|
|
483
511
|
fg = _get_style_fg(style)
|
|
484
512
|
radius = _parse_radius(style)
|
|
485
513
|
bc = _get_style_border_color(style)
|
|
486
|
-
|
|
514
|
+
bw = _parse_border_width(style)
|
|
515
|
+
_app.add_input(placeholder=placeholder, input_type=type, bg=bg, fg=fg, radius=radius, border_color=bc, border_width=bw)
|
|
487
516
|
return None
|
|
488
517
|
|
|
489
518
|
|
|
@@ -503,4 +532,13 @@ def run(start_screen=None):
|
|
|
503
532
|
load_styles_now()
|
|
504
533
|
if start_screen is not None:
|
|
505
534
|
_app.render_screen(start_screen)
|
|
506
|
-
_app.run()
|
|
535
|
+
_app.run()
|
|
536
|
+
|
|
537
|
+
def toast(message):
|
|
538
|
+
"""Exibe uma mensagem flutuante temporária na interface."""
|
|
539
|
+
lbl = tk.Label(
|
|
540
|
+
_app.root, text=message, bg="#333333", fg="white",
|
|
541
|
+
font=("Arial", 11), padx=15, pady=8
|
|
542
|
+
)
|
|
543
|
+
lbl.place(relx=0.5, rely=0.85, anchor="s")
|
|
544
|
+
_app.root.after(2000, lbl.destroy)
|
|
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name='apkpy',
|
|
5
|
-
version='0.2.
|
|
5
|
+
version='0.2.3', # GARANTE que está 0.1.1
|
|
6
6
|
author='Martim',
|
|
7
7
|
description='A simple framework to build Android apps using Python and CSS-like styling',
|
|
8
8
|
long_description=open('README.md', encoding='utf-8').read(), # ADICIONA O ENCODING AQUI
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{apkpy-0.2.2 → apkpy-0.2.3}/apkpy_lib/templates/android_project/app/src/main/AndroidManifest.xml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|