android-notify 1.2__py3-none-any.whl → 1.5__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,4 @@
1
+ """"For Easier Imports For Public Classes"""
2
+ from .core import send_notification
3
+ from .styles import NotificationStyles
4
+ from .sword import Notification,NotificationHandler
android_notify/core.py CHANGED
@@ -1,10 +1,10 @@
1
-
2
- from jnius import autoclass,cast
1
+ """ Non-Advanced Stuff """
3
2
  import random
4
3
  import os
5
4
 
6
5
  ON_ANDROID = False
7
6
  try:
7
+ from jnius import autoclass,cast # Needs Java to be installed
8
8
  # Get the required Java classes
9
9
  PythonActivity = autoclass('org.kivy.android.PythonActivity')
10
10
  NotificationChannel = autoclass('android.app.NotificationChannel')
@@ -20,13 +20,12 @@ except Exception as e:
20
20
 
21
21
  if ON_ANDROID:
22
22
  try:
23
- # NotificationManagerCompat = autoclass('androidx.core.app.NotificationManagerCompat')
23
+ NotificationManagerCompat = autoclass('androidx.core.app.NotificationManagerCompat')
24
24
  NotificationCompat = autoclass('androidx.core.app.NotificationCompat')
25
25
 
26
26
  # Notification Design
27
27
  NotificationCompatBuilder = autoclass('androidx.core.app.NotificationCompat$Builder')
28
28
  NotificationCompatBigTextStyle = autoclass('androidx.core.app.NotificationCompat$BigTextStyle')
29
- # NotificationCompatBigTextStyle = autoclass('android.app.Notification$BigTextStyle')
30
29
  NotificationCompatBigPictureStyle = autoclass('androidx.core.app.NotificationCompat$BigPictureStyle')
31
30
  NotificationCompatInboxStyle = autoclass('androidx.core.app.NotificationCompat$InboxStyle')
32
31
  except Exception as e:
@@ -65,7 +64,14 @@ def get_image_uri(relative_path):
65
64
  return Uri.parse(f"file://{output_path}")
66
65
 
67
66
 
68
- def send_notification(title:str, message:str, style=None, img_path=None, channel_id:str="default_channel"):
67
+ def send_notification(
68
+ title:str,
69
+ message:str,
70
+ style=None,
71
+ img_path=None,
72
+ channel_name="Default Channel",
73
+ channel_id:str="default_channel"
74
+ ):
69
75
  """
70
76
  Send a notification on Android.
71
77
 
@@ -73,21 +79,22 @@ def send_notification(title:str, message:str, style=None, img_path=None, channel
73
79
  :param message: Message body.
74
80
  :param style: Style of the notification ('big_text', 'big_picture', 'inbox', 'large_icon').
75
81
  :param img_path: Path to the image resource.
76
- :param channel_id: Notification channel ID.
82
+ :param channel_id: Notification channel ID.(Default is lowercase channel name arg in lowercase)
77
83
  """
78
84
  if not ON_ANDROID:
79
85
  print('This Package Only Runs on Android !!! ---> Check "https://github.com/Fector101/android_notify/" for Documentation.')
80
86
  return
81
-
87
+ asks_permission_if_needed()
88
+ channel_id=channel_name.replace(' ','_').lower().lower() if not channel_id else channel_id
82
89
  # Get notification manager
83
90
  notification_manager = context.getSystemService(context.NOTIFICATION_SERVICE)
84
91
 
85
- importance= autoclass('android.app.NotificationManager').IMPORTANCE_HIGH # also works #NotificationManager.IMPORTANCE_DEFAULT
86
- # importance= NotificationManagerCompat.IMPORTANCE_HIGH #autoclass('android.app.NotificationManager').IMPORTANCE_HIGH also works #NotificationManager.IMPORTANCE_DEFAULT
92
+ # importance= autoclass('android.app.NotificationManager').IMPORTANCE_HIGH # also works #NotificationManager.IMPORTANCE_DEFAULT
93
+ importance= NotificationManagerCompat.IMPORTANCE_HIGH #autoclass('android.app.NotificationManager').IMPORTANCE_HIGH also works #NotificationManager.IMPORTANCE_DEFAULT
87
94
 
88
95
  # Notification Channel (Required for Android 8.0+)
89
96
  if BuildVersion.SDK_INT >= 26:
90
- channel = NotificationChannel(channel_id, "Default Channel",importance)
97
+ channel = NotificationChannel(channel_id, channel_name,importance)
91
98
  notification_manager.createNotificationChannel(channel)
92
99
 
93
100
  # Build the notification
