2022 Jul. 20.
build.gradle(Module: app)
plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' } android { compileSdk 31 defaultConfig { applicationId "net.sytes.rokkosan.randomplayer" minSdk 24 targetSdk 31 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = '1.8' } buildFeatures { viewBinding true } } dependencies { implementation 'androidx.core:core-ktx:1.7.0' implementation 'androidx.appcompat:appcompat:1.4.1' implementation 'com.google.android.material:material:1.5.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.3' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' }
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="net.sytes.rokkosan.randomplayer"> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.PlayRandomExternalStorageSound" > <activity android:name=".MainActivity" android:screenOrientation="portrait" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
layout: activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/textViewDebugInfo" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintBottom_toTopOf="@id/textViewDir" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/textViewDir" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintBottom_toTopOf="@id/buttonChangeDir" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@id/textViewDebugInfo" /> <Button android:id="@+id/buttonChangeDir" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/button_change_dir" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@id/textViewDir" app:layout_constraintBottom_toTopOf="@id/textViewMediaPath" /> <TextView android:id="@+id/textViewMediaPath" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintBottom_toTopOf="@id/textViewFileSize" app:layout_constraintTop_toBottomOf="@id/buttonChangeDir" app:layout_constraintRight_toRightOf="parent" app:layout_constraintLeft_toLeftOf="parent" /> <TextView android:id="@+id/textViewFileSize" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintBottom_toTopOf="@id/spinnerMediaType" app:layout_constraintTop_toBottomOf="@id/textViewMediaPath" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" /> <Spinner android:id="@+id/spinnerMediaType" android:layout_width="wrap_content" android:layout_height="wrap_content" android:entries="@array/spinner_items_media_type" android:minHeight="48dp" app:layout_constraintBottom_toTopOf="@id/buttonPlay" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@id/textViewFileSize" /> <Button android:id="@+id/buttonPlay" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/button_play" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toLeftOf="@id/buttonReplay" app:layout_constraintTop_toBottomOf="@id/textViewMediaPath" /> <Button android:id="@+id/buttonReplay" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/button_replay" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toRightOf="@id/buttonPlay" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@id/textViewMediaPath" /> </androidx.constraintlayout.widget.ConstraintLayout>
layout: played_files_list.xml
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <ListView android:id="@+id/listViewFiles" android:layout_width="match_parent" android:layout_height="match_parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
menu: option.xml
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/playHistory" android:title="@string/playHistoryTitle" /> </menu>
values: arrays.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <string-array name="spinner_items_media_type"> <item>Video_and_Sound</item> <item>Sound</item> <item>Video</item> </string-array> </resources>
values: strings.xml
<resources> <string name="app_name">RandomPlayer</string> <string name="button_play">Play</string> <string name="button_replay">Replay</string> <string name="button_change_dir">Change_Directory</string> <string name="gotLocalAudioFile">Got an audio file from local device</string> <string name="kb">%sKB</string> <string name="sentIntent">Sent Intent for playing audio</string> <string name="havePermission">Allowed to read storage</string> <string name="noPermission">No Permission to read storage</string> <string name="playHistoryTitle">Play History</string> <string name="pathHistoryFile">path_history.txt</string> <string name="noHistory">No history</string> <string name="no_dirs">No directories</string> <string name="volume_dir_key">volumeDir</string> <string name="relative_dir_key">relativeDir</string> <string name="illegal_typeMedia">Illegal typeMedia</string> <string name="storage_top">/srorage/</string> <string name="external_primary">external_primary</string> <string name="emulated0">emulated/0</string> <string name="not_find">File not found</string> <string name="not_selected">Media type is not selected</string> <string name="reason_for_permission">ストレージファイル読み込みに読込権限の許可が必要です。設定を開きますか?</string> <string name="ok">OK</string> <string name="dismiss">Dismiss</string> <string name="selected_path">/%1$s/%2$s</string> </resources>
MainActivity.kt
package net.sytes.rokkosan.randomplayer /* 2022 Jul. 20. 2022 Jun. 25. 2022 May 15. 2022 Apr. 30. 2022 Feb. 23. Ryuichi Hashimoto. 端末内の、指定ディレクトリ内の、動画もしくは音声ファイルを再生するアプリ 起動時にファイル読み込みパーミッションを取得する。 メイン画面に配置するウィジェット 「読み込みディレクトリ選択」 「音声ファイルのみ・ビデオファイルのみ・音声+ビデオファイル選択」 「再生」 「リプレイ」 */ import android.Manifest import android.content.Context import android.content.Intent import android.content.SharedPreferences import android.content.pm.PackageManager import android.net.Uri import android.os.Build import android.os.Bundle import android.provider.MediaStore import android.view.Menu import android.view.MenuItem import android.widget.ArrayAdapter import android.widget.Spinner import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import net.sytes.rokkosan.randomplayer.databinding.ActivityMainBinding import net.sytes.rokkosan.randomsmbsoundplay.ListPlayedFilesDialogFragment import java.io.File import java.io.FileWriter import java.io.IOException import java.util.* import kotlin.properties.Delegates class MainActivity : AppCompatActivity() { lateinit var binding: ActivityMainBinding private var selectedVolumeDir: String = "" private var selectedRelativeDir: String = "" private val sharedPref: SharedPreferences by lazy {this.getPreferences(Context.MODE_PRIVATE)} private val myPermission = Manifest.permission.READ_EXTERNAL_STORAGE private var isCheckedNotAskAgain: Boolean = false val spinnerMediaType: Spinner by lazy { binding.spinnerMediaType } lateinit var typeOfMedia: MediaMyEnumType private lateinit var appContext: Context private var medType: Int by Delegates.notNull() private var medPath: String = "" private val audioInt = 2 private val videoInt = 3 // mediaType 0:none 1:image 2:audio 3:video 4:playlist 5:subtitle 6:document enum class MediaMyEnumType { SoundVideo, Sound, Video } // ファイル読み込みパーミッション取得 // registerForActivityResult()を定義してlaunch()を呼び出すと、 // パーミッションが許可されていない時はユーザーにパーミッション許可を促すダイアログが表示される。 // パーミッションが「許可された時」「許可されなかった時」のコールバック関数を記述する private val permisLauncher = registerForActivityResult( ActivityResultContracts.RequestPermission() ) { granted -> if (granted) { // パーミッションが取得されている時 // パーミッション取得後にすべき処理に移る isCheckedNotAskAgain = false sharedPref.edit().putBoolean("isCheckedNotAskAgain", isCheckedNotAskAgain).apply() workWithPermission() } else { // パーミッションを取得していない if (shouldShowRequestPermissionRationale(myPermission)) { // パーミッション取得を拒否され、 // 「今後表示しない」は選択されていない isCheckedNotAskAgain = false sharedPref.edit().putBoolean("isCheckedNotAskAgain", isCheckedNotAskAgain).apply() } else { // パーミッション取得を拒否され、 // 「今後表示しない」が選択されて再リクエストも拒否されている isCheckedNotAskAgain = true sharedPref.edit().putBoolean("isCheckedNotAskAgain", isCheckedNotAskAgain).apply() } workWithoutPermission() } } /* * ファイル・ピッカーによるディレクトリ選択 * ContentResolver.queryでの利用に合わせ、 * MediaStore.Files.FileColumns.VOLUME_NAME に当たる文字列を selectedVolumeDir に * MediaStore.Files.FileColumns.RELATIVE_PATH に当たる文字列を selectedRelativeDir に * 格納する * 取得した文字列の画面表示も行う */ private val fileLauncherGetDir = registerForActivityResult( ActivityResultContracts.StartActivityForResult() ) { result -> // 結果を受け取るルーチン if (result.resultCode == RESULT_OK) { // succeeded. // get uri of directory val selectedDirUri = result.data?.data ?: throw Exception() /* uri冒頭の"content://com.android.externalstorage.documents/tree"を削除し、 エンコードされた /(スラッシュ)を戻す。 ContentPreviderに合わせ、VolumeとRelativeの文字列を取得する。 プライマリ・ストレージ文字列は"/external_primary/"に置き換える。 */ val selectedDirString = selectedDirUri.toString(). replaceFirst("content://com.android.externalstorage.documents/tree", "") selectedVolumeDir = selectedDirString. substring(0, selectedDirString.indexOf("%3A")+3 ) if (selectedVolumeDir.indexOf("/primary%3A") > -1) { // primary volume selectedVolumeDir = selectedVolumeDir. replaceFirst("/primary%3A", "external_primary") } else if (selectedVolumeDir.indexOf("%3A") > -1) { // SD card selectedVolumeDir = selectedVolumeDir. replaceFirst("/", "").replaceFirst("%3A", "") } selectedRelativeDir = selectedDirString. substring( selectedDirString.indexOf("%3A")). replace("%3A", "" ) if (selectedRelativeDir.indexOf("%2F") > -1) { selectedRelativeDir = selectedRelativeDir.replace("%2F", "/") } // Relativeの末尾を"/"にしておく if (selectedRelativeDir.takeLast(1) != "/" ) { selectedRelativeDir = selectedRelativeDir + "/" } // 取得した文字列を保存 sharedPref.edit(). putString(getString(R.string.volume_dir_key), selectedVolumeDir). putString(getString(R.string.relative_dir_key), selectedRelativeDir). apply() } else { // failed selectedVolumeDir = "" selectedRelativeDir = "" } binding.textViewDir.text = getString(R.string.selected_path, selectedVolumeDir, selectedRelativeDir) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // setContentView(R.layout.activity_main) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) appContext = applicationContext getFileReadPermission() } /* * パーミッションを取得する * パーミッションが取得されれば workWithPermission() を実行する * パーミッションが取得されなければ workWithoutPermission() を実行する */ private fun getFileReadPermission() { if ( ContextCompat.checkSelfPermission(this, myPermission) == PackageManager.PERMISSION_GRANTED) { // 既にパーミッションが取得されている時 // パーミッション取得後にすべき処理に移る isCheckedNotAskAgain = false sharedPref.edit().putBoolean("isCheckedNotAskAgain", isCheckedNotAskAgain).apply() workWithPermission() } else { // パーミッション未取得 if (isCheckedNotAskAgain) { // 過去に権限取得を拒否され、「今後表示しない」が選択されて再リクエストも拒否されている // ダイアログでユーザーに説明し、ユーザーが望めば設定画面を表示する val openConfigDialogFragment = OpenConfigDialogFragment() openConfigDialogFragment.show(supportFragmentManager, "simple") // ダイアログ後の処理 if ( ContextCompat.checkSelfPermission(this, myPermission) == PackageManager.PERMISSION_GRANTED) { // (設定画面で)パーミッションを取得している isCheckedNotAskAgain = false sharedPref.edit().putBoolean("isCheckedNotAskAgain", isCheckedNotAskAgain). apply() workWithPermission() } else { // パーミッション未取得。 // shouldShowRequestPermissionRationale()に応じて // isCheckedNotAskAgainを設定し保存する if (shouldShowRequestPermissionRationale(myPermission)) { // 「今後表示しない」は選択されていない isCheckedNotAskAgain = false sharedPref.edit().putBoolean("isCheckedNotAskAgain", isCheckedNotAskAgain). apply() } else { // 「今後表示しない」が選択されている isCheckedNotAskAgain = true sharedPref.edit().putBoolean("isCheckedNotAskAgain", isCheckedNotAskAgain). apply() } workWithoutPermission() } } else { // パーミッションを取得しておらず、 // 過去に権限取得で再リクエストの拒否はしていない。 permisLauncher.launch(myPermission) } } } // ファイル読み込みパーミッション取得後の処理 private fun workWithPermission() { binding.textViewMediaPath.text = getString(R.string.havePermission) /* get directory in external storage * ContentResolver.queryでの利用に合わせ、 * MediaStore.Files.FileColumns.VOLUME_NAME に当たる文字列を selectedVolumeDir に * MediaStore.Files.FileColumns.RELATIVE_PATH に当たる文字列を selectedRelativeDir に * 格納する */ // 保存されたディレクトリ情報を取り出す selectedVolumeDir = sharedPref.getString(getString(R.string.volume_dir_key), "") ?: throw Exception() selectedRelativeDir = sharedPref.getString(getString(R.string.relative_dir_key), "") ?: throw Exception() // ディレクトリ情報が保存されていなければファイル・ピッカーでディレクトリを選択する if (selectedVolumeDir.equals("") || selectedRelativeDir.equals("")) { selectDirWithFilePicker() } binding.textViewDir.text = getString(R.string.selected_path, selectedVolumeDir, selectedRelativeDir) // ディレクトリ情報が初期取得できた後の処理 if (!selectedVolumeDir.equals("") && !selectedRelativeDir.equals("")) { // ユーザーによるディレクトリ変更 binding.buttonChangeDir.setOnClickListener { selectDirWithFilePicker() } // select media type to be played with spinner in layout // 音声のみ、動画のみ、音声+動画 から選択する setAdapterSpinner() spinnerMediaType.onItemSelectedListener = CustomItemSelectedListener(this) // 再生 binding.buttonPlay.setOnClickListener { binding.textViewMediaPath.text = "" binding.textViewFileSize.text = "" playMediaInDir(typeOfMedia) } // Replay binding.buttonReplay.setOnClickListener { val mediaIntent = Intent() mediaIntent.action = Intent.ACTION_VIEW if (medType == audioInt) { mediaIntent.setDataAndType(Uri.parse(medPath), "audio/*") } else if (medType == videoInt) { mediaIntent.setDataAndType(Uri.parse(medPath), "video/*") } else { binding.textViewMediaPath.text = getString(R.string.illegal_typeMedia) } if (medType == audioInt || medType == videoInt) { startActivity(mediaIntent) } } } } private fun setAdapterSpinner() { val spinnerAdapter = ArrayAdapter.createFromResource( this, R.array.spinner_items_media_type, android.R.layout.simple_spinner_item) // simple_spinner_item // プラットフォームによってデフォルトで提供されているスピナーの外観レイアウト spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) // simple_spinner_dropdown_item // プラットフォームで定義された標準のドロップダウンレイアウト spinnerMediaType.setAdapter(spinnerAdapter) } private fun workWithoutPermission() { binding.textViewMediaPath.text = getString(R.string.noPermission) } // ファイル・ピッカーでディレクトリを選択し、パス文字列情報を取得する // パス文字列情報の画面への表示も行う private fun selectDirWithFilePicker() { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply { addFlags( Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION ) } fileLauncherGetDir.launch(intent) } /* * Play media. * Int typeMedia: Type of media to play */ @RequiresApi(Build.VERSION_CODES.Q) private fun playMediaInDir(typeMedia: MediaMyEnumType) { lateinit var selectionClause: String /* * get uri of audio media and video media in the directory */ val contentResolver = this.contentResolver val proj = arrayOf( MediaStore.Files.FileColumns.MEDIA_TYPE, MediaStore.Files.FileColumns.SIZE, "_data" ) when(typeMedia) { MediaMyEnumType.SoundVideo -> { // audio & video selectionClause = "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + " = " + MediaStore.Files.FileColumns.MEDIA_TYPE_AUDIO + " OR " + MediaStore.Files.FileColumns.MEDIA_TYPE + " = " + MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO + ")" + " AND " + "(" + MediaStore.Files.FileColumns.VOLUME_NAME + " LIKE ? " + " AND " + MediaStore.Files.FileColumns.RELATIVE_PATH + " LIKE ? " + ")" } MediaMyEnumType.Sound -> { // audio selectionClause = "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + " = " + MediaStore.Files.FileColumns.MEDIA_TYPE_AUDIO + ")" + " AND " + "(" + MediaStore.Files.FileColumns.VOLUME_NAME + " LIKE ? " + " AND " + MediaStore.Files.FileColumns.RELATIVE_PATH + " LIKE ? " + ")" } MediaMyEnumType.Video -> { // video selectionClause = "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + " = " + MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO + ")" + " AND " + "(" + MediaStore.Files.FileColumns.VOLUME_NAME + " LIKE ? " + " AND " + MediaStore.Files.FileColumns.RELATIVE_PATH + " LIKE ? " + ")" } else -> {throw java.lang.Exception(getString(R.string.illegal_typeMedia))} } val selectionArgs = arrayOf(selectedVolumeDir, "$selectedRelativeDir%") val query = contentResolver.query( MediaStore.Files.getContentUri("external"), //MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL), proj, selectionClause, selectionArgs, null ) val numCount = query?.count query?.use { cursor -> if (cursor.count < 1) { binding.textViewMediaPath.text = getString(R.string.not_find) } else { cursor.moveToPosition(getRandomNum(numCount!!)) medType = cursor. getInt(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MEDIA_TYPE)) val medSize = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.SIZE)) medPath = cursor.getString(cursor.getColumnIndexOrThrow("_data")) // play media val mediaIntent = Intent() mediaIntent.action = Intent.ACTION_VIEW if (medType == audioInt) { mediaIntent.setDataAndType(Uri.parse(medPath), "audio/*") } else if (medType == videoInt) { mediaIntent.setDataAndType(Uri.parse(medPath), "video/*") } else { binding.textViewMediaPath.text = getString(R.string.illegal_typeMedia) } if (medType == audioInt || medType == videoInt) { binding.textViewMediaPath.text = medPath binding.textViewFileSize.text = getString(R.string.kb, (medSize / 1000L).toString()) startActivity(mediaIntent) // save audio-file-path to storage lateinit var fw: FileWriter val savePathFile = File(appContext.filesDir, getString(R.string.pathHistoryFile)) if (savePathFile.exists()) { fw = FileWriter(savePathFile, true) } else { fw = FileWriter(savePathFile) } try { fw.write("$medPath,") } catch(e: IOException) { binding.textViewMediaPath.text = e.toString() } finally { try { fw.close() } catch(e: IOException) { binding.textViewMediaPath.text = e.toString() } } } } } query?.close() } // 0以上、maxNum未満の範囲でランダムな数を1つ返す private fun getRandomNum(maxNum: Int): Int { if (maxNum < 1) { throw Exception() } val random = Random() return random.nextInt(maxNum) } // option menu override fun onCreateOptionsMenu(menu: Menu): Boolean { super.onCreateOptionsMenu(menu) val inflater = menuInflater inflater.inflate(R.menu.option, menu) return true } //Option menuのitemがクリックされた時の動作 override fun onOptionsItemSelected(item: MenuItem): Boolean { when(item.itemId) { R.id.playHistory -> { val playedFilesDialog = ListPlayedFilesDialogFragment() playedFilesDialog.show(supportFragmentManager, "simple") } } return super.onOptionsItemSelected(item) } }
OpenConfigDialogFragment.kt
package net.sytes.rokkosan.randomplayer import android.app.AlertDialog import android.app.Dialog import android.content.DialogInterface import android.content.Intent import android.net.Uri import android.os.Bundle import android.provider.Settings import androidx.fragment.app.DialogFragment class OpenConfigDialogFragment: DialogFragment() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { return activity?.let { val builder = AlertDialog.Builder(it) builder.setMessage(R.string.reason_for_permission) .setPositiveButton(R.string.ok, DialogInterface.OnClickListener { dialog, id -> // このアプリの設定画面への暗黙的インテントを用意する val packageName = context?.applicationContext?.packageName val intent = Intent( Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:${packageName}") ) // 新しいタスクで設定画面を開く // https://developer.android.google.cn/guide/components/activities/tasks-and-back-stack?hl=ja intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) startActivity(intent) }) .setNegativeButton(R.string.dismiss, DialogInterface.OnClickListener { dialog, id -> // User cancelled the dialog }) // Create the AlertDialog object and return it builder.create() } ?: throw IllegalStateException("Activity cannot be null") } }
CustomItemSelectedListener.kt
package net.sytes.rokkosan.randomplayer import android.view.View import android.widget.AdapterView class CustomItemSelectedListener(private val mainActivity: MainActivity) : AdapterView.OnItemSelectedListener { override fun onItemSelected(parent: AdapterView<*>, view: View?, pos: Int, id: Long) { when (mainActivity.spinnerMediaType.selectedItemPosition) { 0 -> { mainActivity.typeOfMedia = MainActivity.MediaMyEnumType.SoundVideo } 1 -> { mainActivity.typeOfMedia = MainActivity.MediaMyEnumType.Sound } 2 -> { mainActivity.typeOfMedia = MainActivity.MediaMyEnumType.Video } else -> { mainActivity.binding.textViewMediaPath.text = mainActivity.getString(R.string.illegal_typeMedia) } } } override fun onNothingSelected(parent: AdapterView<*>?) { mainActivity.binding.textViewMediaPath.text = mainActivity.getString(R.string.not_selected) } }
ListPlayedFilesDialogFragment.kt
package net.sytes.rokkosan.randomsmbsoundplay // 2022 Feb. 13. // 2022 Feb. 05. // Ryuichi Hashimoto import android.app.Dialog import android.os.Bundle import androidx.fragment.app.DialogFragment import android.app.AlertDialog import android.widget.ArrayAdapter import android.widget.ListView import android.widget.Toast import net.sytes.rokkosan.randomplayer.R import java.io.* class ListPlayedFilesDialogFragment: DialogFragment(){ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { // get Dialog val builder = AlertDialog.Builder(activity) val inflater = requireActivity().layoutInflater val myDialogView = inflater.inflate(R.layout.played_files_list, null) // get array of played files from saved file var playedFiles = "" lateinit var br: BufferedReader try { val savePathFile = File(this.context?.filesDir, getString(R.string.pathHistoryFile)) // savePathFile has only one line. // the line is filePath data in which each datum is separated by ','. if (savePathFile.exists()) { br = BufferedReader(FileReader(savePathFile)) try { playedFiles = br.readLine() } catch(e: IOException) { playedFiles = e.toString() } finally { try { br.close() } catch(e: IOException) { playedFiles = playedFiles + " " + e.toString() } } } } catch (e: Exception){ playedFiles = playedFiles + " " + e.toString() } if (playedFiles == "") { playedFiles = getString(R.string.noHistory) } playedFiles = playedFiles.removeSuffix(",") val playedFileList = playedFiles.split(",").asReversed() // システムに組み込まれた"android.R.layout.simple_list_item_1"を介して、 // playedFileListをArrayAdapterに関連づける val myAdapter = ArrayAdapter(this.requireContext(), android.R.layout.simple_list_item_1, playedFileList) // ArrayAdapterをダイアログ上のレイアウト内のListViewにセットする val listViewFiles = myDialogView.findViewById<ListView>(R.id.listViewFiles) listViewFiles.adapter = myAdapter // display dialog builder.setView(myDialogView) .setTitle("Played Files") .setPositiveButton("Clear") { dialog, which -> lateinit var fos: FileOutputStream try { val savePathFile = File( context?.filesDir, getString(R.string.pathHistoryFile)) fos = FileOutputStream(savePathFile) fos.write(','.code.toInt()) } catch (e: Exception) { Toast.makeText(context,e.toString(), Toast.LENGTH_LONG ).show() } finally { fos.close() } } .setNeutralButton("OK") { _, _ -> // nothing is done } return builder.create() } }