Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/explore #22

Merged
merged 13 commits into from
Dec 26, 2024
Merged
6 changes: 6 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
android:minSdkVersion="30" />

<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
Expand Down
14 changes: 14 additions & 0 deletions app/src/main/java/kr/co/seedocs/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,24 @@ import kr.co.main.Main
import kr.co.ui.theme.SeeDocsTheme

class MainActivity : ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()

}

override fun onResume() {
super.onResume()

if (checkStoragePermission(this)) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onResume에서 storage permission을 체크하여 setContent를 호출하는 것은 바람직하지 않은 구조같습니다.
권한에 따라 UI의 상태가 변경되어야 한다면, 그에 따른 UI 상태를 Composable에 넘겨주어 리컴포지션할 수 있도록 구조를 변경하는게 좋을 것 같습니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

추상 클래스로 변경했습니다

setContent()
} else {
requestStoragePermission(this)
}
}

private fun setContent() {
setContent {
SeeDocsTheme {
Main()
Expand Down
71 changes: 71 additions & 0 deletions app/src/main/java/kr/co/seedocs/PermissionUtil.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package kr.co.seedocs

import android.Manifest
import android.app.AppOpsManager
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.provider.Settings
import androidx.activity.ComponentActivity
import androidx.annotation.RequiresApi
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat

private const val MANAGE_EXTERNAL_STORAGE_PERMISSION = "android:manage_external_storage"
private const val MANAGE_EXTERNAL_STORAGE_PERMISSION_REQUEST = 100
private const val READ_EXTERNAL_STORAGE_PERMISSION_REQUEST = 101

internal fun checkStoragePermission(
activity: ComponentActivity,
): Boolean =
Copy link

@f-lab-dean f-lab-dean Dec 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Util함수로 구현하는 것 보다, 외부 스토리지 권한 로직을 캡슐화한 클래스 또는 인터페이스로 제공하는 것이 좋을 것 같습니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

추상 클래스로 변경 했습니다

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
checkStoragePermissionApi30(activity)
} else {
checkStoragePermissionApi19(activity)
}

@RequiresApi(Build.VERSION_CODES.R)
private fun checkStoragePermissionApi30(activity: ComponentActivity): Boolean {
val appOps = activity.getSystemService(AppOpsManager::class.java)
val mode = appOps.unsafeCheckOpNoThrow(
MANAGE_EXTERNAL_STORAGE_PERMISSION,
activity.applicationInfo.uid,
activity.packageName
)

return mode == AppOpsManager.MODE_ALLOWED
}

private fun checkStoragePermissionApi19(activity: ComponentActivity): Boolean {
val status =
ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE)

return status == PackageManager.PERMISSION_GRANTED
}

internal fun requestStoragePermission(activity: ComponentActivity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
requestStoragePermissionApi30(activity)
}
else {
requestStoragePermissionApi19(activity)
}
}

@RequiresApi(Build.VERSION_CODES.R)
private fun requestStoragePermissionApi30(
activity: ComponentActivity
) {
val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)

activity.startActivityForResult(intent, MANAGE_EXTERNAL_STORAGE_PERMISSION_REQUEST)
}

fun requestStoragePermissionApi19(activity: ComponentActivity) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 함수가 public인 이유가 무엇일까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

단순 실수였습니다 바로 위 PR에서 수정했습니다

val permissions = arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE)
ActivityCompat.requestPermissions(
activity,
permissions,
READ_EXTERNAL_STORAGE_PERMISSION_REQUEST
)
}
7 changes: 6 additions & 1 deletion core/ui/src/main/java/kr/co/ui/widget/FileBox.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import kr.co.ui.icon.SeeDocsIcon
Expand All @@ -33,7 +35,10 @@ fun FileBox(
.padding(
vertical = 12.dp,
horizontal = 8.dp
),
)
.semantics {
contentDescription = "$name"
},
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(24.dp)
) {
Expand Down
48 changes: 40 additions & 8 deletions feature/explore/src/main/java/kr/co/explore/ExploreScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,39 @@ import kr.co.ui.theme.SeeDocsTheme
import kr.co.ui.theme.Theme
import kr.co.ui.widget.FileBox
import kr.co.widget.FolderBox
import java.io.File

@Composable
internal fun ExploreRoute(
path: String,
padding: PaddingValues,
navigateToPdf: () -> Unit = {}
navigateToFolder: (String) -> Unit = {},
navigateToPdf: () -> Unit = {},
) {
val files = readPDFOrDirectory(path)

ExploreScreen(
path = path.replace("/storage/emulated/0", "내장 저장 공간"),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 컴포저블의 하드코딩된 부분은 나중에 구현되나요?

files = files,
padding = padding,
onFolderClick = { folderPath -> navigateToFolder(folderPath) },
onFileClick = navigateToPdf
)
}

@Composable
private fun ExploreScreen(
path: String,
files: List<Item> = emptyList(),
padding: PaddingValues,
onFolderClick: (String) -> Unit = {},
onFileClick: () -> Unit = {}
) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(padding)
.background(color = Theme.colors.bg)
.background(color = Theme.colors.bg),
) {
LazyVerticalGrid(
modifier = Modifier
Expand All @@ -71,12 +81,13 @@ private fun ExploreScreen(
)
Text(
text = buildAnnotatedString {
append("규상의 S24 >")
append(">${path.split("/").dropLast(1).joinToString(separator = "/")}")
append("/")
withStyle(
Theme.typography.caption1r.copy(color = Theme.colors.highlight)
.toSpanStyle()
) {
append("Download")
append(path.split("/").last())
}
},
style = Theme.typography.caption1r,
Expand All @@ -85,30 +96,51 @@ private fun ExploreScreen(
}
}

items(listOf("Download", "Documents", "DCIM")) { folder ->
items(files.filter { it.isDirectory }) { folder ->
FolderBox(
name = folder
name = folder.name,
onClick = { onFolderClick(folder.path) }
)
}

items(
items = listOf("Effective Kotlin", "Android Developer"),
items = files.filter { !it.isDirectory },
span = { GridItemSpan(maxLineSpan) }
) { file ->
FileBox(
name = file,
name = file.name,
onFileClick = onFileClick
)
}
}
}
}

