Merge remote-tracking branch 'upstream/main'

This commit is contained in:
NBA2K1 2025-08-18 18:00:46 +02:00
commit 00e49e31a0
58 changed files with 5379 additions and 299 deletions

View file

@ -3,7 +3,7 @@ import 'package:html/dom.dart';
import 'package:mangayomi/eval/model/document.dart';
class MDocumentBridge {
final documentBridgedClass = BridgedClassDefinition(
final documentBridgedClass = BridgedClass(
nativeType: MDocument,
name: 'MDocument',
constructors: {

View file

@ -3,7 +3,7 @@ import 'package:html/dom.dart';
import 'package:mangayomi/eval/model/element.dart';
class MElementBridge {
final elementBridgedClass = BridgedClassDefinition(
final elementBridgedClass = BridgedClass(
nativeType: MElement,
name: 'MElement',
constructors: {

View file

@ -2,7 +2,7 @@ import 'package:d4rt/d4rt.dart';
import 'package:mangayomi/eval/model/filter.dart';
class FilterBridge {
final filterBridgedClass = BridgedClassDefinition(
final filterBridgedClass = BridgedClass(
nativeType: FilterList,
name: 'FilterList',
constructors: {
@ -19,7 +19,7 @@ class FilterBridge {
(target as FilterList).filters = value as List,
},
);
final selectFilterBridgedClass = BridgedClassDefinition(
final selectFilterBridgedClass = BridgedClass(
nativeType: SelectFilter,
name: 'SelectFilter',
constructors: {
@ -53,7 +53,7 @@ class FilterBridge {
(target as SelectFilter).typeName = value as String?,
},
);
final selectFilterOptionBridgedClass = BridgedClassDefinition(
final selectFilterOptionBridgedClass = BridgedClass(
nativeType: SelectFilterOption,
name: 'SelectFilterOption',
constructors: {
@ -80,7 +80,7 @@ class FilterBridge {
},
);
final separatorFilterBridgedClass = BridgedClassDefinition(
final separatorFilterBridgedClass = BridgedClass(
nativeType: SeparatorFilter,
name: 'SeparatorFilter',
constructors: {
@ -103,7 +103,7 @@ class FilterBridge {
},
);
final headerFilterBridgedClass = BridgedClassDefinition(
final headerFilterBridgedClass = BridgedClass(
nativeType: HeaderFilter,
name: 'HeaderFilter',
constructors: {
@ -130,7 +130,7 @@ class FilterBridge {
},
);
final textFilterBridgedClass = BridgedClassDefinition(
final textFilterBridgedClass = BridgedClass(
nativeType: TextFilter,
name: 'TextFilter',
constructors: {
@ -161,7 +161,7 @@ class FilterBridge {
},
);
final sortFilterBridgedClass = BridgedClassDefinition(
final sortFilterBridgedClass = BridgedClass(
nativeType: SortFilter,
name: 'SortFilter',
constructors: {
@ -193,7 +193,7 @@ class FilterBridge {
(target as SortFilter).values = value as List,
},
);
final sortStateBridgedClass = BridgedClassDefinition(
final sortStateBridgedClass = BridgedClass(
nativeType: SortState,
name: 'SortState',
constructors: {
@ -220,7 +220,7 @@ class FilterBridge {
},
);
final triStateFilterBridgedClass = BridgedClassDefinition(
final triStateFilterBridgedClass = BridgedClass(
nativeType: TriStateFilter,
name: 'TriStateFilter',
constructors: {
@ -253,7 +253,7 @@ class FilterBridge {
},
);
final groupFilterBridgedClass = BridgedClassDefinition(
final groupFilterBridgedClass = BridgedClass(
nativeType: GroupFilter,
name: 'GroupFilter',
constructors: {
@ -284,7 +284,7 @@ class FilterBridge {
},
);
final checkBoxFilterBridgedClass = BridgedClassDefinition(
final checkBoxFilterBridgedClass = BridgedClass(
nativeType: CheckBoxFilter,
name: 'CheckBoxFilter',
constructors: {

View file

@ -5,7 +5,7 @@ import 'package:mangayomi/eval/model/m_source.dart';
import 'package:mangayomi/services/http/m_client.dart';
class HttpBridge {
final clientBridgedClass = BridgedClassDefinition(
final clientBridgedClass = BridgedClass(
nativeType: InterceptedClient,
name: 'Client',
constructors: {
@ -76,7 +76,7 @@ class HttpBridge {
(target as Client).send(positionalArgs[0] as BaseRequest),
},
);
final baseRequestBridgedClass = BridgedClassDefinition(
final baseRequestBridgedClass = BridgedClass(
nativeType: BaseRequest,
name: 'BaseRequest',
nativeNames: ['Request'],
@ -99,7 +99,7 @@ class HttpBridge {
'finalized': (visitor, target) => (target as BaseRequest).finalized,
},
);
final responseBridgedClass = BridgedClassDefinition(
final responseBridgedClass = BridgedClass(
nativeType: Response,
name: 'Response',
constructors: {
@ -120,7 +120,7 @@ class HttpBridge {
'request': (visitor, target) => (target as Response).request,
},
);
final streamedResponseBridgedClass = BridgedClassDefinition(
final streamedResponseBridgedClass = BridgedClass(
nativeType: StreamedResponse,
name: 'StreamedResponse',
constructors: {
@ -144,7 +144,7 @@ class HttpBridge {
'request': (visitor, target) => (target as StreamedResponse).request,
},
);
final byteStreamBridgedClass = BridgedClassDefinition(
final byteStreamBridgedClass = BridgedClass(
nativeType: ByteStream,
name: 'ByteStream',
constructors: {

View file

@ -2,7 +2,7 @@ import 'package:d4rt/d4rt.dart';
import 'package:mangayomi/eval/model/m_chapter.dart';
class MChapterBridge {
final mChapterBridgedClass = BridgedClassDefinition(
final mChapterBridgedClass = BridgedClass(
nativeType: MChapter,
name: 'MChapter',
constructors: {

View file

@ -3,7 +3,7 @@ import 'package:mangayomi/eval/model/m_manga.dart';
import 'package:mangayomi/models/manga.dart';
class MMangaBridge {
final mMangaBridgedClass = BridgedClassDefinition(
final mMangaBridgedClass = BridgedClass(
nativeType: MManga,
name: 'MManga',
constructors: {

View file

@ -3,7 +3,7 @@ import 'package:mangayomi/eval/model/m_manga.dart';
import 'package:mangayomi/eval/model/m_pages.dart';
class MPagesBridge {
final mPageBridgedClass = BridgedClassDefinition(
final mPageBridgedClass = BridgedClass(
nativeType: MPages,
name: 'MPages',
constructors: {

View file

@ -7,7 +7,7 @@ import 'package:mangayomi/modules/browse/extension/providers/extension_preferenc
import 'package:mangayomi/utils/extensions/string_extensions.dart';
class MProviderBridged {
final mProviderBridged = BridgedClassDefinition(
final mProviderBridged = BridgedClass(
nativeType: MProvider,
name: 'MProvider',
constructors: {

View file

@ -2,7 +2,7 @@ import 'package:d4rt/d4rt.dart';
import 'package:mangayomi/eval/model/m_source.dart';
class MSourceBridge {
final mSourceBridgedClass = BridgedClassDefinition(
final mSourceBridgedClass = BridgedClass(
nativeType: MSource,
name: 'MSource',
constructors: {

View file

@ -2,7 +2,7 @@ import 'package:d4rt/d4rt.dart';
import 'package:mangayomi/models/video.dart';
class MTrackBridge {
final mTrackBridgedClass = BridgedClassDefinition(
final mTrackBridgedClass = BridgedClass(
nativeType: Track,
name: 'MTrack',
constructors: {

View file

@ -2,7 +2,7 @@ import 'package:d4rt/d4rt.dart';
import 'package:mangayomi/models/video.dart';
class MVideoBridge {
final mVideoBridgedClass = BridgedClassDefinition(
final mVideoBridgedClass = BridgedClass(
nativeType: Video,
name: 'MVideo',
constructors: {

View file

@ -3,7 +3,7 @@ import 'package:mangayomi/eval/model/source_preference.dart';
// EditTextPreference
class SourcePreferenceBridge {
final checkBoxPreferenceBridgedClass = BridgedClassDefinition(
final checkBoxPreferenceBridgedClass = BridgedClass(
nativeType: CheckBoxPreference,
name: 'CheckBoxPreference',
constructors: {
@ -28,7 +28,7 @@ class SourcePreferenceBridge {
(target as SourcePreference).checkBoxPreference?.value,
},
);
final switchPreferenceCompatBridgedClass = BridgedClassDefinition(
final switchPreferenceCompatBridgedClass = BridgedClass(
nativeType: SwitchPreferenceCompat,
name: 'SwitchPreferenceCompat',
constructors: {
@ -53,7 +53,7 @@ class SourcePreferenceBridge {
(target as SourcePreference).switchPreferenceCompat?.value,
},
);
final listPreferenceBridgedClass = BridgedClassDefinition(
final listPreferenceBridgedClass = BridgedClass(
nativeType: ListPreference,
name: 'ListPreference',
constructors: {
@ -85,7 +85,7 @@ class SourcePreferenceBridge {
},
);
final multiSelectListPreferenceBridgedClass = BridgedClassDefinition(
final multiSelectListPreferenceBridgedClass = BridgedClass(
nativeType: MultiSelectListPreference,
name: 'MultiSelectListPreference',
constructors: {
@ -116,7 +116,7 @@ class SourcePreferenceBridge {
(target as SourcePreference).multiSelectListPreference?.entryValues,
},
);
final editTextPreferenceBridgedClass = BridgedClassDefinition(
final editTextPreferenceBridgedClass = BridgedClass(
nativeType: EditTextPreference,
name: 'EditTextPreference',
constructors: {

279
lib/l10n/app_as.arb Normal file
View file

@ -0,0 +1,279 @@
{
"@@locale": "as",
"library": "পুথিভঁৰাল",
"updates": "আপডেট",
"history": "ইতিহাস",
"browse": "ব্ৰাউজ",
"more": "অধিক",
"open_random_entry": "যিকোনো এণ্ট্ৰি খোলক",
"import": "আমদানি",
"filter": "ফিল্টাৰ",
"downloaded": "ডাউনলোড কৰা",
"unread": "নপঢ়া",
"started": "আৰম্ভ কৰা",
"bookmarked": "বুকমাৰ্ক কৰা",
"sort": "শাৰী কৰক",
"alphabetically": "বৰ্ণানুক্ৰমে",
"last_read": "শেষত পঢ়া",
"last_update_check": "শেষ আপডেট পৰীক্ষা",
"unread_count": "নপঢ়াৰ সংখ্যা",
"latest_chapter": "শেষৰ অধ্যায়",
"date_added": "তাৰিখ যোগ কৰা",
"display": "প্ৰদৰ্শন",
"display_mode": "প্ৰদৰ্শন মোড",
"compact_grid": "সংক্ষিপ্ত গ্ৰিড",
"comfortable_grid": "আৰামদায়ক গ্ৰিড",
"cover_only_grid": "মাত্ৰ কভাৰৰ গ্ৰিড",
"list": "তালিকা",
"badges": "বেজ",
"downloaded_chapters": "ডাউনলোড কৰা অধ্যায়",
"language": "ভাষা",
"local_source": "স্থানীয় উৎস",
"tabs": "টেব",
"show_category_tabs": "শ্ৰেণীৰ টেব দেখুৱাওক",
"show_numbers_of_items": "বস্তুৰ সংখ্যা দেখুৱাওক",
"other": "অন্যান্য",
"show_continue_reading_buttons": "পঢ়া অব্যাহত ৰখাৰ বুটাম দেখুৱাওক",
"empty_library": "খালী পুথিভঁৰাল",
"search": "সন্ধান...",
"no_recent_updates": "শেহতীয়া আপডেট নাই",
"remove_everything": "সকলো আঁতৰাওক",
"remove_everything_msg": "আপুনি নিশ্চিত নে? সকলো ইতিহাস হেৰাই যাব",
"ok": "ঠিক আছে",
"cancel": "বাতিল",
"remove": "আঁতৰাওক",
"remove_history_msg": "ইয়ে এই অধ্যায়ৰ পঢ়া তাৰিখ আঁতৰাব। আপুনি নিশ্চিত নে?",
"last_used": "শেষবাৰ ব্যৱহৃত",
"pinned": "পিন কৰা",
"sources": "উৎস",
"install": "ইনষ্টল",
"update": "আপডেট",
"latest": "শেষৰ",
"extensions": "এক্সটেনশন",
"migrate": "স্থানান্তৰ",
"incognito_mode": "গোপন মোড",
"incognito_mode_description": "পঢ়াৰ ইতিহাস স্থগিত কৰে",
"download_queue": "ডাউনলোড শাৰী",
"categories": "শ্ৰেণী",
"settings": "ছেটিং",
"about": "বিষয়ে",
"help": "সহায়",
"no_downloads": "কোনো ডাউনলোড নাই",
"edit_categories": "শ্ৰেণী সম্পাদনা",
"edit_categories_description": "আপোনাৰ কোনো শ্ৰেণী নাই। পুথিভঁৰাল সংগঠিত কৰিবলৈ প্লাছ বুটামত টেপ কৰক",
"add": "যোগ কৰক",
"add_category": "শ্ৰেণী যোগ কৰক",
"name": "নাম",
"category_name_required": "*প্ৰয়োজনীয়",
"add_category_error_exist": "এই নামৰ শ্ৰেণী ইতিমধ্যে আছে!",
"delete_category": "শ্ৰেণী মচক",
"delete_category_msg": "আপুনি {name} শ্ৰেণী মচিব বিচাৰে নে?",
"rename_category": "শ্ৰেণীৰ নাম সলনি কৰক",
"general": "সাধাৰণ",
"general_subtitle": "এপৰ ভাষা",
"app_language": "এপৰ ভাষা",
"appearance": "চেহেৰা",
"appearance_subtitle": "থীম, তাৰিখ আৰু সময়ৰ ফৰ্মেট",
"theme": "থীম",
"dark_mode": "ডাৰ্ক মোড",
"on": "অন",
"off": "অফ",
"pure_black_dark_mode": "পিউৰ ব্লেক ডাৰ্ক মোড",
"timestamp": "টাইমষ্টেম্প",
"relative_timestamp": "আপেক্ষিক টাইমষ্টেম্প",
"relative_timestamp_short": "চমু (আজি, কালি)",
"relative_timestamp_long": "দীঘল (চমু+, n দিনৰ আগতে)",
"date_format": "তাৰিখৰ ফৰ্মেট",
"reader": "পাঠক",
"refresh": "ৰিফ্ৰেছ",
"reader_subtitle": "পঢ়াৰ মোড, প্ৰদৰ্শন, নেভিগেশন",
"default_reading_mode": "ডিফল্ট পঢ়াৰ মোড",
"reading_mode_vertical": "উলম্ব",
"reading_mode_horizontal": "অনুভূমিক",
"reading_mode_left_to_right": "বাওঁফালৰ পৰা সোঁফাললৈ",
"reading_mode_right_to_left": "সোঁফালৰ পৰা বাওঁফাললৈ",
"reading_mode_vertical_continuous": "উলম্ব অবিৰত",
"reading_mode_webtoon": "ৱেবটুন",
"double_tap_animation_speed": "ডাবল টেপ এনিমেশনৰ গতি",
"normal": "সাধাৰণ",
"fast": "দ্ৰুত",
"no_animation": "কোনো এনিমেশন নাই",
"animate_page_transitions": "পৃষ্ঠা স্থানান্তৰ এনিমেট কৰক",
"crop_borders": "সীমা কাটক",
"downloads": "ডাউনলোড",
"downloads_subtitle": "ডাউনলোড ছেটিং",
"download_location": "ডাউনলোডৰ স্থান",
"custom_location": "কাষ্টম স্থান",
"only_on_wifi": "কেৱল ৱাই-ফাইত",
"save_as_cbz_archive": "CBZ আৰ্কাইভ হিচাপে সাঁচক",
"browse_subtitle": "উৎস, গ্ল’বেল সন্ধান",
"only_include_pinned_sources": "কেৱল পিন কৰা উৎস অন্তৰ্ভুক্ত কৰক",
"nsfw_sources": "NSFW (+18) উৎস",
"nsfw_sources_show": "উৎস আৰু এক্সটেনশন তালিকাত দেখুৱাওক",
"nsfw_sources_info": "ইয়ে অফিচিয়েল নহোৱা বা সম্ভৱতঃ ভুলকৈ ফ্লেগ কৰা এক্সটেনশনৰ পৰা NSFW (18+) বিষয়বস্তু এপত দেখা দিয়াৰ পৰা ৰক্ষা নকৰে",
"version": "সংস্কৰণ",
"check_for_update": "আপডেটৰ বাবে পৰীক্ষা কৰক",
"n_days_ago": "{days} দিনৰ আগতে",
"today": "আজি",
"yesterday": "কালি",
"a_week_ago": "এসপ্তাহৰ আগতে",
"add_to_library": "পুথিভঁৰালত যোগ কৰক",
"completed": "সম্পূৰ্ণ",
"ongoing": "চলি আছে",
"on_hiatus": "বিৰতিত",
"canceled": "বাতিল কৰা",
"publishing_finished": "প্ৰকাশ সমাপ্ত",
"unknown": "অজ্ঞাত",
"set_categories": "শ্ৰেণী নিৰ্ধাৰণ কৰক",
"edit": "সম্পাদনা",
"in_library": "পুথিভঁৰালত",
"filter_scanlator_groups": "স্কেনলেটৰ গ্ৰুপ ফিল্টাৰ কৰক",
"reset": "ৰিছেট",
"by_source": "উৎস অনুসৰি",
"by_chapter_number": "অধ্যায়ৰ সংখ্যা অনুসৰি",
"by_upload_date": "আপলোডৰ তাৰিখ অনুসৰি",
"source_title": "উৎসৰ শিৰোনাম",
"chapter_number": "অধ্যায়ৰ সংখ্যা",
"share": "শ্বেয়াৰ",
"n_chapters": "{number} অধ্যায়",
"no_description": "কোনো বিৱৰণ নাই",
"resume": "পুনৰ আৰম্ভ",
"read": "পঢ়ক",
"popular": "জনপ্ৰিয়",
"open_in_browser": "ব্ৰাউজাৰত খোলক",
"clear_cookie": "কুকী আঁতৰাওক",
"show_page_number": "পৃষ্ঠাৰ সংখ্যা দেখুৱাওক",
"from_library": "পুথিভঁৰালৰ পৰা",
"downloaded_chapter": "ডাউনলোড কৰা অধ্যায়",
"page": "পৃষ্ঠা {page}",
"global_search": "গ্ল’বেল সন্ধান",
"color_blend_level": "ৰংৰ মিশ্ৰণ স্তৰ",
"current": "বৰ্তমান {char}",
"finished": "শেষ {char}",
"next": "পৰৱৰ্তী {char}",
"previous": "পূৰ্বৰ {char}",
"no_more_chapter": "আৰু কোনো অধ্যায় নাই",
"no_result": "কোনো ফলাফল নাই",
"send": "পঠিয়াওক",
"delete": "মচক",
"start_downloading": "এতিয়া ডাউনলোড আৰম্ভ কৰক",
"retry": "পুনৰ চেষ্টা কৰক",
"add_chapters": "অধ্যায় যোগ কৰক",
"delete_chapters": "অধ্যায় মচক?",
"default0": "ডিফল্ট",
"total_chapters": "মুঠ অধ্যায়",
"import_local_file": "স্থানীয় ফাইল আমদানি কৰক",
"import_files": "ফাইল",
"nothing_read_recently": "শেহতীয়াকৈ একো পঢ়া নাই",
"status": "স্থিতি",
"not_started": "আৰম্ভ হোৱা নাই",
"score": "স্ক’ৰ",
"start_date": "আৰম্ভৰ তাৰিখ",
"finish_date": "শেষৰ তাৰিখ",
"reading": "পঢ়ি আছে",
"on_hold": "ৰখা আছে",
"dropped": "বাদ দিয়া",
"plan_to_read": "পঢ়াৰ পৰিকল্পনা",
"re_reading": "পুনৰ পঢ়ি আছে",
"chapters": "অধ্যায়",
"add_tracker": "ট্ৰেকাৰ যোগ কৰক",
"one_tracker": "১ টা ট্ৰেকাৰ",
"n_tracker": "{n} টা ট্ৰেকাৰ",
"tracking": "ট্ৰেকিং",
"description": "বিৱৰণ",
"episode_progress": "প্ৰগতি: {n}",
"n_episodes": "{n} খণ্ড",
"manga_sources": "মাংগা উৎস",
"anime_sources": "এনিমে উৎস",
"anime_extensions": "এনিমে এক্সটেনশন",
"manga_extensions": "মাংগা এক্সটেনশন",
"anime": "এনিমে",
"manga": "মাংগা",
"library_no_category_exist": "আপোনাৰ এতিয়াও কোনো শ্ৰেণী নাই",
"watching": "চাই আছে",
"plan_to_watch": "চোৱাৰ পৰিকল্পনা",
"re_watching": "পুনৰ চাই আছে",
"episodes": "খণ্ড",
"download": "ডাউনলোড",
"new_update_available": "নতুন আপডেট উপলব্ধ",
"app_version": "এপৰ সংস্কৰণ: v{v}",
"searching_for_updates": "আপডেটৰ বাবে সন্ধান কৰি আছে...",
"no_new_updates_available": "কোনো নতুন আপডেট উপলব্ধ নাই",
"uninstall": "আনইনষ্টল",
"uninstall_extension": "{ext} এক্সটেনশন আনইনষ্টল কৰিব নে?",
"langauage": "ভাষা",
"extension_detail": "এক্সটেনশনৰ বিৱৰণ",
"scale_type": "স্কেলৰ ধৰণ",
"scale_type_fit_screen": "স্ক্ৰীণৰ সৈতে মিলাওক",
"scale_type_stretch": "প্ৰসাৰিত",
"scale_type_fit_width": "প্ৰস্থৰ সৈতে মিলাওক",
"scale_type_fit_height": "উচ্চতাৰ সৈতে মিলাওক",
"scale_type_original_size": "মূল আকাৰ",
"scale_type_smart_fit": "স্মাৰ্ট ফিট",
"page_preload_amount": "পৃষ্ঠা প্ৰিলোডৰ পৰিমাণ",
"page_preload_amount_subtitle": "পঢ়াৰ সময়ত প্ৰিলোড কৰিবলগীয়া পৃষ্ঠাৰ পৰিমাণ। অধিক মানে মসৃণ পঢ়াৰ অভিজ্ঞতা, কিন্তু কেশ্ব আৰু নেটৱৰ্কৰ ব্যৱহাৰ বেছি হ’ব।",
"image_loading_error": "এই ছবি লোড কৰিব পৰা নগ’ল",
"add_episodes": "খণ্ড যোগ কৰক",
"video_quality": "গুণ",
"video_subtitle": "উপশিৰোনাম",
"check_for_extension_updates": "এক্সটেনশন আপডেটৰ বাবে পৰীক্ষা কৰক",
"auto_extensions_updates": "স্বয়ংক্ৰিয় এক্সটেনশন আপডেট",
"auto_extensions_updates_subtitle": "নতুন সংস্কৰণ উপলব্ধ হ’লে এক্সটেনশন স্বয়ংক্ৰিয়ভাৱে আপডেট কৰিব।",
"reading_mode": "পঢ়াৰ মোড",
"custom_filter": "কাষ্টম ফিল্টাৰ",
"background_color": "পটভূমিৰ ৰং",
"white": "বগা",
"black": "ক’লা",
"grey": "ধূসৰ",
"automaic": "স্বয়ংক্ৰিয়",
"preferred_domain": "পছন্দৰ ড’মেইন",
"load_more": "অধিক লোড কৰক",
"cancel_all_for_this_series": "এই শৃংখলাৰ সকলো বাতিল কৰক",
"login": "লগইন",
"login_into": "{tracker} ত লগইন কৰক",
"email_adress": "ইমেইল ঠিকনা",
"password": "পাছৱৰ্ড",
"log_out_from": "{tracker} ৰ পৰা লগ আউট কৰিব নে?",
"log_out": "লগ আউট",
"update_pending": "আপডেট বাকী আছে",
"update_all": "সকলো আপডেট কৰক",
"backup_and_restore": "বেকআপ আৰু পুনৰুদ্ধাৰ",
"create_backup": "বেকআপ সৃষ্টি কৰক",
"create_backup_dialog_title": "আপুনি কি বেকআপ কৰিব বিচাৰে?",
"create_backup_subtitle": "বৰ্তমানৰ পুথিভঁৰাল পুনৰুদ্ধাৰৰ বাবে ব্যৱহাৰ কৰিব পাৰি",
"restore_backup": "বেকআপ পুনৰুদ্ধাৰ",
"restore_backup_subtitle": "বেকআপ ফাইলৰ পৰা পুথিভঁৰাল পুনৰুদ্ধাৰ কৰক",
"automatic_backups": "স্বয়ংক্ৰিয় বেকআপ",
"backup_frequency": "বেকআপৰ কম্পাঙ্ক",
"backup_location": "বেকআপৰ স্থান",
"backup_options": "বেকআপৰ বিকল্প",
"backup_options_dialog_title": "আপুনি কি বেকআপ কৰিব বিচাৰে?",
"backup_options_subtitle": "বেকআপ ফাইলত কি তথ্য অন্তৰ্ভুক্ত কৰিব",
"backup_and_restore_warning_info": "আপুনি বেকআপৰ কপি অন্য ঠাইতো ৰাখিব লাগে",
"library_entries": "পুথিভঁৰালৰ এণ্ট্ৰি",
"chapters_and_episode": "অধ্যায় আৰু খণ্ড",
"every_6_hours": "প্ৰতি ৬ ঘণ্টা",
"every_12_hours": "প্ৰতি ১২ ঘণ্টা",
"daily": "দৈনিক",
"every_2_days": "প্ৰতি ২ দিন",
"weekly": "সাপ্তাহিক",
"restore_backup_warning_title": "বেকআপ পুনৰুদ্ধাৰে সকলো বিদ্যমান তথ্য ওভাৰৰাইট কৰিব।\n\nপুনৰুদ্ধাৰ অব্যাহত ৰাখিব নে?",
"services": "সেৱা",
"tracking_warning_info": "ট্ৰেকিং সেৱাত অধ্যায়ৰ প্ৰগতি আপডেট কৰিবলৈ একমুখী ছিংক। পৃথক এণ্ট্ৰিৰ বাবে ট্ৰেকিং তেওঁলোকৰ ট্ৰেকিং বুটামৰ পৰা ছেট আপ কৰক।",
"use_page_tap_zones": "পৃষ্ঠা টেপ জ’ন ব্যৱহাৰ কৰক",
"manage_trackers": "ট্ৰেকাৰ পৰিচালনা কৰক",
"restore": "পুনৰুদ্ধাৰ",
"backups": "বেকআপ",
"by_scanlator": "স্কেনলেটৰ অনুসৰি",
"by_name": "নাম অনুসৰি",
"installed": "ইনষ্টল কৰা",
"auto_scroll": "স্বয়ংক্ৰিয় স্ক্ৰ’ল",
"video_audio": "অডিঅ’",
"player": "প্লেয়াৰ",
"markEpisodeAsSeenSetting": "খণ্ডটো কেতিয়া দেখা বুলি চিহ্নিত কৰিব",
"default_skip_intro_length": "ডিফল্ট ইনট্ৰ’ এৰি দিয়াৰ দৈৰ্ঘ্য",
"default_playback_speed_length": "ডিফল্ট প্লেবেক গতিৰ দৈৰ্ঘ্য",
"updateProgressAfterReading": "পঢ়াৰ পিছত প্ৰগতি আপডেট কৰক",
"no_sources_installed": "কোনো উৎস ইনষ্টল কৰা নাই!",
"show_extensions": "এক্সটেনশন দেখুৱাওক"
}

View file

@ -77,6 +77,8 @@
"clean_database_desc": "This will remove all items that are not added to the library!",
"incognito_mode": "Incognito Mode",
"incognito_mode_description": "Pauses reading history",
"downloaded_only": "Downloaded only",
"downloaded_only_description": "Only show downloaded entries in your library",
"download_queue": "Download Queue",
"categories": "Categories",
"statistics": "Statistics",
@ -225,6 +227,10 @@
"sync_finished": "Sync finished",
"sync_failed": "Sync failed",
"sync_button_sync": "Sync progress",
"sync_button_upload": "Upload only",
"sync_button_upload_info": "This operation will fully replace the remote data with local data!",
"sync_button_download": "Download only",
"sync_button_download_info": "This operation will fully replace the local data with remote data!",
"sync_on": "Enable sync",
"sync_auto": "Auto Sync",
"sync_auto_warning": "Auto Sync is currently an experimental feature!",
@ -251,6 +257,7 @@
"anime_extensions": "Anime Extensions",
"manga_extensions": "Manga Extensions",
"novel_extensions": "Novel Extensions",
"extension_settings": "Extension settings",
"anime": "Anime",
"manga": "Manga",
"novel": "Novel",
@ -426,6 +433,12 @@
"include_sensitive_settings": "Include sensitive settings (e.g., tracker login tokens)",
"create": "Create",
"downloads_are_limited_to_wifi": "Downloads are limited to Wi-Fi only",
"recommendations_similar": "similar",
"recommendations_weights": "Recommendation Weights",
"recommendations_weights_genre": "Genre Similarity",
"recommendations_weights_setting": "Setting Similarity",
"recommendations_weights_synopsis": "Story Similarity",
"recommendations_weights_theme": "Theme Similarity",
"manga_extensions_repo": "Manga extensions repo",
"anime_extensions_repo": "Anime extensions repo",
"novel_extensions_repo": "Novel extensions repo",

279
lib/l10n/app_hi.arb Normal file
View file

@ -0,0 +1,279 @@
{
"@@locale": "hi",
"library": "पुस्तकालय",
"updates": "अपडेट",
"history": "इतिहास",
"browse": "ब्राउज़",
"more": "और",
"open_random_entry": "कोई भी प्रविष्टि खोलें",
"import": "आयात",
"filter": "फ़िल्टर",
"downloaded": "डाउनलोड किया गया",
"unread": "अपठित",
"started": "शुरू किया",
"bookmarked": "बुकमार्क किया",
"sort": "छाँटें",
"alphabetically": "वर्णानुक्रम",
"last_read": "आखिरी बार पढ़ा",
"last_update_check": "आखिरी अपडेट जांच",
"unread_count": "अपठित गिनती",
"latest_chapter": "नवीनतम अध्याय",
"date_added": "जोड़ा गया तारीख",
"display": "प्रदर्शन",
"display_mode": "प्रदर्शन मोड",
"compact_grid": "संक्षिप्त ग्रिड",
"comfortable_grid": "आरामदायक ग्रिड",
"cover_only_grid": "केवल कवर ग्रिड",
"list": "सूची",
"badges": "बैज",
"downloaded_chapters": "डाउनलोड किए गए अध्याय",
"language": "भाषा",
"local_source": "स्थानीय स्रोत",
"tabs": "टैब",
"show_category_tabs": "श्रेणी टैब दिखाएँ",
"show_numbers_of_items": "आइटम की संख्या दिखाएँ",
"other": "अन्य",
"show_continue_reading_buttons": "पढ़ना जारी रखें बटन दिखाएँ",
"empty_library": "खाली पुस्तकालय",
"search": "खोजें...",
"no_recent_updates": "कोई हालिया अपडेट नहीं",
"remove_everything": "सब कुछ हटाएँ",
"remove_everything_msg": "क्या आप निश्चित हैं? सारा इतिहास खो जाएगा",
"ok": "ठीक है",
"cancel": "रद्द करें",
"remove": "हटाएँ",
"remove_history_msg": "यह इस अध्याय की पढ़ने की तारीख को हटा देगा। क्या आप निश्चित हैं?",
"last_used": "आखिरी बार उपयोग",
"pinned": "पिन किया गया",
"sources": "स्रोत",
"install": "स्थापित करें",
"update": "अपडेट करें",
"latest": "नवीनतम",
"extensions": "एक्सटेंशन",
"migrate": "स्थानांतरण",
"incognito_mode": "गुप्त मोड",
"incognito_mode_description": "पढ़ने का इतिहास रोकता है",
"download_queue": "डाउनलोड कतार",
"categories": "श्रेणियाँ",
"settings": "सेटिंग्स",
"about": "के बारे में",
"help": "मदद",
"no_downloads": "कोई डाउनलोड नहीं",
"edit_categories": "श्रेणियाँ संपादित करें",
"edit_categories_description": "आपके पास कोई श्रेणी नहीं है। अपनी लाइब्रेरी को व्यवस्थित करने के लिए प्लस बटन दबाएँ",
"add": "जोड़ें",
"add_category": "श्रेणी जोड़ें",
"name": "नाम",
"category_name_required": "*आवश्यक",
"add_category_error_exist": "इस नाम की श्रेणी पहले से मौजूद है!",
"delete_category": "श्रेणी हटाएँ",
"delete_category_msg": "क्या आप श्रेणी {name} को हटाना चाहते हैं?",
"rename_category": "श्रेणी का नाम बदलें",
"general": "सामान्य",
"general_subtitle": "ऐप की भाषा",
"app_language": "ऐप की भाषा",
"appearance": "दिखावट",
"appearance_subtitle": "थीम, तारीख और समय प्रारूप",
"theme": "थीम",
"dark_mode": "डार्क मोड",
"on": "चालू",
"off": "बंद",
"pure_black_dark_mode": "पूर्ण काला डार्क मोड",
"timestamp": "समय चिह्न",
"relative_timestamp": "सापेक्ष समय चिह्न",
"relative_timestamp_short": "संक्षिप्त (आज, कल)",
"relative_timestamp_long": "लंबा (संक्षिप्त+, n दिन पहले)",
"date_format": "तारीख प्रारूप",
"reader": "पढ़ने वाला",
"refresh": "ताज़ा करें",
"reader_subtitle": "पढ़ने का मोड, प्रदर्शन, नेविगेशन",
"default_reading_mode": "डिफ़ॉल्ट पढ़ने का मोड",
"reading_mode_vertical": "लंबवत",
"reading_mode_horizontal": "क्षैतिज",
"reading_mode_left_to_right": "बाएँ से दाएँ",
"reading_mode_right_to_left": "दाएँ से बाएँ",
"reading_mode_vertical_continuous": "लंबवत निरंतर",
"reading_mode_webtoon": "वेबटून",
"double_tap_animation_speed": "डबल टैप एनिमेशन गति",
"normal": "सामान्य",
"fast": "तेज़",
"no_animation": "कोई एनिमेशन नहीं",
"animate_page_transitions": "पेज ट्रांज़िशन को एनिमेट करें",
"crop_borders": "किनारों को काटें",
"downloads": "डाउनलोड",
"downloads_subtitle": "डाउनलोड सेटिंग्स",
"download_location": "डाउनलोड स्थान",
"custom_location": "कस्टम स्थान",
"only_on_wifi": "केवल वाईफाई पर",
"save_as_cbz_archive": "सीबीजेड आर्काइव के रूप में सहेजें",
"browse_subtitle": "स्रोत, वैश्विक खोज",
"only_include_pinned_sources": "केवल पिन किए गए स्रोत शामिल करें",
"nsfw_sources": "एनएसएफडब्ल्यू (+18) स्रोत",
"nsfw_sources_show": "स्रोत और एक्सटेंशन सूची में दिखाएँ",
"nsfw_sources_info": "यह अनौपचारिक या संभावित रूप से गलत तरीके से चिह्नित एक्सटेंशन को ऐप के भीतर एनएसएफडब्ल्यू (18+) सामग्री को सामने लाने से नहीं रोकता",
"version": "संस्करण",
"check_for_update": "अपडेट के लिए जांचें",
"n_days_ago": "{days} दिन पहले",
"today": "आज",
"yesterday": "कल",
"a_week_ago": "एक सप्ताह पहले",
"add_to_library": "पुस्तकालय में जोड़ें",
"completed": "पूरा हुआ",
"ongoing": "चल रहा है",
"on_hiatus": "विराम पर",
"canceled": "रद्द",
"publishing_finished": "प्रकाशन समाप्त",
"unknown": "अज्ञात",
"set_categories": "श्रेणियाँ सेट करें",
"edit": "संपादित करें",
"in_library": "पुस्तकालय में",
"filter_scanlator_groups": "स्कैनलेटर समूहों को फ़िल्टर करें",
"reset": "रीसेट",
"by_source": "स्रोत के अनुसार",
"by_chapter_number": "अध्याय संख्या के अनुसार",
"by_upload_date": "अपलोड तारीख के अनुसार",
"source_title": "स्रोत शीर्षक",
"chapter_number": "अध्याय संख्या",
"share": "साझा करें",
"n_chapters": "{number} अध्याय",
"no_description": "कोई विवरण नहीं",
"resume": "जारी रखें",
"read": "पढ़ें",
"popular": "लोकप्रिय",
"open_in_browser": "ब्राउज़र में खोलें",
"clear_cookie": "कुकी साफ़ करें",
"show_page_number": "पेज नंबर दिखाएँ",
"from_library": "पुस्तकालय से",
"downloaded_chapter": "डाउनलोड किया गया अध्याय",
"page": "पेज {page}",
"global_search": "वैश्विक खोज",
"color_blend_level": "रंग मिश्रण स्तर",
"current": "वर्तमान {char}",
"finished": "समाप्त {char}",
"next": "अगला {char}",
"previous": "पिछला {char}",
"no_more_chapter": "कोई और अध्याय नहीं है",
"no_result": "कोई परिणाम नहीं",
"send": "भेजें",
"delete": "हटाएँ",
"start_downloading": "अब डाउनलोड शुरू करें",
"retry": "पुनः प्रयास करें",
"add_chapters": "अध्याय जोड़ें",
"delete_chapters": "अध्याय हटाएँ?",
"default0": "डिफ़ॉल्ट",
"total_chapters": "कुल अध्याय",
"import_local_file": "स्थानीय फ़ाइल आयात करें",
"import_files": "फ़ाइलें",
"nothing_read_recently": "हाल ही में कुछ भी नहीं पढ़ा",
"status": "स्थिति",
"not_started": "शुरू नहीं हुआ",
"score": "स्कोर",
"start_date": "शुरू की तारीख",
"finish_date": "समाप्ति की तारीख",
"reading": "पढ़ रहा है",
"on_hold": "रोक पर",
"dropped": "छोड़ दिया",
"plan_to_read": "पढ़ने की योजना",
"re_reading": "पुनः पढ़ रहा है",
"chapters": "अध्याय",
"add_tracker": "ट्रैकर जोड़ें",
"one_tracker": "1 ट्रैकर",
"n_tracker": "{n} ट्रैकर",
"tracking": "ट्रैकिंग",
"description": "विवरण",
"episode_progress": "प्रगति: {n}",
"n_episodes": "{n} एपिसोड",
"manga_sources": "मंगा स्रोत",
"anime_sources": "एनीमे स्रोत",
"anime_extensions": "एनीमे एक्सटेंशन",
"manga_extensions": "मंगा एक्सटेंशन",
"anime": "एनीमे",
"manga": "मंगा",
"library_no_category_exist": "आपके पास अभी कोई श्रेणी नहीं है",
"watching": "देख रहा है",
"plan_to_watch": "देखने की योजना",
"re_watching": "पुनः देख रहा है",
"episodes": "एपिसोड",
"download": "डाउनलोड",
"new_update_available": "नया अपडेट उपलब्ध",
"app_version": "ऐप संस्करण: v{v}",
"searching_for_updates": "अपडेट की खोज हो रही है...",
"no_new_updates_available": "कोई नया अपडेट उपलब्ध नहीं",
"uninstall": "हटाएँ",
"uninstall_extension": "{ext} एक्सटेंशन हटाएँ?",
"langauage": "भाषा",
"extension_detail": "एक्सटेंशन विवरण",
"scale_type": "स्केल प्रकार",
"scale_type_fit_screen": "स्क्रीन पर फिट",
"scale_type_stretch": "खींचें",
"scale_type_fit_width": "चौड़ाई पर फिट",
"scale_type_fit_height": "ऊँचाई पर फिट",
"scale_type_original_size": "मूल आकार",
"scale_type_smart_fit": "स्मार्ट फिट",
"page_preload_amount": "पेज प्रीलोड मात्रा",
"page_preload_amount_subtitle": "पढ़ते समय प्रीलोड करने वाले पेजों की मात्रा। उच्च मानों से पढ़ने का अनुभव बेहतर होगा, लेकिन कैश और नेटवर्क उपयोग अधिक होगा।",
"image_loading_error": "यह छवि लोड नहीं हो सकी",
"add_episodes": "एपिसोड जोड़ें",
"video_quality": "गुणवत्ता",
"video_subtitle": "उपशीर्षक",
"check_for_extension_updates": "एक्सटेंशन अपडेट की जाँच करें",
"auto_extensions_updates": "स्वचालित एक्सटेंशन अपडेट",
"auto_extensions_updates_subtitle": "जब नया संस्करण उपलब्ध होगा तो एक्सटेंशन स्वचालित रूप से अपडेट हो जाएगा।",
"reading_mode": "पढ़ने का मोड",
"custom_filter": "कस्टम फ़िल्टर",
"background_color": "पृष्ठभूमि रंग",
"white": "सफेद",
"black": "काला",
"grey": "ग्रे",
"automaic": "स्वचालित",
"preferred_domain": "पसंदीदा डोमेन",
"load_more": "और लोड करें",
"cancel_all_for_this_series": "इस सीरीज़ के लिए सभी रद्द करें",
"login": "लॉगिन",
"login_into": "{tracker} में लॉगिन करें",
"email_adress": "ईमेल पता",
"password": "पासवर्ड",
"log_out_from": "{tracker} से लॉग आउट करें?",
"log_out": "लॉग आउट",
"update_pending": "अपडेट लंबित",
"update_all": "सभी अपडेट करें",
"backup_and_restore": "बैकअप और पुनर्स्थापना",
"create_backup": "बैकअप बनाएँ",
"create_backup_dialog_title": "आप क्या बैकअप करना चाहते हैं?",
"create_backup_subtitle": "वर्तमान पुस्तकालय को पुनर्स्थापित करने के लिए उपयोग किया जा सकता है",
"restore_backup": "बैकअप पुनर्स्थापित करें",
"restore_backup_subtitle": "बैकअप फ़ाइल से पुस्तकालय पुनर्स्थापित करें",
"automatic_backups": "स्वचालित बैकअप",
"backup_frequency": "बैकअप आवृत्ति",
"backup_location": "बैकअप स्थान",
"backup_options": "बैकअप विकल्प",
"backup_options_dialog_title": "आप क्या बैकअप करना चाहते हैं?",
"backup_options_subtitle": "बैकअप फ़ाइल में क्या जानकारी शामिल करनी है",
"backup_and_restore_warning_info": "आपको बैकअप की प्रतियां अन्य स्थानों पर भी रखनी चाहिए",
"library_entries": "पुस्तकालय प्रविष्टियाँ",
"chapters_and_episode": "अध्याय और एपिसोड",
"every_6_hours": "हर 6 घंटे",
"every_12_hours": "हर 12 घंटे",
"daily": "दैनिक",
"every_2_days": "हर 2 दिन",
"weekly": "साप्ताहिक",
"restore_backup_warning_title": "बैकअप पुनर्स्थापित करने से सभी मौजूदा डेटा अधिलेखित हो जाएगा।\n\nपुनर्स्थापना जारी रखें?",
"services": "सेवाएँ",
"tracking_warning_info": "ट्रैकिंग सेवाओं में अध्याय प्रगति को अपडेट करने के लिए एकतरफा समन्वय। व्यक्तिगत प्रविष्टियों के लिए ट्रैकिंग सेट करें।",
"use_page_tap_zones": "पेज टैप ज़ोन का उपयोग करें",
"manage_trackers": "ट्रैकर्स प्रबंधित करें",
"restore": "पुनर्स्थापित करें",
"backups": "बैकअप",
"by_scanlator": "स्कैनलेटर के अनुसार",
"by_name": "नाम के अनुसार",
"installed": "स्थापित",
"auto_scroll": "स्वचालित स्क्रॉल",
"video_audio": "ऑडियो",
"player": "प्लेयर",
"markEpisodeAsSeenSetting": "एपिसोड को कब देखा गया के रूप में चिह्नित करना है",
"default_skip_intro_length": "डिफ़ॉल्ट परिचय छोड़ने की अवधि",
"default_playback_speed_length": "डिफ़ॉल्ट प्लेबैक गति अवधि",
"updateProgressAfterReading": "पढ़ने के बाद प्रगति अपडेट करें",
"no_sources_installed": "कोई स्रोत स्थापित नहीं है!",
"show_extensions": "एक्सटेंशन दिखाएँ"
}

View file

@ -6,10 +6,12 @@ import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:intl/intl.dart' as intl;
import 'app_localizations_ar.dart';
import 'app_localizations_as.dart';
import 'app_localizations_de.dart';
import 'app_localizations_en.dart';
import 'app_localizations_es.dart';
import 'app_localizations_fr.dart';
import 'app_localizations_hi.dart';
import 'app_localizations_id.dart';
import 'app_localizations_it.dart';
import 'app_localizations_pt.dart';
@ -105,11 +107,13 @@ abstract class AppLocalizations {
/// A list of this localizations delegate's supported locales.
static const List<Locale> supportedLocales = <Locale>[
Locale('ar'),
Locale('as'),
Locale('de'),
Locale('en'),
Locale('es'),
Locale('es', '419'),
Locale('fr'),
Locale('hi'),
Locale('id'),
Locale('it'),
Locale('pt'),
@ -1421,6 +1425,30 @@ abstract class AppLocalizations {
/// **'Sync progress'**
String get sync_button_sync;
/// No description provided for @sync_button_upload.
///
/// In en, this message translates to:
/// **'Upload only'**
String get sync_button_upload;
/// No description provided for @sync_button_upload_info.
///
/// In en, this message translates to:
/// **'This operation will fully replace the remote data with local data!'**
String get sync_button_upload_info;
/// No description provided for @sync_button_download.
///
/// In en, this message translates to:
/// **'Download only'**
String get sync_button_download;
/// No description provided for @sync_button_download_info.
///
/// In en, this message translates to:
/// **'This operation will fully replace the local data with remote data!'**
String get sync_button_download_info;
/// No description provided for @sync_on.
///
/// In en, this message translates to:
@ -2916,10 +2944,12 @@ class _AppLocalizationsDelegate
@override
bool isSupported(Locale locale) => <String>[
'ar',
'as',
'de',
'en',
'es',
'fr',
'hi',
'id',
'it',
'pt',
@ -2958,6 +2988,8 @@ AppLocalizations lookupAppLocalizations(Locale locale) {
switch (locale.languageCode) {
case 'ar':
return AppLocalizationsAr();
case 'as':
return AppLocalizationsAs();
case 'de':
return AppLocalizationsDe();
case 'en':
@ -2966,6 +2998,8 @@ AppLocalizations lookupAppLocalizations(Locale locale) {
return AppLocalizationsEs();
case 'fr':
return AppLocalizationsFr();
case 'hi':
return AppLocalizationsHi();
case 'id':
return AppLocalizationsId();
case 'it':

View file

@ -696,6 +696,20 @@ class AppLocalizationsAr extends AppLocalizations {
@override
String get sync_button_sync => 'مزامنة التقدم';
@override
String get sync_button_upload => 'Upload only';
@override
String get sync_button_upload_info =>
'This operation will fully replace the remote data with local data!';
@override
String get sync_button_download => 'Download only';
@override
String get sync_button_download_info =>
'This operation will fully replace the local data with remote data!';
@override
String get sync_on => 'تمكين المزامنة';

File diff suppressed because it is too large Load diff

View file

@ -700,6 +700,20 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get sync_button_sync => 'Jetzt synchronisieren';
@override
String get sync_button_upload => 'Upload only';
@override
String get sync_button_upload_info =>
'This operation will fully replace the remote data with local data!';
@override
String get sync_button_download => 'Download only';
@override
String get sync_button_download_info =>
'This operation will fully replace the local data with remote data!';
@override
String get sync_on => 'Sync aktivieren';

View file

@ -698,6 +698,20 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get sync_button_sync => 'Sync progress';
@override
String get sync_button_upload => 'Upload only';
@override
String get sync_button_upload_info =>
'This operation will fully replace the remote data with local data!';
@override
String get sync_button_download => 'Download only';
@override
String get sync_button_download_info =>
'This operation will fully replace the local data with remote data!';
@override
String get sync_on => 'Enable sync';

View file

@ -702,6 +702,20 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get sync_button_sync => 'Sincronizar progreso';
@override
String get sync_button_upload => 'Upload only';
@override
String get sync_button_upload_info =>
'This operation will fully replace the remote data with local data!';
@override
String get sync_button_download => 'Download only';
@override
String get sync_button_download_info =>
'This operation will fully replace the local data with remote data!';
@override
String get sync_on => 'Habilitar sincronización';

View file

@ -705,6 +705,20 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get sync_button_sync => 'Synchroniser les progrès';
@override
String get sync_button_upload => 'Upload only';
@override
String get sync_button_upload_info =>
'This operation will fully replace the remote data with local data!';
@override
String get sync_button_download => 'Download only';
@override
String get sync_button_download_info =>
'This operation will fully replace the local data with remote data!';
@override
String get sync_on => 'Activer la synchronisation';

File diff suppressed because it is too large Load diff

View file

@ -702,6 +702,20 @@ class AppLocalizationsId extends AppLocalizations {
@override
String get sync_button_sync => 'Sinkronkan progres';
@override
String get sync_button_upload => 'Upload only';
@override
String get sync_button_upload_info =>
'This operation will fully replace the remote data with local data!';
@override
String get sync_button_download => 'Download only';
@override
String get sync_button_download_info =>
'This operation will fully replace the local data with remote data!';
@override
String get sync_on => 'Aktifkan sinkronisasi';

View file

@ -702,6 +702,20 @@ class AppLocalizationsIt extends AppLocalizations {
@override
String get sync_button_sync => 'Sincronizza progressi';
@override
String get sync_button_upload => 'Upload only';
@override
String get sync_button_upload_info =>
'This operation will fully replace the remote data with local data!';
@override
String get sync_button_download => 'Download only';
@override
String get sync_button_download_info =>
'This operation will fully replace the local data with remote data!';
@override
String get sync_on => 'Abilita sincronizzazione';

View file

@ -702,6 +702,20 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get sync_button_sync => 'Sincronizar progresso';
@override
String get sync_button_upload => 'Upload only';
@override
String get sync_button_upload_info =>
'This operation will fully replace the remote data with local data!';
@override
String get sync_button_download => 'Download only';
@override
String get sync_button_download_info =>
'This operation will fully replace the local data with remote data!';
@override
String get sync_on => 'Ativar sincronização';

View file

@ -704,6 +704,20 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String get sync_button_sync => 'Синхронизировать прогресс';
@override
String get sync_button_upload => 'Upload only';
@override
String get sync_button_upload_info =>
'This operation will fully replace the remote data with local data!';
@override
String get sync_button_download => 'Download only';
@override
String get sync_button_download_info =>
'This operation will fully replace the local data with remote data!';
@override
String get sync_on => 'Включить синхронизацию';

View file

@ -698,6 +698,20 @@ class AppLocalizationsTh extends AppLocalizations {
@override
String get sync_button_sync => 'ซิงค์ความคืบหน้า';
@override
String get sync_button_upload => 'Upload only';
@override
String get sync_button_upload_info =>
'This operation will fully replace the remote data with local data!';
@override
String get sync_button_download => 'Download only';
@override
String get sync_button_download_info =>
'This operation will fully replace the local data with remote data!';
@override
String get sync_on => 'เปิดการซิงค์';

View file

@ -698,6 +698,20 @@ class AppLocalizationsTr extends AppLocalizations {
@override
String get sync_button_sync => 'İlerlemeyi senkronize et';
@override
String get sync_button_upload => 'Upload only';
@override
String get sync_button_upload_info =>
'This operation will fully replace the remote data with local data!';
@override
String get sync_button_download => 'Download only';
@override
String get sync_button_download_info =>
'This operation will fully replace the local data with remote data!';
@override
String get sync_on => 'Senkronizasyonu etkinleştir';

View file

@ -690,6 +690,20 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get sync_button_sync => '同步进度';
@override
String get sync_button_upload => 'Upload only';
@override
String get sync_button_upload_info =>
'This operation will fully replace the remote data with local data!';
@override
String get sync_button_download => 'Download only';
@override
String get sync_button_download_info =>
'This operation will fully replace the local data with remote data!';
@override
String get sync_on => '启用同步';

View file

@ -258,6 +258,11 @@ class Settings {
bool? rpcShowCoverImage;
bool? downloadedOnlyMode;
late AlgorithmWeights? algorithmWeights;
Settings({
this.id = 227,
this.updatedAt = 0,
@ -373,6 +378,8 @@ class Settings {
this.rpcShowReadingWatchingProgress = true,
this.rpcShowTitle = true,
this.rpcShowCoverImage = true,
this.downloadedOnlyMode = false,
this.algorithmWeights,
});
Settings.fromJson(Map<String, dynamic> json) {
@ -594,6 +601,10 @@ class Settings {
rpcShowReadingWatchingProgress = json['rpcShowReadingWatchingProgress'];
rpcShowTitle = json['rpcShowTitle'];
rpcShowCoverImage = json['rpcShowCoverImage'];
downloadedOnlyMode = json['downloadedOnlyMode'];
algorithmWeights = json['algorithmWeights'] != null
? AlgorithmWeights.fromJson(json['algorithmWeights'])
: null;
}
Map<String, dynamic> toJson() => {
@ -732,6 +743,9 @@ class Settings {
'rpcShowReadingWatchingProgress': rpcShowReadingWatchingProgress,
'rpcShowTitle': rpcShowTitle,
'rpcShowCoverImage': rpcShowCoverImage,
'downloadedOnlyMode': downloadedOnlyMode,
if (algorithmWeights != null)
'algorithmWeights': algorithmWeights!.toJson(),
};
}
@ -1088,6 +1102,35 @@ class PlayerSubtitleSettings {
};
}
@embedded
class AlgorithmWeights {
int? genre;
int? setting;
int? synopsis;
int? theme;
AlgorithmWeights({
this.genre = 30,
this.setting = 15,
this.synopsis = 40,
this.theme = 20,
});
AlgorithmWeights.fromJson(Map<String, dynamic> json) {
genre = json['genre'];
setting = json['setting'];
synopsis = json['synopsis'];
theme = json['theme'];
}
Map<String, dynamic> toJson() => {
'genre': genre,
'setting': setting,
'synopsis': synopsis,
'theme': theme,
};
}
enum ColorFilterBlendMode {
none,
multiply,

View file

@ -124,12 +124,14 @@ class _BrowseScreenState extends ConsumerState<BrowseScreen>
} else {
context.push(
'/globalSearch',
extra:
switch (_tabList[_tabBarController.index]) {
"manga" => ItemType.manga,
"anime" => ItemType.anime,
_ => ItemType.novel,
},
extra: (
null,
switch (_tabList[_tabBarController.index]) {
"manga" => ItemType.manga,
"anime" => ItemType.anime,
_ => ItemType.novel,
},
),
);
}
},

View file

@ -24,15 +24,16 @@ import 'package:mangayomi/modules/widgets/manga_image_card_widget.dart';
import 'package:super_sliver_list/super_sliver_list.dart';
class GlobalSearchScreen extends ConsumerStatefulWidget {
final String? search;
final ItemType itemType;
const GlobalSearchScreen({required this.itemType, super.key});
const GlobalSearchScreen({this.search, required this.itemType, super.key});
@override
ConsumerState<GlobalSearchScreen> createState() => _GlobalSearchScreenState();
}
class _GlobalSearchScreenState extends ConsumerState<GlobalSearchScreen> {
String query = "";
String _query = "";
final _textEditingController = TextEditingController();
late final List<Source> sourceList =
ref.read(onlyIncludePinnedSourceStateProvider)
@ -50,8 +51,17 @@ class _GlobalSearchScreenState extends ConsumerState<GlobalSearchScreen> {
.and()
.itemTypeEqualTo(widget.itemType)
.findAllSync();
@override
void initState() {
super.initState();
_textEditingController.text = widget.search ?? "";
}
@override
Widget build(BuildContext context) {
final query = _query.isNotEmpty ? _query : widget.search ?? "";
return Scaffold(
appBar: AppBar(
leading: Container(),
@ -62,27 +72,27 @@ class _GlobalSearchScreenState extends ConsumerState<GlobalSearchScreen> {
Navigator.pop(context);
},
onFieldSubmitted: (value) async {
if (!(query == _textEditingController.text)) {
if (!(_query == _textEditingController.text)) {
setState(() {
query = "";
_query = "";
});
await Future.delayed(const Duration(milliseconds: 10));
setState(() {
query = value;
_query = value;
});
}
},
onSuffixPressed: () {
_textEditingController.clear();
setState(() {
query = "";
_query = "";
});
},
controller: _textEditingController,
),
],
),
body: query.isNotEmpty
body: _query.isNotEmpty || widget.search != null
? SuperListView.builder(
itemCount: sourceList.length,
extentPrecalculationPolicy: SuperPrecalculationPolicy(),
@ -90,7 +100,11 @@ class _GlobalSearchScreenState extends ConsumerState<GlobalSearchScreen> {
final source = sourceList[index];
return SizedBox(
height: 260,
child: SourceSearchScreen(query: query, source: source),
child: SourceSearchScreen(
key: ValueKey(query),
query: query,
source: source,
),
);
},
)

View file

@ -21,6 +21,7 @@ import 'package:mangayomi/modules/library/providers/local_archive.dart';
import 'package:mangayomi/modules/manga/detail/providers/state_providers.dart';
import 'package:mangayomi/modules/manga/detail/providers/update_manga_detail_providers.dart';
import 'package:mangayomi/modules/more/categories/providers/isar_providers.dart';
import 'package:mangayomi/modules/more/providers/downloaded_only_state_provider.dart';
import 'package:mangayomi/modules/more/settings/appearance/providers/theme_mode_state_provider.dart';
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
import 'package:mangayomi/modules/widgets/bottom_select_bar.dart';
@ -136,6 +137,7 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
final withoutCategories = ref.watch(
getAllMangaWithoutCategoriesStreamProvider(itemType: widget.itemType),
);
final downloadedOnly = ref.watch(downloadedOnlyStateProvider);
final mangaAll = ref.watch(
getAllMangaStreamProvider(
categoryId: null,
@ -249,6 +251,7 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
ref: ref,
localSource: localSource,
settings: settings,
downloadedOnly: downloadedOnly,
);
}
if (tabCount > 0 &&
@ -281,6 +284,7 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
startedFilterType: startedFilterType,
bookmarkedFilterType: bookmarkedFilterType,
sortType: sortType,
downloadedOnly: downloadedOnly,
);
final withoutCategoryNumberOfItemsList =
_filterAndSortManga(
@ -290,6 +294,7 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
startedFilterType: startedFilterType,
bookmarkedFilterType: bookmarkedFilterType,
sortType: sortType,
downloadedOnly: downloadedOnly,
);
return DefaultTabController(
@ -372,6 +377,8 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
categoryId:
entr[i - 1].id!,
settings: settings,
downloadedOnly:
downloadedOnly,
),
],
),
@ -398,6 +405,8 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
continueReaderBtn,
categoryId: entr[i].id!,
settings: settings,
downloadedOnly:
downloadedOnly,
),
],
),
@ -434,6 +443,8 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
ref: ref,
localSource: localSource,
settings: settings,
downloadedOnly:
downloadedOnly,
)
: _bodyWithCatories(
categoryId:
@ -456,6 +467,8 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
ref: ref,
localSource: localSource,
settings: settings,
downloadedOnly:
downloadedOnly,
),
if (withoutCategory.isEmpty)
for (
@ -483,6 +496,7 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
ref: ref,
localSource: localSource,
settings: settings,
downloadedOnly: downloadedOnly,
),
],
),
@ -503,6 +517,7 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
startedFilterType: startedFilterType,
bookmarkedFilterType: bookmarkedFilterType,
sortType: sortType,
downloadedOnly: downloadedOnly,
);
return Scaffold(
appBar: _appBar(
@ -528,6 +543,7 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
ref: ref,
localSource: localSource,
settings: settings,
downloadedOnly: downloadedOnly,
),
);
},
@ -659,6 +675,7 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
required bool continueReaderBtn,
required int categoryId,
required Settings settings,
required bool downloadedOnly,
}) {
final mangas = ref.watch(
getAllMangaStreamProvider(
@ -683,6 +700,7 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
startedFilterType: startedFilterType,
bookmarkedFilterType: bookmarkedFilterType,
sortType: sortType!,
downloadedOnly: downloadedOnly,
);
return CircleAvatar(
backgroundColor: Theme.of(context).focusColor,
@ -719,6 +737,7 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
required WidgetRef ref,
required DisplayType displayType,
required Settings settings,
required bool downloadedOnly,
}) {
final l10n = l10nLocalizations(context)!;
final mangas = ref.watch(
@ -746,6 +765,7 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
startedFilterType: startedFilterType,
bookmarkedFilterType: bookmarkedFilterType,
sortType: sortType!,
downloadedOnly: downloadedOnly,
);
if (entries.isNotEmpty) {
final entriesManga = reverse ? entries.reversed.toList() : entries;
@ -803,6 +823,7 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
required WidgetRef ref,
bool withoutCategories = false,
required Settings settings,
required bool downloadedOnly,
}) {
final sortType = ref
.watch(
@ -835,6 +856,7 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
startedFilterType: startedFilterType,
bookmarkedFilterType: bookmarkedFilterType,
sortType: sortType ?? 0,
downloadedOnly: downloadedOnly,
);
if (entries.isNotEmpty) {
final entriesManga = reverse ? entries.reversed.toList() : entries;
@ -898,6 +920,7 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
required int startedFilterType,
required int bookmarkedFilterType,
required int sortType,
bool downloadedOnly = false,
}) {
List<Manga>? mangas;
final searchQuery = _textEditingController.text;
@ -912,7 +935,7 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
.where((element) {
// Filter by download
List list = [];
if (downloadFilterType == 1) {
if (downloadFilterType == 1 || downloadedOnly) {
for (var chap in element.chapters) {
final modelChapDownload = isar.downloads
.filter()

View file

@ -11,6 +11,7 @@ import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/update.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/more/providers/downloaded_only_state_provider.dart';
import 'package:mangayomi/modules/more/settings/reader/providers/reader_state_provider.dart';
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
import 'package:mangayomi/modules/widgets/loading_icon.dart';
@ -239,10 +240,16 @@ class _MainScreenState extends ConsumerState<MainScreen> {
}
final incognitoMode = ref.watch(incognitoModeStateProvider);
final downloadedOnly = ref.watch(downloadedOnlyStateProvider);
final isLongPressed = ref.watch(isLongPressedStateProvider);
return Column(
children: [
if (!isReadingScreen)
_DownloadedOnlyBar(
downloadedOnly: downloadedOnly,
l10n: l10n,
),
if (!isReadingScreen)
_IncognitoModeBar(incognitoMode: incognitoMode, l10n: l10n),
Flexible(
@ -526,6 +533,45 @@ class _MainScreenState extends ConsumerState<MainScreen> {
}
}
class _DownloadedOnlyBar extends StatelessWidget {
const _DownloadedOnlyBar({required this.downloadedOnly, required this.l10n});
final bool downloadedOnly;
final dynamic l10n;
@override
Widget build(BuildContext context) {
return Material(
child: AnimatedContainer(
height: downloadedOnly
? Platform.isAndroid || Platform.isIOS
? MediaQuery.of(context).padding.top * 2
: 50
: 0,
curve: Curves.easeIn,
duration: const Duration(milliseconds: 150),
color: context.secondaryColor,
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
l10n.downloaded_only,
style: TextStyle(
color: Colors.white,
fontFamily: GoogleFonts.aBeeZee().fontFamily,
),
),
),
],
),
),
);
}
}
class _IncognitoModeBar extends StatelessWidget {
const _IncognitoModeBar({required this.incognitoMode, required this.l10n});

View file

@ -22,6 +22,7 @@ import 'package:mangayomi/modules/manga/detail/providers/track_state_providers.d
import 'package:mangayomi/modules/manga/detail/widgets/tracker_search_widget.dart';
import 'package:mangayomi/modules/manga/detail/widgets/tracker_widget.dart';
import 'package:mangayomi/modules/manga/reader/providers/reader_controller_provider.dart';
import 'package:mangayomi/modules/more/providers/algorithm_weights_state_provider.dart';
import 'package:mangayomi/modules/more/settings/appearance/providers/pure_black_dark_mode_state_provider.dart';
import 'package:mangayomi/modules/more/settings/track/widgets/track_listile.dart';
import 'package:mangayomi/modules/widgets/bottom_select_bar.dart';
@ -625,6 +626,11 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
value: 3,
child: Text(l10n.migrate),
),
if (!isLocalArchive)
PopupMenuItem<int>(
value: 4,
child: Text(l10n.extension_settings),
),
];
},
onSelected: (value) {
@ -652,6 +658,16 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
case 3:
context.push("/migrate", extra: widget.manga);
break;
case 4:
final source = getSource(
widget.manga!.lang!,
widget.manga!.source!,
);
context.push(
'/extension_detail',
extra: source,
);
break;
}
},
),
@ -1596,6 +1612,37 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
),
),
),
const SizedBox(height: 15),
SizedBox(
width: context.width(1),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: OutlinedButton.icon(
style: ButtonStyle(
shape: WidgetStatePropertyAll(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30.0),
),
),
),
onPressed: () {
final algorithmWeights = ref.read(
algorithmWeightsStateProvider,
);
context.push(
"/recommendations",
extra: (
widget.manga!.name,
widget.manga!.itemType,
algorithmWeights,
),
);
},
label: Text(l10n.recommendations),
icon: Icon(Icons.arrow_right_alt_outlined),
),
),
),
if (!context.isTablet)
Column(
children: [

View file

@ -0,0 +1,383 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/modules/widgets/custom_extended_image_provider.dart';
import 'package:mangayomi/modules/widgets/progress_center.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/services/recommendation.dart';
import 'package:mangayomi/utils/constant.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
import 'package:marquee/marquee.dart';
import 'package:photo_view/photo_view.dart';
import 'package:photo_view/photo_view_gallery.dart';
import 'package:super_sliver_list/super_sliver_list.dart';
class RecommendationScreen extends StatefulWidget {
final String name;
final ItemType itemType;
final AlgorithmWeights algorithmWeights;
const RecommendationScreen({
super.key,
required this.name,
required this.itemType,
required this.algorithmWeights,
});
@override
State<RecommendationScreen> createState() => _RecommendationScreenState();
}
class _RecommendationScreenState extends State<RecommendationScreen> {
String _errorMessage = "";
bool _isLoading = true;
List<RecommendationResult>? data;
@override
void initState() {
super.initState();
_init();
}
Future<void> _init() async {
try {
_errorMessage = "";
data = await getRecommendations(
widget.name,
widget.itemType,
widget.algorithmWeights,
);
if (mounted) {
setState(() {
_isLoading = false;
});
}
} catch (e) {
if (mounted) {
setState(() {
_errorMessage = e.toString();
_isLoading = false;
});
}
}
}
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
return Scaffold(
appBar: AppBar(title: Text(l10n.recommendations)),
body: Padding(
padding: EdgeInsetsGeometry.all(5),
child: _isLoading
? const Center(child: CircularProgressIndicator())
: Builder(
builder: (context) {
if (_errorMessage.isNotEmpty) {
return Center(child: Text(_errorMessage));
}
if (data != null && data!.isNotEmpty) {
return SuperListView.builder(
extentPrecalculationPolicy: SuperPrecalculationPolicy(),
itemCount: data!.length,
itemBuilder: (context, index) {
final recommendation = data![index];
return ListTile(
onTap: () => context.push(
'/globalSearch',
extra: (
recommendation.titleEnglish ??
recommendation.titleRomaji ??
recommendation.titleNative,
widget.itemType,
),
),
title: Row(
children: [
if (recommendation.imgURLs.isNotEmpty)
_thumbnailPreview(
context,
recommendation.imgURLs.first,
),
const SizedBox(width: 15),
recommendation.description != null
? Flexible(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
_buildTitle(
recommendation.titleEnglish ??
recommendation.titleRomaji ??
recommendation.titleNative ??
"",
context,
),
Text(
recommendation.description!,
style: const TextStyle(
fontSize: 11,
),
overflow: TextOverflow.clip,
),
],
),
)
: Flexible(
child: _buildTitle(
recommendation.titleEnglish ??
recommendation.titleRomaji ??
recommendation.titleNative ??
"",
context,
),
),
],
),
subtitle: Padding(
padding: const EdgeInsets.symmetric(vertical: 15),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12,
),
child: recommendation.genres.isEmpty
? const SizedBox(height: 15)
: context.isTablet
? Wrap(
children: [
for (
var i = 0;
i < recommendation.genres.length;
i++
)
Padding(
padding: const EdgeInsets.only(
left: 2,
right: 2,
bottom: 5,
),
child: SizedBox(
height: 30,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
elevation: 0,
backgroundColor: Colors
.grey
.withValues(
alpha: 0.2,
),
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(
5,
),
),
),
onPressed: null,
child: Text(
recommendation.genres[i],
style: TextStyle(
fontSize: 11.5,
color: context.isLight
? Colors.black
: Colors.white,
),
),
),
),
),
],
)
: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
mainAxisAlignment:
MainAxisAlignment.start,
children: [
for (
var i = 0;
i <
recommendation
.genres
.length;
i++
)
Padding(
padding:
const EdgeInsets.only(
left: 2,
right: 2,
bottom: 5,
),
child: SizedBox(
height: 30,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
elevation: 0,
backgroundColor: Colors
.grey
.withValues(
alpha: 0.2,
),
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(
5,
),
),
),
onPressed: () {},
child: Text(
recommendation
.genres[i],
style: TextStyle(
fontSize: 11.5,
color: context.isLight
? Colors.black
: Colors.white,
),
),
),
),
),
],
),
),
),
const SizedBox(width: 15),
Text(
"${recommendation.score}% ${l10n.recommendations_similar}",
style: TextStyle(
background: Paint()
..color = Theme.of(context)
.scaffoldBackgroundColor
.withValues(alpha: 0.75)
..strokeWidth = 30.0
..strokeJoin = StrokeJoin.round
..style = PaintingStyle.stroke,
fontWeight: FontWeight.bold,
),
),
],
),
),
);
},
);
}
return Center(child: Text(l10n.no_result));
},
),
),
);
}
Widget _buildTitle(String text, BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
// Make sure that (constraints.maxWidth - (35 + 5)) is strictly positive.
final double availableWidth = constraints.maxWidth - (35 + 5);
final textPainter =
TextPainter(
text: TextSpan(text: text, style: const TextStyle(fontSize: 13)),
maxLines: 1,
textDirection: TextDirection.ltr,
)..layout(
maxWidth: availableWidth > 0 ? availableWidth : 1.0,
); // - Download icon size (download_page_widget.dart, Widget Build SizedBox width: 35)
final isOverflowing = textPainter.didExceedMaxLines;
if (isOverflowing) {
return SizedBox(
height: 20,
child: Marquee(
text: text,
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold),
blankSpace: 40.0,
velocity: 30.0,
pauseAfterRound: const Duration(seconds: 1),
startPadding: 10.0,
),
);
} else {
return Text(
text,
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold),
overflow: TextOverflow.ellipsis,
);
}
},
);
}
Widget _thumbnailPreview(BuildContext context, String? imageUrl) {
final imageProvider = CustomExtendedNetworkImageProvider(
toImgUrl(imageUrl ?? ""),
);
return Padding(
padding: const EdgeInsets.all(3),
child: GestureDetector(
onTap: () {
_openImage(context, imageProvider);
},
child: SizedBox(
width: 100,
height: 150,
child: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(5)),
image: DecorationImage(image: imageProvider, fit: BoxFit.cover),
),
),
),
),
);
}
void _openImage(BuildContext context, ImageProvider imageProvider) {
showDialog(
context: context,
builder: (context) {
return Scaffold(
backgroundColor: Colors.transparent,
body: Stack(
children: [
GestureDetector(
onTap: () => Navigator.pop(context),
child: PhotoViewGallery.builder(
backgroundDecoration: const BoxDecoration(
color: Colors.transparent,
),
itemCount: 1,
builder: (context, index) {
return PhotoViewGalleryPageOptions(
imageProvider: imageProvider,
minScale: PhotoViewComputedScale.contained,
maxScale: 2.0,
);
},
loadingBuilder: (context, event) {
return const ProgressCenter();
},
),
),
],
),
);
},
);
}
}
class SuperPrecalculationPolicy extends ExtentPrecalculationPolicy {
@override
bool shouldPrecalculateExtents(ExtentPrecalculationContext context) {
return context.numberOfItems < 100;
}
}

View file

@ -640,7 +640,7 @@ class _MangaChapterPageGalleryState
reverse: _isReverseHorizontal,
physics: const ClampingScrollPhysics(),
canScrollPage: (_) {
return _horizontalScaleValue == 1.0;
return true;
},
itemBuilder: (context, index) {
if (index < _uChapDataPreload.length &&
@ -693,9 +693,7 @@ class _MangaChapterPageGalleryState
reverse: _isReverseHorizontal,
physics: const ClampingScrollPhysics(),
canScrollPage: (gestureDetails) {
return gestureDetails != null
? !(gestureDetails.totalScale! > 1.0)
: true;
return true;
},
itemBuilder: (BuildContext context, int index) {
if (_uChapDataPreload[index]

View file

@ -1,15 +1,24 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:mangayomi/modules/more/settings/reader/providers/reader_state_provider.dart';
import 'package:mangayomi/modules/more/widgets/downloaded_only_widget.dart';
import 'package:mangayomi/modules/more/widgets/incognito_mode_widget.dart';
import 'package:mangayomi/modules/more/widgets/list_tile_widget.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
class MoreScreen extends StatelessWidget {
class MoreScreen extends ConsumerStatefulWidget {
const MoreScreen({super.key});
@override
ConsumerState<MoreScreen> createState() => MoreScreenState();
}
class MoreScreenState extends ConsumerState<MoreScreen> {
@override
Widget build(BuildContext context) {
final l10n = l10nLocalizations(context);
final hiddenItems = ref.watch(hideItemsStateProvider);
return Scaffold(
body: SingleChildScrollView(
child: Column(
@ -37,8 +46,17 @@ class MoreScreen extends StatelessWidget {
// onChanged: (value) {},
// ),
// ),
const DownloadedOnlyWidget(),
const IncognitoModeWidget(),
const Divider(),
if (hiddenItems.contains("/history"))
ListTileWidget(
onTap: () {
context.push('/history');
},
icon: Icons.history,
title: l10n!.history,
),
ListTileWidget(
onTap: () {
context.push('/downloadQueue');

View file

@ -0,0 +1,35 @@
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'algorithm_weights_state_provider.g.dart';
@riverpod
class AlgorithmWeightsState extends _$AlgorithmWeightsState {
@override
AlgorithmWeights build() {
return isar.settings.getSync(227)!.algorithmWeights ?? AlgorithmWeights();
}
void set(AlgorithmWeights value) {
final settings = isar.settings.getSync(227)!;
state = value;
isar.writeTxnSync(
() => isar.settings.putSync(
settings
..algorithmWeights = state
..updatedAt = DateTime.now().millisecondsSinceEpoch,
),
);
}
void setWeights({int? genre, int? setting, int? synopsis, int? theme}) {
set(
AlgorithmWeights(
genre: genre ?? state.genre,
setting: setting ?? state.setting,
synopsis: synopsis ?? state.synopsis,
theme: theme ?? state.theme,
),
);
}
}

View file

@ -0,0 +1,27 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'algorithm_weights_state_provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$algorithmWeightsStateHash() =>
r'5c20cb9b195a73161b485e082ad024b138c3da9c';
/// See also [AlgorithmWeightsState].
@ProviderFor(AlgorithmWeightsState)
final algorithmWeightsStateProvider = AutoDisposeNotifierProvider<
AlgorithmWeightsState, AlgorithmWeights>.internal(
AlgorithmWeightsState.new,
name: r'algorithmWeightsStateProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$algorithmWeightsStateHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$AlgorithmWeightsState = AutoDisposeNotifier<AlgorithmWeights>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View file

@ -0,0 +1,24 @@
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'downloaded_only_state_provider.g.dart';
@riverpod
class DownloadedOnlyState extends _$DownloadedOnlyState {
@override
bool build() {
return isar.settings.getSync(227)!.downloadedOnlyMode ?? false;
}
void setDownloadedOnly(bool value) {
final settings = isar.settings.getSync(227)!;
state = value;
isar.writeTxnSync(
() => isar.settings.putSync(
settings
..downloadedOnlyMode = state
..updatedAt = DateTime.now().millisecondsSinceEpoch,
),
);
}
}

View file

@ -0,0 +1,27 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'downloaded_only_state_provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$downloadedOnlyStateHash() =>
r'09c451617c435ca59554546f5d3090d20c961bfe';
/// See also [DownloadedOnlyState].
@ProviderFor(DownloadedOnlyState)
final downloadedOnlyStateProvider =
AutoDisposeNotifierProvider<DownloadedOnlyState, bool>.internal(
DownloadedOnlyState.new,
name: r'downloadedOnlyStateProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$downloadedOnlyStateHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$DownloadedOnlyState = AutoDisposeNotifier<bool>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View file

@ -7,7 +7,7 @@ part 'incognito_mode_state_provider.g.dart';
class IncognitoModeState extends _$IncognitoModeState {
@override
bool build() {
return isar.settings.getSync(227)!.incognitoMode!;
return isar.settings.getSync(227)!.incognitoMode ?? false;
}
void setIncognitoMode(bool value) {

View file

@ -7,7 +7,7 @@ part of 'incognito_mode_state_provider.dart';
// **************************************************************************
String _$incognitoModeStateHash() =>
r'149c4dcbc434fb6efc883e196392320bdc7c0821';
r'3858256a820eef632d3df57533f2aad14f555b22';
/// See also [IncognitoModeState].
@ProviderFor(IncognitoModeState)

View file

@ -61,7 +61,6 @@ class _CustomNavigationSettingsState
[
"/more",
"/browse",
"/history",
].any((element) => element == navigation)
? null
: (value) {

View file

@ -1,14 +1,38 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/modules/more/providers/algorithm_weights_state_provider.dart';
import 'package:mangayomi/modules/more/settings/general/providers/general_state_provider.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
class GeneralScreen extends ConsumerWidget {
class GeneralScreen extends ConsumerStatefulWidget {
const GeneralScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
ConsumerState<GeneralScreen> createState() => _GeneralStateScreen();
}
class _GeneralStateScreen extends ConsumerState<GeneralScreen> {
int _genre = 0;
int _setting = 0;
int _synopsis = 0;
int _theme = 0;
@override
void initState() {
super.initState();
final algorithmWeights = ref.read(algorithmWeightsStateProvider);
_genre = algorithmWeights.genre!;
_setting = algorithmWeights.setting!;
_synopsis = algorithmWeights.synopsis!;
_theme = algorithmWeights.theme!;
}
@override
Widget build(BuildContext context) {
final l10n = l10nLocalizations(context);
final enableDiscordRpc = ref.watch(enableDiscordRpcStateProvider);
final hideDiscordRpcInIncognito = ref.watch(
@ -24,6 +48,199 @@ class GeneralScreen extends ConsumerWidget {
body: SingleChildScrollView(
child: Column(
children: [
Container(
margin: const EdgeInsets.all(20.0),
padding: const EdgeInsets.all(10.0),
decoration: BoxDecoration(
border: Border.all(width: 3.0, color: context.primaryColor),
borderRadius: BorderRadius.all(Radius.circular(5.0)),
),
child: Column(
children: [
Row(
children: [
Text(
context.l10n.recommendations_weights,
style: const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(width: 20),
OutlinedButton.icon(
onPressed: () {
final defaultWeights = AlgorithmWeights();
setState(() {
_genre = defaultWeights.genre!;
_setting = defaultWeights.setting!;
_synopsis = defaultWeights.synopsis!;
_theme = defaultWeights.theme!;
});
ref
.read(algorithmWeightsStateProvider.notifier)
.set(defaultWeights);
},
label: Text(context.l10n.reset),
icon: const Icon(Icons.restore),
),
],
),
const SizedBox(height: 10),
Padding(
padding: EdgeInsets.all(8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(context.l10n.recommendations_weights_genre),
Text(
(_genre / 100).toStringAsFixed(2),
style: TextStyle(
fontSize: 11,
color: context.secondaryColor,
),
),
const SizedBox(height: 20),
SliderTheme(
data: SliderTheme.of(context).copyWith(
overlayShape: const RoundSliderOverlayShape(
overlayRadius: 5.0,
),
),
child: Slider.adaptive(
min: 0,
max: 100,
value: _genre.toDouble(),
onChanged: (value) {
HapticFeedback.vibrate();
setState(() {
_genre = value.toInt();
});
},
onChangeEnd: (value) => ref
.read(algorithmWeightsStateProvider.notifier)
.setWeights(genre: _genre),
),
),
],
),
),
const SizedBox(height: 10),
Padding(
padding: EdgeInsets.all(8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(context.l10n.recommendations_weights_setting),
Text(
(_setting / 100).toStringAsFixed(2),
style: TextStyle(
fontSize: 11,
color: context.secondaryColor,
),
),
const SizedBox(height: 20),
SliderTheme(
data: SliderTheme.of(context).copyWith(
overlayShape: const RoundSliderOverlayShape(
overlayRadius: 5.0,
),
),
child: Slider.adaptive(
min: 0,
max: 100,
value: _setting.toDouble(),
onChanged: (value) {
HapticFeedback.vibrate();
setState(() {
_setting = value.toInt();
});
},
onChangeEnd: (value) => ref
.read(algorithmWeightsStateProvider.notifier)
.setWeights(setting: _setting),
),
),
],
),
),
const SizedBox(height: 10),
Padding(
padding: EdgeInsets.all(8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(context.l10n.recommendations_weights_synopsis),
Text(
(_synopsis / 100).toStringAsFixed(2),
style: TextStyle(
fontSize: 11,
color: context.secondaryColor,
),
),
const SizedBox(height: 20),
SliderTheme(
data: SliderTheme.of(context).copyWith(
overlayShape: const RoundSliderOverlayShape(
overlayRadius: 5.0,
),
),
child: Slider.adaptive(
min: 0,
max: 100,
value: _synopsis.toDouble(),
onChanged: (value) {
HapticFeedback.vibrate();
setState(() {
_synopsis = value.toInt();
});
},
onChangeEnd: (value) => ref
.read(algorithmWeightsStateProvider.notifier)
.setWeights(synopsis: _synopsis),
),
),
],
),
),
const SizedBox(height: 10),
Padding(
padding: EdgeInsets.all(8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(context.l10n.recommendations_weights_theme),
Text(
(_theme / 100).toStringAsFixed(2),
style: TextStyle(
fontSize: 11,
color: context.secondaryColor,
),
),
const SizedBox(height: 20),
SliderTheme(
data: SliderTheme.of(context).copyWith(
overlayShape: const RoundSliderOverlayShape(
overlayRadius: 5.0,
),
),
child: Slider.adaptive(
min: 0,
max: 100,
value: _theme.toDouble(),
onChanged: (value) {
HapticFeedback.vibrate();
setState(() {
_theme = value.toInt();
});
},
onChangeEnd: (value) => ref
.read(algorithmWeightsStateProvider.notifier)
.setWeights(theme: _theme),
),
),
],
),
),
],
),
),
SwitchListTile(
value: enableDiscordRpc,
title: Text(l10n.enable_discord_rpc),

View file

@ -116,7 +116,9 @@ class SyncScreen extends ConsumerWidget {
title: Text(l10n.sync_auto),
subtitle: Text(
autoSyncOptions.entries
.where((o) => o.value == syncPreference.autoSyncFrequency)
.where(
(o) => o.value == syncPreference.autoSyncFrequency,
)
.first
.key,
style: TextStyle(
@ -150,29 +152,35 @@ class SyncScreen extends ConsumerWidget {
SwitchListTile(
value: syncPreference.syncHistories,
title: Text(context.l10n.sync_enable_histories),
onChanged: syncPreference.syncOn ? (value) {
ref
.read(SynchingProvider(syncId: 1).notifier)
.setSyncHistories(value);
} : null,
onChanged: syncPreference.syncOn
? (value) {
ref
.read(SynchingProvider(syncId: 1).notifier)
.setSyncHistories(value);
}
: null,
),
SwitchListTile(
value: syncPreference.syncUpdates,
title: Text(context.l10n.sync_enable_updates),
onChanged: syncPreference.syncOn ? (value) {
ref
.read(SynchingProvider(syncId: 1).notifier)
.setSyncUpdates(value);
} : null,
onChanged: syncPreference.syncOn
? (value) {
ref
.read(SynchingProvider(syncId: 1).notifier)
.setSyncUpdates(value);
}
: null,
),
SwitchListTile(
value: syncPreference.syncSettings,
title: Text(context.l10n.sync_enable_settings),
onChanged: syncPreference.syncOn ? (value) {
ref
.read(SynchingProvider(syncId: 1).notifier)
.setSyncSettings(value);
} : null,
onChanged: syncPreference.syncOn
? (value) {
ref
.read(SynchingProvider(syncId: 1).notifier)
.setSyncSettings(value);
}
: null,
),
Padding(
padding: const EdgeInsets.only(
@ -288,6 +296,40 @@ class SyncScreen extends ConsumerWidget {
Text(l10n.sync_button_sync),
],
),
const SizedBox(width: 20),
Column(
children: [
IconButton(
onPressed: !syncPreference.syncOn || !isLogged
? null
: () => _showConfirmDialog(context, ref, true),
icon: Icon(
Icons.file_upload_outlined,
color: !syncPreference.syncOn || !isLogged
? context.secondaryColor
: context.primaryColor,
),
),
Text(l10n.sync_button_upload),
],
),
const SizedBox(width: 20),
Column(
children: [
IconButton(
onPressed: !syncPreference.syncOn || !isLogged
? null
: () => _showConfirmDialog(context, ref, false),
icon: Icon(
Icons.file_download_outlined,
color: !syncPreference.syncOn || !isLogged
? context.secondaryColor
: context.primaryColor,
),
),
Text(l10n.sync_button_download),
],
),
],
),
const SizedBox(height: 20),
@ -298,172 +340,220 @@ class SyncScreen extends ConsumerWidget {
),
);
}
}
void _showDialogLogin(
BuildContext context,
WidgetRef ref,
SyncPreference syncPreference,
) {
final serverController = TextEditingController(text: syncPreference.server);
final emailController = TextEditingController(text: syncPreference.email);
final passwordController = TextEditingController();
String server = "";
String email = "";
String password = "";
String errorMessage = "";
bool isLoading = false;
bool obscureText = true;
final l10n = l10nLocalizations(context)!;
showDialog(
context: context,
builder: (context) => StatefulBuilder(
builder: (context, setState) {
Future<void> _showConfirmDialog(
BuildContext context,
WidgetRef ref,
bool isUpload,
) async {
await showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(
l10n.login_into("SyncServer"),
style: const TextStyle(fontSize: 30),
content: Text(
isUpload
? context.l10n.sync_button_upload_info
: context.l10n.sync_button_download_info,
style: TextStyle(fontWeight: FontWeight.bold),
),
content: SizedBox(
height: 400,
width: MediaQuery.of(context).size.width,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
const SizedBox(height: 10),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: TextFormField(
controller: serverController,
autofocus: true,
onChanged: (value) => setState(() {
server = value;
}),
decoration: InputDecoration(
hintText: l10n.sync_server,
filled: false,
contentPadding: const EdgeInsets.all(12),
enabledBorder: OutlineInputBorder(
borderSide: const BorderSide(width: 0.4),
borderRadius: BorderRadius.circular(5),
),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(),
borderRadius: BorderRadius.circular(5),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: const BorderSide(),
),
),
),
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(context.l10n.cancel),
),
const SizedBox(height: 10),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: TextFormField(
controller: emailController,
autofocus: true,
onChanged: (value) => setState(() {
email = value;
}),
decoration: InputDecoration(
hintText: l10n.email_adress,
filled: false,
contentPadding: const EdgeInsets.all(12),
enabledBorder: OutlineInputBorder(
borderSide: const BorderSide(width: 0.4),
borderRadius: BorderRadius.circular(5),
),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(),
borderRadius: BorderRadius.circular(5),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: const BorderSide(),
),
),
),
),
const SizedBox(height: 10),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: TextFormField(
controller: passwordController,
obscureText: obscureText,
onChanged: (value) => setState(() {
password = value;
}),
decoration: InputDecoration(
hintText: l10n.sync_password,
suffixIcon: IconButton(
onPressed: () => setState(() {
obscureText = !obscureText;
}),
icon: Icon(
obscureText
? Icons.visibility_outlined
: Icons.visibility_off_outlined,
),
),
filled: false,
contentPadding: const EdgeInsets.all(12),
enabledBorder: OutlineInputBorder(
borderSide: const BorderSide(width: 0.4),
borderRadius: BorderRadius.circular(5),
),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(),
borderRadius: BorderRadius.circular(5),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: const BorderSide(),
),
),
),
),
const SizedBox(height: 10),
Text(errorMessage, style: const TextStyle(color: Colors.red)),
const SizedBox(height: 30),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: SizedBox(
width: context.width(1),
height: 50,
child: ElevatedButton(
onPressed: isLoading
? null
: () async {
setState(() {
isLoading = true;
});
final res = await ref
.read(syncServerProvider(syncId: 1).notifier)
.login(l10n, server, email, password);
if (!res.$1) {
setState(() {
isLoading = false;
errorMessage = res.$2;
});
} else {
if (context.mounted) {
Navigator.pop(context);
}
}
},
child: isLoading
? const CircularProgressIndicator()
: Text(l10n.login),
),
),
const SizedBox(width: 15),
ElevatedButton(
onPressed: () {
ref
.read(syncServerProvider(syncId: 1).notifier)
.startSync(
context.l10n,
false,
upload: isUpload,
download: !isUpload,
);
Navigator.pop(context);
},
child: Text(context.l10n.dialog_confirm),
),
],
),
),
],
);
},
),
);
);
}
void _showDialogLogin(
BuildContext context,
WidgetRef ref,
SyncPreference syncPreference,
) {
final serverController = TextEditingController(text: syncPreference.server);
final emailController = TextEditingController(text: syncPreference.email);
final passwordController = TextEditingController();
String server = "";
String email = "";
String password = "";
String errorMessage = "";
bool isLoading = false;
bool obscureText = true;
final l10n = l10nLocalizations(context)!;
showDialog(
context: context,
builder: (context) => StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
title: Text(
l10n.login_into("SyncServer"),
style: const TextStyle(fontSize: 30),
),
content: SizedBox(
height: 400,
width: MediaQuery.of(context).size.width,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 10),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: TextFormField(
controller: serverController,
autofocus: true,
onChanged: (value) => setState(() {
server = value;
}),
decoration: InputDecoration(
hintText: l10n.sync_server,
filled: false,
contentPadding: const EdgeInsets.all(12),
enabledBorder: OutlineInputBorder(
borderSide: const BorderSide(width: 0.4),
borderRadius: BorderRadius.circular(5),
),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(),
borderRadius: BorderRadius.circular(5),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: const BorderSide(),
),
),
),
),
const SizedBox(height: 10),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: TextFormField(
controller: emailController,
autofocus: true,
onChanged: (value) => setState(() {
email = value;
}),
decoration: InputDecoration(
hintText: l10n.email_adress,
filled: false,
contentPadding: const EdgeInsets.all(12),
enabledBorder: OutlineInputBorder(
borderSide: const BorderSide(width: 0.4),
borderRadius: BorderRadius.circular(5),
),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(),
borderRadius: BorderRadius.circular(5),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: const BorderSide(),
),
),
),
),
const SizedBox(height: 10),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: TextFormField(
controller: passwordController,
obscureText: obscureText,
onChanged: (value) => setState(() {
password = value;
}),
decoration: InputDecoration(
hintText: l10n.sync_password,
suffixIcon: IconButton(
onPressed: () => setState(() {
obscureText = !obscureText;
}),
icon: Icon(
obscureText
? Icons.visibility_outlined
: Icons.visibility_off_outlined,
),
),
filled: false,
contentPadding: const EdgeInsets.all(12),
enabledBorder: OutlineInputBorder(
borderSide: const BorderSide(width: 0.4),
borderRadius: BorderRadius.circular(5),
),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(),
borderRadius: BorderRadius.circular(5),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: const BorderSide(),
),
),
),
),
const SizedBox(height: 10),
Text(errorMessage, style: const TextStyle(color: Colors.red)),
const SizedBox(height: 30),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: SizedBox(
width: context.width(1),
height: 50,
child: ElevatedButton(
onPressed: isLoading
? null
: () async {
setState(() {
isLoading = true;
});
final res = await ref
.read(
syncServerProvider(syncId: 1).notifier,
)
.login(l10n, server, email, password);
if (!res.$1) {
setState(() {
isLoading = false;
errorMessage = res.$2;
});
} else {
if (context.mounted) {
Navigator.pop(context);
}
}
},
child: isLoading
? const CircularProgressIndicator()
: Text(l10n.login),
),
),
),
],
),
),
);
},
),
);
}
}

View file

@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/modules/more/providers/downloaded_only_state_provider.dart';
import 'package:mangayomi/modules/more/widgets/list_tile_widget.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
class DownloadedOnlyWidget extends ConsumerWidget {
const DownloadedOnlyWidget({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final l10n = l10nLocalizations(context);
final downloadedOnly = ref.watch(downloadedOnlyStateProvider);
return ListTileWidget(
onTap: () => ref
.read(downloadedOnlyStateProvider.notifier)
.setDownloadedOnly(!downloadedOnly),
icon: Icons.cloud_off_outlined,
subtitle: l10n!.downloaded_only_description,
title: l10n.downloaded_only,
trailing: Switch(
value: downloadedOnly,
onChanged: (value) => ref
.read(downloadedOnlyStateProvider.notifier)
.setDownloadedOnly(value),
),
);
}
}

View file

@ -3,6 +3,7 @@ import 'package:bot_toast/bot_toast.dart';
import 'package:flutter/foundation.dart';
import 'package:go_router/go_router.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/models/track_preference.dart';
import 'package:mangayomi/models/track_search.dart';
@ -12,6 +13,7 @@ import 'package:mangayomi/modules/browse/extension/extension_detail.dart';
import 'package:mangayomi/modules/browse/extension/widgets/create_extension.dart';
import 'package:mangayomi/modules/browse/sources/sources_filter_screen.dart';
import 'package:mangayomi/modules/manga/detail/widgets/migrate_screen.dart';
import 'package:mangayomi/modules/manga/detail/widgets/recommendation_screen.dart';
import 'package:mangayomi/modules/more/data_and_storage/create_backup.dart';
import 'package:mangayomi/modules/more/data_and_storage/data_and_storage.dart';
import 'package:mangayomi/modules/more/settings/appearance/custom_navigation_settings.dart';
@ -168,9 +170,9 @@ class RouterNotifier extends ChangeNotifier {
name: "extension_detail",
builder: (source) => ExtensionDetail(source: source),
),
_genericRoute<ItemType>(
_genericRoute<(String?, ItemType)>(
name: "globalSearch",
builder: (itemType) => GlobalSearchScreen(itemType: itemType),
builder: (data) => GlobalSearchScreen(search: data.$1, itemType: data.$2),
),
_genericRoute(name: "about", child: const AboutScreen()),
_genericRoute(name: "track", child: const TrackScreen()),
@ -222,6 +224,14 @@ class RouterNotifier extends ChangeNotifier {
name: "migrate/tracker",
builder: (data) => MigrationScreen(manga: data.$1, trackSearch: data.$2),
),
_genericRoute<(String, ItemType, AlgorithmWeights)>(
name: "recommendations",
builder: (data) => RecommendationScreen(
name: data.$1,
itemType: data.$2,
algorithmWeights: data.$3,
),
),
];
GoRoute _genericRoute<T>({

View file

@ -0,0 +1,146 @@
import 'dart:convert';
import 'package:http_interceptor/http_interceptor.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/services/http/m_client.dart';
Future<List<RecommendationResult>?> getRecommendations(
String name,
ItemType itemType,
AlgorithmWeights algorithmWeights,
) async {
final http = MClient.init(reqcopyWith: {'useDartHttpClient': true});
try {
final mediaId = await _getSuggest(http, name, itemType);
return _getRecommendation(
http,
mediaId ?? name,
itemType,
algorithmWeights,
);
} catch (_) {
return null;
}
}
Future<List<RecommendationResult>?> _getRecommendation(
InterceptedClient http,
String mediaId,
ItemType itemType,
AlgorithmWeights algorithmWeights,
) async {
final url =
"https://anibrain.ai/api/-/recommender/recs/${itemType != ItemType.anime ? "manga" : "anime"}";
final res = await http.get(
Uri.parse(url),
headers: {
"priority": "u=1, i",
"Referer": "https://anibrain.ai/",
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36",
},
params: {
"filterCountry": '[]',
"filterFormat": '${_fillerType(itemType).map((e) => '"$e"').toList()}',
"filterGenre": '{}',
"filterTag": '{"max":{},"min":{}}',
"filterRelease": '[1930,${DateTime.now().year}]',
"filterScore": 0,
"algorithmWeights": _algorithmWeights(algorithmWeights),
"mediaId": mediaId,
"mediaType": _mediaType(itemType),
"adult": false,
"page": 1,
},
);
final data = json.decode(res.body) as Map<String, dynamic>;
return (data["data"] as List?)
?.map((e) => RecommendationResult.fromJson(e))
.toList();
}
Future<String?> _getSuggest(
InterceptedClient http,
String name,
ItemType itemType,
) async {
final url =
"https://anibrain.ai/api/-/recommender/autosuggest?searchValue=$name&mediaType=${_mediaType(itemType)}&adult=false";
final res = await http.get(
Uri.parse(url),
headers: {
"priority": "u=1, i",
"Referer": "https://anibrain.ai/recommender/manga",
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36",
},
);
final data = json.decode(res.body) as Map<String, dynamic>;
final list = (data["data"] as List?)?.map((e) => e["id"]);
return list?.firstOrNull;
}
String _algorithmWeights(AlgorithmWeights algorithmWeights) {
final genre = ((algorithmWeights.genre ?? 30) / 100).toStringAsFixed(2);
final setting = ((algorithmWeights.setting ?? 15) / 100).toStringAsFixed(2);
final synopsis = ((algorithmWeights.synopsis ?? 40) / 100).toStringAsFixed(2);
final theme = ((algorithmWeights.theme ?? 20) / 100).toStringAsFixed(2);
return '{"genre":$genre,"setting":$setting,"synopsis":$synopsis,"theme":$theme}';
}
String _mediaType(ItemType itemType) {
return switch (itemType) {
ItemType.manga => "MANGA",
ItemType.anime => "ANIME",
ItemType.novel => "NOVEL",
};
}
List<String> _fillerType(ItemType itemType) {
return switch (itemType) {
ItemType.manga => ["MANGA"],
ItemType.anime => ["movie", "ona", "tv"],
ItemType.novel => ["NOVEL"],
};
}
class RecommendationResult {
final String id;
final int? anilistId;
final int? myanimelistId;
final int score;
final String? titleRomaji;
final String? titleEnglish;
final String? titleNative;
final String? description;
final List<String> imgURLs;
final List<String> genres;
RecommendationResult({
required this.id,
this.anilistId,
this.myanimelistId,
required this.score,
this.titleRomaji,
this.titleEnglish,
this.titleNative,
this.description,
required this.imgURLs,
required this.genres,
});
factory RecommendationResult.fromJson(Map<String, dynamic> json) {
return RecommendationResult(
id: json["id"],
anilistId: json["anilistId"],
myanimelistId: json["myanimelistId"],
score: json["score"],
titleRomaji: json["titleRomaji"],
titleEnglish: json["titleEnglish"],
titleNative: json["titleNative"],
description: json["description"],
imgURLs: json["imgURLs"]?.cast<String>() ?? [],
genres: json["genres"]?.cast<String>() ?? [],
);
}
}

View file

@ -41,7 +41,7 @@ class SyncServer extends _$SyncServer {
String username,
String password,
) async {
server = server[server.length - 1] == '/'
server = server.isNotEmpty && server[server.length - 1] == '/'
? server.substring(0, server.length - 1)
: server;
try {
@ -67,7 +67,12 @@ class SyncServer extends _$SyncServer {
}
}
Future<void> startSync(AppLocalizations l10n, bool silent) async {
Future<void> startSync(
AppLocalizations l10n,
bool silent, {
bool upload = false,
bool download = false,
}) async {
if (!silent) {
botToast(l10n.sync_starting, second: 500);
}
@ -75,27 +80,46 @@ class SyncServer extends _$SyncServer {
final syncPreference = ref.read(synchingProvider(syncId: syncId));
final syncNotifier = ref.read(synchingProvider(syncId: syncId).notifier);
final resultManga = await _syncManga(l10n, syncNotifier);
final resultManga = await _syncManga(
l10n,
syncNotifier,
download: download,
upload: upload,
);
if (!resultManga) {
botToast(l10n.sync_failed, second: 5);
return;
}
if (syncPreference.syncHistories) {
final resultHistory = await _syncHistory(l10n, syncNotifier);
final resultHistory = await _syncHistory(
l10n,
syncNotifier,
download: download,
upload: upload,
);
if (!resultHistory) {
botToast(l10n.sync_failed, second: 5);
return;
}
}
if (syncPreference.syncUpdates) {
final resultUpdate = await _syncUpdate(l10n, syncNotifier);
final resultUpdate = await _syncUpdate(
l10n,
syncNotifier,
download: download,
upload: upload,
);
if (!resultUpdate) {
botToast(l10n.sync_failed, second: 5);
return;
}
}
if (syncPreference.syncSettings) {
final resultSettings = await _syncSettings(l10n);
final resultSettings = await _syncSettings(
l10n,
download: download,
upload: upload,
);
if (!resultSettings) {
botToast(l10n.sync_failed, second: 5);
return;
@ -111,8 +135,13 @@ class SyncServer extends _$SyncServer {
}
}
Future<bool> _syncManga(AppLocalizations l10n, Synching syncNotifier) async {
final mangaData = _getMangaData();
Future<bool> _syncManga(
AppLocalizations l10n,
Synching syncNotifier, {
bool upload = false,
bool download = false,
}) async {
final mangaData = _getMangaData(upload: upload, download: download);
final accessToken = _getAccessToken();
var response = await http.post(
Uri.parse('${_getServer()}$_syncMangaUrl'),
@ -127,11 +156,20 @@ class SyncServer extends _$SyncServer {
return false;
}
final jsonData = jsonDecode(response.body) as Map<String, dynamic>;
await _upsertCategories(jsonData, syncNotifier);
await _upsertManga(jsonData, syncNotifier);
await _upsertChapters(jsonData, syncNotifier);
await _upsertTracks(jsonData, syncNotifier);
if (!upload) {
final jsonData = jsonDecode(response.body) as Map<String, dynamic>;
await _upsertCategories(jsonData, syncNotifier);
await _upsertManga(jsonData, syncNotifier);
await _upsertChapters(jsonData, syncNotifier);
await _upsertTracks(jsonData, syncNotifier);
} else {
await syncNotifier.clearChangedParts([
ActionType.removeCategory,
ActionType.removeItem,
ActionType.removeChapter,
ActionType.removeTrack,
], true);
}
syncNotifier.setLastSyncManga(DateTime.now().millisecondsSinceEpoch);
@ -140,9 +178,11 @@ class SyncServer extends _$SyncServer {
Future<bool> _syncHistory(
AppLocalizations l10n,
Synching syncNotifier,
) async {
final historyData = _getHistoryData();
Synching syncNotifier, {
bool upload = false,
bool download = false,
}) async {
final historyData = _getHistoryData(upload: upload, download: download);
final accessToken = _getAccessToken();
var response = await http.post(
Uri.parse('${_getServer()}$_syncHistoryUrl'),
@ -157,16 +197,25 @@ class SyncServer extends _$SyncServer {
return false;
}
final jsonData = jsonDecode(response.body) as Map<String, dynamic>;
await _upsertHistories(jsonData, syncNotifier);
if (!upload) {
final jsonData = jsonDecode(response.body) as Map<String, dynamic>;
await _upsertHistories(jsonData, syncNotifier);
} else {
await syncNotifier.clearChangedParts([ActionType.removeHistory], true);
}
syncNotifier.setLastSyncHistory(DateTime.now().millisecondsSinceEpoch);
return true;
}
Future<bool> _syncUpdate(AppLocalizations l10n, Synching syncNotifier) async {
final updateData = _getUpdateData();
Future<bool> _syncUpdate(
AppLocalizations l10n,
Synching syncNotifier, {
bool upload = false,
bool download = false,
}) async {
final updateData = _getUpdateData(upload: upload, download: download);
final accessToken = _getAccessToken();
var response = await http.post(
Uri.parse('${_getServer()}$_syncUpdateUrl'),
@ -181,16 +230,24 @@ class SyncServer extends _$SyncServer {
return false;
}
final jsonData = jsonDecode(response.body) as Map<String, dynamic>;
await _upsertUpdates(jsonData, syncNotifier);
if (!upload) {
final jsonData = jsonDecode(response.body) as Map<String, dynamic>;
await _upsertUpdates(jsonData, syncNotifier);
} else {
await syncNotifier.clearChangedParts([ActionType.removeUpdate], true);
}
syncNotifier.setLastSyncUpdate(DateTime.now().millisecondsSinceEpoch);
return true;
}
Future<bool> _syncSettings(AppLocalizations l10n) async {
final settingsData = _getSettingsData();
Future<bool> _syncSettings(
AppLocalizations l10n, {
bool upload = false,
bool download = false,
}) async {
final settingsData = _getSettingsData(download: download);
final accessToken = _getAccessToken();
var response = await http.post(
Uri.parse('${_getServer()}$_syncSettingsUrl'),
@ -205,8 +262,10 @@ class SyncServer extends _$SyncServer {
return false;
}
final jsonData = jsonDecode(response.body) as Map<String, dynamic>;
await _upsertSettings(jsonData);
if (!upload) {
final jsonData = jsonDecode(response.body) as Map<String, dynamic>;
await _upsertSettings(jsonData);
}
return true;
}
@ -424,36 +483,61 @@ class SyncServer extends _$SyncServer {
});
}
String _getMangaData() {
String _getMangaData({bool upload = false, bool download = false}) {
Map<String, dynamic> data = {};
data["categories"] = _getCategories();
data["deleted_categories"] = _getDeletedObjects(ActionType.removeCategory);
data["manga"] = _getManga();
data["deleted_manga"] = _getDeletedObjects(ActionType.removeItem);
data["chapters"] = _getChapters();
data["deleted_chapters"] = _getDeletedObjects(ActionType.removeChapter);
data["tracks"] = _getTracks();
data["deleted_tracks"] = _getDeletedObjects(ActionType.removeTrack);
data["categories"] = download ? [] : _getCategories();
data["deleted_categories"] = download
? []
: _getDeletedObjects(ActionType.removeCategory);
data["manga"] = download ? [] : _getManga();
data["deleted_manga"] = download
? []
: _getDeletedObjects(ActionType.removeItem);
data["chapters"] = download ? [] : _getChapters();
data["deleted_chapters"] = download
? []
: _getDeletedObjects(ActionType.removeChapter);
data["tracks"] = download ? [] : _getTracks();
data["deleted_tracks"] = download
? []
: _getDeletedObjects(ActionType.removeTrack);
if (upload) {
data["resetAll"] = true;
}
return jsonEncode(data);
}
String _getHistoryData() {
String _getHistoryData({bool upload = false, bool download = false}) {
Map<String, dynamic> data = {};
data["histories"] = _getHistories();
data["deleted_histories"] = _getDeletedObjects(ActionType.removeHistory);
data["histories"] = download ? [] : _getHistories();
data["deleted_histories"] = download
? []
: _getDeletedObjects(ActionType.removeHistory);
if (upload) {
data["resetAll"] = true;
}
return jsonEncode(data);
}
String _getUpdateData() {
String _getUpdateData({bool upload = false, bool download = false}) {
Map<String, dynamic> data = {};
data["updates"] = _getUpdates();
data["deleted_updates"] = _getDeletedObjects(ActionType.removeUpdate);
data["updates"] = download ? [] : _getUpdates();
data["deleted_updates"] = download
? []
: _getDeletedObjects(ActionType.removeUpdate);
if (upload) {
data["resetAll"] = true;
}
return jsonEncode(data);
}
String _getSettingsData() {
String _getSettingsData({bool download = false}) {
Map<String, dynamic> data = {};
data["settings"] = isar.settings.getSync(227)!..updatedAt ??= DateTime.now().millisecondsSinceEpoch..cookiesList = [];
if (!download) {
data["settings"] = isar.settings.getSync(227)!
..updatedAt ??= DateTime.now().millisecondsSinceEpoch
..cookiesList = [];
}
return jsonEncode(data);
}

View file

@ -160,7 +160,7 @@ class Kitsu extends _$Kitsu {
mediaId: jsonRes['id'],
summary: jsonRes['synopsis'] ?? "",
totalChapter: (jsonRes[totalChapter] ?? 0),
coverUrl: jsonRes['posterImage']['original'] ?? "",
coverUrl: jsonRes['posterImage']?['original'] ?? "",
title: jsonRes['canonicalTitle'],
startDate: "",
publishingType: (jsonRes["subtype"] ?? ""),
@ -192,22 +192,23 @@ class Kitsu extends _$Kitsu {
final mediaId = jsonRes['id'] is String
? int.parse(jsonRes['id'])
: jsonRes['id'];
final score = jsonRes['attributes']['averageRating'] is String
? double.parse(jsonRes['attributes']['averageRating'])
: jsonRes['attributes']['averageRating'];
final attributes = jsonRes['attributes'];
final score = attributes['averageRating'] is String
? double.parse(attributes['averageRating'])
: attributes['averageRating'];
return TrackSearch(
libraryId: mediaId,
syncId: syncId,
trackingUrl: _mediaUrl(isManga ? 'manga' : 'anime', mediaId),
mediaId: mediaId,
summary: jsonRes['attributes']['synopsis'] ?? "",
totalChapter: (jsonRes['attributes'][totalChapter] ?? 0),
coverUrl: jsonRes['attributes']['posterImage']['original'] ?? "",
title: jsonRes['attributes']['canonicalTitle'],
summary: attributes['synopsis'] ?? "",
totalChapter: (attributes[totalChapter] ?? 0),
coverUrl: attributes['posterImage']?['original'] ?? "",
title: attributes['canonicalTitle'],
startDate: "",
score: score,
publishingType: (jsonRes['attributes']['subtype'] ?? ""),
publishingStatus: jsonRes['attributes']['endDate'] == null
publishingType: (attributes['subtype'] ?? ""),
publishingStatus: attributes['endDate'] == null
? "Publishing"
: "Finished",
);
@ -250,7 +251,7 @@ class Kitsu extends _$Kitsu {
trackingUrl: _mediaUrl(type, id),
summary: included['synopsis'] ?? "",
totalChapter: included[totalChapter] ?? 0,
coverUrl: included['posterImage']['original'] ?? "",
coverUrl: included['posterImage']?['original'] ?? "",
title: included['canonicalTitle'],
startDate: "",
publishingType: (included["subtype"] ?? ""),

View file

@ -159,7 +159,7 @@ class MyAnimeList extends _$MyAnimeList {
mediaId: res["id"],
summary: res["synopsis"] ?? "",
totalChapter: res[contentUnit],
coverUrl: res["main_picture"]["large"] ?? "",
coverUrl: res["main_picture"]?["large"] ?? "",
title: res["title"],
startDate: res["start_date"] ?? "",
publishingType: res["media_type"].toString().replaceAll("_", " "),
@ -197,7 +197,7 @@ class MyAnimeList extends _$MyAnimeList {
mediaId: node["id"],
summary: node["synopsis"] ?? "",
totalChapter: node[contentUnit],
coverUrl: node["main_picture"]["large"] ?? "",
coverUrl: node["main_picture"]?["large"] ?? "",
title: node["title"],
score: (node["mean"] as num?)?.toDouble(),
startDate: node["start_date"] ?? "",
@ -237,7 +237,7 @@ class MyAnimeList extends _$MyAnimeList {
mediaId: node["id"],
summary: node["synopsis"] ?? "",
totalChapter: node[contentUnit],
coverUrl: node["main_picture"]["large"] ?? "",
coverUrl: node["main_picture"]?["large"] ?? "",
title: node["title"],
score: (node["mean"] as num?)?.toDouble(),
startDate: node["start_date"] ?? "",

View file

@ -203,6 +203,7 @@ class DiscordRPC {
}
Future<void> disconnect() async {
if (!FlutterDiscordRPC.instance.isConnected) return;
await FlutterDiscordRPC.instance.disconnect();
}

View file

@ -354,10 +354,10 @@ packages:
dependency: "direct main"
description:
name: d4rt
sha256: "40ecf0b73603a8268050e890eb4d037b5bdf4184f439cc18d593e83b100a19e7"
sha256: "4220081caf1cea231e127a8fd2801b4b55464a51f840b56bb079ce2b3792e9e6"
url: "https://pub.dev"
source: hosted
version: "0.0.9"
version: "0.1.1"
dart_style:
dependency: transitive
description:

View file

@ -1,7 +1,7 @@
name: mangayomi
description: Free and open source manga reader and anime streaming cross-plateform app inspired by Tachiyomi and Aniyomi.
publish_to: "none"
version: 0.6.3+85
version: 0.6.35+86
environment:
sdk: ^3.8.1
@ -91,7 +91,7 @@ dependencies:
git:
url: https://github.com/kodjodevf/epubx.dart.git
ref: dev
d4rt: 0.0.9
d4rt: 0.1.1
hive: ^2.2.3
hive_flutter: ^1.1.0
flutter_discord_rpc_fork:
@ -156,5 +156,3 @@ inno_bundle:
- french
- german
admin: false
version: 0.6.3

View file

@ -14,25 +14,32 @@
"bundleIdentifier": "com.kodjodevf.mangayomi",
"developerName": "Moustapha Kodjo Amadou",
"subtitle": "Read manga, novels, and watch anime",
"version": "0.6.3",
"versionDate": "2025-07-09T14:01:54Z",
"version": "0.6.35",
"versionDate": "2025-08-05T14:52:24Z",
"versionDescription": "",
"downloadURL": "https://github.com/kodjodevf/mangayomi/releases/download/v0.6.3/Mangayomi-v0.6.3-ios.ipa",
"downloadURL": "https://github.com/kodjodevf/mangayomi/releases/download/v0.6.35/Mangayomi-v0.6.35-ios.ipa",
"localizedDescription": "Mangayomi is an open-source Flutter app for reading manga, novels, and watching anime across multiple platforms.",
"iconURL": "https://raw.githubusercontent.com/kodjodevf/mangayomi/refs/heads/main/repo/images/icons/icon_default.webp",
"tintColor": "EF4444",
"category": "entertainment",
"size": 58376897,
"size": 59596142,
"screenshotURLs": [
"https://raw.githubusercontent.com/kodjodevf/mangayomi/refs/heads/main/repo/images/screenshots/image_0_default.webp",
"https://raw.githubusercontent.com/kodjodevf/mangayomi/refs/heads/main/repo/images/screenshots/image_1_default.webp",
"https://raw.githubusercontent.com/kodjodevf/mangayomi/refs/heads/main/repo/images/screenshots/image_2_default.webp"
],
"versions": [
{
"version": "0.6.35",
"date": "2025-08-05T14:52:24Z",
"localizedDescription": "",
"downloadURL": "https://github.com/kodjodevf/mangayomi/releases/download/v0.6.35/Mangayomi-v0.6.35-ios.ipa",
"size": 59596142
},
{
"version": "0.6.3",
"date": "2025-07-09T14:01:54Z",
"localizedDescription": "",
"localizedDescription": "What's Changed\r\n* added option to change hwdec by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/483\r\n* Fixes, Refactors, and Async Improvements by @NBA2K1 in https://github.com/kodjodevf/mangayomi/pull/484\r\n* Fix Async Transaction Bug and Category Management Issues by @NBA2K1 in https://github.com/kodjodevf/mangayomi/pull/489\r\n* Create PKGBUILD for Arch linux by @saberr26 in https://github.com/kodjodevf/mangayomi/pull/485\r\n* added two\u2022way\u2022tracking by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/490\r\n* Refactor: Consolidate _openCategory() Methods into Unified Dialog by @NBA2K1 in https://github.com/kodjodevf/mangayomi/pull/491\r\n* Release file lock by closing InputStream by @NBA2K1 in https://github.com/kodjodevf/mangayomi/pull/493\r\n* increased protobuf limit to 250 MB by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/494\r\n* Make buttons white for consistency in light mode by @NBA2K1 in https://github.com/kodjodevf/mangayomi/pull/496\r\n* improved two\u2022way track screen by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/497\r\n* Don't allow zooming by @NBA2K1 in https://github.com/kodjodevf/mangayomi/pull/502\r\n* adjusted tracker library + bug fixes by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/501\r\n* iOS CBZ import fix by @NBA2K1 in https://github.com/kodjodevf/mangayomi/pull/503\r\n* \ud83c\udfac Improve Fullscreen Stability & Simplify AniSkip Logic by @NBA2K1 in https://github.com/kodjodevf/mangayomi/pull/499\r\n* added option to disable merged library navigation by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/504\r\n* reworked sync server by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/506\r\n* Fix \"Read\" and \"Unread\" button [based on PR 506] by @NBA2K1 in https://github.com/kodjodevf/mangayomi/pull/507\r\n* Fix labels on navigation bar [based on PR 506] by @NBA2K1 in https://github.com/kodjodevf/mangayomi/pull/508\r\n* Refactor code by @NBA2K1 in https://github.com/kodjodevf/mangayomi/pull/509\r\n* Fix menu colors on theme change by @NBA2K1 in https://github.com/kodjodevf/mangayomi/pull/511\r\n* Fix delete \"Downloaded episodes/chapters\" by @NBA2K1 in https://github.com/kodjodevf/mangayomi/pull/512\r\n* Add tracker button to localArchive by @NBA2K1 in https://github.com/kodjodevf/mangayomi/pull/513\r\n* Changed the \"Page preload amount\" Setting to be a Slider by @NBA2K1 in https://github.com/kodjodevf/mangayomi/pull/510\r\n\r\nNew Contributors\r\n* @saberr26 made their first contribution in https://github.com/kodjodevf/mangayomi/pull/485\r\n\r\n**Full Changelog**: https://github.com/kodjodevf/mangayomi/compare/v0.6.25...v0.6.3",
"downloadURL": "https://github.com/kodjodevf/mangayomi/releases/download/v0.6.3/Mangayomi-v0.6.3-ios.ipa",
"size": 58376897
},
@ -171,6 +178,17 @@
"imageURL": "https://raw.githubusercontent.com/kodjodevf/mangayomi/refs/heads/main/repo/images/news/update_default.webp",
"notify": true,
"url": "https://github.com/kodjodevf/mangayomi/releases/tag/v0.6.3"
},
{
"appID": "com.kodjodevf.mangayomi",
"title": "0.6.35 - 05 Aug",
"identifier": "release-0.6.35",
"caption": "Update for Mangayomi now available!",
"date": "2025-08-05T14:52:24Z",
"tintColor": "EF4444",
"imageURL": "https://raw.githubusercontent.com/kodjodevf/mangayomi/refs/heads/main/repo/images/news/update_default.webp",
"notify": true,
"url": "https://github.com/kodjodevf/mangayomi/releases/tag/v0.6.35"
}
]
}