android-notify 1.54.1__tar.gz → 1.56__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.

Potentially problematic release.


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

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: android-notify
3
- Version: 1.54.1
3
+ Version: 1.56
4
4
  Summary: A Python package that simpilfies creating Android notifications in Kivy apps.
5
5
  Home-page: https://github.com/fector101/android-notify
6
6
  Author: Fabian
@@ -464,12 +464,22 @@ args
464
464
 
465
465
  - new_message : String to be set as New notification Message
466
466
 
467
+ ### Instance.showInfiniteProgressBar
468
+
469
+ Displays an Infinite Progress Bar in Notification, Can be Removed using `removeProgressBar` and updated using `updateProgressBar` method
470
+
467
471
  ### Instance.updateProgressBar
468
472
 
473
+ if updating title,msg with progressbar frequenlty pass them in too to avoid update issues.
474
+ According to android docs updates shouldn't be too frequent.
475
+
476
+ `updateProgressBar` has a built-in delay of 0.5 secs
477
+
469
478
  args
470
479
 
471
480
  - current_value (str): the value from progressbar current progress
472
481
  - message (str,optional): defaults to last message
482
+ - title (str,optional): defaults to last title
473
483
 
474
484
  ### Instance.removeProgressBar
475
485
 
@@ -479,6 +489,7 @@ args
479
489
 
480
490
  - message (str, optional): notification message, Defaults to 'last message'.
481
491
  - show_on_update (bool, optional): To show notification brifely when progressbar removed. Defaults to True.
492
+ - title (str, optional): notification title, Defaults to 'last title'.
482
493
 
483
494
  ### Instance.addNotificationStyle
484
495
 
@@ -428,12 +428,22 @@ args
428
428
 
429
429
  - new_message : String to be set as New notification Message
430
430
 
431
+ ### Instance.showInfiniteProgressBar
432
+
433
+ Displays an Infinite Progress Bar in Notification, Can be Removed using `removeProgressBar` and updated using `updateProgressBar` method
434
+
431
435
  ### Instance.updateProgressBar
432
436
 
437
+ if updating title,msg with progressbar frequenlty pass them in too to avoid update issues.
438
+ According to android docs updates shouldn't be too frequent.
439
+
440
+ `updateProgressBar` has a built-in delay of 0.5 secs
441
+
433
442
  args
434
443
 
435
444
  - current_value (str): the value from progressbar current progress
436
445
  - message (str,optional): defaults to last message
446
+ - title (str,optional): defaults to last title
437
447
 
438
448
  ### Instance.removeProgressBar
439
449
 
@@ -443,6 +453,7 @@ args
443
453
 
444
454
  - message (str, optional): notification message, Defaults to 'last message'.
445
455
  - show_on_update (bool, optional): To show notification brifely when progressbar removed. Defaults to True.
456
+ - title (str, optional): notification title, Defaults to 'last title'.
446
457
 
447
458
  ### Instance.addNotificationStyle
448
459
 
