WIP: Audio editor
This commit is contained in:
parent
15d23dbca1
commit
cd62cabb52
13 changed files with 815 additions and 144 deletions
|
@ -100,5 +100,7 @@ dependencies {
|
|||
implementation(libs.androidx.swiperefreshlayout)
|
||||
implementation(libs.androidx.constraintlayout)
|
||||
implementation(libs.tandroidlame)
|
||||
implementation(libs.bundles.audiotool)
|
||||
implementation(libs.bundles.amplituda)
|
||||
implementation(libs.autofittextview)
|
||||
}
|
||||
|
|
|
@ -77,6 +77,8 @@
|
|||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".activities.EditRecordingActivity" />
|
||||
|
||||
<activity
|
||||
android:name=".activities.SettingsActivity"
|
||||
android:configChanges="orientation"
|
||||
|
|
|
@ -0,0 +1,362 @@
|
|||
package com.simplemobiletools.voicerecorder.activities
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.media.AudioManager
|
||||
import android.media.MediaPlayer
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.PowerManager
|
||||
import android.provider.DocumentsContract
|
||||
import android.widget.SeekBar
|
||||
import androidx.core.view.children
|
||||
import com.simplemobiletools.commons.extensions.*
|
||||
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
|
||||
import com.simplemobiletools.voicerecorder.R
|
||||
import com.simplemobiletools.voicerecorder.databinding.ActivityEditRecordingBinding
|
||||
import com.simplemobiletools.voicerecorder.extensions.deleteRecordings
|
||||
import com.simplemobiletools.voicerecorder.extensions.getAllRecordings
|
||||
import com.simplemobiletools.voicerecorder.helpers.getAudioFileContentUri
|
||||
import com.simplemobiletools.voicerecorder.models.Recording
|
||||
import linc.com.amplituda.Amplituda
|
||||
import linc.com.amplituda.callback.AmplitudaSuccessListener
|
||||
import linc.com.library.AudioTool
|
||||
import java.io.File
|
||||
import java.util.Timer
|
||||
import java.util.TimerTask
|
||||
|
||||
class EditRecordingActivity : SimpleActivity() {
|
||||
companion object {
|
||||
const val RECORDING_ID = "recording_id"
|
||||
}
|
||||
|
||||
private var player: MediaPlayer? = null
|
||||
private var progressTimer = Timer()
|
||||
private lateinit var recording: Recording
|
||||
private lateinit var currentRecording: Recording
|
||||
private var progressStart: Float = 0f
|
||||
|
||||
private lateinit var binding: ActivityEditRecordingBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
isMaterialActivity = true
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityEditRecordingBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
setupOptionsMenu()
|
||||
|
||||
updateMaterialActivityViews(binding.mainCoordinator, binding.recordingVisualizer, useTransparentNavigation = false, useTopSearchMenu = false)
|
||||
|
||||
val recordingId = intent.getIntExtra(RECORDING_ID, -1)
|
||||
if (recordingId == -1) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
recording = getAllRecordings().first { it.id == recordingId }
|
||||
currentRecording = recording
|
||||
// AudioTool.getInstance(this)
|
||||
// .withAudio(File(recording.path))
|
||||
// .cutAudio("00:00:00", "00:00:00.250") {}
|
||||
// .saveCurrentTo(recording.path)
|
||||
// .release()
|
||||
|
||||
// binding.recordingVisualizer.waveProgressColor = getProperPrimaryColor()
|
||||
// binding.recordingVisualizer.setSampleFrom(recording.path)
|
||||
binding.recordingVisualizer.chunkColor = getProperPrimaryColor()
|
||||
binding.recordingVisualizer.recreate()
|
||||
binding.recordingVisualizer.editListener = {
|
||||
if (binding.recordingVisualizer.startPosition >= 0f) {
|
||||
binding.settingsToolbar.menu.children.forEach { it.isVisible = true }
|
||||
} else {
|
||||
binding.settingsToolbar.menu.children.forEach { it.isVisible = false }
|
||||
}
|
||||
}
|
||||
updateVisualization()
|
||||
// android.media.MediaCodec.createByCodecName().createInputSurface()
|
||||
// binding.recordingVisualizer.update()
|
||||
|
||||
initMediaPlayer()
|
||||
playRecording(recording.path, recording.id, recording.title, recording.duration, false)
|
||||
|
||||
binding.playerControlsWrapper.playPauseBtn.setOnClickListener {
|
||||
togglePlayPause()
|
||||
}
|
||||
setupColors()
|
||||
}
|
||||
|
||||
private fun updateVisualization() {
|
||||
Amplituda(this).processAudio(currentRecording.path)
|
||||
.get(AmplitudaSuccessListener {
|
||||
binding.recordingVisualizer.recreate()
|
||||
binding.recordingVisualizer.clearEditing()
|
||||
binding.recordingVisualizer.putAmplitudes(it.amplitudesAsList())
|
||||
})
|
||||
}
|
||||
|
||||
private fun setupColors() {
|
||||
val properPrimaryColor = getProperPrimaryColor()
|
||||
updateTextColors(binding.mainCoordinator)
|
||||
|
||||
val textColor = getProperTextColor()
|
||||
arrayListOf(binding.playerControlsWrapper.previousBtn, binding.playerControlsWrapper.nextBtn).forEach {
|
||||
it.applyColorFilter(textColor)
|
||||
}
|
||||
|
||||
binding.playerControlsWrapper.playPauseBtn.background.applyColorFilter(properPrimaryColor)
|
||||
binding.playerControlsWrapper.playPauseBtn.setImageDrawable(getToggleButtonIcon(false))
|
||||
}
|
||||
|
||||
private fun setupOptionsMenu() {
|
||||
binding.settingsToolbar.inflateMenu(R.menu.menu_edit)
|
||||
// binding.settingsToolbar.toggleHideOnScroll(false)
|
||||
// binding.settingsToolbar.setupMenu()
|
||||
|
||||
// binding.settingsToolbar.onSearchOpenListener = {
|
||||
// if (binding.viewPager.currentItem == 0) {
|
||||
// binding.viewPager.currentItem = 1
|
||||
// }
|
||||
// }
|
||||
|
||||
// binding.settingsToolbar.onSearchTextChangedListener = { text ->
|
||||
// getPagerAdapter()?.searchTextChanged(text)
|
||||
// }
|
||||
|
||||
binding.settingsToolbar.menu.children.forEach { it.isVisible = false }
|
||||
binding.settingsToolbar.setOnMenuItemClickListener { menuItem ->
|
||||
when (menuItem.itemId) {
|
||||
R.id.play -> {
|
||||
val start = binding.recordingVisualizer.startPosition
|
||||
val end = binding.recordingVisualizer.endPosition
|
||||
|
||||
val startMillis = start * currentRecording.duration
|
||||
val durationMillis = (end - start) * currentRecording.duration
|
||||
val startMillisPart = String.format("%.3f", startMillis - startMillis.toInt()).replace("0.", "")
|
||||
val durationMillisPart = String.format("%.3f", durationMillis - durationMillis.toInt()).replace("0.", "")
|
||||
val startFormatted = (startMillis.toInt()).getFormattedDuration(true) + ".$startMillisPart"
|
||||
val durationFormatted = (durationMillis.toInt()).getFormattedDuration(true) + ".$durationMillisPart"
|
||||
AudioTool.getInstance(this)
|
||||
.withAudio(File(currentRecording.path))
|
||||
.cutAudio(startFormatted, durationFormatted) {
|
||||
progressStart = binding.recordingVisualizer.startPosition
|
||||
playRecording(it.path, null, it.name, durationMillis.toInt(), true)
|
||||
}
|
||||
.release()
|
||||
// playRecording()
|
||||
}
|
||||
R.id.cut -> {
|
||||
val start = binding.recordingVisualizer.startPosition
|
||||
val end = binding.recordingVisualizer.endPosition
|
||||
|
||||
val startMillis = start * currentRecording.duration
|
||||
val endMillis = end * currentRecording.duration
|
||||
val realEnd = (1 - end) * currentRecording.duration
|
||||
val startMillisPart = String.format("%.3f", startMillis - startMillis.toInt()).replace("0.", "")
|
||||
val endMillisPart = String.format("%.3f", endMillis - endMillis.toInt()).replace("0.", "")
|
||||
val realEndMillisPart = String.format("%.3f", realEnd - realEnd.toInt()).replace("0.", "")
|
||||
val startFormatted = (startMillis.toInt()).getFormattedDuration(true) + ".$startMillisPart"
|
||||
val endFormatted = (endMillis.toInt()).getFormattedDuration(true) + ".$endMillisPart"
|
||||
val realEndFormatted = (realEnd.toInt()).getFormattedDuration(true) + ".$realEndMillisPart"
|
||||
|
||||
var leftPart: File? = null
|
||||
var rightPart: File? = null
|
||||
|
||||
fun merge() {
|
||||
if (leftPart != null && rightPart != null) {
|
||||
ensureBackgroundThread {
|
||||
AudioTool.getInstance(this)
|
||||
.joinAudios(arrayOf(leftPart, rightPart), "${currentRecording.path}.edit.${currentRecording.path.getFilenameExtension()}") {
|
||||
runOnUiThread {
|
||||
currentRecording = Recording(-1, it.name, it.path, it.lastModified().toInt(), (startMillis + realEnd).toInt(), it.getProperSize(false).toInt())
|
||||
updateVisualization()
|
||||
playRecording(currentRecording.path, currentRecording.id, currentRecording.title, currentRecording.duration, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AudioTool.getInstance(this)
|
||||
.withAudio(File(currentRecording.path))
|
||||
.cutAudio("00:00:00", startFormatted) {
|
||||
leftPart = it
|
||||
merge()
|
||||
}
|
||||
AudioTool.getInstance(this)
|
||||
.withAudio(File(currentRecording.path))
|
||||
.cutAudio(endFormatted, realEndFormatted) {
|
||||
rightPart = it
|
||||
merge()
|
||||
}
|
||||
}
|
||||
// R.id.save -> {
|
||||
// binding.recordingVisualizer.clearEditing()
|
||||
// currentRecording = recording
|
||||
// playRecording(currentRecording.path, currentRecording.id, currentRecording.title, currentRecording.duration, true)
|
||||
// }
|
||||
R.id.clear -> {
|
||||
progressStart = 0f
|
||||
binding.recordingVisualizer.clearEditing()
|
||||
playRecording(currentRecording.path, currentRecording.id, currentRecording.title, currentRecording.duration, true)
|
||||
}
|
||||
R.id.reset -> {
|
||||
progressStart = 0f
|
||||
binding.recordingVisualizer.clearEditing()
|
||||
currentRecording = recording
|
||||
playRecording(currentRecording.path, currentRecording.id, currentRecording.title, currentRecording.duration, true)
|
||||
}
|
||||
else -> return@setOnMenuItemClickListener false
|
||||
}
|
||||
return@setOnMenuItemClickListener true
|
||||
}
|
||||
}
|
||||
|
||||
private fun initMediaPlayer() {
|
||||
player = MediaPlayer().apply {
|
||||
setWakeMode(this@EditRecordingActivity, PowerManager.PARTIAL_WAKE_LOCK)
|
||||
setAudioStreamType(AudioManager.STREAM_MUSIC)
|
||||
|
||||
setOnCompletionListener {
|
||||
progressTimer.cancel()
|
||||
binding.playerControlsWrapper.playerProgressbar.progress = binding.playerControlsWrapper.playerProgressbar.max
|
||||
binding.playerControlsWrapper.playerProgressCurrent.text = binding.playerControlsWrapper.playerProgressMax.text
|
||||
binding.playerControlsWrapper.playPauseBtn.setImageDrawable(getToggleButtonIcon(false))
|
||||
}
|
||||
|
||||
setOnPreparedListener {
|
||||
// setupProgressTimer()
|
||||
// player?.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun playRecording(path: String, id: Int?, title: String?, duration: Int?, playOnPrepared: Boolean) {
|
||||
resetProgress(title, duration)
|
||||
// (binding.recordingsList.adapter as RecordingsAdapter).updateCurrentRecording(recording.id)
|
||||
// playOnPreparation = playOnPrepared
|
||||
|
||||
player!!.apply {
|
||||
reset()
|
||||
|
||||
try {
|
||||
val uri = Uri.parse(path)
|
||||
when {
|
||||
DocumentsContract.isDocumentUri(this@EditRecordingActivity, uri) -> {
|
||||
setDataSource(this@EditRecordingActivity, uri)
|
||||
}
|
||||
|
||||
path.isEmpty() -> {
|
||||
setDataSource(this@EditRecordingActivity, getAudioFileContentUri(id?.toLong() ?: 0))
|
||||
}
|
||||
|
||||
else -> {
|
||||
setDataSource(path)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
showErrorToast(e)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
prepareAsync()
|
||||
} catch (e: Exception) {
|
||||
showErrorToast(e)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
binding.playerControlsWrapper.playPauseBtn.setImageDrawable(getToggleButtonIcon(false))
|
||||
binding.playerControlsWrapper.playerProgressbar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
||||
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||
if (fromUser) {
|
||||
player?.seekTo(progress * 1000)
|
||||
binding.playerControlsWrapper.playerProgressCurrent.text = progress.getFormattedDuration()
|
||||
resumePlayback()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStartTrackingTouch(seekBar: SeekBar) {}
|
||||
|
||||
override fun onStopTrackingTouch(seekBar: SeekBar) {}
|
||||
})
|
||||
}
|
||||
|
||||
private fun setupProgressTimer() {
|
||||
progressTimer.cancel()
|
||||
progressTimer = Timer()
|
||||
progressTimer.scheduleAtFixedRate(getProgressUpdateTask(), 100, 100)
|
||||
}
|
||||
|
||||
private fun getProgressUpdateTask() = object : TimerTask() {
|
||||
override fun run() {
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
if (player != null) {
|
||||
binding.recordingVisualizer.updateProgress(player!!.currentPosition.toFloat() / (currentRecording.duration * 1000) + progressStart)
|
||||
val progress = Math.round(player!!.currentPosition / 1000.toDouble()).toInt()
|
||||
updateCurrentProgress(progress)
|
||||
binding.playerControlsWrapper.playerProgressbar.progress = progress
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateCurrentProgress(seconds: Int) {
|
||||
binding.playerControlsWrapper.playerProgressCurrent.text = seconds.getFormattedDuration()
|
||||
}
|
||||
|
||||
private fun resetProgress(title: String?, duration: Int?) {
|
||||
updateCurrentProgress(0)
|
||||
binding.playerControlsWrapper.playerProgressbar.progress = 0
|
||||
binding.playerControlsWrapper.playerProgressbar.max = duration ?: 0
|
||||
binding.playerControlsWrapper.playerTitle.text = title ?: ""
|
||||
binding.playerControlsWrapper.playerProgressMax.text = (duration ?: 0).getFormattedDuration()
|
||||
}
|
||||
|
||||
private fun togglePlayPause() {
|
||||
if (getIsPlaying()) {
|
||||
pausePlayback()
|
||||
} else {
|
||||
resumePlayback()
|
||||
}
|
||||
}
|
||||
|
||||
private fun pausePlayback() {
|
||||
player?.pause()
|
||||
binding.playerControlsWrapper.playPauseBtn.setImageDrawable(getToggleButtonIcon(false))
|
||||
progressTimer.cancel()
|
||||
}
|
||||
|
||||
private fun resumePlayback() {
|
||||
player?.start()
|
||||
binding.playerControlsWrapper.playPauseBtn.setImageDrawable(getToggleButtonIcon(true))
|
||||
setupProgressTimer()
|
||||
}
|
||||
|
||||
private fun getToggleButtonIcon(isPlaying: Boolean): Drawable {
|
||||
val drawable = if (isPlaying) com.simplemobiletools.commons.R.drawable.ic_pause_vector else com.simplemobiletools.commons.R.drawable.ic_play_vector
|
||||
return resources.getColoredDrawableWithColor(drawable, getProperPrimaryColor().getContrastColor())
|
||||
}
|
||||
|
||||
private fun skip(forward: Boolean) {
|
||||
// val curr = player?.currentPosition ?: return
|
||||
// var newProgress = if (forward) curr + FAST_FORWARD_SKIP_MS else curr - FAST_FORWARD_SKIP_MS
|
||||
// if (newProgress > player!!.duration) {
|
||||
// newProgress = player!!.duration
|
||||
// }
|
||||
//
|
||||
// player!!.seekTo(newProgress)
|
||||
// resumePlayback()
|
||||
}
|
||||
|
||||
private fun getIsPlaying() = player?.isPlaying == true
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package com.simplemobiletools.voicerecorder.adapters
|
||||
|
||||
import android.content.Intent
|
||||
import android.view.*
|
||||
import android.widget.PopupMenu
|
||||
import android.widget.TextView
|
||||
|
@ -11,6 +12,7 @@ import com.simplemobiletools.commons.helpers.isQPlus
|
|||
import com.simplemobiletools.commons.views.MyRecyclerView
|
||||
import com.simplemobiletools.voicerecorder.BuildConfig
|
||||
import com.simplemobiletools.voicerecorder.R
|
||||
import com.simplemobiletools.voicerecorder.activities.EditRecordingActivity
|
||||
import com.simplemobiletools.voicerecorder.activities.SimpleActivity
|
||||
import com.simplemobiletools.voicerecorder.databinding.ItemRecordingBinding
|
||||
import com.simplemobiletools.voicerecorder.dialogs.DeleteConfirmationDialog
|
||||
|
@ -281,6 +283,13 @@ class RecordingsAdapter(
|
|||
}
|
||||
}
|
||||
|
||||
R.id.cab_edit -> {
|
||||
Intent(activity, EditRecordingActivity::class.java).apply {
|
||||
putExtra(EditRecordingActivity.RECORDING_ID, recordingId)
|
||||
activity.startActivity(this)
|
||||
}
|
||||
}
|
||||
|
||||
R.id.cab_delete -> {
|
||||
executeItemMenuOperation(recordingId, removeAfterCallback = false) {
|
||||
askConfirmDelete()
|
||||
|
|
|
@ -84,23 +84,23 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
|
|||
}
|
||||
|
||||
private fun setupViews() {
|
||||
binding.playPauseBtn.setOnClickListener {
|
||||
if (playedRecordingIDs.empty() || binding.playerProgressbar.max == 0) {
|
||||
binding.nextBtn.callOnClick()
|
||||
binding.playerControlsWrapper.playPauseBtn.setOnClickListener {
|
||||
if (playedRecordingIDs.empty() || binding.playerControlsWrapper.playerProgressbar.max == 0) {
|
||||
binding.playerControlsWrapper.nextBtn.callOnClick()
|
||||
} else {
|
||||
togglePlayPause()
|
||||
}
|
||||
}
|
||||
|
||||
binding.playerProgressCurrent.setOnClickListener {
|
||||
binding.playerControlsWrapper.playerProgressCurrent.setOnClickListener {
|
||||
skip(false)
|
||||
}
|
||||
|
||||
binding.playerProgressMax.setOnClickListener {
|
||||
binding.playerControlsWrapper.playerProgressMax.setOnClickListener {
|
||||
skip(true)
|
||||
}
|
||||
|
||||
binding.previousBtn.setOnClickListener {
|
||||
binding.playerControlsWrapper.previousBtn.setOnClickListener {
|
||||
if (playedRecordingIDs.isEmpty()) {
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
@ -116,14 +116,14 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
|
|||
playRecording(prevRecording, true)
|
||||
}
|
||||
|
||||
binding.playerTitle.setOnLongClickListener {
|
||||
if (binding.playerTitle.value.isNotEmpty()) {
|
||||
context.copyToClipboard(binding.playerTitle.value)
|
||||
binding.playerControlsWrapper.playerTitle.setOnLongClickListener {
|
||||
if (binding.playerControlsWrapper.playerTitle.value.isNotEmpty()) {
|
||||
context.copyToClipboard(binding.playerControlsWrapper.playerTitle.value)
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
binding.nextBtn.setOnClickListener {
|
||||
binding.playerControlsWrapper.nextBtn.setOnClickListener {
|
||||
val adapter = getRecordingsAdapter()
|
||||
if (adapter == null || adapter.recordings.isEmpty()) {
|
||||
return@setOnClickListener
|
||||
|
@ -193,9 +193,9 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
|
|||
|
||||
setOnCompletionListener {
|
||||
progressTimer.cancel()
|
||||
binding.playerProgressbar.progress = binding.playerProgressbar.max
|
||||
binding.playerProgressCurrent.text = binding.playerProgressMax.text
|
||||
binding.playPauseBtn.setImageDrawable(getToggleButtonIcon(false))
|
||||
binding.playerControlsWrapper.playerProgressbar.progress = binding.playerControlsWrapper.playerProgressbar.max
|
||||
binding.playerControlsWrapper.playerProgressCurrent.text = binding.playerControlsWrapper.playerProgressMax.text
|
||||
binding.playerControlsWrapper.playPauseBtn.setImageDrawable(getToggleButtonIcon(false))
|
||||
}
|
||||
|
||||
setOnPreparedListener {
|
||||
|
@ -245,12 +245,12 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
|
|||
}
|
||||
}
|
||||
|
||||
binding.playPauseBtn.setImageDrawable(getToggleButtonIcon(playOnPreparation))
|
||||
binding.playerProgressbar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
||||
binding.playerControlsWrapper.playPauseBtn.setImageDrawable(getToggleButtonIcon(playOnPreparation))
|
||||
binding.playerControlsWrapper.playerProgressbar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
||||
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||
if (fromUser && !playedRecordingIDs.isEmpty()) {
|
||||
player?.seekTo(progress * 1000)
|
||||
binding.playerProgressCurrent.text = progress.getFormattedDuration()
|
||||
binding.playerControlsWrapper.playerProgressCurrent.text = progress.getFormattedDuration()
|
||||
resumePlayback()
|
||||
}
|
||||
}
|
||||
|
@ -273,22 +273,22 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
|
|||
if (player != null) {
|
||||
val progress = Math.round(player!!.currentPosition / 1000.toDouble()).toInt()
|
||||
updateCurrentProgress(progress)
|
||||
binding.playerProgressbar.progress = progress
|
||||
binding.playerControlsWrapper.playerProgressbar.progress = progress
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateCurrentProgress(seconds: Int) {
|
||||
binding.playerProgressCurrent.text = seconds.getFormattedDuration()
|
||||
binding.playerControlsWrapper.playerProgressCurrent.text = seconds.getFormattedDuration()
|
||||
}
|
||||
|
||||
private fun resetProgress(recording: Recording?) {
|
||||
updateCurrentProgress(0)
|
||||
binding.playerProgressbar.progress = 0
|
||||
binding.playerProgressbar.max = recording?.duration ?: 0
|
||||
binding.playerTitle.text = recording?.title ?: ""
|
||||
binding.playerProgressMax.text = (recording?.duration ?: 0).getFormattedDuration()
|
||||
binding.playerControlsWrapper.playerProgressbar.progress = 0
|
||||
binding.playerControlsWrapper.playerProgressbar.max = recording?.duration ?: 0
|
||||
binding.playerControlsWrapper.playerTitle.text = recording?.title ?: ""
|
||||
binding.playerControlsWrapper.playerProgressMax.text = (recording?.duration ?: 0).getFormattedDuration()
|
||||
}
|
||||
|
||||
fun onSearchTextChanged(text: String) {
|
||||
|
@ -307,13 +307,13 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
|
|||
|
||||
private fun pausePlayback() {
|
||||
player?.pause()
|
||||
binding.playPauseBtn.setImageDrawable(getToggleButtonIcon(false))
|
||||
binding.playerControlsWrapper.playPauseBtn.setImageDrawable(getToggleButtonIcon(false))
|
||||
progressTimer.cancel()
|
||||
}
|
||||
|
||||
private fun resumePlayback() {
|
||||
player?.start()
|
||||
binding.playPauseBtn.setImageDrawable(getToggleButtonIcon(true))
|
||||
binding.playerControlsWrapper.playPauseBtn.setImageDrawable(getToggleButtonIcon(true))
|
||||
setupProgressTimer()
|
||||
}
|
||||
|
||||
|
@ -352,12 +352,12 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
|
|||
context.updateTextColors(binding.playerHolder)
|
||||
|
||||
val textColor = context.getProperTextColor()
|
||||
arrayListOf(binding.previousBtn, binding.nextBtn).forEach {
|
||||
arrayListOf(binding.playerControlsWrapper.previousBtn, binding.playerControlsWrapper.nextBtn).forEach {
|
||||
it.applyColorFilter(textColor)
|
||||
}
|
||||
|
||||
binding.playPauseBtn.background.applyColorFilter(properPrimaryColor)
|
||||
binding.playPauseBtn.setImageDrawable(getToggleButtonIcon(false))
|
||||
binding.playerControlsWrapper.playPauseBtn.background.applyColorFilter(properPrimaryColor)
|
||||
binding.playerControlsWrapper.playPauseBtn.setImageDrawable(getToggleButtonIcon(false))
|
||||
}
|
||||
|
||||
fun finishActMode() = getRecordingsAdapter()?.finishActMode()
|
||||
|
|
|
@ -0,0 +1,188 @@
|
|||
package com.simplemobiletools.voicerecorder.views
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.util.AttributeSet
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import com.simplemobiletools.commons.extensions.adjustAlpha
|
||||
import com.simplemobiletools.commons.extensions.getProperPrimaryColor
|
||||
import com.simplemobiletools.commons.extensions.getProperTextColor
|
||||
import com.simplemobiletools.commons.helpers.LOWER_ALPHA
|
||||
import com.visualizer.amplitude.dp
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
class AudioEditorView @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||
) : View(context, attrs, defStyleAttr) {
|
||||
private val chunkPaint = Paint()
|
||||
private val highlightPaint = Paint()
|
||||
private val progressPaint = Paint()
|
||||
|
||||
private var chunks = ArrayList<Float>()
|
||||
private var topBottomPadding = 6.dp()
|
||||
|
||||
private var startX: Float = -1f
|
||||
private var endX: Float = -1f
|
||||
|
||||
private var currentProgress: Float = 0f
|
||||
|
||||
private enum class Dragging {
|
||||
START,
|
||||
END,
|
||||
NONE
|
||||
}
|
||||
|
||||
private var dragging = Dragging.NONE
|
||||
|
||||
var startPosition: Float = -1f
|
||||
var endPosition: Float = -1f
|
||||
|
||||
var editListener: (() -> Unit)? = null
|
||||
|
||||
var chunkColor = Color.RED
|
||||
set(value) {
|
||||
chunkPaint.color = value
|
||||
field = value
|
||||
}
|
||||
private var chunkWidth = 20.dp()
|
||||
set(value) {
|
||||
chunkPaint.strokeWidth = value
|
||||
field = value
|
||||
}
|
||||
private var chunkSpace = 1.dp()
|
||||
var chunkMinHeight = 3.dp() // recommended size > 10 dp
|
||||
var chunkRoundedCorners = false
|
||||
set(value) {
|
||||
if (value) {
|
||||
chunkPaint.strokeCap = Paint.Cap.ROUND
|
||||
} else {
|
||||
chunkPaint.strokeCap = Paint.Cap.BUTT
|
||||
}
|
||||
field = value
|
||||
}
|
||||
|
||||
init {
|
||||
chunkPaint.strokeWidth = chunkWidth
|
||||
chunkPaint.color = chunkColor
|
||||
chunkRoundedCorners = false
|
||||
highlightPaint.color = context.getProperPrimaryColor().adjustAlpha(LOWER_ALPHA)
|
||||
progressPaint.color = context.getProperTextColor()
|
||||
progressPaint.strokeWidth = 4.dp()
|
||||
}
|
||||
|
||||
fun recreate() {
|
||||
chunks.clear()
|
||||
invalidate()
|
||||
}
|
||||
|
||||
fun clearEditing() {
|
||||
startX = -1f
|
||||
endX = -1f
|
||||
startPosition = -1f
|
||||
endPosition = -1f
|
||||
editListener?.invoke()
|
||||
invalidate()
|
||||
}
|
||||
|
||||
fun putAmplitudes(amplitudes: List<Int>) {
|
||||
val maxAmp = amplitudes.max()
|
||||
chunkWidth = (1.0f / amplitudes.size) * (2.0f / 3)
|
||||
chunkSpace = chunkWidth / 2
|
||||
|
||||
chunks.addAll(amplitudes.map { it.toFloat() / maxAmp })
|
||||
|
||||
invalidate()
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
super.onDraw(canvas)
|
||||
if (chunkWidth < 1f) {
|
||||
chunkWidth *= width
|
||||
chunkSpace = chunkWidth / 2
|
||||
}
|
||||
val verticalCenter = height / 2
|
||||
var x = chunkSpace
|
||||
val maxHeight = height - (topBottomPadding * 2)
|
||||
val verticalDrawScale = maxHeight - chunkMinHeight
|
||||
if (verticalDrawScale == 0f) {
|
||||
return
|
||||
}
|
||||
|
||||
chunks.forEach {
|
||||
val chunkHeight = it * verticalDrawScale + chunkMinHeight
|
||||
val startY = verticalCenter - chunkHeight / 2
|
||||
val stopY = verticalCenter + chunkHeight / 2
|
||||
|
||||
canvas.drawLine(x, startY, x, stopY, chunkPaint)
|
||||
x += chunkWidth + chunkSpace
|
||||
}
|
||||
|
||||
if (startPosition >= 0f || startX >= 0f ) {
|
||||
val start: Float
|
||||
val end: Float
|
||||
if (startX >= 0f) {
|
||||
start = startX
|
||||
end = endX
|
||||
} else {
|
||||
start = width * startPosition
|
||||
end = width * endPosition
|
||||
}
|
||||
|
||||
canvas.drawRect(start, 0f, end, height.toFloat(), highlightPaint)
|
||||
}
|
||||
|
||||
canvas.drawLine(width * currentProgress, 0f, width * currentProgress, height.toFloat(), progressPaint)
|
||||
}
|
||||
|
||||
override fun onTouchEvent(event: MotionEvent?): Boolean {
|
||||
when (event?.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
if (abs(event.x - startPosition * width) < 50.0f) {
|
||||
startX = event.x
|
||||
endX = endPosition * width
|
||||
dragging = Dragging.START
|
||||
} else if (abs(event.x - endPosition * width) < 50.0f) {
|
||||
endX = event.x
|
||||
startX = startPosition * width
|
||||
dragging = Dragging.END
|
||||
} else {
|
||||
startX = event.x
|
||||
endX = event.x
|
||||
dragging = Dragging.END
|
||||
}
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
if (dragging == Dragging.START) {
|
||||
startX = event.x
|
||||
} else if (dragging == Dragging.END) {
|
||||
endX = event.x
|
||||
}
|
||||
}
|
||||
MotionEvent.ACTION_UP -> {
|
||||
if (dragging == Dragging.START) {
|
||||
startX = event.x
|
||||
} else if (dragging == Dragging.END) {
|
||||
endX = event.x
|
||||
}
|
||||
dragging = Dragging.NONE
|
||||
startPosition = min(startX, endX) / width
|
||||
endPosition = max(startX, endX) / width
|
||||
startX = -1f
|
||||
endX = -1f
|
||||
}
|
||||
}
|
||||
invalidate()
|
||||
editListener?.invoke()
|
||||
return true
|
||||
}
|
||||
|
||||
fun updateProgress(progress: Float) {
|
||||
currentProgress = progress
|
||||
invalidate()
|
||||
}
|
||||
}
|
5
app/src/main/res/drawable/ic_cut_vector.xml
Normal file
5
app/src/main/res/drawable/ic_cut_vector.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M9.64,7.64c0.23,-0.5 0.36,-1.05 0.36,-1.64 0,-2.21 -1.79,-4 -4,-4S2,3.79 2,6s1.79,4 4,4c0.59,0 1.14,-0.13 1.64,-0.36L10,12l-2.36,2.36C7.14,14.13 6.59,14 6,14c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4c0,-0.59 -0.13,-1.14 -0.36,-1.64L12,14l7,7h3v-1L9.64,7.64zM6,8c-1.1,0 -2,-0.89 -2,-2s0.9,-2 2,-2 2,0.89 2,2 -0.9,2 -2,2zM6,20c-1.1,0 -2,-0.89 -2,-2s0.9,-2 2,-2 2,0.89 2,2 -0.9,2 -2,2zM12,12.5c-0.28,0 -0.5,-0.22 -0.5,-0.5s0.22,-0.5 0.5,-0.5 0.5,0.22 0.5,0.5 -0.22,0.5 -0.5,0.5zM19,3l-6,6 2,2 7,-7L22,3z"/>
|
||||
</vector>
|
42
app/src/main/res/layout/activity_edit_recording.xml
Normal file
42
app/src/main/res/layout/activity_edit_recording.xml
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/main_coordinator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/settings_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@color/color_primary"
|
||||
app:title="@string/settings"
|
||||
app:titleTextAppearance="@style/AppTheme.ActionBar.TitleTextStyle" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/content_wrapper"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="?attr/actionBarSize">
|
||||
|
||||
<com.simplemobiletools.voicerecorder.views.AudioEditorView
|
||||
android:id="@+id/recording_visualizer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@+id/player_controls_wrapper"
|
||||
android:layout_marginTop="?attr/actionBarSize"
|
||||
android:layout_marginStart="@dimen/big_margin"
|
||||
android:layout_marginEnd="@dimen/big_margin" />
|
||||
|
||||
<include
|
||||
android:id="@+id/player_controls_wrapper"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/recording_visualizer"
|
||||
layout="@layout/layout_player_controls" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -1,7 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.simplemobiletools.voicerecorder.fragments.PlayerFragment 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:id="@+id/player_holder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
@ -37,120 +36,7 @@
|
|||
|
||||
</com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller>
|
||||
|
||||
<RelativeLayout
|
||||
<include
|
||||
android:id="@+id/player_controls_wrapper"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:ignore="HardcodedText">
|
||||
|
||||
<View
|
||||
android:id="@+id/player_divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1px"
|
||||
android:background="@color/divider_grey"
|
||||
android:importantForAccessibility="no" />
|
||||
|
||||
<com.simplemobiletools.commons.views.MyTextView
|
||||
android:id="@+id/player_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/medium_margin"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_horizontal"
|
||||
android:maxLines="1"
|
||||
android:padding="@dimen/activity_margin"
|
||||
android:textSize="@dimen/big_text_size"
|
||||
tools:text="Recording title" />
|
||||
|
||||
<com.simplemobiletools.commons.views.MyTextView
|
||||
android:id="@+id/player_progress_current"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/player_controls"
|
||||
android:layout_alignTop="@+id/player_progressbar"
|
||||
android:layout_alignBottom="@+id/player_progressbar"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:gravity="center_vertical"
|
||||
android:maxLines="1"
|
||||
android:paddingStart="@dimen/normal_margin"
|
||||
android:paddingEnd="@dimen/medium_margin"
|
||||
android:text="00:00" />
|
||||
|
||||
<com.simplemobiletools.commons.views.MySeekBar
|
||||
android:id="@+id/player_progressbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/player_title"
|
||||
android:layout_toStartOf="@+id/player_progress_max"
|
||||
android:layout_toEndOf="@+id/player_progress_current"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:max="0"
|
||||
android:paddingTop="@dimen/normal_margin"
|
||||
android:paddingBottom="@dimen/normal_margin" />
|
||||
|
||||
<com.simplemobiletools.commons.views.MyTextView
|
||||
android:id="@+id/player_progress_max"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignTop="@+id/player_progressbar"
|
||||
android:layout_alignBottom="@+id/player_progressbar"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:gravity="center_vertical"
|
||||
android:maxLines="1"
|
||||
android:paddingStart="@dimen/medium_margin"
|
||||
android:paddingEnd="@dimen/normal_margin"
|
||||
android:text="00:00" />
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/player_controls"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/player_progressbar"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/previous_btn"
|
||||
android:layout_width="@dimen/normal_icon_size"
|
||||
android:layout_height="@dimen/normal_icon_size"
|
||||
android:layout_alignTop="@+id/play_pause_btn"
|
||||
android:layout_alignBottom="@+id/play_pause_btn"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toStartOf="@+id/play_pause_btn"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/previous"
|
||||
android:padding="@dimen/medium_margin"
|
||||
android:src="@drawable/ic_previous_vector" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/play_pause_btn"
|
||||
android:layout_width="@dimen/toggle_recording_button_size"
|
||||
android:layout_height="@dimen/toggle_recording_button_size"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginStart="@dimen/player_button_margin"
|
||||
android:layout_marginEnd="@dimen/player_button_margin"
|
||||
android:layout_marginBottom="@dimen/big_margin"
|
||||
android:background="@drawable/circle_background"
|
||||
android:contentDescription="@string/playpause"
|
||||
android:padding="@dimen/activity_margin"
|
||||
android:src="@drawable/ic_play_vector" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/next_btn"
|
||||
android:layout_width="@dimen/normal_icon_size"
|
||||
android:layout_height="@dimen/normal_icon_size"
|
||||
android:layout_alignTop="@+id/play_pause_btn"
|
||||
android:layout_alignBottom="@+id/play_pause_btn"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toEndOf="@+id/play_pause_btn"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/next"
|
||||
android:padding="@dimen/medium_margin"
|
||||
android:src="@drawable/ic_next_vector" />
|
||||
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>
|
||||
layout="@layout/layout_player_controls" />
|
||||
</com.simplemobiletools.voicerecorder.fragments.PlayerFragment>
|
||||
|
|
120
app/src/main/res/layout/layout_player_controls.xml
Normal file
120
app/src/main/res/layout/layout_player_controls.xml
Normal file
|
@ -0,0 +1,120 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/player_controls_wrapper"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:ignore="HardcodedText"
|
||||
tools:showIn="@layout/fragment_player">
|
||||
|
||||
<View
|
||||
android:id="@+id/player_divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1px"
|
||||
android:background="@color/divider_grey"
|
||||
android:importantForAccessibility="no" />
|
||||
|
||||
<com.simplemobiletools.commons.views.MyTextView
|
||||
android:id="@+id/player_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/medium_margin"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_horizontal"
|
||||
android:maxLines="1"
|
||||
android:padding="@dimen/activity_margin"
|
||||
android:textSize="@dimen/big_text_size"
|
||||
tools:text="Recording title" />
|
||||
|
||||
<com.simplemobiletools.commons.views.MyTextView
|
||||
android:id="@+id/player_progress_current"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/player_controls"
|
||||
android:layout_alignTop="@+id/player_progressbar"
|
||||
android:layout_alignBottom="@+id/player_progressbar"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:gravity="center_vertical"
|
||||
android:maxLines="1"
|
||||
android:paddingStart="@dimen/normal_margin"
|
||||
android:paddingEnd="@dimen/medium_margin"
|
||||
android:text="00:00" />
|
||||
|
||||
<com.simplemobiletools.commons.views.MySeekBar
|
||||
android:id="@+id/player_progressbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/player_title"
|
||||
android:layout_toStartOf="@+id/player_progress_max"
|
||||
android:layout_toEndOf="@+id/player_progress_current"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:max="0"
|
||||
android:paddingTop="@dimen/normal_margin"
|
||||
android:paddingBottom="@dimen/normal_margin" />
|
||||
|
||||
<com.simplemobiletools.commons.views.MyTextView
|
||||
android:id="@+id/player_progress_max"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignTop="@+id/player_progressbar"
|
||||
android:layout_alignBottom="@+id/player_progressbar"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:gravity="center_vertical"
|
||||
android:maxLines="1"
|
||||
android:paddingStart="@dimen/medium_margin"
|
||||
android:paddingEnd="@dimen/normal_margin"
|
||||
android:text="00:00" />
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/player_controls"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/player_progressbar"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/previous_btn"
|
||||
android:layout_width="@dimen/normal_icon_size"
|
||||
android:layout_height="@dimen/normal_icon_size"
|
||||
android:layout_alignTop="@+id/play_pause_btn"
|
||||
android:layout_alignBottom="@+id/play_pause_btn"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toStartOf="@+id/play_pause_btn"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/previous"
|
||||
android:padding="@dimen/medium_margin"
|
||||
android:src="@drawable/ic_previous_vector" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/play_pause_btn"
|
||||
android:layout_width="@dimen/toggle_recording_button_size"
|
||||
android:layout_height="@dimen/toggle_recording_button_size"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginStart="@dimen/player_button_margin"
|
||||
android:layout_marginEnd="@dimen/player_button_margin"
|
||||
android:layout_marginBottom="@dimen/big_margin"
|
||||
android:background="@drawable/circle_background"
|
||||
android:contentDescription="@string/playpause"
|
||||
android:padding="@dimen/activity_margin"
|
||||
android:src="@drawable/ic_play_vector" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/next_btn"
|
||||
android:layout_width="@dimen/normal_icon_size"
|
||||
android:layout_height="@dimen/normal_icon_size"
|
||||
android:layout_alignTop="@+id/play_pause_btn"
|
||||
android:layout_alignBottom="@+id/play_pause_btn"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toEndOf="@+id/play_pause_btn"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/next"
|
||||
android:padding="@dimen/medium_margin"
|
||||
android:src="@drawable/ic_next_vector" />
|
||||
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>
|
|
@ -18,6 +18,12 @@
|
|||
android:icon="@drawable/ic_delete_vector"
|
||||
android:showAsAction="always"
|
||||
android:title="@string/delete" />
|
||||
<item
|
||||
android:id="@+id/cab_edit"
|
||||
android:icon="@drawable/ic_edit_vector"
|
||||
android:showAsAction="ifRoom"
|
||||
android:title="@string/edit"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/cab_open_with"
|
||||
android:showAsAction="never"
|
||||
|
|
31
app/src/main/res/menu/menu_edit.xml
Normal file
31
app/src/main/res/menu/menu_edit.xml
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu 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"
|
||||
tools:ignore="AlwaysShowAction">
|
||||
<item
|
||||
android:id="@+id/play"
|
||||
android:icon="@drawable/ic_play_vector"
|
||||
android:title="Play"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/cut"
|
||||
android:icon="@drawable/ic_cut_vector"
|
||||
android:title="Cut"
|
||||
app:showAsAction="ifRoom" />
|
||||
<!-- <item-->
|
||||
<!-- android:id="@+id/save"-->
|
||||
<!-- android:icon="@drawable/ic_save_vector"-->
|
||||
<!-- android:title="Save"-->
|
||||
<!-- app:showAsAction="ifRoom" />-->
|
||||
<item
|
||||
android:id="@+id/reset"
|
||||
android:icon="@drawable/ic_reset_vector"
|
||||
android:title="Reset"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/clear"
|
||||
android:icon="@drawable/ic_cross_vector"
|
||||
android:title="Clear"
|
||||
app:showAsAction="ifRoom" />
|
||||
</menu>
|
|
@ -15,6 +15,11 @@ room = "2.6.0-alpha02"
|
|||
simple-commons = "257a2ab069"
|
||||
#AudioRecordView
|
||||
audiorecordview = "1.0.4"
|
||||
#AudioTool
|
||||
audiotool = "1.2.1"
|
||||
amplituda = "2.2.2"
|
||||
waveformseekbar = "5.0.1"
|
||||
mobileffmpeg = "4.4"
|
||||
#TAndroidLame
|
||||
tandroidlame = "1.1"
|
||||
#AutofitTextView
|
||||
|
@ -24,7 +29,7 @@ gradlePlugins-agp = "8.1.1"
|
|||
#build
|
||||
app-build-compileSDKVersion = "34"
|
||||
app-build-targetSDK = "34"
|
||||
app-build-minimumSDK = "23"
|
||||
app-build-minimumSDK = "24"
|
||||
app-build-javaVersion = "VERSION_17"
|
||||
app-build-kotlinJVMTarget = "17"
|
||||
#versioning
|
||||
|
@ -46,6 +51,11 @@ simple-tools-commons = { module = "com.github.SimpleMobileTools:Simple-Commons",
|
|||
eventbus = { module = "org.greenrobot:eventbus", version.ref = "eventbus" }
|
||||
#AudioRecordView
|
||||
audiorecordview = { module = "com.github.Armen101:AudioRecordView", version.ref = "audiorecordview" }
|
||||
#AudioTool
|
||||
audiotool = { module = "com.github.lincollincol:AudioTool", version.ref = "audiotool" }
|
||||
amplituda = { module = "com.github.lincollincol:amplituda", version.ref = "amplituda" }
|
||||
mobileffmpeg = { module = "com.arthenica:mobile-ffmpeg-full", version.ref = "mobileffmpeg" }
|
||||
waveformseekbar = { module = "com.github.massoudss:waveformSeekBar", version.ref = "waveformseekbar" }
|
||||
#TAndroidLame
|
||||
tandroidlame = { module = "com.github.naman14:TAndroidLame", version.ref = "tandroidlame" }
|
||||
#AutofitTextView
|
||||
|
@ -55,6 +65,14 @@ room = [
|
|||
"androidx-room-ktx",
|
||||
"androidx-room-runtime",
|
||||
]
|
||||
audiotool = [
|
||||
"audiotool",
|
||||
"mobileffmpeg",
|
||||
]
|
||||
amplituda = [
|
||||
"amplituda",
|
||||
"waveformseekbar",
|
||||
]
|
||||
[plugins]
|
||||
android = { id = "com.android.application", version.ref = "gradlePlugins-agp" }
|
||||
kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
|
|
Loading…
Reference in a new issue