mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-04-20 23:22:07 +00:00
Merge pull request #560 from Schnitzel5/feature/subtitle-search
added subtitles search
This commit is contained in:
commit
a061129b86
20 changed files with 617 additions and 3 deletions
|
|
@ -52,7 +52,7 @@ android {
|
|||
applicationId "com.kodjodevf.mangayomi"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
||||
minSdkVersion 21
|
||||
minSdkVersion flutter.minSdkVersion
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
|
|
@ -84,4 +84,4 @@ flutter {
|
|||
|
||||
dependencies {
|
||||
implementation(name: 'libmtorrentserver', ext: 'aar')
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -468,6 +468,7 @@
|
|||
"genre_search_source": "Browse in source",
|
||||
"source_not_added": "Source is not installed!",
|
||||
"load_own_subtitles": "Load your own subtitles...",
|
||||
"search_subtitles": "Search subtitles online...",
|
||||
"extension_notes": "Notes: {notes}",
|
||||
"unsupported_repo": "You've tried to add an unsupported repository. Please check the discord server for support!",
|
||||
"end_of_chapter": "End of chapter",
|
||||
|
|
|
|||
|
|
@ -2883,6 +2883,12 @@ abstract class AppLocalizations {
|
|||
/// **'Load your own subtitles...'**
|
||||
String get load_own_subtitles;
|
||||
|
||||
/// No description provided for @search_subtitles.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Search subtitles online...'**
|
||||
String get search_subtitles;
|
||||
|
||||
/// No description provided for @extension_notes.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
|
|||
|
|
@ -1480,6 +1480,9 @@ class AppLocalizationsAr extends AppLocalizations {
|
|||
@override
|
||||
String get load_own_subtitles => 'تحميل الترجمة الخاصة بك...';
|
||||
|
||||
@override
|
||||
String get search_subtitles => 'Search subtitles online...';
|
||||
|
||||
@override
|
||||
String extension_notes(Object notes) {
|
||||
return 'Notes: $notes';
|
||||
|
|
|
|||
|
|
@ -1482,6 +1482,9 @@ class AppLocalizationsAs extends AppLocalizations {
|
|||
@override
|
||||
String get load_own_subtitles => 'Load your own subtitles...';
|
||||
|
||||
@override
|
||||
String get search_subtitles => 'Search subtitles online...';
|
||||
|
||||
@override
|
||||
String extension_notes(Object notes) {
|
||||
return 'Notes: $notes';
|
||||
|
|
|
|||
|
|
@ -1491,6 +1491,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get load_own_subtitles => 'Deine eigene Untertiteln laden...';
|
||||
|
||||
@override
|
||||
String get search_subtitles => 'Search subtitles online...';
|
||||
|
||||
@override
|
||||
String extension_notes(Object notes) {
|
||||
return 'Hinweis: $notes';
|
||||
|
|
|
|||
|
|
@ -1481,6 +1481,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get load_own_subtitles => 'Load your own subtitles...';
|
||||
|
||||
@override
|
||||
String get search_subtitles => 'Search subtitles online...';
|
||||
|
||||
@override
|
||||
String extension_notes(Object notes) {
|
||||
return 'Notes: $notes';
|
||||
|
|
|
|||
|
|
@ -1498,6 +1498,9 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||
@override
|
||||
String get load_own_subtitles => 'Cargar tus propios subtítulos...';
|
||||
|
||||
@override
|
||||
String get search_subtitles => 'Search subtitles online...';
|
||||
|
||||
@override
|
||||
String extension_notes(Object notes) {
|
||||
return 'Notes: $notes';
|
||||
|
|
|
|||
|
|
@ -1498,6 +1498,9 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||
@override
|
||||
String get load_own_subtitles => 'Charger vos propres sous-titres...';
|
||||
|
||||
@override
|
||||
String get search_subtitles => 'Search subtitles online...';
|
||||
|
||||
@override
|
||||
String extension_notes(Object notes) {
|
||||
return 'Notes: $notes';
|
||||
|
|
|
|||
|
|
@ -1483,6 +1483,9 @@ class AppLocalizationsHi extends AppLocalizations {
|
|||
@override
|
||||
String get load_own_subtitles => 'Load your own subtitles...';
|
||||
|
||||
@override
|
||||
String get search_subtitles => 'Search subtitles online...';
|
||||
|
||||
@override
|
||||
String extension_notes(Object notes) {
|
||||
return 'Notes: $notes';
|
||||
|
|
|
|||
|
|
@ -1487,6 +1487,9 @@ class AppLocalizationsId extends AppLocalizations {
|
|||
@override
|
||||
String get load_own_subtitles => 'Muat subtitle Anda sendiri...';
|
||||
|
||||
@override
|
||||
String get search_subtitles => 'Search subtitles online...';
|
||||
|
||||
@override
|
||||
String extension_notes(Object notes) {
|
||||
return 'Notes: $notes';
|
||||
|
|
|
|||
|
|
@ -1496,6 +1496,9 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||
@override
|
||||
String get load_own_subtitles => 'Carica i tuoi sottotitoli...';
|
||||
|
||||
@override
|
||||
String get search_subtitles => 'Search subtitles online...';
|
||||
|
||||
@override
|
||||
String extension_notes(Object notes) {
|
||||
return 'Notes: $notes';
|
||||
|
|
|
|||
|
|
@ -1495,6 +1495,9 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||
@override
|
||||
String get load_own_subtitles => 'Carregar suas próprias legendas...';
|
||||
|
||||
@override
|
||||
String get search_subtitles => 'Search subtitles online...';
|
||||
|
||||
@override
|
||||
String extension_notes(Object notes) {
|
||||
return 'Notes: $notes';
|
||||
|
|
|
|||
|
|
@ -1497,6 +1497,9 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||
@override
|
||||
String get load_own_subtitles => 'Загрузить свои собственные субтитры...';
|
||||
|
||||
@override
|
||||
String get search_subtitles => 'Search subtitles online...';
|
||||
|
||||
@override
|
||||
String extension_notes(Object notes) {
|
||||
return 'Notes: $notes';
|
||||
|
|
|
|||
|
|
@ -1481,6 +1481,9 @@ class AppLocalizationsTh extends AppLocalizations {
|
|||
@override
|
||||
String get load_own_subtitles => 'โหลดคำบรรยายของคุณเอง...';
|
||||
|
||||
@override
|
||||
String get search_subtitles => 'Search subtitles online...';
|
||||
|
||||
@override
|
||||
String extension_notes(Object notes) {
|
||||
return 'Notes: $notes';
|
||||
|
|
|
|||
|
|
@ -1487,6 +1487,9 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||
@override
|
||||
String get load_own_subtitles => 'Kendi altyazılarınızı yükleyin...';
|
||||
|
||||
@override
|
||||
String get search_subtitles => 'Search subtitles online...';
|
||||
|
||||
@override
|
||||
String extension_notes(Object notes) {
|
||||
return 'Notes: $notes';
|
||||
|
|
|
|||
|
|
@ -1453,6 +1453,9 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||
@override
|
||||
String get load_own_subtitles => '加载自定义字幕';
|
||||
|
||||
@override
|
||||
String get search_subtitles => 'Search subtitles online...';
|
||||
|
||||
@override
|
||||
String extension_notes(Object notes) {
|
||||
return 'Notes: $notes';
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import 'package:mangayomi/modules/widgets/progress_center.dart';
|
|||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/providers/storage_provider.dart';
|
||||
import 'package:mangayomi/services/aniskip.dart';
|
||||
import 'package:mangayomi/services/fetch_subtitles.dart';
|
||||
import 'package:mangayomi/services/get_video_list.dart';
|
||||
import 'package:mangayomi/services/torrent_server.dart';
|
||||
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
|
||||
|
|
@ -52,6 +53,8 @@ import 'package:path_provider/path_provider.dart';
|
|||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:super_sliver_list/super_sliver_list.dart';
|
||||
|
||||
import 'widgets/search_subtitles.dart';
|
||||
|
||||
bool _isDesktop = Platform.isMacOS || Platform.isLinux || Platform.isWindows;
|
||||
|
||||
class AnimePlayerView extends riv.ConsumerStatefulWidget {
|
||||
|
|
@ -1296,7 +1299,7 @@ mp.register_script_message('call_button_${button.id}_long', button${button.id}lo
|
|||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
const SizedBox(height: 30),
|
||||
...videoSubtitleLast.toSet().toList().map((sub) {
|
||||
final title =
|
||||
sub.title ??
|
||||
|
|
@ -1322,6 +1325,7 @@ mp.register_script_message('call_button_${button.id}_long', button${button.id}lo
|
|||
child: textWidget(title, selected),
|
||||
);
|
||||
}),
|
||||
const SizedBox(height: 30),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
try {
|
||||
|
|
@ -1343,6 +1347,34 @@ mp.register_script_message('call_button_${button.id}_long', button${button.id}lo
|
|||
},
|
||||
child: textWidget(context.l10n.load_own_subtitles, false),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
try {
|
||||
final subtitle =
|
||||
await subtitlesSearchraggableMenu(
|
||||
context,
|
||||
chapter: widget.episode,
|
||||
)
|
||||
as ImdbSubtitle?;
|
||||
if (subtitle != null && context.mounted) {
|
||||
_player.setSubtitleTrack(
|
||||
SubtitleTrack.uri(
|
||||
subtitle.url!,
|
||||
title: subtitle.language,
|
||||
language: subtitle.language,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
Navigator.pop(context);
|
||||
} catch (_) {
|
||||
botToast("Error");
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
child: textWidget(context.l10n.search_subtitles, false),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
362
lib/modules/anime/widgets/search_subtitles.dart
Normal file
362
lib/modules/anime/widgets/search_subtitles.dart
Normal file
|
|
@ -0,0 +1,362 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:mangayomi/models/chapter.dart';
|
||||
import 'package:mangayomi/modules/widgets/custom_extended_image_provider.dart';
|
||||
import 'package:mangayomi/modules/widgets/error_text.dart';
|
||||
import 'package:mangayomi/modules/widgets/progress_center.dart';
|
||||
import 'package:mangayomi/services/fetch_subtitles.dart';
|
||||
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
|
||||
import 'package:super_sliver_list/super_sliver_list.dart';
|
||||
|
||||
class SubtitlesWidgetSearch extends ConsumerStatefulWidget {
|
||||
final Chapter chapter;
|
||||
const SubtitlesWidgetSearch({required this.chapter, super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<SubtitlesWidgetSearch> createState() =>
|
||||
_SubtitlesWidgetSearchState();
|
||||
}
|
||||
|
||||
class _SubtitlesWidgetSearchState extends ConsumerState<SubtitlesWidgetSearch> {
|
||||
late final _controller = TextEditingController(text: query);
|
||||
List<ImdbTitle> titles = [];
|
||||
List<ImdbEpisode>? episodes;
|
||||
List<ImdbSubtitle>? subtitles;
|
||||
late String query = widget.chapter.manga.value?.name?.trim() ?? "";
|
||||
bool hide = false;
|
||||
bool _isLoading = true;
|
||||
String? _errorMsg;
|
||||
|
||||
@override
|
||||
initState() {
|
||||
super.initState();
|
||||
_init();
|
||||
}
|
||||
|
||||
_init() async {
|
||||
await Future.delayed(const Duration(microseconds: 100));
|
||||
try {
|
||||
titles = await fetchImdbTitles(query);
|
||||
} catch (e) {
|
||||
_errorMsg = e.toString();
|
||||
}
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(20),
|
||||
bottomRight: Radius.circular(20),
|
||||
),
|
||||
clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||
child: _isLoading
|
||||
? SizedBox(
|
||||
height: context.height(0.3),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: const ProgressCenter(),
|
||||
),
|
||||
)
|
||||
: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: SizedBox(
|
||||
height: context.height(0.8),
|
||||
child: Column(
|
||||
mainAxisAlignment: _errorMsg != null
|
||||
? MainAxisAlignment.center
|
||||
: MainAxisAlignment.start,
|
||||
children: [
|
||||
if (subtitles != null || episodes != null)
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
if (subtitles != null) {
|
||||
subtitles = null;
|
||||
} else if (episodes != null) {
|
||||
episodes = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
icon: const Icon(Icons.keyboard_arrow_left),
|
||||
),
|
||||
if (_errorMsg != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(30),
|
||||
child: ErrorText(_errorMsg!),
|
||||
),
|
||||
if (_errorMsg == null && !hide)
|
||||
Flexible(child: _showImdbList(context)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: TextFormField(
|
||||
onTap: () {
|
||||
if (Platform.isAndroid || Platform.isIOS) {
|
||||
setState(() {
|
||||
hide = true;
|
||||
});
|
||||
}
|
||||
},
|
||||
controller: _controller,
|
||||
keyboardType: TextInputType.text,
|
||||
onChanged: (d) {
|
||||
setState(() {
|
||||
query = d;
|
||||
});
|
||||
},
|
||||
onFieldSubmitted: (d) async {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_errorMsg = null;
|
||||
subtitles = null;
|
||||
episodes = null;
|
||||
});
|
||||
try {
|
||||
titles = await fetchImdbTitles(query);
|
||||
} catch (e) {
|
||||
_errorMsg = e.toString();
|
||||
hide = false;
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
hide = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
filled: true,
|
||||
fillColor: Colors.transparent,
|
||||
suffixIcon: query.isEmpty
|
||||
? null
|
||||
: IconButton(
|
||||
onPressed: () {
|
||||
_controller.clear();
|
||||
},
|
||||
icon: const Icon(Icons.clear),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: context.primaryColor),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: context.primaryColor),
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: context.primaryColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _showImdbList(BuildContext context) {
|
||||
return SuperListView.separated(
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
itemCount: subtitles?.length ?? episodes?.length ?? titles.length,
|
||||
itemBuilder: (context, index) {
|
||||
final isSubtitles = subtitles != null;
|
||||
final isEpisodes = episodes != null;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 5),
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
if (isSubtitles) {
|
||||
Navigator.pop(context, subtitles![index]);
|
||||
} else {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_errorMsg = null;
|
||||
});
|
||||
try {
|
||||
if (isEpisodes) {
|
||||
subtitles = await fetchImdbSubtitles(episodes![index].id);
|
||||
} else {
|
||||
episodes = await fetchImdbEpisodes(titles[index].id);
|
||||
if (episodes == null || episodes!.isEmpty) {
|
||||
subtitles = await fetchImdbSubtitles(titles[index].id);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
_errorMsg = e.toString();
|
||||
}
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (!isSubtitles && !isEpisodes)
|
||||
Material(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
color: Colors.transparent,
|
||||
clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||
child: Ink.image(
|
||||
height: 120,
|
||||
width: 80,
|
||||
fit: BoxFit.cover,
|
||||
image: titles[index].primaryImage != null
|
||||
? CustomExtendedNetworkImageProvider(
|
||||
titles[index].primaryImage!,
|
||||
)
|
||||
: const AssetImage('assets/transparent.png'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: context.width(0.6),
|
||||
child: Text(
|
||||
isSubtitles
|
||||
? "${subtitles![index].name} (${subtitles![index].displayLang}) - ${subtitles![index].format?.toUpperCase() ?? "Unknown"} - ${subtitles![index].encoding ?? "Unknown"}"
|
||||
: isEpisodes
|
||||
? "S${episodes![index].season}E${episodes![index].episode}: ${episodes![index].title}"
|
||||
: titles[index].primaryTitle,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
if (!isSubtitles && !isEpisodes)
|
||||
Row(
|
||||
children: [
|
||||
const Text(
|
||||
"Rating : ",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
titles[index].aggregateRating?.toStringAsFixed(
|
||||
2,
|
||||
) ??
|
||||
"?",
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (!isSubtitles && !isEpisodes)
|
||||
Row(
|
||||
children: [
|
||||
const Text(
|
||||
"Votes : ",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
titles[index].voteCount?.toString() ?? "?",
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (!isSubtitles && !isEpisodes)
|
||||
Row(
|
||||
children: [
|
||||
const Text(
|
||||
"Date : ",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"${titles[index].startYear?.toString() ?? "?"} - ${titles[index].endYear?.toString() ?? "?"}",
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (BuildContext context, int index) {
|
||||
return const Divider();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
subtitlesSearchraggableMenu(
|
||||
BuildContext context, {
|
||||
required Chapter chapter,
|
||||
}) async {
|
||||
var padding = MediaQuery.of(context).padding;
|
||||
return await showDialog(
|
||||
context: context,
|
||||
builder: (context) => Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
body: SingleChildScrollView(
|
||||
child: SizedBox(
|
||||
height: context.height(1) - padding.top - padding.bottom,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(20),
|
||||
topRight: Radius.circular(20),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
icon: const Icon(Icons.clear),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SubtitlesWidgetSearch(chapter: chapter),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
171
lib/services/fetch_subtitles.dart
Normal file
171
lib/services/fetch_subtitles.dart
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
import 'dart:convert';
|
||||
import 'package:mangayomi/services/http/m_client.dart';
|
||||
|
||||
Future<List<ImdbTitle>> fetchImdbTitles(String query) async {
|
||||
final http = MClient.init(reqcopyWith: {'useDartHttpClient': true});
|
||||
try {
|
||||
final url = "https://api.imdbapi.dev/search/titles?query=$query";
|
||||
final res = await http.get(
|
||||
Uri.parse(url),
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"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>;
|
||||
return (data["titles"] as List?)
|
||||
?.map((e) => ImdbTitle.fromJson(e))
|
||||
.toList() ??
|
||||
[];
|
||||
} catch (_) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<ImdbEpisode>?> fetchImdbEpisodes(String imdbId) async {
|
||||
final http = MClient.init(reqcopyWith: {'useDartHttpClient': true});
|
||||
try {
|
||||
final url = "https://api.imdbapi.dev/titles/$imdbId/episodes";
|
||||
final res = await http.get(
|
||||
Uri.parse(url),
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"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>;
|
||||
return (data["episodes"] as List?)
|
||||
?.map((e) => ImdbEpisode.fromJson(e))
|
||||
.toList();
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<ImdbSubtitle>?> fetchImdbSubtitles(String imdbId) async {
|
||||
final http = MClient.init(reqcopyWith: {'useDartHttpClient': true});
|
||||
try {
|
||||
final url = "https://sub.wyzie.ru/search?id=$imdbId";
|
||||
final res = await http.get(
|
||||
Uri.parse(url),
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"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 List?;
|
||||
return data
|
||||
?.map((e) => ImdbSubtitle.fromJson(e))
|
||||
.where((e) => e.url != null)
|
||||
.toList();
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class ImdbTitle {
|
||||
final String id;
|
||||
final String? type;
|
||||
final String primaryTitle;
|
||||
final String? originalTitle;
|
||||
final String? primaryImage;
|
||||
final int? startYear;
|
||||
final int? endYear;
|
||||
final double? aggregateRating;
|
||||
final int? voteCount;
|
||||
|
||||
ImdbTitle({
|
||||
required this.id,
|
||||
this.type,
|
||||
required this.primaryTitle,
|
||||
this.originalTitle,
|
||||
this.primaryImage,
|
||||
this.startYear,
|
||||
this.endYear,
|
||||
this.aggregateRating,
|
||||
this.voteCount,
|
||||
});
|
||||
|
||||
factory ImdbTitle.fromJson(Map<String, dynamic> json) {
|
||||
return ImdbTitle(
|
||||
id: json["id"],
|
||||
type: json["type"],
|
||||
primaryTitle: json["primaryTitle"] ?? "???",
|
||||
originalTitle: json["originalTitle"],
|
||||
primaryImage: json["primaryImage"]?["url"],
|
||||
startYear: json["startYear"],
|
||||
endYear: json["endYear"],
|
||||
aggregateRating: json["rating"]?["aggregateRating"] is int
|
||||
? (json["rating"]?["aggregateRating"] as int).toDouble()
|
||||
: json["rating"]?["aggregateRating"],
|
||||
voteCount: json["rating"]?["voteCount"],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ImdbEpisode {
|
||||
final String id;
|
||||
final String title;
|
||||
final String? primaryImage;
|
||||
final String season;
|
||||
final String episode;
|
||||
|
||||
ImdbEpisode({
|
||||
required this.id,
|
||||
required this.title,
|
||||
this.primaryImage,
|
||||
required this.season,
|
||||
required this.episode,
|
||||
});
|
||||
|
||||
factory ImdbEpisode.fromJson(Map<String, dynamic> json) {
|
||||
return ImdbEpisode(
|
||||
id: json["id"],
|
||||
title: json["title"] ?? "???",
|
||||
primaryImage: json["primaryImage"]?["url"],
|
||||
season: json["season"] ?? "?",
|
||||
episode: (json["episodeNumber"] as int?)?.toString() ?? "?",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ImdbSubtitle {
|
||||
final String id;
|
||||
final String? url;
|
||||
final String? flagUrl;
|
||||
final String? format;
|
||||
final String? encoding;
|
||||
final String? displayLang;
|
||||
final String? language;
|
||||
final String? name;
|
||||
final bool isHearingImpaired;
|
||||
|
||||
ImdbSubtitle({
|
||||
required this.id,
|
||||
this.url,
|
||||
this.flagUrl,
|
||||
this.format,
|
||||
this.encoding,
|
||||
this.displayLang,
|
||||
this.language,
|
||||
this.name,
|
||||
required this.isHearingImpaired,
|
||||
});
|
||||
|
||||
factory ImdbSubtitle.fromJson(Map<String, dynamic> json) {
|
||||
return ImdbSubtitle(
|
||||
id: json["id"],
|
||||
url: json["url"],
|
||||
flagUrl: json["flagUrl"],
|
||||
format: json["format"],
|
||||
encoding: json["encoding"],
|
||||
displayLang: json["display"],
|
||||
language: json["language"],
|
||||
name: json["media"],
|
||||
isHearingImpaired: json["isHearingImpaired"] ?? false,
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue