Skip to content

Commit d92551e

Browse files
committed
feat: improve torrent management functionality and UI
- Extract torrent list query to a reusable hook in queries.ts - Add delete torrent functionality to details page - Implement pull-to-refresh on torrent details screen - Add open in browser capability for unrestricted links - Improve magnet screen with clear button and reset functionality - Refactor torrent list screen to use the extracted query
1 parent 513aa28 commit d92551e

File tree

6 files changed

+113
-53
lines changed

6 files changed

+113
-53
lines changed

android/app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,8 @@ android {
9393
applicationId 'com.gitfudge.kaizoku'
9494
minSdkVersion rootProject.ext.minSdkVersion
9595
targetSdkVersion rootProject.ext.targetSdkVersion
96-
versionCode 1
97-
versionName "1.0.0"
96+
versionCode 2
97+
versionName "1.1.0"
9898
}
9999
buildFeatures {
100100
buildConfig true

api/queries.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { useQuery } from "@tanstack/react-query";
2+
import { api } from "./methods";
3+
4+
export const torrentListQuery = () =>
5+
useQuery({
6+
queryKey: ["torrents"],
7+
queryFn: () => api.getTorrents(),
8+
});

app/(details)/[id].tsx

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import UnrestrictModel from "@/models/Unrestrict.model";
33
import { colors } from "@/theme/colors";
44
import AsyncStorage from "@react-native-async-storage/async-storage";
55
import { useMutation, useQuery } from "@tanstack/react-query";
6-
import { Stack, useLocalSearchParams } from "expo-router";
6+
import { router, Stack, useLocalSearchParams } from "expo-router";
77
import React, { useCallback, useEffect, useState } from "react";
8-
import { ScrollView, StyleSheet, View } from "react-native";
8+
import { Linking, RefreshControl, ScrollView, StyleSheet, View } from "react-native";
99
import BackgroundService from "react-native-background-actions";
1010
import RNFS from "react-native-fs";
1111
import {
@@ -19,6 +19,7 @@ import {
1919
requestNotificationPermission,
2020
requestStoragePermission,
2121
} from "@/utils/deviceMethods";
22+
import { torrentListQuery } from "@/api/queries";
2223

2324
interface DownloadProgress {
2425
[key: string]: {
@@ -49,12 +50,19 @@ export default function TorrentDetailsScreen() {
4950
>({});
5051
const [downloads, setDownloads] = useState<DownloadProgress>({});
5152
const [activeDownloads, setActiveDownloads] = useState<number>(0);
53+
const [refreshing, setRefreshing] = useState(false);
5254

5355
// Fetch torrent details
54-
const { data: torrent, isLoading } = useQuery({
56+
const { data: torrent, isLoading, refetch } = useQuery({
5557
queryKey: ["torrent", id],
5658
queryFn: () => api.getTorrentInfo(id),
5759
});
60+
61+
const onRefresh = useCallback(async () => {
62+
setRefreshing(true);
63+
await refetch();
64+
setRefreshing(false);
65+
}, [refetch]);
5866

5967
// Load cached unrestricted links
6068
useEffect(() => {
@@ -91,6 +99,24 @@ export default function TorrentDetailsScreen() {
9199
};
92100
}, [id]);
93101

102+
const deleteMutation = useMutation({
103+
mutationFn: () => api.deleteTorrent(id),
104+
onSuccess: () => {
105+
Toast.show({
106+
type: "success",
107+
text1: "Deleted",
108+
});
109+
const { refetch: torrentRefetch } = torrentListQuery();
110+
torrentRefetch();
111+
router.push("/(tabs)");
112+
},
113+
onError: () => {
114+
Toast.show({
115+
type: "error",
116+
text1: "Error in deleting",
117+
});
118+
},
119+
});
94120
const unrestrictMutation = useMutation({
95121
mutationFn: async (links: string[]) => {
96122
const results = await Promise.all(
@@ -169,6 +195,17 @@ export default function TorrentDetailsScreen() {
169195
text2: item.filename,
170196
});
171197
}, []);
198+
199+
const handleOpenInBrowser = useCallback((item: UnrestrictModel.UnrestrictedItem) => {
200+
Linking.openURL(item.download).catch(err => {
201+
console.error("Failed to open URL:", err);
202+
Toast.show({
203+
type: "error",
204+
text1: "Failed to open URL",
205+
text2: err.message,
206+
});
207+
});
208+
}, []);
172209

173210
const downloadTask = async (taskDataArguments: any) => {
174211
console.log("Background task started with args:", taskDataArguments);
@@ -386,7 +423,17 @@ export default function TorrentDetailsScreen() {
386423
},
387424
}}
388425
/>
389-
<ScrollView style={styles.container}>
426+
<ScrollView
427+
style={styles.container}
428+
refreshControl={
429+
<RefreshControl
430+
refreshing={refreshing}
431+
onRefresh={onRefresh}
432+
colors={[colors.primary]}
433+
tintColor={colors.primary}
434+
/>
435+
}
436+
>
390437
<View style={styles.header}>
391438
<Text style={styles.filename}>{torrent.filename}</Text>
392439
<Text style={styles.details} variant="bodyMedium">
@@ -398,6 +445,13 @@ export default function TorrentDetailsScreen() {
398445
<Text style={styles.details} variant="bodyMedium">
399446
Progress: {torrent.progress}%
400447
</Text>
448+
<Button
449+
mode="contained"
450+
style={styles.deleteButton}
451+
onPress={() => deleteMutation.mutate()}
452+
>
453+
Delete torrent
454+
</Button>
401455
</View>
402456

403457
<View style={styles.content}>
@@ -447,6 +501,14 @@ export default function TorrentDetailsScreen() {
447501
</View>
448502
{unrestrictedData && (
449503
<View style={styles.linkActions}>
504+
<IconButton
505+
icon="open-in-new"
506+
mode="outlined"
507+
iconColor="#fff"
508+
size={22}
509+
style={styles.iconOnlyButton}
510+
onPress={() => handleOpenInBrowser(unrestrictedData)}
511+
/>
450512
{unrestrictedData.streamable === 1 && (
451513
<IconButton
452514
icon="play"
@@ -634,4 +696,7 @@ const styles = StyleSheet.create({
634696
color: "#fff",
635697
marginBottom: 8,
636698
},
699+
deleteButton: {
700+
width: 150,
701+
},
637702
});

app/(tabs)/index.tsx

Lines changed: 4 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,47 +2,15 @@ import { FlashList } from "@shopify/flash-list";
22
import { StyleSheet, View } from "react-native";
33
import { ActivityIndicator, Text } from "react-native-paper";
44
import { useRouter } from "expo-router";
5-
import Toast from "react-native-toast-message";
65
import { TorrentListItem } from "@/components/TorrentItem";
7-
import { AddMagnetModal } from "../../components/AddMagnetModal";
86
import { colors } from "../../theme/colors";
9-
import { api } from "@/api/methods";
10-
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
11-
import { useState } from "react";
7+
import { useEffect } from "react";
8+
import { torrentListQuery } from "@/api/queries";
129

1310
export default function TorrentScreen() {
14-
const queryClient = useQueryClient();
1511
const router = useRouter();
16-
const [modalVisible, setModalVisible] = useState(false);
1712

18-
const {
19-
data: torrents,
20-
isLoading,
21-
refetch,
22-
} = useQuery({
23-
queryKey: ["torrents"],
24-
queryFn: () => api.getTorrents(),
25-
});
26-
27-
const addMagnetMutation = useMutation({
28-
mutationFn: (magnet: string) => api.addMagnet(magnet),
29-
onSuccess: () => {
30-
queryClient.invalidateQueries({ queryKey: ["torrents"] });
31-
setModalVisible(false);
32-
Toast.show({
33-
type: "success",
34-
text1: "Success",
35-
text2: "Magnet link added successfully",
36-
});
37-
},
38-
onError: (error) => {
39-
Toast.show({
40-
type: "error",
41-
text1: "Error",
42-
text2: error.message,
43-
});
44-
},
45-
});
13+
const { data: torrents, isLoading, refetch } = torrentListQuery();
4614

4715
if (isLoading) {
4816
return (
@@ -77,11 +45,6 @@ export default function TorrentScreen() {
7745
onRefresh={refetch}
7846
refreshing={isLoading}
7947
/>
80-
<AddMagnetModal
81-
visible={modalVisible}
82-
onDismiss={() => setModalVisible(false)}
83-
onSubmit={(magnet) => addMagnetMutation.mutate(magnet)}
84-
/>
8548
</View>
8649
);
8750
}
@@ -95,6 +58,7 @@ const styles = StyleSheet.create({
9558
flex: 1,
9659
justifyContent: "center",
9760
alignItems: "center",
61+
backgroundColor: colors.background,
9862
},
9963
list: {
10064
padding: 16,

app/(tabs)/magnet.tsx

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,6 @@ export default function MagnetScreen() {
6161
},
6262
});
6363

64-
useEffect(() => {
65-
if (torrentInfo?.files) {
66-
setSelectedFiles(new Set(torrentInfo.files.map((f) => f.id)));
67-
}
68-
}, [torrentInfo]);
69-
7064
const handleSubmit = () => {
7165
if (!magnetLink.trim()) {
7266
setError("Please enter a magnet link");
@@ -105,6 +99,26 @@ export default function MagnetScreen() {
10599
isLoadingInfo ||
106100
selectFilesMutation.isPending;
107101

102+
useEffect(() => {
103+
if (torrentInfo?.files) {
104+
setSelectedFiles(new Set(torrentInfo.files.map((f) => f.id)));
105+
}
106+
}, [torrentInfo]);
107+
108+
const resetPage = () => {
109+
setMagnetLink("");
110+
setError("");
111+
setSelectedFiles(new Set());
112+
setTorrentId(null);
113+
addMagnetMutation.reset();
114+
};
115+
116+
useEffect(() => {
117+
return () => {
118+
resetPage();
119+
};
120+
}, []);
121+
108122
return (
109123
<>
110124
<Stack.Screen
@@ -126,6 +140,15 @@ export default function MagnetScreen() {
126140
disabled={isLoading}
127141
style={styles.input}
128142
textColor={colors.onSurface}
143+
right={
144+
magnetLink ? (
145+
<TextInput.Icon
146+
icon="close-circle"
147+
onPress={resetPage}
148+
forceTextInputFocus={false}
149+
/>
150+
) : null
151+
}
129152
theme={{
130153
colors: {
131154
onSurfaceVariant: colors.onSurface, // Make label more visible

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "kaizoku",
33
"main": "expo-router/entry",
4-
"version": "1.0.0",
4+
"version": "1.1.0",
55
"scripts": {
66
"start": "expo start",
77
"reset-project": "node ./scripts/reset-project.js",

0 commit comments

Comments
 (0)