android-notify 1.59.4__py3-none-any.whl → 1.60.1__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.
- android_notify/__init__.py +4 -4
- android_notify/__main__.py +24 -0
- android_notify/an_types.py +226 -91
- android_notify/an_utils.py +129 -18
- android_notify/base.py +97 -90
- android_notify/config.py +145 -0
- android_notify/core.py +227 -139
- android_notify/styles.py +25 -22
- android_notify/sword.py +1043 -992
- {android_notify-1.59.4.dist-info → android_notify-1.60.1.dist-info}/METADATA +69 -12
- android_notify-1.60.1.dist-info/RECORD +13 -0
- android_notify-1.59.4.dist-info/RECORD +0 -12
- {android_notify-1.59.4.dist-info → android_notify-1.60.1.dist-info}/WHEEL +0 -0
- {android_notify-1.59.4.dist-info → android_notify-1.60.1.dist-info}/top_level.txt +0 -0
android_notify/base.py
CHANGED
|
@@ -1,90 +1,97 @@
|
|
|
1
|
-
"""Assists Notification Class with Args keeps subclass cleaner"""
|
|
2
|
-
from dataclasses import dataclass, fields
|
|
3
|
-
import difflib
|
|
4
|
-
from .styles import NotificationStyles
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
1
|
+
"""Assists Notification Class with Args keeps subclass cleaner"""
|
|
2
|
+
from dataclasses import dataclass, fields
|
|
3
|
+
import difflib
|
|
4
|
+
from .styles import NotificationStyles
|
|
5
|
+
# For Dev when creating new attr use have to set type for validate_args to work
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class BaseNotification:
|
|
9
|
+
"""Encapsulation"""
|
|
10
|
+
|
|
11
|
+
# Basic options
|
|
12
|
+
title: str = ''
|
|
13
|
+
message: str = ''
|
|
14
|
+
style: str = 'simple'
|
|
15
|
+
|
|
16
|
+
# Style-specific attributes
|
|
17
|
+
big_picture_path: str = ''
|
|
18
|
+
large_icon_path: str = ''
|
|
19
|
+
progress_max_value: int = 0
|
|
20
|
+
progress_current_value: float = 0.0 # Also Takes in Ints
|
|
21
|
+
body: str = ''
|
|
22
|
+
lines_txt: str = ''
|
|
23
|
+
|
|
24
|
+
# Notification Functions
|
|
25
|
+
name: str = ''
|
|
26
|
+
callback: object = None
|
|
27
|
+
|
|
28
|
+
# Advanced Options
|
|
29
|
+
id: int = 0
|
|
30
|
+
app_icon: str = 'Defaults to package app icon'
|
|
31
|
+
sub_text: str=''
|
|
32
|
+
|
|
33
|
+
# Channel related
|
|
34
|
+
channel_name: str = 'Default Channel'
|
|
35
|
+
"""User visible channel name"""
|
|
36
|
+
channel_id: str = 'default_channel'
|
|
37
|
+
"""Used to reference notification channel"""
|
|
38
|
+
|
|
39
|
+
silent: bool = False
|
|
40
|
+
logs: bool = False
|
|
41
|
+
|
|
42
|
+
# Custom Notification Attrs
|
|
43
|
+
title_color: str = ''
|
|
44
|
+
message_color: str = ''
|
|
45
|
+
|
|
46
|
+
def __init__(self, **kwargs):
|
|
47
|
+
"""Custom init to handle validation before dataclass assigns values"""
|
|
48
|
+
|
|
49
|
+
# Validate provided arguments
|
|
50
|
+
self.validate_args(kwargs)
|
|
51
|
+
|
|
52
|
+
# Assign validated values using the normal dataclass behavior
|
|
53
|
+
for field_ in fields(self):
|
|
54
|
+
field_name = field_.name
|
|
55
|
+
setattr(self, field_name, kwargs.get(field_name, getattr(self, field_name)))
|
|
56
|
+
|
|
57
|
+
def validate_args(self, inputted_kwargs):
|
|
58
|
+
"""Check for unexpected arguments and suggest corrections before Python validation"""
|
|
59
|
+
default_fields = {field.name : field.type for field in fields(self)} #{'title': <class 'str'>, 'message': <class 'str'>,...
|
|
60
|
+
allowed_fields_keys = set(default_fields.keys())
|
|
61
|
+
|
|
62
|
+
# Identify invalid arguments
|
|
63
|
+
invalid_args = set(inputted_kwargs) - allowed_fields_keys
|
|
64
|
+
if invalid_args:
|
|
65
|
+
suggestions = []
|
|
66
|
+
for arg in invalid_args:
|
|
67
|
+
closest_match = difflib.get_close_matches(arg, allowed_fields_keys, n=1, cutoff=0.6)
|
|
68
|
+
if closest_match:
|
|
69
|
+
suggestions.append(f"* '{arg}' is invalid -> Did you mean '{closest_match[0]}'?")
|
|
70
|
+
else:
|
|
71
|
+
suggestions.append(f"* '{arg}' is not a valid argument.")
|
|
72
|
+
|
|
73
|
+
suggestion_text = '\n'.join(suggestions)
|
|
74
|
+
raise ValueError(f"Invalid arguments provided:\n{suggestion_text}")
|
|
75
|
+
|
|
76
|
+
# Validating types
|
|
77
|
+
for each_arg in inputted_kwargs.keys():
|
|
78
|
+
expected_type = default_fields[each_arg]
|
|
79
|
+
actual_value = inputted_kwargs[each_arg]
|
|
80
|
+
|
|
81
|
+
# Allow both int and float for progress_current_value
|
|
82
|
+
if each_arg == "progress_current_value":
|
|
83
|
+
if not isinstance(actual_value, (int, float)):
|
|
84
|
+
raise TypeError(f"Expected '{each_arg}' to be int or float, got {type(actual_value)} instead.")
|
|
85
|
+
else:
|
|
86
|
+
if not isinstance(actual_value, expected_type):
|
|
87
|
+
raise TypeError(f"Expected '{each_arg}' to be {expected_type}, got {type(actual_value)} instead.")
|
|
88
|
+
|
|
89
|
+
# Validate `style` values
|
|
90
|
+
style_values = [value for key, value in vars(NotificationStyles).items() if not key.startswith("__")]
|
|
91
|
+
if 'style' in inputted_kwargs and inputted_kwargs['style'] not in ['',*style_values]:
|
|
92
|
+
inputted_style=inputted_kwargs['style']
|
|
93
|
+
allowed_styles=', '.join(style_values)
|
|
94
|
+
raise ValueError(
|
|
95
|
+
f"Invalid style '{inputted_style}'. Allowed styles: {allowed_styles}"
|
|
96
|
+
)
|
|
97
|
+
|
android_notify/config.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import os, traceback
|
|
2
|
+
|
|
3
|
+
ON_ANDROID = False
|
|
4
|
+
__version__ = "1.60.0"
|
|
5
|
+
|
|
6
|
+
def on_flet_app():
|
|
7
|
+
return os.getenv("MAIN_ACTIVITY_HOST_CLASS_NAME")
|
|
8
|
+
|
|
9
|
+
def get_activity_class_name():
|
|
10
|
+
ACTIVITY_CLASS_NAME = os.getenv("MAIN_ACTIVITY_HOST_CLASS_NAME") # flet python
|
|
11
|
+
if not ACTIVITY_CLASS_NAME:
|
|
12
|
+
try:
|
|
13
|
+
from android import config
|
|
14
|
+
ACTIVITY_CLASS_NAME = config.JAVA_NAMESPACE
|
|
15
|
+
except (ImportError, AttributeError):
|
|
16
|
+
ACTIVITY_CLASS_NAME = 'org.kivy.android'
|
|
17
|
+
return ACTIVITY_CLASS_NAME
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
if os.getenv("MAIN_ACTIVITY_HOST_CLASS_NAME"):
|
|
21
|
+
from jnius import cast, autoclass
|
|
22
|
+
else:
|
|
23
|
+
# print('Not on Flet android env...\n')
|
|
24
|
+
try:
|
|
25
|
+
import kivy #TODO find var for kivy
|
|
26
|
+
from jnius import cast, autoclass
|
|
27
|
+
except Exception as e:
|
|
28
|
+
print('android-notify: No pjnius, not on android')
|
|
29
|
+
# So commandline still works if java isn't installed and get pyjinus import error
|
|
30
|
+
# print('Exception occured in __init__.py: ',e)
|
|
31
|
+
cast = lambda x: x
|
|
32
|
+
autoclass = lambda x: None
|
|
33
|
+
try:
|
|
34
|
+
# Android Imports
|
|
35
|
+
|
|
36
|
+
# Get the required Java classes needs to on android to import
|
|
37
|
+
Bundle = autoclass('android.os.Bundle')
|
|
38
|
+
String = autoclass('java.lang.String')
|
|
39
|
+
Intent = autoclass('android.content.Intent')
|
|
40
|
+
PendingIntent = autoclass('android.app.PendingIntent')
|
|
41
|
+
BitmapFactory = autoclass('android.graphics.BitmapFactory')
|
|
42
|
+
BuildVersion = autoclass('android.os.Build$VERSION')
|
|
43
|
+
NotificationManager = autoclass('android.app.NotificationManager')
|
|
44
|
+
NotificationChannel = autoclass('android.app.NotificationChannel')
|
|
45
|
+
RemoteViews = autoclass('android.widget.RemoteViews')
|
|
46
|
+
|
|
47
|
+
ON_ANDROID = RemoteViews
|
|
48
|
+
except Exception as e:
|
|
49
|
+
from .an_types import *
|
|
50
|
+
if hasattr(e,'name') and e.name != 'android' :
|
|
51
|
+
print('Exception: ',e)
|
|
52
|
+
print(traceback.format_exc())
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
if ON_ANDROID:
|
|
56
|
+
try:
|
|
57
|
+
NotificationManagerCompat = autoclass('androidx.core.app.NotificationManagerCompat')
|
|
58
|
+
NotificationCompat = autoclass('androidx.core.app.NotificationCompat')
|
|
59
|
+
IconCompat = autoclass('androidx.core.graphics.drawable.IconCompat')
|
|
60
|
+
Color = autoclass('android.graphics.Color')
|
|
61
|
+
|
|
62
|
+
# Notification Design
|
|
63
|
+
NotificationCompatBuilder = autoclass('androidx.core.app.NotificationCompat$Builder')
|
|
64
|
+
NotificationCompatBigTextStyle = autoclass('androidx.core.app.NotificationCompat$BigTextStyle')
|
|
65
|
+
NotificationCompatBigPictureStyle = autoclass('androidx.core.app.NotificationCompat$BigPictureStyle')
|
|
66
|
+
NotificationCompatInboxStyle = autoclass('androidx.core.app.NotificationCompat$InboxStyle')
|
|
67
|
+
NotificationCompatDecoratedCustomViewStyle = autoclass('androidx.core.app.NotificationCompat$DecoratedCustomViewStyle')
|
|
68
|
+
|
|
69
|
+
except Exception as dependencies_import_error:
|
|
70
|
+
print('dependencies_import_error: ',dependencies_import_error)
|
|
71
|
+
print("""
|
|
72
|
+
Dependency Error: Add the following in buildozer.spec:
|
|
73
|
+
* android.gradle_dependencies = androidx.core:core-ktx:1.15.0, androidx.core:core:1.6.0
|
|
74
|
+
* android.enable_androidx = True
|
|
75
|
+
* android.permissions = POST_NOTIFICATIONS
|
|
76
|
+
""")
|
|
77
|
+
|
|
78
|
+
from .an_types import *
|
|
79
|
+
else:
|
|
80
|
+
from .an_types import *
|
|
81
|
+
|
|
82
|
+
def from_service_file():
|
|
83
|
+
return 'PYTHON_SERVICE_ARGUMENT' in os.environ
|
|
84
|
+
|
|
85
|
+
run_on_ui_thread = None
|
|
86
|
+
if on_flet_app() or from_service_file() or not ON_ANDROID:
|
|
87
|
+
def run_on_ui_thread(func):
|
|
88
|
+
"""Fallback for Developing on PC"""
|
|
89
|
+
|
|
90
|
+
def wrapper(*args, **kwargs):
|
|
91
|
+
# print("Simulating run on UI thread")
|
|
92
|
+
return func(*args, **kwargs)
|
|
93
|
+
|
|
94
|
+
return wrapper
|
|
95
|
+
else:# TODO find var for kivy
|
|
96
|
+
from android.runnable import run_on_ui_thread
|
|
97
|
+
|
|
98
|
+
def get_python_activity():
|
|
99
|
+
if not ON_ANDROID:
|
|
100
|
+
from .an_types import PythonActivity
|
|
101
|
+
return PythonActivity
|
|
102
|
+
ACTIVITY_CLASS_NAME = get_activity_class_name()
|
|
103
|
+
if from_service_file():
|
|
104
|
+
PythonActivity = autoclass(ACTIVITY_CLASS_NAME + '.PythonService')
|
|
105
|
+
elif on_flet_app():
|
|
106
|
+
PythonActivity = autoclass(ACTIVITY_CLASS_NAME)
|
|
107
|
+
else:
|
|
108
|
+
PythonActivity = autoclass(ACTIVITY_CLASS_NAME + '.PythonActivity')
|
|
109
|
+
return PythonActivity
|
|
110
|
+
|
|
111
|
+
def get_python_activity_context():
|
|
112
|
+
if not ON_ANDROID:
|
|
113
|
+
from .an_types import Context
|
|
114
|
+
return Context
|
|
115
|
+
|
|
116
|
+
PythonActivity = get_python_activity()
|
|
117
|
+
if from_service_file():
|
|
118
|
+
service = PythonActivity.mService
|
|
119
|
+
context = service.getApplication().getApplicationContext()
|
|
120
|
+
# context = PythonActivity.mService
|
|
121
|
+
else:
|
|
122
|
+
context = PythonActivity.mActivity
|
|
123
|
+
return context
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
if ON_ANDROID:
|
|
127
|
+
context = get_python_activity_context()
|
|
128
|
+
else:
|
|
129
|
+
context = None
|
|
130
|
+
|
|
131
|
+
def get_notification_manager():
|
|
132
|
+
if not ON_ANDROID:
|
|
133
|
+
return None
|
|
134
|
+
notification_service = context.getSystemService(context.NOTIFICATION_SERVICE)
|
|
135
|
+
return cast(NotificationManager, notification_service)
|
|
136
|
+
|
|
137
|
+
def app_storage_path():
|
|
138
|
+
if on_flet_app():
|
|
139
|
+
return os.path.join(context.getFilesDir().getAbsolutePath(), 'flet')
|
|
140
|
+
else:
|
|
141
|
+
try:
|
|
142
|
+
from android.storage import app_storage_path as kivy_app_storage_path # type: ignore
|
|
143
|
+
return kivy_app_storage_path()
|
|
144
|
+
except Exception as e:
|
|
145
|
+
return './' # TODO return file main.py path (not android)
|