diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..217e5c5 --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index d3f8ab2..feb8520 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,48 +1,62 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' android { - compileSdkVersion 30 - buildToolsVersion "30.0.1" + compileSdkVersion 33 defaultConfig { applicationId "com.alterpat.voicerecorder" - minSdkVersion 24 - targetSdkVersion 30 + minSdkVersion 26 + targetSdkVersion 33 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } - + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = '17' + } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } + + viewBinding { + enabled = true + } + + namespace 'com.alterpat.voicerecorder' } dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation 'androidx.core:core-ktx:1.3.2' - implementation 'androidx.appcompat:appcompat:1.2.0' - implementation 'androidx.constraintlayout:constraintlayout:2.0.4' - testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test.ext:junit:1.1.2' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + implementation "org.jetbrains.kotlin:kotlin-stdlib:1.8.21" + implementation 'androidx.core:core-ktx:1.10.0' + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' - implementation 'com.google.android.material:material:1.4.0-alpha02' + implementation 'com.google.android.material:material:1.9.0' def room_version = "2.2.6" - implementation "androidx.room:room-runtime:$room_version" + implementation "androidx.room:room-runtime:2.5.1" kapt "androidx.room:room-compiler:$room_version" // optional - Kotlin Extensions and Coroutines support for Room - implementation "androidx.room:room-ktx:$room_version" + //ROOM + implementation "androidx.room:room-runtime:2.5.1" + kapt "androidx.room:room-compiler:2.5.1" + implementation "androidx.room:room-ktx:2.5.1" + implementation "androidx.room:room-paging:2.5.1" } \ No newline at end of file diff --git a/app/src/androidTest/java/com/alterpat/voicerecorder/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/alterpat/voicerecorder/ExampleInstrumentedTest.kt index 8b818ec..f721d4c 100644 --- a/app/src/androidTest/java/com/alterpat/voicerecorder/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/com/alterpat/voicerecorder/ExampleInstrumentedTest.kt @@ -1,13 +1,11 @@ package com.alterpat.voicerecorder -import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 - +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.* import org.junit.Test import org.junit.runner.RunWith -import org.junit.Assert.* - /** * Instrumented test, which will execute on an Android device. * diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f32a027..35ad590 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + @@ -11,11 +10,12 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> - + + android:windowSoftInputMode="adjustResize" + android:exported="true"> diff --git a/app/src/main/java/com/alterpat/voicerecorder/Adapter.kt b/app/src/main/java/com/alterpat/voicerecorder/Adapter.kt index ad9f1f5..4a98066 100644 --- a/app/src/main/java/com/alterpat/voicerecorder/Adapter.kt +++ b/app/src/main/java/com/alterpat/voicerecorder/Adapter.kt @@ -7,45 +7,52 @@ import android.view.ViewGroup import android.widget.CheckBox import android.widget.TextView import androidx.recyclerview.widget.RecyclerView +import com.alterpat.voicerecorder.databinding.ItemviewLayoutBinding import com.alterpat.voicerecorder.db.AudioRecord import java.text.SimpleDateFormat -import java.util.* +import java.util.Date -class Adapter(private var audioRecords: List, - private val listener: OnItemClickListener) : RecyclerView.Adapter() { +class Adapter( + private var audioRecords: List, + private val listener: OnItemClickListener +) : RecyclerView.Adapter() { private var editMode = false + lateinit var binding : ItemviewLayoutBinding interface OnItemClickListener { fun onItemClick(position: Int) fun onItemLongClick(position: Int) } - inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener, View.OnLongClickListener { - var filename : TextView = itemView.findViewById(R.id.filename) - var fileMeta : TextView = itemView.findViewById(R.id.file_meta) - var checkBox : CheckBox = itemView.findViewById(R.id.checkbox) + inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), + View.OnClickListener, View.OnLongClickListener { + var filename: TextView = itemView.findViewById(R.id.filename) + var fileMeta: TextView = itemView.findViewById(R.id.file_meta) + var checkBox: CheckBox = itemView.findViewById(R.id.checkbox) init { itemView.setOnClickListener(this) itemView.setOnLongClickListener(this) } + override fun onClick(p0: View?) { val position = adapterPosition // property of the recyclerview class - if(position != RecyclerView.NO_POSITION) + if (position != RecyclerView.NO_POSITION) listener.onItemClick(position) } override fun onLongClick(p0: View?): Boolean { val position = adapterPosition // property of the recyclerview class - if(position != RecyclerView.NO_POSITION) + if (position != RecyclerView.NO_POSITION) listener.onItemLongClick(position) return true } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - val view = LayoutInflater.from(parent.context).inflate(R.layout.itemview_layout, parent, false) + binding = ItemviewLayoutBinding.inflate(LayoutInflater.from(parent.context),parent,false) + val view = binding.root return ViewHolder(view) } @@ -54,22 +61,22 @@ class Adapter(private var audioRecords: List, } override fun onBindViewHolder(holder: ViewHolder, position: Int) { - if(position != RecyclerView.NO_POSITION){ + if (position != RecyclerView.NO_POSITION) { var audioRecord = audioRecords[position] holder.filename.text = audioRecord.filename val sdf = SimpleDateFormat("dd/MM/yy") val netDate = Date(audioRecord.date) - val date =sdf.format(netDate) + val date = sdf.format(netDate) holder.fileMeta.text = "${audioRecord.duration} $date" Log.d("ListingTag", audioRecord.isChecked.toString()) - if(editMode) { + if (editMode) { holder.checkBox.visibility = View.VISIBLE if (audioRecord.isChecked) holder.checkBox.isChecked = audioRecord.isChecked - }else { + } else { holder.checkBox.visibility = View.GONE audioRecord.isChecked = false holder.checkBox.isChecked = false @@ -77,21 +84,19 @@ class Adapter(private var audioRecords: List, } } - fun setData(audioRecords: List){ + fun setData(audioRecords: List) { this.audioRecords = audioRecords notifyDataSetChanged() } - fun setEditMode(mode: Boolean){ + fun setEditMode(mode: Boolean) { editMode = mode notifyDataSetChanged() } - fun isEditMode():Boolean{ + fun isEditMode(): Boolean { return editMode } - - } \ No newline at end of file diff --git a/app/src/main/java/com/alterpat/voicerecorder/BottomSheet.kt b/app/src/main/java/com/alterpat/voicerecorder/BottomSheet.kt index 94803dd..fe13106 100644 --- a/app/src/main/java/com/alterpat/voicerecorder/BottomSheet.kt +++ b/app/src/main/java/com/alterpat/voicerecorder/BottomSheet.kt @@ -8,17 +8,16 @@ import android.view.ViewGroup import android.view.WindowManager import android.view.inputmethod.InputMethodManager import android.widget.Button +import com.alterpat.voicerecorder.databinding.ActivityMainBinding +import com.alterpat.voicerecorder.databinding.BottomSheetBinding import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.textfield.TextInputEditText import java.io.File -class BottomSheet: BottomSheetDialogFragment { +class BottomSheet : BottomSheetDialogFragment { - // Step 1 - This interface defines the type of messages I want to communicate to my owner interface OnClickListener { - // These methods are the different events and - // need to pass relevant arguments related to the event triggered fun onCancelClicked() fun onOkClicked(filePath: String, filename: String) } @@ -26,11 +25,12 @@ class BottomSheet: BottomSheetDialogFragment { // Step 2 - This variable represents the listener passed in by the owning object // The listener must implement the events interface and passes messages up to the parent. private lateinit var listener: OnClickListener + private lateinit var binding: BottomSheetBinding private lateinit var filename: String private lateinit var dirPath: String - constructor(dirPath: String, filename : String, listener: OnClickListener){ + constructor(dirPath: String, filename: String, listener: OnClickListener) { this.dirPath = dirPath this.filename = filename this.listener = listener @@ -41,7 +41,8 @@ class BottomSheet: BottomSheetDialogFragment { container: ViewGroup?, savedInstanceState: Bundle? ): View? { - var view = inflater.inflate(R.layout.bottom_sheet, container) + binding = BottomSheetBinding.inflate(layoutInflater) + var view = binding.root var editText = view.findViewById(R.id.filenameInput) @@ -61,9 +62,9 @@ class BottomSheet: BottomSheetDialogFragment { // update filename if need val updatedFilename = editText.text.toString() - if(updatedFilename != filename){ + if (updatedFilename != filename) { var newFile = File("$dirPath$updatedFilename.mp3") - File(dirPath+filename).renameTo(newFile) + File(dirPath + filename).renameTo(newFile) } // add entry to db @@ -80,7 +81,7 @@ class BottomSheet: BottomSheetDialogFragment { // hide keyboard hideKeyboard(view) // delete file from storage - File(dirPath+filename).delete() + File(dirPath + filename).delete() // dismiss dialog dismiss() @@ -95,7 +96,8 @@ class BottomSheet: BottomSheetDialogFragment { private fun showKeyboard(view: View) { if (view.requestFocus()) { - val imm = view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager? + val imm = + view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager? imm?.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0) } } diff --git a/app/src/main/java/com/alterpat/voicerecorder/ListingActivity.kt b/app/src/main/java/com/alterpat/voicerecorder/ListingActivity.kt index 6962b10..ed3a4b0 100644 --- a/app/src/main/java/com/alterpat/voicerecorder/ListingActivity.kt +++ b/app/src/main/java/com/alterpat/voicerecorder/ListingActivity.kt @@ -6,7 +6,6 @@ import android.text.Editable import android.text.TextWatcher import android.util.Log import android.view.Menu -import android.view.MenuItem import android.view.View import android.widget.LinearLayout import android.widget.Toast @@ -14,53 +13,56 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.content.res.ResourcesCompat import androidx.recyclerview.widget.LinearLayoutManager import androidx.room.Room +import com.alterpat.voicerecorder.databinding.ActivityListingBinding import com.alterpat.voicerecorder.db.AppDatabase import com.alterpat.voicerecorder.db.AudioRecord import com.google.android.material.bottomsheet.BottomSheetBehavior -import kotlinx.android.synthetic.main.activity_listing.* import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch class ListingActivity : AppCompatActivity(), Adapter.OnItemClickListener { - private lateinit var adapter : Adapter - private lateinit var audioRecords : List - private lateinit var db : AppDatabase + private lateinit var binding: ActivityListingBinding + private lateinit var adapter: Adapter + private lateinit var audioRecords: List + private lateinit var db: AppDatabase private lateinit var bottomSheetBehavior: BottomSheetBehavior - private lateinit var menu : Menu + private lateinit var menu: Menu private var allSelected = false private var nbSelected = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_listing) + binding = ActivityListingBinding.inflate(layoutInflater) + setContentView(binding.root) - setSupportActionBar(toolbar) + setSupportActionBar(binding.toolbar) supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayShowHomeEnabled(true) - toolbar.setNavigationOnClickListener { + binding.toolbar.setNavigationOnClickListener { onBackPressed() } - bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet) + bottomSheetBehavior = BottomSheetBehavior.from(binding.bottomSheet) bottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN audioRecords = emptyList() adapter = Adapter(audioRecords, this) - recyclerview.adapter = adapter - recyclerview.layoutManager = LinearLayoutManager(this) + binding.recyclerview.adapter = adapter + binding.recyclerview.layoutManager = LinearLayoutManager(this) db = Room.databaseBuilder( this, AppDatabase::class.java, - "audioRecords") + "audioRecords" + ) //.fallbackToDestructiveMigration() .build() fetchAll() - searchInput.addTextChangedListener(object : TextWatcher{ + binding.searchInput.addTextChangedListener(object : TextWatcher { override fun afterTextChanged(p0: Editable?) {} override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {} @@ -72,7 +74,7 @@ class ListingActivity : AppCompatActivity(), Adapter.OnItemClickListener { }) - btnSelectAll.setOnClickListener { + binding.btnSelectAll.setOnClickListener { allSelected = !allSelected Log.d("ListingTag", allSelected.toString()) audioRecords.forEach { @@ -85,30 +87,30 @@ class ListingActivity : AppCompatActivity(), Adapter.OnItemClickListener { adapter.notifyDataSetChanged() } - btnClose.setOnClickListener { + binding.btnClose.setOnClickListener { closeEditor() } - btnDelete.setOnClickListener { + binding.btnDelete.setOnClickListener { closeEditor() - var toDelete : List = audioRecords.filter { it.isChecked } + var toDelete: List = audioRecords.filter { it.isChecked } audioRecords = audioRecords.filter { !it.isChecked } GlobalScope.launch { db.audioRecordDAO().delete(toDelete) - if(audioRecords.isEmpty()) + if (audioRecords.isEmpty()) fetchAll() else adapter.setData(audioRecords) } } - btnRename.setOnClickListener { + binding.btnRename.setOnClickListener { Toast.makeText(this, "rename clicked", Toast.LENGTH_SHORT).show() } } - private fun closeEditor(){ + private fun closeEditor() { allSelected = false adapter.setEditMode(false) bottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN @@ -117,55 +119,63 @@ class ListingActivity : AppCompatActivity(), Adapter.OnItemClickListener { supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayShowHomeEnabled(true) // show relative layout - editorBar.visibility = View.GONE + binding.editorBar.visibility = View.GONE nbSelected = 0 } - private fun fetchAll(){ + private fun fetchAll() { GlobalScope.launch { audioRecords = db.audioRecordDAO().getAll() adapter.setData(audioRecords) } } - private fun searchDatabase(query: String){ + private fun searchDatabase(query: String) { GlobalScope.launch { audioRecords = db.audioRecordDAO().searchDatabase(query) - runOnUiThread{ + runOnUiThread { adapter.setData(audioRecords) } } } - private fun updateBottomSheet(){ - when(nbSelected){ + private fun updateBottomSheet() { + when (nbSelected) { 0 -> { - btnRename.isClickable = false - btnRename.background = ResourcesCompat.getDrawable(resources, R.drawable.ic_edit_disabled, theme) - tvRename.setTextColor(resources.getColor(R.color.colorDisabled, theme)) - btnDelete.isClickable = false - btnDelete.background = ResourcesCompat.getDrawable(resources, R.drawable.ic_delete_disabled2, theme) - tvDelete.setTextColor(resources.getColor(R.color.colorDisabled, theme)) + binding.btnRename.isClickable = false + binding.btnRename.background = + ResourcesCompat.getDrawable(resources, R.drawable.ic_edit_disabled, theme) + binding.tvRename.setTextColor(resources.getColor(R.color.colorDisabled, theme)) + binding.btnDelete.isClickable = false + binding.btnDelete.background = + ResourcesCompat.getDrawable(resources, R.drawable.ic_delete_disabled2, theme) + binding.tvDelete.setTextColor(resources.getColor(R.color.colorDisabled, theme)) } + 1 -> { - btnRename.isClickable = true - btnRename.background = ResourcesCompat.getDrawable(resources, R.drawable.ic_edit, theme) - tvRename.setTextColor(resources.getColor(R.color.colorText, theme)) + binding.btnRename.isClickable = true + binding.btnRename.background = + ResourcesCompat.getDrawable(resources, R.drawable.ic_edit, theme) + binding.tvRename.setTextColor(resources.getColor(R.color.colorText, theme)) - btnDelete.isClickable = true - btnDelete.background = ResourcesCompat.getDrawable(resources, R.drawable.ic_delete, theme) - tvDelete.setTextColor(resources.getColor(R.color.colorText, theme)) + binding.btnDelete.isClickable = true + binding.btnDelete.background = + ResourcesCompat.getDrawable(resources, R.drawable.ic_delete, theme) + binding.tvDelete.setTextColor(resources.getColor(R.color.colorText, theme)) } + else -> { - btnRename.isClickable = false - btnRename.background = ResourcesCompat.getDrawable(resources, R.drawable.ic_edit_disabled, theme) - tvRename.setTextColor(resources.getColor(R.color.colorDisabled, theme)) + binding.btnRename.isClickable = false + binding.btnRename.background = + ResourcesCompat.getDrawable(resources, R.drawable.ic_edit_disabled, theme) + binding.tvRename.setTextColor(resources.getColor(R.color.colorDisabled, theme)) - btnDelete.isClickable = true - btnDelete.background = ResourcesCompat.getDrawable(resources, R.drawable.ic_delete, theme) - tvDelete.setTextColor(resources.getColor(R.color.colorText, theme)) + binding.btnDelete.isClickable = true + binding.btnDelete.background = + ResourcesCompat.getDrawable(resources, R.drawable.ic_delete, theme) + binding.tvDelete.setTextColor(resources.getColor(R.color.colorText, theme)) } } @@ -175,15 +185,15 @@ class ListingActivity : AppCompatActivity(), Adapter.OnItemClickListener { var intent = Intent(this, PlayerActivity::class.java) var audioRecord = audioRecords[position] - if(adapter.isEditMode()){ + if (adapter.isEditMode()) { Log.d("ITEMCHANGE", audioRecord.isChecked.toString()) audioRecord.isChecked = !audioRecord.isChecked adapter.notifyItemChanged(position) - nbSelected = if (audioRecord.isChecked) nbSelected+1 else nbSelected-1 + nbSelected = if (audioRecord.isChecked) nbSelected + 1 else nbSelected - 1 updateBottomSheet() - }else{ + } else { intent.putExtra("filepath", audioRecord.filePath) intent.putExtra("filename", audioRecord.filename) startActivity(intent) @@ -199,14 +209,14 @@ class ListingActivity : AppCompatActivity(), Adapter.OnItemClickListener { audioRecord.isChecked = !audioRecord.isChecked - nbSelected = if (audioRecord.isChecked) nbSelected+1 else nbSelected-1 + nbSelected = if (audioRecord.isChecked) nbSelected + 1 else nbSelected - 1 updateBottomSheet() // hide back button supportActionBar?.setDisplayHomeAsUpEnabled(false) supportActionBar?.setDisplayShowHomeEnabled(false) // show relative layout - editorBar.visibility = View.VISIBLE + binding.editorBar.visibility = View.VISIBLE } diff --git a/app/src/main/java/com/alterpat/voicerecorder/MainActivity.kt b/app/src/main/java/com/alterpat/voicerecorder/MainActivity.kt index e5dbc7d..7ba2b3d 100644 --- a/app/src/main/java/com/alterpat/voicerecorder/MainActivity.kt +++ b/app/src/main/java/com/alterpat/voicerecorder/MainActivity.kt @@ -4,38 +4,39 @@ import android.Manifest import android.content.Intent import android.content.pm.PackageManager import android.media.MediaRecorder +import android.os.Build import android.os.Bundle import android.os.Handler import android.os.Looper import android.util.Log +import android.view.LayoutInflater import android.view.View import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import androidx.room.Room +import com.alterpat.voicerecorder.databinding.ActivityMainBinding import com.alterpat.voicerecorder.db.AppDatabase import com.alterpat.voicerecorder.db.AudioRecord import com.alterpat.voicerecorder.tools.Timer -import kotlinx.android.synthetic.main.activity_main.* import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import java.io.File import java.io.IOException -import java.lang.Exception import java.text.SimpleDateFormat -import java.util.* +import java.util.Date private const val LOG_TAG = "AudioRecordTest" private const val REQUEST_RECORD_AUDIO_PERMISSION = 200 class MainActivity : AppCompatActivity(), BottomSheet.OnClickListener, Timer.OnTimerUpdateListener { - + private lateinit var binding: ActivityMainBinding private lateinit var fileName: String private lateinit var dirPath: String private var recorder: MediaRecorder? = null private var recording = false private var onPause = false - private var refreshRate : Long = 60 + private var refreshRate: Long = 60 private lateinit var timer: Timer private lateinit var handler: Handler @@ -46,14 +47,15 @@ class MainActivity : AppCompatActivity(), BottomSheet.OnClickListener, Timer.OnT override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) + binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) // Record to the external cache directory for visibility ActivityCompat.requestPermissions(this, permissions, REQUEST_RECORD_AUDIO_PERMISSION) handler = Handler(Looper.myLooper()!!) - recordBtn.setOnClickListener { + binding.recordBtn.setOnClickListener { when { onPause -> resumeRecording() @@ -62,21 +64,21 @@ class MainActivity : AppCompatActivity(), BottomSheet.OnClickListener, Timer.OnT } } - doneBtn.setOnClickListener { + binding.doneBtn.setOnClickListener { stopRecording() showBottomSheet() } - listBtn.setOnClickListener { + binding.listBtn.setOnClickListener { startActivity(Intent(this, ListingActivity::class.java)) } - deleteBtn.setOnClickListener { + binding.deleteBtn.setOnClickListener { stopRecording() - File(dirPath+fileName).delete() + File(dirPath + fileName).delete() } - deleteBtn.isClickable = false + binding.deleteBtn.isClickable = false } override fun onRequestPermissionsResult( @@ -93,17 +95,17 @@ class MainActivity : AppCompatActivity(), BottomSheet.OnClickListener, Timer.OnT //if (!permissionToRecordAccepted) finish() } - private fun startRecording(){ + private fun startRecording() { - if(!permissionToRecordAccepted){ + if (!permissionToRecordAccepted) { ActivityCompat.requestPermissions(this, permissions, REQUEST_RECORD_AUDIO_PERMISSION) return } - listBtn.visibility = View.GONE - doneBtn.visibility = View.VISIBLE - deleteBtn.isClickable = true - deleteBtn.setImageResource(R.drawable.ic_delete_enabled) + binding.listBtn.visibility = View.GONE + binding.doneBtn.visibility = View.VISIBLE + binding.deleteBtn.isClickable = true + binding.deleteBtn.setImageResource(R.drawable.ic_delete_enabled) recording = true timer = Timer(this) @@ -117,16 +119,14 @@ class MainActivity : AppCompatActivity(), BottomSheet.OnClickListener, Timer.OnT dirPath = "${externalCacheDir?.absolutePath}/" fileName = "voice_record_${date}.mp3" - recorder = MediaRecorder().apply { + recorder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) MediaRecorder(this) + else MediaRecorder().apply { setAudioSource(MediaRecorder.AudioSource.MIC) - /** START COMMENT - * These two together enable saving file into mp3 format - * because android doesn't support mp3 saving explicitly **/ setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) setAudioEncoder(MediaRecorder.AudioEncoder.AAC) - /** END COMMENT **/ - - setOutputFile(dirPath+fileName) + setAudioEncodingBitRate(96000) + setAudioSamplingRate(44100) + setOutputFile(dirPath + fileName) try { prepare() } catch (e: IOException) { @@ -136,16 +136,16 @@ class MainActivity : AppCompatActivity(), BottomSheet.OnClickListener, Timer.OnT start() } - recordBtn.setImageResource(R.drawable.ic_pause) + binding.recordBtn.setImageResource(R.drawable.ic_pause) animatePlayerView() } - private fun animatePlayerView(){ - if(recording && !onPause){ - var amp = recorder!!.maxAmplitude - playerView.updateAmps(amp) + private fun animatePlayerView() { + if (recording && !onPause) { + val amp = recorder!!.maxAmplitude + binding.playerView.updateAmps(amp) // write maxmap to a file for visualization in player activity @@ -157,27 +157,27 @@ class MainActivity : AppCompatActivity(), BottomSheet.OnClickListener, Timer.OnT } } - private fun pauseRecording(){ + private fun pauseRecording() { onPause = true recorder?.apply { pause() } - recordBtn.setImageResource(R.drawable.ic_record) + binding.recordBtn.setImageResource(R.drawable.ic_record) timer.pause() } - private fun resumeRecording(){ + private fun resumeRecording() { onPause = false recorder?.apply { resume() } - recordBtn.setImageResource(R.drawable.ic_pause) + binding.recordBtn.setImageResource(R.drawable.ic_pause) animatePlayerView() timer.start() } - private fun stopRecording(){ + private fun stopRecording() { recording = false onPause = false recorder?.apply { @@ -185,29 +185,29 @@ class MainActivity : AppCompatActivity(), BottomSheet.OnClickListener, Timer.OnT release() } recorder = null - recordBtn.setImageResource(R.drawable.ic_record) + binding.recordBtn.setImageResource(R.drawable.ic_record) - listBtn.visibility = View.VISIBLE - doneBtn.visibility = View.GONE - deleteBtn.isClickable = false - deleteBtn.setImageResource(R.drawable.ic_delete_disabled) + binding.listBtn.visibility = View.VISIBLE + binding.doneBtn.visibility = View.GONE + binding.deleteBtn.isClickable = false + binding.deleteBtn.setImageResource(R.drawable.ic_delete_disabled) - playerView.reset() + binding.playerView.reset() try { timer.stop() - }catch (e: Exception){} + } catch (_: Exception) { + } - timerView.text = "00:00.00" + binding.timerView.text = "00:00.00" } - private fun showBottomSheet(){ - var bottomSheet = BottomSheet(dirPath, fileName, this) + private fun showBottomSheet() { + val bottomSheet = BottomSheet(dirPath, fileName, this) bottomSheet.show(supportFragmentManager, LOG_TAG) } - override fun onCancelClicked() { Toast.makeText(this, "Audio record deleted", Toast.LENGTH_SHORT).show() stopRecording() @@ -215,12 +215,13 @@ class MainActivity : AppCompatActivity(), BottomSheet.OnClickListener, Timer.OnT override fun onOkClicked(filePath: String, filename: String) { // add audio record info to database - var db = Room.databaseBuilder( + val db = Room.databaseBuilder( this, AppDatabase::class.java, - "audioRecords").build() + "audioRecords" + ).build() - var duration = timer.format().split(".")[0] + val duration = timer.format().split(".")[0] stopRecording() GlobalScope.launch { @@ -230,9 +231,9 @@ class MainActivity : AppCompatActivity(), BottomSheet.OnClickListener, Timer.OnT } override fun onTimerUpdate(duration: String) { - runOnUiThread{ - if(recording) - timerView.text = duration + runOnUiThread { + if (recording) + binding.timerView.text = duration } } } \ No newline at end of file diff --git a/app/src/main/java/com/alterpat/voicerecorder/PlayerActivity.kt b/app/src/main/java/com/alterpat/voicerecorder/PlayerActivity.kt index 0faf67f..3cc3942 100644 --- a/app/src/main/java/com/alterpat/voicerecorder/PlayerActivity.kt +++ b/app/src/main/java/com/alterpat/voicerecorder/PlayerActivity.kt @@ -2,47 +2,46 @@ package com.alterpat.voicerecorder import android.media.MediaPlayer import android.media.PlaybackParams -import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.os.Handler import android.os.Looper import android.util.Log import android.widget.SeekBar +import androidx.appcompat.app.AppCompatActivity import androidx.core.content.res.ResourcesCompat -import kotlinx.android.synthetic.main.activity_listing.* -import kotlinx.android.synthetic.main.activity_player.* -import kotlinx.android.synthetic.main.activity_player.toolbar +import com.alterpat.voicerecorder.databinding.ActivityPlayerBinding class PlayerActivity : AppCompatActivity() { - + private lateinit var binding: ActivityPlayerBinding private val delay = 100L - private lateinit var runnable : Runnable - private lateinit var handler : Handler - private lateinit var mediaPlayer : MediaPlayer - private var playbackSpeed :Float = 1.0f + private lateinit var runnable: Runnable + private lateinit var handler: Handler + private lateinit var mediaPlayer: MediaPlayer + private var playbackSpeed: Float = 1.0f override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_player) - - setSupportActionBar(toolbar) + binding = ActivityPlayerBinding.inflate(layoutInflater) + setContentView(binding.root) + + setSupportActionBar(binding.toolbar) supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayShowHomeEnabled(true) - toolbar.setNavigationOnClickListener { + binding.toolbar.setNavigationOnClickListener { onBackPressed() } var filePath = intent.getStringExtra("filepath") var filename = intent.getStringExtra("filename") - tvFilename.text = filename + binding.tvFilename.text = filename mediaPlayer = MediaPlayer() mediaPlayer.apply { setDataSource(filePath) prepare() } - seekBar.max = mediaPlayer.duration + binding.seekBar.max = mediaPlayer.duration handler = Handler(Looper.getMainLooper()) playPausePlayer() @@ -51,76 +50,82 @@ class PlayerActivity : AppCompatActivity() { stopPlayer() } - btnPlay.setOnClickListener { + binding.btnPlay.setOnClickListener { playPausePlayer() } - btnForward.setOnClickListener { + binding.btnForward.setOnClickListener { mediaPlayer.seekTo(mediaPlayer.currentPosition + 1000) - seekBar.progress += 1000 + binding.seekBar.progress += 1000 } - btnBackward.setOnClickListener { + binding.btnBackward.setOnClickListener { mediaPlayer.seekTo(mediaPlayer.currentPosition - 1000) - seekBar.progress -= 1000 + binding.seekBar.progress -= 1000 } - seekBar.setOnSeekBarChangeListener(object: SeekBar.OnSeekBarChangeListener{ + binding.seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { override fun onProgressChanged(p0: SeekBar?, p1: Int, p2: Boolean) { - if(p2) mediaPlayer.seekTo(p1) + if (p2) mediaPlayer.seekTo(p1) } + override fun onStartTrackingTouch(p0: SeekBar?) {} override fun onStopTrackingTouch(p0: SeekBar?) {} }) - chip.setOnClickListener { - when(playbackSpeed){ + binding.chip.setOnClickListener { + when (playbackSpeed) { 0.5f -> playbackSpeed += 0.5f 1.0f -> playbackSpeed += 0.5f 1.5f -> playbackSpeed += 0.5f 2.0f -> playbackSpeed = 0.5f } mediaPlayer.playbackParams = PlaybackParams().setSpeed(playbackSpeed) - chip.text = "x $playbackSpeed" + binding.chip.text = "x $playbackSpeed" } } - private fun playPausePlayer(){ - if(!mediaPlayer.isPlaying){ + private fun playPausePlayer() { + if (!mediaPlayer.isPlaying) { mediaPlayer.start() - btnPlay.background = ResourcesCompat.getDrawable(resources, R.drawable.ic_pause_circle, theme) + binding.btnPlay.background = + ResourcesCompat.getDrawable(resources, R.drawable.ic_pause_circle, theme) runnable = Runnable { - var progress = mediaPlayer.currentPosition + val progress = mediaPlayer.currentPosition Log.d("progress", progress.toString()) - seekBar.progress = progress + binding.seekBar.progress = progress - var amp = 80 + Math.random()*300 - playerView.updateAmps(amp.toInt()) + val amp = 80 + Math.random() * 300 + binding.playerView.updateAmps(amp.toInt()) handler.postDelayed(runnable, delay) } handler.postDelayed(runnable, delay) - }else{ + } else { mediaPlayer.pause() - btnPlay.background = ResourcesCompat.getDrawable(resources, R.drawable.ic_play_circle, theme) + binding.btnPlay.background = + ResourcesCompat.getDrawable(resources, R.drawable.ic_play_circle, theme) handler.removeCallbacks(runnable) } } - private fun stopPlayer(){ - btnPlay.background = ResourcesCompat.getDrawable(resources, R.drawable.ic_play_circle, theme) + private fun stopPlayer() { + binding.btnPlay.background = + ResourcesCompat.getDrawable(resources, R.drawable.ic_play_circle, theme) handler.removeCallbacks(runnable) } + @Deprecated("Deprecated in Java") override fun onBackPressed() { super.onBackPressed() mediaPlayer.stop() mediaPlayer.release() handler.removeCallbacks(runnable) } + } \ No newline at end of file diff --git a/app/src/main/java/com/alterpat/voicerecorder/PlayerWaveformView.kt b/app/src/main/java/com/alterpat/voicerecorder/PlayerWaveformView.kt index 4c7228b..e0bd79e 100644 --- a/app/src/main/java/com/alterpat/voicerecorder/PlayerWaveformView.kt +++ b/app/src/main/java/com/alterpat/voicerecorder/PlayerWaveformView.kt @@ -1,37 +1,41 @@ package com.alterpat.voicerecorder import android.content.Context -import android.graphics.* +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.graphics.Rect +import android.graphics.RectF import android.util.AttributeSet -import android.util.DisplayMetrics import android.util.Log import android.view.View -import androidx.annotation.Nullable -class PlayerWaveformView: View { +class PlayerWaveformView : View { private lateinit var spikes: Array private lateinit var paintRead: Paint - private var w : Int = 18 - private var d : Int = 4 - private var sw : Int = 0 - private var maxAmp : Int = 200 + private var w: Int = 18 + private var d: Int = 4 + private var sw: Int = 0 + private var maxAmp: Int = 200 private var delta = 320 private lateinit var rect: Rect private var nbSpikes = 30 - constructor(context: Context?) : super(context){ + constructor(context: Context?) : super(context) { init(null) } - constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs){ + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { init(attrs) } + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( context, attrs, defStyleAttr - ){ + ) { init(attrs) } @@ -40,15 +44,15 @@ class PlayerWaveformView: View { attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int - ) : super(context, attrs, defStyleAttr, defStyleRes){ + ) : super(context, attrs, defStyleAttr, defStyleRes) { init(attrs) } // this function is to avoid duplicating code in every constructor // indeed each constructor is called in a specific situation // and we want the View to de the same thing no matter what - private fun init(attrs: AttributeSet?){ - spikes = Array(nbSpikes){ RectF() } + private fun init(attrs: AttributeSet?) { + spikes = Array(nbSpikes) { RectF() } paintRead = Paint() //Paint.ANTI_ALIAS_FLAG paintRead.color = Color.rgb(244, 81, 30) // orange @@ -64,15 +68,15 @@ class PlayerWaveformView: View { } - fun updateAmps(amp: Int){ + fun updateAmps(amp: Int) { //var norm = Math.min(amp/7, maxAmp) // 100*abs(Math.log10(1.0*amp/(sqrt(amp*1.0)+1))) var norm = amp - for(i in spikes.indices){ - var bottom : Float = (Math.random() * norm).toFloat() + for (i in spikes.indices) { + var bottom: Float = (Math.random() * norm).toFloat() var top = delta - bottom - var rectUp = RectF(i*(w+d)*1f, top, i*(w+d) + w*1f, bottom) + var rectUp = RectF(i * (w + d) * 1f, top, i * (w + d) + w * 1f, bottom) spikes[i] = rectUp } @@ -86,7 +90,7 @@ class PlayerWaveformView: View { spikes.forEach { Log.d("waveform", it.bottom.toString()) - canvas?.drawRoundRect(it,10f, 10f, paintRead) + canvas?.drawRoundRect(it, 10f, 10f, paintRead) } //Log.d("waveform", rect.bottom.toString()) //canvas?.drawRect(rect, paintRead) diff --git a/app/src/main/java/com/alterpat/voicerecorder/RecorderWaveformView.kt b/app/src/main/java/com/alterpat/voicerecorder/RecorderWaveformView.kt index a771a76..cc36431 100644 --- a/app/src/main/java/com/alterpat/voicerecorder/RecorderWaveformView.kt +++ b/app/src/main/java/com/alterpat/voicerecorder/RecorderWaveformView.kt @@ -1,34 +1,37 @@ package com.alterpat.voicerecorder import android.content.Context -import android.graphics.* +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.graphics.RectF import android.util.AttributeSet -import android.util.DisplayMetrics import android.view.View -import androidx.annotation.Nullable -class RecorderWaveformView: View { +class RecorderWaveformView : View { private lateinit var amplitudes: ArrayList private lateinit var spikes: ArrayList private lateinit var paintRead: Paint - private var w : Float = 9f - private var d : Float = 4f - private var sw : Int = 0 - private var maxSpikes : Int = 0 - private var maxAmp : Int = 200 + private var w: Float = 9f + private var d: Float = 4f + private var sw: Int = 0 + private var maxSpikes: Int = 0 + private var maxAmp: Int = 200 - constructor(context: Context?) : super(context){ + constructor(context: Context?) : super(context) { init(null) } - constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs){ + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { init(attrs) } + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( context, attrs, defStyleAttr - ){ + ) { init(attrs) } @@ -37,14 +40,14 @@ class RecorderWaveformView: View { attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int - ) : super(context, attrs, defStyleAttr, defStyleRes){ + ) : super(context, attrs, defStyleAttr, defStyleRes) { init(attrs) } // this function is to avoid duplicating code in every constructor // indeed each constructor is called in a specific situation // and we want the View to de the same thing no matter what - private fun init(attrs: AttributeSet?){ + private fun init(attrs: AttributeSet?) { amplitudes = ArrayList() paintRead = Paint() //Paint.ANTI_ALIAS_FLAG paintRead.color = Color.rgb(244, 81, 30) // orange @@ -53,30 +56,30 @@ class RecorderWaveformView: View { val displayMetrics = resources.displayMetrics sw = displayMetrics.widthPixels - maxSpikes = (sw/(w+d)).toInt() + maxSpikes = (sw / (w + d)).toInt() spikes = ArrayList() } - fun reset(){ + fun reset() { amplitudes.clear() spikes.clear() invalidate() } - fun updateAmps(amp: Int){ + fun updateAmps(amp: Int) { - var norm = Math.min(amp/7, maxAmp) // 100*abs(Math.log10(1.0*amp/(sqrt(amp*1.0)+1))) + var norm = Math.min(amp / 7, maxAmp) // 100*abs(Math.log10(1.0*amp/(sqrt(amp*1.0)+1))) amplitudes.add(norm) var amps = amplitudes.takeLast(maxSpikes) spikes.clear() - for(i in amps.indices){ + for (i in amps.indices) { val delta = maxAmp.toFloat() val top = delta - amps[i] var bottom = top + amps[i] as Int - var rectUp = RectF(sw-i*(w+d), top, sw-i*(w+d) - w, bottom) - var rectDown = RectF(sw-i*(w+d), delta-2, sw-i*(w+d) - w, delta+amps[i]) + var rectUp = RectF(sw - i * (w + d), top, sw - i * (w + d) - w, bottom) + var rectDown = RectF(sw - i * (w + d), delta - 2, sw - i * (w + d) - w, delta + amps[i]) spikes.add(rectUp) spikes.add(rectDown) } @@ -88,7 +91,7 @@ class RecorderWaveformView: View { // therefore we shouldn't initialize objects here spikes.forEach { - canvas?.drawRoundRect(it, 6f, 6f,paintRead) + canvas?.drawRoundRect(it, 6f, 6f, paintRead) } } } \ No newline at end of file diff --git a/app/src/main/java/com/alterpat/voicerecorder/db/AppDatabase.kt b/app/src/main/java/com/alterpat/voicerecorder/db/AppDatabase.kt index b70a3bf..5e214c3 100644 --- a/app/src/main/java/com/alterpat/voicerecorder/db/AppDatabase.kt +++ b/app/src/main/java/com/alterpat/voicerecorder/db/AppDatabase.kt @@ -4,6 +4,6 @@ import androidx.room.Database import androidx.room.RoomDatabase @Database(entities = [AudioRecord::class], version = 1) -abstract class AppDatabase : RoomDatabase(){ +abstract class AppDatabase : RoomDatabase() { abstract fun audioRecordDAO(): AudioRecordDAO } \ No newline at end of file diff --git a/app/src/main/java/com/alterpat/voicerecorder/db/AudioRecord.kt b/app/src/main/java/com/alterpat/voicerecorder/db/AudioRecord.kt index b35bb4b..4e07636 100644 --- a/app/src/main/java/com/alterpat/voicerecorder/db/AudioRecord.kt +++ b/app/src/main/java/com/alterpat/voicerecorder/db/AudioRecord.kt @@ -3,17 +3,18 @@ package com.alterpat.voicerecorder.db import androidx.room.Entity import androidx.room.Ignore import androidx.room.PrimaryKey -import java.sql.Timestamp @Entity(tableName = "audioRecords") -data class AudioRecord ( +data class AudioRecord( var filename: String, var filePath: String, var date: Long, - var duration: String){ + var duration: String +) { @PrimaryKey(autoGenerate = true) var id: Int = 0 + @Ignore var isChecked: Boolean = false } \ No newline at end of file diff --git a/app/src/main/java/com/alterpat/voicerecorder/tools/Timer.kt b/app/src/main/java/com/alterpat/voicerecorder/tools/Timer.kt index f48dfdb..b95ec5a 100644 --- a/app/src/main/java/com/alterpat/voicerecorder/tools/Timer.kt +++ b/app/src/main/java/com/alterpat/voicerecorder/tools/Timer.kt @@ -1,30 +1,30 @@ package com.alterpat.voicerecorder.tools -import java.util.* import java.util.Timer +import java.util.TimerTask class Timer(private var listener: OnTimerUpdateListener) { - interface OnTimerUpdateListener{ + interface OnTimerUpdateListener { fun onTimerUpdate(duration: String) } - private var duration : Long = 0 - private var period : Long = 258 - private lateinit var timer : Timer + private var duration: Long = 0 + private var period: Long = 258 + private lateinit var timer: Timer - fun start(){ + fun start() { timer = Timer() - timer.scheduleAtFixedRate(object : TimerTask(){ + timer.scheduleAtFixedRate(object : TimerTask() { override fun run() { duration += period listener.onTimerUpdate(format()) } - },period, period) + }, period, period) } - fun pause(){ + fun pause() { timer.cancel() } @@ -34,19 +34,21 @@ class Timer(private var listener: OnTimerUpdateListener) { timer.purge() } - fun getDuration(): Long{return duration} + fun getDuration(): Long { + return duration + } - fun format(): String{ + fun format(): String { val milli = duration % 1000 - val seconds = (duration / 1000) % 60 + val seconds = (duration / 1000) % 60 val minutes = (duration / (1000 * 60) % 60) val hours = (duration / (1000 * 60 * 60) % 24) - var formatted : String - formatted = if(hours > 0) - "%02d:%02d:%02d.%02d".format(hours, minutes, seconds, milli/10) + var formatted: String + formatted = if (hours > 0) + "%02d:%02d:%02d.%02d".format(hours, minutes, seconds, milli / 10) else - "%02d:%02d.%02d".format(minutes, seconds, milli/10) + "%02d:%02d.%02d".format(minutes, seconds, milli / 10) return formatted } diff --git a/app/src/main/java/com/alterpat/voicerecorder/tools/TimerHandler.kt b/app/src/main/java/com/alterpat/voicerecorder/tools/TimerHandler.kt index 565028b..053b72f 100644 --- a/app/src/main/java/com/alterpat/voicerecorder/tools/TimerHandler.kt +++ b/app/src/main/java/com/alterpat/voicerecorder/tools/TimerHandler.kt @@ -4,13 +4,14 @@ import android.os.Handler import android.os.Looper const val delay = 100L + class Timer(private val listener: OnTimerUpdateListener) { - interface OnTimerUpdateListener{ + interface OnTimerUpdateListener { fun onTimerTicks(duration: String) } - private var handler : Handler = Handler(Looper.getMainLooper()) + private var handler: Handler = Handler(Looper.getMainLooper()) private lateinit var runnable: Runnable private var duration = 0L @@ -23,30 +24,30 @@ class Timer(private val listener: OnTimerUpdateListener) { } } - fun start(){ + fun start() { handler.postDelayed(runnable, delay) } - fun pause(){ + fun pause() { handler.removeCallbacks(runnable) } - fun stop(){ + fun stop() { handler.removeCallbacks(runnable) listener.onTimerTicks("00:00.00") duration = 0L } - private fun format(): String{ + private fun format(): String { val milli = duration % 1000 val seconds = (duration / 1000) % 60 val minutes = (duration / (1000 * 60)) % 60 val hours = (duration / (1000 * 60 * 60)) % 24 - var formatted = if(hours > 0) - "%02d:%02d:%02d.%02d".format(hours, minutes, seconds, milli/10) + var formatted = if (hours > 0) + "%02d:%02d:%02d.%02d".format(hours, minutes, seconds, milli / 10) else - "%02d:%02d.%02d".format(minutes, seconds, milli/10) + "%02d:%02d.%02d".format(minutes, seconds, milli / 10) return formatted } diff --git a/app/src/main/res/drawable/ic_back.xml b/app/src/main/res/drawable/ic_back.xml index 3ab2d9d..34b7cb8 100644 --- a/app/src/main/res/drawable/ic_back.xml +++ b/app/src/main/res/drawable/ic_back.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_backward.xml b/app/src/main/res/drawable/ic_backward.xml index 75888e6..a366ef7 100644 --- a/app/src/main/res/drawable/ic_backward.xml +++ b/app/src/main/res/drawable/ic_backward.xml @@ -1,8 +1,10 @@ - - + android:viewportWidth="24" + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_circle.xml b/app/src/main/res/drawable/ic_circle.xml index 00f3986..a1f57db 100644 --- a/app/src/main/res/drawable/ic_circle.xml +++ b/app/src/main/res/drawable/ic_circle.xml @@ -1,5 +1,4 @@ - - + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_circle_pressed.xml b/app/src/main/res/drawable/ic_circle_pressed.xml index de79604..f3d0e68 100644 --- a/app/src/main/res/drawable/ic_circle_pressed.xml +++ b/app/src/main/res/drawable/ic_circle_pressed.xml @@ -1,5 +1,4 @@ - - + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_circle_selectable.xml b/app/src/main/res/drawable/ic_circle_selectable.xml index a76a3b7..5b7df86 100644 --- a/app/src/main/res/drawable/ic_circle_selectable.xml +++ b/app/src/main/res/drawable/ic_circle_selectable.xml @@ -1,8 +1,6 @@ - + - + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_circle_selectable_white.xml b/app/src/main/res/drawable/ic_circle_selectable_white.xml index 09c09ad..e2dd5b3 100644 --- a/app/src/main/res/drawable/ic_circle_selectable_white.xml +++ b/app/src/main/res/drawable/ic_circle_selectable_white.xml @@ -1,8 +1,6 @@ - + - + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_circle_white.xml b/app/src/main/res/drawable/ic_circle_white.xml index f095461..507d925 100644 --- a/app/src/main/res/drawable/ic_circle_white.xml +++ b/app/src/main/res/drawable/ic_circle_white.xml @@ -1,5 +1,4 @@ - - + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_close.xml b/app/src/main/res/drawable/ic_close.xml index e82b00a..13a42e5 100644 --- a/app/src/main/res/drawable/ic_close.xml +++ b/app/src/main/res/drawable/ic_close.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_delete.xml b/app/src/main/res/drawable/ic_delete.xml index 433896d..accd0d7 100644 --- a/app/src/main/res/drawable/ic_delete.xml +++ b/app/src/main/res/drawable/ic_delete.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_delete_disabled.xml b/app/src/main/res/drawable/ic_delete_disabled.xml index 985d12d..7d4a595 100644 --- a/app/src/main/res/drawable/ic_delete_disabled.xml +++ b/app/src/main/res/drawable/ic_delete_disabled.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_delete_disabled2.xml b/app/src/main/res/drawable/ic_delete_disabled2.xml index fb25953..da6955b 100644 --- a/app/src/main/res/drawable/ic_delete_disabled2.xml +++ b/app/src/main/res/drawable/ic_delete_disabled2.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_delete_enabled.xml b/app/src/main/res/drawable/ic_delete_enabled.xml index 43fe2a1..dc0b983 100644 --- a/app/src/main/res/drawable/ic_delete_enabled.xml +++ b/app/src/main/res/drawable/ic_delete_enabled.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_done.xml b/app/src/main/res/drawable/ic_done.xml index dbb58cd..9ae924a 100644 --- a/app/src/main/res/drawable/ic_done.xml +++ b/app/src/main/res/drawable/ic_done.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_edit.xml b/app/src/main/res/drawable/ic_edit.xml index 339af96..985adb9 100644 --- a/app/src/main/res/drawable/ic_edit.xml +++ b/app/src/main/res/drawable/ic_edit.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_edit_disabled.xml b/app/src/main/res/drawable/ic_edit_disabled.xml index 4316947..489e4c7 100644 --- a/app/src/main/res/drawable/ic_edit_disabled.xml +++ b/app/src/main/res/drawable/ic_edit_disabled.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_forward.xml b/app/src/main/res/drawable/ic_forward.xml index 99962de..d846063 100644 --- a/app/src/main/res/drawable/ic_forward.xml +++ b/app/src/main/res/drawable/ic_forward.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_list.xml b/app/src/main/res/drawable/ic_list.xml index ddc740c..9d4d39a 100644 --- a/app/src/main/res/drawable/ic_list.xml +++ b/app/src/main/res/drawable/ic_list.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_list_selected.xml b/app/src/main/res/drawable/ic_list_selected.xml index 011c6d7..7c66c46 100644 --- a/app/src/main/res/drawable/ic_list_selected.xml +++ b/app/src/main/res/drawable/ic_list_selected.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_menu.xml b/app/src/main/res/drawable/ic_menu.xml index 44bf628..ea4b0ea 100644 --- a/app/src/main/res/drawable/ic_menu.xml +++ b/app/src/main/res/drawable/ic_menu.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_pause.xml b/app/src/main/res/drawable/ic_pause.xml index 6bfbe82..fbc2524 100644 --- a/app/src/main/res/drawable/ic_pause.xml +++ b/app/src/main/res/drawable/ic_pause.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_pause_circle.xml b/app/src/main/res/drawable/ic_pause_circle.xml index f35034b..d66c7bc 100644 --- a/app/src/main/res/drawable/ic_pause_circle.xml +++ b/app/src/main/res/drawable/ic_pause_circle.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_play.xml b/app/src/main/res/drawable/ic_play.xml index 399fa5e..469232d 100644 --- a/app/src/main/res/drawable/ic_play.xml +++ b/app/src/main/res/drawable/ic_play.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_play_circle.xml b/app/src/main/res/drawable/ic_play_circle.xml index 415c740..5eccf89 100644 --- a/app/src/main/res/drawable/ic_play_circle.xml +++ b/app/src/main/res/drawable/ic_play_circle.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_record.xml b/app/src/main/res/drawable/ic_record.xml index 7a3812b..edde0e3 100644 --- a/app/src/main/res/drawable/ic_record.xml +++ b/app/src/main/res/drawable/ic_record.xml @@ -1,5 +1,4 @@ - - + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_search.xml b/app/src/main/res/drawable/ic_search.xml index 47bab97..3111e97 100644 --- a/app/src/main/res/drawable/ic_search.xml +++ b/app/src/main/res/drawable/ic_search.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_stop.xml b/app/src/main/res/drawable/ic_stop.xml index d57c65a..b4645c5 100644 --- a/app/src/main/res/drawable/ic_stop.xml +++ b/app/src/main/res/drawable/ic_stop.xml @@ -1,8 +1,7 @@ - - + + - + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_listing.xml b/app/src/main/res/layout/activity_listing.xml index 6edae88..e842fea 100644 --- a/app/src/main/res/layout/activity_listing.xml +++ b/app/src/main/res/layout/activity_listing.xml @@ -1,75 +1,79 @@ - + + android:outlineSpotShadowColor="@android:color/transparent" + android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"> + app:layout_scrollFlags="scroll|exitUntilCollapsed" + app:statusBarScrim="@color/colorBackground"> + app:title="Recordings"> + + + android:background="@drawable/ic_close" /> + android:layout_marginEnd="16dp" + android:background="@drawable/ic_list" /> - + + - - + android:hint="Search audio record" + android:imeOptions="actionSearch" + android:inputType="text" + android:textColor="@color/colorText" + android:textColorHint="@color/colorGrayDark" /> + + + android:layout_height="match_parent" /> + + app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"> + + + android:background="@drawable/ic_edit_disabled" + android:clickable="false" /> + + android:text="Rename" + android:textColor="@color/colorDisabled" /> + + android:background="@drawable/ic_delete_disabled2" + android:clickable="false" /> + + android:text="Delete" + android:textColor="@color/colorDisabled" /> diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index f91a090..9e408bc 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,10 +1,10 @@ - + + app:layout_constraintStart_toStartOf="parent" /> + android:clickable="false" + android:src="@drawable/ic_delete_disabled" /> + android:background="@drawable/ic_record" /> + android:src="@drawable/ic_menu" /> + android:src="@drawable/ic_done" + android:visibility="gone" /> diff --git a/app/src/main/res/layout/activity_player.xml b/app/src/main/res/layout/activity_player.xml index d56b9cb..2791f4e 100644 --- a/app/src/main/res/layout/activity_player.xml +++ b/app/src/main/res/layout/activity_player.xml @@ -12,14 +12,15 @@ android:layout_height="?actionBarSize" app:layout_constraintTop_toTopOf="parent" app:title=" "> + + android:gravity="center" + android:text="" + android:textSize="20sp" /> - - + + android:layout_marginTop="40dp" + android:text="Save recording?" + android:textSize="24sp" /> + app:boxStrokeColor="@color/colorBox" + app:boxStrokeWidth="2dp"> + + android:padding="16dp" + android:text="12" /> + android:layout_marginBottom="30dp" + android:gravity="center_horizontal"> + + android:text="Cancel" + android:textColor="@color/colorGrayDark" + app:cornerRadius="26dp" + app:rippleColor="@color/colorGrayDark" /> + + android:text="OK" + android:textColor="@android:color/white" + app:cornerRadius="26dp" + app:rippleColor="@color/colorGrayDark" /> \ No newline at end of file diff --git a/app/src/main/res/layout/itemview_layout.xml b/app/src/main/res/layout/itemview_layout.xml index 32d666a..1b58368 100644 --- a/app/src/main/res/layout/itemview_layout.xml +++ b/app/src/main/res/layout/itemview_layout.xml @@ -1,40 +1,44 @@ - + android:paddingStart="22dp"> + + android:background="@drawable/ic_circle" + android:src="@drawable/ic_play" /> + + android:layout_marginStart="20dp" + android:layout_weight="1" + android:orientation="vertical"> + + android:textSize="18sp" + android:textStyle="bold" /> + + android:textSize="14sp" /> + + android:layout_height="20dp" + android:clickable="false" + android:visibility="gone" /> \ No newline at end of file diff --git a/app/src/test/java/com/alterpat/voicerecorder/ExampleUnitTest.kt b/app/src/test/java/com/alterpat/voicerecorder/ExampleUnitTest.kt index aea4121..9c01cc2 100644 --- a/app/src/test/java/com/alterpat/voicerecorder/ExampleUnitTest.kt +++ b/app/src/test/java/com/alterpat/voicerecorder/ExampleUnitTest.kt @@ -1,9 +1,8 @@ package com.alterpat.voicerecorder +import org.junit.Assert.assertEquals import org.junit.Test -import org.junit.Assert.* - /** * Example local unit test, which will execute on the development machine (host). * diff --git a/build.gradle b/build.gradle index 1241d12..25ce43d 100644 --- a/build.gradle +++ b/build.gradle @@ -1,12 +1,12 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = "1.3.72" + ext.kotlin_version = '1.8.21' repositories { google() - jcenter() + mavenCentral() } dependencies { - classpath "com.android.tools.build:gradle:4.0.1" + classpath 'com.android.tools.build:gradle:8.0.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong @@ -17,7 +17,7 @@ buildscript { allprojects { repositories { google() - jcenter() + mavenCentral() } } diff --git a/gradle.properties b/gradle.properties index 4d15d01..fde9fa4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,4 +18,7 @@ android.useAndroidX=true # Automatically convert third-party libraries to use AndroidX android.enableJetifier=true # Kotlin code style for this project: "official" or "obsolete": -kotlin.code.style=official \ No newline at end of file +kotlin.code.style=official +android.defaults.buildfeatures.buildconfig=true +android.nonTransitiveRClass=false +android.nonFinalResIds=false \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8c58e9d..b70a3fc 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip