Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Android NDK and the libtorrent-rasterbar C++ library.
## Features

- Add torrents via magnet links or `.torrent` files
- Sample torrents library — curated list of public domain and Creative Commons content for quick testing (Big Buck Bunny, Cosmos Laundromat, Sintel, Tears of Steel)
- Create `.torrent` files from any file or folder on the device and seed them immediately
- Real-time piece-map visualization (missing / downloaded / actively transferring)
- Collapsible per-torrent peer list (top 5 by download speed, refreshed every 3 s)
Expand Down Expand Up @@ -62,14 +63,15 @@ SimpleTorrent/
│ │ ├── NavigationKeys.kt # Serializable NavKey types: Main, Player(filePath, title)
│ │ ├── data/
│ │ │ ├── TorrentManager.kt # Singleton: loads .so, data classes, JNI declarations
│ │ │ └── DataRepository.kt # Interface + DefaultImpl polling every 3 s
│ │ │ ├── DataRepository.kt # Interface + DefaultImpl polling every 3 s
│ │ │ └── SampleTorrents.kt # Curated list of public domain / CC torrents for testing
│ │ ├── theme/
│ │ │ ├── Color.kt # Material You color tokens
│ │ │ ├── Theme.kt # SimpleTorrentTheme
│ │ │ └── Type.kt # Typography scale
│ │ └── ui/
│ │ ├── main/
│ │ │ ├── MainScreen.kt # Compose UI: cards, piece map, file list, peer list
│ │ │ ├── MainScreen.kt # Compose UI: cards, piece map, file list, peer list, sample torrents
│ │ │ └── MainScreenViewModel.kt
│ │ └── player/
│ │ └── PlayerScreen.kt # ExoPlayer fullscreen player; position saved per file path; auto-detects and loads multi-language subtitles
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.jpcottin.simpletorrent.data

data class SampleTorrent(
val title: String,
val description: String,
val magnet: String,
)

val SAMPLE_TORRENTS = listOf(
SampleTorrent(
"Big Buck Bunny",
"Animated short film, CC BY 3.0",
"magnet:?xt=urn:btih:dd8255ecdc7ca55fb0bbf81323d87062db1f6d1c&dn=Big+Buck+Bunny&tr=udp%3A%2F%2Fexplodie.org%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.empire-js.us%3A1337&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2F&xs=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2Fbig-buck-bunny.torrent",
),
SampleTorrent(
"Cosmos Laundromat",
"Blender film, CC BY 3.0",
"magnet:?xt=urn:btih:c9e15763f722f23e98a29decdfae341b98d53056&dn=Cosmos+Laundromat&tr=udp%3A%2F%2Fexplodie.org%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.empire-js.us%3A1337&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2F&xs=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2Fcosmos-laundromat.torrent",
),
SampleTorrent(
"Sintel",
"Blender film, CC BY 3.0",
"magnet:?xt=urn:btih:08ada5a7a6183aae1e09d831df6748d566095a10&dn=Sintel&tr=udp%3A%2F%2Fexplodie.org%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.empire-js.us%3A1337&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2F&xs=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2Fsintel.torrent",
),
SampleTorrent(
"Tears of Steel",
"Blender film, CC BY 3.0, includes subtitles",
"magnet:?xt=urn:btih:209c8226b299b308beaf2b9cd3fb49212dbd13ec&dn=Tears+of+Steel&tr=udp%3A%2F%2Fexplodie.org%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.empire-js.us%3A1337&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2F&xs=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2Ftears-of-steel.torrent",
),
)
60 changes: 60 additions & 0 deletions app/src/main/java/com/jpcottin/simpletorrent/ui/main/MainScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ import androidx.compose.material.icons.filled.FolderOpen
import androidx.compose.material.icons.filled.Pause
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material.icons.filled.Share
import androidx.compose.material.icons.filled.PlaylistAdd
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Card
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.DropdownMenu
Expand Down Expand Up @@ -77,6 +80,7 @@ import androidx.navigation3.runtime.NavKey
import com.jpcottin.simpletorrent.data.DefaultDataRepository
import com.jpcottin.simpletorrent.data.FileInfo
import com.jpcottin.simpletorrent.data.PeerInfo
import com.jpcottin.simpletorrent.data.SAMPLE_TORRENTS
import com.jpcottin.simpletorrent.data.TorrentInfo
import com.jpcottin.simpletorrent.data.TorrentManager
import com.jpcottin.simpletorrent.theme.SimpleTorrentTheme
Expand All @@ -94,6 +98,7 @@ fun MainScreen(
val state by viewModel.uiState.collectAsStateWithLifecycle()
val createState by viewModel.createState.collectAsStateWithLifecycle()
var magnetInput by remember { mutableStateOf("") }
var showSampleSheet by remember { mutableStateOf(false) }
val context = LocalContext.current

fun resolveTreeToPath(uri: android.net.Uri): String? {
Expand Down Expand Up @@ -214,6 +219,7 @@ fun MainScreen(
},
onCreateFile = { fileLauncher.launch(arrayOf("*/*")) },
onCreateFolder = { folderLauncher.launch(null) },
onSampleTorrents = { showSampleSheet = true },
)

when (state) {
Expand Down Expand Up @@ -261,6 +267,16 @@ fun MainScreen(
}
}
}

