Skip to content

Commit

Permalink
Merge pull request #1 from reysand/dev
Browse files Browse the repository at this point in the history
 Implement a file manager with fundamental features
  • Loading branch information
reysand authored Nov 11, 2023
2 parents ae3c78f + 742f910 commit ec10ced
Show file tree
Hide file tree
Showing 39 changed files with 2,290 additions and 56 deletions.
26 changes: 26 additions & 0 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Android CI

on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- name: set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: gradle

- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build
2 changes: 1 addition & 1 deletion .idea/kotlinc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 25 additions & 20 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
alias libs.plugins.android.application
alias libs.plugins.kotlin.android
}

android {
Expand All @@ -11,8 +11,8 @@ android {
applicationId "com.reysand.files"
minSdk 33
targetSdk 33
versionCode 1
versionName "0.0.1"
versionCode 3
versionName "0.1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
Expand All @@ -37,7 +37,7 @@ android {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion '1.4.3'
kotlinCompilerExtensionVersion '1.5.4'
}
packaging {
resources {
Expand All @@ -48,19 +48,24 @@ android {

dependencies {

implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'
implementation 'androidx.activity:activity-compose:1.7.2'
implementation platform('androidx.compose:compose-bom:2023.03.00')
implementation 'androidx.compose.ui:ui'
implementation 'androidx.compose.ui:ui-graphics'
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'androidx.compose.material3:material3'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation platform('androidx.compose:compose-bom:2023.03.00')
androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
debugImplementation 'androidx.compose.ui:ui-tooling'
debugImplementation 'androidx.compose.ui:ui-test-manifest'
implementation libs.core.ktx
implementation libs.lifecycle.runtime.ktx
implementation libs.activity.compose

implementation platform(libs.compose.bom)
implementation libs.ui
implementation libs.ui.graphics
implementation libs.ui.tooling.preview
implementation libs.material3
implementation libs.lifecycle.viewmodel.compose
implementation libs.navigation.compose

testImplementation libs.junit
androidTestImplementation libs.androidx.test.ext.junit
androidTestImplementation libs.espresso.core
androidTestImplementation platform(libs.compose.bom)
androidTestImplementation libs.ui.test.junit4

debugImplementation libs.ui.tooling
debugImplementation libs.ui.test.manifest
}
9 changes: 8 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,16 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<!-- Requesting all-files access due to Android 11 (API level 30) changes. -->
<uses-permission
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />

<application
android:name=".FilesApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
Expand Down
30 changes: 30 additions & 0 deletions app/src/main/java/com/reysand/files/FilesApplication.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2023 Andrey Slyusar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.reysand.files

import android.app.Application
import com.reysand.files.data.AppContainer
import com.reysand.files.data.DefaultAppContainer

class FilesApplication : Application() {

lateinit var container: AppContainer

override fun onCreate() {
super.onCreate()
container = DefaultAppContainer()
}
}
33 changes: 3 additions & 30 deletions app/src/main/java/com/reysand/files/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,44 +18,17 @@ package com.reysand.files
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.reysand.files.ui.FilesApp
import com.reysand.files.ui.theme.FilesTheme

class MainActivity : ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
FilesTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Greeting("Android")
}
FilesApp()
}
}
}
}

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
FilesTheme {
Greeting("Android")
}
}
31 changes: 31 additions & 0 deletions app/src/main/java/com/reysand/files/data/AppContainer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright 2023 Andrey Slyusar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.reysand.files.data

import com.reysand.files.data.local.FileLocalDataSource
import com.reysand.files.data.repository.FileRepository

interface AppContainer {

val fileRepository: FileRepository
}

class DefaultAppContainer : AppContainer {

override val fileRepository: FileRepository by lazy {
FileLocalDataSource()
}
}
125 changes: 125 additions & 0 deletions app/src/main/java/com/reysand/files/data/local/FileLocalDataSource.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Copyright 2023 Andrey Slyusar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.reysand.files.data.local

import android.os.Environment
import com.reysand.files.data.model.FileModel
import com.reysand.files.data.repository.FileRepository
import com.reysand.files.data.util.FileSizeFormatter
import java.io.File

/**
* Implementation of [FileRepository] for accessing local files.
*/
class FileLocalDataSource : FileRepository {

override fun getStorageFreeSpace(): String {
val availableBytes = Environment.getExternalStorageDirectory().freeSpace
return FileSizeFormatter.getFormattedSize(availableBytes)
}

override suspend fun getFiles(path: String): List<FileModel> {
// List files in the specified path
val files = File(path).listFiles()

// Separate directories and other files
val directories = mutableListOf<FileModel>()
val others = mutableListOf<FileModel>()

files?.forEach { file ->
val fileModel = createFileModel(file)
when (fileModel.fileType) {
FileModel.FileType.DIRECTORY -> directories.add(fileModel)
else -> others.add(fileModel)
}
}

// Sort directories and other files separately
directories.sortBy { it.name.lowercase() }
others.sortBy { it.name.lowercase() }

// Combine and return the sorted list
return directories + others
}

override suspend fun moveFile(source: String, destination: String): Boolean {
return renameFile(source, destination)
}

override suspend fun copyFile(source: String, destination: String): Boolean {
return operateOnFile(source, destination) { sourceFile, destinationFile ->
sourceFile.copyRecursively(destinationFile, true)
}
}

override suspend fun renameFile(from: String, to: String): Boolean {
return operateOnFile(from, to) { sourceFile, destinationFile ->
sourceFile.renameTo(destinationFile)
}
}

override suspend fun deleteFile(path: String): Boolean {
return File(path).deleteRecursively()
}

/**
* Creates a [FileModel] object from a [File] instance.
*
* @param file The [File] instance to create a [FileModel] from.
* @return A [FileModel] object representing the given file.
*/
private fun createFileModel(file: File): FileModel {
return FileModel(
name = file.name,
path = file.path,
fileType = when (file.isDirectory) {
true -> FileModel.FileType.DIRECTORY
else -> FileModel.FileType.OTHER
},
size = file.length(),
lastModified = file.lastModified()
)
}

/**
* Performs an operation on a file.
*
* @param source The path of the source file.
* @param destination The path of the destination file.
* @param operation The operation to perform on the file.
* @return A boolean value indicating the success of the operation.
*/
private fun operateOnFile(
source: String,
destination: String,
operation: (File, File) -> Boolean
): Boolean {
return try {
val sourceFile = File(source)
val destinationFile = File(destination)

if (operation(sourceFile, destinationFile)) {
createFileModel(destinationFile)
true
} else {
false
}
} catch (e: Exception) {
e.printStackTrace()
false
}
}
}
Loading

0 comments on commit ec10ced

Please sign in to comment.