mirror of
https://gitlab.com/foxixus/neomovies_mobile.git
synced 2025-10-28 03:58:50 +05:00
torrent downloads
This commit is contained in:
@@ -11,8 +11,10 @@ class Torrent with _$Torrent {
|
||||
String? name,
|
||||
String? quality,
|
||||
int? seeders,
|
||||
@JsonKey(name: 'size_gb') double? sizeGb,
|
||||
int? size, // размер в байтах
|
||||
}) = _Torrent;
|
||||
|
||||
factory Torrent.fromJson(Map<String, dynamic> json) => _$TorrentFromJson(json);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -25,8 +25,7 @@ mixin _$Torrent {
|
||||
String? get name => throw _privateConstructorUsedError;
|
||||
String? get quality => throw _privateConstructorUsedError;
|
||||
int? get seeders => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'size_gb')
|
||||
double? get sizeGb => throw _privateConstructorUsedError;
|
||||
int? get size => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this Torrent to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@@ -48,7 +47,7 @@ abstract class $TorrentCopyWith<$Res> {
|
||||
String? name,
|
||||
String? quality,
|
||||
int? seeders,
|
||||
@JsonKey(name: 'size_gb') double? sizeGb});
|
||||
int? size});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -71,7 +70,7 @@ class _$TorrentCopyWithImpl<$Res, $Val extends Torrent>
|
||||
Object? name = freezed,
|
||||
Object? quality = freezed,
|
||||
Object? seeders = freezed,
|
||||
Object? sizeGb = freezed,
|
||||
Object? size = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
magnet: null == magnet
|
||||
@@ -94,10 +93,10 @@ class _$TorrentCopyWithImpl<$Res, $Val extends Torrent>
|
||||
? _value.seeders
|
||||
: seeders // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
sizeGb: freezed == sizeGb
|
||||
? _value.sizeGb
|
||||
: sizeGb // ignore: cast_nullable_to_non_nullable
|
||||
as double?,
|
||||
size: freezed == size
|
||||
? _value.size
|
||||
: size // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
@@ -115,7 +114,7 @@ abstract class _$$TorrentImplCopyWith<$Res> implements $TorrentCopyWith<$Res> {
|
||||
String? name,
|
||||
String? quality,
|
||||
int? seeders,
|
||||
@JsonKey(name: 'size_gb') double? sizeGb});
|
||||
int? size});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -136,7 +135,7 @@ class __$$TorrentImplCopyWithImpl<$Res>
|
||||
Object? name = freezed,
|
||||
Object? quality = freezed,
|
||||
Object? seeders = freezed,
|
||||
Object? sizeGb = freezed,
|
||||
Object? size = freezed,
|
||||
}) {
|
||||
return _then(_$TorrentImpl(
|
||||
magnet: null == magnet
|
||||
@@ -159,10 +158,10 @@ class __$$TorrentImplCopyWithImpl<$Res>
|
||||
? _value.seeders
|
||||
: seeders // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
sizeGb: freezed == sizeGb
|
||||
? _value.sizeGb
|
||||
: sizeGb // ignore: cast_nullable_to_non_nullable
|
||||
as double?,
|
||||
size: freezed == size
|
||||
? _value.size
|
||||
: size // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -176,7 +175,7 @@ class _$TorrentImpl implements _Torrent {
|
||||
this.name,
|
||||
this.quality,
|
||||
this.seeders,
|
||||
@JsonKey(name: 'size_gb') this.sizeGb});
|
||||
this.size});
|
||||
|
||||
factory _$TorrentImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$TorrentImplFromJson(json);
|
||||
@@ -192,12 +191,11 @@ class _$TorrentImpl implements _Torrent {
|
||||
@override
|
||||
final int? seeders;
|
||||
@override
|
||||
@JsonKey(name: 'size_gb')
|
||||
final double? sizeGb;
|
||||
final int? size;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Torrent(magnet: $magnet, title: $title, name: $name, quality: $quality, seeders: $seeders, sizeGb: $sizeGb)';
|
||||
return 'Torrent(magnet: $magnet, title: $title, name: $name, quality: $quality, seeders: $seeders, size: $size)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -210,13 +208,13 @@ class _$TorrentImpl implements _Torrent {
|
||||
(identical(other.name, name) || other.name == name) &&
|
||||
(identical(other.quality, quality) || other.quality == quality) &&
|
||||
(identical(other.seeders, seeders) || other.seeders == seeders) &&
|
||||
(identical(other.sizeGb, sizeGb) || other.sizeGb == sizeGb));
|
||||
(identical(other.size, size) || other.size == size));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, magnet, title, name, quality, seeders, sizeGb);
|
||||
Object.hash(runtimeType, magnet, title, name, quality, seeders, size);
|
||||
|
||||
/// Create a copy of Torrent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -241,7 +239,7 @@ abstract class _Torrent implements Torrent {
|
||||
final String? name,
|
||||
final String? quality,
|
||||
final int? seeders,
|
||||
@JsonKey(name: 'size_gb') final double? sizeGb}) = _$TorrentImpl;
|
||||
final int? size}) = _$TorrentImpl;
|
||||
|
||||
factory _Torrent.fromJson(Map<String, dynamic> json) = _$TorrentImpl.fromJson;
|
||||
|
||||
@@ -256,8 +254,7 @@ abstract class _Torrent implements Torrent {
|
||||
@override
|
||||
int? get seeders;
|
||||
@override
|
||||
@JsonKey(name: 'size_gb')
|
||||
double? get sizeGb;
|
||||
int? get size;
|
||||
|
||||
/// Create a copy of Torrent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
|
||||
@@ -13,7 +13,7 @@ _$TorrentImpl _$$TorrentImplFromJson(Map<String, dynamic> json) =>
|
||||
name: json['name'] as String?,
|
||||
quality: json['quality'] as String?,
|
||||
seeders: (json['seeders'] as num?)?.toInt(),
|
||||
sizeGb: (json['size_gb'] as num?)?.toDouble(),
|
||||
size: (json['size'] as num?)?.toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$TorrentImplToJson(_$TorrentImpl instance) =>
|
||||
@@ -23,5 +23,5 @@ Map<String, dynamic> _$$TorrentImplToJson(_$TorrentImpl instance) =>
|
||||
'name': instance.name,
|
||||
'quality': instance.quality,
|
||||
'seeders': instance.seeders,
|
||||
'size_gb': instance.sizeGb,
|
||||
'size': instance.size,
|
||||
};
|
||||
|
||||
223
lib/data/services/torrent_platform_service.dart
Normal file
223
lib/data/services/torrent_platform_service.dart
Normal file
@@ -0,0 +1,223 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
/// Data classes for torrent metadata (matching Kotlin side)
|
||||
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/torrent');
|
||||
|
||||
/// Get torrent metadata from magnet link
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,6 +77,25 @@ class TorrentService {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Форматировать размер из байтов в читаемый формат
|
||||
String formatFileSize(int? sizeInBytes) {
|
||||
if (sizeInBytes == null || sizeInBytes == 0) return 'Неизвестно';
|
||||
|
||||
const int kb = 1024;
|
||||
const int mb = kb * 1024;
|
||||
const int gb = mb * 1024;
|
||||
|
||||
if (sizeInBytes >= gb) {
|
||||
return '${(sizeInBytes / gb).toStringAsFixed(1)} GB';
|
||||
} else if (sizeInBytes >= mb) {
|
||||
return '${(sizeInBytes / mb).toStringAsFixed(0)} MB';
|
||||
} else if (sizeInBytes >= kb) {
|
||||
return '${(sizeInBytes / kb).toStringAsFixed(0)} KB';
|
||||
} else {
|
||||
return '$sizeInBytes B';
|
||||
}
|
||||
}
|
||||
|
||||
/// Группировать торренты по качеству
|
||||
Map<String, List<Torrent>> groupTorrentsByQuality(List<Torrent> torrents) {
|
||||
final groups = <String, List<Torrent>>{};
|
||||
|
||||
Reference in New Issue
Block a user