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/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/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..22b0bb4 --- /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..b2c751a --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ 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..58c2428 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,16 +1,17 @@ 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" minSdk = 26 - targetSdk = 34 + targetSdk = 35 versionCode = 1 versionName = "1.0" @@ -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..de0c4d8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,10 @@ + + + + + + - diff --git a/app/src/main/java/com/example/bcsd_android_2025_1/MainActivity.kt b/app/src/main/java/com/example/bcsd_android_2025_1/MainActivity.kt index 3ffa0eb..ee77688 100644 --- a/app/src/main/java/com/example/bcsd_android_2025_1/MainActivity.kt +++ b/app/src/main/java/com/example/bcsd_android_2025_1/MainActivity.kt @@ -1,14 +1,155 @@ package com.example.bcsd_android_2025_1 +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Build import android.os.Bundle -import androidx.activity.enableEdgeToEdge +import android.provider.MediaStore +import android.view.View +import android.widget.Button +import android.widget.TextView +import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat +import androidx.appcompat.app.AlertDialog +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.example.bcsd_android_2025_1.model.MusicData class MainActivity : AppCompatActivity() { + + private lateinit var recyclerView: RecyclerView + private lateinit var permissionMessage: TextView + private lateinit var openSettingsButton: Button + private lateinit var musicAdapter: MusicAdapter + + private val musicList = mutableListOf() + + private val requestPermissionLauncher = registerForActivityResult( + ActivityResultContracts.RequestPermission() + ) { isGranted -> + if (isGranted) { + showMusicList() + } else { + showPermissionDialog() + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) + + recyclerView = findViewById(R.id.MusicRecyclerView) + permissionMessage = findViewById(R.id.text1) + openSettingsButton = findViewById(R.id.OpenButton) + + musicAdapter = MusicAdapter(musicList) + recyclerView.adapter = musicAdapter + recyclerView.layoutManager = LinearLayoutManager(this) + + recyclerView.visibility = View.GONE + permissionMessage.visibility = View.GONE + openSettingsButton.visibility = View.GONE + + if (hasPermission()) { + showMusicList() + } else { + requestPermission() + } + + openSettingsButton.setOnClickListener { + val intent = Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS) + val uri = Uri.fromParts("package", packageName, null) + intent.data = uri + startActivity(intent) + } + } + + private fun hasPermission(): Boolean { + val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + android.Manifest.permission.READ_MEDIA_AUDIO + } else { + android.Manifest.permission.READ_EXTERNAL_STORAGE + } + return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED + } + + private fun requestPermission() { + val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + android.Manifest.permission.READ_MEDIA_AUDIO + } else { + android.Manifest.permission.READ_EXTERNAL_STORAGE + } + + when { + ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED -> { + showMusicList() + } + shouldShowRequestPermissionRationale(permission) -> { + showPermissionDialog() + } + else -> { + requestPermissionLauncher.launch(permission) + } + } + } + + private fun showPermissionDialog() { + AlertDialog.Builder(this) + .setTitle(getString(R.string.permission_dialog_title)) + .setMessage(getString(R.string.permission_dialog_message)) + .setPositiveButton(getString(R.string.ok)) { dialog, _ -> + dialog.dismiss() + showPermissionUI() + } + .setNegativeButton(getString(R.string.cancel)) { dialog, _ -> + dialog.dismiss() + } + .show() + } + + private fun showPermissionUI() { + recyclerView.visibility = View.GONE + permissionMessage.visibility = View.VISIBLE + openSettingsButton.visibility = View.VISIBLE + } + + private fun showMusicList() { + recyclerView.visibility = View.VISIBLE + permissionMessage.visibility = View.GONE + openSettingsButton.visibility = View.GONE + + loadMusicList() + } + + private fun loadMusicList() { + musicList.clear() + + val uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + val projection = arrayOf( + MediaStore.Audio.Media.TITLE, + MediaStore.Audio.Media.ARTIST, + MediaStore.Audio.Media.DURATION + ) + + val cursor = contentResolver.query(uri, projection, null, null, null) + cursor?.use { + while (it.moveToNext()) { + val title = it.getString(0) + val artist = it.getString(1) + val duration = it.getLong(2) + musicList.add(MusicData(title, artist, duration)) + } + } + + musicAdapter.notifyDataSetChanged() + } + + override fun onResume() { + super.onResume() + if (hasPermission()) { + showMusicList() + } } -} \ 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..ddf70f6 --- /dev/null +++ b/app/src/main/java/com/example/bcsd_android_2025_1/MusicAdapter.kt @@ -0,0 +1,39 @@ +package com.example.bcsd_android_2025_1 + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.example.bcsd_android_2025_1.model.MusicData + +class MusicAdapter(private val musicList: List) : + RecyclerView.Adapter() { + + inner class MusicViewHolder(view: View) : RecyclerView.ViewHolder(view) { + val title: TextView = view.findViewById(R.id.titleText) + val artist: TextView = view.findViewById(R.id.artistText) + val duration: TextView = view.findViewById(R.id.durationText) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MusicViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.music, parent, false) + return MusicViewHolder(view) + } + + override fun onBindViewHolder(holder: MusicViewHolder, position: Int) { + val music = musicList[position] + holder.title.text = music.title + holder.artist.text = music.artist + holder.duration.text = formatDuration(music.duration) + } + + override fun getItemCount(): Int = musicList.size + + private fun formatDuration(ms: Long): String { + val minutes = ms / 1000 / 60 + val seconds = (ms / 1000) % 60 + return "%d:%02d".format(minutes, seconds) + } +} diff --git a/app/src/main/java/com/example/bcsd_android_2025_1/model/MusicData.kt b/app/src/main/java/com/example/bcsd_android_2025_1/model/MusicData.kt new file mode 100644 index 0000000..4e4d6d2 --- /dev/null +++ b/app/src/main/java/com/example/bcsd_android_2025_1/model/MusicData.kt @@ -0,0 +1,7 @@ +package com.example.bcsd_android_2025_1.model + +data class MusicData( + val title: String, + val artist: String, + val duration: Long +) \ 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..dda7431 --- /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 Android_251Theme( + 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/drawable/fr.png b/app/src/main/res/drawable/fr.png new file mode 100644 index 0000000..0570b67 Binary files /dev/null and b/app/src/main/res/drawable/fr.png differ diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 311f3cb..e980572 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,19 +1,40 @@ + > - + + + app:layout_constraintEnd_toEndOf="parent" + android:visibility="gone" + /> + + + \ No newline at end of file diff --git a/app/src/main/res/layout/music.xml b/app/src/main/res/layout/music.xml new file mode 100644 index 0000000..2b5dbb3 --- /dev/null +++ b/app/src/main/res/layout/music.xml @@ -0,0 +1,36 @@ + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c6c4daf..89bb59f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,24 @@ - BCSD_Android_2025-1 - \ No newline at end of file + Wonder Why + To Heart + 랜덤 알림 채널 + 랜덤 숫자 알림을 위한 채널입니다. + 날씨 좋은 날 지구에서 + fromm앱에서 알림이 도착했습니다. + 더보기 + 닫기 + + 권한이 필요합니다 + 권한이 필요합니다. 설정으로 이동하시겠습니까? + OK + Cancel + + RandomResultReceiver.kt + RandomResultReceiver + Constants + MusicAdapter.kt + MusicAdapter + MusicData.kt + + + diff --git a/build.gradle.kts b/build.gradle.kts index 922f551..952b930 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,4 +2,5 @@ plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.kotlin.android) apply false + alias(libs.plugins.kotlin.compose) apply false } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 283fec9..6f6fcd5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.9.0" +agp = "8.9.2" kotlin = "2.0.21" coreKtx = "1.13.1" junit = "4.13.2" @@ -9,6 +9,9 @@ appcompat = "1.7.0" material = "1.12.0" activity = "1.9.3" constraintlayout = "2.2.1" +lifecycleRuntimeKtx = "2.9.0" +activityCompose = "1.10.1" +composeBom = "2024.09.00" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -19,8 +22,19 @@ androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version material = { group = "com.google.android.material", name = "material", version.ref = "material" } androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" } androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } +androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } +androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } +androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } +androidx-ui = { group = "androidx.compose.ui", name = "ui" } +androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } +androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } +androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } +androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } +androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } +androidx-material3 = { group = "androidx.compose.material3", name = "material3" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }