Skip to content

Commit b3c6732

Browse files
committed
test: for flet app
1 parent 0e1e4ad commit b3c6732

4 files changed

Lines changed: 333 additions & 0 deletions

File tree

31.5 KB
Loading
29.1 KB
Loading

docs/tests/flet/src/core.py

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
""" Non-Advanced Stuff """
2+
import random
3+
import os
4+
ON_ANDROID = False
5+
6+
def on_flet_app():
7+
return os.getenv("MAIN_ACTIVITY_HOST_CLASS_NAME")
8+
9+
def get_activity_class_name():
10+
ACTIVITY_CLASS_NAME = os.getenv("MAIN_ACTIVITY_HOST_CLASS_NAME") # flet python
11+
if not ACTIVITY_CLASS_NAME:
12+
try:
13+
from android import config
14+
ACTIVITY_CLASS_NAME = getattr(config, "JAVA_NAMESPACE", None)
15+
except (ImportError, AttributeError):
16+
ACTIVITY_CLASS_NAME = 'org.kivy.android'
17+
return ACTIVITY_CLASS_NAME
18+
19+
try:
20+
21+
from jnius import autoclass # Needs Java to be installed
22+
# Get the required Java classes
23+
ACTIVITY_CLASS_NAME = get_activity_class_name()
24+
PythonActivity = autoclass(ACTIVITY_CLASS_NAME)
25+
context = PythonActivity.mActivity # Get the app's context
26+
NotificationChannel = autoclass('android.app.NotificationChannel')
27+
String = autoclass('java.lang.String')
28+
Intent = autoclass('android.content.Intent')
29+
PendingIntent = autoclass('android.app.PendingIntent')
30+
BitmapFactory = autoclass('android.graphics.BitmapFactory')
31+
BuildVersion = autoclass('android.os.Build$VERSION')
32+
ON_ANDROID=True
33+
except Exception as e:
34+
print('\nThis Package Only Runs on Android !!! ---> Check "https://github.com/Fector101/android_notify/" to see design patterns and more info.\n')
35+
36+
if ON_ANDROID:
37+
try:
38+
NotificationManagerCompat = autoclass('androidx.core.app.NotificationManagerCompat')
39+
NotificationCompat = autoclass('androidx.core.app.NotificationCompat')
40+
41+
# Notification Design
42+
NotificationCompatBuilder = autoclass('androidx.core.app.NotificationCompat$Builder')
43+
NotificationCompatBigTextStyle = autoclass('androidx.core.app.NotificationCompat$BigTextStyle')
44+
NotificationCompatBigPictureStyle = autoclass('androidx.core.app.NotificationCompat$BigPictureStyle')
45+
NotificationCompatInboxStyle = autoclass('androidx.core.app.NotificationCompat$InboxStyle')
46+
except Exception as e:
47+
print("""\n
48+
Dependency Error: Add the following in buildozer.spec:
49+
* android.gradle_dependencies = androidx.core:core-ktx:1.15.0, androidx.core:core:1.6.0
50+
* android.enable_androidx = True
51+
* android.permissions = POST_NOTIFICATIONS\n
52+
""")
53+
54+
55+
def get_app_root_path():
56+
if on_flet_app():
57+
return os.path.join(context.getFilesDir().getAbsolutePath(),'flet')
58+
else:
59+
from android.storage import app_storage_path # type: ignore
60+
return app_storage_path()
61+
62+
def asks_permission_if_needed():
63+
"""
64+
Ask for permission to send notifications if needed.
65+
"""
66+
if on_flet_app():
67+
VERSION_CODES = autoclass('android.os.Build$VERSION_CODES')
68+
ContextCompat = autoclass('androidx.core.content.ContextCompat')
69+
# if you get error `Failed to find class: androidx/core/app/ActivityCompat`
70+
#in proguard-rules.pro add `-keep class androidx.core.app.ActivityCompat { *; }`
71+
ActivityCompat = autoclass('androidx.core.app.ActivityCompat')
72+
Manifest = autoclass('android.Manifest$permission')
73+
VERSION_CODES = autoclass('android.os.Build$VERSION_CODES')
74+
75+
if BuildVersion.SDK_INT >= VERSION_CODES.TIRAMISU:
76+
permission = Manifest.POST_NOTIFICATIONS
77+
granted = ContextCompat.checkSelfPermission(context, permission)
78+
79+
if granted != 0: # PackageManager.PERMISSION_GRANTED == 0
80+
ActivityCompat.requestPermissions(context, [permission], 101)
81+
else: # android package is from p4a which is for kivy
82+
try:
83+
from android.permissions import request_permissions, Permission,check_permission # type: ignore
84+
permissions=[Permission.POST_NOTIFICATIONS]
85+
if not all(check_permission(p) for p in permissions):
86+
request_permissions(permissions)
87+
except Exception as e:
88+
print("android_notify- error trying to request notification access: ", e)
89+
90+
def get_image_uri(relative_path):
91+
"""
92+
Get the absolute URI for an image in the assets folder.
93+
:param relative_path: The relative path to the image (e.g., 'assets/imgs/icon.png').
94+
:return: Absolute URI java Object (e.g., 'file:///path/to/file.png').
95+
"""
96+
app_root_path = get_app_root_path()
97+
output_path = os.path.join(app_root_path,'app', relative_path)
98+
# print(output_path,'output_path') # /data/user/0/org.laner.lan_ft/files/app/assets/imgs/icon.png
99+
100+
if not os.path.exists(output_path):
101+
raise FileNotFoundError(f"\nImage not found at path: {output_path}\n")
102+
103+
Uri = autoclass('android.net.Uri')
104+
return Uri.parse(f"file://{output_path}")
105+
106+
def get_icon_object(uri):
107+
BitmapFactory = autoclass('android.graphics.BitmapFactory')
108+
IconCompat = autoclass('androidx.core.graphics.drawable.IconCompat')
109+
110+
bitmap= BitmapFactory.decodeStream(context.getContentResolver().openInputStream(uri))
111+
return IconCompat.createWithBitmap(bitmap)
112+
113+
def insert_app_icon(builder,custom_icon_path):
114+
if custom_icon_path:
115+
try:
116+
uri = get_image_uri(custom_icon_path)
117+
icon = get_icon_object(uri)
118+
builder.setSmallIcon(icon)
119+
except Exception as e:
120+
print('android_notify- error: ',e)
121+
builder.setSmallIcon(context.getApplicationInfo().icon)
122+
else:
123+
print('Found res icon -->',context.getApplicationInfo().icon,'<--')
124+
builder.setSmallIcon(context.getApplicationInfo().icon)
125+
126+
def send_notification(
127+
title:str,
128+
message:str,
129+
style=None,
130+
img_path=None,
131+
channel_name="Default Channel",
132+
channel_id:str="default_channel",
133+
custom_app_icon_path="",
134+
135+
big_picture_path='',
136+
large_icon_path='',
137+
big_text="",
138+
lines=""
139+
):
140+
"""
141+
Send a notification on Android.
142+
143+
:param title: Title of the notification.
144+
:param message: Message body.
145+
:param style: Style of the notification ('big_text', 'big_picture', 'inbox', 'large_icon').
146+
:param img_path: Path to the image resource.
147+
:param channel_id: Notification channel ID.(Default is lowercase channel name arg in lowercase)
148+
"""
149+
if not ON_ANDROID:
150+
print('This Package Only Runs on Android !!! ---> Check "https://github.com/Fector101/android_notify/" for Documentation.')
151+
return
152+
153+
asks_permission_if_needed()
154+
channel_id=channel_name.replace(' ','_').lower().lower() if not channel_id else channel_id
155+
# Get notification manager
156+
notification_manager = context.getSystemService(context.NOTIFICATION_SERVICE)
157+
158+
# importance= autoclass('android.app.NotificationManager').IMPORTANCE_HIGH # also works #NotificationManager.IMPORTANCE_DEFAULT
159+
importance= NotificationManagerCompat.IMPORTANCE_HIGH #autoclass('android.app.NotificationManager').IMPORTANCE_HIGH also works #NotificationManager.IMPORTANCE_DEFAULT
160+
161+
# Notification Channel (Required for Android 8.0+)
162+
if BuildVersion.SDK_INT >= 26:
163+
channel = NotificationChannel(channel_id, channel_name,importance)
164+
notification_manager.createNotificationChannel(channel)
165+
166+
# Build the notification
167+
builder = NotificationCompatBuilder(context, channel_id)
168+
builder.setContentTitle(title)
169+
builder.setContentText(message)
170+
insert_app_icon(builder,custom_app_icon_path)
171+
builder.setDefaults(NotificationCompat.DEFAULT_ALL)
172+
builder.setPriority(NotificationCompat.PRIORITY_HIGH)
173+
174+
if img_path:
175+
print('android_notify- img_path arg deprecated use "large_icon_path or big_picture_path or custom_app_icon_path"instead ')
176+
177+
big_picture = None
178+
if big_picture_path:
179+
try:
180+
big_picture = get_image_uri(big_picture_path)
181+
except FileNotFoundError as e:
182+
print('android_notify- Error Getting Uri for big_picture_path: ',e)
183+
184+
large_icon = None
185+
if large_icon_path:
186+
try:
187+
large_icon = get_image_uri(large_icon_path)
188+
except FileNotFoundError as e:
189+
print('android_notify- Error Getting Uri for large_icon_path: ',e)
190+
191+
192+
# Apply notification styles
193+
try:
194+
if big_text:
195+
big_text_style = NotificationCompatBigTextStyle()
196+
big_text_style.bigText(big_text)
197+
builder.setStyle(big_text_style)
198+
199+
elif lines:
200+
inbox_style = NotificationCompatInboxStyle()
201+
for line in lines.split("\n"):
202+
inbox_style.addLine(line)
203+
builder.setStyle(inbox_style)
204+
205+
if large_icon:
206+
bitmap = BitmapFactory.decodeStream(context.getContentResolver().openInputStream(large_icon))
207+
builder.setLargeIcon(bitmap)
208+
209+
if big_picture:
210+
bitmap = BitmapFactory.decodeStream(context.getContentResolver().openInputStream(big_picture))
211+
builder.setLargeIcon(bitmap)
212+
big_picture_style = NotificationCompatBigPictureStyle().bigPicture(bitmap)
213+
builder.setStyle(big_picture_style)
214+
215+
except Exception as e:
216+
print('android_notify- Error Failed Adding Style: ',e)
217+
# Display the notification
218+
notification_id = random.randint(0, 100)
219+
notification_manager.notify(notification_id, builder.build())
220+
return notification_id
221+