private fun readPDFOrDirectory(
path: String,
): List<Item> =
File(path).listFiles()?.filter { !it.isHidden && (it.isDirectory || it.extension == "pdf") }?.map {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 함수를 UI 쓰레드에서 호출해도 괜찮을까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

백그라운드 스레드에서 호출하는게 맞을 것 같네요

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네, TODO 등의 코멘트를 달고 다른 PR에서 수정하시면 될 것 같습니다.

Item(
name = it.name,
path = it.path,
type = it.extension,
isDirectory = it.isDirectory
)
}?: emptyList()

private data class Item(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI 모델이라면 분리된 파일 또는 패키지에 위치하는 것은 어떤가요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여러 화면에서 사용될 모델 이라 생각해서 임시로 안에 적은 뒤
다른 화면 로컬 파일 설정등을 하며 데이터 윤곽이 잡히면 수정할 계획이었습니다.

val name: String,
val path: String,
val type: String,
val isDirectory: Boolean,
)

@Preview
@Composable
private fun Preview() {
SeeDocsTheme {
ExploreScreen(
path = "/storage/emulated/0/Download",
padding = PaddingValues(),
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
package kr.co.navigation

import android.os.Environment
import androidx.compose.foundation.layout.PaddingValues
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import androidx.navigation.toRoute
import kr.co.explore.ExploreRoute

fun NavGraphBuilder.exploreNavGraph(
padding: PaddingValues,
navigateToFolder: (String) -> Unit = {},
navigateToPdf: () -> Unit = {}
) {
composable<MainNavigation.Explore> {
ExploreRoute(
path = it.toRoute<MainNavigation.Explore>().path?: Environment.getExternalStorageDirectory().absolutePath,
padding = padding,
navigateToFolder = navigateToFolder,
navigateToPdf = navigateToPdf
)
}
Expand Down
5 changes: 5 additions & 0 deletions feature/explore/src/main/java/kr/co/widget/FolderBox.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package kr.co.widget

import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
Expand All @@ -13,6 +14,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
Expand All @@ -25,9 +27,12 @@ import kr.co.ui.theme.Theme
internal fun FolderBox(
name: String,
modifier: Modifier = Modifier,
onClick: () -> Unit = {}
) {
Box(
modifier = modifier
.clip(RoundedCornerShape(8.dp))
.clickable(onClick = onClick)
.border(
width = 1.dp,
color = Theme.colors.grayText,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package kr.co.main.navigation
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.navigation.compose.NavHost
import kr.co.navigation.MainNavigation
import kr.co.navigation.Route
import kr.co.navigation.bookmarkNavGraph
import kr.co.navigation.exploreNavGraph
Expand All @@ -20,7 +21,8 @@ internal fun MainNavHost(
) {
exploreNavGraph(
padding = padding,
navigateToPdf = { navigator.navigate(Route.Pdf){null} }
navigateToFolder = { navigator.navigate(MainNavigation.Explore(it)) },
navigateToPdf = { navigator.navigate(Route.Pdf) }
)

recentNavGraph(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ internal class MainNavigator(
@Composable get() = navController
.currentBackStackEntryAsState().value?.destination

val startDestination: MainNavigation = MainNavigation.Explore
val startDestination: MainNavigation = MainNavigation.Explore()

@Composable
fun currentTab(): MainTab? =
Expand All @@ -47,7 +47,7 @@ internal class MainNavigator(
navController.navigate(T::class, navOptions(builder))
}

fun <T : Any> navigate(route: T, builder: NavOptionsBuilder.() -> Unit) {
fun <T : Any> navigate(route: T, builder: NavOptionsBuilder.() -> Unit = {}) {
navController.navigate(route, navOptions(builder))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ internal enum class MainTab(
Explore(
icon = SeeDocsIcon.Explore,
contentDescription = "Explore",
route = MainNavigation.Explore,
route = MainNavigation.Explore(),
),
Recent(
icon = SeeDocsIcon.RecentFill,
Expand Down
Loading