Skip to content

Commit

Permalink
📁 Add file explorer feature (#12)
Browse files Browse the repository at this point in the history
* Can delete a file

* Fix bug, can't delete file with spaces

* Add some pictures for files

* Can use finder to upload file

* Can open save dialog

* Add logging + can download a file

* Clean up
  • Loading branch information
ThomasBernard03 authored Sep 21, 2024
1 parent dfcb244 commit 8cc2df9
Show file tree
Hide file tree
Showing 24 changed files with 405 additions and 84 deletions.
2 changes: 2 additions & 0 deletions composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ kotlin {
implementation(libs.koin.core)

implementation(libs.kotlinx.datetime)

implementation("io.klogging:klogging-jvm:0.7.2")
}
// https://gist.github.com/OysterD3?page=3
// https://betterprogramming.pub/how-to-create-an-auto-updater-for-desktop-application-jetpack-compose-d118db26d65f
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="512" android:viewportWidth="512" android:width="24dp">

<path android:fillColor="#000000" android:pathData="M120.61,169h270.79v220.66c0,13.11 -10.63,23.74 -23.72,23.74h-27.12v67.2c0,17.07 -13.61,30.9 -30.42,30.9c-16.85,0 -30.44,-13.83 -30.44,-30.9v-67.2h-47.37v67.2c0,17.07 -13.64,30.9 -30.44,30.9c-16.8,0 -30.44,-13.83 -30.44,-30.9v-67.2h-27.1c-13.1,0 -23.74,-10.63 -23.74,-23.74V169zM67.54,167.2c-16.97,0 -30.72,13.96 -30.72,31.2v121.94c0,17.22 13.75,31.2 30.72,31.2c16.98,0 30.72,-13.99 30.72,-31.2V198.4C98.26,181.16 84.52,167.2 67.54,167.2zM391.39,146.76H120.61c3.34,-38.58 28.37,-71.78 64.39,-91l-25.75,-37.8c-3.47,-5.1 -2.16,-12.05 2.95,-15.52c5.1,-3.47 12.04,-2.15 15.53,2.94l28.06,41.23c15.56,-5.38 32.45,-8.47 50.21,-8.47c17.78,0 34.67,3.09 50.23,8.48L334.29,5.39c3.45,-5.11 10.41,-6.43 15.51,-2.96c5.11,3.47 6.42,10.43 2.95,15.52l-25.73,37.8C363.05,74.98 388.05,108.18 391.39,146.76zM213.87,94.35c0,-8.27 -6.7,-14.98 -14.97,-14.98c-8.29,0 -14.99,6.71 -14.99,14.98c0,8.27 6.72,14.98 14.99,14.98S213.87,102.61 213.87,94.35zM329.99,94.35c0,-8.27 -6.72,-14.98 -14.99,-14.98c-8.29,0 -14.97,6.71 -14.97,14.98c0,8.27 6.68,14.98 14.97,14.98C323.27,109.32 329.99,102.61 329.99,94.35zM444.48,167.16c-16.96,0 -30.74,13.98 -30.74,31.22v121.98c0,17.24 13.79,31.23 30.74,31.23c16.98,0 30.7,-13.99 30.7,-31.23v-121.98C475.18,181.14 461.46,167.16 444.48,167.16z"/>

</vector>
14 changes: 12 additions & 2 deletions composeApp/src/commonMain/composeResources/drawable/file.xml
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="#000000" android:pathData="M6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8.83c0,-0.53 -0.21,-1.04 -0.59,-1.41l-4.83,-4.83c-0.37,-0.38 -0.88,-0.59 -1.41,-0.59L6,2zM13,8L13,3.5L18.5,9L14,9c-0.55,0 -1,-0.45 -1,-1z"/>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M13,3.001C12.905,3 12.797,3 12.675,3H8.2C7.08,3 6.52,3 6.092,3.218C5.715,3.41 5.41,3.715 5.218,4.092C5,4.52 5,5.08 5,6.2V17.8C5,18.92 5,19.48 5.218,19.908C5.41,20.284 5.715,20.59 6.092,20.782C6.519,21 7.079,21 8.197,21L15.803,21C16.921,21 17.48,21 17.907,20.782C18.284,20.59 18.59,20.284 18.782,19.908C19,19.48 19,18.921 19,17.804V9.326C19,9.203 19,9.096 18.999,9M13,3.001C13.286,3.003 13.466,3.014 13.639,3.055C13.843,3.104 14.038,3.185 14.217,3.295C14.419,3.419 14.592,3.592 14.938,3.938L18.063,7.063C18.409,7.409 18.581,7.581 18.705,7.783C18.814,7.962 18.895,8.157 18.944,8.361C18.986,8.534 18.996,8.715 18.999,9M13,3.001V5.8C13,6.92 13,7.48 13.218,7.908C13.41,8.284 13.715,8.59 14.092,8.782C14.519,9 15.079,9 16.197,9H18.999M18.999,9H19"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
</vector>
9 changes: 9 additions & 0 deletions composeApp/src/commonMain/composeResources/drawable/image.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="32"
android:viewportHeight="32">
<path
android:pathData="M0,26.016q0,2.496 1.76,4.224t4.256,1.76h20q2.464,0 4.224,-1.76t1.76,-4.224v-20q0,-2.496 -1.76,-4.256t-4.224,-1.76h-20q-2.496,0 -4.256,1.76t-1.76,4.256v20zM4,26.016v-20q0,-0.832 0.576,-1.408t1.44,-0.608h20q0.8,0 1.408,0.608t0.576,1.408v20q0,0.832 -0.576,1.408t-1.408,0.576h-20q-0.832,0 -1.44,-0.576t-0.576,-1.408zM6.016,24q0,0.832 0.576,1.44t1.408,0.576h16q0.832,0 1.408,-0.576t0.608,-1.44v-0.928q-0.224,-0.448 -1.12,-2.688t-1.6,-3.584 -1.28,-2.112q-0.544,-0.576 -1.12,-0.608t-1.152,0.384 -1.152,1.12 -1.184,1.568 -1.152,1.696 -1.152,1.6 -1.088,1.184 -1.088,0.448q-0.576,0 -1.664,-1.44 -0.16,-0.192 -0.48,-0.608 -1.12,-1.504 -1.6,-1.824 -0.768,-0.512 -1.184,0.352 -0.224,0.512 -0.928,2.24t-1.056,2.56v0.64zM6.016,9.024q0,1.248 0.864,2.112t2.112,0.864 2.144,-0.864 0.864,-2.112 -0.864,-2.144 -2.144,-0.864 -2.112,0.864 -0.864,2.144z"
android:fillColor="#000000"/>
</vector>
9 changes: 9 additions & 0 deletions composeApp/src/commonMain/composeResources/drawable/video.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M20.8,7.3C20,6.8 19,6.6 18.1,6.9l-2.3,0.8C15.2,6.1 13.7,5 12,5H6C3.8,5 2,6.8 2,9v6c0,2.2 1.8,4 4,4h6c1.7,0 3.2,-1.1 3.8,-2.7l2.3,0.8c0.3,0.1 0.6,0.2 1,0.2c0.6,0 1.2,-0.2 1.7,-0.6c0.8,-0.6 1.2,-1.5 1.2,-2.4V9.8C22,8.8 21.5,7.9 20.8,7.3zM12,17H6c-1.1,0 -2,-0.9 -2,-2V9c0,-1.1 0.9,-2 2,-2h6c1.1,0 2,0.9 2,2v6C14,16.1 13.1,17 12,17zM20,14.2c0,0.3 -0.2,0.6 -0.4,0.8c-0.3,0.2 -0.6,0.2 -0.9,0.1L16,14.3V9.7l2.7,-0.9C19,8.7 19.3,8.8 19.6,9C19.8,9.2 20,9.4 20,9.8V14.2z"/>
</vector>
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,15 @@ import fr.thomasbernard03.androidtools.domain.repositories.ApplicationRepository
import fr.thomasbernard03.androidtools.domain.repositories.DeviceRepository
import fr.thomasbernard03.androidtools.domain.repositories.FileRepository
import fr.thomasbernard03.androidtools.domain.repositories.LogcatRepository
import io.klogging.Klogger
import io.klogging.logger
import org.koin.dsl.module

val androidToolsModule = module {
// Logger
single<Klogger> { logger("main") }


// Datasource
single { ShellDataSource() }

Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,24 @@
package fr.thomasbernard03.androidtools.data.datasources

import androidtools.composeapp.generated.resources.Res
import com.russhwolf.settings.Settings
import fr.thomasbernard03.androidtools.commons.SettingsConstants
import fr.thomasbernard03.androidtools.commons.helpers.AdbProviderHelper
import io.klogging.Klogger
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.withContext
import org.jetbrains.compose.resources.ExperimentalResourceApi
import org.koin.java.KoinJavaComponent.get
import java.io.BufferedReader
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
import java.io.InputStreamReader
import java.util.zip.ZipInputStream

class ShellDataSource(
private val settings: Settings = Settings(),
private val logger : Klogger = get(Klogger::class.java),
private val adbProviderHelper: AdbProviderHelper = get(AdbProviderHelper::class.java)
) {
suspend fun executeAdbCommand(vararg formatArgs: String): String = withContext(Dispatchers.IO) {
logger.info("*** Start executing adb command ***")
val arguments = mutableListOf<String>()

val currentDevice = settings.getStringOrNull(key = SettingsConstants.SELECTED_DEVICE_KEY)
Expand All @@ -34,6 +31,8 @@ class ShellDataSource(
arguments.addAll(formatArgs)

val adb = adbProviderHelper.getAdb()
logger.debug("Adb path: ${adb.absolutePath}")
logger.info("${adb.absolutePath} ${arguments.joinToString(" ")}")

val process = ProcessBuilder(listOf(adb.absolutePath) + arguments).start()
val reader = BufferedReader(InputStreamReader(process.inputStream))
Expand All @@ -45,6 +44,10 @@ class ShellDataSource(

process.waitFor()

logger.info("$output")

logger.info("*** End of adb command ***")

return@withContext output.toString()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package fr.thomasbernard03.androidtools.data.repositories

import fr.thomasbernard03.androidtools.commons.SettingsConstants
import fr.thomasbernard03.androidtools.data.datasources.ShellDataSource
import fr.thomasbernard03.androidtools.domain.models.DeviceInformation
import fr.thomasbernard03.androidtools.domain.repositories.DeviceRepository
Expand All @@ -11,8 +10,6 @@ import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.withContext
import org.koin.java.KoinJavaComponent.get
import java.io.BufferedReader
import java.io.InputStreamReader

class DeviceRepositoryImpl(
private val shellDataSource: ShellDataSource = get(ShellDataSource::class.java)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,12 @@ class FileRepositoryImpl(
override suspend fun uploadFile(path: String, targetPath : String) {
shellDataSource.executeAdbCommand("push", path, targetPath)
}

override suspend fun deleteFile(path: String) {
shellDataSource.executeAdbCommand("shell", "rm", "'$path'")
}

override suspend fun downloadFile(path: String, targetPath: String) {
shellDataSource.executeAdbCommand("pull", path, targetPath)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
package fr.thomasbernard03.androidtools.domain.repositories

interface FileRepository {
/**
* Upload a file from the computer to the android device
* @param path the path of the file to upload (On the computer)
* @param targetPath the path where to upload the file (On the android device)
*/
suspend fun uploadFile(path: String, targetPath : String)


/**
* Download a file from the android device to the computer
* @param path the path of the file to download (On the android device)
* @param targetPath the path where to download the file (On the computer)
*/
suspend fun downloadFile(path: String, targetPath : String)



suspend fun deleteFile(path: String)
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
package fr.thomasbernard03.androidtools.domain.usecases.device

import com.russhwolf.settings.Settings
import fr.thomasbernard03.androidtools.commons.SettingsConstants
import fr.thomasbernard03.androidtools.domain.models.DeviceInformation
import fr.thomasbernard03.androidtools.domain.repositories.DeviceRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.koin.java.KoinJavaComponent.get
import java.io.BufferedReader
import java.io.InputStreamReader

class GetDeviceInformationUseCase(
private val deviceRepository: DeviceRepository = get(DeviceRepository::class.java)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package fr.thomasbernard03.androidtools.domain.usecases.file

import fr.thomasbernard03.androidtools.domain.repositories.FileRepository
import org.koin.java.KoinJavaComponent.get

class DeleteFileUseCase(
private val fileRepository: FileRepository = get(FileRepository::class.java)
) {
suspend operator fun invoke(path: String) {
fileRepository.deleteFile(path)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package fr.thomasbernard03.androidtools.domain.usecases.file

import fr.thomasbernard03.androidtools.domain.repositories.FileRepository
import org.koin.java.KoinJavaComponent.get

class DownloadFileUseCase(
private val fileRepository: FileRepository = get(FileRepository::class.java)
) {
suspend operator fun invoke(path: String, targetPath: String) {
fileRepository.downloadFile(path, targetPath)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@ import fr.thomasbernard03.androidtools.commons.di.androidToolsModule
import fr.thomasbernard03.androidtools.presentation.main.MainScreen
import fr.thomasbernard03.androidtools.presentation.main.MainViewModel
import fr.thomasbernard03.androidtools.presentation.theme.AndroidToolsTheme
import io.klogging.config.DEFAULT_CONSOLE
import io.klogging.config.loggingConfiguration
import org.jetbrains.compose.resources.stringResource
import org.koin.core.context.GlobalContext.startKoin

fun main() = application {
startKoin { modules(androidToolsModule) }
loggingConfiguration { DEFAULT_CONSOLE() }

Window(
onCloseRequest = ::exitApplication,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package fr.thomasbernard03.androidtools.presentation.commons.extensions

import androidtools.composeapp.generated.resources.Res
import androidtools.composeapp.generated.resources.android
import androidtools.composeapp.generated.resources.file
import androidtools.composeapp.generated.resources.image
import androidtools.composeapp.generated.resources.video
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import fr.thomasbernard03.androidtools.presentation.theme.AndroidColor
import fr.thomasbernard03.androidtools.presentation.theme.FileColor
import fr.thomasbernard03.androidtools.presentation.theme.ImageColor
import fr.thomasbernard03.androidtools.presentation.theme.VideoColor
import org.jetbrains.compose.resources.painterResource

@Composable
fun String.fileIcon() {
if (this.endsWith(".apk")) {
Icon(
painter = painterResource(Res.drawable.android),
contentDescription = "Android icon",
tint = AndroidColor,
modifier = Modifier.size(24.dp)
)
}
else if (this.endsWith(".mp4") || this.endsWith(".avi") || this.endsWith(".mkv")) {
Icon(
painter = painterResource(Res.drawable.video),
contentDescription = "Video icon",
modifier = Modifier.size(24.dp),
tint = VideoColor
)
}
// else if (this.endsWith(".mp3") || this.endsWith(".wav") || this.endsWith(".flac")) {
// Icon(
// painter = painterResource(Res.drawable.music),
// contentDescription = "",
// modifier = Modifier.size(24.dp),
// tint = MaterialTheme.colorScheme.onBackground
// )
// }
else if (this.endsWith(".jpg", ignoreCase = true) || this.endsWith(".jpeg") || this.endsWith(".png", ignoreCase = true) || this.endsWith(".gif")) {
Icon(
painter = painterResource(Res.drawable.image),
contentDescription = "",
modifier = Modifier.size(24.dp).padding(4.dp),
tint = ImageColor
)
}
else {
Icon(
painter = painterResource(Res.drawable.file),
contentDescription = "",
modifier = Modifier.size(24.dp),
tint = FileColor
)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package fr.thomasbernard03.androidtools.presentation.fileexplorer

import fr.thomasbernard03.androidtools.domain.models.File
import fr.thomasbernard03.androidtools.domain.models.Folder
import fr.thomasbernard03.androidtools.presentation.commons.Event

Expand All @@ -12,4 +13,8 @@ sealed class FileExplorerEvent : Event {
data object OnRefresh : FileExplorerEvent()

data class OnAddFile(val path : String) : FileExplorerEvent()
data class OnDelete(val path : String) : FileExplorerEvent()
data class OnDownload(val path : String, val targetPath : String) : FileExplorerEvent()

data class OnFileSelected(val file : File?) : FileExplorerEvent()
}
Loading

0 comments on commit 8cc2df9

Please sign in to comment.