@@ -98,7 +105,6 @@ def send_notification(title:str, message:str, style=None, img_path=None, channel
98
105
  builder.setDefaults(NotificationCompat.DEFAULT_ALL)
99
106
  builder.setPriority(NotificationCompat.PRIORITY_HIGH)
100
107
 
101
- # Get Image
102
108
  img=None
103
109
  if img_path:
104
110
  try:
@@ -112,8 +118,6 @@ def send_notification(title:str, message:str, style=None, img_path=None, channel
112
118
  big_text_style = NotificationCompatBigTextStyle()
113
119
  big_text_style.bigText(message)
114
120
  builder.setStyle(big_text_style)
115
-
116
-
117
121
  elif style == "big_picture" and img_path:
118
122
  bitmap = BitmapFactory.decodeStream(context.getContentResolver().openInputStream(img))
119
123
  builder.setLargeIcon(bitmap)
@@ -132,4 +136,5 @@ def send_notification(title:str, message:str, style=None, img_path=None, channel
132
136
  # Display the notification
133
137
  notification_id = random.randint(0, 100)
134
138
  notification_manager.notify(notification_id, builder.build())
135
- return notification_id
139
+ return notification_id
140
+
android_notify/styles.py CHANGED
@@ -1,5 +1,15 @@
1
- class NotificationStyles:
2
- BIG_TEXT = "big_text"
3
- BIG_PICTURE = "big_picture"
1
+ class NotificationStyles():
2
+ """ Safely Adding Styles"""
3
+ DEFAULT = "simple"
4
+
5
+ PROGRESS = "progress"
4
6
  INBOX = "inbox"
7
+ BIG_TEXT = "big_text"
8
+
5
9
  LARGE_ICON = "large_icon"
10
+ BIG_PICTURE = "big_picture"
11
+ BOTH_IMGS = "both_imgs"
12
+
13
+ MESSAGING = "messaging" # TODO
14
+ CUSTOM = "custom" # TODO
15
+
@@ -0,0 +1,547 @@
1
+ """This Module Contain Class for creating Notification With Java"""
2
+ import difflib
3
+ import traceback
4
+ import os
5
+ import re
6
+
7
+ DEV=0
8
+ ON_ANDROID = False
9
+
10
+ try:
11
+ from jnius import autoclass,cast # Needs Java to be installed pylint: disable=W0611, C0114
12
+ from android import activity # pylint: disable=import-error
13
+ from android.config import ACTIVITY_CLASS_NAME # pylint: disable=import-error
14
+
15
+ # Get the required Java classes
16
+ Bundle = autoclass('android.os.Bundle')
17
+ PythonActivity = autoclass('org.kivy.android.PythonActivity')
18
+ String = autoclass('java.lang.String')
19
+ Intent = autoclass('android.content.Intent')
20
+ PendingIntent = autoclass('android.app.PendingIntent')
21
+ context = PythonActivity.mActivity # Get the app's context
22
+ BitmapFactory = autoclass('android.graphics.BitmapFactory')
23
+ BuildVersion = autoclass('android.os.Build$VERSION')
24
+ NotificationManager = autoclass('android.app.NotificationManager')
25
+ NotificationChannel = autoclass('android.app.NotificationChannel')
26
+ ON_ANDROID = True
27
+ except Exception as e:# pylint: disable=W0718
28
+ MESSAGE='This Package Only Runs on Android !!! ---> Check "https://github.com/Fector101/android_notify/" to see design patterns and more info.' # pylint: disable=C0301
29
+ print(MESSAGE if DEV else '')
30
+
31
+ if ON_ANDROID:
32
+ try:
33
+ from android.permissions import request_permissions, Permission,check_permission # pylint: disable=E0401
34
+ from android.storage import app_storage_path # pylint: disable=E0401
35
+
36
+ NotificationManagerCompat = autoclass('androidx.core.app.NotificationManagerCompat')
37
+ NotificationCompat = autoclass('androidx.core.app.NotificationCompat')
38
+
39
+ # Notification Design
40
+ NotificationCompatBuilder = autoclass('androidx.core.app.NotificationCompat$Builder') # pylint: disable=C0301
41
+ NotificationCompatBigTextStyle = autoclass('androidx.core.app.NotificationCompat$BigTextStyle') # pylint: disable=C0301
42
+ NotificationCompatBigPictureStyle = autoclass('androidx.core.app.NotificationCompat$BigPictureStyle') # pylint: disable=C0301
43
+ NotificationCompatInboxStyle = autoclass('androidx.core.app.NotificationCompat$InboxStyle')
44
+ except Exception as e:# pylint: disable=W0718
45
+ print(e if DEV else '','Import Fector101')
46
+ # print(e if DEV else '')
47
+ print("""
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
52
+ """)
53
+
54
+ class Notification:
55
+ """
56
+ Send a notification on Android.
57
+
58
+ :param title: Title of the notification.
59
+ :param message: Message body.
60
+ :param style: Style of the notification
61
+ ('simple', 'progress', 'big_text', 'inbox', 'big_picture', 'large_icon', 'both_imgs').
62
+ both_imgs == using lager icon and big picture
63
+ :param big_picture_path: Relative Path to the image resource.
64
+ :param large_icon_path: Relative Path to the image resource.
65
+ :param callback: Function for notification Click.
66
+ ---
67
+ (Advance Options)
68
+ :param channel_name: - str Defaults to "Default Channel"
69
+ :param channel_id: - str Defaults to "default_channel"
70
+ ---
71
+ (Options during Dev On PC)
72
+ :param logs: - Bool Defaults to True
73
+ """
74
+ notification_ids=[0]
75
+ button_ids=[0]
76
+ btns_box={}
77
+ main_functions={}
78
+ style_values=[
79
+ '','simple',
80
+ 'progress','big_text',
81
+ 'inbox', 'big_picture',
82
+ 'large_icon','both_imgs',
83
+ 'custom'
84
+ ] # TODO make pattern for non-android Notifications
85
+ defaults={
86
+ 'title':'Default Title',
87
+ 'message':'Default Message', # TODO Might change message para to list if style set to inbox
88
+ 'style':'simple',
89
+ 'big_picture_path':'',
90
+ 'large_icon_path':'',
91
+ 'progress_max_value': 0,
92
+ 'progress_current_value': 0,
93
+ 'channel_name':'Default Channel',
94
+ 'channel_id':'default_channel',
95
+ 'logs':True,
96
+ "identifer": '',
97
+ 'callback': None
98
+ }
99
+ # During Development (When running on PC)
100
+ logs=not ON_ANDROID
101
+ def __init__(self,**kwargs):
102
+ self.__validateArgs(kwargs)
103
+ # Basic options
104
+ self.title=''
105
+ self.message=''
106
+ self.style=''
107
+ self.large_icon_path=''
108
+ self.big_picture_path=''
109
+ self.progress_current_value=0
110
+ self.progress_max_value=0
111
+
112
+ # For Nofitication Functions
113
+ self.identifer=''
114
+ self.callback = None
115
+
116
+ # Advance Options
117
+ self.channel_name='Default Channel'
118
+ self.channel_id='default_channel'
119
+ self.silent=False
120
+
121
+ # During Dev on PC
122
+ self.logs=self.logs
123
+
124
+ # Private (Don't Touch)
125
+ self.__id = self.__getUniqueID()
126
+ self.__setArgs(kwargs)
127
+
128
+ if not ON_ANDROID:
129
+ return
130
+ # TODO make send method wait for __asks_permission_if_needed method
131
+ self.__asks_permission_if_needed()
132
+ self.notification_manager = context.getSystemService(context.NOTIFICATION_SERVICE)
133
+ self.__builder=NotificationCompatBuilder(context, self.channel_id)# pylint: disable=E0606
134
+
135
+ def updateTitle(self,new_title):
136
+ """Changes Old Title
137
+
138
+ Args:
139
+ new_title (str): New Notification Title
140
+ """
141
+ self.title=new_title
142
+ if ON_ANDROID:
143
+ self.__builder.setContentTitle(new_title)
144
+
145
+ def updateMessage(self,new_message):
146
+ """Changes Old Message
147
+
148
+ Args:
149
+ new_message (str): New Notification Message
150
+ """
151
+ self.message=new_message
152
+ if ON_ANDROID:
153
+ self.__builder.setContentText(new_message)
154
+
155
+ def updateProgressBar(self,current_value,message:str=''):
156
+ """message defaults to last message"""
157
+ if not ON_ANDROID:
158
+ return
159
+
160
+ if self.logs:
161
+ print(f'Progress Bar Update value: {current_value}')
162
+ self.__builder.setProgress(self.progress_max_value, current_value, False)
163
+ if message:
164
+ self.__builder.setContentText(String(message))
165
+ self.notification_manager.notify(self.__id, self.__builder.build())
166
+
167
+ def removeProgressBar(self,message=''):
168
+ """message defaults to last message"""
169
+ if message:
170
+ self.__builder.setContentText(String(message))
171
+ self.__builder.setProgress(0, 0, False)
172
+ self.notification_manager.notify(self.__id, self.__builder.build())
173
+
174
+ def send(self,silent:bool=False):
175
+ """Sends notification
176
+
177
+ Args:
178
+ silent (bool): True if you don't want to show briefly on screen
179
+ """
180
+ self.silent=self.silent or silent
181
+ if ON_ANDROID:
182
+ self.__startNotificationBuild()
183
+ self.notification_manager.notify(self.__id, self.__builder.build())
184
+ elif self.logs:
185
+ string_to_display=''
186
+ for name,value in vars(self).items():
187
+ if value and name not in ['logs','_Notification__id']:
188
+ string_to_display += f'\n {name}: {value}'
189
+ string_to_display +="\n (Won't Print Logs When Complied,except if selected `Notification.logs=True`)"
190
+ print(string_to_display)
191
+ if DEV:
192
+ print(f'channel_name: {self.channel_name}, Channel ID: {self.channel_id}, id: {self.__id}')
193
+ print('Can\'t Send Package Only Runs on Android !!! ---> Check "https://github.com/Fector101/android_notify/" for Documentation.\n' if DEV else '\n') # pylint: disable=C0301
194
+
195
+ def __validateArgs(self,inputted_kwargs):
196
+
197
+ def checkInReference(inputted_keywords,accepteable_inputs,input_type):
198
+ def singularForm(plural_form):
199
+ return plural_form[:-1]
200
+ invalid_args= set(inputted_keywords) - set(accepteable_inputs)
201
+ if invalid_args:
202
+ suggestions=[]
203
+ for arg in invalid_args:
204
+ closest_match = difflib.get_close_matches(arg,accepteable_inputs,n=2,cutoff=0.6)
205
+ if closest_match:
206
+ suggestions.append(f"* '{arg}' Invalid -> Did you mean '{closest_match[0]}'? ") # pylint: disable=C0301
207
+ else:
208
+ suggestions.append(f"* {arg} is not a valid {singularForm(input_type)}.")
209
+ suggestion_text='\n'.join(suggestions)
210
+ hint_msg=singularForm(input_type) if len(invalid_args) < 2 else input_type
211
+
212
+ raise ValueError(f"Invalid {hint_msg} provided: \n\t{suggestion_text}\n\t* list of valid {input_type}: [{', '.join(accepteable_inputs)}]")
213
+
214
+ allowed_keywords=self.defaults.keys()
215
+ inputted_keywords_=inputted_kwargs.keys()
216
+ checkInReference(inputted_keywords_,allowed_keywords,'arguments')
217
+
218
+ # Validate style values
219
+ if 'style' in inputted_keywords_ and inputted_kwargs['style'] not in self.style_values:
220
+ checkInReference([inputted_kwargs['style']],self.style_values,'values')
221
+
222
+ def __setArgs(self,options_dict:dict):
223
+ for key,value in options_dict.items():
224
+ if key == 'channel_name' and value.strip():
225
+ setattr(self,key, value[:40])
226
+ elif key == 'channel_id' and value.strip(): # If user input's a channel id (i format properly)
227
+ setattr(self,key, self.__generate_channel_id(value))
228
+ else:
229
+ setattr(self,key, value if value else self.defaults[key])
230
+
231
+ if "channel_id" not in options_dict and 'channel_name' in options_dict: # if User doesn't input channel id but inputs channel_name
232
+ setattr(self,'channel_id', self.__generate_channel_id(options_dict['channel_name']))
233
+
234
+ def __startNotificationBuild(self):
235
+ self.__createBasicNotification()
236
+ if self.style not in ['simple','']:
237
+ self.__addNotificationStyle()
238
+
239
+ def __createBasicNotification(self):
240
+ # Notification Channel (Required for Android 8.0+)
241
+ # print("THis is cchannel is ",self.channel_id) #"BLAH"
242
+ if BuildVersion.SDK_INT >= 26 and self.notification_manager.getNotificationChannel(self.channel_id) is None:
243
+ importance=NotificationManagerCompat.IMPORTANCE_DEFAULT if self.silent else NotificationManagerCompat.IMPORTANCE_HIGH # pylint: disable=possibly-used-before-assignment
244
+ # importance = 3 or 4
245
+ channel = NotificationChannel(
246
+ self.channel_id,
247
+ self.channel_name,
248
+ importance
249
+ )
250
+ self.notification_manager.createNotificationChannel(channel)
251
+
252
+ # Build the notification
253
+ # self.__builder = NotificationCompatBuilder(context, self.channel_id)# pylint: disable=E0606
254
+ self.__builder.setContentTitle(str(self.title))
255
+ self.__builder.setContentText(str(self.message))
256
+ self.__builder.setSmallIcon(context.getApplicationInfo().icon)
257
+ self.__builder.setDefaults(NotificationCompat.DEFAULT_ALL) # pylint: disable=E0606
258
+ self.__builder.setPriority(NotificationCompat.PRIORITY_DEFAULT if self.silent else NotificationCompat.PRIORITY_HIGH)
259
+ self.__addIntentToOpenApp()
260
+ def __addNotificationStyle(self):
261
+ # pylint: disable=trailing-whitespace
262
+
263
+ large_icon_javapath=None
264
+ if self.large_icon_path:
265
+ try:
266
+ large_icon_javapath = self.__get_image_uri(self.large_icon_path)
267
+ except FileNotFoundError as e:
268
+ print('Failed Adding Big Picture Bitmap: ',e)
269
+
270
+ big_pic_javapath=None
271
+ if self.big_picture_path:
272
+ try:
273
+ big_pic_javapath = self.__get_image_uri(self.big_picture_path)
274
+ except FileNotFoundError as e:
275
+ print('Failed Adding Lagre Icon Bitmap: ',e)
276
+
277
+
278
+ if self.style == "big_text":
279
+ big_text_style = NotificationCompatBigTextStyle() # pylint: disable=E0606
280
+ big_text_style.bigText(self.message)
281
+ self.__builder.setStyle(big_text_style)
282
+
283
+ elif self.style == "inbox":
284
+ inbox_style = NotificationCompatInboxStyle() # pylint: disable=E0606
285
+ for line in self.message.split("\n"):
286
+ inbox_style.addLine(line)
287
+ self.__builder.setStyle(inbox_style)
288
+
289
+ elif self.style == "big_picture" and big_pic_javapath:
290
+ big_pic_bitmap = self.__getBitmap(big_pic_javapath)
291
+ big_picture_style = NotificationCompatBigPictureStyle().bigPicture(big_pic_bitmap) # pylint: disable=E0606
292
+ self.__builder.setStyle(big_picture_style)
293
+
294
+ elif self.style == "large_icon" and large_icon_javapath:
295
+ large_icon_bitmap = self.__getBitmap(large_icon_javapath)
296
+ self.__builder.setLargeIcon(large_icon_bitmap)
297
+
298
+ elif self.style == 'both_imgs' and (large_icon_javapath or big_pic_javapath):
299
+ if big_pic_javapath:
300
+ big_pic_bitmap = self.__getBitmap(big_pic_javapath)
301
+ big_picture_style = NotificationCompatBigPictureStyle().bigPicture(big_pic_bitmap)
302
+ self.__builder.setStyle(big_picture_style)
303
+ if large_icon_javapath:
304
+ large_icon_bitmap = self.__getBitmap(large_icon_javapath)
305
+ self.__builder.setLargeIcon(large_icon_bitmap)
306
+ elif self.style == 'progress':
307
+ self.__builder.setContentTitle(String(self.title))
308
+ self.__builder.setContentText(String(self.message))
309
+ self.__builder.setProgress(self.progress_max_value, self.progress_current_value, False)
310
+ # elif self.style == 'custom':
311
+ # self.__builder = self.__doCustomStyle()
312
+
313
+ # def __doCustomStyle(self):
314
+ # # TODO Will implement when needed
315
+ # return self.__builder
316
+
317
+ def __getUniqueID(self):
318
+ notification_id = self.notification_ids[-1] + 1
319
+ self.notification_ids.append(notification_id)
320
+ return notification_id
321
+
322
+ def __asks_permission_if_needed(self):
323
+ """
324
+ Ask for permission to send notifications if needed.
325
+ """
326
+ def on_permissions_result(permissions, grant): # pylint: disable=unused-argument
327
+ if self.logs:
328
+ print("Permission Grant State: ",grant)
329
+
330
+ permissions=[Permission.POST_NOTIFICATIONS] # pylint: disable=E0606
331
+ if not all(check_permission(p) for p in permissions):
332
+ request_permissions(permissions,on_permissions_result) # pylint: disable=E0606
333
+
334
+ def __get_image_uri(self,relative_path):
335
+ """
336
+ Get the absolute URI for an image in the assets folder.
337
+ :param relative_path: The relative path to the image (e.g., 'assets/imgs/icon.png').
338
+ :return: Absolute URI java Object (e.g., 'file:///path/to/file.png').
339
+ """
340
+
341
+ output_path = os.path.join(app_storage_path(),'app', relative_path) # pylint: disable=possibly-used-before-assignment
342
+ # print(output_path) # /data/user/0/(package.domain+package.name)/files/app/assets/imgs/icon.png | pylint: disable=:line-too-long
343
+
344
+ if not os.path.exists(output_path):
345
+ # TODO Use images From Any where even Web
346
+ raise FileNotFoundError(f"Image not found at path: {output_path}, (Can Only Use Images in App Path)")
347
+ Uri = autoclass('android.net.Uri')
348
+ return Uri.parse(f"file://{output_path}")
349
+ def __getBitmap(self,img_path):
350
+ return BitmapFactory.decodeStream(context.getContentResolver().openInputStream(img_path))
351
+
352
+ def __generate_channel_id(self,channel_name: str) -> str:
353
+ """
354
+ Generate a readable and consistent channel ID from a channel name.
355
+
356
+ Args:
357
+ channel_name (str): The name of the notification channel.
358
+
359
+ Returns:
360
+ str: A sanitized channel ID.
361
+ """
362
+ # Normalize the channel name
363
+ channel_id = channel_name.strip().lower()
364
+ # Replace spaces and special characters with underscores
365
+ channel_id = re.sub(r'[^a-z0-9]+', '_', channel_id)
366
+ # Remove leading/trailing underscores
367
+ channel_id = channel_id.strip('_')
368
+ return channel_id[:50]
369
+
370
+ def __addIntentToOpenApp(self):
371
+ intent = Intent(context, PythonActivity)
372
+ action = str(self.identifer) or f"ACTION_{self.__id}"
373
+ intent.setAction(action)
374
+ self.__addDataToIntent(intent)
375
+ self.main_functions[action]=self.callback
376
+ intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
377
+
378
+ pending_intent = PendingIntent.getActivity(
379
+ context, 0,
380
+ intent, PendingIntent.FLAG_IMMUTABLE if BuildVersion.SDK_INT >= 31 else PendingIntent.FLAG_UPDATE_CURRENT
381
+ )
382
+ self.__builder.setContentIntent(pending_intent)
383
+ self.__builder.setAutoCancel(True)
384
+
385
+ def __addDataToIntent(self,intent):
386
+ """Persit Some data to notification object for later use"""
387
+ bundle = Bundle()
388
+ bundle.putString("title", self.title or 'Title Placeholder')
389
+ bundle.putInt("notify_id", self.__id)
390
+ intent.putExtras(bundle)
391
+
392
+ def __getIDForButton(self):
393
+ btn_id = self.button_ids[-1] + 1
394
+ self.button_ids.append(btn_id)
395
+ return btn_id
396
+
397
+ def addButton(self, text:str,on_release):
398
+ """For adding action buttons
399
+
400
+ Args:
401
+ text (str): Text For Button
402
+ """
403
+ if self.logs:
404
+ print('Added Button: ', text)
405
+
406
+ if not ON_ANDROID:
407
+ return
408
+
409
+ btn_id= self.__getIDForButton()
410
+ action = f"BTN_ACTION_{btn_id}"
411
+
412
+ action_intent = Intent(context, PythonActivity)
413
+ action_intent.setAction(action)
414
+ action_intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
415
+ bundle = Bundle()
416
+ bundle.putString("title", self.title or 'Title Placeholder')
417
+ bundle.putInt("key_int", 123)
418
+ action_intent.putExtras(bundle)
419
+ action_intent.putExtra("button_id", btn_id)
420
+
421
+ self.btns_box[action] = on_release
422
+ # action_intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP)
423
+
424
+ if self.logs:
425
+ print('Button id: ',btn_id)
426
+ pending_action_intent = PendingIntent.getActivity(
427
+ context,
428
+ 0,
429
+ action_intent,
430
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
431
+ )
432
+ # Convert text to CharSequence
433
+ action_text = cast('java.lang.CharSequence', String(text))
434
+
435
+
436
+
437
+ # Add action with proper types
438
+ self.__builder.addAction(
439
+ int(context.getApplicationInfo().icon), # Cast icon to int
440
+ action_text, # CharSequence text
441
+ pending_action_intent # PendingIntent
442
+ )
443
+ # Set content intent for notification tap
444
+ self.__builder.setContentIntent(pending_action_intent)
445
+
446
+
447
+ class NotificationHandler:
448
+ """For Notification Operations """
449
+ __identifer = None
450
+
451
+ @classmethod
452
+ def getIdentifer(cls):
453
+ """Returns identifer for Clicked Notification."""
454
+ if not cls.is_on_android():
455
+ return "Not on Android"
456
+
457
+ saved_intent = cls.__identifer
458
+ if not saved_intent or (isinstance(saved_intent, str) and saved_intent.startswith("android.intent")):
459
+ # All other notifications are not None after First notification opens app
460
+ # NOTE these notifications are also from Last time app was opened and they Still Give Value after first one opens App
461
+ # TODO Find a way to get intent when App if Swiped From recents
462
+ __PythonActivity = autoclass(ACTIVITY_CLASS_NAME)
463
+ __mactivity = __PythonActivity.mActivity
464
+ __context = cast('android.content.Context', __mactivity)
465
+ __Intent = autoclass('android.content.Intent')
466
+ __intent = __Intent(__context, __PythonActivity)
467
+ action = __intent.getAction()
468
+ print('Start up Intent ----', action)
469
+ print('start Up Title --->',__intent.getStringExtra("title"))
470
+
471
+ return saved_intent
472
+
473
+ @classmethod
474
+ def __notificationHandler(cls,intent):
475
+ """Calls Function Attached to notification on click.
476
+ Don't Call this function manual, it's Already Attach to Notification.
477
+
478
+ Returns:
479
+ str: The Identiter of Nofication that was clicked.
480
+ """
481
+ if not cls.is_on_android():
482
+ return "Not on Android"
483
+ buttons_object=Notification.btns_box
484
+ notifty_functions=Notification.main_functions
485
+ if DEV:
486
+ print("notifty_functions ",notifty_functions)
487
+ print("buttons_object", buttons_object)
488
+ action = None
489
+ try:
490
+ action = intent.getAction()
491
+ cls.__identifer = action
492
+
493
+ print("The Action --> ",action)
494
+ if action == "android.intent.action.MAIN": # Not Open From Notification
495
+ return 'Not notification'
496
+
497
+ print(intent.getStringExtra("title"))
498
+ try:
499
+ if action in notifty_functions and notifty_functions[action]:
500
+ notifty_functions[action]()
501
+ elif action in buttons_object:
502
+ buttons_object[action]()
503
+ except Exception as e: # pylint: disable=broad-exception-caught
504
+ print('Failed to run function: ', traceback.format_exc())
505
+ print("Error Type ",e)
506
+ except Exception as e: # pylint: disable=broad-exception-caught
507
+ print('Notify Hanlder Failed ',e)
508
+ return action
509
+
510
+ @classmethod
511
+ def bindNotifyListener(cls):
512
+ """Binds the notification listener.\n\n
513
+ ```
514
+ from kivy.app import App
515
+ from android_notify import bindNotifyListener
516
+ class Myapp(App):
517
+ def on_start(self):
518
+ bindNotifyListener() # if successfull returns True
519
+ ```
520
+ """
521
+ if not cls.is_on_android():
522
+ return "Not on Android"
523
+ #Beta TODO Automatic bind when Notification object is called the first time use keep trying BroadcastReceiver
524
+ try:
525
+ activity.bind(on_new_intent=cls.__notificationHandler)
526
+ return True
527
+ except Exception as e: # pylint: disable=broad-exception-caught
528
+ print('Failed to bin notitfications listener',e)
529
+ return False
530
+ @classmethod
531
+ def unbindNotifyListener(cls):
532
+ """Removes Listener for Notifications Click"""
533
+ if not cls.is_on_android():
534
+ return "Not on Android"
535
+
536
+ #Beta TODO use BroadcastReceiver
537
+ try:
538
+ activity.unbind(on_new_intent=cls.__notificationHandler)
539
+ return True
540
+ except Exception as e: # pylint: disable=broad-exception-caught
541
+ print("Failed to unbind notifications listener: ",e)
542
+ return False
543
+
544
+ @staticmethod
545
+ def is_on_android():
546
+ """Utility to check if the app is running on Android."""
547
+ return ON_ANDROID
@@ -0,0 +1,452 @@
1
+ Metadata-Version: 2.2
2
+ Name: android-notify
3
+ Version: 1.5
4
+ Summary: A Python package that simpilfies creating Android notifications in Kivy apps.
5
+ Home-page: https://github.com/fector101/android-notify
6
+ Author: Fabian
7
+ Author-email: fector101@yahoo.com
8
+ License: MIT
9
+ Project-URL: Documentation, https://github.com/fector101/android-notify/
10
+ Project-URL: Source, https://github.com/fector101/android-notify
11
+ Project-URL: Tracker, https://github.com/fector101/android-notify/issues
12
+ Project-URL: Funding, https://www.buymeacoffee.com/fector101
13
+ Keywords: android,notifications,kivy,mobile,post-notifications,pyjnius,android-notifications,kivy-notifications,python-android,mobile-development,push-notifications,mobile-app,kivy-application
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: Android
17
+ Classifier: Development Status :: 5 - Production/Stable
18
+ Classifier: Intended Audience :: Developers
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Python: >=3.6
21
+ Description-Content-Type: text/markdown
22
+ Requires-Dist: kivy>=2.0.0
23
+ Requires-Dist: pyjnius>=1.4.2
24
+ Dynamic: author
25
+ Dynamic: author-email
26
+ Dynamic: classifier
27
+ Dynamic: description
28
+ Dynamic: description-content-type
29
+ Dynamic: home-page
30
+ Dynamic: keywords
31
+ Dynamic: license
32
+ Dynamic: project-url
33
+ Dynamic: requires-dist
34
+ Dynamic: requires-python
35
+ Dynamic: summary
36
+
37
+ <div align="center">
38
+ <br>
39
+ <h1> Android-Notifiy </h1>
40
+ <p> A Python library for effortlessly creating and managing Android notifications in Kivy android apps.</p>
41
+ <p>Supports various styles and ensures seamless integration and customization.</p>
42
+ <!-- <br> -->
43
+ <!-- <img src="https://raw.githubusercontent.com/Fector101/android_notify/main/docs/imgs/democollage.jpg"> -->
44
+ </div>
45
+
46
+ ## Features
47
+
48
+ - Also Compatible with Android 8.0+.
49
+ - Supports including images in notifications.
50
+ - All Notifications can take Functions (version 1.5+) [functions docs](#functions).
51
+ - Support for multiple notification styles:
52
+ - [Simple](#basic-usage)
53
+ - [Progress](#progress-bar-notification)
54
+ - [Big Picture](#notification-with-an-image-big-picture-style)
55
+ - [Inbox](#inbox-notification-style)
56
+ - [Large Icon](#notification-with-an-image-large-icon-style)
57
+ - [Buttons](#notification-with-buttons)
58
+ - [Big Text](#big-text-notification-will-display-as-simple-text-if-device-dosent-support)
59
+
60
+ This module automatically handles:
61
+
62
+ - Permission requests for notifications
63
+ - Customizable notification channels.
64
+
65
+ ## Installation
66
+
67
+ This package is available on PyPI and can be installed via pip:
68
+
69
+ ```bash
70
+ pip install android-notify
71
+ ```
72
+
73
+ ## **Dependencies**
74
+
75
+ **Prerequisites:**
76
+
77
+ - Kivy
78
+
79
+ In your **`buildozer.spec`** file, ensure you include the following:
80
+
81
+ ```ini
82
+ # Add pyjnius so ensure it's packaged with the build
83
+ requirements = python3, kivy, pyjnius, android-notify
84
+
85
+ # Add permission for notifications
86
+ android.permissions = POST_NOTIFICATIONS
87
+
88
+ # Required dependencies (write exactly as shown, no quotation marks)
89
+ android.gradle_dependencies = androidx.core:core:1.6.0, androidx.core:core-ktx:1.15.0
90
+ android.enable_androidx = True
91
+ ```
92
+
93
+ ---
94
+
95
+ ## Basic Usage
96
+
97
+ ```python
98
+ from android_notify import Notification
99
+
100
+ # Create a simple notification
101
+ notification = Notification(
102
+ title="Hello",
103
+ message="This is a basic notification."
104
+ )
105
+ notification.send()
106
+ ```
107
+
108
+ **Sample Image:**
109
+ ![basic notification img sample](https://raw.githubusercontent.com/Fector101/android_notify/main/docs/imgs/basicnoti.jpg)
110
+
111
+ ## Notification Styles
112
+
113
+ The library supports multiple notification styles:
114
+
115
+ 1. `simple` - Basic notification with title and message
116
+ 2. `progress` - Shows a progress bar
117
+ 3. `big_text` - Expandable notification with long text
118
+ 4. `inbox` - List-style notification
119
+ 5. `big_picture` - Notification with a large image
120
+ 6. `large_icon` - Notification with a custom icon
121
+ 7. `both_imgs` - Combines big picture and large icon
122
+ 8. `custom` - For custom notification styles
123
+
124
+ ### Style Examples
125
+
126
+ #### Progress Bar notification
127
+
128
+ ```python
129
+ from kivy.clock import Clock
130
+
131
+ notification = Notification(
132
+ title="Downloading...",
133
+ message="0% downloaded",
134
+ style="progress",
135
+ progress_max_value=100,
136
+ progress_current_value=0
137
+ )
138
+ notification.send()
139
+ Clock.schedule_once(lambda dt: notification.updateProgressBar(30, "30% downloaded"), 350)
140
+ ```
141
+
142
+ **Sample Image:**
143
+ ![progress img sample](https://raw.githubusercontent.com/Fector101/android_notify/main/docs/imgs/progress.jpg)
144
+
145
+ #### Notification with an Image (Big Picture Style)
146
+
147
+ ```python
148
+ # Image notification
149
+ notification = Notification(
150
+ title='Picture Alert!',
151
+ message='This notification includes an image.',
152
+ style="big_picture",
153
+ big_picture_path="assets/imgs/photo.png"
154
+ )
155
+ notification.send()
156
+
157
+ ```
158
+
159
+ **Sample Image:**
160
+ ![big_picture img sample](https://raw.githubusercontent.com/Fector101/android_notify/main/docs/imgs/bigpicturenoti.jpg)
161
+
162
+ #### Inbox Notification Style
163
+
164
+ ```python
165
+ # Send a notification with inbox style
166
+ notification = Notification(
167
+ title='Inbox Notification',
168
+ message='Line 1\nLine 2\nLine 3',
169
+ style='inbox'
170
+ )
171
+ notification.send()
172
+
173
+ ```
174
+
175
+ **Sample Image:**
176
+ ![Inbox Notification sample](https://raw.githubusercontent.com/Fector101/android_notify/main/docs/imgs/inboxnoti.jpg)
177
+
178
+ #### Notification with an Image (Large Icon Style)
179
+
180
+ ```python
181
+ notification = Notification(
182
+ title="FabianDev_",
183
+ message="A twitter about some programming stuff",
184
+ style="large_icon",
185
+ large_icon_path="assets/imgs/profile.png"
186
+ )
187
+
188
+ ```
189
+
190
+ **Sample Image:**
191
+ ![large_icon img sample](https://raw.githubusercontent.com/Fector101/android_notify/main/docs/imgs/large_icon.jpg)
192
+
193
+ #### Notification with Buttons
194
+
195
+ ```python
196
+ notification = Notification(title="Jane Dough", message="How to use android-notify #coding #purepython")
197
+ def playVideo():
198
+ print('Playing Video')
199
+
200
+ def turnOffNoti():
201
+ print('Please Turn OFf Noti')
202
+
203
+ def watchLater():
204
+ print('Add to Watch Later')
205
+
206
+ notification.addButton(text="Play",on_release=playVideo)
207
+ notification.addButton(text="Turn Off",on_release=turnOffNoti)
208
+ notification.addButton(text="Watch Later",on_release=watchLater)
209
+ notification.send()
210
+ ```
211
+
212
+ **Sample Image:**
213
+ ![btns img sample](https://raw.githubusercontent.com/Fector101/android_notify/main/docs/imgs/btns.jpg)
214
+
215
+ #### Big text notification (Will Display as normal text if Device dosen't support)
216
+
217
+ ```python
218
+ notification = Notification(
219
+ title="Article",
220
+ message="Long article content...",
221
+ style="big_text"
222
+ )
223
+ ```
224
+
225
+ ## Advanced Features
226
+
227
+ ### Updating Notifications
228
+
229
+ ```python
230
+ notification = Notification(title="Initial Title")
231
+ notification.send()
232
+
233
+ # Update title
234
+ notification.updateTitle("New Title")
235
+
236
+ # Update message
237
+ notification.updateMessage("New Message")
238
+ ```
239
+
240
+ ### Progress Bar Management
241
+
242
+ ```python
243
+ notification = Notification(
244
+ title="Download..",
245
+ style="progress"
246
+ )
247
+
248
+ # Update progress
249
+ notification.updateProgressBar(30, "30% downloaded")
250
+
251
+ # Remove progress bar
252
+ notification.removeProgressBar("Download Complete")
253
+ ```
254
+
255
+ ### Channel Management
256
+
257
+ Notifications are organized into channels. You can customize the channel name and ID:
258
+
259
+ - Custom Channel Name's Gives User ability to turn on/off specific
260
+
261
+ ```python
262
+ notification = Notification(
263
+ title="Download finished",
264
+ message="How to Catch a Fish.mp4",
265
+ channel_name="Download Notifications", # Will create User-visible name "Download Notifications"
266
+ channel_id="downloads_notifications" # Optional: specify custom channel ID
267
+ )
268
+ ```
269
+
270
+ **Sample Image:**
271
+ ![channels img sample](https://raw.githubusercontent.com/Fector101/android_notify/main/docs/imgs/channel_name.jpg)
272
+
273
+ ### Silent Notifications
274
+
275
+ To send a notification without sound or heads-up display:
276
+
277
+ ```python
278
+ notification = Notification(title="Silent Update")
279
+ notification.send(silent=True)
280
+ ```
281
+
282
+ ## Functions
283
+
284
+ ### NotificationHandler - To Attach Listener
285
+
286
+ Add this to your main.py App Class so it runs Once, In later Versions It'll be Attached Automatical
287
+
288
+ ```python
289
+ from kivymd.app import MDApp
290
+ from android_notify import Notification, NotificationHandler
291
+
292
+ class Myapp(MDApp):
293
+
294
+ def on_start(self):
295
+ # Is called Once when app is Starts up
296
+ NotificationHandler.bindNotifyListener() # if successfull returns True
297
+ Notification(title="Hello", message="This is a basic notification.",callback=self.doSomething).send()
298
+
299
+ def doSomething(self):
300
+ print("print in Debug Console")
301
+ ```
302
+
303
+ ### Get Which Notification was used to Open App - identifer (str)
304
+
305
+ If you just want to get the Exact Notification Clicked to Open App, you can use NotificationHandler to get unique identifer
306
+
307
+ ```python
308
+ from kivymd.app import MDApp
309
+ from android_notify import Notification, NotificationHandler
310
+
311
+ class Myapp(MDApp):
312
+
313
+ def on_start(self):
314
+
315
+ notify = Notification(title="Change Page", message="Click to change App page.", identifer='change_app_page')
316
+ notify.send()
317
+
318
+ notify1 = Notification(title="Change Colour", message="Click to change App Colour", identifer='change_app_color')
319
+ notify1.send()
320
+
321
+ NotificationHandler.bindNotifyListener()
322
+
323
+ def on_resume(self):
324
+ # Is called everytime app is reopened
325
+ notify_identifer = NotificationHandler.getIdentifer()
326
+ if notify_identifer == 'change_app_page':
327
+ # Code to change Screen
328
+ pass
329
+ elif notify_identifer == 'change_app_color':
330
+ # Code to change Screen Color
331
+ pass
332
+ ```
333
+
334
+
335
+
336
+ ### Assist
337
+
338
+ - How to Copy image to app folder
339
+
340
+ ```python
341
+ import shutil,os # These modules come packaged with python
342
+ from android.storage import app_storage_path # type: ignore -- This works only on android
343
+
344
+ app_path = os.path.join(app_storage_path(),'app')
345
+ image_path= "/storage/emulated/0/Download/profile.png"
346
+
347
+ shutil.copy(image_path, os.path.join(app_path, "profile.png"))
348
+ ```
349
+
350
+ - Avoiding Human Error when using different notification styles
351
+
352
+ ```python
353
+ from android_notify import Notification, NotificationStyles
354
+ Notification(
355
+ title="New Photo",
356
+ message="Check out this image",
357
+ style=NotificationStyles.BIG_PICTURE,
358
+ big_picture_path="assets/imgs/photo.png"
359
+ ).send()
360
+ ```
361
+
362
+ ## Development Mode
363
+
364
+ When developing on non-Android platforms, the library provides debugging output:
365
+
366
+ ```python
367
+ # Enable logs (default is True when not on Android)
368
+ Notification.logs = True
369
+
370
+ # Create notification for testing
371
+ notification = Notification(title="Test")
372
+ notification.send()
373
+ # Will print notification properties instead of sending
374
+ ```
375
+
376
+ ## Image Requirements
377
+
378
+ - Images must be located within your app's folder
379
+ - Supported paths are relative to your app's storage path
380
+ - Example: `assets/imgs/icon.png`
381
+
382
+ ## Error Handling
383
+
384
+ The library validates arguments and provides helpful error messages:
385
+
386
+ - Invalid style names will suggest the closest matching style
387
+ - Invalid arguments will list all valid options
388
+ - Missing image files will raise FileNotFoundError with the attempted path
389
+
390
+ ## Limitations
391
+
392
+ 1. Only works on Android devices
393
+ 2. Images must be within the app's storage path
394
+ 3. Channel names are limited to 40 characters
395
+ 4. Channel IDs are limited to 50 characters
396
+
397
+ ## Best Practices
398
+
399
+ 1. Always handle permissions appropriately
400
+ 2. Use meaningful channel names for organization
401
+ 3. Keep progress bar updates reasonable (don't update too frequently)
402
+ 4. Test notifications on different Android versions
403
+ 5. Consider using silent notifications for frequent updates
404
+
405
+ ## Debugging Tips
406
+
407
+ 1. Enable logs during development: `Notification.logs = True`
408
+ 2. Check channel creation with Android's notification settings
409
+ 3. Verify image paths before sending notifications
410
+ 4. Test different styles to ensure proper display
411
+
412
+ Remember to check Android's notification documentation for best practices and guidelines regarding notification frequency and content.
413
+
414
+ ## Contribution
415
+
416
+ Feel free to open issues or submit pull requests for improvements!
417
+
418
+ ## Reporting Issues
419
+
420
+ Found a bug? Please open an issue on our [GitHub Issues](https://github.com/Fector101/android_notify/issues) page.
421
+
422
+ ## Author
423
+
424
+ - Fabian - <fector101@yahoo.com>
425
+ - GitHub: [Android Notify Repo](https://github.com/Fector101/android_notify)
426
+ - Twitter: [FabianDev_](https://twitter.com/intent/user?user_id=1246911115319263233)
427
+
428
+ For feedback or contributions, feel free to reach out!
429
+
430
+ ---
431
+
432
+ ## ☕ Support the Project
433
+
434
+ 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.
435
+
436
+ <a href="https://www.buymeacoffee.com/fector101" target="_blank">
437
+ <img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" height="60">
438
+ </a>
439
+
440
+ ---
441
+
442
+ ## Acknowledgments
443
+
444
+ - This Project was thoroughly Tested by the [Laner Project](https://github.com/Fector101/Laner/) - A application for Securely Transfering Files Wirelessly between your PC and Phone.
445
+ - Thanks to the Kivy and Pyjnius communities.
446
+
447
+ ---
448
+
449
+ ## 🌐 **Links**
450
+
451
+ - **PyPI:** [android-notify on PyPI](https://pypi.org/project/android-notify/)
452
+ - **GitHub:** [Source Code Repository](https://github.com/Fector101/android_notify/)
@@ -0,0 +1,9 @@
1
+ android_notify/__init__.py,sha256=lcLjyfegXgU7cyGhfSphAOBipXwemrVkdYy3mcF6X5Y,172
2
+ android_notify/__main__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ android_notify/core.py,sha256=B3gOgbLGGI6tz-Q_T2wmk74oggOSDX0Qz4lqj00vaFo,6114
4
+ android_notify/styles.py,sha256=I2p31qStg9DaML9U4nXRvdpGzpppK6RS-qlDKuOv_Tk,328
5
+ android_notify/sword.py,sha256=6dmlTQRYtuhHUyO8E4fh3YlnLJC1FCvkLtgBKGvJnmI,23252
6
+ android_notify-1.5.dist-info/METADATA,sha256=D-xTAAdOyVgeK_kqcZL1cU_HAbwcFvJkwi6smOIE41Y,13161
7
+ android_notify-1.5.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
8
+ android_notify-1.5.dist-info/top_level.txt,sha256=IR1ONMrRSRINZpWn2X0dL5gbWwWINsK7PW8Jy2p4fU8,15
9
+ android_notify-1.5.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.6.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,210 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: android_notify
3
- Version: 1.2
4
- Summary: A Python package for sending Android notifications.
5
- Home-page: https://github.com/Fector101/android_notify/
6
- Author: Fabian
7
- Author-email: fabianjoseph063@gmail.com
8
- Project-URL: Funding, https://buymeacoffee.com/fector101
9
- Project-URL: Source, https://github.com/Fector101/android_notify/
10
- Requires-Python: >=3.6
11
- Description-Content-Type: text/markdown
12
- Requires-Dist: pyjnius
13
-
14
- # Android Notify
15
-
16
- `android_notify` is a Python module designed to simplify sending Android notifications using Kivy and Pyjnius. It supports multiple notification styles, including text, images, and inbox layouts.
17
-
18
- ## Features
19
-
20
- - Send Android notifications with custom titles and messages.
21
- - Support for multiple notification styles:
22
- - Big Text
23
- - Big Picture
24
- - Large Icon
25
- - Inbox
26
- - Supports including images in notifications.
27
- - Compatible with Android 8.0+ (Notification Channels).
28
- - Customizable notification channels.
29
-
30
- ## Installation
31
-
32
- This package is available on PyPI and can be installed via pip:
33
-
34
- ```bash
35
- pip install android-notify
36
- ```
37
-
38
- ## **Dependencies**
39
-
40
- **Prerequisites:**
41
-
42
- - Buildozer
43
- - Kivy
44
-
45
- In your **`buildozer.spec`** file, ensure you include the following:
46
-
47
- ```ini
48
- # Add pyjnius so it's packaged with the build
49
- requirements = python3,kivy,pyjnius
50
-
51
- # Add permission for notifications
52
- android.permissions = POST_NOTIFICATIONS
53
-
54
- # Required dependencies (write exactly as shown, no quotation marks)
55
- android.gradle_dependencies = androidx.core:core:1.6.0, androidx.core:core-ktx:1.15.0
56
- android.enable_androidx = True
57
- ```
58
-
59
- ---
60
-
61
- ### Example Notification
62
-
63
- ```python
64
- from android_notify.core import send_notification
65
-
66
- # Send a basic notification
67
- send_notification("Hello", "This is a basic notification.")
68
-
69
- # Send a notification with an image
70
- send_notification(
71
- title='Picture Alert!',
72
- message='This notification includes an image.',
73
- style='big_picture',
74
- img_path='assets/imgs/icon.png'
75
- )
76
-
77
- # Send a notification with inbox style
78
- send_notification(
79
- title='Inbox Notification',
80
- message='Line 1\nLine 2\nLine 3',
81
- style='inbox'
82
- )
83
-
84
- # Send a Big Text notification (Note this send as a normal notification if not supported on said device)
85
- send_notification(
86
- title='Hello!',
87
- message='This is a sample notification.',
88
- style='big_text'
89
- )
90
- ```
91
-
92
- ---
93
-
94
- ### **Assist** -- How to Copy image to app folder
95
-
96
- ```python
97
- import shutil # This module comes packaged with python
98
- from android.storage import app_storage_path # type: ignore -- This works only on android
99
-
100
- app_path = os.path.join(app_storage_path(),'app')
101
- image_path= "/storage/emulated/0/Download/profile.png"
102
-
103
- shutil.copy(image_path, os.path.join(app_path, "profile.png"))
104
- ```
105
-
106
- ---
107
-
108
- ### **Functions Reference**
109
-
110
- ### 1. `asks_permission_if_needed()`
111
-
112
- **Description:**
113
-
114
- - Checks if notification permissions are granted and requests them if missing.
115
-
116
- **Usage:**
117
-
118
- ```python
119
- asks_permission_if_needed()
120
- ```
121
-
122
- ---
123
-
124
- ### 2. `get_image_uri(relative_path)`
125
-
126
- **Description:**
127
-
128
- - Resolves the absolute URI for an image in the app's storage.
129
-
130
- **Parameters:**
131
-
132
- - `relative_path` *(str)*: Path to the image (e.g., `assets/imgs/icon.png`).
133
-
134
- **Returns:**
135
-
136
- - `Uri`: Android URI object for the image.
137
-
138
- **Usage:**
139
-
140
- ```python
141
- uri = get_image_uri('assets/imgs/icon.png')
142
- ```
143
-
144
- ---
145
-
146
- ### 3. `send_notification(title, message, style=None, img_path=None, channel_id='default_channel')`
147
-
148
- **Description:**
149
-
150
- - Sends an Android notification with optional styles and images.
151
-
152
- **Parameters:**
153
-
154
- - `title` *(str)*: Notification title.
155
- - `message` *(str)*: Notification message.
156
- - `style` *(str, optional)*: Notification style (`big_text`, `big_picture`, `inbox`, `large_icon`).
157
- - `img_path` *(str, optional)*: Path to the image resource.(for `big_picture` or `large_icon` styles).
158
- - `channel_id` *(str, optional)*: Notification channel ID.
159
-
160
- Returns - notification id
161
-
162
- ### Advanced Usage
163
-
164
- You can customize notification channels for different types of notifications.
165
-
166
- ```python
167
- send_notification(
168
- title='Custom Channel Notification',
169
- message='This uses a custom notification channel.',
170
- channel_id='custom_channel'
171
- )
172
- ```
173
-
174
- ## Contribution
175
-
176
- Feel free to open issues or submit pull requests for improvements!
177
-
178
- ## 🐛 Reporting Issues
179
-
180
- Found a bug? Please open an issue on our [GitHub Issues](https://github.com/Fector101/android_notify/issues) page.
181
-
182
- ## Author
183
-
184
- - Fabian - <fector101@yahoo.com>
185
- - GitHub: <https://github.com/Fector101/android_notify>
186
-
187
- For feedback or contributions, feel free to reach out!
188
-
189
- ---
190
-
191
- ## ☕ Support the Project
192
-
193
- If you find this project helpful, consider buying me a coffee! Your support helps maintain and improve the project.
194
-
195
- <a href="https://www.buymeacoffee.com/fector101" target="_blank">
196
- <img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" height="60">
197
- </a>
198
-
199
- ---
200
-
201
- ## Acknowledgments
202
-
203
- - Thanks to the Kivy and Pyjnius communities for their support.
204
-
205
- ---
206
-
207
- ## 🌐 **Links**
208
-
209
- - **PyPI:** [android-notify on PyPI](https://pypi.org/project/android-notify/)
210
- - **GitHub:** [Source Code Repository](hhttps://github.com/Fector101/android_notify/)
@@ -1,8 +0,0 @@
1
- android_notify/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- android_notify/__main__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- android_notify/core.py,sha256=P80jtB4Dim9c10ibgVPioEDJOKgjcoetl7rHAHEZEAI,5960
4
- android_notify/styles.py,sha256=P_8sAqb3Hbf_vbhqSoCVjKeqJ05Fr_CksO-HX5pj8pU,134
5
- android_notify-1.2.dist-info/METADATA,sha256=pkhzt6TaEZ1gTTEShVpXrATujLqcsVA-GQn3g7X65Qg,5125
6
- android_notify-1.2.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
7
- android_notify-1.2.dist-info/top_level.txt,sha256=IR1ONMrRSRINZpWn2X0dL5gbWwWINsK7PW8Jy2p4fU8,15
8
- android_notify-1.2.dist-info/RECORD,,