Skip to content
Open
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
24 changes: 24 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,25 @@ android {
baselineProfile {
dexLayoutOptimization = true
}

packaging {
resources {
// Ignorar archivos duplicados META-INF que causan conflictos
excludes += listOf(
"META-INF/versions/9/OSGI-INF/MANIFEST.MF",
"META-INF/DEPENDENCIES",
"META-INF/LICENSE",
"META-INF/LICENSE.txt",
"META-INF/NOTICE",
"META-INF/NOTICE.txt",
"META-INF/*.kotlin_module"
)
}
}
}

dependencies {
implementation(libs.androidx.material3)
"baselineProfile"(project(":baselineprofile"))
implementation(libs.androidx.profileinstaller)
coreLibraryDesugaring(libs.desugar.jdk.libs)
Expand Down Expand Up @@ -106,4 +122,12 @@ dependencies {
implementation(libs.gson)
implementation(libs.storage)
implementation(libs.zip4j)

// SMB 2/3 support
implementation(libs.smbj)
implementation(libs.dcerpc) {
exclude(group = "com.google.code.findbugs", module = "jsr305")
}
// SMB 1 support (JCIFS-NG)
implementation(libs.jcifs.ng)
}
11 changes: 11 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,16 @@
-keep class org.joni.** { *; }
-keep class android.content.** { *; }
-keep class com.android.apksig.** { *; }
# SMB SUPPORT
-keep class com.hierynomus.** { *; }
-keep class com.rapid7.** { *; }
-keep class sun.security.** { *; }
-dontwarn sun.security.**
-keep class java.rmi.** { *; }
-dontwarn java.rmi.**
-keep class javax.el.** { *; }
-dontwarn javax.el.**
-keep class org.ietf.jgss.** { *; }
-dontwarn org.ietf.jgss.**

-keepnames interface * { *; }
3 changes: 3 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" tools:node="remove"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

<uses-feature
android:glEsVersion="0x00020000"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,11 @@ import com.raival.compose.file.explorer.screen.main.tab.files.holder.LocalFileHo
import com.raival.compose.file.explorer.screen.main.tab.files.ui.FilesTabContentView
import com.raival.compose.file.explorer.screen.main.tab.home.HomeTab
import com.raival.compose.file.explorer.screen.main.tab.home.ui.HomeTabContentView
import com.raival.compose.file.explorer.screen.main.ui.AddSMBDriveDialog
import com.raival.compose.file.explorer.screen.main.ui.AddStorageMenuDialog
import com.raival.compose.file.explorer.screen.main.ui.AppInfoDialog
import com.raival.compose.file.explorer.screen.main.ui.JumpToPathDialog
import com.raival.compose.file.explorer.screen.main.ui.LanDiscoveryDialog
import com.raival.compose.file.explorer.screen.main.ui.SaveTextEditorFilesDialog
import com.raival.compose.file.explorer.screen.main.ui.StartupTabsSettingsScreen
import com.raival.compose.file.explorer.screen.main.ui.TabLayout
Expand Down Expand Up @@ -127,6 +130,47 @@ class MainActivity : BaseActivity() {
onDismiss = { mainActivityManager.toggleJumpToPathDialog(false) }
)

AddSMBDriveDialog(
show = mainActivityState.showAddSMBDriveDialog,
onDismiss = { mainActivityManager.toggleAddSMBDriveDialog(false) }
)

LanDiscoveryDialog(
show = mainActivityState.showLanDiscoveryDialog,
isScanning = mainActivityState.isLanScanningRunning,
devices = mainActivityState.lanDevices,
onDismiss = { mainActivityManager.toggleLanDiscoveryDialog(false) },
onDeviceSelected = { selected ->
val matchResult = "\\((\\d+\\.\\d+\\.\\d+\\.\\d+):(\\d+)\\)$".toRegex().find(selected)
val ip: String
val port: String

if (matchResult != null) {
ip = matchResult.groupValues[1]
port = matchResult.groupValues[2]
} else {
ip = selected
port = "445"
}

mainActivityManager.toggleLanDiscoveryDialog(false)
mainActivityManager.toggleAddSMBDriveDialog(true, defaultHost = ip, defaultPort = port)
}
)

AddStorageMenuDialog(
show = mainActivityState.showStorageMenuDialog,
onDismiss = { mainActivityManager.toggleStorageMenuDialog(false) },
onAddSmb = {
mainActivityManager.toggleStorageMenuDialog(false)
mainActivityManager.toggleAddSMBDriveDialog(true)
},
onAddLan = {
mainActivityManager.toggleStorageMenuDialog(false)
mainActivityManager.toggleLanDiscoveryDialog(true)
}
)

AppInfoDialog(
show = mainActivityState.showAppInfoDialog,
hasNewUpdate = mainActivityState.hasNewUpdate,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.raival.compose.file.explorer.screen.main

import android.annotation.SuppressLint
import android.content.Context
import android.net.wifi.WifiManager
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.raival.compose.file.explorer.App.Companion.globalClass
Expand All @@ -21,23 +23,37 @@ import com.raival.compose.file.explorer.screen.main.tab.Tab
import com.raival.compose.file.explorer.screen.main.tab.apps.AppsTab
import com.raival.compose.file.explorer.screen.main.tab.files.FilesTab
import com.raival.compose.file.explorer.screen.main.tab.files.holder.LocalFileHolder
import com.raival.compose.file.explorer.screen.main.tab.files.holder.SMB1FileHolder
import com.raival.compose.file.explorer.screen.main.tab.files.holder.SMBFileHolder
import com.raival.compose.file.explorer.screen.main.tab.files.provider.StorageProvider
import com.raival.compose.file.explorer.screen.main.tab.home.HomeTab
import jcifs.netbios.NbtAddress
import jcifs.netbios.UniAddress
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import java.io.File
import java.io.InputStreamReader
import java.net.ConnectException
import java.net.HttpURLConnection
import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.NetworkInterface
import java.net.Socket
import java.net.URL
import java.net.UnknownHostException
import java.nio.ByteOrder
import kotlin.math.max
import kotlin.math.min


class MainActivityManager {
val managerScope = CoroutineScope(Dispatchers.IO)
private val _state = MutableStateFlow(MainActivityState())
Expand Down Expand Up @@ -223,12 +239,61 @@ class MainActivityManager {
openFile(file, context)
}


fun addSmbDrive(
host: String,
port: Int,
username: String,
password: String,
anonymous: Boolean,
domain: String,
context: Context
): Boolean {
return try {
openSMBFile(SMBFileHolder(host, port, username, password, anonymous, domain, ""), context)
} catch (e: Exception) {
false
}
}

fun addSmb1Drive(
host: String,
port: Int,
username: String,
password: String,
anonymous: Boolean,
domain: String,
context: Context
): Boolean {
return try {
openSMB1File(SMB1FileHolder(host, port, username, password, anonymous, domain, ""), context)
} catch (e: Exception) {
false
}
}

private fun openFile(file: LocalFileHolder, context: Context) {
if (file.exists()) {
addTabAndSelect(FilesTab(file, context))
}
}

private fun openSMBFile(file: SMBFileHolder, context: Context) : Boolean {
return if (file.exists()) {
addTabAndSelect(FilesTab(file, context))
true
}else
false
}

private fun openSMB1File(file: SMB1FileHolder, context: Context) : Boolean {
return if (file.exists()) {
addTabAndSelect(FilesTab(file, context))
true
}else
false
}

fun resumeActiveTab() {
getActiveTab()?.onTabResumed()
}
Expand Down Expand Up @@ -513,6 +578,132 @@ class MainActivityManager {
}
}

fun toggleLanDiscoveryDialog(show: Boolean) {
_state.update {
it.copy(
showLanDiscoveryDialog = show,
isLanScanningRunning = show,
lanDevices = if (show) emptyList() else it.lanDevices
)
}

if (show) {
startLanScan(globalClass)
}
}

@SuppressLint("ServiceCast")
fun getLocalIpAddress(context: Context): Pair<String, String>? {
try {
val interfaces = NetworkInterface.getNetworkInterfaces()
for (iface in interfaces) {
val addrs = iface.inetAddresses
for (addr in addrs) {
if (!addr.isLoopbackAddress && addr is InetAddress && addr.address.size == 4) {
val ip = addr.hostAddress
val prefixLength = iface.interfaceAddresses.find { it.address.hostAddress == ip }?.networkPrefixLength
if (prefixLength != null) {
return Pair(ip, prefixLength.toString())
}
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
return null
}

fun calculateIpRange(ip: String, prefixLength: Int): List<String> {
val ipParts = ip.split(".").map { it.toInt() }
val ipInt = (ipParts[0] shl 24) or (ipParts[1] shl 16) or (ipParts[2] shl 8) or ipParts[3]

val mask = (-1 shl (32 - prefixLength))
val network = ipInt and mask
val broadcast = network or mask.inv()

val ips = mutableListOf<String>()
for (current in network + 1 until broadcast) {
val octets = listOf(
(current shr 24) and 0xFF,
(current shr 16) and 0xFF,
(current shr 8) and 0xFF,
current and 0xFF
)
ips.add(octets.joinToString("."))
}

return ips
}



fun startLanScan(context: Context) {
managerScope.launch {
_state.update { it.copy(isLanScanningRunning = true, lanDevices = emptyList()) }

val local = getLocalIpAddress(context)
if (local == null) {
_state.update { it.copy(isLanScanningRunning = false) }
return@launch
}

val (localIp, prefixStr) = local
val prefixLength = prefixStr.toInt()
val ipsToScan = calculateIpRange(localIp, prefixLength)

val foundDevices = mutableSetOf<String>()
val ports = listOf(445, 139, 4450)
val semaphore = Semaphore(50)

val jobs = ipsToScan.map { ip ->
async(Dispatchers.IO) {
semaphore.withPermit {
for (port in ports) {
try {
Socket().use { socket ->
socket.connect(java.net.InetSocketAddress(ip, port), 200)
val inetAddress = InetAddress.getByName(ip)
val hostName = inetAddress.hostName

val display = "$hostName ($ip:$port)"
if (foundDevices.add(display)) {
_state.update {
it.copy(lanDevices = it.lanDevices + display)
}
}

break
}
} catch (_: Exception) {}
}
}
}
}

jobs.awaitAll()
_state.update { it.copy(isLanScanningRunning = false) }
}
}

fun toggleAddSMBDriveDialog(show: Boolean, defaultHost: String = "", defaultPort: String = "") {
_state.update {
it.copy(
showAddSMBDriveDialog = show,
smbDefaultHost = defaultHost,
smbDefaultPort = defaultPort
)
}
}

fun toggleStorageMenuDialog(show: Boolean) {
_state.update {
it.copy(
showStorageMenuDialog = show
)
}
}

fun toggleAppInfoDialog(show: Boolean) {
_state.update {
it.copy(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,19 @@ data class MainActivityState(
val subtitle: String = emptyString,
val showAppInfoDialog: Boolean = false,
val showJumpToPathDialog: Boolean = false,
val showAddSMBDriveDialog: Boolean = false,
val showStorageMenuDialog: Boolean = false,
val showLanDiscoveryDialog: Boolean = false,
val isLanScanningRunning: Boolean = true,
val lanDevices: List<String> = emptyList(),
val smbDefaultHost: String = "",
val smbDefaultPort: String = "",
val showSaveEditorFilesDialog: Boolean = false,
val showStartupTabsDialog: Boolean = false,
val isSavingFiles: Boolean = false,
val selectedTabIndex: Int = 0,
val storageDevices: List<StorageDevice> = emptyList(),
val tabs: List<Tab> = emptyList(),
val tabLayoutState: LazyListState = LazyListState(),
val hasNewUpdate: Boolean = false
val hasNewUpdate: Boolean = false,
)
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import com.raival.compose.file.explorer.screen.main.MainActivity
import com.raival.compose.file.explorer.screen.main.tab.Tab
import com.raival.compose.file.explorer.screen.main.tab.files.holder.ContentHolder
import com.raival.compose.file.explorer.screen.main.tab.files.holder.LocalFileHolder
import com.raival.compose.file.explorer.screen.main.tab.files.holder.SMBFileHolder
import com.raival.compose.file.explorer.screen.main.tab.files.holder.VirtualFileHolder
import com.raival.compose.file.explorer.screen.main.tab.files.holder.ZipFileHolder
import com.raival.compose.file.explorer.screen.main.tab.files.misc.FileListCategory
Expand Down
Loading
Loading