android-notify 1.3__py3-none-any.whl → 1.60.6.dev0__py3-none-any.whl

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.

Potentially problematic release.


This version of android-notify might be problematic. Click here for more details.

@@ -0,0 +1,171 @@
1
+ Metadata-Version: 2.4
2
+ Name: android-notify
3
+ Version: 1.60.6.dev0
4
+ Summary: A Python package that simplifies creating Android notifications in Kivy and Flet apps.
5
+ Author-email: Fabian <fector101@yahoo.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://android-notify.vercel.app
8
+ Project-URL: Documentation, https://android-notify.vercel.app/
9
+ Project-URL: Source, https://github.com/fector101/android_notify
10
+ Project-URL: Tracker, https://github.com/fector101/android_notify/issues
11
+ Project-URL: Funding, https://www.buymeacoffee.com/fector101
12
+ Keywords: android,notifications,kivy,mobile,post-notifications,pyjnius,android-notifications,kivy-notifications,python-android,mobile-development,push-notifications,mobile-app,kivy-application
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Operating System :: Android
15
+ Classifier: Development Status :: 5 - Production/Stable
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Requires-Python: >=3.6
19
+ Description-Content-Type: text/markdown
20
+ Requires-Dist: pyjnius>=1.4.2
21
+ Provides-Extra: dev
22
+ Requires-Dist: kivy>=2.0.0; extra == "dev"
23
+
24
+ <div align="center">
25
+ <br>
26
+ <h1> Android-Notify </h1>
27
+ <p><a href='https://android-notify.vercel.app'>Android Notify</a> is a Python library for effortlessly creating and managing Android notifications in Kivy and Flet apps.</p>
28
+ <p>Supports various styles and ensures seamless integration, customization and Pythonic APIs.</p>
29
+ <!-- <br> -->
30
+ <!-- <img src="https://raw.githubusercontent.com/Fector101/android_notify/main/docs/imgs/democollage.jpg"> -->
31
+ </div>
32
+ <!-- Channel [CRUD]
33
+ The Android Notify package provides a simple yet comprehensive way to create and manage rich notifications on Android devices directly from your Python code. This library bridges the gap between Python and Android's notification system, giving you full control over notifications with a clean, Pythonic API. -->
34
+
35
+ ## Features
36
+
37
+ - **Multiple Notification Styles**: Support for various notification styles including:
38
+ - Simple text notifications
39
+ - [Progress bar notifications](https://android-notify.vercel.app/components#progress-bars) (determinate and indeterminate)
40
+ - Large icon notifications
41
+ - Big picture notifications
42
+ - Combined image styles
43
+ - Custom notification Icon - [images section](https://android-notify.vercel.app/components#images)
44
+ - Big text notifications
45
+ - Inbox-style notifications
46
+ - Colored texts and Icons
47
+
48
+ - **Rich Functionality**:
49
+ - Add action buttons with custom callbacks
50
+ - [Update notification](https://android-notify.vercel.app/advanced-methods#updating-notification) content dynamically
51
+ - Manage progress bars with fine-grained control
52
+ - [Custom notification channels](https://android-notify.vercel.app/advanced-methods#channel-management) for Android 8.0+ (Creating and Deleting)
53
+ - Silent notifications
54
+ - Persistent notifications
55
+ - Click handlers and callbacks
56
+ - Cancel Notifications
57
+
58
+ ## Quick Start
59
+
60
+ ```python
61
+ from android_notify import Notification
62
+
63
+ # Simple notification
64
+ Notification(
65
+ title="Hello",
66
+ message="This is a basic notification."
67
+ ).send()
68
+
69
+ ```
70
+
71
+ **Sample Image:**
72
+ ![basic notification img sample](https://raw.githubusercontent.com/Fector101/android_notify/main/docs/imgs/basicnoti.jpg)
73
+
74
+ ## Installation
75
+
76
+ ### Kivy apps:
77
+
78
+ In your **`buildozer.spec`** file, ensure you include the following:
79
+
80
+ ```ini
81
+ # Add pyjnius so ensure it's packaged with the build
82
+ requirements = python3, kivy, pyjnius, android-notify>=1.60.6.dev0
83
+ # Add permission for notifications
84
+ android.permissions = POST_NOTIFICATIONS
85
+ ```
86
+
87
+ ### Flet apps:
88
+
89
+ In your `pyproject.toml` file, ensure you include the following:
90
+
91
+ ```toml
92
+ [tool.flet.android]
93
+ dependencies = [
94
+ "pyjnius","android-notify>=1.60.6.dev0"
95
+ ]
96
+
97
+ [tool.flet.android.permission]
98
+ "android.permission.POST_NOTIFICATIONS" = true
99
+ ```
100
+
101
+ ### Pydroid 3
102
+ In the [pydroid 3](https://play.google.com/store/apps/details?id=ru.iiec.pydroid3) mobile app for running python code you can test some features.
103
+ - In pip section where you're asked to insert `Libary name` paste `android-notify>=1.60.6.dev0`
104
+
105
+
106
+ ### Testing
107
+ Can be installed via `pip` For testing purposes:
108
+
109
+ ```bash
110
+ pip install android_notify
111
+ android-notify -v
112
+ ```
113
+
114
+ ## Documentation
115
+ For Dev Version use
116
+ ```requirements = python3, kivy, pyjnius, https://github.com/Fector101/android_notify/archive/without-androidx.zip```
117
+
118
+ ### To talk to BroadCast Listener From Buttons
119
+
120
+ - Make things happen without being in your app
121
+ ```python
122
+
123
+ from android_notify import Notification
124
+ notification = Notification(title="Reciver Notification")
125
+ notification.addButton(text="Stop", receiver_name="CarouselReceiver", action="ACTION_STOP")
126
+ notification.addButton(text="Skip", receiver_name="CarouselReceiver", action="ACTION_SKIP")
127
+ ```
128
+ You can use this [wiki](https://github.com/Fector101/android_notify/wiki/How-to#use-with-broadcast-listener-in-kivy) as a guide create a broadcast listener
129
+
130
+ ### To use colored text in your notifications
131
+
132
+ - Copy the [res](https://github.com/Fector101/android_notify/tree/main/android_notify/res) folder to your app path.
133
+ Lastly in your `buildozer.spec` file
134
+ - Add `source.include_exts = xml` and `android.add_resources = ./res`
135
+
136
+
137
+ ### To use Custom Sounds
138
+
139
+ - Put audio files in `res/raw` folder,
140
+ - Then from `buildozer.spec` point to res folder `android.add_resources = res`
141
+ - and includes it's format `source.include_exts = wav`.
142
+
143
+ Lastly From the code
144
+ ```py
145
+ # Create a custom notification channel with a unique sound resource for android 8+
146
+ Notification.createChannel(
147
+ id="weird_sound_tester",
148
+ name="Weird Sound Tester",
149
+ description="A test channel used to verify custom notification sounds from the res/raw folder.",
150
+ res_sound_name="sneeze" # file name without .wav or .mp3
151
+ )
152
+
153
+ # Send a notification through the created channel
154
+ n=Notification(
155
+ title="Custom Sound Notification",
156
+ message="This tests playback of a custom sound (sneeze.wav) stored in res/raw.",
157
+ channel_id="weird_sound_tester" # important tells notification to use right channel
158
+ )
159
+ n.setSound("sneeze")# for android 7 below
160
+ n.send()
161
+ ```
162
+
163
+ ### For full documentation, examples, and advanced usage, API reference visit the [documentation](https://android-notify.vercel.app)
164
+
165
+ ## ☕ Support the Project
166
+
167
+ If you find this project helpful, consider buying me a coffee! 😊 Or Giving it a star on 🌟 [GitHub](https://github.com/Fector101/android_notify/) Your support helps maintain and improve the project.
168
+
169
+ <a href="https://www.buymeacoffee.com/fector101" target="_blank">
170
+ <img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" height="60">
171
+ </a>
@@ -0,0 +1,24 @@
1
+ android_notify/__init__.py,sha256=lcLjyfegXgU7cyGhfSphAOBipXwemrVkdYy3mcF6X5Y,172
2
+ android_notify/__main__.py,sha256=TqZBjgW5wO61QvCpZje-YDKhBehtxXJSa71aM0RHom8,684
3
+ android_notify/an_types.py,sha256=8qFBx4qLOz_Z2VxVWW3BxBx3jOj0DGoJ6T8dxnkcTB0,10428
4
+ android_notify/an_utils.py,sha256=1GD9E8a3zVAA7ThttGQbqZQj333e5I44WqY4uyLDaxs,7448
5
+ android_notify/base.py,sha256=6HKFseaVwPgxk1BKSocUIN-x-uYFW6IbhUJyb2mPoBE,3709
6
+ android_notify/config.py,sha256=VN1ixPL4p7qDCD4u1oyI3sQEvWaifWp8hUgFHHhns6M,5384
7
+ android_notify/core.py,sha256=3tHMKBwkGR8QKOJNu1feN9l9hKXnDlfY9ao7LCKMnFk,9133
8
+ android_notify/styles.py,sha256=-8ueetUAec1xCXkMV2dF8Ywep_bu-5hiT9_eyh5TTyU,899
9
+ android_notify/sword.py,sha256=Z_N3d-4gfy09v4n67lYxjwR0j8EDXRXH0fVzj6T9plo,49496
10
+ android_notify/fallback-icons/flet-appicon.png,sha256=HLLHbB0MYu4Gimp6C7oe5QHK1frNKgRUHvht88JqBdE,32278
11
+ android_notify/fallback-icons/pydroid3-appicon.png,sha256=R54qUn-ohBLKxMauwQpkJtIPIkEFRA2AEdTAEdD3e9w,13403
12
+ docs/examples/flet-working/src/core.py,sha256=Pzhp6IhmHadvg_7zYhBBGz8araeVsuy44MVUVpW91cY,9261
13
+ docs/examples/flet-working/src/main.py,sha256=zTc6hOid6OBLiH9X1ha4MzvZE5qtVDjFFi7SJmfzsk4,1838
14
+ docs/tests/flet/adv/main.py,sha256=xVPB4HL1xD2aWUucemb8ha60z-rr9em_Kw-5oUPfp8s,3555
15
+ docs/tests/flet/adv/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ docs/tests/flet/adv/tests/test_android_notify_full.py,sha256=jlB0ME_F1HlcgIQeHQTMtj9fIVa80_SizHkIM5pUO44,7283
17
+ docs/tests/flet/basic/src/core.py,sha256=Pzhp6IhmHadvg_7zYhBBGz8araeVsuy44MVUVpW91cY,9261
18
+ docs/tests/flet/basic/src/main.py,sha256=nnBp3tSIeBUcinmeSc2q7aAYri_e1GyfljOrFzHzSVE,3993
19
+ docs/website/src/pages/data/laner_Sent.py,sha256=QQI8MxDO9X37qTIiVrydajoKE-MnRpmKeLPRsgJcfbs,609
20
+ android_notify-1.60.6.dev0.dist-info/METADATA,sha256=wYblJ6yOf-zGQdqbVEUMK4U-uBy8TJVbz79lC2C4X5k,6697
21
+ android_notify-1.60.6.dev0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
22
+ android_notify-1.60.6.dev0.dist-info/entry_points.txt,sha256=pMWHYogqxd-a8tXQ97tjyxay8sTpFO15J0YX1CYJe4U,64
23
+ android_notify-1.60.6.dev0.dist-info/top_level.txt,sha256=kH29wUCpnrTwPF-o2KxkgUGFCQ96BinxQ0j7LneHKnw,20
24
+ android_notify-1.60.6.dev0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.6.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ android-notify = android_notify.__main__:main
@@ -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
+
@@ -0,0 +1,68 @@
1
+ import flet as ft
2
+ import os,traceback
3
+
4
+
5
+ md1 = """ """
6
+ i=0
7
+
8
+ def main(page: ft.Page):
9
+ page.scroll = ft.ScrollMode.ADAPTIVE
10
+ mdObj=ft.Markdown(
11
+ md1,
12
+ selectable=True,
13
+ extension_set=ft.MarkdownExtensionSet.GITHUB_WEB,
14
+ on_tap_link=lambda e: page.launch_url(e.data),
15
+ )
16
+ def console(a):
17
+ global md1
18
+ try:
19
+ with open(os.getenv("FLET_APP_CONSOLE"), "r") as f:
20
+ md1 = f.read()
21
+ mdObj.value=md1
22
+ except Exception as e:
23
+ print(e,"hello readddd it's me")
24
+ mdObj.value=f"{e}, hello readddd it's me"
25
+ finally:
26
+ mdObj.update()
27
+
28
+ def send_basic(e):
29
+
30
+ try:
31
+ from core import send_notification
32
+ send_notification(title='Hello World',message='From android_notify',custom_app_icon_path=f'assets/icon.png')
33
+ except Exception as e:
34
+ print("Error importing android_notify: {}".format(e))
35
+
36
+ def log_stuff(e):
37
+ global i
38
+ i+=1
39
+ print('Testing print visiblity: ',i,'\n')
40
+ console(None)
41
+
42
+ def request_permission(e):
43
+ try:
44
+ from core import asks_permission_if_needed
45
+ asks_permission_if_needed()
46
+ except Exception as e:
47
+ print( f"pyjnius android_ import error: {traceback.format_exc()}" )
48
+
49
+
50
+
51
+
52
+ page.add(
53
+ ft.OutlinedButton(
54
+ "Request Permission if Needed",
55
+ on_click=request_permission,
56
+ ), ft.OutlinedButton(
57
+ "Refresh Prints",
58
+ on_click=console,
59
+ ), ft.OutlinedButton(
60
+ "Testing print visiblity",
61
+ on_click=log_stuff,
62
+ )
63
+ )
64
+ btn = ft.ElevatedButton("Click me!", on_click=send_basic)
65
+ page.add(btn)
66
+ page.add(mdObj)
67
+
68
+ ft.app(main)
@@ -0,0 +1,97 @@
1
+ import os,traceback,io,sys,unittest
2
+ import flet as ft
3
+ from contextlib import redirect_stdout
4
+ from android_notify.core import get_app_root_path,asks_permission_if_needed
5
+
6
+ md1=''
7
+ i=0
8
+ def main(page: ft.Page):
9
+ page.scroll = ft.ScrollMode.ADAPTIVE
10
+ page.add(ft.Text("1111111111 Android Notify Test Results", size=24, weight=ft.FontWeight.BOLD))
11
+ logs_path=os.path.join(get_app_root_path(),'last.txt')
12
+ mdObj = ft.Markdown(
13
+ md1,
14
+ selectable=True,
15
+ extension_set=ft.MarkdownExtensionSet.GITHUB_WEB,
16
+ on_tap_link=lambda e: page.launch_url(e.data),
17
+ )
18
+ page.add(mdObj)
19
+ def console(a):
20
+ global md1,i
21
+ i+=1
22
+ print('Testing print visibility: ',i,'\n')
23
+ try:
24
+ if os.getenv("FLET_APP_CONSOLE"):
25
+ with open(os.getenv("FLET_APP_CONSOLE"), "r") as f:
26
+ md1 = f.read()
27
+ mdObj.value = md1
28
+
29
+ with open(logs_path, 'r') as logf:
30
+ mdObj.value = logf.read() + md1
31
+ except Exception as err:
32
+ mdObj.value = f"Error reading log: {err}"
33
+ finally:
34
+ mdObj.update()
35
+
36
+
37
+ def send_basic(e):
38
+ """Send a notification to verify android_notify works"""
39
+ try:
40
+ from android_notify import Notification
41
+ Notification(title="Hello World", message="From android_notify").send()
42
+ except Exception as err:
43
+ mdObj.value = f"Notification error: {err}"
44
+ mdObj.update()
45
+ def asks_permission_if_needed_(e):
46
+ asks_permission_if_needed()
47
+
48
+
49
+ def ensure_tests_folder():
50
+ try:
51
+ base_path = get_app_root_path()
52
+ except Exception:
53
+ base_path = os.path.dirname(__file__)
54
+
55
+ tests_path = os.path.join(base_path, "tests")
56
+ os.makedirs(tests_path, exist_ok=True)
57
+ init_file = os.path.join(tests_path, "__init__.py")
58
+ if not os.path.exists(init_file):
59
+ open(init_file, "w").close()
60
+
61
+ page.add(ft.Text(f"{tests_path}", size=24, weight=ft.FontWeight.BOLD))
62
+ return tests_path
63
+
64
+ page.add(ft.Text("2222222222 Android Notify Test Results", size=24, weight=ft.FontWeight.BOLD))
65
+
66
+ def run_tests(e=None):
67
+ """Run tests and log results to /sdcard/flet_app_console.txt"""
68
+ tests_path = ensure_tests_folder()
69
+
70
+ try:
71
+ with open(logs_path, "w") as logf, redirect_stdout(logf):
72
+ loader = unittest.TestLoader()
73
+ suite = loader.discover(start_dir=tests_path, pattern="test_*.py")
74
+ print("Discovered tests:",suite.countTestCases())
75
+ if suite.countTestCases() ==0:
76
+ print("No tests found")
77
+
78
+
79
+ runner = unittest.TextTestRunner(stream=logf, verbosity=2)
80
+ runner.run(suite)
81
+
82
+ mdObj.value = f"Tests complete. Log saved to:\n`{logs_path}`"
83
+ except Exception as err:
84
+ mdObj.value = f"Test error:\n{traceback.format_exc()}"
85
+ mdObj.update()
86
+ def has_per(e=None):
87
+ from android_notify import NotificationHandler
88
+ print('permmm:',NotificationHandler.has_permission())
89
+ page.add(
90
+ ft.OutlinedButton("🔔 Send Basic Notification", on_click=send_basic),
91
+ ft.OutlinedButton("🔁 Refresh Prints", on_click=console),
92
+ ft.OutlinedButton("🧪 Run Tests", on_click=run_tests),
93
+ ft.OutlinedButton("🧪permmm", on_click=has_per),
94
+ ft.OutlinedButton("🧪asks_permission_if_needed", on_click=asks_permission_if_needed_),
95
+ )
96
+
97
+ ft.app(main)
File without changes