From 6e295f21d445dd69ea0b1e42c04237652984eef6 Mon Sep 17 00:00:00 2001 From: kkyoungchan Date: Mon, 23 Jun 2025 19:50:22 +0900 Subject: [PATCH 1/2] assignment09 --- .idea/.gitignore | 3 + .idea/.name | 1 + .idea/AndroidProjectSystem.xml | 6 + .idea/compiler.xml | 6 + .idea/deploymentTargetSelector.xml | 10 ++ .idea/encodings.xml | 6 + .idea/gradle.xml | 19 +++ .idea/kotlinc.xml | 6 + .idea/migrations.xml | 10 ++ .idea/misc.xml | 10 ++ .idea/runConfigurations.xml | 17 +++ .idea/vcs.xml | 6 + app/build.gradle.kts | 17 ++- app/src/main/AndroidManifest.xml | 2 + .../bcsd_android_2025_1/MainActivity.kt | 115 +++++++++++++++++- .../bcsd_android_2025_1/MusicAdapter.kt | 38 ++++++ .../bcsd_android_2025_1/ui/theme/Color.kt | 11 ++ .../bcsd_android_2025_1/ui/theme/Theme.kt | 58 +++++++++ .../bcsd_android_2025_1/ui/theme/Type.kt | 34 ++++++ app/src/main/res/layout/activity_main.xml | 40 ++++-- app/src/main/res/layout/item_music.xml | 29 +++++ app/src/main/res/values/strings.xml | 12 ++ build.gradle.kts | 1 + gradle/libs.versions.toml | 14 +++ 24 files changed, 458 insertions(+), 13 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/.name create mode 100644 .idea/AndroidProjectSystem.xml create mode 100644 .idea/compiler.xml create mode 100644 .idea/deploymentTargetSelector.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/gradle.xml create mode 100644 .idea/kotlinc.xml create mode 100644 .idea/migrations.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/runConfigurations.xml create mode 100644 .idea/vcs.xml create mode 100644 app/src/main/java/com/example/bcsd_android_2025_1/MusicAdapter.kt create mode 100644 app/src/main/java/com/example/bcsd_android_2025_1/ui/theme/Color.kt create mode 100644 app/src/main/java/com/example/bcsd_android_2025_1/ui/theme/Theme.kt create mode 100644 app/src/main/java/com/example/bcsd_android_2025_1/ui/theme/Type.kt create mode 100644 app/src/main/res/layout/item_music.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..c4e4683 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +BCSD_Android_2025-1 \ No newline at end of file diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml new file mode 100644 index 0000000..4a53bee --- /dev/null +++ b/.idea/AndroidProjectSystem.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b86273d --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..b268ef3 --- /dev/null +++ b/.idea/deploymentTargetSelector.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..97626ba --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..639c779 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..c224ad5 --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..74dd639 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..16660f1 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5791375..81271fd 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,11 +1,12 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.compose) } android { namespace = "com.example.bcsd_android_2025_1" - compileSdk = 34 + compileSdk = 35 defaultConfig { applicationId = "com.example.bcsd_android_2025_1" @@ -33,6 +34,9 @@ android { kotlinOptions { jvmTarget = "11" } + buildFeatures { + compose = true + } } dependencies { @@ -42,7 +46,18 @@ dependencies { implementation(libs.material) implementation(libs.androidx.activity) implementation(libs.androidx.constraintlayout) + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.activity.compose) + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.ui) + implementation(libs.androidx.ui.graphics) + implementation(libs.androidx.ui.tooling.preview) + implementation(libs.androidx.material3) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(platform(libs.androidx.compose.bom)) + androidTestImplementation(libs.androidx.ui.test.junit4) + debugImplementation(libs.androidx.ui.tooling) + debugImplementation(libs.androidx.ui.test.manifest) } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4c80941..16f4cde 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,8 @@ + + = Build.VERSION_CODES.TIRAMISU) + Manifest.permission.READ_MEDIA_AUDIO + else + Manifest.permission.READ_EXTERNAL_STORAGE + + private val requestPermission = + registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> + if (isGranted) { + showMusicList() + } else { + if (shouldShowRequestPermissionRationale(permission)) { + showPermissionDialog() + } else { + showSettingsButton() + } + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) + + settingsBtn = findViewById(R.id.button_goto_settings) + explanationText = findViewById(R.id.textview_permission) + + settingsBtn.setOnClickListener { + val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { + data = Uri.parse("package:$packageName") + } + startActivity(intent) + } + + checkPermissionAndRequest() + } + + private fun checkPermissionAndRequest() { + if (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED) { + showMusicList() + } else { + requestPermission.launch(permission) + } + } + + private fun showPermissionDialog() { + AlertDialog.Builder(this) + .setTitle(R.string.permdialog_title) + .setMessage(R.string.permdialog_title) + .setPositiveButton(R.string.permdialog_pos) { _, _ -> + requestPermission.launch(permission) + } + .setNegativeButton(R.string.permdialog_neg, null) + .show() + } + + private fun showSettingsButton() { + explanationText.visibility = View.VISIBLE + settingsBtn.visibility = View.VISIBLE + } + + private fun showMusicList() { + val musicList = mutableListOf>() + + val projection = arrayOf( + MediaStore.Audio.Media.TITLE, + MediaStore.Audio.Media.ARTIST, + MediaStore.Audio.Media.DURATION + ) + + val selection = "${MediaStore.Audio.Media.IS_MUSIC} != 0" + val sortOrder = "${MediaStore.Audio.Media.TITLE} ASC" + + val cursor = contentResolver.query( + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, + projection, + selection, + null, + sortOrder + ) + cursor?.use { + val titleIndex = it.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE) + val artistIndex = it.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST) + val timeIndex = it.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION) + + while (it.moveToNext()) { + val title = it.getString(titleIndex) ?: "제목없음" + val artist = it.getString(artistIndex) ?: "가수없음" + val time = it.getLong(timeIndex) + musicList.add(Triple(title, artist, time)) + } + } + + val recyclerView = findViewById(R.id.recyclerView) + recyclerView.adapter = MusicAdapter(musicList) + recyclerView.layoutManager = LinearLayoutManager(this) } } \ No newline at end of file diff --git a/app/src/main/java/com/example/bcsd_android_2025_1/MusicAdapter.kt b/app/src/main/java/com/example/bcsd_android_2025_1/MusicAdapter.kt new file mode 100644 index 0000000..21b937b --- /dev/null +++ b/app/src/main/java/com/example/bcsd_android_2025_1/MusicAdapter.kt @@ -0,0 +1,38 @@ +package com.example.bcsd_android_2025_1 +import android.Manifest +import android.content.Intent +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView + +class MusicAdapter(private val musicList: List>) : + RecyclerView.Adapter() { + + inner class MusicViewHolder(view: View) : RecyclerView.ViewHolder(view) { + val title = view.findViewById(R.id.tv_title) + val artist = view.findViewById(R.id.tv_artist) + val time = view.findViewById(R.id.tv_time) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MusicViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.item_music, parent, false) + return MusicViewHolder(view) + } + + override fun onBindViewHolder(holder: MusicViewHolder, position: Int) { + val (title, artist, time) = musicList[position] + holder.title.text = title + holder.artist.text = artist + holder.time.text = settime(time) + } + + override fun getItemCount() = musicList.size + + private fun settime(timeMs: Long): String { + val min = timeMs / 1000 / 60 + val sec = (timeMs / 1000 % 60) + return String.format("%02d:%02d", min, sec) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/bcsd_android_2025_1/ui/theme/Color.kt b/app/src/main/java/com/example/bcsd_android_2025_1/ui/theme/Color.kt new file mode 100644 index 0000000..e897c79 --- /dev/null +++ b/app/src/main/java/com/example/bcsd_android_2025_1/ui/theme/Color.kt @@ -0,0 +1,11 @@ +package com.example.bcsd_android_2025_1.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple80 = Color(0xFFD0BCFF) +val PurpleGrey80 = Color(0xFFCCC2DC) +val Pink80 = Color(0xFFEFB8C8) + +val Purple40 = Color(0xFF6650a4) +val PurpleGrey40 = Color(0xFF625b71) +val Pink40 = Color(0xFF7D5260) \ No newline at end of file diff --git a/app/src/main/java/com/example/bcsd_android_2025_1/ui/theme/Theme.kt b/app/src/main/java/com/example/bcsd_android_2025_1/ui/theme/Theme.kt new file mode 100644 index 0000000..9a49c51 --- /dev/null +++ b/app/src/main/java/com/example/bcsd_android_2025_1/ui/theme/Theme.kt @@ -0,0 +1,58 @@ +package com.example.bcsd_android_2025_1.ui.theme + +import android.app.Activity +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext + +private val DarkColorScheme = darkColorScheme( + primary = Purple80, + secondary = PurpleGrey80, + tertiary = Pink80 +) + +private val LightColorScheme = lightColorScheme( + primary = Purple40, + secondary = PurpleGrey40, + tertiary = Pink40 + + /* Other default colors to override + background = Color(0xFFFFFBFE), + surface = Color(0xFFFFFBFE), + onPrimary = Color.White, + onSecondary = Color.White, + onTertiary = Color.White, + onBackground = Color(0xFF1C1B1F), + onSurface = Color(0xFF1C1B1F), + */ +) + +@Composable +fun BCSD_Android_20251Theme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = true, + content: @Composable () -> Unit +) { + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/bcsd_android_2025_1/ui/theme/Type.kt b/app/src/main/java/com/example/bcsd_android_2025_1/ui/theme/Type.kt new file mode 100644 index 0000000..e350104 --- /dev/null +++ b/app/src/main/java/com/example/bcsd_android_2025_1/ui/theme/Type.kt @@ -0,0 +1,34 @@ +package com.example.bcsd_android_2025_1.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) + /* Other default text styles to override + titleLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp + ), + labelSmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp + ) + */ +) \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 311f3cb..11ae4c5 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,19 +1,43 @@ - + android:layout_height="match_parent"> + + + +