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.
- android_notify/__init__.py +4 -0
- android_notify/core.py +19 -14
- android_notify/styles.py +13 -3
- android_notify/sword.py +547 -0
- android_notify-1.5.dist-info/METADATA +452 -0
- android_notify-1.5.dist-info/RECORD +9 -0
- {android_notify-1.2.dist-info → android_notify-1.5.dist-info}/WHEEL +1 -1
- android_notify-1.2.dist-info/METADATA +0 -210
- android_notify-1.2.dist-info/RECORD +0 -8
- {android_notify-1.2.dist-info → android_notify-1.5.dist-info}/top_level.txt +0 -0
android_notify/__init__.py
CHANGED
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
|
-
|
|
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(
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
3
|
-
|
|
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
|
+
|
android_notify/sword.py
ADDED
|
@@ -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
|
+

|
|
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
|
+

|
|
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
|
+

|
|
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
|
+

|
|
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
|
+

|
|
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
|
+

|
|
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
|
+

|
|
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,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,,
|
|
File without changes
|