Skip to content

Commit

Permalink
Merge branch 'albayan_beta' of https://github.com/tecwindow/albayan i…
Browse files Browse the repository at this point in the history
…nto albayan_beta
  • Loading branch information
ZeinabBahaa committed Dec 11, 2024
2 parents d51e53a + a287bf9 commit 66145dd
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 61 deletions.
63 changes: 44 additions & 19 deletions core_functions/athkar/athkar_scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
from random import choice
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
from sqlalchemy.exc import IntegrityError
from sqlalchemy.exc import IntegrityError
from .athkar_db_manager import AthkarDBManager
from .athkar_refresher import AthkarRefresher
from utils.audio_player import AthkarPlayer
from utils.const import Globals
from utils.logger import Logger
from exceptions.base import ErrorMessage

class AthkarScheduler:
def __init__(self, athkar_db_folder: Union[Path, str], default_category_path: Optional[Union[Path, str]] = None, text_athkar_path: Optional[Union[Path, str]] = None, default_category_settings: Optional[Dict[str, int]] = None) -> None:
Expand All @@ -19,19 +21,18 @@ def __init__(self, athkar_db_folder: Union[Path, str], default_category_path: Op
self.default_category_settings = default_category_settings if default_category_settings is not None else {}
self.categories = None
self.db_manager = AthkarDBManager(athkar_db_folder)
self.scheduler = BackgroundScheduler()
self.scheduler = BackgroundScheduler()
self.setup()

def setup(self) -> None:

if self.default_category_path:
self.default_category_path.mkdir(parents=True, exist_ok=True)
try:
category_id = self.db_manager.create_category("default", str(self.default_category_path), **self.default_category_settings)
if self.text_athkar_path and self.text_athkar_path.exists():
with open(self.text_athkar_path, encoding="UTF-8") as f:
self.db_manager.add_text_athkar(json.load(f), category_id)
except IntegrityError as e:
except IntegrityError:
pass

self.categories = self.db_manager.get_all_categories()
Expand All @@ -46,38 +47,62 @@ def audio_athkar_job(self, category_id: int, audio_path: str) -> None:
def text_athkar_job(self, category_id: int) -> None:
random_text_athkar = choice(self.db_manager.get_text_athkar(category_id))
text = random_text_athkar.text

if len(text) > 256:
# Extract the first 5 words for the title
title = " ".join(text.split()[:10])
description = " ".join(text.split()[10:])
else:
title = "البيان"
description = text

Globals.TRAY_ICON.showMessage(title, description, msecs=5000)

@staticmethod
def _parse_time(time_str: str) -> time:
time_ob = datetime.strptime(time_str, "%H:%M")
return time_ob
return datetime.strptime(time_str, "%H:%M").time()

def start(self,) -> None:
def _create_triggers(self, from_time: time, to_time: time, play_interval: int):
minute = "0" if play_interval == 60 else f"*/{play_interval}"

if from_time < to_time:
return [CronTrigger(minute=minute, hour=f"{from_time.hour}-{to_time.hour}")]
else:
return [
CronTrigger(minute=minute, hour=f"{from_time.hour}-23"),
CronTrigger(minute=minute, hour=f"0-{to_time.hour}")
]

def _add_jobs(self, category, trigger):
if category.audio_athkar_enabled:
self.scheduler.add_job(
self.audio_athkar_job,
trigger,
args=[category.id, category.audio_path],
id=f"audio_athkar_{category.id}_{trigger}"
)
if category.text_athkar_enabled:
self.scheduler.add_job(
self.text_athkar_job,
trigger,
args=[category.id],
id=f"text_athkar_{category.id}_{trigger}"
)

def start(self) -> None:
for category in self.categories:
from_time = self._parse_time(category.from_time)
to_time = self._parse_time(category.to_time)
minute = "0" if category.play_interval == 60 else f"*/{category.play_interval}"
trigger = CronTrigger(minute=minute, hour=f"{from_time.hour}-{to_time.hour}")
if category.audio_athkar_enabled :
self.scheduler.add_job(self.audio_athkar_job, trigger, args=[category.id, category.audio_path])
if category.text_athkar_enabled:
self.scheduler.add_job(self.text_athkar_job, trigger, args=[category.id])
try:
from_time = self._parse_time(category.from_time)
to_time = self._parse_time(category.to_time)
triggers = self._create_triggers(from_time, to_time, category.play_interval)
for trigger in triggers:
self._add_jobs(category, trigger)
except Exception as e:
Logger.error(ErrorMessage(e))

if not self.scheduler.running:
self.scheduler.start()

def refresh(self) -> None:

if self.scheduler is not None:
self.scheduler.remove_all_jobs()

Expand Down
1 change: 1 addition & 0 deletions ui/dialogs/find.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ def initUI(self):
self.search_type_radio_quarter.toggled.connect(self.on_radio_toggled)

self.on_radio_toggled()
self.OnEdit()

def OnEdit(self):
self.search_button.setEnabled(bool(self.search_box.text()))
Expand Down
33 changes: 22 additions & 11 deletions ui/dialogs/info_dialog.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import sys
import json
import random
from PyQt6.QtWidgets import QDialog, QVBoxLayout, QLabel, QTextEdit, QPushButton, QApplication
from PyQt6.QtWidgets import QDialog, QVBoxLayout, QLabel, QTextEdit, QPushButton, QApplication, QMessageBox
from PyQt6.QtCore import QTimer
from PyQt6.QtGui import QKeySequence, QClipboard
from ui.widgets.qText_edit import ReadOnlyTextEdit
from utils.universal_speech import UniversalSpeech
from utils.const import Globals, data_folder
from exceptions.json import JSONFileNotFoundError, InvalidJSONFormatError
from exceptions.error_decorators import exception_handler

class InfoDialog(QDialog):
def __init__(self, parent, title: str, label: str, text: str, is_html_content: bool = False, show_message_button: bool = False):
Expand Down Expand Up @@ -44,7 +46,7 @@ def init_ui(self):

# Message to you button (conditionally added)
message_to_you_button = QPushButton('رسالة لك', self)
message_to_you_button.clicked.connect(self.message_to_you)
message_to_you_button.clicked.connect(self.OnNewMessage)
message_to_you_button.setShortcut(QKeySequence("Ctrl+M"))
message_to_you_button.setStyleSheet('background-color: red; color: white;')
message_to_you_button.setVisible(self.show_message_button)
Expand Down Expand Up @@ -82,14 +84,23 @@ def copy_text(self):
def reject(self):
Globals.effects_manager.play("clos")
self.deleteLater()

def choose_QuotesMessage(self):
file_path = data_folder/"quotes/QuotesMessages.json"
if not file_path.exists():
raise JSONFileNotFoundError(file_path)

try:
with open(file_path, "r", encoding="utf-8") as file:
quotes_list = json.load(file)
message = random.choice(quotes_list)
except json.JSONDecodeError as e:
raise InvalidJSONFormatError(file_path, e)

self.text_edit.setText(message)
UniversalSpeech.say(message)

def message_to_you(self):
# Load the quotes and choose a random message
with open(data_folder/"quotes/QuotesMessages.json", "r", encoding="utf-8") as file:
quotes_list = json.load(file)
new_message = random.choice(quotes_list)

# Update the text without opening a new dialog
self.text_edit.setText(new_message)
UniversalSpeech.say(new_message)
def OnNewMessage(self):
self.choose_QuotesMessage()
Globals.effects_manager.play("message")

42 changes: 19 additions & 23 deletions ui/quran_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
from utils.const import program_name, program_icon, user_db_path, data_folder, Globals
from utils.audio_player import bass, SoundEffectPlayer
from exceptions.error_decorators import exception_handler
from exceptions.json import JSONFileNotFoundError, InvalidJSONFormatError


class QuranInterface(QMainWindow):
Expand Down Expand Up @@ -214,16 +213,18 @@ def set_text_ctrl_label(self):
self.menu_bar.previous_action.setEnabled(back_status)
self.toolbar.navigation.reset_position()
self.toolbar.set_buttons_status()

def OnQuickAccess(self):

@exception_handler(ui_element=QMessageBox)
def OnQuickAccess(self, event):
dialog = QuickAccess(self, "الوصول السريع")
if not dialog.exec():
return

self.set_text_ctrl_label()
self.quran_view.setFocus()

def OnSearch(self):
@exception_handler(ui_element=QMessageBox)
def OnSearch(self, event):
search_dialog = SearchDialog(self, "بحث")
if search_dialog.exec():
self.set_text_ctrl_label()
Expand All @@ -236,6 +237,7 @@ def get_current_ayah_info(self) -> list:

return ayah_info

@exception_handler(ui_element=QMessageBox)
def OnInterpretation(self):

selected_category = self.sender().text()
Expand Down Expand Up @@ -296,15 +298,16 @@ def on_copy_verse(self):
UniversalSpeech.say("تم نسخ الآية.")
Globals.effects_manager.play("copy")


def OnSyntax(self):
@exception_handler(ui_element=QMessageBox)
def OnSyntax(self, event):
aya_info = self.get_current_ayah_info()
title = "إعراب آية رقم {} من {}".format(aya_info[3], aya_info[2])
label = "الإعراب"
text = E3rab(aya_info[0], aya_info[1]).text
InfoDialog(self, title, label, text).exec()

def OnVerseReasons(self):
@exception_handler(ui_element=QMessageBox)
def OnVerseReasons(self, event):
aya_info = self.get_current_ayah_info()
title = "أسباب نزول آية رقم {} من {}".format(aya_info[3], aya_info[2])
label = "الأسباب"
Expand All @@ -315,14 +318,16 @@ def OnVerseReasons(self):
else:
QMessageBox.information(self, "لا يتوفر معلومات للآية", "للأسف لا يتوفر في الوقت الحالي معلومات لهذه الآية.")

def OnAyahInfo(self):
@exception_handler(ui_element=QMessageBox)
def OnAyahInfo(self, event):
aya_info = self.get_current_ayah_info()
title = "معلومات آية رقم {} من {}".format(aya_info[3], aya_info[2])
label = "معلومات الآية:"
text = AyaInfo(aya_info[1]).text
InfoDialog(self, title, label, text, is_html_content=True).exec()

def OnSaveBookmark(self):

@exception_handler(ui_element=QMessageBox)
def OnSaveBookmark(self, event):

bookmark_manager= BookmarkManager()
aya_info = self.get_current_ayah_info()
Expand Down Expand Up @@ -365,17 +370,8 @@ def closeEvent(self, event):
self.tray_manager.hide_icon()
bass.BASS_Free()

@exception_handler(ui_element=QMessageBox )
@exception_handler(ui_element=QMessageBox)
def OnRandomMessages(self, event):
file_path = data_folder/"quotes/QuotesMessages.json"
if not file_path.exists():
raise JSONFileNotFoundError(file_path)

try:
with open(file_path, "r", encoding="utf-8") as file:
quotes_list = json.load(file)
message = random.choice(quotes_list)
except json.JSONDecodeError as e:
raise InvalidJSONFormatError(file_path, e)

InfoDialog(self, 'رسالة لك', '', message, is_html_content=False, show_message_button=True).exec()
info_dialog = InfoDialog(self, 'رسالة لك', '', "", is_html_content=False, show_message_button=True)
info_dialog.choose_QuotesMessage()
info_dialog.exec()
37 changes: 29 additions & 8 deletions utils/audio_player/bass_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,19 +102,40 @@ def decrease_volume(self, step: float = 0.01) -> None:
new_volume = max(self.volume - step, 0.0)
self.set_volume(new_volume)

def forward(self, seconds: float = 10) -> None:
def set_position(self, new_seconds: float) -> None:
"""Sets the playback position to the specified number of seconds."""
if self.current_channel:
new_seconds = max(0.0, new_seconds)

duration = bass.BASS_ChannelBytes2Seconds(self.current_channel, bass.BASS_ChannelGetLength( self.current_channel, 0))
new_seconds = min(new_seconds, duration)
new_position = bass.BASS_ChannelSeconds2Bytes(self.current_channel, new_seconds, 0)
bass.BASS_ChannelSetPosition(self.current_channel, new_position, 0)

def forward(self, seconds: float = 0.5) -> None:
"""Forwards the audio playback by the specified number of seconds."""
if self.current_channel:
current_position = bass.BASS_ChannelGetPosition(self.current_channel)
new_position = current_position + seconds
bass.BASS_ChannelSetPosition(self.current_channel, new_position)
current_position = bass.BASS_ChannelGetPosition(self.current_channel, 0)
if current_position == -1:
print("Error getting current position.")
return

current_seconds = bass.BASS_ChannelBytes2Seconds(self.current_channel, current_position)
new_seconds = current_seconds + seconds
new_seconds = 111
self.set_position(new_seconds)

def rewind(self, seconds: float = 10) -> None:
def rewind(self, seconds: float = 0.5) -> None:
"""Rewinds the audio playback by the specified number of seconds."""
if self.current_channel:
current_position = bass.BASS_ChannelGetPosition(self.current_channel)
new_position = max(0.0, current_position - seconds)
bass.BASS_ChannelSetPosition(self.current_channel, new_position)
current_position = bass.BASS_ChannelGetPosition(self.current_channel, 0)
if current_position == -1:
print("Error getting current position.")
return

current_seconds = bass.BASS_ChannelBytes2Seconds(self.current_channel, current_position)
new_seconds = current_seconds - seconds
self.set_position(new_seconds)

def get_playback_status(self) -> PlaybackStatus:
"""Returns the current playback status."""
Expand Down

0 comments on commit 66145dd

Please sign in to comment.