@@ -0,0 +1,82 @@
1
+ """Assists Notification Class with Args keeps sub class cleaner"""
2
+ from dataclasses import dataclass, fields
3
+ import difflib
4
+ from .styles import NotificationStyles
5
+
6
+ @dataclass
7
+ class BaseNotification:
8
+ """Encapsulator"""
9
+
10
+ # Basic options
11
+ title: str = ''
12
+ message: str = ''
13
+ style: str = 'simple'
14
+
15
+ # Style-specific attributes
16
+ big_picture_path: str = ''
17
+ large_icon_path: str = ''
18
+ progress_max_value: int = 100
19
+ progress_current_value: float = 0.0 # Also Takes in Ints
20
+ body: str = ''
21
+
22
+ # Notification Functions
23
+ identifer: str = ''
24
+ callback: object = None
25
+
26
+ # Advanced Options
27
+ app_icon: str = 'Defaults to package app icon'
28
+ channel_name: str = 'Default Channel'
29
+ channel_id: str = 'default_channel'
30
+ silent: bool = False
31
+ logs: bool = False
32
+
33
+ def __init__(self, **kwargs):
34
+ """Custom init to handle validation before dataclass assigns values"""
35
+ # Validate provided arguments
36
+ self.validate_args(kwargs)
37
+
38
+ # Assign validated values using the normal dataclass behavior
39
+ for field_ in fields(self):
40
+ field_name = field_.name
41
+ setattr(self, field_name, kwargs.get(field_name, getattr(self, field_name)))
42
+
43
+ def validate_args(self, inputted_kwargs):
44
+ """Check for unexpected arguments and suggest corrections before Python validation"""
45
+ default_fields = {field.name : field.type for field in fields(self)} #{'title': <class 'str'>, 'message': <class 'str'>,...
46
+ allowed_fields_keys = set(default_fields.keys())
47
+
48
+ # Identify invalid arguments
49
+ invalid_args = set(inputted_kwargs) - allowed_fields_keys
50
+ if invalid_args:
51
+ suggestions = []
52
+ for arg in invalid_args:
53
+ closest_match = difflib.get_close_matches(arg, allowed_fields_keys, n=1, cutoff=0.6)
54
+ if closest_match:
55
+ suggestions.append(f"* '{arg}' is invalid -> Did you mean '{closest_match[0]}'?")
56
+ else:
57
+ suggestions.append(f"* '{arg}' is not a valid argument.")
58
+
59
+ suggestion_text = '\n'.join(suggestions)
60
+ raise ValueError(f"Invalid arguments provided:\n{suggestion_text}")
61
+
62
+ # Validating types
63
+ for each_arg in inputted_kwargs.keys():
64
+ expected_type = default_fields[each_arg]
65
+ actual_value = inputted_kwargs[each_arg]
66
+
67
+ # Allow both int and float for progress_current_value
68
+ if each_arg == "progress_current_value":
69
+ if not isinstance(actual_value, (int, float)):
70
+ raise TypeError(f"Expected '{each_arg}' to be int or float, got {type(actual_value)} instead.")
71
+ else:
72
+ if not isinstance(actual_value, expected_type):
73
+ raise TypeError(f"Expected '{each_arg}' to be {expected_type}, got {type(actual_value)} instead.")
74
+
75
+ # Validate `style` values
76
+ style_values = [value for key, value in vars(NotificationStyles).items() if not key.startswith("__")]
77
+ if 'style' in inputted_kwargs and inputted_kwargs['style'] not in ['',*style_values]:
78
+ inputted_style=inputted_kwargs['style']
79
+ allowed_styles=', '.join(style_values)
80
+ raise ValueError(
81
+ f"Invalid style '{inputted_style}'. Allowed styles: {allowed_styles}"
82
+ )
@@ -1,7 +1,6 @@
1
1
  """ Non-Advanced Stuff """
2
2
  import random
3
3
  import os
4
- print("This function has deprecated use Notification() class")
5
4
  ON_ANDROID = False
6
5
  try:
7
6
  from jnius import autoclass,cast # Needs Java to be installed
@@ -1,6 +1,6 @@
1
1
  """Contains Safe way to call Styles"""
2
2
 
3
- class NotificationStyles():
3
+ class NotificationStyles:
4
4
  """ Safely Adding Styles"""
5
5
  DEFAULT = "simple"
6
6
 
@@ -12,5 +12,5 @@ class NotificationStyles():
12
12
  BIG_PICTURE = "big_picture"
13
13
  BOTH_IMGS = "both_imgs"
14
14
 
15
- MESSAGING = "messaging" # TODO
16
- CUSTOM = "custom" # TODO
15
+ # MESSAGING = "messaging" # TODO
16
+ # CUSTOM = "custom" # TODO
@@ -5,7 +5,7 @@ import os
5
5
  import threading
6
6
  import re
7
7
  from .styles import NotificationStyles
8
- from .facade import BaseNotification
8
+ from .base import BaseNotification
9
9
  DEV=0
10
10
  ON_ANDROID = False
11
11
 
@@ -31,7 +31,7 @@ try:
31
31
  ON_ANDROID = True
32
32
  except Exception as e:# pylint: disable=W0718
33
33
  MESSAGE='This Package Only Runs on Android !!! ---> Check "https://github.com/Fector101/android_notify/" to see design patterns and more info.' # pylint: disable=C0301
34
- print(MESSAGE if DEV else '')
34
+ # print(MESSAGE) Already Printing in core.py
35
35
 
36
36
  # This is so no crashes when developing on PC
37
37
  def run_on_ui_thread(func):
@@ -55,8 +55,7 @@ if ON_ANDROID:
55
55
  NotificationCompatBigPictureStyle = autoclass('androidx.core.app.NotificationCompat$BigPictureStyle') # pylint: disable=C0301