docs/tests/flet/src/main.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import flet as ft
2+
import os,traceback
3+
4+
try:
5+
from core import send_notification
6+
except Exception as e:
7+
print("Not on android")
8+
print("Error importing android_notify: {}".format(e))
9+
10+
md1 = """ """
11+
i=0
12+
13+
def main(page: ft.Page):
14+
page.scroll = ft.ScrollMode.ADAPTIVE
15+
mdObj=ft.Markdown(
16+
md1,
17+
selectable=True,
18+
extension_set=ft.MarkdownExtensionSet.GITHUB_WEB,
19+
on_tap_link=lambda e: page.launch_url(e.data),
20+
)
21+
def handle_click(style):
22+
if style == "big_text":
23+
send_notification(
24+
title="Big Text Example",
25+
message="This uses the BigTextStyle!",
26+
style="big_text",
27+
big_text="This is an expanded notification showing how much text can fit here."
28+
)
29+
elif style == "big_picture":
30+
send_notification(
31+
title="Big Picture Example",
32+
message="This uses the BigPictureStyle!",
33+
big_picture_path="assets/splash_android.png"
34+
)
35+
elif style == "large_icon":
36+
send_notification(
37+
title="Large Icon Example",
38+
message="This uses a Large Icon!",
39+
large_icon_path="assets/icon.png"
40+
)
41+
elif style == "both_imgs":
42+
send_notification(
43+
title="Both 'Large Icon' and 'Big Picture' Example",
44+
message="This uses a Both Imgs!",
45+
big_picture_path="assets/splash_android.png",
46+
large_icon_path="assets/icon.png"
47+
)
48+
elif style == "inbox":
49+
send_notification(
50+
title="Inbox Example",
51+
message="This uses the InboxStyle!",
52+
lines="Line 1\nLine 2\nLine 3"
53+
)
54+
else:
55+
send_notification(
56+
title="Simple Notification",
57+
message="This is a normal notification!"
58+
)
59+
60+
page.snack_bar = ft.SnackBar(ft.Text(f"Sent: {style or 'default'}"))
61+
page.snack_bar.open = True
62+
page.update()
63+
64+
def console(a):
65+
global md1,i
66+
i+=1
67+
print('Testing print visibility: ',i,'\n')
68+
try:
69+
with open(os.getenv("FLET_APP_CONSOLE"), "r") as f:
70+
md1 = f.read()
71+
mdObj.value=md1
72+
except Exception as e:
73+
mdObj.value=f"{e}, hello readddd me i'm Error"
74+
finally:
75+
mdObj.update()
76+
77+
78+
def send_basic(e):
79+
send_notification(title='Hello World',message='From android_notify',custom_app_icon_path=f'assets/icon.png')
80+
81+
def request_permission(e):
82+
try:
83+
from core import asks_permission_if_needed
84+
asks_permission_if_needed()
85+
except Exception as e:
86+
print( f"pyjnius android_ import error: {traceback.format_exc()}" )
87+
88+
page.add(
89+
ft.OutlinedButton(
90+
"Request Permission if Needed",
91+
on_click=request_permission,
92+
), ft.OutlinedButton(
93+
"Refresh Prints",
94+
on_click=console,
95+
)
96+
)
97+
page.add(
98+
ft.Text("🧪 Android Notify Test", size=25, weight=ft.FontWeight.BOLD),
99+
ft.Text("Click a button below to test the corresponding style.\n", size=16),
100+
101+
ft.ElevatedButton("🔔 Simple Notification", on_click=lambda e: handle_click(None)),
102+
ft.ElevatedButton("🖼️ Big Picture Style", on_click=lambda e: handle_click("big_picture")),
103+
ft.ElevatedButton("🧩 Large Icon Style", on_click=lambda e: handle_click("large_icon")),
104+
ft.ElevatedButton("# Both imgs", on_click=lambda e: handle_click("both_imgs")),
105+
ft.ElevatedButton("📝 Big Text Style", on_click=lambda e: handle_click("big_text")),
106+
ft.ElevatedButton("📋 Inbox Style", on_click=lambda e: handle_click("inbox")),
107+
)
108+
btn = ft.ElevatedButton("Click me!", on_click=send_basic)
109+
page.add(btn)
110+
page.add(mdObj)
111+
ft.app(main)
112+

0 commit comments

Comments
 (0)