diff --git a/app/src/androidMain/AndroidManifest.xml b/app/src/androidMain/AndroidManifest.xml
index c31a86b..3aba942 100644
--- a/app/src/androidMain/AndroidManifest.xml
+++ b/app/src/androidMain/AndroidManifest.xml
@@ -27,6 +27,16 @@
+
+
+
+
+
+
+
+
+
+
()
+ when (intent.action) {
+ Intent.ACTION_SEND -> {
+ intent.getParcelableExtra(Intent.EXTRA_STREAM)?.let { uris.add(it) }
+ }
+ Intent.ACTION_SEND_MULTIPLE -> {
+ intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM)?.let { uris.addAll(it) }
+ }
+ }
+
+ if (uris.isNotEmpty()) {
+ val files = uris.mapNotNull { uri ->
+ copyUriToTempFile(uri)
+ }
+ manager?.setSharedFiles(files)
+ }
+ }
+
+ private fun copyUriToTempFile(uri: Uri): File? {
+ return try {
+ val fileName = getFileName(uri) ?: "shared_file"
+ val tempFile = File(cacheDir, "share_${System.currentTimeMillis()}_$fileName")
+ contentResolver.openInputStream(uri)?.use { input ->
+ tempFile.outputStream().use { output -> input.copyTo(output) }
+ }
+ tempFile
+ } catch (_: Exception) {
+ null
+ }
+ }
+
+ private fun getFileName(uri: Uri): String? {
+ return try {
+ contentResolver.query(uri, null, null, null, null)?.use { cursor ->
+ val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
+ if (cursor.moveToFirst() && nameIndex >= 0) {
+ cursor.getString(nameIndex)
+ } else null
+ }
+ } catch (_: Exception) {
+ uri.lastPathSegment
+ }
+ }
+
private fun startApp() {
- if (manager != null) return
+ if (manager != null) {
+ handleShareIntent(intent)
+ return
+ }
startForegroundService(Intent(this, AeroTossService::class.java))
@@ -79,6 +137,7 @@ class MainActivity : ComponentActivity() {
lifecycleScope.launch {
val port = withContext(Dispatchers.IO) { transferManager.startServer(0) }
mgr.start(port)
+ handleShareIntent(intent)
}
setContent { App(mgr) }
diff --git a/app/src/androidMain/kotlin/com/aerotoss/transfer/AndroidTransferManager.kt b/app/src/androidMain/kotlin/com/aerotoss/transfer/AndroidTransferManager.kt
index 68425b3..c1f60d9 100644
--- a/app/src/androidMain/kotlin/com/aerotoss/transfer/AndroidTransferManager.kt
+++ b/app/src/androidMain/kotlin/com/aerotoss/transfer/AndroidTransferManager.kt
@@ -242,6 +242,8 @@ class AndroidFileTransferManager(private val context: Context) : TransferManager
val sha256 = MessageDigest.getInstance("SHA-256")
var bytesSent = 0L
+ var lastProgressTime = System.currentTimeMillis()
+ var lastProgressBytes = 0L
val buffer = ByteArray(65536)
file.inputStream().use { fis ->
while (true) {
@@ -250,13 +252,20 @@ class AndroidFileTransferManager(private val context: Context) : TransferManager
output.write(buffer, 0, read)
sha256.update(buffer, 0, read)
bytesSent += read
- updateOutgoingById(requestId, TransferProgress(
- id = requestId,
- request = request,
- bytesTransferred = bytesSent,
- totalBytes = file.length(),
- state = TransferState.TRANSFERRING
- ))
+ val now = System.currentTimeMillis()
+ if (now - lastProgressTime >= 500) {
+ updateOutgoingById(requestId, TransferProgress(
+ id = requestId,
+ request = request,
+ bytesTransferred = bytesSent,
+ totalBytes = file.length(),
+ lastUpdateTimeMs = now,
+ lastBytesTransferred = lastProgressBytes,
+ state = TransferState.TRANSFERRING
+ ))
+ lastProgressTime = now
+ lastProgressBytes = bytesSent
+ }
}
}
output.flush()
diff --git a/app/src/commonMain/kotlin/com/aerotoss/core/AeroTossManager.kt b/app/src/commonMain/kotlin/com/aerotoss/core/AeroTossManager.kt
index 23111ad..6456712 100644
--- a/app/src/commonMain/kotlin/com/aerotoss/core/AeroTossManager.kt
+++ b/app/src/commonMain/kotlin/com/aerotoss/core/AeroTossManager.kt
@@ -1,8 +1,10 @@
package com.aerotoss.core
import com.aerotoss.discovery.DiscoveryManager
+import com.aerotoss.discovery.TcpDiscovery
import com.aerotoss.model.Device
import com.aerotoss.model.TransferProgress
+import com.aerotoss.model.TransferState
import com.aerotoss.transfer.TransferManager
import kotlinx.coroutines.flow.Flow
import java.io.File
@@ -18,6 +20,9 @@ class AeroTossManager(
val serverPort: Int
get() = transfer.getServerPort()
+ var sharedFiles: List = emptyList()
+ private set
+
fun start(serverPort: Int) {
discovery.startDiscovery(serverPort)
}
@@ -28,13 +33,27 @@ class AeroTossManager(
discovery.startDiscovery(port)
}
+ fun setSharedFiles(files: List) {
+ sharedFiles = files
+ }
+
+ fun setActive(active: Boolean) {
+ (discovery as? TcpDiscovery)?.isActive = active
+ }
+
+ fun setTransferring(transferring: Boolean) {
+ (discovery as? TcpDiscovery)?.isTransferring = transferring
+ }
+
suspend fun sendFile(file: File, target: Device): Flow {
require(file.exists() && file.isFile) { "File does not exist or is not a regular file" }
+ setTransferring(true)
return transfer.sendFile(file, target.hostAddress, target.port)
}
fun cancelTransfer(requestId: String) {
transfer.cancelTransfer(requestId)
+ setTransferring(false)
}
fun stop() {
diff --git a/app/src/commonMain/kotlin/com/aerotoss/discovery/TcpDiscovery.kt b/app/src/commonMain/kotlin/com/aerotoss/discovery/TcpDiscovery.kt
index 0f9b054..279aacf 100644
--- a/app/src/commonMain/kotlin/com/aerotoss/discovery/TcpDiscovery.kt
+++ b/app/src/commonMain/kotlin/com/aerotoss/discovery/TcpDiscovery.kt
@@ -34,6 +34,9 @@ class TcpDiscovery(
private val discoveredDevices = ConcurrentHashMap()
private val json = Json { ignoreUnknownKeys = true; encodeDefaults = true }
+ @Volatile var isActive = true
+ @Volatile var isTransferring = false
+
override fun startDiscovery(servicePort: Int) {
if (serverThread != null) return
@@ -69,7 +72,8 @@ class TcpDiscovery(
var lastBroadcast = System.currentTimeMillis()
while (!Thread.currentThread().isInterrupted) {
val now = System.currentTimeMillis()
- if (now - lastBroadcast > 1_000) {
+ val interval = if (isTransferring) 1_000L else if (isActive) 3_000L else 15_000L
+ if (now - lastBroadcast > interval) {
broadcast(socket, servicePort)
lastBroadcast = now
}
diff --git a/app/src/commonMain/kotlin/com/aerotoss/model/TransferProgress.kt b/app/src/commonMain/kotlin/com/aerotoss/model/TransferProgress.kt
index 19c7ef4..f07d437 100644
--- a/app/src/commonMain/kotlin/com/aerotoss/model/TransferProgress.kt
+++ b/app/src/commonMain/kotlin/com/aerotoss/model/TransferProgress.kt
@@ -15,6 +15,8 @@ data class TransferProgress(
val totalBytes: Long,
val state: TransferState,
val startTimeMs: Long = System.currentTimeMillis(),
+ val lastUpdateTimeMs: Long = System.currentTimeMillis(),
+ val lastBytesTransferred: Long = 0,
val error: String? = null
) {
val progress: Float
@@ -25,11 +27,11 @@ data class TransferProgress(
val speed: Long
get() {
- if (state != TransferState.TRANSFERRING || bytesTransferred == 0L) return 0L
- val elapsed = (System.currentTimeMillis() - startTimeMs).coerceAtLeast(1)
- return bytesTransferred * 1000 / elapsed
+ if (state != TransferState.TRANSFERRING) return 0L
+ val now = System.currentTimeMillis()
+ val timeDelta = (now - lastUpdateTimeMs).coerceAtLeast(1)
+ val bytesDelta = bytesTransferred - lastBytesTransferred
+ if (bytesDelta <= 0) return 0L
+ return bytesDelta * 1000 / timeDelta
}
-
- val elapsedMs: Long
- get() = System.currentTimeMillis() - startTimeMs
}
diff --git a/app/src/commonMain/kotlin/com/aerotoss/ui/navigation/AppNavigation.kt b/app/src/commonMain/kotlin/com/aerotoss/ui/navigation/AppNavigation.kt
index 3b0156d..526c547 100644
--- a/app/src/commonMain/kotlin/com/aerotoss/ui/navigation/AppNavigation.kt
+++ b/app/src/commonMain/kotlin/com/aerotoss/ui/navigation/AppNavigation.kt
@@ -11,6 +11,13 @@ fun AppNavigation(manager: AeroTossManager) {
var currentScreen by remember { mutableStateOf(Screen.HOME) }
var selectedDevice by remember { mutableStateOf(null) }
+ // Auto-navigate to send screen if shared files exist
+ LaunchedEffect(manager.sharedFiles) {
+ if (manager.sharedFiles.isNotEmpty() && currentScreen == Screen.HOME) {
+ currentScreen = Screen.SEND
+ }
+ }
+
when (currentScreen) {
Screen.HOME -> HomeScreen(
manager = manager,
diff --git a/app/src/commonMain/kotlin/com/aerotoss/ui/screens/HomeScreen.kt b/app/src/commonMain/kotlin/com/aerotoss/ui/screens/HomeScreen.kt
index f599cf3..337395c 100644
--- a/app/src/commonMain/kotlin/com/aerotoss/ui/screens/HomeScreen.kt
+++ b/app/src/commonMain/kotlin/com/aerotoss/ui/screens/HomeScreen.kt
@@ -23,6 +23,7 @@ fun HomeScreen(
) {
val devices by manager.devices.collectAsState(initial = emptyList())
val outgoingTransfer by manager.outgoingTransfers.collectAsState(initial = null)
+ val incomingTransfer by manager.incomingTransfers.collectAsState(initial = null)
Scaffold(
topBar = {
@@ -56,20 +57,47 @@ fun HomeScreen(
.padding(padding)
.padding(16.dp)
) {
- if (outgoingTransfer != null) {
- outgoingTransfer?.let { transfer ->
- TransferProgressBar(transfer)
- if (transfer.state == TransferState.TRANSFERRING) {
- Spacer(modifier = Modifier.height(4.dp))
- OutlinedButton(
- onClick = { manager.cancelTransfer(transfer.id) },
- modifier = Modifier.fillMaxWidth()
- ) {
- Text("Отменить отправку")
+ if (incomingTransfer != null) {
+ incomingTransfer?.let { transfer ->
+ Card(
+ modifier = Modifier.fillMaxWidth(),
+ colors = CardDefaults.cardColors(
+ containerColor = MaterialTheme.colorScheme.secondaryContainer
+ )
+ ) {
+ Column(modifier = Modifier.padding(12.dp)) {
+ Text("Входящая передача:", style = MaterialTheme.typography.titleSmall)
+ TransferProgressBar(transfer)
}
}
+ Spacer(modifier = Modifier.height(8.dp))
+ }
+ }
+
+ if (outgoingTransfer != null) {
+ outgoingTransfer?.let { transfer ->
+ Card(
+ modifier = Modifier.fillMaxWidth(),
+ colors = CardDefaults.cardColors(
+ containerColor = MaterialTheme.colorScheme.tertiaryContainer
+ )
+ ) {
+ Column(modifier = Modifier.padding(12.dp)) {
+ Text("Исходящая передача:", style = MaterialTheme.typography.titleSmall)
+ TransferProgressBar(transfer)
+ if (transfer.state == TransferState.TRANSFERRING) {
+ Spacer(modifier = Modifier.height(4.dp))
+ OutlinedButton(
+ onClick = { manager.cancelTransfer(transfer.id) },
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Text("Отменить")
+ }
+ }
+ }
+ }
+ Spacer(modifier = Modifier.height(8.dp))
}
- Spacer(modifier = Modifier.height(8.dp))
}
if (devices.isEmpty()) {
diff --git a/app/src/desktopMain/kotlin/com/aerotoss/transfer/DesktopTransferManager.kt b/app/src/desktopMain/kotlin/com/aerotoss/transfer/DesktopTransferManager.kt
index e6c30d7..db6bb8e 100644
--- a/app/src/desktopMain/kotlin/com/aerotoss/transfer/DesktopTransferManager.kt
+++ b/app/src/desktopMain/kotlin/com/aerotoss/transfer/DesktopTransferManager.kt
@@ -240,6 +240,8 @@ class DesktopFileTransferManager : TransferManager {
val sha256 = MessageDigest.getInstance("SHA-256")
var bytesSent = 0L
+ var lastProgressTime = System.currentTimeMillis()
+ var lastProgressBytes = 0L
val buffer = ByteArray(65536)
file.inputStream().use { fis ->
while (true) {
@@ -248,11 +250,18 @@ class DesktopFileTransferManager : TransferManager {
output.write(buffer, 0, read)
sha256.update(buffer, 0, read)
bytesSent += read
- stateFlow.value = stateFlow.value.copy(
- bytesTransferred = bytesSent,
- state = TransferState.TRANSFERRING
- )
- updateOutgoing(stateFlow.value)
+ val now = System.currentTimeMillis()
+ if (now - lastProgressTime >= 500) {
+ stateFlow.value = stateFlow.value.copy(
+ bytesTransferred = bytesSent,
+ lastUpdateTimeMs = now,
+ lastBytesTransferred = lastProgressBytes,
+ state = TransferState.TRANSFERRING
+ )
+ updateOutgoing(stateFlow.value)
+ lastProgressTime = now
+ lastProgressBytes = bytesSent
+ }
}
}
output.flush()