diff --git a/assets/img/flag/cnr.png b/assets/img/flag/cnr.png new file mode 100644 index 000000000..67cf6d3f0 Binary files /dev/null and b/assets/img/flag/cnr.png differ diff --git a/assets/img/flag/he.png b/assets/img/flag/he.png index bc8a0b589..e87d1b062 100644 Binary files a/assets/img/flag/he.png and b/assets/img/flag/he.png differ diff --git a/lib/l10n/intl_ar.arb b/lib/l10n/intl_ar.arb index 91efe82db..37677ffdc 100644 --- a/lib/l10n/intl_ar.arb +++ b/lib/l10n/intl_ar.arb @@ -172,7 +172,7 @@ "@duaaElEftarText": { "description": "اللهم اني لگ صمت وعلى رزقك افطرت واليك انبت وعليگ توكلت ذهب الظما وابتلت العروق وثبت الاجر انشاء الله" }, - "secondaryScreenExplanation": "غرفة الصلاة الثانوية )غرفة النساء أو طابق آخر على سبيل المثال(، ستظهر هذه الشاشة البث المباشر للجمعة إذا تم تفعيله على حساب MAWAQIT", + "secondaryScreenExplanation": "غرفة الصلاة الثانوية (غرفة النساء أو طابق آخر على سبيل المثال)، ستظهر هذه الشاشة البث المباشر للجمعة إذا تم تفعيله على حساب MAWAQIT", "mainScreenExplanation": "غرفة المسجد الرئيسية، هذه الشاشة لن تظهر البث المباشر للجمعة", "normalModeExplanation": "ستظهر الشاشة العادية مع أوقات الصلاة والإعلانات.", "announcementOnlyModeExplanation": "ستظهر الإعلانات طوال الوقت", @@ -365,6 +365,21 @@ "installingUpdate": "جارٍ تنزيل التحديث...", "updateCompletedSuccessfully": "تم التحديث بنجاح", "updateFailed": "فشل التحديث", + "save": "حفظ", + "enterRtspUrl": "أدخل رابط RTSP أو YouTube Live", + "addRtspUrl": "أضف رابط بث كاميرا RTSP الخاص بك أدناه", + "enableRtspCamera": "تفعيل بث الكاميرا", + "rtspCameraSettings": "إعدادات الكاميرا", + "invalidRtspUrl": "رابط RTSP غير صالح. يرجى التحقق من الرابط والمحاولة مرة أخرى.", + "validRtspUrl": "تم التحقق من رابط RTSP وحفظه بنجاح.", + "rtspCameraSettingTitle": "اتصال الكاميرا المباشر", + "rtspCameraSettingDesc": "اتصل بالكاميرا المحلية واعرض بث صلاة الجمعة على شاشة التلفاز.", + "rtspCameraSettingScreenDesc": "إذا أدخلت رابطًا هنا، ستتحول شاشتك تلقائيًا إلى وضع بث الفيديو عند وصول وقت الجمعة.", + "validatingStream": "جارٍ التحقق من البث...", + "checkInternetLiveCamera": "يجب عليك الاتصال بالإنترنت لإعداد الكاميرا ", + "somethingWentWrong": "حدث خطأ ما! يرجى المحاولة مرة أخرى", + "somethingWrong": "حدث خطأ ما", + "tryAgainLater": "يرجى المحاولة لاحقًا", "checkInternetUpdate": "يجب عليك الاتصال بالإنترنت للتحقق من وجود تحديثات جديدة", "appUpdateAvailable": "تطبيقك يعمل بالإصدار {currentVersion}. تحديث جديد (الإصدار {updatedVersion}) متوفر مع أحدث الميزات والتحسينات.", "@appUpdateAvailable": { diff --git a/lib/l10n/intl_cnr.arb b/lib/l10n/intl_cnr.arb new file mode 100644 index 000000000..d3faff89f --- /dev/null +++ b/lib/l10n/intl_cnr.arb @@ -0,0 +1,369 @@ +{ + "@@locale": "cnr", + "home": "Početna", + "share": "Podijelite", + "about": "O aplikaciji", + "rate": "Ocijenite nas", + "languages": "Jezik", + "appLang": "Jezik aplikacije", + "descLang": "Molimo odaberite željeni jezik", + "hadithLangDesc": "This override your choice in the admin console, you can chose a different language by screen", + "whoops": "Upss!", + "noInternet": "Nemate internetsku mrezu", + "tryAgain": "Pokušajte ponovo", + "closeApp": "Zatvorite aplikaciju", + "quit": "Izlaz", + "forceStaging": "Prijeđite na prikaz", + "disableStaging": "Prijeđite na modus izrade", + "sureCloseApp": "Da li ste sigurni da želite izaći iz aplikacije?", + "ok": "U redu", + "cancel": "Otkaži", + "darkMode": "Tamni način prikaza", + "lightMode": "Svijetli način prikaza", + "changeMosque": "Promijeni džamiju", + "in1": "za", + "sec": "Sek", + "online": "Online", + "missingMosqueId": "Nedostaje MAWAQIT #ID ili #ID džamije", + "mosqueIdIsNotValid": "Izvinite, {mosqueId} ID džamije nije validan", + "selectMosqueId": "Molimo Vas unesite ID Džamije", + "mawaqitWelcome": "Dobro došli u MAWAQIT", + "mawaqitDesc": "Esselamu alejkum, Allah vas nagradio što ste izabrali MAWAQIT-a, prve svjetske mreže #1 pametnih džamija, koju koriste milioni muslimana širom svijeta u više od 85 zemalja od 2016. godine.\n\nPružamo vam najnapredniji Smart Mosque Display, dostupan na više uređaja (mobilni, pametni sat, TV ekrani), bez prikupljanja ili dijeljenja vaših ličnih podataka.\n\nMolimo podržite ovaj blagoslovljeni projekat ovdje: https://donate.mawaqit.net\n\nMi smo neprofitna organizacija, a ovaj projekat je \"Vakuf fi'sabili Allah\" (Posvećena zadužbina).\n\nVaše donacije čine ovaj projekat dostupnim svima, bilo gdje, potpuno BESPLATNO, BEZ OGLASA, i to bez MJESEČNE PRETPLATE.\n\nOvaj projekat ne bi bio moguć bez Allahove pomoći koji je okupio strastvenu zajednicu talentiranih i strastvenih volontera, koji rade danonoćno kako bi vam pružili najbolju moguću uslugu, i najsuvremenijeg sistema koji je dostupan 24/7.\n\nMolimo vas da razmislite o doniranju kako bismo svi skupa nastavili ovaj blagoslovljeni projekat. Allah Vas nagradio za Vaše povjerenje i podršku.", + "privacyPolicy": "Politika privatnosti", + "termsOfService": "Uslovi korištenja", + "installationGuide": "Vodič za instalaciju", + "drawerTitle": "MAWAQIT", + "drawerDesc": "Povezivanje muslimana sa džamijama", + "backendError": "Nažalost, ne možemo se povezati sa serverom.\nProvjerite internetsku vezu ili pokušajte ponovo kasnije.", + "selectWithMosqueId": "Pokušajte: 256, to je ID od 'Grande Mosquée de Paris'", + "searchForMosque": "Koju džamiju tražite? (ID, Ime, Grad, Poštanski broj...)", + "searchMosque": "Potražite džamiju", + "mosqueNameError": "Unesite naziv (ime) džamije", + "slugError": "Džamijski ''Id'' nije validan", + "doYouKnowMosqueId": "Da li znate vaš ID instalacije ili ID vaše džamije?", + "yes": "Da", + "no": "Ne", + "networkStatus": "Status mreže", + "mosqueNoMore": "Nema više rezultata", + "mosqueNoResults": "Bez rezultata", + "offline": "Offline", + "imsak": "Zora(Imsak)", + "jumua": "Džuma", + "duhr": "Podne", + "fajr": "Sabah", + "asr": "Ikindija", + "maghrib": "Akšam", + "isha": " Jacija", + "afterAdhanHadithTitle": "Dova poslije ezana", + "afterSalahHadith": "Allahumme rabbe hazihid-da’vetit-tammeti vessalatil-kaimeti ati muhammedenil-vessilete velfadilete veb-ashu mekamen mahmudenil-lezi ve’at tehu.\n\n(Moj Allahu, Gospodaru ovog potpunog poziva i namaza koji se upravo uspostavlja podari Muhammedu, (sallallahu alejhi ve sellem), Vesilet i svako dobro i proživi ga na pohvalnom mjestu koje si mu obećao.)", + "alIqama": "Ikamet", + "alAdhan": "Ezan", + "turnOfPhones": "Molimo vas da svoje telefone prebacite u NEČUJNI način rada", + "iqamaIn": "Ikamet za", + "alAthkar": "Dhikr", + "azkarList0": "Estagfirullah, Estagfirullah, Estagfirullah. \n\nAllahumme entes-selamu ve minkes-selam, tebarekte ja zel-dželali vel-ikram!\n\n''Gospodaru naš, Ti si izvor spasa i od Tebe je spas. Uzvišen si Ti, o Gospodaru veličine i plemenitosti''\n(Muslim, Ebu Davud, Tirmizi, Nesa’i, Ibn Madže, Taberani, Darimi)", + "@azkarList0": { + "description": "أَسْـتَغْفِرُ الله، أَسْـتَغْفِرُ الله، أَسْـتَغْفِرُ الله اللّهُـمَّ أَنْـتَ السَّلامُ ، وَمِـنْكَ السَّلام ، تَبارَكْتَ يا ذا الجَـلالِ وَالإِكْـرام اللَّهُمَّ أَعِنِّي عَلَى ذِكْرِكَ وَشُكْرِكَ وَحُسْنِ عِبَادَتِكَ" + }, + "azkarList1": "Subhanellahi – 33 puta; El-Hamdu lillahi – 33 puta; Allahu ekber – 33 puta (Buhari, Muslim, Nesa’i); ili svaku od ove tri riječi po 11 puta.\nLa ilahe illellahu vahdehu la šerike leh, lehu-l-mulku ve lehu-l-hamdu ve huve ‘ala kulli šej-in kadir", + "@azkarList1": { + "description": "سُـبْحانَ اللهِ، والحَمْـدُ لله، واللهُ أكْـبَر 33 مرة لا إِلَٰهَ إلاّ اللّهُ وَحْـدَهُ لا شريكَ لهُ، لهُ الملكُ ولهُ الحَمْد، وهُوَ على كُلّ شَيءٍ قَـدير" + }, + "azkarList2": "", + "@azkarList2": { + "description": "بِسۡمِ ٱللَّهِ ٱلرَّحۡمَٰنِ ٱلرَّحِيمِ قُلۡ أَعُوذُ بِرَبِّ ٱلنَّاسِ ، مَلِكِ ٱلنَّاسِ ، إِلَٰهِ ٱلنَّاسِ ، مِن شَرِّ ٱلۡوَسۡوَاسِ ٱلۡخَنَّاسِ ، ٱلَّذِي يُوَسۡوِسُ فِي صُدُورِ ٱلنَّاسِ ، مِنَ ٱلۡجِنَّةِ وَٱلنَّاس" + }, + "azkarList3": "", + "@azkarList3": { + "description": "بِسۡمِ ٱللَّهِ ٱلرَّحۡمَٰنِ ٱلرَّحِيمِقُلۡ أَعُوذُ بِرَبِّ ٱلۡفَلَقِ ، مِن شَرِّ مَا خَلَقَ ، وَمِن شَرِّ غَاسِقٍ إِذَا وَقَبَ ، وَمِن شَرِ ٱلنَّفَّٰثَٰتِ فِي ٱلۡعُقَدِ ، وَمِن شَرِّ حَاسِدٍ إِذَا حَسَدَ" + }, + "azkarList4": "", + "@azkarList4": { + "description": "بِسۡمِ ٱللَّهِ ٱلرَّحۡمَٰنِ ٱلرَّحِيمِ قُلۡ هُوَ ٱللَّهُ أَحَدٌ ، ٱللَّهُ ٱلصَّمَدُ ، لَمۡ يَلِدۡ وَلَمۡ يُولَدۡ ، وَلَمۡ يَكُن لَّهُۥ كُفُوًا أَحَدُۢ" + }, + "azkarList5": "", + "@azkarList5": { + "description": "ٱللَّهُ لَآ إِلَٰهَ إِلَّا هُوَ ٱلۡحَيُّ ٱلۡقَيُّومُۚ لَا تَأۡخُذُهُۥ سِنَةٞ وَلَا نَوۡمٞۚ لَّهُۥ مَا فِي ٱلسَّمَٰوَٰتِ وَمَا فِي ٱلۡأَرۡضِۗ مَن ذَا ٱلَّذِي يَشۡفَعُ عِندَهُۥٓ إِلَّا بِإِذۡنِهِۦۚ يَعۡلَمُ مَا بَيۡنَ أَيۡدِيهِمۡ وَمَا خَلۡفَهُمۡۖ وَلَا يُحِيطُونَ بِشَيۡءٖ مِّنۡ عِلۡمِهِۦٓ إِلَّا بِمَا شَآءَۚ وَسِعَ كُرۡسِيُّهُ ٱلسَّمَٰوَٰتِ وَٱلۡأَرۡضَۖ وَلَا يَ‍ُٔودُهُۥ حِفۡظُهُمَاۚ وَهُوَ ٱلۡعَلِيُّ ٱلۡعَظِيمُ" + }, + "azkarList6": "La ilahe illellahu vahdehu la šerike leh, lehu-l-mulku ve lehu-l-hamdu ve huve ‘ala kulli šej-in kadir", + "@azkarList6": { + "description": "لا إِلَٰهَ إلاّ اللّهُ وحدَهُ لا شريكَ لهُ، لهُ المُـلْكُ ولهُ الحَمْد، وهوَ على كلّ شَيءٍ قَدير، اللّهُـمَّ لا مانِعَ لِما أَعْطَـيْت، وَلا مُعْطِـيَ لِما مَنَـعْت، وَلا يَنْفَـعُ ذا الجَـدِّ مِنْـكَ الجَـد" + }, + "azkarList7": "''Allahu, Ti si moj Gospodar! Nema boga osim Tebe! Ti si me stvorio, i ja sam Tvoj rob! Držim se, koliko god mogu, svoje obaveze (prema Tebi) i svoga obećanja Tebi! Od Tebe tražim zaštitu od zla onoga što sam uradio! Priznajem Tvoje blagodati prema meni, i priznajem svoje grijehe! Oprosti mi, jer, doista, niko osim Tebe ne može grijehe oprostiti.''", + "@azkarList7": { + "description": "اللهم أنت ربي، لا إله إلا أنت، خلقتني وأنا عبدُك, وأنا على عهدِك ووعدِك ما استطعتُ، أعوذ بك من شر ما صنعتُ، أبوءُ لَكَ بنعمتكَ عَلَيَّ، وأبوء بذنبي، فاغفر لي، فإنه لا يغفرُ الذنوب إلا أنت" + }, + "azkarList8": "Osvanusmo i osvanu svekolika Allahova vlast. Hvala Allahu i nema boga osim Njega Jedinog, Koji druga nema. Njemu pripada sva vlast i svaka pohvala i on nad svačim ima moć. Allahu, molim Te za sva dobra ovog dana i za dobra poslije njega. I utječem ti se od zla ovog dana i zla poslije njega. Gospodaru moj, Tebi se sklanjam pred lijenošću i zlohudom ohološću. Tebi se sklanjam pred kaznom Džehennema i kaburskom kaznom. (Muslim)", + "@azkarList8": { + "description": "أصبحنا وأصبح الملك لله، والحمد لله ولا إله إلا الله وحده لا شريك له، له الملك وله الحمد، وهو على كل شيء قدير، أسألك خير ما في هذا اليوم، وخير ما بعده، وأعوذ بك من شر هذا اليوم، وشر ما بعده، وأعوذ بك من الكسل وسوء الكبر، وأعوذ بك من عذاب النار وعذاب القبر" + }, + "azkarList9": "\"Allahu moj, jutros svjedočim pred Tobom, pred onima koji nose Tvoj Arš, pred Tvojim melekima i svim Tvojim stvorenjima da si Ti Allah, nema boga osim Tebe, Jedini, koji nemaš ortaka, i da je Muhammed Tvoj rob i Tvoj poslanik\" (četiri puta). A kada nastupi večer, kaže se: 'Allahu moj, večeras svjedočim...'", + "@azkarList9": { + "description": "اللَّهُمَّ إِنِّي أَصْبَحْتُ أُشْهِدُكَ، وَأُشْهِدُ حَمَلَةَ عَرْشِكَ، وَمَلاَئِكَتِكَ، وَجَمِيعَ خَلْقِكَ، أَنَّكَ أَنْتَ اللَّهُ لَا إِلَهَ إِلاَّ أَنْتَ وَحْدَكَ لاَ شَرِيكَ لَكَ، وَأَنَّ مُحَمَّداً عَبْدُكَ وَرَسُولُكَ |أربعَ مَرَّات|. [ وإذا أمسى قال: اللَّهم إني أمسيت...]" + }, + "azkarList10": "\"Allahu moj, podari mi zdravlje u tijelu, Allahu moj, podari mi zdravlje u sluhu, Allahu moj, podari mi zdravlje u očima, nema boga osim Tebe. Allahu, utječem Ti se od nevjerovanja i siromaštva, i tražim utočište od kaburske patnje, nema boga osim Tebe\" (izgovara se 3 puta)", + "@azkarList10": { + "description": "|اللَّهُمَّ عَافِنِي فِي بَدَنِي، اللَّهُمَّ عَافِنِي فِي سَمْعِي، اللَّهُمَّ عَافِنِي فِي بَصَرِي، لاَ إِلَهَ إِلاَّ أَنْتَ. اللَّهُمَّ إِنِّي أَعُوذُ بِكَ مِنَ الْكُفْرِ، وَالفَقْرِ، وَأَعُوذُ بِكَ مِنْ عَذَابِ القَبْرِ، لاَ إِلَهَ إِلاَّ أَنْتَ |ثلاثَ مرَّاتٍ" + }, + "azkarList11": "\"Dovoljan mi je Allah, nema boga osim Njega. Na Njega se oslanjam, i On je Gospodar Prijestolja veličanstvenog.\"\n(ponavlja se 7 puta)", + "@azkarList11": { + "description": "|حَسْبِيَ اللَّهُ لاَ إِلَهَ إِلاَّ هُوَ عَلَيهِ تَوَكَّلتُ وَهُوَ رَبُّ الْعَرْشِ الْعَظِيمِ |سَبْعَ مَرّاتٍ" + }, + "azkarList12": "Zadovoljan sam da mi je Allah Gospodar, vjera islam, Muhammed, a. s., vjerovjesnik. (Ponavlja se 3 puta)", + "@azkarList12": { + "description": "|رَضِيتُ بِاللَّهِ رَبَّاً، وَبِالْإِسْلاَمِ دِيناً، وَبِمُحَمَّدٍ صلى الله عليه وسلم نَبِيّاً |ثلاثَ مرَّاتٍ" + }, + "azkarList13": "Nema boga osim Allaha, Jedinoga, Koji druga nema. Njemu pripada vlast nad svim i sva pohvala. On sve može. (Ponavlja se 10 puta)", + "@azkarList13": { + "description": "|لاَ إِلَهَ إِلاَّ اللَّهُ وَحْدَهُ لاَ شَرِيكَ لَهُ، لَهُ الْمُلْكُ وَلَهُ الْحَمْدُ، وَهُوَ عَلَى كُلِّ شَيْءٍ قَدِيرٌ |عشرَ مرَّات" + }, + "jumuaaScreenTitle": "Vrijeme džuma namaza", + "jumuaaHadith": "Allahov Poslanik s. a. vs., je rekao: „Ko opere svoju glavu i okupa se petkom, potom porani na džumu-namaz idući pješice, ne koristeći prijevozno sredstvo, zatim se približi imamu i pažljivo sluša, imaće nagradu za svaki učinjeni korak godinu dana posta i namaza.“\n\nHadis-Sabirači Sunena", + "shuruk": "Izlazak sunca", + "reset": "Resetiraj", + "mosqueNotFoundMessage": "Žao nam je, vaša džamija nije pronađena ili je privremeno neaktivna.", + "noInternetMessage": "Nemate pristup internetu. Provjerite svoju internetsku vezu i pokušajte ponovo. Da li je vaš Wi-Fi ili Internet povezan?", + "error": "Greška", + "mosqueErrorMessage": "Greška, ako ste administrator džamije kontaktirajte Mawaqit da riješimo ovaj problem", + "muharram": "Muharram", + "safar": "Safer", + "rabiAlawwal": "Rebiul-evvelv", + "rabiAlthani": "Rebiul-ahir", + "jumadaAlula": "Džumadel-ula", + "jumadaAlakhirah": "Džumadel-uhra", + "rajab": "Redžeb", + "shaban": "Ša’ban", + "ramadan": "Ramazan", + "shawwal": "Ševval", + "dhuAlqidah": "Dhul-ka’de", + "dhuAlhijjah": "Dhul-hidždže", + "duaaBetweenSalahAndAdhan": "Enes b. Malik, radijallahu anhu, prenosi da je Poslanik, sallallahu alejhi ve sellem, rekao: “Dova između ezana i ikameta se ne odbija.” \n(Ebu Davud, Sunen, 521).", + "salatKhayrMinaNawm": "Namaz je bolji od spavanja", + "salatElEid": "Bajram- namaz", + "webView": "Omogući naslijeđeni način rada", + "developersHomeScreen": "Početni ekrana programera", + "onlineHome": "Online početna stranica", + "prayerTimes": "Namaska vremena", + "alerts": "Upozorenje", + "iqamaaCountDown": "Ikamet odbrojavanje", + "afterAdhanHadith": "Dova poslije ezana", + "afterSalahAzkar": "Dhikr poslije namaza", + "iqama": "Ikamet", + "randomHadith": "Hadis", + "announcement": "Obavještenja", + "jumuaaLive": "Džuma [Uživo prijenos]", + "showSecondaryScreen": "Koristite kao sekundarni zaslon (za obavještenja)", + "normalScreen": "Koristite kao glavni ekran", + "duaaRemainder": "Dova", + "fajrWakeUp": "Ustani, Sabah namaz", + "changeLanguage": "Promijeni jezik", + "forceScreen": "Ne gasi ekran", + "clear": "Ukloni", + "changeTheme": "Promijeni temu", + "next": "Dalje", + "mainScreenOrSecondaryScreen": "Orijentacija ekrana", + "mainScreenOrSecondaryScreenEXPLINATION": "Da li želite da instalirate ovaj ekran u glavnu prostoriju za namaz (prostorija za muškarce)?", + "mainScreen": "Glavni ekran", + "secondaryScreen": "Sekundarni ekran", + "duaaElEftar": "Dova Iftara", + "announcementOnlyMode": "Režim obavještenja", + "normalMode": "Normalni način ", + "announcementOnlyModeEXPLINATION": "Odaberite hoće li vaš ekran stalno prikazivati ​​najave i obavještenja, ovo može biti korisno ako na primjer instalirate ekran na ulazu.", + "duaaElEftarText": "", + "@duaaElEftarText": { + "description": "اللهم اني لگ صمت وعلى رزقك افطرت واليك انبت وعليگ توكلت ذهب الظما وابتلت العروق وثبت الاجر انشاء الله" + }, + "secondaryScreenExplanation": "Za sekundarnu prostoriju za namaz (ženska prostorija ili neki drugi sprat), ovaj ekran će prikazati Džumu-namaz uživo", + "mainScreenExplanation": "Za sekundarnu prostoriju za namaz (ženska prostorija ili neki drugi sprat), ovaj ekran će prikazati Džumu-namaz uživo", + "normalModeExplanation": "Prikazaće normalan ekran sa vremenima namaza i najavama(obavještenja).", + "announcementOnlyModeExplanation": "Stalno će prikazivati ​​obavještenja", + "orientation": "Orijentacija", + "selectYourMawaqitTvAppOrientation": "Odaberite orijentaciju ekrana za Mawaqit tv aplikaciju", + "deviceDefault": "Zadani uređaj", + "deviceDefaultBTNDescription": "Mawaqit će automatski odabrati zadanu orijentaciju na osnovu orijentacije ekrana", + "portrait": "Vertikalni prikaz", + "portraitBTNDescription": "Vertikalnu orijentaciju preporučuje se za džamije sa malim prostorom", + "landscape": "Horizontalni prikaz", + "landscapeBTNDescription": "Horizontalna orijentacija je glavni izgled za Mawaqit tv aplikaciju i preporučeni prikaz za većinu džamija", + "eidMubarak": "Bajram Šerif Mubarek Olsun! \nHajrli Bajram", + "takbeerAleidText": "Allahu Akber, Allahu Akber, Allahu Akber, la ilahe il-la Allah, Allahu Akber, Allahu Akber, ve lillahi el-hamd", + "settings": "Settings", + "applicationModes": "Način primjene", + "ifYouAreFacingAnIssueWithTheAppActivateThis": "Ako imate problema s aplikacijom, pokušajte aktivirati ovu opciju", + "hijriAdjustments": "Podešavanje hidžretskog datuma na vašem uređaju", + "hijriAdjustmentsDescription": "Ispravite hidžretski datum na svom uređaju. Ovo neće utjecati na opšte postavke u net platformi", + "backoffice_default": "Backoffice zadano", + "recommended": "Preporučeno", + "sabah": "Sabah", + "randomHadithLanguage": "Slučajni jezik hadisa", + "en": "English", + "fr": "French", + "ar": "Arabic", + "tr": "Turkish", + "de": "German", + "es": "Spanish", + "pt": "Portuguese", + "nl": "Dutch", + "fr_ar": "French & Arabic", + "en_ar": "English & Arabic", + "de_ar": "German & Arabic", + "ta_ar": "Tamil & Arabic", + "tr_ar": "Turkish & Arabic", + "es_ar": "Spanish & Arabic", + "pt_ar": "Portuguese & Arabic", + "nl_ar": "Dutch & Arabic", + "connectToChangeHadith": "Please connect to the internet to change the hadith language.", + "retry": "Retry", + "timeSetting": "Configuring the time", + "timeSettingDesc": "Podešavanje vremena", + "selectedTime": "Trenutno odabrano vrijeme", + "confirmation": "Potvrda", + "confirmationMessage": "Jeste li sigurni da želite koristiti vrijeme uređaja?", + "useDeviceTime": "Koristite vrijeme uređaja", + "selectTime": "Odaberite vrijeme", + "previous": "\nPrethodno", + "appTimezone": "Vremenska zona aplikacije", + "descTimezone": "Odaberite svoju vremensku zonu kako biste dobili tačna namaska vremena.", + "appWifi": "Povežite se na wifi", + "descWifi": "Povežite se na željeni wifi", + "searchCountries": "Pretražite zemlje", + "scanAgain": "Skeniraj ponovno", + "noScannedResultsFound": "Nema pronađenih pristupnih tačaka", + "connect": "Povežite se", + "wifiPassword": "Password", + "skip": "Preskoči", + "noSSID": "**Skriveni SSID**", + "close": "Zatvori", + "search": "Traži", + "wifiSuccess": "Uspješno povezan na WiFi.", + "wifiFailure": "Povezivanje na WiFi nije uspjelo.", + "timezoneSuccess": "Vremenska zona je uspješno postavljena.", + "timezoneFailure": "Postavljanje vremenske zone nije uspjelo.", + "screenLock": "Uključivanje/isključivanje ekrana", + "screenLockConfig": "Konfigurišite ekran za uključivanje/isključivanje", + "screenLockMode": "Uključivanje/isključivanje ekrana", + "screenLockDesc": "Uključite/isključite TV prije i poslije svakog namaza, kako biste uštedjeli energiju", + "screenLockDesc2": "Ova funkcija uključuje/isključuje uređaj prije i poslije svakog namaza", + "before": "minuta prije svakog namaza", + "after": "minuta nakon svakog namaza", + "updateAvailable": "Dostupno je ažuriranje", + "seeMore": "Vidi više", + "whatIsNew": "Što je novo", + "update": "Ažuriraj", + "automaticUpdate": "Më njofto për UPDATE", + "automaticUpdateDescription": "Aktivizo përditësimin e njoftimit për të marrë veçoritë dhe përmirësimet më të fundit", + "checkInternetLegacyMode": "Duhet të lidheni me internetin për të përdorur modalitetin e vjetër", + "powerOnScreen": "Ndizni ekranin", + "powerOffScreen": "Fikni ekranin", + "deviceSettings": "Device Settings", + "later": "Më vonë", + "downloadQuran": "Shkarkoni Kur'anin", + "quran": "Kur'an", + "askDownloadQuran": "Dëshironi ta shkarkoni Kur'anin?", + "download": "Shkarkoni", + "downloadingQuran": "Shkarkimi i Kuranit", + "extractingQuran": "Nxjerrja e Kuranit", + "updatedQuran": "Kurani i përditësuar", + "quranLatestVersion": "Kurani është i përditësuar", + "quranUpdatedVersion": "Versioni i përditësuar i Kuranit është: {version}", + "quranIsUpdated": "Kurani është përditësuar", + "quranDownloaded": "Kurani u shkarkua", + "quranIsAlreadyDownloaded": "Kurani është shkarkuar tashmë", + "chooseReciter": "Zgjidhni Recituesin", + "reciteType": "Lloji i recitimit", + "readingMode": "dua të lexoj", + "listeningMode": "Unë dua të dëgjoj", + "quranReadingPage": "Faqja {leftPage} - {rightPage} / {totalPages}", + "@quranReadingPage": { + "description": "Placeholder text for displaying Quran reading page numbers", + "placeholders": { + "leftPage": { + "type": "int", + "example": "1" + }, + "rightPage": { + "type": "int", + "example": "2" + }, + "totalPages": { + "type": "int", + "example": "604" + } + } + }, + "quranReadingPagePortrait": "Page {currentPage} / {totalPages}", + "@quranReadingPagePortrait": { + "description": "Placeholder text for displaying Quran reading page portrait numbers", + "placeholders": { + "currentPage": { + "type": "int", + "example": "1" + }, + "totalPages": { + "type": "int", + "example": "604" + } + } + }, + "chooseQuranPage": "Zgjidhni faqen", + "checkingForUpdates": "Po kontrollon për përditësime...", + "chooseQuranType": "Zgjidhni Kuranin", + "hafs": "Hafs", + "warsh": "Versh", + "favorites": "Të preferuarat", + "allReciters": "Të gjithë Recituesit", + "reciterAddedToFavorites": "Recituesi {name} u shtua në të preferuarat", + "@reciterAddedToFavorites": { + "description": "Message shown when a reciter is added to favorites", + "placeholders": { + "name": { + "type": "String", + "example": "Abdul Basit" + } + } + }, + "reciterRemovedFromFavorites": "Recituesi {name} u hoq nga të preferuarat", + "@reciterRemovedFromFavorites": { + "description": "Message shown when a reciter is removed from favorites", + "placeholders": { + "name": { + "type": "String", + "example": "Abdul Basit" + } + } + }, + "noFavoriteReciters": "Nuk ka recitues të preferuar. Provoni të shtoni një në listë", + "@noFavoriteReciters": { + "description": "Message shown when there are no favorite reciters" + }, + "noReciterSearchResult": "No results found for your search", + "searchForReciter": "Search for a reciter", + "downloadAllSuwarSuccessfully": "The whole quran is downloaded", + "noSuwarDownload": "No new suwars to download", + "connectDownloadQuran": "Please connect to Internet to download", + "playInOnlineModeQuran": "Please connect to internet to play", + "downloaded": "Downloaded", + "switchQuranType": "Go to {name}", + "@switchQuranType": { + "description": "Message shown when a reciter is added to favorites", + "placeholders": { + "name": { + "type": "String", + "example": "Warsh" + } + } + }, + "surahSelector": "Select Surah", + "checkForUpdates": "Check for Updates", + "checkForNewVersion": "Check if a new version is available", + "wouldYouLikeToUpdate": "Would you like to update the app?", + "updateCompleted": "Update completed successfully!", + "noUpdates": "No Updates", + "usingLatestVersion": "You are using the latest version.", + "updateCancelled": "Update cancelled", + "checkingUpdates": "Checking updates...", + "downloadingUpdate": "Downloading update...", + "installingUpdate": "Installing update...", + "updateCompletedSuccessfully": "Update completed successfully", + "updateFailed": "Update failed" +} diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index bf9130cd8..237a0b3fd 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -365,6 +365,22 @@ "installingUpdate": "Installing update...", "updateCompletedSuccessfully": "Update completed successfully", "updateFailed": "Update failed", + "save":"Save", + "enterRtspUrl":"Enter RTSP or Youtube Live URL", + "addRtspUrl":"Add your camera stream URL below", + "enableRtspCamera":"Enable Camera Streaming", + "rtspCameraSettings":"Camera Settings", + "invalidRtspUrl":"Invalid URL. Please check the URL and try again.", + "validRtspUrl":"URL validated and saved successfully.", + "rtspCameraSettingTitle":"Live camera connection", + "rtspCameraSettingDesc":"Connect to your local camera and display jumua prayer stream on the TV screen.", + "rtspCameraSettingScreenDesc":"If you enter a URL here, your screen will automatically switch to video streaming when Jumua time arrives", + "validatingStream":"Validating Stream...", + "checkInternetLiveCamera": "You must connect to internet to setup the live camera", + "somethingWentWrong": "Something went wrong! please try again", + "somethingWrong": "Something went wrong", + "tryAgainLater": "Please try again later", + "hintTextRtspUrl": "rtsp://... or https://youtube.com/live/...", "checkInternetUpdate": "You must connect to internet to check for new updates", "appUpdateAvailable": "Your app is running version {currentVersion}. A new update (version {updatedVersion}) is available with the latest features and improvements.", "@appUpdateAvailable": { @@ -380,5 +396,4 @@ } } } - } diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index b565369e0..4cdadd2c7 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -24,17 +24,17 @@ "sec": "Sec", "online": "Connecté", "missingMosqueId": "Numéro d'identification MAWAQIT #ID ou MOSQUE #ID manquant", - "mosqueIdIsNotValid": "Désolé, le {mosqueId} n'est pas un id de mosquée valide", + "mosqueIdIsNotValid": "{mosqueId} n'est pas un id de mosquée valide", "selectMosqueId": "Veuillez saisir l'ID de votre MAWAQIT", "mawaqitWelcome": "Bienvenue sur MAWAQIT", - "mawaqitDesc": "Assalamu Alaikom, et Baraka'Allah fikom pour avoir choisi MAWAQIT, le premier et #1 réseau de mosquées intelligentes au monde, utilisé par des millions de musulmans dans le monde entier à travers une centaine de pays depuis 2016.\n\nNous vous fournissons l'affichage de mosquée intelligent le plus avancé, disponible sur plusieurs appareils (Mobile, Tablettes, Smartwatch, écrans de télévision), sans collecter ou partager vos données personnelles.\n\nNous sommes une organisation à but non lucratif, et ce projet est un \"Waqf fi'sabili Allah\" (dotation dédiée).\nVeuillez soutenir ce projet béni ici : https://donate.mawaqit.net\n\nVos dons permettent à ce projet d'être accessible à tous, partout, totalement GRATUITEMENT, SANS PUBLICITÉ, et SANS ABONNEMENT MENSUEL.\n\nCe projet ne serait pas possible sans l'aide d'Allah qui a rassemblé une communauté passionnée de bénévoles talentueux et passionnés, qui travaillent jour et nuit pour vous fournir le meilleur service possible, et un système à la pointe de la technologie disponible 7/24.\n\nVeuillez envisager de faire un don pour que ce projet béni puisse continuer. Baraka'Allah fikom pour votre confiance et votre soutien continus.", + "mawaqitDesc": "Assalamu Alaikom, et Baraka'Allah fikom pour avoir choisi MAWAQIT, le premier et #1 réseau de mosquées intelligentes au monde, utilisé par des millions de musulmans dans le monde entier à travers 85+ pays depuis 2016.\n\nNous vous fournissons l'affichage de mosquée intelligente le plus avancé, disponible sur plusieurs appareils (Mobile, Smartwatch, écrans de télévision), sans collecter ou partager vos données personnelles.\n\nVeuillez soutenir ce projet béni ici : https://donate.mawaqit.net\n\nNous sommes une organisation à but non lucratif, et ce projet est un \"Waqf fi'sabili Allah\" (dotation dédiée).\n\nVos dons permettent à ce projet d'être accessible à tous, partout, totalement GRATUITEMENT, SANS PUBLICITÉ, et SANS ABONNEMENT MENSUEL.\n\nCe projet ne serait pas possible sans l'aide d'Allah qui a rassemblé une communauté passionnée de bénévoles talentueux et passionnés, qui travaillent jour et nuit pour vous fournir le meilleur service possible, et un système à la pointe de la technologie disponible 24 heures sur 24, 7 jours sur 7.\n\nVeuillez envisager de faire un don pour que ce projet béni puisse continuer. Baraka'Allah fikom pour votre confiance et votre soutien continus.", "privacyPolicy": "Politique de confidentialité", "termsOfService": "Conditions générales d’utilisation", "installationGuide": "Guide d'installation", "drawerTitle": "MAWAQIT", "drawerDesc": "Connecting muslims to Mosques", "backendError": "Désolé, nous n'avons pas pu nous connecter au serveur.\nVeuillez vérifier votre connexion Internet ou réessayer plus tard.", - "selectWithMosqueId": "Votre ID se trouve dans votre espace utilisateur mawaqit.net", + "selectWithMosqueId": "Essayez: 256, c'est l'ID de 'Grande Mosquée de Paris'", "searchForMosque": "Quelle mosquée recherchez-vous ? (ID, nom, ville, code postal...)", "searchMosque": "Chercher une mosquée", "mosqueNameError": "Entrer le nom de la mosquée", @@ -88,36 +88,36 @@ "@azkarList6": { "description": "لا إِلَٰهَ إلاّ اللّهُ وحدَهُ لا شريكَ لهُ، لهُ المُـلْكُ ولهُ الحَمْد، وهوَ على كلّ شَيءٍ قَدير، اللّهُـمَّ لا مانِعَ لِما أَعْطَـيْت، وَلا مُعْطِـيَ لِما مَنَـعْت، وَلا يَنْفَـعُ ذا الجَـدِّ مِنْـكَ الجَـد" }, - "azkarList7": "اللهم أنت ربي، لا إله إلا أنت، خلقتني وأنا عبدُك, وأنا على عهدِك ووعدِك ما استطعتُ، أعوذ بك من شر ما صنعتُ، أبوءُ لَكَ بنعمتكَ عَلَيَّ، وأبوء بذنبي، فاغفر لي، فإنه لا يغفرُ الذنوب إلا أنت", + "azkarList7": "اللهم أنت ربي، لا إله إلا أنت، خلقتني وأنا عبدُك, وأنا على عهدِك ووعدِك ما استطعتُ، أعوذ بك من شر ما صنعتُ، أبوءُ لَكَ بنعمتكَ عَلَيَّ، وأبوء بذنبي، فاغفر لي، فإنه لا يغفرُ الذنوب إلا أنت", "@azkarList7": { "description": "اللهم أنت ربي، لا إله إلا أنت، خلقتني وأنا عبدُك, وأنا على عهدِك ووعدِك ما استطعتُ، أعوذ بك من شر ما صنعتُ، أبوءُ لَكَ بنعمتكَ عَلَيَّ، وأبوء بذنبي، فاغفر لي، فإنه لا يغفرُ الذنوب إلا أنت" }, - "azkarList8": "أصبحنا وأصبح الملك لله، والحمد لله ولا إله إلا الله وحده لا شريك له، له الملك وله الحمد، وهو على كل شيء قدير، أسألك خير ما في هذا اليوم، وخير ما بعده، وأعوذ بك من شر هذا اليوم، وشر ما بعده، وأعوذ بك من الكسل وسوء الكبر، وأعوذ بك من عذاب النار وعذاب القبر", + "azkarList8": "أصبحنا وأصبح الملك لله، والحمد لله ولا إله إلا الله وحده لا شريك له، له الملك وله الحمد، وهو على كل شيء قدير، أسألك خير ما في هذا اليوم، وخير ما بعده، وأعوذ بك من شر هذا اليوم، وشر ما بعده، وأعوذ بك من الكسل وسوء الكبر، وأعوذ بك من عذاب النار وعذاب القبر", "@azkarList8": { "description": "أصبحنا وأصبح الملك لله، والحمد لله ولا إله إلا الله وحده لا شريك له، له الملك وله الحمد، وهو على كل شيء قدير، أسألك خير ما في هذا اليوم، وخير ما بعده، وأعوذ بك من شر هذا اليوم، وشر ما بعده، وأعوذ بك من الكسل وسوء الكبر، وأعوذ بك من عذاب النار وعذاب القبر" }, - "azkarList9": "اللَّهُمَّ إِنِّي أَصْبَحْتُ أُشْهِدُكَ، وَأُشْهِدُ حَمَلَةَ عَرْشِكَ، وَمَلاَئِكَتِكَ، وَجَمِيعَ خَلْقِكَ، أَنَّكَ أَنْتَ اللَّهُ لَا إِلَهَ إِلاَّ أَنْتَ وَحْدَكَ لاَ شَرِيكَ لَكَ، وَأَنَّ مُحَمَّداً عَبْدُكَ وَرَسُولُكَ |أربعَ مَرَّات|. [ وإذا أمسى قال: اللَّهم إني أمسيت...]", + "azkarList9": "اللَّهُمَّ إِنِّي أَصْبَحْتُ أُشْهِدُكَ، وَأُشْهِدُ حَمَلَةَ عَرْشِكَ، وَمَلاَئِكَتِكَ، وَجَمِيعَ خَلْقِكَ، أَنَّكَ أَنْتَ اللَّهُ لَا إِلَهَ إِلاَّ أَنْتَ وَحْدَكَ لاَ شَرِيكَ لَكَ، وَأَنَّ مُحَمَّداً عَبْدُكَ وَرَسُولُكَ |أربعَ مَرَّات|. [ وإذا أمسى قال: اللَّهم إني أمسيت...]", "@azkarList9": { "description": "اللَّهُمَّ إِنِّي أَصْبَحْتُ أُشْهِدُكَ، وَأُشْهِدُ حَمَلَةَ عَرْشِكَ، وَمَلاَئِكَتِكَ، وَجَمِيعَ خَلْقِكَ، أَنَّكَ أَنْتَ اللَّهُ لَا إِلَهَ إِلاَّ أَنْتَ وَحْدَكَ لاَ شَرِيكَ لَكَ، وَأَنَّ مُحَمَّداً عَبْدُكَ وَرَسُولُكَ |أربعَ مَرَّات|. [ وإذا أمسى قال: اللَّهم إني أمسيت...]" }, - "azkarList10": "|اللَّهُمَّ عَافِنِي فِي بَدَنِي، اللَّهُمَّ عَافِنِي فِي سَمْعِي، اللَّهُمَّ عَافِنِي فِي بَصَرِي، لاَ إِلَهَ إِلاَّ أَنْتَ. اللَّهُمَّ إِنِّي أَعُوذُ بِكَ مِنَ الْكُفْرِ، وَالفَقْرِ، وَأَعُوذُ بِكَ مِنْ عَذَابِ القَبْرِ، لاَ إِلَهَ إِلاَّ أَنْتَ |ثلاثَ مرَّاتٍ", + "azkarList10": "|اللَّهُمَّ عَافِنِي فِي بَدَنِي، اللَّهُمَّ عَافِنِي فِي سَمْعِي، اللَّهُمَّ عَافِنِي فِي بَصَرِي، لاَ إِلَهَ إِلاَّ أَنْتَ. اللَّهُمَّ إِنِّي أَعُوذُ بِكَ مِنَ الْكُفْرِ، وَالفَقْرِ، وَأَعُوذُ بِكَ مِنْ عَذَابِ القَبْرِ، لاَ إِلَهَ إِلاَّ أَنْتَ |ثلاثَ مرَّاتٍ", "@azkarList10": { "description": "|اللَّهُمَّ عَافِنِي فِي بَدَنِي، اللَّهُمَّ عَافِنِي فِي سَمْعِي، اللَّهُمَّ عَافِنِي فِي بَصَرِي، لاَ إِلَهَ إِلاَّ أَنْتَ. اللَّهُمَّ إِنِّي أَعُوذُ بِكَ مِنَ الْكُفْرِ، وَالفَقْرِ، وَأَعُوذُ بِكَ مِنْ عَذَابِ القَبْرِ، لاَ إِلَهَ إِلاَّ أَنْتَ |ثلاثَ مرَّاتٍ" }, - "azkarList11": "|حَسْبِيَ اللَّهُ لاَ إِلَهَ إِلاَّ هُوَ عَلَيهِ تَوَكَّلتُ وَهُوَ رَبُّ الْعَرْشِ الْعَظِيمِ |سَبْعَ مَرّاتٍ", + "azkarList11": "|حَسْبِيَ اللَّهُ لاَ إِلَهَ إِلاَّ هُوَ عَلَيهِ تَوَكَّلتُ وَهُوَ رَبُّ الْعَرْشِ الْعَظِيمِ |سَبْعَ مَرّاتٍ", "@azkarList11": { "description": "|حَسْبِيَ اللَّهُ لاَ إِلَهَ إِلاَّ هُوَ عَلَيهِ تَوَكَّلتُ وَهُوَ رَبُّ الْعَرْشِ الْعَظِيمِ |سَبْعَ مَرّاتٍ" }, - "azkarList12": "|رَضِيتُ بِاللَّهِ رَبَّاً، وَبِالْإِسْلاَمِ دِيناً، وَبِمُحَمَّدٍ صلى الله عليه وسلم نَبِيّاً |ثلاثَ مرَّاتٍ", + "azkarList12": "|رَضِيتُ بِاللَّهِ رَبَّاً، وَبِالْإِسْلاَمِ دِيناً، وَبِمُحَمَّدٍ صلى الله عليه وسلم نَبِيّاً |ثلاثَ مرَّاتٍ", "@azkarList12": { "description": "|رَضِيتُ بِاللَّهِ رَبَّاً، وَبِالْإِسْلاَمِ دِيناً، وَبِمُحَمَّدٍ صلى الله عليه وسلم نَبِيّاً |ثلاثَ مرَّاتٍ" }, - "azkarList13": "|لاَ إِلَهَ إِلاَّ اللَّهُ وَحْدَهُ لاَ شَرِيكَ لَهُ، لَهُ الْمُلْكُ وَلَهُ الْحَمْدُ، وَهُوَ عَلَى كُلِّ شَيْءٍ قَدِيرٌ |عشرَ مرَّاتٍ", + "azkarList13": "|لاَ إِلَهَ إِلاَّ اللَّهُ وَحْدَهُ لاَ شَرِيكَ لَهُ، لَهُ الْمُلْكُ وَلَهُ الْحَمْدُ، وَهُوَ عَلَى كُلِّ شَيْءٍ قَدِيرٌ |عشرَ مرَّاتٍ", "@azkarList13": { "description": "|لاَ إِلَهَ إِلاَّ اللَّهُ وَحْدَهُ لاَ شَرِيكَ لَهُ، لَهُ الْمُلْكُ وَلَهُ الْحَمْدُ، وَهُوَ عَلَى كُلِّ شَيْءٍ قَدِيرٌ |عشرَ مرَّات" }, "jumuaaScreenTitle": "L'heure du Joumoua", - "jumuaaHadith": "Le Prophète Alayhi essalam a dit : \"Quiconque fait les ablutions parfaitement puis va à la joumoua puis écoute et se tait, il lui est pardonné ce qui se trouve entre ce moment et le vendredi suivant et trois autres jours, et celui qui touche des pierres a certainement fait une futilité\".", + "jumuaaHadith": "Le Prophète ﷺ a dit : \"Quiconque fait les ablutions parfaitement puis va à la jumua puis écoute et se tait, il lui est pardonné ce qui se trouve entre ce moment et le vendredi suivant et trois autres jours, et celui qui touche des pierres a certainement fait une futilité\".", "shuruk": "Chourouk", "reset": "Reset", "mosqueNotFoundMessage": "Désolé, votre mosquée n'a pas été trouvée, elle est peut-être manquante ou temporairement désactivée.", @@ -127,16 +127,16 @@ "muharram": "Mouharram", "safar": "Safar", "rabiAlawwal": "Rabi' al-Awwal", - "rabiAlthani": "Rabi' al-thani", - "jumadaAlula": "Joumada al-oula", + "rabiAlthani": "Rabi' al-akhir", + "jumadaAlula": "Jumada al-Ula", "jumadaAlakhirah": "Joumada al-akhirah", "rajab": "Rajab", "shaban": "Chaabane", "ramadan": "Ramadan", - "shawwal": "Chaoual", - "dhuAlqidah": "Dhou al-Qi'dah", - "dhuAlhijjah": "Dhou al-Hijja", - "duaaBetweenSalahAndAdhan": "Selon Anas Ibn Mâlik, le Prophète (ﷺ) a dit : \"Les invocations entre l'Adhân et l’Iqâma ne sont pas rejetées\"", + "shawwal": "Shawwal", + "dhuAlqidah": "Dhu al-Qi'dah", + "dhuAlhijjah": "Dhu al-Hijja", + "duaaBetweenSalahAndAdhan": " Selon Anas Ibn Mâlik, le Prophète (ﷺ) a dit : \"Les invocations entre l'Adhân et l’Iqâmah ne sont pas rejetées\"", "salatKhayrMinaNawm": "Assalatou khayroun mina nawm", "salatElEid": "Salat Al Aïd", "webView": "Forcer l'ancienne version (Online)", @@ -290,20 +290,6 @@ } } }, - "quranReadingPagePortrait": "Page {currentPage} / {totalPages}", - "@quranReadingPagePortrait": { - "description": "Placeholder text for displaying Quran reading page portrait numbers", - "placeholders": { - "currentPage": { - "type": "int", - "example": "1" - }, - "totalPages": { - "type": "int", - "example": "604" - } - } - }, "chooseQuranPage": "Choisir une page", "checkingForUpdates": "Vérification des mises à jour...", "chooseQuranType": "Choisir quran", @@ -311,6 +297,7 @@ "warsh": "Warch", "favorites": "Favoris", "allReciters": "Tous les Réciteurs", + "noFavoriteReciters": "Pas de réciteur favori. Essayez d'en ajouter un à la liste", "reciterAddedToFavorites": "Le réciteur {name} a été ajouté aux favoris", "@reciterAddedToFavorites": { "description": "Message shown when a reciter is added to favorites", @@ -335,11 +322,11 @@ "@noFavoriteReciters": { "description": "Message shown when there are no favorite reciters" }, - "noReciterSearchResult": "Aucun résultat trouvé pour votre recherche.", - "searchForReciter": "Chercher un réciteur", + "noReciterSearchResult": "Aucun résultat trouvé pour votre recherche.", + "searchForReciter": "Chercher un réciteur", "downloadAllSuwarSuccessfully": "Tout le Coran est téléchargé", "noSuwarDownload": "Aucune nouvelle sourate à télécharger", - "connectDownloadQuran": "Veuillez vous connecter à Internet pour télécharger", + "connectDownloadQuran":"Veuillez vous connecter à Internet pour télécharger", "playInOnlineModeQuran": "Veuillez vous connecter à Internet pour jouer", "downloaded": "Téléchargé", "switchQuranType": "Aller à {name}", @@ -352,7 +339,36 @@ } } }, - "surahSelector": "Sélectionner une sourat", + "surahSelector":"Sélectionner une sourat", + "quranReadingPagePortrait": "Page {currentPage} / {totalPages}", + "@quranReadingPagePortrait": { + "description": "Placeholder text for displaying Quran reading page portrait numbers", + "placeholders": { + "currentPage": { + "type": "int", + "example": "1" + }, + "totalPages": { + "type": "int", + "example": "604" + } + } + }, + "save": "Enregistrer", + "enterRtspUrl": "Entrez l'URL RTSP ou YouTube Live", + "addRtspUrl": "Ajoutez l'URL de votre flux de caméra ci-dessous", + "enableRtspCamera": "Activer le flux de la caméra", + "rtspCameraSettings": "Paramètres de la caméra", + "invalidRtspUrl": "URL invalide. Veuillez vérifier l'URL et réessayer.", + "validRtspUrl": "URL validée et enregistrée avec succès.", + "rtspCameraSettingTitle": "Connexion de la caméra en direct", + "rtspCameraSettingDesc": "Connectez-vous à votre caméra locale et affichez le flux de la prière de la jumua sur l'écran de la TV.", + "rtspCameraSettingScreenDesc": "Si vous entrez une URL ici, votre écran passera automatiquement en mode streaming vidéo lorsque l'heure de la Jumua arrive.", + "validatingStream": "Validation du flux...", + "checkInternetLiveCamera": "Vous devez vous connecter à Internet pour configurer la caméra en direct", + "somethingWentWrong": "Quelque chose s'est mal passé ! Veuillez réessayer", + "somethingWrong": "Quelque chose s'est mal passé", + "tryAgainLater": "Veuillez réessayer plus tard", "checkForUpdates": "Vérifier les mises à jour", "checkForNewVersion": "Vérifiez si une nouvelle version est disponible", "wouldYouLikeToUpdate": "Souhaitez-vous mettre à jour l'application ?", @@ -365,6 +381,7 @@ "installingUpdate": "Installation de la mise à jour...", "updateCompletedSuccessfully": "Mise à jour terminée avec succès", "updateFailed": "Échec de la mise à jour", + "updateFailed": "Échec de la mise à jour", "checkInternetUpdate": "Vous devez être connecté à Internet pour vérifier les nouvelles mises à jour", "appUpdateAvailable": "Votre application utilise la version {currentVersion}. Une nouvelle mise à jour (version {updatedVersion}) est disponible avec les dernières fonctionnalités et améliorations.", "@appUpdateAvailable": { diff --git a/lib/main.dart b/lib/main.dart index 9dffcb2c6..08c16e0e2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -27,14 +27,15 @@ import 'package:mawaqit/src/pages/SplashScreen.dart'; import 'package:mawaqit/src/services/audio_manager.dart'; import 'package:mawaqit/src/services/FeatureManager.dart'; import 'package:mawaqit/src/services/mosque_manager.dart'; -import 'package:mawaqit/src/services/settings_manager.dart'; import 'package:mawaqit/src/services/theme_manager.dart'; import 'package:mawaqit/src/services/user_preferences_manager.dart'; +import 'package:media_kit/media_kit.dart'; import 'package:path_provider/path_provider.dart'; import 'package:provider/provider.dart'; import 'package:sizer/sizer.dart'; import 'package:timezone/data/latest.dart' as tz; import 'package:mawaqit/src/routes/route_generator.dart'; +import 'package:montenegrin_localization/montenegrin_localization.dart'; final logger = Logger(); @@ -51,6 +52,7 @@ Future main() async { Hive.registerAdapter(SurahModelAdapter()); Hive.registerAdapter(ReciterModelAdapter()); Hive.registerAdapter(MoshafModelAdapter()); + MediaKit.ensureInitialized(); runApp( riverpod.ProviderScope( child: MyApp(), @@ -72,7 +74,6 @@ class MyApp extends riverpod.ConsumerWidget { ChangeNotifierProvider(create: (context) => ThemeNotifier()), ChangeNotifierProvider(create: (context) => AppLanguage()), ChangeNotifierProvider(create: (context) => MosqueManager()), - ChangeNotifierProvider(create: (context) => SettingsManager()), ChangeNotifierProvider(create: (context) => AudioManager()), ChangeNotifierProvider(create: (context) => FeatureManager(context)), ChangeNotifierProvider(create: (context) => UserPreferencesManager(), lazy: false), @@ -109,6 +110,9 @@ class MyApp extends riverpod.ConsumerWidget { AnalyticsWrapper.observer(), ], localizationsDelegates: [ + MontenegrinMaterialLocalizations.delegate, + MontenegrinWidgetsLocalizations.delegate, + MontenegrinCupertinoLocalizations.delegate, S.delegate, GlobalCupertinoLocalizations.delegate, GlobalMaterialLocalizations.delegate, diff --git a/lib/src/const/config.dart b/lib/src/const/config.dart index 235fb6a9c..c251282cc 100644 --- a/lib/src/const/config.dart +++ b/lib/src/const/config.dart @@ -187,5 +187,6 @@ class Config { "yo": {"name": "Yoruba", "nativeName": "Yorùbá"}, "za": {"name": "Zhuang, Chuang", "nativeName": "Saɯ cueŋƅ, Saw cuengh"}, "zu": {"name": "Zulu", "nativeName": "Zulu"}, + "cnr": {"name": "Montenegrin", "nativeName": "Crnogorski"} }; } diff --git a/lib/src/const/constants.dart b/lib/src/const/constants.dart index 968afac7c..9ddb1f0c5 100644 --- a/lib/src/const/constants.dart +++ b/lib/src/const/constants.dart @@ -97,7 +97,28 @@ abstract class SystemFeaturesConstant { static const String kEthernet = 'android.hardware.ethernet'; } +abstract class MawaqitBackendSettingsConstant { + static const String kSettingsTitle = "Mawaqit"; + static const String kSettingsShare = + "Download Mawaqit\r\nAndroid:\r\nhttps:\/\/play.google.com\/store\/apps\/details?id=com.mawaqit.admin\r\niOS:\r\nhttps:\/\/apps.apple.com\/fr\/app\/mawaqit-prayer-times-mosque\/id1460522683\r\n"; + static const String kSettingsAndroidUserAgent = + "Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/95.0.4638.69 Safari\/537.36"; + static const String kSettingsIosUserAgent = + "Mozilla\/5.0 (iPhone; CPU iPhone OS 14_5 like Mac OS X) AppleWebKit\/605.1.15 (KHTML, like Gecko) CriOS\/90.0.4430.78 Mobile\/15E148 Safari\/604.1"; +} + abstract class ManualUpdateConstant { static const String githubApiBaseUrl = 'https://api.github.com/repos/mawaqit/android-tv-app/releases'; static const String githubAcceptHeader = 'application/vnd.github.v3+json'; } + +abstract class RtspCameraStreamConstant { + static const maxRetries = 3; + static const retryDelay = Duration(seconds: 2); + static const prefKeyEnabled = 'rtsp_enabled'; + static const prefKeyUrl = 'rtsp_url'; + static const String youtubeUrlPattern = + r'http(?:s?):\/\/(?:www\.)?youtu(?:be\.com\/watch\?v=|\.be\/)([\w\-\_]*)(&(amp;)?‌​[\w\?‌​=]*)?'; + + static final RegExp youtubeUrlRegex = RegExp(youtubeUrlPattern); +} diff --git a/lib/src/domain/error/rtsp_expceptions.dart b/lib/src/domain/error/rtsp_expceptions.dart new file mode 100644 index 000000000..0354c6837 --- /dev/null +++ b/lib/src/domain/error/rtsp_expceptions.dart @@ -0,0 +1,40 @@ +abstract class RTSPCameraException implements Exception { + final String message; + final String errorCode; + + RTSPCameraException(this.message, this.errorCode); + + @override + String toString() => 'Error ($errorCode): $message'; +} + +class RTSPInitializationException extends RTSPCameraException { + RTSPInitializationException(String message) + : super('Error during RTSP initialization: $message', 'RTSP_INITIALIZATION_ERROR'); +} + +class RTSPToggleException extends RTSPCameraException { + RTSPToggleException(String message) : super('Error toggling RTSP camera: $message', 'RTSP_TOGGLE_ERROR'); +} + +class InvalidRTSPURLException extends RTSPCameraException { + InvalidRTSPURLException(String message) : super('Invalid RTSP URL: $message', 'INVALID_RTSP_URL_ERROR'); +} + +class URLNotProvidedRTSPURLException extends RTSPCameraException { + URLNotProvidedRTSPURLException(String message) + : super('URL not provided: $message', 'URL_NOT_PROVIDED_RTSP_URL_ERROR'); +} + +class YouTubeVideoIdExtractionException extends RTSPCameraException { + YouTubeVideoIdExtractionException(String message) + : super('Error extracting YouTube video ID: $message', 'YOUTUBE_VIDEO_ID_EXTRACTION_ERROR'); +} + +class RTSPStreamUpdateException extends RTSPCameraException { + RTSPStreamUpdateException(String message) : super('Error updating RTSP stream: $message', 'RTSP_STREAM_UPDATE_ERROR'); +} + +class RTSPUnknownException extends RTSPCameraException { + RTSPUnknownException(String message) : super('Unknown RTSP error: $message', 'RTSP_UNKNOWN_ERROR'); +} diff --git a/lib/src/elements/HorizontalList.dart b/lib/src/elements/HorizontalList.dart deleted file mode 100644 index 90eab26a9..000000000 --- a/lib/src/elements/HorizontalList.dart +++ /dev/null @@ -1,363 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:mawaqit/src/helpers/HexColor.dart'; -import 'package:mawaqit/src/models/settings.dart'; -import 'package:mawaqit/src/themes/UIImages.dart'; - -class HorizontalList extends StatefulWidget { - String title; - String description; - String selected; - String selectedFirstColor; - String selectedSecondColor; - String type; - IconData icon; - List list; - Function? onTap; - Function? onTapColor; - Function? onTapLoader; - Settings? settings; - - HorizontalList( - {Key? key, - this.title = "", - this.description = "", - this.selected = "", - this.selectedFirstColor = "", - this.selectedSecondColor = "", - this.type = "", - this.icon = Icons.edit, - this.list = const [], - this.onTap, - this.onTapColor, - this.onTapLoader, - this.settings = null}) - : super(key: key); - - @override - State createState() => new _HorizontalList(); -} - -class _HorizontalList extends State { - @override - Widget build(BuildContext context) { - return Container( - width: MediaQuery.of(context).size.width, - alignment: Alignment.topLeft, - margin: EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 0.0), - padding: EdgeInsets.fromLTRB(0.0, 15.0, 0, 15.0), - decoration: BoxDecoration( - border: Border(bottom: BorderSide(color: Colors.transparent)), - color: Colors.transparent, - ), - child: Container( - child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Flexible( - child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( - child: Text( - widget.title, - style: TextStyle(fontSize: 24, fontWeight: FontWeight.w600), - ), - margin: EdgeInsets.only(top: 0.0, bottom: 0.0, left: 12.0, right: 12.0), - ), - SizedBox(height: 10.0), - _buildHorizontalList(widget.list, widget.onTap, widget.onTapColor, widget.onTapLoader, widget.selected, - widget.selectedFirstColor, widget.selectedSecondColor, widget.type, widget.settings) - ])) - ]))); - } - - Widget _buildHorizontalList(List list, Function? onTap, Function? onTapColor, Function? onTapLoader, String selected, - String selectedFirstColor, String selectedSecondColor, String type, Settings? settings) { - if (type == "option") { - return SizedBox( - height: 100.0, - child: new ListView( - scrollDirection: Axis.horizontal, - children: list.map((obj) { - return _buildItem(obj['image'], obj['value'], obj['url'], onTap, selected, settings!); - }).toList(), - ), - ); - } - if (type == "color") { - return SizedBox( - height: 150.0, - child: new ListView( - scrollDirection: Axis.horizontal, - children: list.map((obj) { - return _buildItemGradient(obj['title'], obj['image'], obj['firstColor'], obj['secondColor'], onTapColor, - selectedFirstColor, selectedSecondColor, settings); - }).toList(), - ), - ); - } else { - return SizedBox( - height: 120.0, - child: new ListView( - scrollDirection: Axis.horizontal, - children: list.map((obj) { - return _buildItemLoader(obj, onTapLoader, settings!); - }).toList(), - ), - ); - } - } - - Widget _buildItem(AssetImage image_, String? text, String? url, Function? onTap, String selected, Settings settings) { - double edgeSize = 0.0; - - return Container( - padding: EdgeInsets.all(edgeSize), - margin: EdgeInsets.fromLTRB(Directionality.of(context) == TextDirection.rtl ? 0 : 15, 12, - Directionality.of(context) == TextDirection.rtl ? 15 : 0, 12), - child: SizedBox( - width: 230, - child: Container( - margin: EdgeInsets.all(0.0), - padding: EdgeInsets.all(0.0), - alignment: Alignment.topCenter, - decoration: BoxDecoration( - color: Colors.transparent, - border: Border.all( - width: 0.0, - color: Colors.transparent, - ), - boxShadow: [ - new BoxShadow( - color: Colors.black.withOpacity(0.2), - offset: new Offset(2.0, 2.0), - blurRadius: 8.0, - spreadRadius: 1.0) - ]), - child: ElevatedButton( - onPressed: () { - onTap!(text, url); - }, - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)), - padding: EdgeInsets.all(0.0), - ), - child: Ink( - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - HexColor(settings.firstColor).withOpacity(selected == text ? 1.0 : 0.4), - HexColor(settings.secondColor).withOpacity(selected == text ? 1.0 : 0.4) - ], - begin: Alignment.centerLeft, - end: Alignment.centerRight, - ), - borderRadius: BorderRadius.circular(12.0)), - child: new Column( - //constraints:BoxConstraints(maxWidth: 300.0, minHeight: 50.0), - //alignment: Alignment.center, - children: [ - new Expanded( - child: new Container( - decoration: new BoxDecoration( - borderRadius: BorderRadius.circular(12.0), - image: new DecorationImage( - image: image_, - fit: BoxFit.fill, - ), - ), - alignment: AlignmentDirectional.topCenter, - child: Row( - /* - children: [ - Expanded( - flex: 1, - child: Container( - child: new Text(selected), - //color: Colors.green, - ), - ), - Expanded( - flex: 1, - child: Container( - color: Colors.yellow.withOpacity(0.5), - child: new Text(text), - ), - ), - ], - */ - ), - ), - ), - ])))), - )); - } - - Widget _buildItemGradient(String title, AssetImage? image_, String? firstColor, String? secondColor, - Function? onTapColor, String selectedFirstColor, String selectedSecondColor, Settings? settings) { - double edgeSize = 0.0; - - return Container( - padding: EdgeInsets.all(edgeSize), - margin: EdgeInsets.fromLTRB(15, 12, 0, 12), - child: SizedBox( - width: 130, - child: Container( - margin: EdgeInsets.all(0.0), - padding: EdgeInsets.all(0.0), - alignment: Alignment.topCenter, - decoration: BoxDecoration( - color: Colors.transparent, - border: Border.all( - width: 0.0, - color: Colors.transparent, - ), - boxShadow: [ - new BoxShadow( - color: Colors.black.withOpacity(0.2), - offset: new Offset(2.0, 2.0), - blurRadius: 8.0, - spreadRadius: 1.0) - ]), - child: ElevatedButton( - onPressed: () { - onTapColor!(firstColor, secondColor); - }, - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)), - padding: EdgeInsets.all(0.0), - ), - child: Ink( - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - HexColor(firstColor).withOpacity( - (selectedFirstColor == firstColor && selectedSecondColor == secondColor) ? 1.0 : 0.4), - HexColor(secondColor).withOpacity( - (selectedFirstColor == firstColor && selectedSecondColor == secondColor) ? 1.0 : 0.4) - ], - begin: Alignment.centerLeft, - end: Alignment.centerRight, - ), - borderRadius: BorderRadius.circular(12.0)), - child: new Column(children: [ - new Expanded( - child: new Container( - decoration: null, - alignment: AlignmentDirectional.topCenter, - child: Column( - children: [ - Expanded( - flex: 3, - child: Container( - alignment: FractionalOffset(0.5, 0.5), - child: (selectedFirstColor == firstColor && selectedSecondColor == secondColor) - ? UIImages.checked - : null, - ), - ), - Expanded( - flex: 1, - child: Container( - //color: Colors.yellow.withOpacity(0.5), - child: Text( - title, - //overflow: TextOverflow.ellipsis, - //softWrap: true, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: - (selectedFirstColor == firstColor && selectedSecondColor == secondColor) - ? Colors.white - : Colors.grey[300]), - ), - ), - ), - ], - ), - ), - ), - ])))), - )); - } - - Widget _buildItemLoader(dynamic obj, Function? onTapLoader, Settings settings) { - double edgeSize = 0.0; - - return Container( - padding: EdgeInsets.all(edgeSize), - margin: EdgeInsets.fromLTRB(15, 12, 0, 12), - child: SizedBox( - width: 100, - child: Container( - margin: EdgeInsets.all(0.0), - padding: EdgeInsets.all(0.0), - alignment: Alignment.topCenter, - decoration: BoxDecoration( - color: Colors.transparent, - border: Border.all( - width: 0.0, - color: Colors.transparent, - ), - boxShadow: [ - new BoxShadow( - color: Colors.black.withOpacity(0.2), - offset: new Offset(2.0, 2.0), - blurRadius: 8.0, - spreadRadius: 1.0) - ]), - child: ElevatedButton( - onPressed: () { - onTapLoader!(obj["value"]); - }, - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)), - padding: EdgeInsets.all(0.0), - ), - child: Ink( - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - HexColor(settings.firstColor).withOpacity(settings.loader == obj["value"] ? 1.0 : 0.4), - HexColor(settings.secondColor).withOpacity(settings.loader == obj["value"] ? 1.0 : 0.4) - ], - begin: Alignment.centerLeft, - end: Alignment.centerRight, - ), - borderRadius: BorderRadius.circular(12.0)), - child: new Column(children: [ - new Expanded( - child: new Container( - decoration: null, - alignment: AlignmentDirectional.topCenter, - child: Column( - children: [ - Expanded( - flex: 3, - child: Container(alignment: FractionalOffset(0.5, 0.5), child: obj["loading"]), - ), - /* - Expanded( - flex: 1, - child: Container( - //color: Colors.yellow.withOpacity(0.5), - child: Text( - "title", - //overflow: TextOverflow.ellipsis, - //softWrap: true, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: - ( settings.loader == "RotatingCircle") - ? Colors.white - : Colors.grey[300]), - ), - ), - ), - */ - ], - ), - ), - ), - ])))), - )); - } -} diff --git a/lib/src/models/times.dart b/lib/src/models/times.dart index ce30f7379..763c651d4 100644 --- a/lib/src/models/times.dart +++ b/lib/src/models/times.dart @@ -6,6 +6,7 @@ import 'package:mawaqit/src/helpers/time_utils.dart'; class Times { final String? jumua; final String? jumua2; + final String? jumua3; final String? aidPrayerTime; final String? aidPrayerTime2; final int hijriAdjustment; @@ -70,6 +71,7 @@ class Times { const Times({ required this.jumua, required this.jumua2, + required this.jumua3, required this.aidPrayerTime, required this.aidPrayerTime2, required this.hijriAdjustment, @@ -88,6 +90,7 @@ class Times { runtimeType == other.runtimeType && jumua == other.jumua && jumua2 == other.jumua2 && + jumua3 == other.jumua3 && aidPrayerTime == other.aidPrayerTime && aidPrayerTime2 == other.aidPrayerTime2 && hijriAdjustment == other.hijriAdjustment && @@ -101,6 +104,7 @@ class Times { int get hashCode => jumua.hashCode ^ jumua2.hashCode ^ + jumua3.hashCode ^ aidPrayerTime.hashCode ^ aidPrayerTime2.hashCode ^ hijriAdjustment.hashCode ^ @@ -115,6 +119,7 @@ class Times { return 'Times{' + ' jumua: $jumua,' + ' jumua2: $jumua2,' + + ' jumua3: $jumua3,' + ' aidPrayerTime: $aidPrayerTime,' + ' aidPrayerTime2: $aidPrayerTime2,' + ' hijriAdjustment: $hijriAdjustment,' + @@ -132,6 +137,7 @@ class Times { return Times( jumua: map['jumua'] ?? map['jumua2'], jumua2: replacedJumua, + jumua3: map['jumua3'], aidPrayerTime: map['aidPrayerTime'], aidPrayerTime2: map['aidPrayerTime2'], hijriAdjustment: map['hijriAdjustment'] ?? -1, diff --git a/lib/src/pages/PageScreen.dart b/lib/src/pages/PageScreen.dart deleted file mode 100644 index 8713e50e3..000000000 --- a/lib/src/pages/PageScreen.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'package:flutter/material.dart' hide Page; -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; -import 'package:mawaqit/src/helpers/HexColor.dart'; -import 'package:mawaqit/src/models/page.dart'; -import 'package:mawaqit/src/models/settings.dart'; -import 'package:mawaqit/src/widgets/MawaqitWebViewWidget.dart'; -import 'package:mawaqit/src/services/settings_manager.dart'; -import 'package:provider/provider.dart'; - -/// displays data on dynamic pages of drawer -class PageScreen extends StatefulWidget { - final Page page; - - const PageScreen(this.page); - - @override - State createState() => new _PageScreen(); -} - -class _PageScreen extends State { - InAppWebViewController? _webViewController; - - bool isLoading = true; - - @override - Widget build(BuildContext context) { - final settingsManager = Provider.of(context); - final settings = settingsManager.settings; - - return Scaffold( - appBar: _renderAppBar(context, settings, widget.page), - body: MawaqitWebViewWidget( - path: widget.page.url, - clean: true, - ), - ); - } -} - -AppBar _renderAppBar(context, Settings settings, Page page) { - return AppBar( - title: Text( - page.title!, - style: TextStyle(color: Colors.white, fontSize: 22.0, fontWeight: FontWeight.bold), - ), - flexibleSpace: Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.centerLeft, - end: Alignment.centerRight, - colors: [ - Theme.of(context).brightness == Brightness.light - ? HexColor(settings.firstColor) - : Theme.of(context).primaryColor, - Theme.of(context).brightness == Brightness.light - ? HexColor(settings.secondColor) - : Theme.of(context).primaryColor, - ], - ), - ), - )); -} - -const extractContent = ''' - const header = document.querySelector(".header"); - header?.parentElement.removeChild(header); - - const footer = document.querySelector(".footer"); - footer?.parentElement.removeChild(footer); - - const breadcrumb = document.querySelector(".breadcrumb"); - breadcrumb?.parentElement.removeChild(breadcrumb); -'''; diff --git a/lib/src/pages/SettingScreen.dart b/lib/src/pages/SettingScreen.dart index 01e6a6080..c50332ff3 100644 --- a/lib/src/pages/SettingScreen.dart +++ b/lib/src/pages/SettingScreen.dart @@ -41,6 +41,7 @@ import '../state_management/random_hadith/random_hadith_notifier.dart'; import '../widgets/screen_lock_widget.dart'; import '../widgets/time_picker_widget.dart'; import 'home/widgets/show_check_internet_dialog.dart'; +import 'rtsp_camera_settings_screen.dart'; class SettingScreen extends ConsumerStatefulWidget { const SettingScreen({super.key}); @@ -175,6 +176,58 @@ class _SettingScreenState extends ConsumerState { ); }, ), + Consumer( + builder: (context, ref, child) { + return _SettingSwitchItem( + title: S.of(context).automaticUpdate, + subtitle: S.of(context).automaticUpdateDescription, + icon: Icon(Icons.update, size: 35), + onChanged: (value) { + logger.d('setting: disable the update $value'); + ref.read(appUpdateProvider.notifier).toggleAutoUpdateChecking(); + }, + value: ref.watch(appUpdateProvider).maybeWhen( + orElse: () => false, + data: (data) => data.isAutoUpdateChecking, + ), + ); + }, + ), + _SettingItem( + title: S.of(context).rtspCameraSettingTitle, + subtitle: S.of(context).rtspCameraSettingDesc, + icon: Icon(Icons.video_camera_back, size: 35), + onTap: () async { + await ref.read(connectivityProvider.notifier).checkInternetConnection(); + ref.watch(connectivityProvider).maybeWhen( + orElse: () { + showCheckInternetDialog( + context: context, + onRetry: () { + AppRouter.pop(); + }, + title: checkInternet, + content: S.of(context).checkInternetLiveCamera, + ); + }, + data: (isConnectedToInternet) { + if (isConnectedToInternet == ConnectivityStatus.disconnected) { + showCheckInternetDialog( + context: context, + onRetry: () { + AppRouter.pop(); + }, + title: checkInternet, + content: S.of(context).checkInternetLiveCamera, + ); + } else { + AppRouter.push(RTSPCameraSettingsScreen()); + } + }, + ); + }, + ), + SizedBox(height: 30), Divider(), SizedBox(height: 10), Text( diff --git a/lib/src/pages/SplashScreen.dart b/lib/src/pages/SplashScreen.dart index f322a41c0..176b4539b 100644 --- a/lib/src/pages/SplashScreen.dart +++ b/lib/src/pages/SplashScreen.dart @@ -20,12 +20,10 @@ import 'package:mawaqit/src/helpers/PerformanceHelper.dart'; import 'package:mawaqit/src/helpers/RelativeSizes.dart'; import 'package:mawaqit/src/helpers/SharedPref.dart'; import 'package:mawaqit/src/helpers/StreamGenerator.dart'; -import 'package:mawaqit/src/models/settings.dart'; import 'package:mawaqit/src/pages/ErrorScreen.dart'; import 'package:mawaqit/src/pages/home/OfflineHomeScreen.dart'; import 'package:mawaqit/src/pages/onBoarding/OnBoardingScreen.dart'; import 'package:mawaqit/src/services/mosque_manager.dart'; -import 'package:mawaqit/src/services/settings_manager.dart'; import 'package:mawaqit/src/state_management/random_hadith/random_hadith_notifier.dart'; import 'package:mawaqit/src/widgets/InfoWidget.dart'; import 'package:provider/provider.dart'; @@ -91,13 +89,10 @@ class _SpashState extends ConsumerState { prefs.setBool("isEventsSet", false); } - Future _initSettings() async { + Future _initSettings() async { FeatureManagerProvider.initialize(context); await context.read().fetchLocale(); await context.read().init().logPerformance("Mosque manager"); - final settingsManage = context.read(); - await settingsManage.init().logPerformance('Setting manager'); - return settingsManage.settings; } Future loadBoarding() async { @@ -111,6 +106,7 @@ class _SpashState extends ConsumerState { try { await initApplicationUI(); var settings = await _initSettings(); + var goBoarding = await loadBoarding(); var mosqueManager = context.read(); bool hasNoMosque = mosqueManager.mosqueUUID == null; @@ -118,7 +114,7 @@ class _SpashState extends ConsumerState { /// waite for the animation if it is not loaded yet await animationFuture.future; - if (hasNoMosque || goBoarding && settings.boarding == "1") { + if (hasNoMosque || goBoarding) { AppRouter.pushReplacement(OnBoardingScreen()); } else { AppRouter.pushReplacement(OfflineHomeScreen()); diff --git a/lib/src/pages/WebScreen.dart b/lib/src/pages/WebScreen.dart deleted file mode 100644 index 15392c78e..000000000 --- a/lib/src/pages/WebScreen.dart +++ /dev/null @@ -1,188 +0,0 @@ -import 'dart:async'; -import 'dart:io'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; -import 'package:geolocator/geolocator.dart'; -import 'package:global_configuration/global_configuration.dart'; -import 'package:mawaqit/i18n/l10n.dart'; -import 'package:mawaqit/src/elements/RaisedGradientButton.dart'; -import 'package:mawaqit/src/domain/model/connectivity_status.dart'; -import 'package:mawaqit/src/helpers/HexColor.dart'; -import 'package:mawaqit/src/models/settings.dart'; -import 'package:mawaqit/src/services/settings_manager.dart'; -import 'package:mawaqit/src/themes/UIImages.dart'; -import 'package:mawaqit/src/widgets/MawaqitWebViewWidget.dart'; -import 'package:provider/provider.dart'; -import 'package:uni_links/uni_links.dart'; - -class WebScreen extends StatefulWidget { - final String? url; - - const WebScreen(this.url); - - @override - State createState() { - return new _WebScreen(); - } -} - -class _WebScreen extends State { - InAppWebViewController? get _webViewController => webViewKey.currentState?.webViewController; - final webViewKey = GlobalKey(); - - PullToRefreshController? pullToRefreshController; - - List> webViewGPSPositionStreams = []; - - StreamSubscription? _sub; - - @override - void initState() { - super.initState(); - - pullToRefreshController = PullToRefreshController( - options: PullToRefreshOptions(color: Colors.blue), - onRefresh: () async { - if (Platform.isAndroid) { - _webViewController?.reload(); - } else if (Platform.isIOS) { - _webViewController?.loadUrl(urlRequest: URLRequest(url: await _webViewController?.getUrl())); - } - }, - ); - - _handleIncomingLinks(); - } - - void _handleIncomingLinks() { - if (!kIsWeb) { - _sub = uriLinkStream.listen((Uri? uri) { - var link = uri.toString().replaceAll('${GlobalConfiguration().getValue('deeplink')}://url/', ''); - _webViewController?.loadUrl(urlRequest: URLRequest(url: Uri.parse(link))); - }, onError: (Object err) {}); - } - } - - @override - void dispose() { - _sub?.cancel(); - - webViewGPSPositionStreams - .forEach((StreamSubscription _flutterGeolocationStream) => _flutterGeolocationStream.cancel()); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - MediaQueryData mediaQueryData = MediaQuery.of(context); - var bottomPadding = mediaQueryData.padding.bottom; - var connectionStatus = Provider.of(context); - final settingsManager = Provider.of(context); - final settings = settingsManager.settings; - - if (connectionStatus == ConnectivityStatus.Offline) return _offline(bottomPadding, settings); - - return WillPopScope( - onWillPop: _onBackPressed, - child: Container( - decoration: BoxDecoration(color: HexColor("#f5f4f4")), - padding: EdgeInsets.only(bottom: bottomPadding), - child: Scaffold( - appBar: _renderAppBar(context, settings), - body: MawaqitWebViewWidget(path: widget.url, key: webViewKey), - ), - ), - ); - } - - Widget _offline(bottomPadding, Settings settings) { - return WillPopScope( - onWillPop: _onBackPressed, - child: Container( - decoration: BoxDecoration(color: HexColor("#f5f4f4")), - padding: EdgeInsets.only(bottom: bottomPadding), - child: Scaffold( - body: Column( - children: [ - Container( - height: 130, - ), - Column(crossAxisAlignment: CrossAxisAlignment.center, children: [ - Container( - width: 100.0, - height: 100.0, - child: Image.asset( - UIImages.imageDir + "/wifi.png", - color: Colors.black26, - fit: BoxFit.contain, - )), - SizedBox(height: 40), - Text( - S.of(context).whoops, - style: TextStyle(color: Colors.black45, fontSize: 40.0, fontWeight: FontWeight.bold), - ), - SizedBox(height: 20), - Text( - S.of(context).noInternet, - style: TextStyle(color: Colors.black87, fontSize: 15.0), - ), - SizedBox(height: 5), - SizedBox(height: 60), - RaisedGradientButton( - child: Text( - S.of(context).tryAgain, - style: TextStyle(color: Colors.white, fontSize: 18.0, fontWeight: FontWeight.bold), - ), - width: 250, - gradient: LinearGradient( - colors: [HexColor(settings.secondColor), HexColor(settings.firstColor)], - ), - onPressed: () {}), - ]), - Container( - height: 100, - ), - ], - ), - )), - ); - } - - PreferredSizeWidget _renderAppBar(context, Settings settings) { - return AppBar( - flexibleSpace: Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.centerLeft, - end: Alignment.centerRight, - colors: [ - Theme.of(context).brightness == Brightness.light - ? HexColor(settings.firstColor) - : Theme.of(context).primaryColor, - Theme.of(context).brightness == Brightness.light - ? HexColor(settings.secondColor) - : Theme.of(context).primaryColor, - ], - ), - ), - )); - } - - Future _onBackPressed() async { - try { - if (_webViewController != null) { - if (await _webViewController!.canGoBack()) { - _webViewController!.goBack(); - return false; - } else { - Navigator.pop(context); - } - } - } catch (e) { - Navigator.pop(context); - } - return true; - } -} diff --git a/lib/src/pages/home/sub_screens/JummuaLive.dart b/lib/src/pages/home/sub_screens/JummuaLive.dart index e4e2cc7f3..6a073a227 100644 --- a/lib/src/pages/home/sub_screens/JummuaLive.dart +++ b/lib/src/pages/home/sub_screens/JummuaLive.dart @@ -6,9 +6,15 @@ import 'package:mawaqit/i18n/l10n.dart'; import 'package:mawaqit/src/helpers/RelativeSizes.dart'; import 'package:mawaqit/src/models/address_model.dart'; import 'package:mawaqit/src/services/mosque_manager.dart'; +import 'package:mawaqit/src/state_management/rtsp_camera_stream/rtsp_camera_stream_notifier.dart'; +import 'package:mawaqit/src/state_management/rtsp_camera_stream/rtsp_camera_stream_state.dart'; import 'package:mawaqit/src/state_management/quran/quran/quran_notifier.dart'; import 'package:mawaqit/src/themes/UIShadows.dart'; +import 'package:media_kit/media_kit.dart'; +import 'package:media_kit_video/media_kit_video.dart'; import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:youtube_player_flutter/youtube_player_flutter.dart'; import '../../../../main.dart'; import '../../../helpers/connectivity_provider.dart'; @@ -29,7 +35,6 @@ class JummuaLive extends ConsumerStatefulWidget { } class _JummuaLiveState extends ConsumerState { - /// invalid channel id bool invalidStreamUrl = false; @override @@ -41,6 +46,7 @@ class _JummuaLiveState extends ConsumerState { }); log('JummuaLive: invalidStreamUrl: $invalidStreamUrl'); + super.initState(); } @@ -49,29 +55,104 @@ class _JummuaLiveState extends ConsumerState { final mosqueManager = context.read(); final userPrefs = context.watch(); final connectivity = ref.watch(connectivityProvider); + final streamStateAsync = ref.watch(rtspCameraSettingsProvider); - /// disable live stream in mosque primary screen final jumuaaDisableInMosque = !userPrefs.isSecondaryScreen && mosqueManager.typeIsMosque; - return switch (connectivity) { - AsyncData(:final value) => switchStreamWidget(value, mosqueManager, jumuaaDisableInMosque), - _ => CircularProgressIndicator( - valueColor: AlwaysStoppedAnimation(Theme.of(context).primaryColor), // Green color + return connectivity.when( + data: (value) => streamStateAsync.when( + data: (streamState) { + return _switchStreamWidget( + value, + mosqueManager, + jumuaaDisableInMosque, + streamState, + ); + }, + loading: () => const Center( + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(Colors.blue), + ), + ), + error: (error, stack) => _switchStreamWidget( + value, + mosqueManager, + jumuaaDisableInMosque, + RTSPCameraSettingsState(), ), - }; + ), + loading: () => const CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(Colors.blue), + ), + error: (_, __) => _switchStreamWidget( + ConnectivityStatus.disconnected, + mosqueManager, + jumuaaDisableInMosque, + RTSPCameraSettingsState(), + ), + ); } - Widget switchStreamWidget( - ConnectivityStatus connectivityStatus, MosqueManager mosqueManager, bool jumuaaDisableInMosque) { - if (invalidStreamUrl || - mosqueManager.mosque?.streamUrl == null || - jumuaaDisableInMosque || - connectivityStatus == ConnectivityStatus.disconnected) { - if (mosqueManager.mosqueConfig!.jumuaDhikrReminderEnabled == true) + Widget _switchStreamWidget( + ConnectivityStatus connectivityStatus, + MosqueManager mosqueManager, + bool jumuaaDisableInMosque, + RTSPCameraSettingsState streamState, + ) { + // First check if we should show Hadith screen or black screen + if (jumuaaDisableInMosque || connectivityStatus == ConnectivityStatus.disconnected) { + if (mosqueManager.mosqueConfig!.jumuaDhikrReminderEnabled == true) { return JumuaHadithSubScreen(onDone: widget.onDone); + } + return const Scaffold(backgroundColor: Colors.black); + } + + // Check if RTSP is enabled and properly configured + final isRTSPWorking = streamState.isRTSPEnabled && + streamState.streamType == StreamType.rtsp && + streamState.videoController != null && + streamState.streamUrl != null && + connectivityStatus != ConnectivityStatus.disconnected; + + // Check if YouTube stream is configured + final isYouTubeWorking = streamState.isRTSPEnabled && + streamState.streamType == StreamType.youtubeLive && + streamState.youtubeController != null && + streamState.streamUrl != null && + connectivityStatus != ConnectivityStatus.disconnected; + + // Priority 1: RTSP Stream if working + if (isRTSPWorking) { + return Scaffold( + backgroundColor: Colors.black, + body: Center( + child: AspectRatio( + aspectRatio: 16 / 9, + child: Video( + controller: streamState.videoController!, + ), + ), + ), + ); + } - return Scaffold(backgroundColor: Colors.black); - } else { + // Priority 2: YouTube Stream from RTSP settings if working + if (isYouTubeWorking) { + return Scaffold( + backgroundColor: Colors.black, + body: Center( + child: AspectRatio( + aspectRatio: 16 / 9, + child: YoutubePlayer( + controller: streamState.youtubeController!, + ), + ), + ), + ); + } + + // Priority 3: Mosque Manager's YouTube stream as fallback + if (mosqueManager.mosque?.streamUrl != null) { return MawaqitYoutubePlayer( channelId: mosqueManager.mosque!.streamUrl!, onDone: widget.onDone, @@ -79,5 +160,8 @@ class _JummuaLiveState extends ConsumerState { onNotFound: () => setState(() => invalidStreamUrl = true), ); } + + // Fallback case + return const Scaffold(backgroundColor: Colors.black); } } diff --git a/lib/src/pages/home/widgets/FlashWidget.dart b/lib/src/pages/home/widgets/FlashWidget.dart index f8f179685..5db8cdad4 100644 --- a/lib/src/pages/home/widgets/FlashWidget.dart +++ b/lib/src/pages/home/widgets/FlashWidget.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:marquee/marquee.dart'; +import 'package:mawaqit/i18n/AppLanguage.dart'; import 'package:mawaqit/src/helpers/RelativeSizes.dart'; import 'package:mawaqit/src/models/mosque.dart'; import 'package:provider/provider.dart'; @@ -21,11 +22,22 @@ class _FlashWidgetState extends State { final isFlashEnabled = context.select((mosque) => mosque.flashEnabled); final flash = context.select((mosque) => mosque.mosque?.flash); if (!isFlashEnabled) return SizedBox(); + final isPortrait = MediaQuery.of(context).orientation == Orientation.portrait; + final appLanguage = Provider.of(context); + if (!isFlashEnabled) return SizedBox(); + + TextDirection getTextDirection() { + if (isPortrait && appLanguage.appLocal.toLanguageTag() == "ar") { + return flash?.orientation == 'rtl' ? TextDirection.ltr : TextDirection.rtl; + } else { + return flash?.orientation == 'rtl' ? TextDirection.rtl : TextDirection.ltr; + } + } return RepaintBoundary( child: Marquee( key: ValueKey(flash!.content), - textDirection: flash.orientation == 'rtl' ? TextDirection.rtl : TextDirection.ltr, + textDirection: getTextDirection(), text: flash.content ?? '', velocity: 90, blankSpace: 400, diff --git a/lib/src/pages/home/widgets/salah_items/SalahItem.dart b/lib/src/pages/home/widgets/salah_items/SalahItem.dart index a1c1178cc..381ba0a77 100644 --- a/lib/src/pages/home/widgets/salah_items/SalahItem.dart +++ b/lib/src/pages/home/widgets/salah_items/SalahItem.dart @@ -14,6 +14,7 @@ class SalahItemWidget extends StatelessOrientationWidget { required this.time, this.title, this.iqama, + this.iqama2, this.active = false, this.removeBackground = false, this.withDivider = true, @@ -24,6 +25,7 @@ class SalahItemWidget extends StatelessOrientationWidget { final String? title; final String time; final String? iqama; + final String? iqama2; /// show divider only when both time and iqama exists final bool withDivider; @@ -76,39 +78,98 @@ class SalahItemWidget extends StatelessOrientationWidget { ), ), SizedBox(height: 1.vr), - if (time.trim().isEmpty) - Icon(Icons.dnd_forwardslash, size: 6.vwr) - else - Container( - decoration: (iqama != null && showIqama && withDivider) - ? BoxDecoration( - border: Border(bottom: BorderSide(color: Colors.white, width: 1)), - ) - : null, - child: TimeWidget.fromString( - show24hFormat: !is12period, - time: time, - style: TextStyle( - fontSize: isIqamaMoreImportant ? smallFont : bigFont, - fontWeight: FontWeight.w700, - shadows: kHomeTextShadow, - color: Colors.white, + if (iqama2 != null) // Three times layout + Column( + children: [ + TimeWidget.fromString( + show24hFormat: !is12period, + time: time, + style: TextStyle( + fontSize: bigFont, + fontWeight: FontWeight.w700, + shadows: kHomeTextShadow, + color: Colors.white, + height: 1, + ), + ), + Container( + margin: EdgeInsets.symmetric(vertical: 1.vr), + width: 20.vwr, // Adjust this value to match your needs height: 1, - // fontFamily: StringManager.getFontFamily(context), + color: Colors.white, ), - ), - ), - if (iqama != null && showIqama) - TimeWidget.fromString( - show24hFormat: !is12period, - time: iqama!, - style: TextStyle( - fontSize: isIqamaMoreImportant ? bigFont : smallFont, - fontWeight: FontWeight.bold, - shadows: kHomeTextShadow, - letterSpacing: 1, - color: Colors.white, - ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + TimeWidget.fromString( + show24hFormat: !is12period, + time: iqama!, + style: TextStyle( + fontSize: smallFont, + fontWeight: FontWeight.w700, + shadows: kHomeTextShadow, + color: Colors.white, + height: 1, + ), + ), + Container( + height: bigFont, + width: 1, + margin: EdgeInsets.symmetric(horizontal: 1.vwr), + color: Colors.white, + ), + TimeWidget.fromString( + show24hFormat: !is12period, + time: iqama2!, + style: TextStyle( + fontSize: smallFont, + fontWeight: FontWeight.w700, + shadows: kHomeTextShadow, + color: Colors.white, + height: 1, + ), + ), + ], + ), + ], + ) + else // Original two times layout + Column( + children: [ + if (time.trim().isEmpty) + Icon(Icons.dnd_forwardslash, size: 6.vwr) + else + Container( + decoration: (iqama != null && showIqama && withDivider) + ? BoxDecoration( + border: Border(bottom: BorderSide(color: Colors.white, width: 1)), + ) + : null, + child: TimeWidget.fromString( + show24hFormat: !is12period, + time: time, + style: TextStyle( + fontSize: isIqamaMoreImportant ? smallFont : bigFont, + fontWeight: FontWeight.w700, + shadows: kHomeTextShadow, + color: Colors.white, + height: 1, + ), + ), + ), + if (iqama != null && showIqama) + TimeWidget.fromString( + show24hFormat: !is12period, + time: iqama!, + style: TextStyle( + fontSize: isIqamaMoreImportant ? bigFont : smallFont, + fontWeight: FontWeight.bold, + shadows: kHomeTextShadow, + letterSpacing: 1, + color: Colors.white, + ), + ), + ], ), ], ), diff --git a/lib/src/pages/quran/page/quran_mode_selection_screen.dart b/lib/src/pages/quran/page/quran_mode_selection_screen.dart index 093ebdec1..fe66126d5 100644 --- a/lib/src/pages/quran/page/quran_mode_selection_screen.dart +++ b/lib/src/pages/quran/page/quran_mode_selection_screen.dart @@ -80,12 +80,12 @@ class _QuranModeSelectionState extends ConsumerState { } } - void _handleNavigation(int index) { + Future _handleNavigation(int index) async { if (index == 0) { - ref.read(quranNotifierProvider.notifier).selectModel(QuranMode.reading); + await ref.read(quranNotifierProvider.notifier).selectModel(QuranMode.reading); Navigator.pushReplacementNamed(context, Routes.quranReading); } else { - ref.read(quranNotifierProvider.notifier).selectModel(QuranMode.listening); + await ref.read(quranNotifierProvider.notifier).selectModel(QuranMode.listening); Navigator.pushReplacementNamed(context, Routes.quranReciter); } } diff --git a/lib/src/pages/quran/reading/quran_reading_screen.dart b/lib/src/pages/quran/reading/quran_reading_screen.dart index 74fda1aea..fa2ff88dc 100644 --- a/lib/src/pages/quran/reading/quran_reading_screen.dart +++ b/lib/src/pages/quran/reading/quran_reading_screen.dart @@ -136,11 +136,6 @@ class NormalViewStrategy implements QuranViewStrategy { ) { if (isPortrait) { return [ - BackButtonWidget( - isPortrait: isPortrait, - userPrefs: userPrefs, - focusNode: focusNodes.backButtonNode, - ), SurahSelectorWidget( isPortrait: isPortrait, focusNode: focusNodes.surahSelectorNode, @@ -157,15 +152,15 @@ class NormalViewStrategy implements QuranViewStrategy { focusNode: focusNodes.switchQuranNode, isThereCurrentDialogShowing: false, ), + BackButtonWidget( + isPortrait: isPortrait, + userPrefs: userPrefs, + focusNode: focusNodes.backButtonNode, + ), ]; } return [ - BackButtonWidget( - isPortrait: isPortrait, - userPrefs: userPrefs, - focusNode: focusNodes.backButtonNode, - ), _buildNavigationButtons( context, focusNodes, @@ -188,6 +183,11 @@ class NormalViewStrategy implements QuranViewStrategy { focusNode: focusNodes.switchQuranNode, isThereCurrentDialogShowing: false, ), + BackButtonWidget( + isPortrait: isPortrait, + userPrefs: userPrefs, + focusNode: focusNodes.backButtonNode, + ), ]; } @@ -247,7 +247,6 @@ class _QuranReadingScreenState extends ConsumerState { WidgetsBinding.instance.addPostFrameCallback((_) async { ref.read(downloadQuranNotifierProvider); - ref.read(quranReadingNotifierProvider); }); } @@ -321,38 +320,51 @@ class _QuranReadingScreenState extends ConsumerState { }); final autoReadingState = ref.watch(autoScrollNotifierProvider); - - return WillPopScope( - onWillPop: () async { - userPrefs.orientationLandscape = true; - return true; - }, - child: quranReadingState.when( - data: (state) { - setState(() { - _isRotated = state.isRotated; - }); - return RotatedBox( - quarterTurns: state.isRotated ? -1 : 0, - child: SizedBox( - width: MediaQuery.of(context).size.height, - height: MediaQuery.of(context).size.width, - child: Scaffold( - backgroundColor: Colors.white, - floatingActionButtonLocation: _getFloatingActionButtonLocation(context), - floatingActionButton: QuranFloatingActionControls( - switchScreenViewFocusNode: _switchScreenViewFocusNode, - switchQuranModeNode: _switchQuranModeNode, - switchToPlayQuranFocusNode: _switchToPlayQuranFocusNode, - ), - body: _buildBody(quranReadingState, state.isRotated, userPrefs, autoReadingState), - ), + final downloadState = ref.watch(downloadQuranNotifierProvider); + return downloadState.when( + data: (data) { + if (data is NeededDownloadedQuran || data is Downloading || data is Extracting) { + return Scaffold( + body: Container( + color: Colors.white, ), ); - }, - loading: () => SizedBox(), - error: (error, stack) => const Icon(Icons.error), - ), + } + return WillPopScope( + onWillPop: () async { + userPrefs.orientationLandscape = true; + return true; + }, + child: quranReadingState.when( + data: (state) { + setState(() { + _isRotated = state.isRotated; + }); + return RotatedBox( + quarterTurns: state.isRotated ? -1 : 0, + child: SizedBox( + width: MediaQuery.of(context).size.height, + height: MediaQuery.of(context).size.width, + child: Scaffold( + backgroundColor: Colors.white, + floatingActionButtonLocation: _getFloatingActionButtonLocation(context), + floatingActionButton: QuranFloatingActionControls( + switchScreenViewFocusNode: _switchScreenViewFocusNode, + switchQuranModeNode: _switchQuranModeNode, + switchToPlayQuranFocusNode: _switchToPlayQuranFocusNode, + ), + body: _buildBody(quranReadingState, state.isRotated, userPrefs, autoReadingState), + ), + ), + ); + }, + loading: () => Scaffold(body: SizedBox()), + error: (error, stack) => Scaffold(body: const Icon(Icons.error)), + ), + ); + }, + loading: () => Scaffold(body: _buildLoadingIndicator()), + error: (error, stack) => Scaffold(body: _buildErrorIndicator(error)), ); } diff --git a/lib/src/pages/quran/widget/download_quran_popup.dart b/lib/src/pages/quran/widget/download_quran_popup.dart index a1e0db5ca..bc6db8535 100644 --- a/lib/src/pages/quran/widget/download_quran_popup.dart +++ b/lib/src/pages/quran/widget/download_quran_popup.dart @@ -8,7 +8,7 @@ import 'package:mawaqit/src/domain/model/quran/moshaf_type_model.dart'; import 'package:mawaqit/src/state_management/quran/download_quran/download_quran_notifier.dart'; import 'package:mawaqit/src/state_management/quran/download_quran/download_quran_state.dart'; import 'package:mawaqit/src/state_management/quran/reading/moshaf_type_notifier.dart'; -import 'package:mawaqit/src/state_management/quran/reading/quran_reading_state.dart'; +import 'package:mawaqit/src/state_management/quran/reading/quran_reading_notifer.dart'; class DownloadQuranDialog extends ConsumerStatefulWidget { const DownloadQuranDialog({super.key}); @@ -19,15 +19,23 @@ class DownloadQuranDialog extends ConsumerStatefulWidget { class _DownloadQuranDialogState extends ConsumerState { MoshafType selectedMoshafType = MoshafType.hafs; + late FocusNode _dialogFocusNode; @override void initState() { super.initState(); + _dialogFocusNode = FocusNode(); WidgetsBinding.instance.addPostFrameCallback((_) { _checkForUpdate(); }); } + @override + void dispose() { + _dialogFocusNode.dispose(); + super.dispose(); + } + void _checkForUpdate() { final notifier = ref.read(downloadQuranNotifierProvider.notifier); // notifier.checkForUpdate(notifier.selectedMoshafType); @@ -35,14 +43,34 @@ class _DownloadQuranDialogState extends ConsumerState { @override Widget build(BuildContext context) { - final state = ref.watch(downloadQuranNotifierProvider); - return state.when( - data: (data) => _buildContent(context, data), - loading: () => Container(), - error: (error, _) => _buildErrorDialog(context, error), + final downloadState = ref.watch(downloadQuranNotifierProvider); + + return downloadState.when( + data: (data) => _buildDialogContent(context, data), + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, stack) => _buildErrorDialog(context, error), ); } + Widget _buildDialogContent(BuildContext context, DownloadQuranState state) { + return switch (state) { + NeededDownloadedQuran() => _buildChooseDownloadMoshaf(context), + Downloading() => _buildDownloadingDialog(context, state), + Extracting() => _buildExtractingDialog(context, state), + Success() => _handleSuccess(context), + CancelDownload() => const SizedBox(), + _ => const SizedBox(), + }; + } + + Widget _handleSuccess(BuildContext context) { + // Auto close dialog on success + WidgetsBinding.instance.addPostFrameCallback((_) { + Navigator.of(context).pop(); + }); + return const SizedBox(); + } + Widget _buildContent(BuildContext context, DownloadQuranState state) { // return Container(); return switch (state) { @@ -50,7 +78,7 @@ class _DownloadQuranDialogState extends ConsumerState { // UpdateAvailable() => _buildUpdateAvailableDialog(context, state), Downloading() => _buildDownloadingDialog(context, state), Extracting() => _buildExtractingDialog(context, state), - Success() => _buildSuccessDialog(context, state), + Success() => _successDialog(context), CancelDownload() => Container(), // NoUpdate() => _buildNoUpdateDialog(context, state), _ => Container(), @@ -79,63 +107,67 @@ class _DownloadQuranDialogState extends ConsumerState { } Widget _buildDownloadingDialog(BuildContext context, Downloading state) { - return AlertDialog( - title: Text(S.of(context).downloadingQuran), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - LinearProgressIndicator(value: state.progress / 100), - SizedBox(height: 16), - Text('${state.progress.toStringAsFixed(2)}%'), - ], - ), - actions: [ - TextButton( - autofocus: true, - onPressed: () async { - final notifier = ref.read(downloadQuranNotifierProvider.notifier); - ref.read(moshafTypeNotifierProvider).maybeWhen( - orElse: () {}, - data: (state) async { - state.selectedMoshaf.fold(() { - return null; - }, (selectedMoshaf) async { - await notifier.cancelDownload(selectedMoshaf); // Await cancellation - Navigator.pop(context); // Close dialog after cancel completes - }); - }, - ); - }, - child: Text(S.of(context).cancel), + return Focus( + focusNode: _dialogFocusNode, + child: AlertDialog( + title: Text(S.of(context).downloadingQuran), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + LinearProgressIndicator(value: state.progress / 100), + SizedBox(height: 16), + Text('${state.progress.toStringAsFixed(2)}%'), + ], ), - ], - ); - } - - Widget _buildExtractingDialog(BuildContext context, Extracting state) { - return AlertDialog( - title: Text(S.of(context).extractingQuran), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - LinearProgressIndicator(value: state.progress / 100), - SizedBox(height: 16), - Text('${state.progress.toStringAsFixed(2)}%'), + actions: [ + TextButton( + autofocus: true, + onPressed: () async { + final notifier = ref.read(downloadQuranNotifierProvider.notifier); + final moshafType = ref.watch(moshafTypeNotifierProvider); + ref.read(moshafTypeNotifierProvider).maybeWhen( + orElse: () {}, + data: (state) async { + state.selectedMoshaf.fold(() { + return null; + }, (selectedMoshaf) async { + await notifier.cancelDownload(selectedMoshaf); // Await cancellation + }); + }, + ); + moshafType.when( + data: (data) { + if (data.isFirstTime) { + Navigator.popUntil(context, (route) => route.isFirst); + } else { + Navigator.pop(context); + } + }, + error: (_, __) {}, + loading: () {}, + ); + }, + child: Text(S.of(context).cancel), + ), ], ), ); } - Widget _buildSuccessDialog(BuildContext context, Success state) { - return AlertDialog( - title: Text(S.of(context).quranDownloaded), - actions: [ - TextButton( - autofocus: true, - onPressed: () => Navigator.pop(context), - child: Text(S.of(context).ok), + Widget _buildExtractingDialog(BuildContext context, Extracting state) { + return Focus( + focusNode: _dialogFocusNode, + child: AlertDialog( + title: Text(S.of(context).extractingQuran), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + LinearProgressIndicator(value: state.progress / 100), + SizedBox(height: 16), + Text('${state.progress.toStringAsFixed(2)}%'), + ], ), - ], + ), ); } @@ -152,41 +184,57 @@ class _DownloadQuranDialogState extends ConsumerState { } Widget _buildChooseDownloadMoshaf(BuildContext context) { - return AlertDialog( - title: Text(S.of(context).chooseQuranType), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - _buildMoshafTypeRadio( - context, - title: S.of(context).warsh, - value: MoshafType.warsh, - setState: setState, + return Focus( + focusNode: _dialogFocusNode, + child: AlertDialog( + title: Text(S.of(context).chooseQuranType), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildMoshafTypeRadio( + context, + title: S.of(context).warsh, + value: MoshafType.warsh, + setState: setState, + autofocus: selectedMoshafType == MoshafType.warsh, + ), + _buildMoshafTypeRadio( + context, + title: S.of(context).hafs, + value: MoshafType.hafs, + setState: setState, + autofocus: selectedMoshafType == MoshafType.hafs, + ), + ], + ), + actions: [ + TextButton( + onPressed: () { + final moshafType = ref.watch(moshafTypeNotifierProvider); + moshafType.when( + data: (data) { + if (data.isFirstTime) { + Navigator.popUntil(context, (route) => route.isFirst); + } else { + Navigator.pop(context); + } + }, + error: (_, __) {}, + loading: () {}, + ); + }, + child: Text(S.of(context).cancel), ), - _buildMoshafTypeRadio( - context, - title: S.of(context).hafs, - value: MoshafType.hafs, - setState: setState, + TextButton( + autofocus: true, + onPressed: () async { + Navigator.pop(context); + await ref.read(downloadQuranNotifierProvider.notifier).downloadQuran(selectedMoshafType); + }, + child: Text(S.of(context).download), ), ], ), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - }, - child: Text(S.of(context).cancel), - ), - TextButton( - autofocus: true, - onPressed: () async { - Navigator.pop(context); - await ref.read(downloadQuranNotifierProvider.notifier).downloadQuran(selectedMoshafType); - }, - child: Text(S.of(context).download), - ), - ], ); } @@ -195,11 +243,12 @@ class _DownloadQuranDialogState extends ConsumerState { required String title, required MoshafType value, required void Function(VoidCallback fn) setState, + bool autofocus = false, }) { return RadioListTile( title: Text(title), value: value, - autofocus: true, + autofocus: autofocus, groupValue: selectedMoshafType, onChanged: (MoshafType? selected) { setState(() { @@ -232,4 +281,8 @@ class _DownloadQuranDialogState extends ConsumerState { ], ); } + + Widget _successDialog(BuildContext context) { + return Container(); + } } diff --git a/lib/src/pages/rtsp_camera_settings_screen.dart b/lib/src/pages/rtsp_camera_settings_screen.dart new file mode 100644 index 000000000..637a993c5 --- /dev/null +++ b/lib/src/pages/rtsp_camera_settings_screen.dart @@ -0,0 +1,335 @@ +import 'dart:async'; +import 'dart:developer'; +import 'package:flutter/material.dart'; +import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mawaqit/i18n/l10n.dart'; +import 'package:mawaqit/src/domain/error/rtsp_expceptions.dart'; +import 'package:mawaqit/src/state_management/rtsp_camera_stream/rtsp_camera_stream_notifier.dart'; +import 'package:mawaqit/src/state_management/rtsp_camera_stream/rtsp_camera_stream_state.dart'; +import 'package:mawaqit/src/widgets/ScreenWithAnimation.dart'; +import 'package:media_kit_video/media_kit_video.dart'; +import 'package:sizer/sizer.dart'; +import 'package:youtube_player_flutter/youtube_player_flutter.dart'; + +class RTSPCameraSettingsScreen extends ConsumerStatefulWidget { + const RTSPCameraSettingsScreen({Key? key}) : super(key: key); + + @override + ConsumerState createState() => _RTSPCameraSettingsScreenState(); +} + +class _RTSPCameraSettingsScreenState extends ConsumerState { + final TextEditingController _urlController = TextEditingController(); + final FocusNode _saveButtonFocusNode = FocusNode(); + late StreamSubscription keyboardSubscription; + + @override + void initState() { + super.initState(); + var keyboardVisibilityController = KeyboardVisibilityController(); + keyboardSubscription = keyboardVisibilityController.onChange.listen((bool visible) { + if (!visible) { + FocusScope.of(context).requestFocus(_saveButtonFocusNode); + } + }); + + WidgetsBinding.instance.addPostFrameCallback((_) { + final state = ref.read(rtspCameraSettingsProvider); + state.whenData((value) { + if (value.streamUrl != null) { + _urlController.text = value.streamUrl!; + } + }); + }); + } + + @override + void dispose() { + _urlController.dispose(); + keyboardSubscription.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final asyncState = ref.watch(rtspCameraSettingsProvider); + ref.listen(rtspCameraSettingsProvider, (previous, next) { + if (previous != next && !next.isLoading && next.hasValue && !next.hasError && next.value!.isRTSPEnabled) { + final state = next.value!; + + // Only show snackbar when URL validation status changes + ScaffoldMessenger.of(context).clearSnackBars(); + + String message; + Color backgroundColor; + + if (state.streamUrl != null && !state.isInvalidUrl) { + message = S.of(context).validRtspUrl; + backgroundColor = Colors.green; + } else if (state.isInvalidUrl) { + message = S.of(context).invalidRtspUrl; + backgroundColor = Colors.red; + } else { + return; + } + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + message, + style: const TextStyle(fontSize: 16), + ), + backgroundColor: backgroundColor, + duration: const Duration(seconds: 3), + behavior: SnackBarBehavior.floating, + margin: const EdgeInsets.all(16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + ); + } + }); + + return asyncState.when( + data: (state) { + return Scaffold( + appBar: state.isRTSPEnabled + ? AppBar( + backgroundColor: Colors.transparent, + elevation: 0, + leading: IconButton( + icon: const Icon(Icons.arrow_back), + iconSize: 12.sp, + splashRadius: 7.sp, + onPressed: () => Navigator.of(context).pop(), + ), + ) + : null, + body: SafeArea( + child: Stack( + children: [ + if (!state.isRTSPEnabled) + ScreenWithAnimationWidget( + animation: "settings", + child: SingleChildScrollView( + padding: const EdgeInsets.all(16.0), + child: _buildSettingsContent(state), + ), + ) + else + Row( + children: [ + Expanded( + flex: 1, + child: Container( + margin: const EdgeInsets.all(16.0), + decoration: BoxDecoration( + border: Border.all(color: Theme.of(context).dividerColor), + borderRadius: BorderRadius.circular(20), + ), + child: AspectRatio( + aspectRatio: 16 / 9, + child: _buildVideoPreview(state), + ), + ), + ), + Expanded( + flex: 1, + child: SingleChildScrollView( + padding: const EdgeInsets.all(16.0), + child: _buildSettingsContent(state), + ), + ), + ], + ), + ], + ), + ), + ); + }, + loading: () => Scaffold( + body: _buildLoadingOverlay(), + ), + error: (error, stackTrace) { + if (error is RTSPCameraException) { + return _buildErrorScreen(error); + } else { + return _buildErrorScreen(RTSPStreamUpdateException(error.toString())); + } + }, + ); + } + + Widget _buildLoadingOverlay() { + return Container( + color: Colors.black54, + child: Center( + child: Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 20, + horizontal: 30, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const CircularProgressIndicator(), + const SizedBox(height: 16), + Text( + S.of(context).validatingStream, + style: Theme.of(context).textTheme.titleMedium, + ), + ], + ), + ), + ), + ), + ); + } + + Widget _buildErrorScreen(RTSPCameraException error) { + String errorMessage = ''; + + errorMessage = S.of(context).somethingWentWrong; + + return Scaffold( + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.error_outline, + size: 48, + color: Colors.red, + ), + const SizedBox(height: 16), + Text( + errorMessage, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + ElevatedButton( + onPressed: () { + ref.invalidate(rtspCameraSettingsProvider); + }, + child: Text(S.of(context).tryAgain), + ), + ], + ), + ), + ); + } + + Widget _buildVideoPreview(RTSPCameraSettingsState state) { + if (state.streamType == StreamType.youtubeLive && state.youtubeController != null) { + return YoutubePlayer(controller: state.youtubeController!); + } + if (state.videoController != null) { + return Video(controller: state.videoController!); + } + return const SizedBox.shrink(); + } + + Widget _buildSettingsContent(RTSPCameraSettingsState state) { + return Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + S.of(context).rtspCameraSettings, + style: Theme.of(context).textTheme.titleMedium?.apply(fontSizeFactor: 2), + textAlign: TextAlign.center, + ), + const Divider(indent: 50, endIndent: 50), + const SizedBox(height: 10), + Text( + S.of(context).rtspCameraSettingScreenDesc, + style: Theme.of(context).textTheme.bodySmall?.apply(fontSizeFactor: 1.5), + textAlign: TextAlign.center, + ), + const SizedBox(height: 20), + SwitchListTile( + title: Text(S.of(context).enableRtspCamera), + value: state.isRTSPEnabled, + onChanged: (value) { + ref.read(rtspCameraSettingsProvider.notifier).toggleEnabled(value); + }, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + side: BorderSide(color: Theme.of(context).dividerColor), + ), + ), + if (state.isRTSPEnabled) ...[ + const SizedBox(height: 20), + Text( + S.of(context).addRtspUrl, + style: Theme.of(context).textTheme.bodyLarge?.apply(fontSizeFactor: 1.2), + textAlign: TextAlign.center, + ), + const Divider(indent: 50, endIndent: 50), + const SizedBox(height: 20), + TextField( + controller: _urlController, + onSubmitted: (_) => ref.read(rtspCameraSettingsProvider.notifier).updateStream( + isEnabled: true, + url: _urlController.text, + ), + decoration: InputDecoration( + labelText: S.of(context).enterRtspUrl, + hintText: S.of(context).hintTextRtspUrl, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(20), + ), + ), + ), + const SizedBox(height: 20), + ElevatedButton.icon( + focusNode: _saveButtonFocusNode, + onPressed: () => ref.read(rtspCameraSettingsProvider.notifier).updateStream( + isEnabled: true, + url: _urlController.text, + ), + icon: const Icon(Icons.save), + label: Text(S.of(context).save), + style: ButtonStyle( + padding: MaterialStateProperty.all( + const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + ), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + ), + backgroundColor: MaterialStateProperty.resolveWith((states) { + if (states.contains(MaterialState.focused)) { + return Theme.of(context).primaryColor; + } + return Colors.white; + }), + iconColor: MaterialStateProperty.resolveWith((states) { + if (states.contains(MaterialState.focused)) { + return Colors.white; + } + return Colors.black; + }), + foregroundColor: MaterialStateProperty.resolveWith((states) { + if (states.contains(MaterialState.focused)) { + return Colors.white; + } + return Colors.black; + }), + ), + ) + ], + ], + ); + } +} diff --git a/lib/src/pages/times/widgets/jumua_widget.dart b/lib/src/pages/times/widgets/jumua_widget.dart index 690546f55..4ac15fb15 100644 --- a/lib/src/pages/times/widgets/jumua_widget.dart +++ b/lib/src/pages/times/widgets/jumua_widget.dart @@ -11,12 +11,22 @@ import 'package:provider/provider.dart'; class JumuaWidget extends StatelessWidget { const JumuaWidget({super.key}); + List getOrderedJumuaTimes(MosqueManager mosqueManager) { + final times = mosqueManager.times; + List jumuaTimes = []; + + if (times?.jumua != null) jumuaTimes.add(times!.jumua!); + if (times?.jumua2 != null) jumuaTimes.add(times!.jumua2!); + if (times?.jumua3 != null) jumuaTimes.add(times!.jumua3!); + + return jumuaTimes; + } + @override Widget build(BuildContext context) { final mosqueManager = context.watch(); final userPrefs = context.watch(); - /// show eid instead of jumuaa if its eid time and eid is enabled if (mosqueManager.showEid(userPrefs.hijriAdjustments)) { return FadeInOutWidget( first: eidWidget(mosqueManager, context), @@ -43,14 +53,27 @@ class JumuaWidget extends StatelessWidget { } Widget jumuaTile(MosqueManager mosqueManager, BuildContext context) { + final jumuaTimes = getOrderedJumuaTimes(mosqueManager); + + if (jumuaTimes.isEmpty) { + return SalahItemWidget( + withDivider: true, + removeBackground: true, + title: S.of(context).jumua, + time: "", + isIqamaMoreImportant: false, + ); + } + return SalahItemWidget( withDivider: true, removeBackground: true, title: S.of(context).jumua, - time: !mosqueManager.isJumuaOrJumua2EmptyOrNull() ? DateFormat.Hm().format(mosqueManager.activeJumuaaDate()) : "", - iqama: mosqueManager.times!.jumua2, + time: jumuaTimes[0], + iqama: jumuaTimes.length > 1 ? jumuaTimes[1] : null, + iqama2: jumuaTimes.length > 2 ? jumuaTimes[2] : null, isIqamaMoreImportant: false, - active: mosqueManager.nextIqamaIndex() == 1 && AppDateTime.isFriday && mosqueManager.times?.jumua != null, + active: mosqueManager.nextIqamaIndex() == 1 && AppDateTime.isFriday, ); } } diff --git a/lib/src/repository/settings_service.dart b/lib/src/repository/settings_service.dart deleted file mode 100644 index cb7261aa1..000000000 --- a/lib/src/repository/settings_service.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/cupertino.dart'; -import 'package:flutter/services.dart'; -import 'package:global_configuration/global_configuration.dart'; -import 'package:http/http.dart' as http; -import 'package:mawaqit/const/resource.dart'; -import 'package:mawaqit/src/helpers/SharedPref.dart'; -import 'package:mawaqit/src/models/settings.dart'; - -ValueNotifier setting = new ValueNotifier(new Settings()); - -class SettingsService { - final _sharedPref = SharedPref(); - - /// fetch the setting from the server and cache it for future usage - /// - /// 1. load settings from server - /// 2. in case of server error uses the last cached settings value - /// 3. in case of not exists uses the default value in `assets/cfg/settings.json` - Future getSettings() async { - try { - var res = await http - .get( - Uri.parse( - '${GlobalConfiguration().getValue('api_base_url')}/api/settings/settings.php', - ), - ) - .timeout(Duration(seconds: 5)); - - if (res.statusCode == 200) { - final json = jsonDecode(res.body); - - Settings settings = Settings.fromJson(json["data"]); - - saveCachedSettings(json['data']); - - return settings; - } else { - throw Exception('Getting local saved settings'); - } - } catch (e) { - var localSettings = await getCachedSettings().catchError((e) => null); - localSettings ??= await getLocalSettings(); - - if (localSettings == null) throw Exception('Failed to load /api/settings'); - - return localSettings; - } - } - - Future saveCachedSettings(dynamic settings) => _sharedPref.save("settings", settings); - - /// used for performance improvement (initial start up time) - /// used for fall back in case of server down - Future getCachedSettings() async { - final settings = await _sharedPref.read('settings'); - - if (settings == null) return null; - - return Settings.fromJson(settings); - } - - Future getLocalSettings() async { - final data = await rootBundle.loadString(R.ASSETS_CFG_SETTINGS_JSON); - - final settings = jsonDecode(data); - return Settings.fromJson(settings); - } -} diff --git a/lib/src/services/settings_manager.dart b/lib/src/services/settings_manager.dart deleted file mode 100644 index 9486427ae..000000000 --- a/lib/src/services/settings_manager.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:mawaqit/main.dart'; -import 'package:mawaqit/src/helpers/SharedPref.dart'; -import 'package:mawaqit/src/models/settings.dart'; -import 'package:mawaqit/src/repository/settings_service.dart'; - -class SettingsManager extends ChangeNotifier { - final settingsService = SettingsService(); - final sharedPref = SharedPref(); - - Settings? _settings; - - Settings get settings => _settings!; - - bool get settingsLoaded => _settings != null; - - /// 1- check for cached value first to speed up the first load time - /// 2- fetch the new value and cache it for future use - Future init() async { - _settings = await settingsService.getCachedSettings().catchError((e) => null); - - if (_settings != null) { - if (hasListeners) notifyListeners(); - } - - _settings = await settingsService.getSettings(); - notifyListeners(); - } -} diff --git a/lib/src/state_management/quran/download_quran/download_quran_notifier.dart b/lib/src/state_management/quran/download_quran/download_quran_notifier.dart index 74d22929e..46e535360 100644 --- a/lib/src/state_management/quran/download_quran/download_quran_notifier.dart +++ b/lib/src/state_management/quran/download_quran/download_quran_notifier.dart @@ -131,13 +131,23 @@ class DownloadQuranNotifier extends AutoDisposeAsyncNotifier } Future downloadQuran(MoshafType moshafType) async { - state = const AsyncLoading(); + final link = ref.keepAlive(); // Keep alive during download + try { + state = const AsyncLoading(); + + // First ensure moshaf type is selected + await ref.read(moshafTypeNotifierProvider.notifier).selectMoshafType(moshafType); + final downloadState = await _downloadQuran(moshafType); if (downloadState is Success) { await ref.read(moshafTypeNotifierProvider.notifier).setNotFirstTime(); - } - if (downloadState is! CancelDownload) { + + state = AsyncData(downloadState); + + // Force rebuild reading provider in next frame + ref.invalidate(quranReadingNotifierProvider); + } else if (downloadState is! CancelDownload) { state = AsyncData(downloadState); } } catch (e, s) { @@ -145,6 +155,8 @@ class DownloadQuranNotifier extends AutoDisposeAsyncNotifier return; } state = AsyncError(e, s); + } finally { + link.close(); } } diff --git a/lib/src/state_management/quran/reading/quran_reading_notifer.dart b/lib/src/state_management/quran/reading/quran_reading_notifer.dart index d6fd02e25..b23bcc3eb 100644 --- a/lib/src/state_management/quran/reading/quran_reading_notifer.dart +++ b/lib/src/state_management/quran/reading/quran_reading_notifer.dart @@ -1,12 +1,15 @@ import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:fpdart/fpdart.dart'; import 'package:mawaqit/src/const/constants.dart'; import 'package:mawaqit/src/domain/model/quran/moshaf_type_model.dart'; import 'package:mawaqit/src/domain/model/quran/surah_model.dart'; import 'package:mawaqit/src/domain/repository/quran/quran_reading_repository.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:mawaqit/src/module/shared_preference_module.dart'; +import 'package:mawaqit/src/state_management/quran/download_quran/download_quran_notifier.dart'; +import 'package:mawaqit/src/state_management/quran/download_quran/download_quran_state.dart'; import 'package:mawaqit/src/state_management/quran/quran/quran_notifier.dart'; import 'package:mawaqit/src/state_management/quran/reading/moshaf_type_notifier.dart'; import 'package:mawaqit/src/state_management/quran/reading/quran_reading_state.dart'; @@ -15,13 +18,24 @@ import 'package:mawaqit/src/data/repository/quran/quran_reading_impl.dart'; class QuranReadingNotifier extends AutoDisposeAsyncNotifier { @override Future build() async { - final repository = ref.read(quranReadingRepositoryProvider.future); - ref.onDispose(() { - if (state.hasValue) { - state.value!.pageController.dispose(); - } - }); - return _initState(repository); + final link = ref.keepAlive(); + + try { + final repository = await ref.read(quranReadingRepositoryProvider.future); + + ref.onDispose(() { + if (state.hasValue) { + state.value!.pageController.dispose(); + } + }); + + final result = await _initState(repository); + link.close(); + return result; + } catch (e) { + link.close(); + rethrow; + } } void nextPage({bool isPortrait = false}) async { @@ -106,38 +120,47 @@ class QuranReadingNotifier extends AutoDisposeAsyncNotifier { ); } - Future _initState(Future repository) async { - final quranReadingRepository = await repository; + Future _initState(QuranReadingRepository repository) async { final mosqueModel = await ref.read(moshafTypeNotifierProvider.future); - return mosqueModel.selectedMoshaf.fold( - () { - throw Exception('No MoshafType'); - }, - (moshaf) async { - state = AsyncLoading(); - final svgs = await _loadSvgs(moshafType: moshaf); - final lastReadPage = await quranReadingRepository.getLastReadPage(); - final pageController = PageController(initialPage: (lastReadPage / 2).floor()); - final suwar = await getAllSuwar(); - final initialSurahName = _getCurrentSurahName(lastReadPage, suwar); - return QuranReadingState( - currentJuz: 1, - currentSurah: 1, - suwar: suwar, - currentPage: lastReadPage, - svgs: svgs, - pageController: pageController, - currentSurahName: initialSurahName, - ); - }, - ); + + try { + // Get moshaf type or set default + final moshafType = mosqueModel.selectedMoshaf.getOrElse(() => MoshafType.hafs); + + // Set moshaf type if none selected + if (mosqueModel.selectedMoshaf.isNone()) { + await ref.read(moshafTypeNotifierProvider.notifier).selectMoshafType(moshafType); + } + + state = AsyncLoading(); + final svgs = await _loadSvgs(moshafType: moshafType); + + if (svgs.isEmpty) { + throw Exception('No SVGs found for moshaf type: ${moshafType.name}'); + } + + final lastReadPage = await repository.getLastReadPage(); + final pageController = PageController(initialPage: (lastReadPage / 2).floor()); + final suwar = await getAllSuwar(); + + return QuranReadingState( + currentJuz: 1, + currentSurah: 1, + suwar: suwar, + currentPage: lastReadPage, + svgs: svgs, + pageController: pageController, + currentSurahName: _getCurrentSurahName(lastReadPage, suwar), + ); + } catch (e) { + rethrow; + } } Future _saveLastReadPage(int index) async { try { final quranRepository = await ref.read(quranReadingRepositoryProvider.future); await quranRepository.saveLastReadPage(index); - log('quran: QuranReadingNotifier: Saved last read page: $index'); } catch (e, s) { state = AsyncError(e, s); } diff --git a/lib/src/state_management/rtsp_camera_stream/rtsp_camera_stream_notifier.dart b/lib/src/state_management/rtsp_camera_stream/rtsp_camera_stream_notifier.dart new file mode 100644 index 000000000..045113165 --- /dev/null +++ b/lib/src/state_management/rtsp_camera_stream/rtsp_camera_stream_notifier.dart @@ -0,0 +1,282 @@ +import 'dart:developer'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mawaqit/src/const/constants.dart'; +import 'package:mawaqit/src/domain/error/rtsp_expceptions.dart'; +import 'package:mawaqit/src/state_management/rtsp_camera_stream/rtsp_camera_stream_state.dart'; +import 'package:media_kit/media_kit.dart'; +import 'package:media_kit_video/media_kit_video.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:youtube_explode_dart/youtube_explode_dart.dart'; +import 'package:youtube_player_flutter/youtube_player_flutter.dart'; + +enum StreamType { rtsp, youtubeLive } + +class RTSPCameraSettingsNotifier extends AutoDisposeAsyncNotifier { + YoutubePlayerController? _youtubeController; + Player? _player; + VideoController? _videoController; + + Future dispose() async { + try { + if (_youtubeController != null) { + _youtubeController!.dispose(); + _youtubeController = null; + } + + if (_player != null) { + await _player!.pause(); + await _player!.dispose(); + _player = null; + } + + if (_videoController != null) { + await _videoController!.player.dispose(); + _videoController = null; + } + } catch (e) { + log('Error disposing controllers: $e'); + } + } + + @override + Future build() async { + ref.onDispose(() async { + await dispose(); + }); + + return await initializeSettings(); + } + + Future initializeSettings() async { + try { + final prefs = await SharedPreferences.getInstance(); + final isEnabled = prefs.getBool(RtspCameraStreamConstant.prefKeyEnabled) ?? false; + final savedUrl = prefs.getString(RtspCameraStreamConstant.prefKeyUrl); + if (!isEnabled || savedUrl == null || savedUrl.isEmpty) { + return RTSPCameraSettingsState( + isRTSPEnabled: isEnabled, + streamUrl: savedUrl, + isInvalidUrl: false, + ); + } + return await _initializeFromSavedUrl(isEnabled: isEnabled, url: savedUrl); + } catch (e, s) { + throw RTSPInitializationException(e.toString()); + } + } + + Future _initializeFromSavedUrl({ + required bool isEnabled, + required String url, + }) async { + try { + await dispose(); + if (RtspCameraStreamConstant.youtubeUrlRegex.hasMatch(url)) { + return await _handleYoutubeStream(isEnabled, url); + } else if (url.startsWith('rtsp://')) { + return await _handleRTSPStream(isEnabled, url); + } + + throw InvalidRTSPURLException('Invalid URL format: $url'); + } catch (e) { + if (e is InvalidRTSPURLException) { + return RTSPCameraSettingsState( + isRTSPEnabled: isEnabled, + streamUrl: url, + isInvalidUrl: true, + ); + } + return RTSPCameraSettingsState( + isRTSPEnabled: isEnabled, + streamUrl: url, + ); + } + } + + // Modified toggleEnabled method + Future toggleEnabled(bool isEnabled) async { + try { + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool(RtspCameraStreamConstant.prefKeyEnabled, isEnabled); + + final currentState = state.value; + if (currentState != null) { + if (!isEnabled) { + await pauseStreams(); + } + state = AsyncValue.data( + currentState.copyWith( + isRTSPEnabled: isEnabled, + isInvalidUrl: false, + ), + ); + + if (isEnabled && currentState.streamUrl != null) { + await updateStream(isEnabled: isEnabled, url: currentState.streamUrl ?? ''); + await resumeStreams(); + } + } + } catch (e, s) { + state = AsyncValue.error(RTSPToggleException(e.toString()), s); + } + } + + Future updateStream({ + required bool isEnabled, + required String url, + }) async { + state = const AsyncValue.loading(); + try { + if (url.isEmpty) { + throw URLNotProvidedRTSPURLException(url); + } + + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool(RtspCameraStreamConstant.prefKeyEnabled, isEnabled); + await prefs.setString(RtspCameraStreamConstant.prefKeyUrl, url); + + // Dispose of existing controllers before creating new ones + await dispose(); + + // Handle YouTube URLs (including live streams) + if (RtspCameraStreamConstant.youtubeUrlRegex.hasMatch(url)) { + final newState = await _handleYoutubeStream(isEnabled, url); + if (state.hasValue) { + // Ensure we're not keeping any references to old controllers + state = AsyncValue.data( + state.value!.copyWith( + videoController: null, + youtubeController: newState.youtubeController, + streamType: StreamType.youtubeLive, + streamUrl: url, + isInvalidUrl: false, + ), + ); + } else { + state = AsyncValue.data(newState); + } + return; + } + // Handle RTSP URLs + else if (url.startsWith('rtsp://')) { + final newState = await _handleRTSPStream(isEnabled, url); + if (state.hasValue) { + // Ensure we're not keeping any references to old controllers + state = AsyncValue.data( + state.value!.copyWith( + youtubeController: null, + videoController: newState.videoController, + streamType: StreamType.rtsp, + streamUrl: url, + isInvalidUrl: false, + ), + ); + } else { + state = AsyncValue.data(newState); + } + return; + } + + throw InvalidRTSPURLException('Invalid URL format: $url'); + } catch (e, s) { + // Clean up on error + await dispose(); + + if (e is InvalidRTSPURLException || e is URLNotProvidedRTSPURLException) { + state = AsyncValue.data( + state.value!.copyWith( + isInvalidUrl: true, + videoController: null, + youtubeController: null, + ), + ); + } else { + log('Error updating stream: $e', error: e, stackTrace: s); + state = AsyncValue.error(e, s); + } + } + } + + String? extractVideoId(String url) { + if (url.contains('youtube.com/live/')) { + return url.split('youtube.com/live/')[1].split('?').first; + } + return YoutubePlayer.convertUrlToId(url); + } + + Future _handleYoutubeStream(bool isEnabled, String url) async { + try { + // Ensure previous controllers are disposed + await dispose(); + + final videoId = extractVideoId(url); + if (videoId == null) { + throw InvalidRTSPURLException('URL is empty: $url'); + } + _youtubeController = YoutubePlayerController( + initialVideoId: videoId, + flags: const YoutubePlayerFlags( + autoPlay: true, + mute: false, + enableCaption: false, + hideControls: true, + isLive: true, + useHybridComposition: true, + forceHD: true, + ), + ); + + return RTSPCameraSettingsState( + isRTSPEnabled: isEnabled, + streamUrl: url, + isInvalidUrl: false, + streamType: StreamType.youtubeLive, + youtubeController: _youtubeController, + ); + } catch (e) { + await dispose(); + throw YouTubeVideoIdExtractionException(e.toString()); + } + } + + Future _handleRTSPStream(bool isEnabled, String url) async { + try { + // Ensure previous controllers are disposed + await dispose(); + + _player = Player(); + _videoController = VideoController(_player!); + await _player!.open(Media(url)); + + return RTSPCameraSettingsState( + isRTSPEnabled: isEnabled, + streamUrl: url, + streamType: StreamType.rtsp, + isInvalidUrl: false, + videoController: _videoController, + ); + } catch (e) { + await dispose(); + throw RTSPStreamUpdateException(e.toString()); + } + } + + // Add this method to pause/stop streams + Future pauseStreams() async { + _youtubeController?.pause(); + await _player?.pause(); + } + + // Add this method to resume streams + Future resumeStreams() async { + _youtubeController?.play(); + await _player?.play(); + } +} + +final rtspCameraSettingsProvider = + AutoDisposeAsyncNotifierProvider(() { + return RTSPCameraSettingsNotifier(); +}); diff --git a/lib/src/state_management/rtsp_camera_stream/rtsp_camera_stream_state.dart b/lib/src/state_management/rtsp_camera_stream/rtsp_camera_stream_state.dart new file mode 100644 index 000000000..e5b6d7838 --- /dev/null +++ b/lib/src/state_management/rtsp_camera_stream/rtsp_camera_stream_state.dart @@ -0,0 +1,63 @@ +import 'dart:async'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mawaqit/src/state_management/rtsp_camera_stream/rtsp_camera_stream_notifier.dart'; +import 'package:media_kit_video/media_kit_video.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:youtube_player_flutter/youtube_player_flutter.dart'; + +class RTSPCameraSettingsState extends Equatable { + final bool isRTSPEnabled; + final String? streamUrl; + final StreamType? streamType; + final VideoController? videoController; + final YoutubePlayerController? youtubeController; + final bool isInvalidUrl; + + const RTSPCameraSettingsState({ + this.isRTSPEnabled = false, + this.streamUrl, + this.streamType, + this.videoController, + this.youtubeController, + this.isInvalidUrl = false, + }); + + RTSPCameraSettingsState copyWith({ + bool? isRTSPEnabled, + String? streamUrl, + StreamType? streamType, + VideoController? videoController, + YoutubePlayerController? youtubeController, + bool? invalidStreamUrl, + bool? showValidationSnackbar, + bool? isInvalidUrl, + }) { + return RTSPCameraSettingsState( + isRTSPEnabled: isRTSPEnabled ?? this.isRTSPEnabled, + streamUrl: streamUrl ?? this.streamUrl, + streamType: streamType ?? this.streamType, + videoController: videoController ?? this.videoController, + youtubeController: youtubeController ?? this.youtubeController, + isInvalidUrl: isInvalidUrl ?? this.isInvalidUrl, + ); + } + + @override + String toString() { + return 'RTSPCameraSettingsState(isRTSPEnabled: $isRTSPEnabled, streamUrl: $streamUrl, streamType: $streamType, videoController: $videoController, youtubeController: $youtubeController, isInvalidUrl: $isInvalidUrl)'; + } + + @override + List get props { + return [ + isRTSPEnabled, + streamUrl, + streamType, + videoController, + youtubeController, + isInvalidUrl, + ]; + } +} diff --git a/lib/src/widgets/MawaqitDrawer.dart b/lib/src/widgets/MawaqitDrawer.dart index 6940a800d..49b5854e3 100644 --- a/lib/src/widgets/MawaqitDrawer.dart +++ b/lib/src/widgets/MawaqitDrawer.dart @@ -1,9 +1,8 @@ import 'dart:developer'; -import 'dart:io'; import 'package:flutter/material.dart' hide Page; import 'package:flutter/services.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart' show ConsumerWidget, WidgetRef, ProviderContainer; +import 'package:flutter_riverpod/flutter_riverpod.dart' show ConsumerWidget, WidgetRef; import 'package:flutter_svg/svg.dart'; import 'package:launch_review/launch_review.dart'; import 'package:mawaqit/const/resource.dart'; @@ -12,17 +11,10 @@ import 'package:mawaqit/src/const/constants.dart'; import 'package:mawaqit/src/elements/DrawerListTitle.dart'; import 'package:mawaqit/src/helpers/AppRouter.dart'; import 'package:mawaqit/src/helpers/RelativeSizes.dart'; -import 'package:mawaqit/src/helpers/StringUtils.dart'; -import 'package:mawaqit/src/models/menu.dart'; -import 'package:mawaqit/src/models/page.dart'; -import 'package:mawaqit/src/models/settings.dart'; import 'package:mawaqit/src/pages/AboutScreen.dart'; -import 'package:mawaqit/src/pages/PageScreen.dart'; -import 'package:mawaqit/src/pages/WebScreen.dart'; import 'package:mawaqit/src/pages/quran/page/reciter_selection_screen.dart'; import 'package:mawaqit/src/routes/routes_constant.dart'; import 'package:mawaqit/src/services/mosque_manager.dart'; -import 'package:mawaqit/src/services/settings_manager.dart'; import 'package:mawaqit/src/services/user_preferences_manager.dart'; import 'package:mawaqit/src/widgets/InfoWidget.dart'; import 'package:provider/provider.dart'; @@ -43,8 +35,6 @@ class MawaqitDrawer extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final settings = Provider.of(context).settings; - final mosqueManager = context.watch(); final userPrefs = context.watch(); final theme = Theme.of(context); @@ -160,15 +150,10 @@ class MawaqitDrawer extends ConsumerWidget { icon: Icons.home, text: S.of(context).home, onTap: () async { - if (settings.tabNavigationEnable == "1") { - AppRouter.popAndPush(WebScreen(settings.url), name: 'HomeScreen'); - } else { - Navigator.pop(context); + Navigator.pop(context); - goHome(); - } + goHome(); }), - _renderMenuDrawer(settings, context), DrawerListTitle( icon: Icons.book, text: S.of(context).quran, @@ -211,7 +196,8 @@ class MawaqitDrawer extends ConsumerWidget { icon: Icons.share, text: S.of(context).share, onTap: () { - _shareApp(context, settings.title, settings.share!); + _shareApp(context, MawaqitBackendSettingsConstant.kSettingsTitle, + MawaqitBackendSettingsConstant.kSettingsShare); }), DrawerListTitle( icon: Icons.star, @@ -226,43 +212,6 @@ class MawaqitDrawer extends ConsumerWidget { ); } - Widget _renderMenuDrawer(Settings settings, BuildContext context) { - List menus = settings.menus ?? []; - - return new Column( - children: menus - .map((Menu menu) => DrawerListTitle( - iconUrl: menu.iconUrl, - forceThemeColor: true, - autoTranslate: true, - text: menu.title, - onTap: () async { - AppRouter.push(WebScreen(menu.url), name: menu.title); - Navigator.pop(context); - })) - .toList(), - ); - } - - Widget _renderPageDrawer(List pages, context) { - final translations = { - "privacyPolicy": S.of(context).privacyPolicy, - "networkStatus": S.of(context).networkStatus, - "termsOfService": S.of(context).termsOfService, - "installationGuide": S.of(context).installationGuide, - }; - - return Column( - children: pages - .map((Page page) => DrawerListTitle( - forceThemeColor: true, - iconUrl: page.iconUrl, - text: translations[page.title!.toCamelCase] ?? page.title, - onTap: () => AppRouter.popAndPush(PageScreen(page), name: page.title))) - .toList(), - ); - } - _shareApp(BuildContext context, String? text, String share) { final RenderBox box = context.findRenderObject() as RenderBox; Share.share(share, subject: text, sharePositionOrigin: box.localToGlobal(Offset.zero) & box.size); diff --git a/lib/src/widgets/MawaqitWebViewWidget.dart b/lib/src/widgets/MawaqitWebViewWidget.dart index 918c06244..4b0427ca4 100644 --- a/lib/src/widgets/MawaqitWebViewWidget.dart +++ b/lib/src/widgets/MawaqitWebViewWidget.dart @@ -12,11 +12,11 @@ import 'package:geolocator/geolocator.dart'; import 'package:mawaqit/const/resource.dart'; //import 'package:location/location.dart' hide LocationAccuracy; import 'package:mawaqit/i18n/l10n.dart'; +import 'package:mawaqit/src/const/constants.dart'; import 'package:mawaqit/src/domain/model/position/PositionResponse.dart'; import 'package:mawaqit/src/elements/Loader.dart'; import 'package:mawaqit/src/helpers/HexColor.dart'; import 'package:mawaqit/src/pages/OfflineScreen.dart'; -import 'package:mawaqit/src/services/settings_manager.dart'; import 'package:provider/provider.dart'; import 'package:store_redirect/store_redirect.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -119,7 +119,6 @@ class MawaqitWebViewWidgetState extends State Widget build(BuildContext context) { super.build(context); print(widget.path); - final settings = Provider.of(context).settings; final userPreferences = context.watch(); return Focus( @@ -151,7 +150,9 @@ class MawaqitWebViewWidgetState extends State useShouldOverrideUrlLoading: true, useOnDownloadStart: true, mediaPlaybackRequiresUserGesture: false, - userAgent: Platform.isAndroid ? settings.userAgent!.valueAndroid! : settings.userAgent!.valueIOS!, + userAgent: Platform.isAndroid + ? MawaqitBackendSettingsConstant.kSettingsAndroidUserAgent + : MawaqitBackendSettingsConstant.kSettingsIosUserAgent, ), android: AndroidInAppWebViewOptions( useHybridComposition: true, @@ -159,7 +160,6 @@ class MawaqitWebViewWidgetState extends State ios: IOSInAppWebViewOptions( allowsInlineMediaPlayback: true, )), - pullToRefreshController: settings.pullRefresh == "1" ? pullToRefreshController : null, onWebViewCreated: (InAppWebViewController controller) { webViewController = controller; controller.addJavaScriptHandler( @@ -266,7 +266,7 @@ class MawaqitWebViewWidgetState extends State print(consoleMessage); }, ), - (isLoading && settings.loader != "empty") + (isLoading) ? Positioned( top: 0, bottom: 0, @@ -275,9 +275,9 @@ class MawaqitWebViewWidgetState extends State child: Container( color: Theme.of(context).scaffoldBackgroundColor, child: Loader( - type: settings.loader, + type: "Circle", color: Theme.of(context).brightness == Brightness.light - ? HexColor(settings.loaderColor) + ? HexColor("#490094") : Theme.of(context).primaryColor), ), ) diff --git a/pubspec.yaml b/pubspec.yaml index f797dae85..d07b2d35b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.17.3+1 +version: 1.18.0+1 environment: @@ -68,7 +68,7 @@ dependencies: store_redirect: ^2.0.1 connectivity_plus: ^4.0.0 uni_links: ^0.5.1 - + # TOOLS google_fonts: ^4.0.4 device_info_plus: ^9.0.2 @@ -96,7 +96,9 @@ dependencies: hijri: ^3.0.0 # webview_flutter: ^3.0.0 - + media_kit: ^1.1.11 # Primary package. + media_kit_video: ^1.2.5 # For video rendering. + media_kit_libs_video: ^1.0.5 # Native video dependencies. rive_splash_screen: ^0.1.1 lottie: ^2.3.2 flutter_svg: ^2.0.5 @@ -145,6 +147,8 @@ dependencies: scroll_to_index: ^3.0.1 fast_cached_network_image: ^1.2.0 + montenegrin_localization: ^0.1.0+1 + dev_dependencies: flutter_test: sdk: flutter