56
56
  NotificationCompatInboxStyle = autoclass('androidx.core.app.NotificationCompat$InboxStyle')
57
57
  except Exception as e:# pylint: disable=W0718
58
- print(e if DEV else '','Import Fector101')
59
- # print(e if DEV else '')
58
+ print(e)
60
59
  print("""
61
60
  Dependency Error: Add the following in buildozer.spec:
62
61
  * android.gradle_dependencies = androidx.core:core-ktx:1.15.0, androidx.core:core:1.6.0
@@ -91,46 +90,28 @@ class Notification(BaseNotification):
91
90
  button_ids=[0]
92
91
  btns_box={}
93
92
  main_functions={}
94
- style_values=[
95
- '','simple',
96
- 'progress','big_text',
97
- 'inbox', 'big_picture',
98
- 'large_icon','both_imgs',
99
- 'custom'
100
- ] # TODO make pattern for non-android Notifications
101
- defaults={
102
- 'title':'Default Title',
103
- 'message':'Default Message',
104
- 'style':'simple',
105
- 'big_picture_path':'',
106
- 'large_icon_path':'',
107
- 'progress_max_value': 0.5,
108
- 'progress_current_value': 0.5,
109
- 'body':'',
110
- 'channel_name':'Default Channel',
111
- 'channel_id':'default_channel',
112
- 'logs':True,
113
- "identifer": '',
114
- 'callback': None,
115
- 'app_icon': 'Defaults to package app icon'
116
- }
117
93
 
118
94
  # During Development (When running on PC)
119
- logs=not ON_ANDROID
95
+ BaseNotification.logs=not ON_ANDROID
120
96
  def __init__(self,**kwargs): #pylint: disable=W0231 #@dataclass already does work
121
- self.__validateArgs(kwargs)
122
- # Private (Don't Touch)
97
+ super().__init__(**kwargs)
98
+
123
99
  self.__id = self.__getUniqueID()
124
- self.__setArgs(kwargs)
125
100
  self.__update_timer = None # To Track progressbar last update (According to Android Docs Don't update bar to often, I also faced so issues when doing that)
126
-
101
+ self.__formatChannel(kwargs)
127
102
  if not ON_ANDROID:
128
103
  return
129
104
  # TODO make send method wait for __asks_permission_if_needed method
130
105
  self.__asks_permission_if_needed()
131
106
  self.notification_manager = context.getSystemService(context.NOTIFICATION_SERVICE)
132
107
  self.__builder=NotificationCompatBuilder(context, self.channel_id)# pylint: disable=E0606
133
-
108
+ def showInfiniteProgressBar(self):
109
+ """Displays an (Infinite) progress Bar in Notification, that continues loading indefinitely.
110
+ Can be Removed By `removeProgressBar` Method
111
+ """
112
+ self.__builder.setProgress(0,0, True)
113
+ self.__dispatchNotification()
114
+
134
115
  def updateTitle(self,new_title):
135
116
  """Changes Old Title
136
117
 
@@ -142,7 +123,7 @@ class Notification(BaseNotification):
142
123
  print(f'new notification title: {self.title}')
143
124
  if ON_ANDROID:
144
125
  self.__builder.setContentTitle(String(self.title))
145
- self.notification_manager.notify(self.__id, self.__builder.build())
126
+ self.__dispatchNotification()
146
127
 
147
128
  def updateMessage(self,new_message):
148
129
  """Changes Old Message
@@ -155,63 +136,75 @@ class Notification(BaseNotification):
155
136
  print(f'new notification message: {self.message}')
156
137
  if ON_ANDROID:
157
138
  self.__builder.setContentText(String(self.message))
158
- self.notification_manager.notify(self.__id, self.__builder.build())
139
+ self.__dispatchNotification()
159
140
 
160
- def updateProgressBar(self,current_value,message:str=''):
141
+ def updateProgressBar(self,current_value:int,message:str='',title:str=''):
161
142
  """Updates progress bar current value
162
143
 
163
144
  Args:
164
- current_value (str): the value from progressbar current progress
145
+ current_value (int): the value from progressbar current progress
165
146
  message (str): defaults to last message
147
+ title (str): defaults to last title
166
148
 
167
- NOTE: There is a 0.5sec delay
149
+ NOTE: There is a 0.5sec delay, if updating title,msg with progressbar frequenlty pass them in too to avoid update issues
168
150
  """
169
151
 
170
152
  # Cancel any existing timer before setting a new one
171
153
  if self.__update_timer:
172
- return False
173
-
174
- def delayed_update():
154
+ self.__update_timer.cancel()
175
155
  self.__update_timer = None
156
+
157
+ def delayed_update():
158
+ if self.__update_timer is None:
159
+ # Ensure we are not executing an old timer
160
+ return
176
161
  if self.logs:
177
162
  print(f'Progress Bar Update value: {current_value}')
178
163
 
179
164
  self.progress_current_value = current_value
180
165
 
181
166
  if not ON_ANDROID:
182
- return False
167
+ return
183
168
  self.__builder.setProgress(self.progress_max_value, current_value, False)
184
169
  if message:
185
170
  self.updateMessage(message)
186
- self.notification_manager.notify(self.__id, self.__builder.build())
187
- return True
171
+ if title:
172
+ self.updateTitle(title)
173
+ self.__dispatchNotification()
174
+ self.__update_timer = None
175
+
188
176
 
189
177
  # Start a new timer that runs after 0.5 seconds
190
178
  self.__update_timer = threading.Timer(0.5, delayed_update)
191
179
  self.__update_timer.start()
192
-
193
- def removeProgressBar(self,message='',show_on_update=True) -> None:
180
+
181
+ def removeProgressBar(self,message='',show_on_update=True, title:str='') -> None:
194
182
  """Removes Progress Bar from Notification
195
183
 
196
184
  Args:
197
185
  message (str, optional): notification message. Defaults to 'last message'.
198
186
  show_on_update (bool, optional): To show notification brifely when progressbar removed. Defaults to True.
187
+ title (str, optional): notification title. Defaults to 'last title'.
199
188
  """
200
189
  if self.__update_timer:
201
190
  self.__update_timer.cancel()
202
191
  self.__update_timer = None
203
192
 
204
193
  if self.logs:
205
- print(f'removed progress bar with message: {self.message}')
194
+ msg = message or self.message
195
+ title_=title or self.title
196
+ print(f'removed progress bar with message: {msg} and title: {title_}')
206
197
 
207
198
  if not ON_ANDROID:
208
199
  return False
209
200
 
210
201
  self.__builder.setOnlyAlertOnce(not show_on_update)
211
202
  if message:
212
- self.__builder.setContentText(String(message))
203
+ self.updateMessage(message)
204
+ if title:
205
+ self.updateTitle(title)
213
206
  self.__builder.setProgress(0, 0, False)
214
- self.notification_manager.notify(self.__id, self.__builder.build())
207
+ self.__dispatchNotification()
215
208
  return True
216
209
 
217
210
  def send(self,silent:bool=False,persistent=False,close_on_click=True):
@@ -225,13 +218,18 @@ class Notification(BaseNotification):
225
218
  self.silent=self.silent or silent
226
219
  if ON_ANDROID:
227
220
  self.__startNotificationBuild(persistent,close_on_click)
228
- self.notification_manager.notify(self.__id, self.__builder.build())
229
- elif self.logs:
221
+ self.__dispatchNotification()
222
+ if self.logs:
230
223
  string_to_display=''
231
224
  print("\n Sent Notification!!!")
232
225
  for name,value in vars(self).items():
233
226
  if value and name in ["title", "message", "style", "body", "large_icon_path", "big_picture_path", "progress_current_value", "progress_max_value", "channel_name"]:
234
- string_to_display += f'\n {name}: {value}'
227
+ if name == "progress_max_value":
228
+ if self.style == NotificationStyles.PROGRESS:
229
+ string_to_display += f'\n {name}: {value}'
230
+ else:
231
+ string_to_display += f'\n {name}: {value}'
232
+
235
233
  string_to_display +="\n (Won't Print Logs When Complied,except if selected `Notification.logs=True`)"
236
234
  print(string_to_display)
237
235
  if DEV:
@@ -292,7 +290,7 @@ class Notification(BaseNotification):
292
290
  """
293
291
  if ON_ANDROID:
294
292
  self.__builder.mActions.clear()
295
- self.notification_manager.notify(self.__id, self.__builder.build())
293
+ self.__dispatchNotification()
296
294
  if self.logs:
297
295
  print('Removed Notication Buttons')
298
296
 
@@ -331,60 +329,17 @@ class Notification(BaseNotification):
331
329
  self.__buildImg(self.large_icon_path, NotificationStyles.LARGE_ICON)
332
330
 
333
331
  elif style == NotificationStyles.PROGRESS:
334
- self.__builder.setContentTitle(String(self.title))
335
- self.__builder.setContentText(String(self.message))
336
332
  self.__builder.setProgress(self.progress_max_value, self.progress_current_value, False)
337
333
 
338
334
  if already_sent:
339
- self.notification_manager.notify(self.__id, self.__builder.build())
335
+ self.__dispatchNotification()
340
336
 
341
337
  return True
342
338
  # elif style == 'custom':
343
339
  # self.__builder = self.__doCustomStyle()
344
340
 
345
- def __validateArgs(self,inputted_kwargs):
346
-
347
- def checkInReference(inputted_keywords,accepteable_inputs,input_type):
348
- def singularForm(plural_form):
349
- return plural_form[:-1]
350
- invalid_args= set(inputted_keywords) - set(accepteable_inputs)
351
- if invalid_args:
352
- suggestions=[]
353
- for arg in invalid_args:
354
- closest_match = difflib.get_close_matches(arg,accepteable_inputs,n=2,cutoff=0.6)
355
- if closest_match:
356
- suggestions.append(f"* '{arg}' Invalid -> Did you mean '{closest_match[0]}'? ") # pylint: disable=C0301
357
- else:
358
- suggestions.append(f"* {arg} is not a valid {singularForm(input_type)}.")
359
- suggestion_text='\n'.join(suggestions)
360
- hint_msg=singularForm(input_type) if len(invalid_args) < 2 else input_type
361
-
362
- raise ValueError(f"Invalid {hint_msg} provided: \n\t{suggestion_text}\n\t* list of valid {input_type}: [{', '.join(accepteable_inputs)}]")
363
-
364
- allowed_keywords=self.defaults.keys()
365
- inputted_keywords_=inputted_kwargs.keys()
366
- checkInReference(inputted_keywords_,allowed_keywords,'arguments')
367
-
368
- # Validate style values
369
- if 'style' in inputted_keywords_ and inputted_kwargs['style'] not in self.style_values:
370
- checkInReference([inputted_kwargs['style']],self.style_values,'values')
371
-
372
- def __setArgs(self,options_dict:dict):
373
- non_string_keys=['progress_max_value','progress_current_value','callback','logs']
374
-
375
- for key,value in options_dict.items():
376
- if key not in non_string_keys: # Fixing Types
377
- value = str(value)
378
- if key == 'channel_name' and value.strip():
379
- setattr(self,key, value[:40])
380
- elif key == 'channel_id' and value.strip(): # If user input's a channel id (i format properly)
381
- setattr(self,key, self.__generate_channel_id(value))
382
- else:
383
- setattr(self,key, value if value else self.defaults[key])
384
-
385
- if "channel_id" not in options_dict and 'channel_name' in options_dict: # if User doesn't input channel id but inputs channel_name
386
- setattr(self,'channel_id', self.__generate_channel_id(options_dict['channel_name']))
387
-
341
+ def __dispatchNotification(self):
342
+ self.notification_manager.notify(self.__id, self.__builder.build())
388
343
  def __startNotificationBuild(self,persistent,close_on_click):
389
344
  self.__createBasicNotification(persistent,close_on_click)
390
345
  if self.style not in ['simple','']:
@@ -415,7 +370,7 @@ class Notification(BaseNotification):
415
370
  self.__builder.setOngoing(persistent)
416
371
  self.__builder.setAutoCancel(close_on_click)
417
372
  self.__addIntentToOpenApp()
418
-
373
+
419
374
  # def __doCustomStyle(self):
420
375
  # # TODO Will implement when needed
421
376
  # return self.__builder
@@ -424,7 +379,7 @@ class Notification(BaseNotification):
424
379
  self.__setIconFromBitmap(self.app_icon)
425
380
  else:
426
381
  self.__builder.setSmallIcon(context.getApplicationInfo().icon)
427
-
382
+
428
383
  def __buildImg(self, user_img,img_style):
429
384
  if user_img.startswith('http://') or user_img.startswith('https://'):
430
385
  def callback(bitmap):
@@ -438,7 +393,7 @@ class Notification(BaseNotification):
438
393
  bitmap = self.__getImgFromPath(user_img)
439
394
  if bitmap:
440
395
  self.__applyNotificationImage(bitmap,img_style)
441
-
396
+
442
397
  def __setIconFromBitmap(self,img_path):
443
398
  """Path can be link or relative path"""
444
399
  if img_path.startswith('http://') or img_path.startswith('https://'):
@@ -458,8 +413,7 @@ class Notification(BaseNotification):
458
413
  if self.logs:
459
414
  print('Failed getting img for custom notification icon defaulting to app icon')
460
415
  self.__builder.setSmallIcon(context.getApplicationInfo().icon)
461
-
462
-
416
+
463
417
  def __getImgFromPath(self, relative_path):
464
418
  app_folder=os.path.join(app_storage_path(),'app') # pylint: disable=possibly-used-before-assignment
465
419
  output_path = os.path.join(app_folder, relative_path)
@@ -499,25 +453,6 @@ class Notification(BaseNotification):
499
453
  print('Error Type ',e)
500
454
  print('Failed to get Bitmap from URL ',traceback.format_exc())
501
455
 
502
- # def __getImgFromURL(self,url,img_style):
503
- # if self.logs:
504
- # print("getting image from URL---")
505
- # try:
506
- # URL = autoclass('java.net.URL')
507
- # url = URL(url)
508
- # connection = url.openConnection()
509
- # connection.connect()
510
- # input_stream = connection.getInputStream()
511
- # bitmap = BitmapFactory.decodeStream(input_stream)
512
- # input_stream.close()
513
- # if bitmap:
514
- # self.__applyNotificationImage(bitmap,img_style)
515
-
516
- # except Exception as e:
517
- # # TODO get type of JAVA Error
518
- # print('Error Type ',e)
519
- # print('Failed to get Img from URL ',traceback.format_exc())
520
-
521
456
  @run_on_ui_thread
522
457
  def __applyNotificationImage(self,bitmap,img_style):
523
458
  if self.logs:
@@ -528,7 +463,7 @@ class Notification(BaseNotification):
528
463
  self.__builder.setStyle(big_picture_style)
529
464
  elif img_style == NotificationStyles.LARGE_ICON:
530
465
  self.__builder.setLargeIcon(bitmap)
531
- self.notification_manager.notify(self.__id, self.__builder.build())
466
+ self.__dispatchNotification()
532
467
  if self.logs:
533
468
  print('Done adding image to notification-------')
534
469
  except Exception as e:
@@ -553,24 +488,6 @@ class Notification(BaseNotification):
553
488
  if not all(check_permission(p) for p in permissions):
554
489
  request_permissions(permissions,on_permissions_result) # pylint: disable=E0606
555
490
 
556
- def __generate_channel_id(self,channel_name: str) -> str:
557
- """
558
- Generate a readable and consistent channel ID from a channel name.
559
-
560
- Args:
561
- channel_name (str): The name of the notification channel.
562
-
563
- Returns:
564
- str: A sanitized channel ID.
565
- """
566
- # Normalize the channel name
567
- channel_id = channel_name.strip().lower()
568
- # Replace spaces and special characters with underscores
569
- channel_id = re.sub(r'[^a-z0-9]+', '_', channel_id)
570
- # Remove leading/trailing underscores
571
- channel_id = channel_id.strip('_')
572
- return channel_id[:50]
573
-
574
491
  def __addIntentToOpenApp(self):
575
492
  intent = Intent(context, PythonActivity)
576
493
  action = str(self.identifer) or f"ACTION_{self.__id}"
@@ -597,6 +514,36 @@ class Notification(BaseNotification):
597
514
  self.button_ids.append(btn_id)
598
515
  return btn_id
599
516
 
517
+ def __formatChannel(self, inputted_kwargs):
518
+ if 'channel_name' in inputted_kwargs:
519
+ cleaned_name = inputted_kwargs['channel_name'].strip()
520
+ self.channel_name = cleaned_name[:40] if cleaned_name else 'Default Channel'
521
+
522
+ if 'channel_id' in inputted_kwargs:
523
+ cleaned_id = inputted_kwargs['channel_id'].strip()
524
+ self.channel_id = self.__generate_channel_id(cleaned_id) if cleaned_id else 'default_channel'
525
+ elif 'channel_name' in inputted_kwargs:
526
+ # Generate channel_id from channel_name if only channel_name is provided
527
+ generated_id = self.__generate_channel_id(inputted_kwargs['channel_name'])
528
+ self.channel_id = generated_id
529
+
530
+ def __generate_channel_id(self,channel_name: str) -> str:
531
+ """
532
+ Generate a readable and consistent channel ID from a channel name.
533
+
534
+ Args:
535
+ channel_name (str): The name of the notification channel.
536
+
537
+ Returns:
538
+ str: A sanitized channel ID.
539
+ """
540
+ # Normalize the channel name
541
+ channel_id = channel_name.strip().lower()
542
+ # Replace spaces and special characters with underscores
543
+ channel_id = re.sub(r'[^a-z0-9]+', '_', channel_id)
544
+ # Remove leading/trailing underscores
545
+ channel_id = channel_id.strip('_')
546
+ return channel_id[:50]
600
547
 
601
548
  class NotificationHandler:
602
549
  """For Notification Operations """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: android-notify
3
- Version: 1.54.1
3
+ Version: 1.56
4
4
  Summary: A Python package that simpilfies creating Android notifications in Kivy apps.
5
5
  Home-page: https://github.com/fector101/android-notify
6
6
  Author: Fabian
@@ -464,12 +464,22 @@ args
464
464
 
465
465
  - new_message : String to be set as New notification Message
466
466
 
467
+ ### Instance.showInfiniteProgressBar
468
+
469
+ Displays an Infinite Progress Bar in Notification, Can be Removed using `removeProgressBar` and updated using `updateProgressBar` method
470
+
467
471
  ### Instance.updateProgressBar
468
472
 
473
+ if updating title,msg with progressbar frequenlty pass them in too to avoid update issues.
474
+ According to android docs updates shouldn't be too frequent.
475
+
476
+ `updateProgressBar` has a built-in delay of 0.5 secs
477
+
469
478
  args
470
479
 
471
480
  - current_value (str): the value from progressbar current progress
472
481
  - message (str,optional): defaults to last message
482
+ - title (str,optional): defaults to last title
473
483
 
474
484
  ### Instance.removeProgressBar
475
485
 
@@ -479,6 +489,7 @@ args
479
489
 
480
490
  - message (str, optional): notification message, Defaults to 'last message'.
481
491
  - show_on_update (bool, optional): To show notification brifely when progressbar removed. Defaults to True.
492
+ - title (str, optional): notification title, Defaults to 'last title'.
482
493
 
483
494
  ### Instance.addNotificationStyle
484
495
 
@@ -2,8 +2,8 @@ README.md
2
2
  setup.py
3
3
  android_notify/__init__.py
4
4
  android_notify/__main__.py
5
+ android_notify/base.py
5
6
  android_notify/core.py
6
- android_notify/facade.py
7
7
  android_notify/styles.py
8
8
  android_notify/sword.py
9
9
  android_notify.egg-info/PKG-INFO
@@ -6,7 +6,7 @@ with open("README.md", "r", encoding="utf-8") as readme_data:
6
6
 
7
7
  setup(
8
8
  name="android-notify",
9
- version="1.54.1",
9
+ version="1.56",
10
10
  author="Fabian",
11
11
  author_email='fector101@yahoo.com',
12
12
  description="A Python package that simpilfies creating Android notifications in Kivy apps.",
@@ -1,28 +0,0 @@
1
- """Assists Notification Class with Args auto-complete: keeps sub class cleaner"""
2
- from dataclasses import dataclass
3
-
4
- @dataclass
5
- class BaseNotification:
6
- """Encapsulator"""
7
-
8
- # Basic options
9
- title: str = ''
10
- message: str = ''
11
- style: str = 'simple'
12
-
13
- # Style specfic attributes
14
- big_picture_path: str = ''
15
- large_icon_path: str = ''
16
- progress_max_value: int = 100
17
- progress_current_value: int = 0
18
- body: str = ''
19
-
20
- # For Nofitication Functions
21
- identifer: str = ''
22
- callback: object = None
23
-
24
- # Advance Options
25
- channel_name: str = 'Default Channel'
26
- channel_id: str = 'default_channel'
27
- silent: bool = False
28
- app_icon: str = 'Defaults to package app icon'
File without changes