if (showSampleSheet) {
SampleTorrentsSheet(
onDismiss = { showSampleSheet = false },
onAdd = { magnet ->
showSampleSheet = false
viewModel.addMagnet(magnet)
},
)
}
}

@Composable
Expand All @@ -270,6 +286,7 @@ internal fun MagnetInputBar(
onAdd: () -> Unit,
onCreateFile: () -> Unit,
onCreateFolder: () -> Unit,
onSampleTorrents: () -> Unit,
modifier: Modifier = Modifier,
) {
var showCreateMenu by remember { mutableStateOf(false) }
Expand All @@ -289,6 +306,9 @@ internal fun MagnetInputBar(
keyboardActions = KeyboardActions(onDone = { onAdd() }),
)
androidx.compose.material3.Button(onClick = onAdd) { Text("Add") }
IconButton(onClick = onSampleTorrents) {
Icon(Icons.Filled.PlaylistAdd, contentDescription = "Sample torrents")
}
Box {
IconButton(onClick = { showCreateMenu = true }) {
Icon(Icons.Filled.Add, contentDescription = "Create torrent")
Expand All @@ -310,6 +330,44 @@ internal fun MagnetInputBar(
}
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun SampleTorrentsSheet(
onDismiss: () -> Unit,
onAdd: (String) -> Unit,
) {
ModalBottomSheet(onDismissRequest = onDismiss) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
) {
Text(
"Sample Torrents",
fontSize = 18.sp,
modifier = Modifier.padding(bottom = 16.dp),
)
SAMPLE_TORRENTS.forEach { torrent ->
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Column(modifier = Modifier.weight(1f)) {
Text(torrent.title, fontSize = 14.sp)
Text(torrent.description, fontSize = 12.sp, color = Color.Gray)
}
TextButton(onClick = { onAdd(torrent.magnet) }) {
Text("Add")
}
}
}
}
}
}

private val PLAYABLE_EXTENSIONS = setOf(
"mp4", "mkv", "avi", "mov", "webm", "m4v", "ts", "flv",
"mp3", "flac", "ogg", "m4a", "aac", "wav", "opus",
Expand Down Expand Up @@ -724,6 +782,7 @@ private fun MagnetInputBarPreview() {
onAdd = {},
onCreateFile = {},
onCreateFolder = {},
onSampleTorrents = {},
)
}
}
Expand All @@ -738,6 +797,7 @@ private fun MagnetInputBarFilledPreview() {
onAdd = {},
onCreateFile = {},
onCreateFolder = {},
onSampleTorrents = {},
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ fun MagnetInputBarScreenshot() {
onAdd = {},
onCreateFile = {},
onCreateFolder = {},
onSampleTorrents = {},
)
}
}
Expand Down