mirror of
https://github.com/K3vinb5/Unyo.git
synced 2026-06-13 05:49:42 +00:00
Merge pull request #79 from OTAKUWeBer/master
Add Discord RPC, Add Localization Support, Enhance Version Comparison, Fix Title issue, Adjust UI, Fix Video Aspect Ratio Problem, Fix And Improve Advanced Search, Persist server choice per title & resolve manga UI/shortcut issues
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -51,3 +51,6 @@ libmtorrentserver.so
|
||||
.fvm/
|
||||
/.fvmrc
|
||||
/pubspec.lock
|
||||
|
||||
# VsCode related
|
||||
.vscode/
|
||||
127
assets/languages/bn.json
Normal file
127
assets/languages/bn.json
Normal file
@@ -0,0 +1,127 @@
|
||||
{
|
||||
"entries": "এন্ট্রি",
|
||||
"continue_watching": "দেখা চালিয়ে যান",
|
||||
"continue_reading": "পড়া চালিয়ে যান",
|
||||
"episodes_watched": "দেখা হয়েছে এমন এপিসোড",
|
||||
"hours_watched": "দেখা হয়েছে এমন ঘণ্টা",
|
||||
"update_progress_automatically": "প্রগতি স্বয়ংক্রিয়ভাবে আপডেট করুন",
|
||||
"buttons_layout_menu_layout": "বোতামের বিন্যাস / মেনুর বিন্যাস",
|
||||
"select_language": "ভাষা নির্বাচন করুন",
|
||||
"video_duration_remaining_time": "ভিডিও সময়কাল / বাকি সময় দেখান",
|
||||
"select_theme": "থিম নির্বাচন করুন",
|
||||
"home": "হোম",
|
||||
"anime_list": "অ্যানিমে তালিকা",
|
||||
"manga_list": "মাঙ্গা তালিকা",
|
||||
"calendar": "ক্যালেন্ডার",
|
||||
"anime": "অ্যানিমে",
|
||||
"manga": "মাঙ্গা",
|
||||
"recently_released": "সাম্প্রতিক মুক্তি",
|
||||
"search": "অনুসন্ধান করুন...",
|
||||
"trending": "ট্রেন্ডিং",
|
||||
"season_popular": "এই মৌসুমে জনপ্রিয়",
|
||||
"advanced_search": "উন্নত অনুসন্ধান",
|
||||
"select_format": "ফরম্যাট নির্বাচন করুন",
|
||||
"select_season": "মৌসুম নির্বাচন করুন",
|
||||
"select_year": "বছর নির্বাচন করুন",
|
||||
"select_sorting": "ক্রম নির্বাচন করুন",
|
||||
"releasing": "মুক্তি পাচ্ছে",
|
||||
"unreleased": "এখনও মুক্তি পায়নি",
|
||||
"finished": "শেষ হয়েছে",
|
||||
"episodes": "এপিসোড",
|
||||
"chapters": "অধ্যায়",
|
||||
"wrong_no_title": "ভুল শিরোনাম / শিরোনাম নেই?",
|
||||
"update_entry": "এন্ট্রি আপডেট করুন",
|
||||
"status": "অবস্থা",
|
||||
"progress": "প্রগতি",
|
||||
"score": "স্কোর",
|
||||
"start_end_data": "শুরু/শেষ তারিখ",
|
||||
"confirm": "নিশ্চিত করুন",
|
||||
"cancel": "বাতিল করুন",
|
||||
"list_editor": "তালিকা সম্পাদনা",
|
||||
"select_title": "শিরোনাম নির্বাচন করুন",
|
||||
"select_new_title_text": "নতুন শিরোনাম নির্বাচন করুন অথবা নতুনভাবে অনুসন্ধান করুন",
|
||||
"yearly_popular": "বার্ষিক জনপ্রিয়",
|
||||
"monday": "সোমবার",
|
||||
"tuesday": "মঙ্গলবার",
|
||||
"wednesday": "বুধবার",
|
||||
"thursday": "বৃহস্পতিবার",
|
||||
"friday": "শুক্রবার",
|
||||
"saturday": "শনিবার",
|
||||
"sunday": "রবিবার",
|
||||
"not_yet_released": "এখনও মুক্তি পায়নি",
|
||||
"show_adult_content": "প্রাপ্তবয়স্ক বিষয়বস্তু দেখান",
|
||||
"please_wait_text": "অনুগ্রহ করে অপেক্ষা করুন, কয়েক সেকেন্ড সময় লাগতে পারে...",
|
||||
"select_quality": "মান নির্বাচন করুন",
|
||||
"error_occured_video_title": "একটি ত্রুটি ঘটেছে D:",
|
||||
"error_occured_video_text": "ত্রুটি ঘটেছে, অনুগ্রহ করে অন্য উৎস বা সার্ভার/মান চেষ্টা করুন",
|
||||
"quality_no_results": "এই উৎসে কিছু পাওয়া যায়নি, অনুগ্রহ করে অন্য উৎস বা সার্ভার/মান চেষ্টা করুন :D",
|
||||
"released": "মুক্তি পেয়েছে",
|
||||
"episode": "এপিসোড",
|
||||
"chapter": "অধ্যায়",
|
||||
"settings": "সেটিংস",
|
||||
"remote_endpoint": "রিমোট এক্সটেনশন / লোকাল এক্সটেনশন",
|
||||
"local_extensions": "লোকাল এক্সটেনশন",
|
||||
"extension": "এক্সটেনশন",
|
||||
"extensions": "এক্সটেনশনসমূহ",
|
||||
"change_repo": "রিপোজিটরি পরিবর্তন করুন",
|
||||
"change_repo_message": "ভিন্ন রিপোজিটরি ব্যবহার করতে চাইলে এখানে নতুন লিঙ্ক পেস্ট করুন এবং নিশ্চিত করুন",
|
||||
"need_help": "সাহায্য দরকার?",
|
||||
"need_help_title": "আপনার প্রয়োজনীয় সকল তথ্য ও সহায়তা এখানে আছে :D",
|
||||
"need_help_message": "অনেকেই ভাবতে পারেন, এই লোকাল এক্সটেনশনগুলো কী? মূলত, Unyo সার্ভারের বদলে আপনার নিজের কম্পিউটারে উৎস থাকে। এতে অনেক দেশে ভালো পারফরম্যান্স পাওয়া যায়। এছাড়াও, আপনি শুধু প্রয়োজনীয় এক্সটেনশন দেখতে পাবেন এবং নিজের কোডও করতে পারবেন। মূল রিপোতে নিয়মিত আপডেট আসবে এবং নতুন এক্সটেনশন যোগ হবে।",
|
||||
"no_extensions_title": "কোন এক্সটেনশন ইনস্টল করা নেই",
|
||||
"no_extensions_message": "আপনি লোকাল এক্সটেনশন চালু করেছেন কিন্তু কিছু ইনস্টল করেননি। লোকাল এক্সটেনশন পেজে গিয়ে ইনস্টল করতে পারেন :D",
|
||||
"unyo2gether_message": "আপনার বন্ধুর PeerId পেস্ট করুন অথবা নতুন একজন খুঁজুন!",
|
||||
"extensions_not_enabled_message": "লোকাল এক্সটেনশন সক্রিয় নয়। সেটিংসে গিয়ে এটি চালু করুন",
|
||||
"installed": "ইনস্টল হয়েছে",
|
||||
"console": "এক্সটেনশন সার্ভারের কনসোল",
|
||||
"previous_episode": "পূর্ববর্তী এপিসোড",
|
||||
"next_episode": "পরবর্তী এপিসোড",
|
||||
"play": "চালু করুন",
|
||||
"pause": "থামান",
|
||||
"change_audiotrack": "অডিও ট্র্যাক পরিবর্তন করুন",
|
||||
"change_subtitles": "সাবটাইটেল পরিবর্তন করুন",
|
||||
"enter_fullscreen": "পুরো স্ক্রিনে যান",
|
||||
"exit_fullscreen": "পুরো স্ক্রিন থেকে বের হোন",
|
||||
"connected": "সংযুক্ত",
|
||||
"not_connected": "সংযুক্ত নয়",
|
||||
"delete": "মুছে ফেলুন",
|
||||
"download": "ডাউনলোড করুন",
|
||||
"exit_fullscreen_on_video_exit": "ভিডিও বন্ধ হলে পুরো স্ক্রিন থেকে বের হয়ে যান",
|
||||
"resume_from": "এখান থেকে আবার শুরু করুন",
|
||||
"search_from_website": "ওয়েবসাইটে শিরোনাম অনুসন্ধান করুন",
|
||||
"current_selection": "বর্তমান নির্বাচন",
|
||||
"skip_opening_automatically": "স্বয়ংক্রিয়ভাবে ওপেনিং স্কিপ করুন (যদি থাকে)",
|
||||
"select_intro_skip_time": "ইন্ট্রো স্কিপের জন্য ডিফল্ট সময় নির্বাচন করুন",
|
||||
"select_default_title_type": "ডিফল্ট শিরোনামের ধরন নির্বাচন করুন",
|
||||
"change_playback_speed": "প্লেব্যাক গতি পরিবর্তন করুন",
|
||||
"select_episode_completed_percentage": "এপিসোড সম্পূর্ণ হয়েছে বোঝাতে কত শতাংশ নির্বাচন করবেন",
|
||||
"select_chapter_completed_percentage": "অধ্যায় সম্পূর্ণ হয়েছে বোঝাতে কত শতাংশ নির্বাচন করবেন",
|
||||
"enable_open_subtitles": "opensubtitles.org-এর সাবটাইটেল চালু করুন (যদি থাকে)",
|
||||
"no_title_found_dialog": "কোন শিরোনাম পাওয়া যায়নি, অনুগ্রহ করে 'ভুল শিরোনাম/নেই' মেনু অথবা অন্য উৎস চেষ্টা করুন",
|
||||
"page": "পৃষ্ঠা",
|
||||
"show_chapters": "অধ্যায়গুলো দেখান",
|
||||
"single_page": "একক পৃষ্ঠা",
|
||||
"double_page": "ডাবল পৃষ্ঠা",
|
||||
"long_strip": "লম্বা স্ট্রিপ",
|
||||
"left_to_right": "বাম থেকে ডানে",
|
||||
"right_to_left": "ডান থেকে বামে",
|
||||
"light_mode": "লাইট মোড",
|
||||
"dark_mode": "ডার্ক মোড",
|
||||
"fit_height": "উচ্চতার সাথে মানানসই করুন",
|
||||
"fit_width": "প্রস্থের সাথে মানানসই করুন",
|
||||
"logout_title": "আপনি কি লগ আউট করতে চান?",
|
||||
"logout_text": "আপনি কি সত্যিই লগ আউট করতে চান?",
|
||||
"select_user": "কেউ দেখছে?",
|
||||
"restore_default": "ডিফল্ট সেটিং ফিরিয়ে আনুন",
|
||||
"create_local_account": "লোকাল অ্যাকাউন্ট তৈরি করুন",
|
||||
"create_local_account_title": "নতুন অ্যাকাউন্ট",
|
||||
"add_account": "অ্যাকাউন্ট যোগ করুন",
|
||||
"insert_new_name": "নতুন নাম দিন",
|
||||
"sign_int_title": "নতুন অ্যাকাউন্ট যোগ করুন",
|
||||
"previous_chapter": "পূর্ববর্তী অধ্যায়",
|
||||
"next_chapter": "পরবর্তী অধ্যায়",
|
||||
"next_page": "পরবর্তী পৃষ্ঠা",
|
||||
"previous_page": "পূর্ববর্তী পৃষ্ঠা",
|
||||
"enable_discord_rpc": "Discord RPC"
|
||||
}
|
||||
|
||||
@@ -121,5 +121,6 @@
|
||||
"previous_chapter" : "Vorheriges Kapitel",
|
||||
"next_chapter" : "Nächstes Kapitel",
|
||||
"next_page" : "Nächste Seite",
|
||||
"previous_page" : "Vorherige Seite"
|
||||
"previous_page" : "Vorherige Seite",
|
||||
"enable_discord_rpc" : "Discord RPC"
|
||||
}
|
||||
|
||||
@@ -123,5 +123,6 @@
|
||||
"next_page" : "Next Page",
|
||||
"previous_page" : "Previous Page",
|
||||
"video_loading" : "Please wait, the video is loading",
|
||||
"cast_to_tv" : "Cast to TV"
|
||||
"cast_to_tv" : "Cast to TV",
|
||||
"enable_discord_rpc": "Discord RPC"
|
||||
}
|
||||
|
||||
@@ -121,5 +121,6 @@
|
||||
"previous_chapter" : "Capítulo anterior",
|
||||
"next_chapter" : "Próximo capítulo",
|
||||
"next_page" : "Página siguiente",
|
||||
"previous_page" : "Página anterior"
|
||||
"previous_page" : "Página anterior",
|
||||
"enable_discord_rpc" : "Discord RPC"
|
||||
}
|
||||
|
||||
@@ -121,5 +121,6 @@
|
||||
"previous_chapter" : "Chapitre précédent",
|
||||
"next_chapter" : "Chapitre suivant",
|
||||
"next_page" : "Page suivante",
|
||||
"previous_page" : "Page précédente"
|
||||
"previous_page" : "Page précédente",
|
||||
"enable_discord_rpc" : "Discord RPC"
|
||||
}
|
||||
|
||||
127
assets/languages/hi.json
Normal file
127
assets/languages/hi.json
Normal file
@@ -0,0 +1,127 @@
|
||||
{
|
||||
"entries": "प्रविष्टियाँ",
|
||||
"continue_watching": "देखना जारी रखें",
|
||||
"continue_reading": "पढ़ना जारी रखें",
|
||||
"episodes_watched": "देखे गए एपिसोड",
|
||||
"hours_watched": "देखे गए घंटे",
|
||||
"update_progress_automatically": "प्रगति स्वचालित रूप से अपडेट करें",
|
||||
"buttons_layout_menu_layout": "बटन लेआउट / मेनू लेआउट",
|
||||
"select_language": "भाषा चुनें",
|
||||
"video_duration_remaining_time": "वीडियो की अवधि दिखाएं / शेष समय दिखाएं",
|
||||
"select_theme": "थीम चुनें",
|
||||
"home": "मुखपृष्ठ",
|
||||
"anime_list": "ऐनिमी सूची",
|
||||
"manga_list": "मांगा सूची",
|
||||
"calendar": "कैलेंडर",
|
||||
"anime": "ऐनिमी",
|
||||
"manga": "मांगा",
|
||||
"recently_released": "हाल ही में जारी",
|
||||
"search": "खोजें...",
|
||||
"trending": "प्रचलित",
|
||||
"season_popular": "सीजन के लोकप्रिय",
|
||||
"advanced_search": "उन्नत खोज",
|
||||
"select_format": "प्रारूप चुनें",
|
||||
"select_season": "सीजन चुनें",
|
||||
"select_year": "वर्ष चुनें",
|
||||
"select_sorting": "क्रमबद्ध करना चुनें",
|
||||
"releasing": "जारी हो रहा है",
|
||||
"unreleased": "अप्रकाशित",
|
||||
"finished": "पूरा हुआ",
|
||||
"episodes": "एपिसोड",
|
||||
"chapters": "अध्याय",
|
||||
"wrong_no_title": "गलत शीर्षक / कोई शीर्षक नहीं?",
|
||||
"update_entry": "प्रविष्टि अपडेट करें",
|
||||
"status": "स्थिति",
|
||||
"progress": "प्रगति",
|
||||
"score": "स्कोर",
|
||||
"start_end_data": "प्रारंभ/समाप्ति तिथि",
|
||||
"confirm": "पुष्टि करें",
|
||||
"cancel": "रद्द करें",
|
||||
"list_editor": "सूची संपादक",
|
||||
"select_title": "शीर्षक चुनें",
|
||||
"select_new_title_text": "एक नया शीर्षक चुनें या नया खोजें",
|
||||
"yearly_popular": "वर्ष के लोकप्रिय",
|
||||
"monday": "सोमवार",
|
||||
"tuesday": "मंगलवार",
|
||||
"wednesday": "बुधवार",
|
||||
"thursday": "गुरुवार",
|
||||
"friday": "शुक्रवार",
|
||||
"saturday": "शनिवार",
|
||||
"sunday": "रविवार",
|
||||
"not_yet_released": "अभी तक जारी नहीं",
|
||||
"show_adult_content": "वयस्क सामग्री दिखाएं",
|
||||
"please_wait_text": "कृपया प्रतीक्षा करें, इसमें कुछ सेकंड लग सकते हैं...",
|
||||
"select_quality": "गुणवत्ता चुनें",
|
||||
"error_occured_video_title": "एक त्रुटि हुई D:",
|
||||
"error_occured_video_text": "एक त्रुटि हुई, कृपया अन्य स्रोत या सर्वर/गुणवत्ता आज़माएं",
|
||||
"quality_no_results": "स्रोत से कोई परिणाम नहीं मिला, कृपया अन्य स्रोत या सर्वर/गुणवत्ता आज़माएं :D",
|
||||
"released": "जारी किया गया",
|
||||
"episode": "एपिसोड",
|
||||
"chapter": "अध्याय",
|
||||
"settings": "सेटिंग्स",
|
||||
"remote_endpoint": "रिमोट एक्सटेंशन / स्थानीय एक्सटेंशन",
|
||||
"local_extensions": "स्थानीय एक्सटेंशन",
|
||||
"extension": "एक्सटेंशन",
|
||||
"extensions": "एक्सटेंशन",
|
||||
"change_repo": "भंडार बदलें",
|
||||
"change_repo_message": "यदि आप कोई अन्य भंडार उपयोग करना चाहते हैं, तो नया लिंक यहां चिपकाएं और पुष्टि करें",
|
||||
"need_help": "मदद चाहिए?",
|
||||
"need_help_title": "यहाँ वह मदद और जानकारी है जिसकी शायद आपको आवश्यकता है :D",
|
||||
"need_help_message": "कुछ लोग शायद सोच रहे होंगे, ये स्थानीय एक्सटेंशन क्या हैं? मूल रूप से, Unyo सर्वर की बजाय स्रोत आपके कंप्यूटर पर होते हैं। इससे कुछ देशों में बेहतर प्रदर्शन होता है। इसके अन्य लाभ हैं जैसे केवल आवश्यक एक्सटेंशन दिखाना, और आप अपने स्वयं के एक्सटेंशन भी कोड कर सकते हैं। मुख्य भंडार सक्रिय रूप से विकसित हो रहा है और समय के साथ और एक्सटेंशन जोड़े जाएंगे।",
|
||||
"no_extensions_title": "आपने कोई एक्सटेंशन इंस्टॉल नहीं किया है",
|
||||
"no_extensions_message": "इसका मतलब है कि आपने स्थानीय एक्सटेंशन चालू कर रखे हैं लेकिन कोई इंस्टॉल नहीं किया है। आप स्थानीय एक्सटेंशन पेज से कुछ इंस्टॉल कर सकते हैं :D",
|
||||
"unyo2gether_message": "कृपया अपने मित्र का PeerId चिपकाएं या एक नया ढूंढें!",
|
||||
"extensions_not_enabled_message": "स्थानीय एक्सटेंशन सक्रिय नहीं हैं। आप इन्हें सेटिंग्स में सक्रिय कर सकते हैं",
|
||||
"installed": "इंस्टॉल किया गया",
|
||||
"console": "एक्सटेंशन सर्वर कंसोल",
|
||||
"previous_episode": "पिछला एपिसोड",
|
||||
"next_episode": "अगला एपिसोड",
|
||||
"play": "चलाएं",
|
||||
"pause": "रोकें",
|
||||
"change_audiotrack": "ऑडियो ट्रैक बदलें",
|
||||
"change_subtitles": "उपशीर्षक बदलें",
|
||||
"enter_fullscreen": "पूर्ण स्क्रीन में जाएं",
|
||||
"exit_fullscreen": "पूर्ण स्क्रीन से बाहर आएं",
|
||||
"connected": "कनेक्टेड",
|
||||
"not_connected": "कनेक्ट नहीं है",
|
||||
"delete": "हटाएं",
|
||||
"download": "डाउनलोड करें",
|
||||
"exit_fullscreen_on_video_exit": "वीडियो बंद होने पर पूर्ण स्क्रीन से बाहर आएं",
|
||||
"resume_from": "यहां से फिर से शुरू करें",
|
||||
"search_from_website": "यहाँ साइट पर अपना शीर्षक खोजें",
|
||||
"current_selection": "वर्तमान चयन",
|
||||
"skip_opening_automatically": "स्वचालित रूप से ओपनिंग स्किप करें (यदि उपलब्ध हो)",
|
||||
"select_intro_skip_time": "इंट्रो स्किप करने के लिए डिफ़ॉल्ट समय चुनें",
|
||||
"select_default_title_type": "डिफ़ॉल्ट शीर्षक प्रकार चुनें",
|
||||
"change_playback_speed": "प्लेबैक गति बदलें",
|
||||
"select_episode_completed_percentage": "एपिसोड को पूर्ण मानने के लिए प्रतिशत चुनें",
|
||||
"select_chapter_completed_percentage": "अध्याय को पूर्ण मानने के लिए प्रतिशत चुनें",
|
||||
"enable_open_subtitles": "opensubtitles.org के उपशीर्षक सक्षम करें (यदि उपलब्ध हों)",
|
||||
"no_title_found_dialog": "कोई शीर्षक नहीं मिला, कृपया 'गलत शीर्षक/नहीं मिला' मेनू या अन्य स्रोत आज़माएं",
|
||||
"page": "पृष्ठ",
|
||||
"show_chapters": "अध्याय दिखाएं",
|
||||
"single_page": "एकल पृष्ठ",
|
||||
"double_page": "दोहरा पृष्ठ",
|
||||
"long_strip": "लंबी पट्टी",
|
||||
"left_to_right": "बाएं से दाएं",
|
||||
"right_to_left": "दाएं से बाएं",
|
||||
"light_mode": "प्रकाश मोड",
|
||||
"dark_mode": "गहरा मोड",
|
||||
"fit_height": "ऊंचाई के अनुसार फिट करें",
|
||||
"fit_width": "चौड़ाई के अनुसार फिट करें",
|
||||
"logout_title": "क्या आप लॉगआउट करना चाहते हैं?",
|
||||
"logout_text": "क्या आप वाकई लॉगआउट करना चाहते हैं?",
|
||||
"select_user": "कौन देख रहा है?",
|
||||
"restore_default": "डिफ़ॉल्ट पुनर्स्थापित करें",
|
||||
"create_local_account": "स्थानीय खाता बनाएँ",
|
||||
"create_local_account_title": "नया खाता",
|
||||
"add_account": "खाता जोड़ें",
|
||||
"insert_new_name": "उपयोगकर्ता नाम दर्ज करें",
|
||||
"sign_int_title": "नया खाता जोड़ें",
|
||||
"previous_chapter": "पिछला अध्याय",
|
||||
"next_chapter": "अगला अध्याय",
|
||||
"next_page": "अगला पृष्ठ",
|
||||
"previous_page": "पिछला पृष्ठ",
|
||||
"enable_discord_rpc" : "Discord RPC"
|
||||
}
|
||||
|
||||
@@ -121,5 +121,6 @@
|
||||
"previous_chapter" : "Capitolo precedente",
|
||||
"next_chapter" : "Capitolo successivo",
|
||||
"next_page" : "Pagina successiva",
|
||||
"previous_page" : "Pagina precedente"
|
||||
"previous_page" : "Pagina precedente",
|
||||
"enable_discord_rpc" : "Discord RPC"
|
||||
}
|
||||
|
||||
127
assets/languages/ja.json
Normal file
127
assets/languages/ja.json
Normal file
@@ -0,0 +1,127 @@
|
||||
{
|
||||
"entries": "エントリー",
|
||||
"continue_watching": "続きを見る",
|
||||
"continue_reading": "続きを読む",
|
||||
"episodes_watched": "視聴済みエピソード",
|
||||
"hours_watched": "視聴時間",
|
||||
"update_progress_automatically": "進捗を自動的に更新する",
|
||||
"buttons_layout_menu_layout": "ボタン配置 / メニュー配置",
|
||||
"select_language": "言語を選択",
|
||||
"video_duration_remaining_time": "動画の長さを表示 / 残り時間を表示",
|
||||
"select_theme": "テーマを選択",
|
||||
"home": "ホーム",
|
||||
"anime_list": "アニメ一覧",
|
||||
"manga_list": "マンガ一覧",
|
||||
"calendar": "カレンダー",
|
||||
"anime": "アニメ",
|
||||
"manga": "マンガ",
|
||||
"recently_released": "最近のリリース",
|
||||
"search": "検索...",
|
||||
"trending": "トレンド",
|
||||
"season_popular": "今期の人気作",
|
||||
"advanced_search": "高度な検索",
|
||||
"select_format": "フォーマットを選択",
|
||||
"select_season": "シーズンを選択",
|
||||
"select_year": "年を選択",
|
||||
"select_sorting": "並び替えを選択",
|
||||
"releasing": "放送中",
|
||||
"unreleased": "未公開",
|
||||
"finished": "完結済み",
|
||||
"episodes": "エピソード",
|
||||
"chapters": "チャプター",
|
||||
"wrong_no_title": "間違ったタイトル / タイトルがありませんか?",
|
||||
"update_entry": "エントリーを更新",
|
||||
"status": "ステータス",
|
||||
"progress": "進捗",
|
||||
"score": "スコア",
|
||||
"start_end_data": "開始日 / 終了日",
|
||||
"confirm": "確認",
|
||||
"cancel": "キャンセル",
|
||||
"list_editor": "リストエディタ",
|
||||
"select_title": "タイトルを選択",
|
||||
"select_new_title_text": "新しいタイトルを選択または検索してください",
|
||||
"yearly_popular": "年間人気作",
|
||||
"monday": "月曜日",
|
||||
"tuesday": "火曜日",
|
||||
"wednesday": "水曜日",
|
||||
"thursday": "木曜日",
|
||||
"friday": "金曜日",
|
||||
"saturday": "土曜日",
|
||||
"sunday": "日曜日",
|
||||
"not_yet_released": "まだリリースされていません",
|
||||
"show_adult_content": "アダルトコンテンツを表示",
|
||||
"please_wait_text": "少々お待ちください、数秒かかる場合があります...",
|
||||
"select_quality": "画質を選択",
|
||||
"error_occured_video_title": "エラーが発生しました D:",
|
||||
"error_occured_video_text": "エラーが発生しました。別のソースまたはサーバー/画質を試してください。",
|
||||
"quality_no_results": "ソースから結果が返されませんでした。他のソースまたはサーバー/画質をお試しください :D",
|
||||
"released": "リリース済み",
|
||||
"episode": "エピソード",
|
||||
"chapter": "チャプター",
|
||||
"settings": "設定",
|
||||
"remote_endpoint": "リモート拡張機能 / ローカル拡張機能",
|
||||
"local_extensions": "ローカル拡張機能",
|
||||
"extension": "拡張機能",
|
||||
"extensions": "拡張機能",
|
||||
"change_repo": "リポジトリを変更",
|
||||
"change_repo_message": "別のリポジトリを使用するには、新しいリンクを入力して確認してください",
|
||||
"need_help": "ヘルプが必要ですか?",
|
||||
"need_help_title": "必要なヘルプと情報はこちらです :D",
|
||||
"need_help_message": "ローカル拡張機能とは何か疑問に思っているかもしれません。これは、ソースがUnyoのサーバーではなく自分のPCにあるということです。これにより、ドイツのサーバーの制限を受けず、地域に合ったパフォーマンスが得られます。さらに、必要な拡張機能だけを使える利点もあります。自分で拡張機能を作成することも可能です。メインリポジトリは現在も活発に開発されており、今後さらに拡張機能が追加される予定です。",
|
||||
"no_extensions_title": "拡張機能がインストールされていません",
|
||||
"no_extensions_message": "ローカル拡張機能は有効ですが、まだ何もインストールされていません。ローカル拡張機能のページからインストールできます :D",
|
||||
"unyo2gether_message": "友達のPeerIdを入力するか、新しいものを見つけてください!",
|
||||
"extensions_not_enabled_message": "ローカル拡張機能は無効です。設定で有効にしてください。",
|
||||
"installed": "インストール済み",
|
||||
"console": "拡張機能サーバーのコンソール",
|
||||
"previous_episode": "前のエピソード",
|
||||
"next_episode": "次のエピソード",
|
||||
"play": "再生",
|
||||
"pause": "一時停止",
|
||||
"change_audiotrack": "音声トラックを変更",
|
||||
"change_subtitles": "字幕を変更",
|
||||
"enter_fullscreen": "全画面にする",
|
||||
"exit_fullscreen": "全画面を終了",
|
||||
"connected": "接続済み",
|
||||
"not_connected": "未接続",
|
||||
"delete": "削除",
|
||||
"download": "ダウンロード",
|
||||
"exit_fullscreen_on_video_exit": "動画終了時に全画面を解除する",
|
||||
"resume_from": "ここから再開",
|
||||
"search_from_website": "ここでタイトルを検索",
|
||||
"current_selection": "現在の選択",
|
||||
"skip_opening_automatically": "オープニングを自動スキップ(可能な場合)",
|
||||
"select_intro_skip_time": "オープニングスキップのデフォルト時間を選択",
|
||||
"select_default_title_type": "デフォルトのタイトル種別を選択",
|
||||
"change_playback_speed": "再生速度を変更",
|
||||
"select_episode_completed_percentage": "エピソード完了とする進捗率を選択",
|
||||
"select_chapter_completed_percentage": "チャプター完了とする進捗率を選択",
|
||||
"enable_open_subtitles": "opensubtitles.orgの字幕を有効にする(可能な場合)",
|
||||
"no_title_found_dialog": "タイトルが見つかりませんでした。「間違ったタイトル/見つからない」メニューまたは別のソースをお試しください",
|
||||
"page": "ページ",
|
||||
"show_chapters": "チャプターを表示",
|
||||
"single_page": "シングルページ",
|
||||
"double_page": "ダブルページ",
|
||||
"long_strip": "ロングストリップ",
|
||||
"left_to_right": "左から右へ",
|
||||
"right_to_left": "右から左へ",
|
||||
"light_mode": "ライトモード",
|
||||
"dark_mode": "ダークモード",
|
||||
"fit_height": "高さに合わせる",
|
||||
"fit_width": "幅に合わせる",
|
||||
"logout_title": "ログアウトしますか?",
|
||||
"logout_text": "本当にログアウトしますか?",
|
||||
"select_user": "誰が視聴していますか?",
|
||||
"restore_default": "デフォルトに戻す",
|
||||
"create_local_account": "ローカルアカウントを作成",
|
||||
"create_local_account_title": "新しいアカウント",
|
||||
"add_account": "アカウントを追加",
|
||||
"insert_new_name": "ユーザー名を入力",
|
||||
"sign_int_title": "新しいアカウントを追加",
|
||||
"previous_chapter": "前のチャプター",
|
||||
"next_chapter": "次のチャプター",
|
||||
"next_page": "次のページ",
|
||||
"previous_page": "前のページ",
|
||||
"enable_discord_rpc" : "Discord RPC"
|
||||
}
|
||||
|
||||
@@ -121,5 +121,6 @@
|
||||
"previous_chapter" : "Capítulo anterior",
|
||||
"next_chapter" : "Próximo capítulo",
|
||||
"next_page" : "Próxima página",
|
||||
"previous_page" : "Página anterior"
|
||||
"previous_page" : "Página anterior",
|
||||
"enable_discord_rpc" : "Discord RPC"
|
||||
}
|
||||
|
||||
@@ -121,5 +121,6 @@
|
||||
"previous_chapter" : "Предыдущая глава",
|
||||
"next_chapter" : "Следующая глава",
|
||||
"next_page" : "Следующая страница",
|
||||
"previous_page" : "Предыдущая страница"
|
||||
"previous_page" : "Предыдущая страница",
|
||||
"enable_discord_rpc" : "Discord RPC"
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ Future<List<AnimeModel>> getAnimeModelListTrending(
|
||||
int page, int n, int attempt) async {
|
||||
Map<String, dynamic> query = {
|
||||
"query":
|
||||
"query(\$page:Int = 1 \$id:Int \$type:MediaType \$isAdult:Boolean = false \$search:String \$format:[MediaFormat]\$status:MediaStatus \$countryOfOrigin:CountryCode \$source:MediaSource \$season:MediaSeason \$seasonYear:Int \$year:String \$onList:Boolean \$yearLesser:FuzzyDateInt \$yearGreater:FuzzyDateInt \$episodeLesser:Int \$episodeGreater:Int \$durationLesser:Int \$durationGreater:Int \$chapterLesser:Int \$chapterGreater:Int \$volumeLesser:Int \$volumeGreater:Int \$licensedBy:[Int]\$isLicensed:Boolean \$genres:[String]\$excludedGenres:[String]\$tags:[String]\$excludedTags:[String]\$minimumTagRank:Int \$sort:[MediaSort]=[POPULARITY_DESC,SCORE_DESC]){Page(page:\$page,perPage:$n){pageInfo{total perPage currentPage lastPage hasNextPage}media(id:\$id type:\$type season:\$season format_in:\$format status:\$status countryOfOrigin:\$countryOfOrigin source:\$source search:\$search onList:\$onList seasonYear:\$seasonYear startDate_like:\$year startDate_lesser:\$yearLesser startDate_greater:\$yearGreater episodes_lesser:\$episodeLesser episodes_greater:\$episodeGreater duration_lesser:\$durationLesser duration_greater:\$durationGreater chapters_lesser:\$chapterLesser chapters_greater:\$chapterGreater volumes_lesser:\$volumeLesser volumes_greater:\$volumeGreater licensedById_in:\$licensedBy isLicensed:\$isLicensed genre_in:\$genres genre_not_in:\$excludedGenres tag_in:\$tags tag_not_in:\$excludedTags minimumTagRank:\$minimumTagRank sort:\$sort isAdult:\$isAdult){id idMal title{userPreferred english romaji}coverImage{extraLarge large color}startDate{year month day}endDate{year month day}bannerImage season seasonYear description type format status(version:2)episodes duration chapters volumes genres isAdult averageScore popularity nextAiringEpisode{airingAt timeUntilAiring episode}mediaListEntry{id status}studios(isMain:true){edges{isMain node{id name}}}}}}",
|
||||
"query(\$page:Int = 1 \$id:Int \$type:MediaType \$isAdult:Boolean = false \$search:String \$format:[MediaFormat] \$status:MediaStatus \$countryOfOrigin:CountryCode \$source:MediaSource \$season:MediaSeason \$seasonYear:Int \$year:String \$onList:Boolean \$yearLesser:FuzzyDateInt \$yearGreater:FuzzyDateInt \$episodeLesser:Int \$episodeGreater:Int \$durationLesser:Int \$durationGreater:Int \$chapterLesser:Int \$chapterGreater:Int \$volumeLesser:Int \$volumeGreater:Int \$licensedBy:[Int]\$isLicensed:Boolean \$genres:[String]\$excludedGenres:[String]\$tags:[String]\$excludedTags:[String]\$minimumTagRank:Int \$sort:[MediaSort]=[POPULARITY_DESC,SCORE_DESC]){Page(page:\$page,perPage:$n){pageInfo{total perPage currentPage lastPage hasNextPage}media(id:\$id type:\$type season:\$season format_in:\$format status:\$status countryOfOrigin:\$countryOfOrigin source:\$source search:\$search onList:\$onList seasonYear:\$seasonYear startDate_like:\$year startDate_lesser:\$yearLesser startDate_greater:\$yearGreater episodes_lesser:\$episodeLesser episodes_greater:\$episodeGreater duration_lesser:\$durationLesser duration_greater:\$durationGreater chapters_lesser:\$chapterLesser chapters_greater:\$chapterGreater volumes_lesser:\$volumeLesser volumes_greater:\$volumeGreater licensedById_in:\$licensedBy isLicensed:\$isLicensed genre_in:\$genres genre_not_in:\$excludedGenres tag_in:\$tags tag_not_in:\$excludedTags minimumTagRank:\$minimumTagRank sort:\$sort isAdult:\$isAdult){id idMal title{userPreferred english romaji}coverImage{extraLarge large color}startDate{year month day}endDate{year month day}bannerImage season seasonYear description type format status(version:2)episodes duration chapters volumes genres isAdult averageScore popularity nextAiringEpisode{airingAt timeUntilAiring episode}mediaListEntry{id status}studios(isMain:true){edges{isMain node{id name}}}}}}",
|
||||
"variables": {
|
||||
"page": page,
|
||||
"type": "ANIME",
|
||||
@@ -127,12 +127,21 @@ Future<List<AnimeModel>> getAnimeModelListSeasonPopular(
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<AnimeModel>> getAnimeModelListSearch(String search, String sort,
|
||||
String season, String format, String year, int n) async {
|
||||
Future<List<AnimeModel>> getAnimeModelListSearch(
|
||||
String search,
|
||||
String genre,
|
||||
String sort,
|
||||
String season,
|
||||
String status,
|
||||
String format,
|
||||
String year,
|
||||
int n,
|
||||
) async {
|
||||
String finalSearch = search.isNotEmpty ? "search:\"$search\"" : "";
|
||||
|
||||
Map<String, dynamic> query = {
|
||||
"query":
|
||||
"query(\$page:Int = 1 \$id:Int \$type:MediaType \$isAdult:Boolean = false \$format:[MediaFormat]\$status:MediaStatus \$countryOfOrigin:CountryCode \$source:MediaSource \$season:MediaSeason \$seasonYear:Int \$year:String \$onList:Boolean \$yearLesser:FuzzyDateInt \$yearGreater:FuzzyDateInt \$episodeLesser:Int \$episodeGreater:Int \$durationLesser:Int \$durationGreater:Int \$chapterLesser:Int \$chapterGreater:Int \$volumeLesser:Int \$volumeGreater:Int \$licensedBy:[Int]\$isLicensed:Boolean \$genres:[String]\$excludedGenres:[String]\$tags:[String]\$excludedTags:[String]\$minimumTagRank:Int \$sort:[MediaSort]=[POPULARITY_DESC,SCORE_DESC]){Page(page:\$page,perPage:$n){pageInfo{total perPage currentPage lastPage hasNextPage}media(id:\$id type:\$type season:\$season format_in:\$format status:\$status countryOfOrigin:\$countryOfOrigin source:\$source $finalSearch onList:\$onList seasonYear:\$seasonYear startDate_like:\$year startDate_lesser:\$yearLesser startDate_greater:\$yearGreater episodes_lesser:\$episodeLesser episodes_greater:\$episodeGreater duration_lesser:\$durationLesser duration_greater:\$durationGreater chapters_lesser:\$chapterLesser chapters_greater:\$chapterGreater volumes_lesser:\$volumeLesser volumes_greater:\$volumeGreater licensedById_in:\$licensedBy isLicensed:\$isLicensed genre_in:\$genres genre_not_in:\$excludedGenres tag_in:\$tags tag_not_in:\$excludedTags minimumTagRank:\$minimumTagRank sort:\$sort isAdult:\$isAdult){id idMal title{userPreferred english romaji}coverImage{extraLarge large color}startDate{year month day}endDate{year month day}bannerImage season seasonYear description type format status(version:2)episodes duration chapters volumes genres isAdult averageScore popularity nextAiringEpisode{airingAt timeUntilAiring episode}mediaListEntry{id status}studios(isMain:true){edges{isMain node{id name}}}}}}",
|
||||
"query(\$page:Int = 1 \$id:Int \$type:MediaType \$isAdult:Boolean = false \$format:[MediaFormat] \$status:MediaStatus \$countryOfOrigin:CountryCode \$source:MediaSource \$season:MediaSeason \$seasonYear:Int \$year:String \$onList:Boolean \$yearLesser:FuzzyDateInt \$yearGreater:FuzzyDateInt \$episodeLesser:Int \$episodeGreater:Int \$durationLesser:Int \$durationGreater:Int \$chapterLesser:Int \$chapterGreater:Int \$volumeLesser:Int \$volumeGreater:Int \$licensedBy:[Int]\$isLicensed:Boolean \$genres:[String]\$excludedGenres:[String]\$tags:[String]\$excludedTags:[String]\$minimumTagRank:Int \$sort:[MediaSort]=[POPULARITY_DESC,SCORE_DESC]){Page(page:\$page,perPage:$n){pageInfo{total perPage currentPage lastPage hasNextPage}media(id:\$id type:\$type season:\$season format_in:\$format status:\$status countryOfOrigin:\$countryOfOrigin source:\$source $finalSearch onList:\$onList seasonYear:\$seasonYear startDate_like:\$year startDate_lesser:\$yearLesser startDate_greater:\$yearGreater episodes_lesser:\$episodeLesser episodes_greater:\$episodeGreater duration_lesser:\$durationLesser duration_greater:\$durationGreater chapters_lesser:\$chapterLesser chapters_greater:\$chapterGreater volumes_lesser:\$volumeLesser volumes_greater:\$volumeGreater licensedById_in:\$licensedBy isLicensed:\$isLicensed genre_in:\$genres genre_not_in:\$excludedGenres tag_in:\$tags tag_not_in:\$excludedTags minimumTagRank:\$minimumTagRank sort:\$sort isAdult:\$isAdult){id idMal title{userPreferred english romaji}coverImage{extraLarge large color}startDate{year month day}endDate{year month day}bannerImage season seasonYear description type format status(version:2)episodes duration chapters volumes genres isAdult averageScore popularity nextAiringEpisode{airingAt timeUntilAiring episode}mediaListEntry{id status}studios(isMain:true){edges{isMain node{id name}}}}}}",
|
||||
"variables": {
|
||||
"page": 1,
|
||||
"type": "ANIME",
|
||||
@@ -140,10 +149,11 @@ Future<List<AnimeModel>> getAnimeModelListSearch(String search, String sort,
|
||||
"sort": "SEARCH_MATCH"
|
||||
else
|
||||
"sort": "${sort.toUpperCase()}_DESC",
|
||||
if (format != "Select Format") "format": format.toUpperCase(),
|
||||
if (format != "Select Format") "format": [format.toUpperCase().replaceAll(' ', '_')],
|
||||
if (season != "Select Season") "season": season.toUpperCase(),
|
||||
if (year != "Select Year") "seasonYear": int.parse(year),
|
||||
//"search": search
|
||||
if (genre != "Select Genre") "genres": [genre],
|
||||
if (status != "Select Status") "status": status.toUpperCase().replaceAll(' ', '_')
|
||||
}
|
||||
};
|
||||
var url = Uri.parse(anilistEndpoint);
|
||||
|
||||
@@ -10,7 +10,7 @@ Future<List<MangaModel>> getMangaModelListTrending(
|
||||
int page, int n, int attempt) async {
|
||||
Map<String, dynamic> query = {
|
||||
"query":
|
||||
"query(\$page:Int \$id:Int \$type:MediaType \$isAdult:Boolean = false \$search:String \$format:[MediaFormat]\$status:MediaStatus \$countryOfOrigin:CountryCode \$source:MediaSource \$season:MediaSeason \$seasonYear:Int \$year:String \$onList:Boolean \$yearLesser:FuzzyDateInt \$yearGreater:FuzzyDateInt \$episodeLesser:Int \$episodeGreater:Int \$durationLesser:Int \$durationGreater:Int \$chapterLesser:Int \$chapterGreater:Int \$volumeLesser:Int \$volumeGreater:Int \$licensedBy:[Int]\$isLicensed:Boolean \$genres:[String]\$excludedGenres:[String]\$tags:[String]\$excludedTags:[String]\$minimumTagRank:Int \$sort:[MediaSort]=[POPULARITY_DESC,SCORE_DESC]){Page(page:\$page,perPage:$n){pageInfo{total perPage currentPage lastPage hasNextPage}media(id:\$id type:\$type season:\$season format_in:\$format status:\$status countryOfOrigin:\$countryOfOrigin source:\$source search:\$search onList:\$onList seasonYear:\$seasonYear startDate_like:\$year startDate_lesser:\$yearLesser startDate_greater:\$yearGreater episodes_lesser:\$episodeLesser episodes_greater:\$episodeGreater duration_lesser:\$durationLesser duration_greater:\$durationGreater chapters_lesser:\$chapterLesser chapters_greater:\$chapterGreater volumes_lesser:\$volumeLesser volumes_greater:\$volumeGreater licensedById_in:\$licensedBy isLicensed:\$isLicensed genre_in:\$genres genre_not_in:\$excludedGenres tag_in:\$tags tag_not_in:\$excludedTags minimumTagRank:\$minimumTagRank sort:\$sort isAdult:\$isAdult){id idMal title{userPreferred english romaji}coverImage{extraLarge large color}startDate{year month day}endDate{year month day}bannerImage season seasonYear description type format status(version:2)chapters duration chapters volumes genres isAdult averageScore popularity nextAiringEpisode{airingAt timeUntilAiring episode}mediaListEntry{id status}studios(isMain:true){edges{isMain node{id name}}}}}}",
|
||||
"query(\$page:Int \$id:Int \$type:MediaType \$isAdult:Boolean = false \$search:String \$format:[MediaFormat] \$status:MediaStatus \$countryOfOrigin:CountryCode \$source:MediaSource \$season:MediaSeason \$seasonYear:Int \$year:String \$onList:Boolean \$yearLesser:FuzzyDateInt \$yearGreater:FuzzyDateInt \$episodeLesser:Int \$episodeGreater:Int \$durationLesser:Int \$durationGreater:Int \$chapterLesser:Int \$chapterGreater:Int \$volumeLesser:Int \$volumeGreater:Int \$licensedBy:[Int]\$isLicensed:Boolean \$genres:[String]\$excludedGenres:[String]\$tags:[String]\$excludedTags:[String]\$minimumTagRank:Int \$sort:[MediaSort]=[POPULARITY_DESC,SCORE_DESC]){Page(page:\$page,perPage:$n){pageInfo{total perPage currentPage lastPage hasNextPage}media(id:\$id type:\$type season:\$season format_in:\$format status:\$status countryOfOrigin:\$countryOfOrigin source:\$source search:\$search onList:\$onList seasonYear:\$seasonYear startDate_like:\$year startDate_lesser:\$yearLesser startDate_greater:\$yearGreater episodes_lesser:\$episodeLesser episodes_greater:\$episodeGreater duration_lesser:\$durationLesser duration_greater:\$durationGreater chapters_lesser:\$chapterLesser chapters_greater:\$chapterGreater volumes_lesser:\$volumeLesser volumes_greater:\$volumeGreater licensedById_in:\$licensedBy isLicensed:\$isLicensed genre_in:\$genres genre_not_in:\$excludedGenres tag_in:\$tags tag_not_in:\$excludedTags minimumTagRank:\$minimumTagRank sort:\$sort isAdult:\$isAdult){id idMal title{userPreferred english romaji}coverImage{extraLarge large color}startDate{year month day}endDate{year month day}bannerImage season seasonYear description type format status(version:2)chapters duration chapters volumes genres isAdult averageScore popularity nextAiringEpisode{airingAt timeUntilAiring episode}mediaListEntry{id status}studios(isMain:true){edges{isMain node{id name}}}}}}",
|
||||
"variables": {
|
||||
"page": page,
|
||||
"type": "MANGA",
|
||||
@@ -418,25 +418,58 @@ Future<List<MangaModel>> getMangaModelListRecentlyReleased(
|
||||
// print(response.body);
|
||||
// }
|
||||
|
||||
Future<List<MangaModel>> getMangaModelListSearch(String search, String sort,
|
||||
String season, String format, String year, int n) async {
|
||||
Future<List<MangaModel>> getMangaModelListSearch(
|
||||
String search,
|
||||
String sort,
|
||||
String format,
|
||||
String status,
|
||||
String country,
|
||||
String genre,
|
||||
int n)
|
||||
async {
|
||||
String finalSearch = search.isNotEmpty ? "search:\"$search\"" : "";
|
||||
|
||||
String? countryFilter;
|
||||
if (country != "Select Country") {
|
||||
switch (country) {
|
||||
case 'Japan':
|
||||
countryFilter = 'JP';
|
||||
break;
|
||||
case 'Taiwan':
|
||||
countryFilter = 'TW';
|
||||
break;
|
||||
case 'South Korea':
|
||||
countryFilter = 'KR';
|
||||
break;
|
||||
case 'China':
|
||||
countryFilter = 'CN';
|
||||
break;
|
||||
default:
|
||||
countryFilter = country.toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> query = {
|
||||
"query":
|
||||
"query(\$page:Int = 1 \$id:Int \$type:MediaType \$isAdult:Boolean = false \$format:[MediaFormat]\$status:MediaStatus \$countryOfOrigin:CountryCode \$source:MediaSource \$season:MediaSeason \$seasonYear:Int \$year:String \$onList:Boolean \$yearLesser:FuzzyDateInt \$yearGreater:FuzzyDateInt \$episodeLesser:Int \$episodeGreater:Int \$durationLesser:Int \$durationGreater:Int \$chapterLesser:Int \$chapterGreater:Int \$volumeLesser:Int \$volumeGreater:Int \$licensedBy:[Int]\$isLicensed:Boolean \$genres:[String]\$excludedGenres:[String]\$tags:[String]\$excludedTags:[String]\$minimumTagRank:Int \$sort:[MediaSort]=[POPULARITY_DESC,SCORE_DESC]){Page(page:\$page,perPage:$n){pageInfo{total perPage currentPage lastPage hasNextPage}media(id:\$id type:\$type season:\$season format_in:\$format status:\$status countryOfOrigin:\$countryOfOrigin source:\$source $finalSearch onList:\$onList seasonYear:\$seasonYear startDate_like:\$year startDate_lesser:\$yearLesser startDate_greater:\$yearGreater episodes_lesser:\$episodeLesser episodes_greater:\$episodeGreater duration_lesser:\$durationLesser duration_greater:\$durationGreater chapters_lesser:\$chapterLesser chapters_greater:\$chapterGreater volumes_lesser:\$volumeLesser volumes_greater:\$volumeGreater licensedById_in:\$licensedBy isLicensed:\$isLicensed genre_in:\$genres genre_not_in:\$excludedGenres tag_in:\$tags tag_not_in:\$excludedTags minimumTagRank:\$minimumTagRank sort:\$sort isAdult:\$isAdult){id idMal title{userPreferred english romaji}coverImage{extraLarge large color}startDate{year month day}endDate{year month day}bannerImage season seasonYear description type format status(version:2)episodes duration chapters volumes genres isAdult averageScore popularity nextAiringEpisode{airingAt timeUntilAiring episode}mediaListEntry{id status}studios(isMain:true){edges{isMain node{id name}}}}}}",
|
||||
"query(\$page:Int = 1 \$id:Int \$type:MediaType \$isAdult:Boolean = false \$format:[MediaFormat] \$status:MediaStatus \$countryOfOrigin:CountryCode \$source:MediaSource \$season:MediaSeason \$seasonYear:Int \$year:String \$onList:Boolean \$yearLesser:FuzzyDateInt \$yearGreater:FuzzyDateInt \$episodeLesser:Int \$episodeGreater:Int \$durationLesser:Int \$durationGreater:Int \$chapterLesser:Int \$chapterGreater:Int \$volumeLesser:Int \$volumeGreater:Int \$licensedBy:[Int]\$isLicensed:Boolean \$genres:[String]\$excludedGenres:[String]\$tags:[String]\$excludedTags:[String]\$minimumTagRank:Int \$sort:[MediaSort]=[POPULARITY_DESC,SCORE_DESC]){Page(page:\$page,perPage:$n){pageInfo{total perPage currentPage lastPage hasNextPage}media(id:\$id type:\$type season:\$season format_in:\$format status:\$status countryOfOrigin:\$countryOfOrigin source:\$source $finalSearch onList:\$onList seasonYear:\$seasonYear startDate_like:\$year startDate_lesser:\$yearLesser startDate_greater:\$yearGreater episodes_lesser:\$episodeLesser episodes_greater:\$episodeGreater duration_lesser:\$durationLesser duration_greater:\$durationGreater chapters_lesser:\$chapterLesser chapters_greater:\$chapterGreater volumes_lesser:\$volumeLesser volumes_greater:\$volumeGreater licensedById_in:\$licensedBy isLicensed:\$isLicensed genre_in:\$genres genre_not_in:\$excludedGenres tag_in:\$tags tag_not_in:\$excludedTags minimumTagRank:\$minimumTagRank sort:\$sort isAdult:\$isAdult){id idMal title{userPreferred english romaji}coverImage{extraLarge large color}startDate{year month day}endDate{year month day}bannerImage season seasonYear description type format status(version:2)episodes duration chapters volumes genres isAdult averageScore popularity nextAiringEpisode{airingAt timeUntilAiring episode}mediaListEntry{id status}studios(isMain:true){edges{isMain node{id name}}}}}}",
|
||||
"variables": {
|
||||
"page": 1,
|
||||
"type": "MANGA",
|
||||
if (sort == "Select Sorting")
|
||||
"sort": "SEARCH_MATCH"
|
||||
else if (sort == "A-Z")
|
||||
"sort": "TITLE_ENGLISH"
|
||||
else if (sort == "Z-A")
|
||||
"sort": "TITLE_ENGLISH_DESC"
|
||||
else
|
||||
"sort": sort.toUpperCase(),
|
||||
if (format != "Select Format") "format": format.toUpperCase(),
|
||||
if (season != "Select Season") "season": season.toUpperCase(),
|
||||
if (year != "Select Year") "seasonYear": int.parse(year),
|
||||
//"search": search
|
||||
if (format != "Select Format") "format": [format.toUpperCase().replaceAll(' ', '_')],
|
||||
if (genre != "Select Genre") "genres": [genre],
|
||||
if (status != "Select Status") "status": status.toUpperCase().replaceAll(' ', '_'),
|
||||
if (countryFilter != null) "countryOfOrigin": countryFilter,
|
||||
}
|
||||
};
|
||||
|
||||
var url = Uri.parse(anilistEndpoint);
|
||||
var response = await http.post(
|
||||
url,
|
||||
|
||||
@@ -94,6 +94,20 @@ class UpdateDialog extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if [newVersion] is strictly greater than [currentVersion].
|
||||
bool _isVersionGreater(String newVersion, String currentVersion) {
|
||||
List<String> newParts = newVersion.split('.');
|
||||
List<String> currParts = currentVersion.split('.');
|
||||
int maxLength = newParts.length > currParts.length ? newParts.length : currParts.length;
|
||||
for (int i = 0; i < maxLength; i++) {
|
||||
int n = i < newParts.length ? int.tryParse(newParts[i]) ?? 0 : 0;
|
||||
int c = i < currParts.length ? int.tryParse(currParts[i]) ?? 0 : 0;
|
||||
if (n > c) return true;
|
||||
if (n < c) return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void showUpdateDialog(BuildContext context) async {
|
||||
var url = Uri.parse(apiEndpoint);
|
||||
var response = await http.get(url);
|
||||
@@ -114,7 +128,9 @@ void showUpdateDialog(BuildContext context) async {
|
||||
Map<String, dynamic> jsonResponse = json.decode(response.body);
|
||||
final String markdown = jsonResponse["body"] as String;
|
||||
final String newVersion = jsonResponse["tag_name"] as String;
|
||||
if (currentVersion == newVersion) return;
|
||||
|
||||
// Don't show dialog if version is not newer or tagged to ignore
|
||||
if (!_isVersionGreater(newVersion, currentVersion)) return;
|
||||
if (newVersion.contains("ignore")) return;
|
||||
showDialog(
|
||||
context: context,
|
||||
|
||||
@@ -16,6 +16,13 @@ import 'package:fvp/fvp.dart' as fvp;
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:unyo/util/utils.dart';
|
||||
import 'package:flutter_window_close/flutter_window_close.dart';
|
||||
import 'package:unyo/models/models.dart';
|
||||
import 'package:unyo/util/constants.dart';
|
||||
|
||||
Future<void> shutdownCleanup() async {
|
||||
await discord.cleanup();
|
||||
processManager.stopProcess();
|
||||
}
|
||||
|
||||
Future<void> main() async {
|
||||
logger.i("Initializing dependencies");
|
||||
@@ -29,19 +36,37 @@ Future<void> main() async {
|
||||
Hive.registerAdapter(UserMediaModelAdapter());
|
||||
Hive.registerAdapter(MangaModelAdapter());
|
||||
Hive.registerAdapter(AnimeModelAdapter());
|
||||
fvp.registerWith(options: {
|
||||
'platforms': ['linux', 'macos'],
|
||||
});
|
||||
|
||||
prefs = PreferencesModel();
|
||||
await prefs.init();
|
||||
|
||||
if (Platform.isWindows) {
|
||||
fvp.registerWith(options: {
|
||||
'platforms': ['windows'],
|
||||
'video.decoders': ['DXVA', 'FFmpeg'],
|
||||
'player': {"avformat.extension_picky": "0"}
|
||||
});
|
||||
} else {
|
||||
fvp.registerWith(options: {
|
||||
'platforms': ['linux', 'macos'],
|
||||
});
|
||||
}
|
||||
// await FlutterDiscordRPC.initialize(
|
||||
// "1266242749485809748",
|
||||
// );
|
||||
|
||||
final bool discordEnabled = prefs.getBool("discord_rpc") ?? false;
|
||||
if (discordEnabled) {
|
||||
await discord.initDiscordRPC();
|
||||
}
|
||||
|
||||
// Handle forced shutdown (Ctrl+C, SIGTERM)
|
||||
ProcessSignal.sigint.watch().listen((_) async {
|
||||
await shutdownCleanup();
|
||||
exit(0);
|
||||
});
|
||||
ProcessSignal.sigterm.watch().listen((_) async {
|
||||
await shutdownCleanup();
|
||||
exit(0);
|
||||
});
|
||||
|
||||
logger.i("Initializing Unyo");
|
||||
runApp(
|
||||
EasyLocalization(
|
||||
@@ -53,6 +78,9 @@ Future<void> main() async {
|
||||
Locale('it'),
|
||||
Locale('pt'),
|
||||
Locale('ru'),
|
||||
Locale('ja'),
|
||||
Locale('bn'),
|
||||
Locale('hi'),
|
||||
],
|
||||
useOnlyLangCode: true,
|
||||
path: 'assets/languages',
|
||||
@@ -82,15 +110,17 @@ class _MyAppState extends State<MyApp> {
|
||||
super.initState();
|
||||
FlutterWindowClose.setWindowShouldCloseHandler(() async {
|
||||
logger.i("Unyo is exiting...");
|
||||
processManager.stopProcess();
|
||||
print("Killed internal server");
|
||||
await shutdownCleanup();
|
||||
logger.i("Cleanup done; exiting now.");
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
processManager.stopProcess();
|
||||
Future.microtask(() async {
|
||||
await shutdownCleanup();
|
||||
});
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
@@ -85,18 +85,27 @@ class AnimeModel {
|
||||
};
|
||||
}
|
||||
|
||||
String getDefaultTitle() {
|
||||
int titleType = prefs.getInt("default_title_type") ?? 0;
|
||||
switch (titleType) {
|
||||
case 0:
|
||||
return userPreferedTitle ?? "";
|
||||
case 1:
|
||||
return englishTitle ?? "";
|
||||
case 2:
|
||||
return japaneseTitle ?? "";
|
||||
default:
|
||||
return userPreferedTitle ?? "";
|
||||
/// Returns the first non-empty string in [candidates], or `''` if none.
|
||||
String _firstNonEmpty(List<String?> candidates) {
|
||||
for (final s in candidates) {
|
||||
if (s?.isNotEmpty ?? false) return s!;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
String getDefaultTitle() {
|
||||
final type = prefs.getInt("default_title_type") ?? 0;
|
||||
|
||||
final orders = <List<String?>>[
|
||||
[userPreferedTitle, englishTitle, japaneseTitle], // 0
|
||||
[englishTitle, userPreferedTitle, japaneseTitle], // 1
|
||||
[japaneseTitle, userPreferedTitle, englishTitle], // 2
|
||||
];
|
||||
final candidates = (type >= 0 && type < orders.length)
|
||||
? orders[type]
|
||||
: orders[0];
|
||||
|
||||
return _firstNonEmpty(candidates);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -79,24 +79,36 @@ class MangaModel {
|
||||
'description': description,
|
||||
'format': format,
|
||||
'averageScore': averageScore,
|
||||
'chaptes': chapters,
|
||||
'chapters': chapters,
|
||||
'currentEpisode': currentEpisode,
|
||||
'duration': duration,
|
||||
};
|
||||
}
|
||||
|
||||
String getDefaultTitle() {
|
||||
int titleType = prefs.getInt("default_title_type") ?? 0;
|
||||
switch (titleType) {
|
||||
case 0:
|
||||
return userPreferedTitle ?? "";
|
||||
case 1:
|
||||
return englishTitle ?? "";
|
||||
case 2:
|
||||
return japaneseTitle ?? "";
|
||||
default:
|
||||
return userPreferedTitle ?? "";
|
||||
/// Returns the first non-empty string in [candidates], or `''` if none.
|
||||
String _firstNonEmpty(List<String?> candidates) {
|
||||
for (final s in candidates) {
|
||||
if (s?.isNotEmpty ?? false) return s!;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
String getDefaultTitle() {
|
||||
final type = prefs.getInt("default_title_type") ?? 0;
|
||||
|
||||
// Define the three title‐orderings
|
||||
final orders = <List<String?>>[
|
||||
[userPreferedTitle, englishTitle, japaneseTitle], // 0
|
||||
[englishTitle, userPreferedTitle, japaneseTitle], // 1
|
||||
[japaneseTitle, userPreferedTitle, englishTitle], // 2
|
||||
];
|
||||
|
||||
// Safely pick an order (defaults to orders[0])
|
||||
final candidates = (type >= 0 && type < orders.length)
|
||||
? orders[type]
|
||||
: orders[0];
|
||||
|
||||
return _firstNonEmpty(candidates);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -23,6 +23,7 @@ class AnimeDetailsScreen extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _AnimeDetailsScreenState extends State<AnimeDetailsScreen> {
|
||||
String get _prefsKeyForThisAnime => 'default_anime_source_${widget.currentAnime.id}';
|
||||
UserMediaModel? userAnimeModel;
|
||||
List<String> searches = [];
|
||||
List<String> searchesId = [];
|
||||
@@ -64,12 +65,35 @@ class _AnimeDetailsScreenState extends State<AnimeDetailsScreen> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
animeSources = globalAnimesSources;
|
||||
|
||||
mediaContentModel = MediaContentModel(anilistId: widget.currentAnime.id);
|
||||
mediaContentModel.init();
|
||||
Future.delayed(Duration.zero, () {
|
||||
updateSource(0, context);
|
||||
|
||||
Future.delayed(Duration.zero, () async {
|
||||
|
||||
final keys = animeSources.keys.toList()..sort();
|
||||
final savedKey = prefs.getInt(_prefsKeyForThisAnime);
|
||||
|
||||
int initialSource = 0;
|
||||
|
||||
if (keys.isNotEmpty) {
|
||||
if (savedKey != null && keys.contains(savedKey)) {
|
||||
initialSource = savedKey;
|
||||
} else if (savedKey == null || savedKey < keys.first) {
|
||||
initialSource = keys.first;
|
||||
} else if (savedKey > keys.last) {
|
||||
initialSource = keys.last;
|
||||
} else {
|
||||
initialSource = keys.lastWhere((k) => k < savedKey);
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint('Using animeSource key $initialSource '
|
||||
'(available: $keys, saved: $savedKey)');
|
||||
if (!mounted) return;
|
||||
updateSource(initialSource, context);
|
||||
setUserAnimeModel();
|
||||
});
|
||||
setUserAnimeModel();
|
||||
}
|
||||
|
||||
void setWrongTitleSearch(void Function(void Function()) setDialogState) {
|
||||
@@ -203,6 +227,7 @@ class _AnimeDetailsScreenState extends State<AnimeDetailsScreen> {
|
||||
Navigator.of(context).pop();
|
||||
return;
|
||||
}
|
||||
prefs.setInt(_prefsKeyForThisAnime, newSource);
|
||||
setState(() {
|
||||
manualTitleSelection = false;
|
||||
currentSource = newSource;
|
||||
@@ -262,7 +287,7 @@ class _AnimeDetailsScreenState extends State<AnimeDetailsScreen> {
|
||||
showErrorDialog(context, exception: context.tr("no_title_found_dialog"));
|
||||
return;
|
||||
}
|
||||
discordRPC.setWatchingAnimeActivity(
|
||||
discord.setWatchingAnimeActivity(
|
||||
widget.currentAnime, animeEpisode, mediaContentModel);
|
||||
if (!mounted) return;
|
||||
showDialog(
|
||||
@@ -306,7 +331,7 @@ class _AnimeDetailsScreenState extends State<AnimeDetailsScreen> {
|
||||
height: height,
|
||||
wrongTitleSearchController: wrongTitleSearchController,
|
||||
wrongTitleEntries: wrongTitleEntries,
|
||||
manualSelection: currentSearchIndex,
|
||||
manualSelection: currentSearchIndex ?? 0,
|
||||
currentSearchString: manualTitleSelection
|
||||
? currentSearchString!
|
||||
: searches.isNotEmpty
|
||||
|
||||
@@ -96,11 +96,14 @@ class _AnimeScreenState extends State<AnimeScreen> {
|
||||
}
|
||||
|
||||
void setScrollListener() {
|
||||
double offset = pageScrollController.offset;
|
||||
if (offset > 200 && bannerInfoVisible) {
|
||||
final offset = pageScrollController.offset;
|
||||
const hideThreshold = 220.0;
|
||||
const showThreshold = 100.0;
|
||||
|
||||
if (offset > hideThreshold && bannerInfoVisible) {
|
||||
bannerInfoVisible = false;
|
||||
setState(() {});
|
||||
} else if (offset < 200 && !bannerInfoVisible) {
|
||||
} else if (offset < showThreshold && !bannerInfoVisible) {
|
||||
bannerInfoVisible = true;
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@@ -35,8 +35,8 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
List<MangaModel>? rereadingList;
|
||||
|
||||
void attemptLogin() async {
|
||||
prefs = PreferencesModel();
|
||||
await prefs.init();
|
||||
// prefs = PreferencesModel();
|
||||
// await prefs.init();
|
||||
if (!prefs.isUserLogged()) {
|
||||
Future.delayed(Duration.zero, () {
|
||||
// prefs.getUsers(setState);
|
||||
@@ -75,7 +75,6 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
updateUserLists();
|
||||
};
|
||||
updateHomeScreenState = setState;
|
||||
discordRPC.initDiscordRPC();
|
||||
}
|
||||
|
||||
void startExtensions() {
|
||||
|
||||
@@ -26,6 +26,7 @@ class MangaDetailsScreen extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _MangaDetailsScreenState extends State<MangaDetailsScreen> {
|
||||
String get _prefsKeyForThisManga => 'default_manga_source_${widget.currentManga.id}';
|
||||
late VideoScreen videoScreen;
|
||||
UserMediaModel? userMangaModel;
|
||||
List<String> searches = [];
|
||||
@@ -67,10 +68,34 @@ class _MangaDetailsScreenState extends State<MangaDetailsScreen> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
mangaSources = globalMangasSources;
|
||||
Future.delayed(Duration.zero, () {
|
||||
updateSource(0, context);
|
||||
|
||||
Future.delayed(Duration.zero, () async {
|
||||
|
||||
final keys = mangaSources.keys.toList()..sort();
|
||||
final savedKey = prefs.getInt(_prefsKeyForThisManga);
|
||||
|
||||
int initialSource = 0;
|
||||
|
||||
if (keys.isNotEmpty) {
|
||||
if (savedKey != null && keys.contains(savedKey)) {
|
||||
initialSource = savedKey;
|
||||
} else if (savedKey == null || savedKey < keys.first) {
|
||||
initialSource = keys.first;
|
||||
} else if (savedKey > keys.last) {
|
||||
initialSource = keys.last;
|
||||
} else {
|
||||
initialSource = keys.lastWhere((k) => k < savedKey);
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint(
|
||||
'Using mangaSource key $initialSource '
|
||||
'(available: $keys, saved: $savedKey)'
|
||||
);
|
||||
if (!mounted) return;
|
||||
updateSource(initialSource, context);
|
||||
setUserMangaModel();
|
||||
});
|
||||
setUserMangaModel();
|
||||
}
|
||||
|
||||
void setWrongTitleSearch(void Function(void Function()) setDialogState) {
|
||||
@@ -211,7 +236,7 @@ class _MangaDetailsScreenState extends State<MangaDetailsScreen> {
|
||||
Navigator.of(context).pop();
|
||||
return;
|
||||
}
|
||||
|
||||
prefs.setInt(_prefsKeyForThisManga, newSource);
|
||||
setState(() {
|
||||
currentSource = newSource;
|
||||
currentSearch = 0;
|
||||
@@ -281,7 +306,7 @@ class _MangaDetailsScreenState extends State<MangaDetailsScreen> {
|
||||
showErrorDialog(context, exception: context.tr("no_title_found_dialog"));
|
||||
return;
|
||||
}
|
||||
discordRPC.setReadingMangaActivity(widget.currentManga, chapterNum);
|
||||
discord.setReadingMangaActivity(widget.currentManga, chapterNum);
|
||||
Navigator.push(
|
||||
context,
|
||||
customPageRouter(
|
||||
|
||||
@@ -54,14 +54,16 @@ class _MangaScreenState extends State<MangaScreen> {
|
||||
}
|
||||
|
||||
void setScrollListener() {
|
||||
if (pageScrollController.offset > 200 && bannerInfoVisible) {
|
||||
setState(() {
|
||||
bannerInfoVisible = false;
|
||||
});
|
||||
} else if (pageScrollController.offset <= 200 && !bannerInfoVisible) {
|
||||
setState(() {
|
||||
bannerInfoVisible = true;
|
||||
});
|
||||
final offset = pageScrollController.offset;
|
||||
const hideThreshold = 225.0;
|
||||
const showThreshold = 140.0;
|
||||
|
||||
if (offset > hideThreshold && bannerInfoVisible) {
|
||||
bannerInfoVisible = false;
|
||||
setState(() {});
|
||||
} else if (offset < showThreshold && !bannerInfoVisible) {
|
||||
bannerInfoVisible = true;
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,8 @@ class _MediaSearchScreenState extends State<MediaSearchScreen> {
|
||||
double totalHeight = 0;
|
||||
List<dynamic> searchMediaList = [];
|
||||
Timer searchTimer = Timer(const Duration(milliseconds: 500), () {});
|
||||
|
||||
// Filters
|
||||
List<String> sortBy = [
|
||||
"Select Sorting",
|
||||
"Score",
|
||||
@@ -38,7 +40,7 @@ class _MediaSearchScreenState extends State<MediaSearchScreen> {
|
||||
"A-Z",
|
||||
"Z-A"
|
||||
];
|
||||
List<String> format = [
|
||||
List<String> animeFormat = [
|
||||
"Select Format",
|
||||
"Tv",
|
||||
"Tv Short",
|
||||
@@ -48,13 +50,70 @@ class _MediaSearchScreenState extends State<MediaSearchScreen> {
|
||||
"Ona",
|
||||
"Music"
|
||||
];
|
||||
List<String> mangaFormat = [
|
||||
"Select Format",
|
||||
"Manga",
|
||||
"Novel",
|
||||
"One shot",
|
||||
];
|
||||
List<String> publishingStatus = [
|
||||
"Select Status",
|
||||
"Releasing",
|
||||
"Finished",
|
||||
"Not yet released",
|
||||
"Hiatus",
|
||||
"Cancelled"
|
||||
];
|
||||
List<String> animeStatus = [
|
||||
"Select Status",
|
||||
"Releasing",
|
||||
"Finished",
|
||||
"Not yet released",
|
||||
"Cancelled"
|
||||
];
|
||||
List<String> countryOfOrigin = [
|
||||
"Select Country",
|
||||
"Japan",
|
||||
"South Korea",
|
||||
"China",
|
||||
"Taiwan",
|
||||
];
|
||||
List<String> genre = [
|
||||
"Select Genre",
|
||||
"Action",
|
||||
"Adventure",
|
||||
"Comedy",
|
||||
"Drama",
|
||||
"Ecchi",
|
||||
"Fantasy",
|
||||
"Hentai",
|
||||
"Horror",
|
||||
"Mahou Shoujo",
|
||||
"Mecha",
|
||||
"Music",
|
||||
"Mystery",
|
||||
"Psychological",
|
||||
"Romance",
|
||||
"Sci-Fi",
|
||||
"Slice of Life",
|
||||
"Sports",
|
||||
"Supernatural",
|
||||
"Thriller"
|
||||
];
|
||||
List<String> season = ["Select Season", "Winter", "Spring", "Summer", "Fall"];
|
||||
|
||||
late List<String> years;
|
||||
|
||||
String currentSortBy = "Select Sorting";
|
||||
String currentFormat = "Select Format";
|
||||
String currentGenre = "Select Genre";
|
||||
String currentStatus = "Select Status";
|
||||
String currentSeason = "Select Season";
|
||||
String currentCountry = "Select Country";
|
||||
String currentYear = "Select Year";
|
||||
|
||||
TextEditingController textFieldController = TextEditingController();
|
||||
|
||||
final double minimumWidth = 124.08;
|
||||
final double minimumHeight = 195.44;
|
||||
double maximumWidth = 0;
|
||||
@@ -98,19 +157,29 @@ class _MediaSearchScreenState extends State<MediaSearchScreen> {
|
||||
void resetSearchTimer(String search) {
|
||||
searchTimer.cancel();
|
||||
searchTimer = Timer(const Duration(milliseconds: 500), () async {
|
||||
//Calls anilist anilist api
|
||||
if (widget.type == "ANIME") {
|
||||
var newSearchMediaList = await getAnimeModelListSearch(search,
|
||||
currentSortBy, currentSeason, currentFormat, currentYear, 50);
|
||||
setState(() {
|
||||
searchMediaList = newSearchMediaList;
|
||||
});
|
||||
var newList = await getAnimeModelListSearch(
|
||||
search,
|
||||
currentGenre,
|
||||
currentSortBy,
|
||||
currentSeason,
|
||||
currentStatus,
|
||||
currentFormat,
|
||||
currentYear,
|
||||
50,
|
||||
);
|
||||
setState(() => searchMediaList = newList);
|
||||
} else {
|
||||
var newSearchMediaList = await getMangaModelListSearch(search,
|
||||
currentSortBy, currentSeason, currentFormat, currentYear, 50);
|
||||
setState(() {
|
||||
searchMediaList = newSearchMediaList;
|
||||
});
|
||||
var newList = await getMangaModelListSearch(
|
||||
search,
|
||||
currentSortBy,
|
||||
currentFormat,
|
||||
currentStatus,
|
||||
currentCountry,
|
||||
currentGenre,
|
||||
50,
|
||||
);
|
||||
setState(() => searchMediaList = newList);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -191,7 +260,7 @@ class _MediaSearchScreenState extends State<MediaSearchScreen> {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
StyledTextField(
|
||||
width: totalWidth * 0.22,
|
||||
width: totalWidth * 0.20,
|
||||
controller: textFieldController,
|
||||
onChanged: (text) {
|
||||
resetSearchTimer(text);
|
||||
@@ -201,77 +270,86 @@ class _MediaSearchScreenState extends State<MediaSearchScreen> {
|
||||
hint: "search".tr(),
|
||||
),
|
||||
StyledDropDown(
|
||||
width: totalWidth * 0.22,
|
||||
horizontalPadding: 8,
|
||||
width: widget.type == "ANIME" ? totalWidth * 0.11 : totalWidth * 0.15,
|
||||
onTap: (index) {
|
||||
currentFormat = format[index];
|
||||
currentGenre = genre[index];
|
||||
resetSearchTimer(textFieldController.text);
|
||||
},
|
||||
horizontalPadding: 0,
|
||||
items: const [
|
||||
Text("Select Format"),
|
||||
Text("Tv"),
|
||||
Text("Tv Short"),
|
||||
Text("Movie"),
|
||||
Text("Special"),
|
||||
Text("Ova"),
|
||||
Text("Ona"),
|
||||
Text("Music"),
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
width: totalWidth * 0.26,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
StyledDropDown(
|
||||
width: totalWidth * 0.12,
|
||||
onTap: (index) {
|
||||
currentSeason = season[index];
|
||||
resetSearchTimer(textFieldController.text);
|
||||
},
|
||||
horizontalPadding: 0,
|
||||
items: const [
|
||||
Text("Select Season"),
|
||||
Text("Winter"),
|
||||
Text("Spring"),
|
||||
Text("Summer"),
|
||||
Text("Fall"),
|
||||
],
|
||||
),
|
||||
StyledDropDown(
|
||||
width: totalWidth * 0.12,
|
||||
onTap: (index) {
|
||||
currentYear = years[index];
|
||||
resetSearchTimer(textFieldController.text);
|
||||
},
|
||||
horizontalPadding: 0,
|
||||
items: [
|
||||
...years.map(
|
||||
(year) {
|
||||
return Text(year);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
items: genre.map((e) => Text(e)).toList(),
|
||||
),
|
||||
StyledDropDown(
|
||||
horizontalPadding: 8,
|
||||
width: widget.type == "ANIME" ? totalWidth * 0.09 : totalWidth * 0.12,
|
||||
onTap: (index) {
|
||||
currentFormat = widget.type == "MANGA"
|
||||
? mangaFormat[index]
|
||||
: animeFormat[index];
|
||||
resetSearchTimer(textFieldController.text);
|
||||
},
|
||||
items: (widget.type == "MANGA"
|
||||
? mangaFormat
|
||||
: animeFormat)
|
||||
.map((e) => Text(e))
|
||||
.toList(),
|
||||
),
|
||||
if (widget.type == "ANIME") ...[
|
||||
StyledDropDown(
|
||||
horizontalPadding: 8,
|
||||
width: totalWidth * 0.09,
|
||||
onTap: (index) {
|
||||
currentSeason = season[index];
|
||||
resetSearchTimer(textFieldController.text);
|
||||
},
|
||||
items: season.map((e) => Text(e)).toList(),
|
||||
),
|
||||
StyledDropDown(
|
||||
horizontalPadding: 8,
|
||||
width: totalWidth * 0.09,
|
||||
onTap: (index) {
|
||||
currentYear = years[index];
|
||||
resetSearchTimer(textFieldController.text);
|
||||
},
|
||||
items: years.map((e) => Text(e)).toList(),
|
||||
),
|
||||
StyledDropDown(
|
||||
horizontalPadding: 8,
|
||||
width: totalWidth * 0.11,
|
||||
onTap: (index) {
|
||||
currentStatus = animeStatus[index];
|
||||
resetSearchTimer(textFieldController.text);
|
||||
},
|
||||
items: animeStatus.map((e) => Text(e)).toList(),
|
||||
),
|
||||
],
|
||||
if (widget.type == "MANGA") ...[
|
||||
StyledDropDown(
|
||||
horizontalPadding: 8,
|
||||
width: totalWidth * 0.12,
|
||||
onTap: (index) {
|
||||
currentStatus = publishingStatus[index];
|
||||
resetSearchTimer(textFieldController.text);
|
||||
},
|
||||
items: publishingStatus.map((e) => Text(e)).toList(),
|
||||
),
|
||||
StyledDropDown(
|
||||
horizontalPadding: 8,
|
||||
width: totalWidth * 0.12,
|
||||
onTap: (index) {
|
||||
currentCountry = countryOfOrigin[index];
|
||||
resetSearchTimer(textFieldController.text);
|
||||
},
|
||||
items: countryOfOrigin.map((e) => Text(e)).toList(),
|
||||
),
|
||||
],
|
||||
StyledDropDown(
|
||||
horizontalPadding: 8,
|
||||
width: widget.type == "ANIME" ? totalWidth * 0.09 : totalWidth * 0.12,
|
||||
onTap: (index) {
|
||||
currentSortBy = sortBy[index];
|
||||
resetSearchTimer(textFieldController.text);
|
||||
},
|
||||
width: totalWidth * 0.22,
|
||||
horizontalPadding: 0,
|
||||
items: const [
|
||||
Text("Select Sorting"),
|
||||
Text("Score"),
|
||||
Text("Popularity"),
|
||||
Text("Trending"),
|
||||
Text("A-Z"),
|
||||
Text("Z-A"),
|
||||
],
|
||||
items: sortBy.map((e) => Text(e)).toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -286,45 +364,43 @@ class _MediaSearchScreenState extends State<MediaSearchScreen> {
|
||||
children: [
|
||||
Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
children: [
|
||||
...searchMediaList.mapIndexed((index, mediaModel) {
|
||||
double calculatedWidth = adjustedWidth * 0.1;
|
||||
double calculatedHeight = adjustedHeight * 0.28;
|
||||
return Hero(
|
||||
tag: "${"grid-view"}-$index",
|
||||
child: AnimeWidget(
|
||||
title: (widget.type == "MANGA")
|
||||
? mediaModel.title
|
||||
: mediaModel.getDefaultTitle(),
|
||||
children: searchMediaList.mapIndexed((index, mediaModel) {
|
||||
double w = adjustedWidth * 0.1;
|
||||
double h = adjustedHeight * 0.28;
|
||||
final widgetWidth = min(max(w, minimumWidth), maximumWidth);
|
||||
final widgetHeight = min(max(h, minimumHeight), maximumHeight);
|
||||
|
||||
final mediaTile = widget.type == "ANIME"
|
||||
? AnimeWidget(
|
||||
title: mediaModel.getDefaultTitle(),
|
||||
score: mediaModel.averageScore,
|
||||
coverImage: mediaModel.coverImage,
|
||||
onTap: () {
|
||||
if (widget.type == "ANIME") {
|
||||
openAnime(
|
||||
context,
|
||||
mediaModel,
|
||||
"grid-view-$index",
|
||||
);
|
||||
} else {
|
||||
openMangaDetails(
|
||||
context,
|
||||
mediaModel,
|
||||
"grid-view-$index",
|
||||
);
|
||||
}
|
||||
},
|
||||
onTap: () => openAnime(context, mediaModel, "grid-view-$index"),
|
||||
textColor: Colors.white,
|
||||
height: min(max(calculatedHeight, minimumHeight),
|
||||
maximumHeight),
|
||||
width: min(max(calculatedWidth, minimumWidth),
|
||||
maximumWidth),
|
||||
width: widgetWidth,
|
||||
height: widgetHeight,
|
||||
year: mediaModel.startDate,
|
||||
format: mediaModel.format,
|
||||
status: mediaModel.status,
|
||||
),
|
||||
);
|
||||
})
|
||||
],
|
||||
)
|
||||
: MangaWidget(
|
||||
title: mediaModel.getDefaultTitle(),
|
||||
score: mediaModel.averageScore,
|
||||
coverImage: mediaModel.coverImage,
|
||||
onTap: () => openMangaDetails(context, mediaModel, "grid-view-$index"),
|
||||
textColor: Colors.white,
|
||||
width: widgetWidth,
|
||||
height: widgetHeight,
|
||||
year: mediaModel.startDate,
|
||||
format: mediaModel.format,
|
||||
status: mediaModel.status,
|
||||
);
|
||||
|
||||
return Hero(
|
||||
tag: "grid-view-$index",
|
||||
child: mediaTile,
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -50,11 +50,18 @@ class _ReadingScreenState extends State<ReadingScreen> {
|
||||
}
|
||||
|
||||
void initPages(String chapterId) async {
|
||||
chapterPages = await widget.getMangaChapterPages(chapterId);
|
||||
setState(() {
|
||||
currentPage = 0;
|
||||
totalPages = 0;
|
||||
chapterBytes = [];
|
||||
});
|
||||
|
||||
final chapterPages = await widget.getMangaChapterPages(chapterId);
|
||||
|
||||
setState(() {
|
||||
this.chapterPages = chapterPages;
|
||||
totalPages = chapterPages.length;
|
||||
//kinda scuffed
|
||||
chapterBytes = List.filled(totalPages, null);
|
||||
chapterBytes = List<Uint8List?>.filled(totalPages, null);
|
||||
});
|
||||
downloadChapterPages();
|
||||
}
|
||||
@@ -71,42 +78,44 @@ class _ReadingScreenState extends State<ReadingScreen> {
|
||||
|
||||
void goBackPage() {
|
||||
setState(() {
|
||||
if (currentPageOption == 1) {
|
||||
if (currentPage > 1) {
|
||||
currentPage -= 2;
|
||||
}
|
||||
} else {
|
||||
if (currentPage > 0) {
|
||||
currentPage--;
|
||||
}
|
||||
if (currentPageOption == 1) { // Double page mode
|
||||
currentPage = (currentPage - 2).clamp(0, totalPages - 1);
|
||||
} else { // Single page mode
|
||||
currentPage = (currentPage - 1).clamp(0, totalPages - 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void goForwardPage() {
|
||||
setState(() {
|
||||
if (currentPageOption == 1) {
|
||||
if (currentPage < totalPages - 3) {
|
||||
currentPage += 2;
|
||||
} else if (currentPage < totalPages - 2) {
|
||||
currentPage++;
|
||||
}
|
||||
} else {
|
||||
currentPage++;
|
||||
if (currentPageOption == 1) { // Double page mode
|
||||
final newPage = (currentPage + 2).clamp(0, totalPages - 1);
|
||||
currentPage = newPage < totalPages ? newPage : totalPages - 1;
|
||||
} else { // Single page mode
|
||||
currentPage = (currentPage + 1).clamp(0, totalPages - 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void onReceivedKeys(LogicalKeyboardKey logicalKey) {
|
||||
bool isLeftToRight = currentOrientationOption == 0;
|
||||
switch (logicalKey) {
|
||||
case LogicalKeyboardKey.space:
|
||||
goForwardPage();
|
||||
break;
|
||||
case LogicalKeyboardKey.arrowLeft:
|
||||
goBackPage();
|
||||
if (isLeftToRight) {
|
||||
goBackPage();
|
||||
} else {
|
||||
goForwardPage();
|
||||
}
|
||||
break;
|
||||
case LogicalKeyboardKey.arrowRight:
|
||||
goForwardPage();
|
||||
if (isLeftToRight) {
|
||||
goForwardPage();
|
||||
} else {
|
||||
goBackPage();
|
||||
}
|
||||
break;
|
||||
case LogicalKeyboardKey.arrowUp:
|
||||
goBackPage();
|
||||
@@ -183,9 +192,16 @@ class _ReadingScreenState extends State<ReadingScreen> {
|
||||
}
|
||||
|
||||
Widget singlePageList(double width, double height) {
|
||||
if (currentPage == totalPages - 1) {
|
||||
currentPage--;
|
||||
if (chapterBytes.isEmpty) {
|
||||
return SizedBox(
|
||||
width: width,
|
||||
height: height,
|
||||
child: Center(
|
||||
child: LoadingAnimationWidget.inkDrop(color: Colors.white, size: 30),
|
||||
),
|
||||
);
|
||||
}
|
||||
final safePage = currentPage.clamp(0, chapterBytes.length - 1);
|
||||
return SizedBox(
|
||||
width: width,
|
||||
height: height,
|
||||
@@ -212,14 +228,14 @@ class _ReadingScreenState extends State<ReadingScreen> {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
chapterBytes[currentPage] != null
|
||||
chapterBytes[safePage] != null
|
||||
? SizedBox(
|
||||
height: height,
|
||||
width: width,
|
||||
child: currentFittingOption != 0
|
||||
? SingleChildScrollView(
|
||||
child: Image.memory(
|
||||
chapterBytes[currentPage]!,
|
||||
chapterBytes[safePage]!,
|
||||
color: currentInverseModeOption == 0
|
||||
? Colors.black
|
||||
: Colors.white,
|
||||
@@ -230,7 +246,7 @@ class _ReadingScreenState extends State<ReadingScreen> {
|
||||
),
|
||||
)
|
||||
: Image.memory(
|
||||
chapterBytes[currentPage]!,
|
||||
chapterBytes[safePage]!,
|
||||
color: currentInverseModeOption == 0
|
||||
? Colors.black
|
||||
: Colors.white,
|
||||
@@ -251,6 +267,7 @@ class _ReadingScreenState extends State<ReadingScreen> {
|
||||
}
|
||||
|
||||
Widget doublePageList(bool leftToRight, double width, double height) {
|
||||
bool hasNextPage = (currentPage + 1) < chapterBytes.length;
|
||||
return SizedBox(
|
||||
width: width,
|
||||
// height: height,
|
||||
@@ -277,8 +294,8 @@ class _ReadingScreenState extends State<ReadingScreen> {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
(chapterBytes[currentPage] != null &&
|
||||
chapterBytes[currentPage + 1] != null)
|
||||
(chapterBytes[currentPage] != null && (!hasNextPage ||
|
||||
chapterBytes[currentPage + 1] != null))
|
||||
? SizedBox(
|
||||
height: height,
|
||||
width: width,
|
||||
@@ -297,7 +314,7 @@ class _ReadingScreenState extends State<ReadingScreen> {
|
||||
}
|
||||
|
||||
Widget doublePages(bool leftToRight, double width) {
|
||||
if (currentPage == chapterBytes.length) {
|
||||
if (currentPage + 1 >= chapterBytes.length) {
|
||||
//last chapter page
|
||||
return Image.memory(
|
||||
chapterBytes[currentPage]!,
|
||||
|
||||
@@ -44,38 +44,38 @@ class _ScaffoldScreenState extends State<ScaffoldScreen> {
|
||||
refreshAnimeScreenState(() {});
|
||||
isScreenRefreshed.add(to);
|
||||
}
|
||||
discordRPC.setPageActivity("Anime Screen");
|
||||
discord.setPageActivity("Anime Screen");
|
||||
break;
|
||||
case 1:
|
||||
discordRPC.setPageActivity("Home Screen");
|
||||
discord.setPageActivity("Home Screen");
|
||||
break;
|
||||
case 2:
|
||||
if (!isScreenRefreshed.contains(to)) {
|
||||
refreshMangaScreenState(() {});
|
||||
isScreenRefreshed.add(to);
|
||||
}
|
||||
discordRPC.setPageActivity("Manga Screen");
|
||||
discord.setPageActivity("Manga Screen");
|
||||
break;
|
||||
case 3:
|
||||
if (!isScreenRefreshed.contains(to)) {
|
||||
refreshAnimeUserListScreenState(() {});
|
||||
isScreenRefreshed.add(to);
|
||||
}
|
||||
discordRPC.setPageActivity("Anime List Screen");
|
||||
discord.setPageActivity("Anime List Screen");
|
||||
break;
|
||||
case 4:
|
||||
if (!isScreenRefreshed.contains(to)) {
|
||||
refreshMangaUserListScreenState(() {});
|
||||
isScreenRefreshed.add(to);
|
||||
}
|
||||
discordRPC.setPageActivity("Manga List Screen");
|
||||
discord.setPageActivity("Manga List Screen");
|
||||
break;
|
||||
case 5:
|
||||
if (!isScreenRefreshed.contains(to)) {
|
||||
refreshCalendarScreenState(() {});
|
||||
isScreenRefreshed.add(to);
|
||||
}
|
||||
discordRPC.setPageActivity("Calendar Screen");
|
||||
discord.setPageActivity("Calendar Screen");
|
||||
break;
|
||||
case 6:
|
||||
if (!(prefs.getBool("remote_endpoint") ?? false)) {
|
||||
@@ -90,7 +90,7 @@ class _ScaffoldScreenState extends State<ScaffoldScreen> {
|
||||
refreshLocalExtensionsScreenState!(() {});
|
||||
}
|
||||
}
|
||||
discordRPC.setPageActivity("Local Extensions Screen");
|
||||
discord.setPageActivity("Local Extensions Screen");
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,6 +121,20 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
},
|
||||
value: (prefs.getBool("display_video_duration") ?? false),
|
||||
),
|
||||
SettingsSwitchOptionWidget(
|
||||
title: context.tr("enable_discord_rpc"),
|
||||
onPressed: (bool newValue) async {
|
||||
setState(() {
|
||||
prefs.setBool("discord_rpc", newValue);
|
||||
});
|
||||
if (newValue) {
|
||||
await discord.initDiscordRPC();
|
||||
} else {
|
||||
await discord.cleanup();
|
||||
}
|
||||
},
|
||||
value: prefs.getBool("discord_rpc") ?? false,
|
||||
),
|
||||
SettingsSwitchOptionWidget(
|
||||
title: context.tr("enable_open_subtitles"),
|
||||
onPressed: (bool newValue) {
|
||||
|
||||
@@ -176,7 +176,7 @@ class _VideoScreenState extends State<VideoScreen> {
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: 16 / 9,
|
||||
aspectRatio: _mixedController.videoController.value.aspectRatio(),
|
||||
child:
|
||||
VideoPlayer(_mixedController.videoController),
|
||||
),
|
||||
|
||||
@@ -32,7 +32,7 @@ String remoteEndPoint = "https://unyo-be.k3vinb5.dev";
|
||||
String localEndPoint = "http://localhost:8084";
|
||||
const String anilistEndpoint = "https://graphql.anilist.co";
|
||||
Future<List<CastDevice>> devices = CastDiscoveryService().search();
|
||||
DiscordRPC discordRPC = DiscordRPC();
|
||||
final DiscordRPC discord = DiscordRPC();
|
||||
MTorrentServer torrentServer = MTorrentServer();
|
||||
var logger = Logger(
|
||||
printer: PrettyPrinter(),
|
||||
@@ -69,6 +69,9 @@ Map<String, String> langs = {
|
||||
"es": "Spanish",
|
||||
"it": "Italian",
|
||||
"de": "German",
|
||||
"ja": "Japanese",
|
||||
"bn": "Bangla",
|
||||
"hi": "Hindi",
|
||||
// "po" : "Polish",
|
||||
"ru": "Russian",
|
||||
// "zh-cn" : "Chinese (Traditional)",
|
||||
@@ -98,6 +101,7 @@ Map<String, double> chapterCompletedOptions = {
|
||||
"100%": 1.0,
|
||||
};
|
||||
|
||||
|
||||
void setBannerPallete(
|
||||
String url, void Function(void Function()) setState) async {
|
||||
ImageProvider image = NetworkImage(url);
|
||||
@@ -156,7 +160,7 @@ void initThemes(int selected, void Function(void Function()) setState) async {
|
||||
|
||||
void openMangaDetails(
|
||||
BuildContext context, MangaModel currentManga, String tag) {
|
||||
discordRPC.setNavigatingMangaActivity(currentManga);
|
||||
discord.setNavigatingMangaActivity(currentManga);
|
||||
var mangaScreen = MangaDetailsScreen(
|
||||
currentManga: currentManga,
|
||||
tag: tag,
|
||||
@@ -168,7 +172,7 @@ void openMangaDetails(
|
||||
}
|
||||
|
||||
void openAnime(BuildContext context, AnimeModel currentAnime, String tag) {
|
||||
discordRPC.setNavigatingAnimeActivity(currentAnime);
|
||||
discord.setNavigatingAnimeActivity(currentAnime);
|
||||
var animeScreen = AnimeDetailsScreen(
|
||||
currentAnime: currentAnime,
|
||||
tag: tag,
|
||||
|
||||
@@ -1,23 +1,64 @@
|
||||
import 'package:flutter_discord_rpc/flutter_discord_rpc.dart';
|
||||
import 'package:unyo/models/models.dart';
|
||||
import 'package:unyo/util/utils.dart';
|
||||
|
||||
class DiscordRPC {
|
||||
late DateTime initTime;
|
||||
late bool discordFound;
|
||||
static const _appId = '1266242749485809748';
|
||||
bool _initialized = false;
|
||||
DateTime initTime = DateTime.now();
|
||||
bool discordConnected = false;
|
||||
|
||||
void initDiscordRPC() {
|
||||
/// Initialize Discord RPC once
|
||||
Future<void> initDiscordRPC() async {
|
||||
try {
|
||||
FlutterDiscordRPC.instance.connect();
|
||||
initTime = DateTime.now();
|
||||
setPageActivity("Home Screen");
|
||||
// only call initialize() once per process
|
||||
if (!_initialized) {
|
||||
await FlutterDiscordRPC.initialize(_appId);
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
// if we're already connected just update the activity
|
||||
if (discordConnected && FlutterDiscordRPC.instance.isConnected) {
|
||||
setPageActivity('Home Screen');
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise, connect and then set the initial activity
|
||||
await FlutterDiscordRPC.instance.connect(autoRetry: true);
|
||||
discordConnected = FlutterDiscordRPC.instance.isConnected;
|
||||
|
||||
logger.i('Discord RPC connected: $discordConnected');
|
||||
|
||||
if (discordConnected) {
|
||||
setPageActivity('Home Screen');
|
||||
} else {
|
||||
logger.i('Discord RPC not connected');
|
||||
}
|
||||
|
||||
// listen for reconnects (e.g. if the user restarts Discord)
|
||||
FlutterDiscordRPC.instance.isConnectedStream.listen((connected) {
|
||||
discordConnected = connected;
|
||||
logger.i('Discord RPC connection status: $connected');
|
||||
if (connected) {
|
||||
setPageActivity('Home Screen');
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
print("No Discord instance was found");
|
||||
discordFound = false;
|
||||
logger.e('Discord RPC initialization failed: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> cleanup() async {
|
||||
if (!discordConnected) return;
|
||||
try {
|
||||
await FlutterDiscordRPC.instance.clearActivity();
|
||||
await FlutterDiscordRPC.instance.disconnect();
|
||||
} catch (_) {}
|
||||
discordConnected = false;
|
||||
}
|
||||
|
||||
void setPageActivity(String page) {
|
||||
if (!discordFound) return;
|
||||
if (!discordConnected) return;
|
||||
FlutterDiscordRPC.instance.setActivity(
|
||||
activity: RPCActivity(
|
||||
assets: RPCAssets(
|
||||
@@ -37,7 +78,7 @@ class DiscordRPC {
|
||||
}
|
||||
|
||||
void setNavigatingAnimeActivity(AnimeModel animeModel) {
|
||||
if (!discordFound) return;
|
||||
if (!discordConnected) return;
|
||||
FlutterDiscordRPC.instance.setActivity(
|
||||
activity: RPCActivity(
|
||||
assets: RPCAssets(
|
||||
@@ -57,7 +98,7 @@ class DiscordRPC {
|
||||
}
|
||||
|
||||
void setNavigatingMangaActivity(MangaModel mangaModel) {
|
||||
if (!discordFound) return;
|
||||
if (!discordConnected) return;
|
||||
FlutterDiscordRPC.instance.setActivity(
|
||||
activity: RPCActivity(
|
||||
assets: RPCAssets(
|
||||
@@ -77,28 +118,27 @@ class DiscordRPC {
|
||||
}
|
||||
|
||||
void setWatchingAnimeActivity(
|
||||
AnimeModel animeModel, int episode, MediaContentModel mediaContentModel) {
|
||||
if (!discordFound) return;
|
||||
AnimeModel animeModel,
|
||||
int episode,
|
||||
MediaContentModel mediaContentModel,
|
||||
) {
|
||||
if (!discordConnected) return;
|
||||
FlutterDiscordRPC.instance.setActivity(
|
||||
activity: RPCActivity(
|
||||
assets: RPCAssets(
|
||||
largeImage: mediaContentModel.imageUrls != null &&
|
||||
mediaContentModel.imageUrls!.length > episode
|
||||
mediaContentModel.imageUrls!.length >= episode
|
||||
? mediaContentModel.imageUrls![episode - 1]
|
||||
: animeModel.coverImage,
|
||||
largeText:
|
||||
"Watching ${animeModel.userPreferedTitle}, Episode $episode",
|
||||
largeText: "Watching ${animeModel.userPreferedTitle}, Episode $episode",
|
||||
smallImage: "https://i.imgur.com/tF7Hv84.png",
|
||||
smallText: "Unyo",
|
||||
),
|
||||
state: mediaContentModel.titles != null &&
|
||||
mediaContentModel.titles!.length > episode
|
||||
mediaContentModel.titles!.length >= episode
|
||||
? "Episode $episode, ${mediaContentModel.titles![episode - 1]}"
|
||||
: "Watching ${animeModel.userPreferedTitle}",
|
||||
details: mediaContentModel.titles != null &&
|
||||
mediaContentModel.titles!.length > episode
|
||||
? "Watching ${animeModel.userPreferedTitle}"
|
||||
: "Episode $episode",
|
||||
details: "Watching ${animeModel.userPreferedTitle}",
|
||||
timestamps: RPCTimestamps(
|
||||
start: initTime.millisecondsSinceEpoch,
|
||||
end: null,
|
||||
@@ -108,13 +148,12 @@ class DiscordRPC {
|
||||
}
|
||||
|
||||
void setReadingMangaActivity(MangaModel mangaModel, int chapter) {
|
||||
if (!discordFound) return;
|
||||
if (!discordConnected) return;
|
||||
FlutterDiscordRPC.instance.setActivity(
|
||||
activity: RPCActivity(
|
||||
assets: RPCAssets(
|
||||
largeImage: mangaModel.coverImage,
|
||||
largeText:
|
||||
"Reading ${mangaModel.userPreferedTitle}, Chapter $chapter",
|
||||
largeText: "Reading ${mangaModel.userPreferedTitle}, Chapter $chapter",
|
||||
smallImage: "https://i.imgur.com/tF7Hv84.png",
|
||||
smallText: "Unyo",
|
||||
),
|
||||
|
||||
@@ -77,7 +77,7 @@ class _AnimeWidgetListState extends State<AnimeWidgetList> {
|
||||
}
|
||||
|
||||
void openAnime(AnimeModel currentAnime, String tag) {
|
||||
discordRPC.setNavigatingAnimeActivity(currentAnime);
|
||||
discord.setNavigatingAnimeActivity(currentAnime);
|
||||
animeScreen = AnimeDetailsScreen(
|
||||
currentAnime: currentAnime,
|
||||
tag: tag,
|
||||
|
||||
@@ -39,6 +39,8 @@ class _SearchingAnimeMenuState extends State<SearchingAnimeMenu> {
|
||||
String currentFormat = "Select Format";
|
||||
String currentSeason = "Select Season";
|
||||
String currentYear = "Select Year";
|
||||
String currentStatus = "Select Status";
|
||||
String currentGenre = "Select Genre";
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -64,7 +66,7 @@ class _SearchingAnimeMenuState extends State<SearchingAnimeMenu> {
|
||||
searchTimer.cancel();
|
||||
searchTimer = Timer(const Duration(milliseconds: 500), () async {
|
||||
List<AnimeModel> newSearchMediaList = await getAnimeModelListSearch(
|
||||
text, currentSortBy, currentSeason, currentFormat, currentYear, 10);
|
||||
text, currentGenre, currentSortBy, currentSeason, currentStatus, currentFormat, currentYear,10);
|
||||
setState(() {
|
||||
listAnimeModels = newSearchMediaList;
|
||||
listEntries = [
|
||||
|
||||
@@ -195,7 +195,7 @@ class MangaWidget extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
color: veryLightBorderColor.withOpacity(0.8),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
fontSize: format == "TV_SHORT" ? 10 : 14,
|
||||
fontSize: format == "ONE_SHOT" ? 10 : 12,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
|
||||
@@ -77,7 +77,7 @@ class _MangaWidgetListState extends State<MangaWidgetList> {
|
||||
}
|
||||
|
||||
void openMangaDetails(MangaModel currentManga, String tag) {
|
||||
discordRPC.setNavigatingMangaActivity(currentManga);
|
||||
discord.setNavigatingMangaActivity(currentManga);
|
||||
mangaScreen = MangaDetailsScreen(
|
||||
currentManga: currentManga,
|
||||
tag: tag,
|
||||
|
||||
@@ -39,7 +39,9 @@ class _SearchingMangaMenuState extends State<SearchingMangaMenu> {
|
||||
String currentSortBy = "Select Sorting";
|
||||
String currentFormat = "Select Format";
|
||||
String currentSeason = "Select Season";
|
||||
String currentYear = "Select Year";
|
||||
String currentCountry = "Select Country";
|
||||
String currentStatus = "Select Status";
|
||||
String currentGenre = "Select Genre";
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -70,7 +72,7 @@ class _SearchingMangaMenuState extends State<SearchingMangaMenu> {
|
||||
searchTimer.cancel();
|
||||
searchTimer = Timer(const Duration(milliseconds: 500), () async {
|
||||
List<MangaModel> newSearchMediaList = await getMangaModelListSearch(
|
||||
text, currentSortBy, currentSeason, currentFormat, currentYear, 10);
|
||||
text, currentSortBy, currentFormat, currentStatus, currentCountry, currentGenre, 10);
|
||||
setState(() {
|
||||
listMangaModels = newSearchMediaList;
|
||||
listEntries = [
|
||||
|
||||
@@ -123,6 +123,19 @@ class VideoPlayerValue {
|
||||
final mdk.Player player;
|
||||
final VideoCaptionFile caption;
|
||||
|
||||
VideoPlayerValue({required this.player, required this.caption});
|
||||
|
||||
/// Preserve native video ratio from the first video track.
|
||||
double aspectRatio() {
|
||||
final streams = player.mediaInfo.video;
|
||||
if (streams != null && streams.isNotEmpty) {
|
||||
final codec = streams.first.codec;
|
||||
// codec.width and codec.height are ints
|
||||
return codec.width / codec.height;
|
||||
}
|
||||
return 16 / 9;
|
||||
}
|
||||
|
||||
Duration get position => Duration(milliseconds: player.position);
|
||||
|
||||
Duration get duration => Duration(milliseconds: player.mediaInfo.duration);
|
||||
@@ -131,8 +144,6 @@ class VideoPlayerValue {
|
||||
|
||||
bool get isPlaying => player.state == mdk.PlaybackState.playing;
|
||||
|
||||
VideoPlayerValue({required this.player, required this.caption});
|
||||
|
||||
void setNewCaptionFile(ClosedCaptionFile? newClosedCaptionFile) {
|
||||
caption.setNewCaptionFile(newClosedCaptionFile);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user