mirror of
https://gitlab.com/foxixus/neomovies_mobile.git
synced 2025-10-28 00:38:50 +05:00
1. Обжариваем лук до золотистого цвета. 2. Добавляем морковь — жарим до мягкости. 3. Всыпаем нарезанное мясо, жарим до румяной корочки. 4. Добавляем специи: зиру, барбарис, соль. 5. Засыпаем промытый рис, сверху — головка чеснока. 6. Заливаем кипятком на 1-2 см выше риса. 7. Готовим под крышкой на слабом огне до испарения воды.
494 lines
14 KiB
Dart
494 lines
14 KiB
Dart
import 'dart:convert';
|
||
import 'package:flutter/services.dart';
|
||
|
||
/// Data classes for torrent metadata (matching Kotlin side)
|
||
|
||
/// Базовая информация из magnet-ссылки
|
||
class MagnetBasicInfo {
|
||
final String name;
|
||
final String infoHash;
|
||
final List<String> trackers;
|
||
final int totalSize;
|
||
|
||
MagnetBasicInfo({
|
||
required this.name,
|
||
required this.infoHash,
|
||
required this.trackers,
|
||
this.totalSize = 0,
|
||
});
|
||
|
||
factory MagnetBasicInfo.fromJson(Map<String, dynamic> json) {
|
||
return MagnetBasicInfo(
|
||
name: json['name'] as String,
|
||
infoHash: json['infoHash'] as String,
|
||
trackers: List<String>.from(json['trackers'] as List),
|
||
totalSize: json['totalSize'] as int? ?? 0,
|
||
);
|
||
}
|
||
|
||
Map<String, dynamic> toJson() {
|
||
return {
|
||
'name': name,
|
||
'infoHash': infoHash,
|
||
'trackers': trackers,
|
||
'totalSize': totalSize,
|
||
};
|
||
}
|
||
}
|
||
|
||
/// Информация о файле в торренте
|
||
class FileInfo {
|
||
final String name;
|
||
final String path;
|
||
final int size;
|
||
final int index;
|
||
final String extension;
|
||
final bool isVideo;
|
||
final bool isAudio;
|
||
final bool isImage;
|
||
final bool isDocument;
|
||
final bool selected;
|
||
|
||
FileInfo({
|
||
required this.name,
|
||
required this.path,
|
||
required this.size,
|
||
required this.index,
|
||
this.extension = '',
|
||
this.isVideo = false,
|
||
this.isAudio = false,
|
||
this.isImage = false,
|
||
this.isDocument = false,
|
||
this.selected = false,
|
||
});
|
||
|
||
factory FileInfo.fromJson(Map<String, dynamic> json) {
|
||
return FileInfo(
|
||
name: json['name'] as String,
|
||
path: json['path'] as String,
|
||
size: json['size'] as int,
|
||
index: json['index'] as int,
|
||
extension: json['extension'] as String? ?? '',
|
||
isVideo: json['isVideo'] as bool? ?? false,
|
||
isAudio: json['isAudio'] as bool? ?? false,
|
||
isImage: json['isImage'] as bool? ?? false,
|
||
isDocument: json['isDocument'] as bool? ?? false,
|
||
selected: json['selected'] as bool? ?? false,
|
||
);
|
||
}
|
||
|
||
Map<String, dynamic> toJson() {
|
||
return {
|
||
'name': name,
|
||
'path': path,
|
||
'size': size,
|
||
'index': index,
|
||
'extension': extension,
|
||
'isVideo': isVideo,
|
||
'isAudio': isAudio,
|
||
'isImage': isImage,
|
||
'isDocument': isDocument,
|
||
'selected': selected,
|
||
};
|
||
}
|
||
|
||
FileInfo copyWith({
|
||
String? name,
|
||
String? path,
|
||
int? size,
|
||
int? index,
|
||
String? extension,
|
||
bool? isVideo,
|
||
bool? isAudio,
|
||
bool? isImage,
|
||
bool? isDocument,
|
||
bool? selected,
|
||
}) {
|
||
return FileInfo(
|
||
name: name ?? this.name,
|
||
path: path ?? this.path,
|
||
size: size ?? this.size,
|
||
index: index ?? this.index,
|
||
extension: extension ?? this.extension,
|
||
isVideo: isVideo ?? this.isVideo,
|
||
isAudio: isAudio ?? this.isAudio,
|
||
isImage: isImage ?? this.isImage,
|
||
isDocument: isDocument ?? this.isDocument,
|
||
selected: selected ?? this.selected,
|
||
);
|
||
}
|
||
}
|
||
|
||
/// Узел директории
|
||
class DirectoryNode {
|
||
final String name;
|
||
final String path;
|
||
final List<FileInfo> files;
|
||
final List<DirectoryNode> subdirectories;
|
||
final int totalSize;
|
||
final int fileCount;
|
||
|
||
DirectoryNode({
|
||
required this.name,
|
||
required this.path,
|
||
required this.files,
|
||
required this.subdirectories,
|
||
required this.totalSize,
|
||
required this.fileCount,
|
||
});
|
||
|
||
factory DirectoryNode.fromJson(Map<String, dynamic> json) {
|
||
return DirectoryNode(
|
||
name: json['name'] as String,
|
||
path: json['path'] as String,
|
||
files: (json['files'] as List)
|
||
.map((file) => FileInfo.fromJson(file as Map<String, dynamic>))
|
||
.toList(),
|
||
subdirectories: (json['subdirectories'] as List)
|
||
.map((dir) => DirectoryNode.fromJson(dir as Map<String, dynamic>))
|
||
.toList(),
|
||
totalSize: json['totalSize'] as int,
|
||
fileCount: json['fileCount'] as int,
|
||
);
|
||
}
|
||
}
|
||
|
||
/// Структура файлов торрента
|
||
class FileStructure {
|
||
final DirectoryNode rootDirectory;
|
||
final int totalFiles;
|
||
final Map<String, int> filesByType;
|
||
|
||
FileStructure({
|
||
required this.rootDirectory,
|
||
required this.totalFiles,
|
||
required this.filesByType,
|
||
});
|
||
|
||
factory FileStructure.fromJson(Map<String, dynamic> json) {
|
||
return FileStructure(
|
||
rootDirectory: DirectoryNode.fromJson(json['rootDirectory'] as Map<String, dynamic>),
|
||
totalFiles: json['totalFiles'] as int,
|
||
filesByType: Map<String, int>.from(json['filesByType'] as Map),
|
||
);
|
||
}
|
||
}
|
||
|
||
/// Полные метаданные торрента
|
||
class TorrentMetadataFull {
|
||
final String name;
|
||
final String infoHash;
|
||
final int totalSize;
|
||
final int pieceLength;
|
||
final int numPieces;
|
||
final FileStructure fileStructure;
|
||
final List<String> trackers;
|
||
final int creationDate;
|
||
final String comment;
|
||
final String createdBy;
|
||
|
||
TorrentMetadataFull({
|
||
required this.name,
|
||
required this.infoHash,
|
||
required this.totalSize,
|
||
required this.pieceLength,
|
||
required this.numPieces,
|
||
required this.fileStructure,
|
||
required this.trackers,
|
||
required this.creationDate,
|
||
required this.comment,
|
||
required this.createdBy,
|
||
});
|
||
|
||
factory TorrentMetadataFull.fromJson(Map<String, dynamic> json) {
|
||
return TorrentMetadataFull(
|
||
name: json['name'] as String,
|
||
infoHash: json['infoHash'] as String,
|
||
totalSize: json['totalSize'] as int,
|
||
pieceLength: json['pieceLength'] as int,
|
||
numPieces: json['numPieces'] as int,
|
||
fileStructure: FileStructure.fromJson(json['fileStructure'] as Map<String, dynamic>),
|
||
trackers: List<String>.from(json['trackers'] as List),
|
||
creationDate: json['creationDate'] as int,
|
||
comment: json['comment'] as String,
|
||
createdBy: json['createdBy'] as String,
|
||
);
|
||
}
|
||
|
||
/// Получить плоский список всех файлов
|
||
List<FileInfo> getAllFiles() {
|
||
final List<FileInfo> allFiles = [];
|
||
_collectFiles(fileStructure.rootDirectory, allFiles);
|
||
return allFiles;
|
||
}
|
||
|
||
void _collectFiles(DirectoryNode directory, List<FileInfo> result) {
|
||
result.addAll(directory.files);
|
||
for (final subdir in directory.subdirectories) {
|
||
_collectFiles(subdir, result);
|
||
}
|
||
}
|
||
}
|
||
|
||
class TorrentFileInfo {
|
||
final String path;
|
||
final int size;
|
||
final bool selected;
|
||
|
||
TorrentFileInfo({
|
||
required this.path,
|
||
required this.size,
|
||
this.selected = false,
|
||
});
|
||
|
||
factory TorrentFileInfo.fromJson(Map<String, dynamic> json) {
|
||
return TorrentFileInfo(
|
||
path: json['path'] as String,
|
||
size: json['size'] as int,
|
||
selected: json['selected'] as bool? ?? false,
|
||
);
|
||
}
|
||
|
||
Map<String, dynamic> toJson() {
|
||
return {
|
||
'path': path,
|
||
'size': size,
|
||
'selected': selected,
|
||
};
|
||
}
|
||
|
||
TorrentFileInfo copyWith({
|
||
String? path,
|
||
int? size,
|
||
bool? selected,
|
||
}) {
|
||
return TorrentFileInfo(
|
||
path: path ?? this.path,
|
||
size: size ?? this.size,
|
||
selected: selected ?? this.selected,
|
||
);
|
||
}
|
||
}
|
||
|
||
class TorrentMetadata {
|
||
final String name;
|
||
final int totalSize;
|
||
final List<TorrentFileInfo> files;
|
||
final String infoHash;
|
||
|
||
TorrentMetadata({
|
||
required this.name,
|
||
required this.totalSize,
|
||
required this.files,
|
||
required this.infoHash,
|
||
});
|
||
|
||
factory TorrentMetadata.fromJson(Map<String, dynamic> json) {
|
||
return TorrentMetadata(
|
||
name: json['name'] as String,
|
||
totalSize: json['totalSize'] as int,
|
||
files: (json['files'] as List)
|
||
.map((file) => TorrentFileInfo.fromJson(file as Map<String, dynamic>))
|
||
.toList(),
|
||
infoHash: json['infoHash'] as String,
|
||
);
|
||
}
|
||
|
||
Map<String, dynamic> toJson() {
|
||
return {
|
||
'name': name,
|
||
'totalSize': totalSize,
|
||
'files': files.map((file) => file.toJson()).toList(),
|
||
'infoHash': infoHash,
|
||
};
|
||
}
|
||
}
|
||
|
||
class DownloadProgress {
|
||
final String infoHash;
|
||
final double progress;
|
||
final int downloadRate;
|
||
final int uploadRate;
|
||
final int numSeeds;
|
||
final int numPeers;
|
||
final String state;
|
||
|
||
DownloadProgress({
|
||
required this.infoHash,
|
||
required this.progress,
|
||
required this.downloadRate,
|
||
required this.uploadRate,
|
||
required this.numSeeds,
|
||
required this.numPeers,
|
||
required this.state,
|
||
});
|
||
|
||
factory DownloadProgress.fromJson(Map<String, dynamic> json) {
|
||
return DownloadProgress(
|
||
infoHash: json['infoHash'] as String,
|
||
progress: (json['progress'] as num).toDouble(),
|
||
downloadRate: json['downloadRate'] as int,
|
||
uploadRate: json['uploadRate'] as int,
|
||
numSeeds: json['numSeeds'] as int,
|
||
numPeers: json['numPeers'] as int,
|
||
state: json['state'] as String,
|
||
);
|
||
}
|
||
}
|
||
|
||
/// Platform service for torrent operations using jlibtorrent on Android
|
||
class TorrentPlatformService {
|
||
static const MethodChannel _channel = MethodChannel('com.neo.neomovies_mobile/torrent');
|
||
|
||
/// Получить базовую информацию из magnet-ссылки
|
||
static Future<MagnetBasicInfo> parseMagnetBasicInfo(String magnetUri) async {
|
||
try {
|
||
final String result = await _channel.invokeMethod('parseMagnetBasicInfo', {
|
||
'magnetUri': magnetUri,
|
||
});
|
||
|
||
final Map<String, dynamic> json = jsonDecode(result);
|
||
return MagnetBasicInfo.fromJson(json);
|
||
} on PlatformException catch (e) {
|
||
throw Exception('Failed to parse magnet URI: ${e.message}');
|
||
} catch (e) {
|
||
throw Exception('Failed to parse magnet basic info: $e');
|
||
}
|
||
}
|
||
|
||
/// Получить полные метаданные торрента
|
||
static Future<TorrentMetadataFull> fetchFullMetadata(String magnetUri) async {
|
||
try {
|
||
final String result = await _channel.invokeMethod('fetchFullMetadata', {
|
||
'magnetUri': magnetUri,
|
||
});
|
||
|
||
final Map<String, dynamic> json = jsonDecode(result);
|
||
return TorrentMetadataFull.fromJson(json);
|
||
} on PlatformException catch (e) {
|
||
throw Exception('Failed to fetch torrent metadata: ${e.message}');
|
||
} catch (e) {
|
||
throw Exception('Failed to parse torrent metadata: $e');
|
||
}
|
||
}
|
||
|
||
/// Тестирование торрент-сервиса
|
||
static Future<String> testTorrentService() async {
|
||
try {
|
||
final String result = await _channel.invokeMethod('testTorrentService');
|
||
return result;
|
||
} on PlatformException catch (e) {
|
||
throw Exception('Torrent service test failed: ${e.message}');
|
||
}
|
||
}
|
||
|
||
/// Get torrent metadata from magnet link (legacy method)
|
||
static Future<TorrentMetadata> getTorrentMetadata(String magnetLink) async {
|
||
try {
|
||
final String result = await _channel.invokeMethod('getTorrentMetadata', {
|
||
'magnetLink': magnetLink,
|
||
});
|
||
|
||
final Map<String, dynamic> json = jsonDecode(result);
|
||
return TorrentMetadata.fromJson(json);
|
||
} on PlatformException catch (e) {
|
||
throw Exception('Failed to get torrent metadata: ${e.message}');
|
||
} catch (e) {
|
||
throw Exception('Failed to parse torrent metadata: $e');
|
||
}
|
||
}
|
||
|
||
/// Start downloading selected files from torrent
|
||
static Future<String> startDownload({
|
||
required String magnetLink,
|
||
required List<int> selectedFiles,
|
||
String? downloadPath,
|
||
}) async {
|
||
try {
|
||
final String infoHash = await _channel.invokeMethod('startDownload', {
|
||
'magnetLink': magnetLink,
|
||
'selectedFiles': selectedFiles,
|
||
'downloadPath': downloadPath,
|
||
});
|
||
|
||
return infoHash;
|
||
} on PlatformException catch (e) {
|
||
throw Exception('Failed to start download: ${e.message}');
|
||
}
|
||
}
|
||
|
||
/// Get download progress for a torrent
|
||
static Future<DownloadProgress?> getDownloadProgress(String infoHash) async {
|
||
try {
|
||
final String? result = await _channel.invokeMethod('getDownloadProgress', {
|
||
'infoHash': infoHash,
|
||
});
|
||
|
||
if (result == null) return null;
|
||
|
||
final Map<String, dynamic> json = jsonDecode(result);
|
||
return DownloadProgress.fromJson(json);
|
||
} on PlatformException catch (e) {
|
||
if (e.code == 'NOT_FOUND') return null;
|
||
throw Exception('Failed to get download progress: ${e.message}');
|
||
} catch (e) {
|
||
throw Exception('Failed to parse download progress: $e');
|
||
}
|
||
}
|
||
|
||
/// Pause download
|
||
static Future<bool> pauseDownload(String infoHash) async {
|
||
try {
|
||
final bool result = await _channel.invokeMethod('pauseDownload', {
|
||
'infoHash': infoHash,
|
||
});
|
||
|
||
return result;
|
||
} on PlatformException catch (e) {
|
||
throw Exception('Failed to pause download: ${e.message}');
|
||
}
|
||
}
|
||
|
||
/// Resume download
|
||
static Future<bool> resumeDownload(String infoHash) async {
|
||
try {
|
||
final bool result = await _channel.invokeMethod('resumeDownload', {
|
||
'infoHash': infoHash,
|
||
});
|
||
|
||
return result;
|
||
} on PlatformException catch (e) {
|
||
throw Exception('Failed to resume download: ${e.message}');
|
||
}
|
||
}
|
||
|
||
/// Cancel and remove download
|
||
static Future<bool> cancelDownload(String infoHash) async {
|
||
try {
|
||
final bool result = await _channel.invokeMethod('cancelDownload', {
|
||
'infoHash': infoHash,
|
||
});
|
||
|
||
return result;
|
||
} on PlatformException catch (e) {
|
||
throw Exception('Failed to cancel download: ${e.message}');
|
||
}
|
||
}
|
||
|
||
/// Get all active downloads
|
||
static Future<List<DownloadProgress>> getAllDownloads() async {
|
||
try {
|
||
final String result = await _channel.invokeMethod('getAllDownloads');
|
||
|
||
final List<dynamic> jsonList = jsonDecode(result);
|
||
return jsonList
|
||
.map((json) => DownloadProgress.fromJson(json as Map<String, dynamic>))
|
||
.toList();
|
||
} on PlatformException catch (e) {
|
||
throw Exception('Failed to get all downloads: ${e.message}');
|
||
} catch (e) {
|
||
throw Exception('Failed to parse downloads: $e');
|
||
}
|
||
}
|
||
}
|