mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-04-20 23:22:07 +00:00
Merge remote-tracking branch 'upstream/main'
This commit is contained in:
commit
00e49e31a0
58 changed files with 5379 additions and 299 deletions
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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
279
lib/l10n/app_as.arb
Normal 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": "এক্সটেনশন দেখুৱাওক"
|
||||
}
|
||||
|
|
@ -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
279
lib/l10n/app_hi.arb
Normal 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": "एक्सटेंशन दिखाएँ"
|
||||
}
|
||||
|
|
@ -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':
|
||||
|
|
|
|||
|
|
@ -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 => 'تمكين المزامنة';
|
||||
|
||||
|
|
|
|||
1513
lib/l10n/generated/app_localizations_as.dart
Normal file
1513
lib/l10n/generated/app_localizations_as.dart
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
1514
lib/l10n/generated/app_localizations_hi.dart
Normal file
1514
lib/l10n/generated/app_localizations_hi.dart
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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 => 'Включить синхронизацию';
|
||||
|
||||
|
|
|
|||
|
|
@ -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 => 'เปิดการซิงค์';
|
||||
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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 => '启用同步';
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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});
|
||||
|
||||
|
|
|
|||
|
|
@ -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: [
|
||||
|
|
|
|||
383
lib/modules/manga/detail/widgets/recommendation_screen.dart
Normal file
383
lib/modules/manga/detail/widgets/recommendation_screen.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ part of 'incognito_mode_state_provider.dart';
|
|||
// **************************************************************************
|
||||
|
||||
String _$incognitoModeStateHash() =>
|
||||
r'149c4dcbc434fb6efc883e196392320bdc7c0821';
|
||||
r'3858256a820eef632d3df57533f2aad14f555b22';
|
||||
|
||||
/// See also [IncognitoModeState].
|
||||
@ProviderFor(IncognitoModeState)
|
||||
|
|
|
|||
|
|
@ -61,7 +61,6 @@ class _CustomNavigationSettingsState
|
|||
[
|
||||
"/more",
|
||||
"/browse",
|
||||
"/history",
|
||||
].any((element) => element == navigation)
|
||||
? null
|
||||
: (value) {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
29
lib/modules/more/widgets/downloaded_only_widget.dart
Normal file
29
lib/modules/more/widgets/downloaded_only_widget.dart
Normal 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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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>({
|
||||
|
|
|
|||
146
lib/services/recommendation.dart
Normal file
146
lib/services/recommendation.dart
Normal 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>() ?? [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"] ?? ""),
|
||||
|
|
|
|||
|
|
@ -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"] ?? "",
|
||||
|
|
|
|||
|
|
@ -203,6 +203,7 @@ class DiscordRPC {
|
|||
}
|
||||
|
||||
Future<void> disconnect() async {
|
||||
if (!FlutterDiscordRPC.instance.isConnected) return;
|
||||
await FlutterDiscordRPC.instance.disconnect();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Reference in a new issue