Merge pull request #107 from KryptKode/feat/customise_recordings_path
allow users to customise recording folder
This commit is contained in:
commit
2073dbb9cc
|
@ -62,7 +62,7 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.github.SimpleMobileTools:Simple-Commons:e3376e4f56'
|
||||
implementation 'com.github.SimpleMobileTools:Simple-Commons:202656a071'
|
||||
implementation 'org.greenrobot:eventbus:3.2.0'
|
||||
implementation 'com.github.Armen101:AudioRecordView:1.0.4'
|
||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="28" />
|
||||
android:maxSdkVersion="29" />
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.faketouch"
|
||||
|
|
|
@ -82,7 +82,7 @@ class MainActivity : SimpleActivity() {
|
|||
}
|
||||
|
||||
private fun tryInitVoiceRecorder() {
|
||||
if (isQPlus()) {
|
||||
if (isRPlus()) {
|
||||
setupViewPager()
|
||||
} else {
|
||||
handlePermission(PERMISSION_WRITE_STORAGE) {
|
||||
|
|
|
@ -99,13 +99,20 @@ class SettingsActivity : SimpleActivity() {
|
|||
}
|
||||
|
||||
private fun setupSaveRecordingsFolder() {
|
||||
settings_save_recordings_holder.beGoneIf(isQPlus())
|
||||
settings_save_recordings.text = humanizePath(config.saveRecordingsFolder)
|
||||
settings_save_recordings_holder.setOnClickListener {
|
||||
FilePickerDialog(this, config.saveRecordingsFolder, false, showFAB = true) {
|
||||
val path = it
|
||||
handleSAFDialog(it) {
|
||||
if (it) {
|
||||
handleSAFDialog(path) { grantedSAF ->
|
||||
if (!grantedSAF) {
|
||||
return@handleSAFDialog
|
||||
}
|
||||
|
||||
handleSAFDialogSdk30(path) { grantedSAF30 ->
|
||||
if (!grantedSAF30) {
|
||||
return@handleSAFDialogSdk30
|
||||
}
|
||||
|
||||
config.saveRecordingsFolder = path
|
||||
settings_save_recordings.text = humanizePath(config.saveRecordingsFolder)
|
||||
}
|
||||
|
|
|
@ -116,10 +116,10 @@ class RecordingsAdapter(
|
|||
|
||||
private fun shareRecordings() {
|
||||
val selectedItems = getSelectedItems()
|
||||
val paths = if (isQPlus()) {
|
||||
selectedItems.map { getAudioFileContentUri(it.id.toLong()).toString() }
|
||||
} else {
|
||||
selectedItems.map { it.path }
|
||||
val paths = selectedItems.map {
|
||||
it.path.ifEmpty {
|
||||
getAudioFileContentUri(it.id.toLong()).toString()
|
||||
}
|
||||
}
|
||||
|
||||
activity.sharePathsIntent(paths, BuildConfig.APPLICATION_ID)
|
||||
|
|
|
@ -7,6 +7,10 @@ import android.content.Intent
|
|||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Environment
|
||||
import com.simplemobiletools.commons.extensions.internalStoragePath
|
||||
import com.simplemobiletools.commons.helpers.isQPlus
|
||||
import com.simplemobiletools.voicerecorder.R
|
||||
import com.simplemobiletools.voicerecorder.helpers.Config
|
||||
import com.simplemobiletools.voicerecorder.helpers.IS_RECORDING
|
||||
import com.simplemobiletools.voicerecorder.helpers.MyWidgetRecordDisplayProvider
|
||||
|
@ -34,3 +38,16 @@ fun Context.updateWidgets(isRecording: Boolean) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.getDefaultRecordingsFolder(): String {
|
||||
val defaultPath = getDefaultRecordingsRelativePath()
|
||||
return "$internalStoragePath/$defaultPath"
|
||||
}
|
||||
|
||||
fun Context.getDefaultRecordingsRelativePath(): String {
|
||||
return if (isQPlus()) {
|
||||
"${Environment.DIRECTORY_MUSIC}/Recordings"
|
||||
} else {
|
||||
getString(R.string.app_name)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,15 +6,18 @@ import android.graphics.drawable.Drawable
|
|||
import android.media.AudioManager
|
||||
import android.media.MediaMetadataRetriever
|
||||
import android.media.MediaPlayer
|
||||
import android.net.Uri
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.PowerManager
|
||||
import android.provider.DocumentsContract
|
||||
import android.provider.MediaStore
|
||||
import android.provider.MediaStore.Audio.Media
|
||||
import android.util.AttributeSet
|
||||
import android.widget.SeekBar
|
||||
import com.simplemobiletools.commons.extensions.*
|
||||
import com.simplemobiletools.commons.helpers.isQPlus
|
||||
import com.simplemobiletools.commons.helpers.isRPlus
|
||||
import com.simplemobiletools.voicerecorder.R
|
||||
import com.simplemobiletools.voicerecorder.activities.SimpleActivity
|
||||
import com.simplemobiletools.voicerecorder.adapters.RecordingsAdapter
|
||||
|
@ -29,6 +32,8 @@ import org.greenrobot.eventbus.Subscribe
|
|||
import org.greenrobot.eventbus.ThreadMode
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.math.roundToLong
|
||||
|
||||
class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPagerFragment(context, attributeSet), RefreshRecordingsListener {
|
||||
private val FAST_FORWARD_SKIP_MS = 10000
|
||||
|
@ -162,10 +167,24 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
|
|||
}
|
||||
|
||||
private fun getRecordings(): ArrayList<Recording> {
|
||||
return if (isQPlus()) {
|
||||
getMediaStoreRecordings()
|
||||
} else {
|
||||
getLegacyRecordings()
|
||||
val recordings = ArrayList<Recording>()
|
||||
return when {
|
||||
isRPlus() -> {
|
||||
recordings.addAll(getMediaStoreRecordings())
|
||||
recordings.addAll(getSAFRecordings())
|
||||
recordings
|
||||
}
|
||||
isQPlus() -> {
|
||||
recordings.addAll(getMediaStoreRecordings())
|
||||
recordings.addAll(getLegacyRecordings())
|
||||
recordings
|
||||
}
|
||||
else -> {
|
||||
recordings.addAll(getLegacyRecordings())
|
||||
recordings
|
||||
}
|
||||
}.apply {
|
||||
sortByDescending { it.timestamp }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -194,7 +213,7 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
|
|||
var size = cursor.getIntValue(Media.SIZE)
|
||||
|
||||
if (duration == 0L) {
|
||||
duration = getDurationFromUri(id.toLong())
|
||||
duration = getDurationFromUri(getAudioFileContentUri(id.toLong()))
|
||||
}
|
||||
|
||||
if (size == 0) {
|
||||
|
@ -222,17 +241,34 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
|
|||
val recording = Recording(id, title, path, timestamp, duration, size)
|
||||
recordings.add(recording)
|
||||
}
|
||||
return recordings
|
||||
}
|
||||
|
||||
private fun getSAFRecordings(): ArrayList<Recording> {
|
||||
val recordings = ArrayList<Recording>()
|
||||
val files = context.getDocumentSdk30(context.config.saveRecordingsFolder)?.listFiles() ?: return recordings
|
||||
|
||||
files.filter { it.type?.startsWith("audio") == true && !it.name.isNullOrEmpty() }.forEach {
|
||||
val id = it.hashCode()
|
||||
val title = it.name!!
|
||||
val path = it.uri.toString()
|
||||
val timestamp = (it.lastModified() / 1000).toInt()
|
||||
val duration = getDurationFromUri(it.uri)
|
||||
val size = it.length().toInt()
|
||||
val recording = Recording(id, title, path, timestamp, duration.toInt(), size)
|
||||
recordings.add(recording)
|
||||
}
|
||||
|
||||
recordings.sortByDescending { it.timestamp }
|
||||
return recordings
|
||||
}
|
||||
|
||||
private fun getDurationFromUri(id: Long): Long {
|
||||
private fun getDurationFromUri(uri: Uri): Long {
|
||||
return try {
|
||||
val retriever = MediaMetadataRetriever()
|
||||
retriever.setDataSource(context, getAudioFileContentUri(id))
|
||||
retriever.setDataSource(context, uri)
|
||||
val time = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)!!
|
||||
Math.round(time.toLong() / 1000.toDouble())
|
||||
(time.toLong() / 1000.toDouble()).roundToLong()
|
||||
} catch (e: Exception) {
|
||||
0L
|
||||
}
|
||||
|
@ -279,10 +315,17 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
|
|||
reset()
|
||||
|
||||
try {
|
||||
if (isQPlus()) {
|
||||
setDataSource(context, getAudioFileContentUri(recording.id.toLong()))
|
||||
} else {
|
||||
setDataSource(recording.path)
|
||||
val uri = Uri.parse(recording.path)
|
||||
when {
|
||||
DocumentsContract.isDocumentUri(context, uri) -> {
|
||||
setDataSource(context, uri)
|
||||
}
|
||||
recording.path.isEmpty() -> {
|
||||
setDataSource(context, getAudioFileContentUri(recording.id.toLong()))
|
||||
}
|
||||
else -> {
|
||||
setDataSource(recording.path)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
context?.showErrorToast(e)
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.content.Context
|
|||
import android.media.MediaRecorder
|
||||
import com.simplemobiletools.commons.helpers.BaseConfig
|
||||
import com.simplemobiletools.voicerecorder.R
|
||||
import com.simplemobiletools.voicerecorder.extensions.getDefaultRecordingsFolder
|
||||
|
||||
class Config(context: Context) : BaseConfig(context) {
|
||||
companion object {
|
||||
|
@ -15,7 +16,7 @@ class Config(context: Context) : BaseConfig(context) {
|
|||
set(hideNotification) = prefs.edit().putBoolean(HIDE_NOTIFICATION, hideNotification).apply()
|
||||
|
||||
var saveRecordingsFolder: String
|
||||
get() = prefs.getString(SAVE_RECORDINGS, "$internalStoragePath/${context.getString(R.string.app_name)}")!!
|
||||
get() = prefs.getString(SAVE_RECORDINGS, context.getDefaultRecordingsFolder())!!
|
||||
set(saveRecordingsFolder) = prefs.edit().putString(SAVE_RECORDINGS, saveRecordingsFolder).apply()
|
||||
|
||||
var extension: Int
|
||||
|
|
|
@ -9,7 +9,6 @@ import android.content.Intent
|
|||
import android.media.MediaRecorder
|
||||
import android.media.MediaScannerConnection
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.os.IBinder
|
||||
import android.provider.MediaStore
|
||||
import android.provider.MediaStore.Audio.Media
|
||||
|
@ -17,10 +16,11 @@ import androidx.core.app.NotificationCompat
|
|||
import com.simplemobiletools.commons.extensions.*
|
||||
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
|
||||
import com.simplemobiletools.commons.helpers.isOreoPlus
|
||||
import com.simplemobiletools.commons.helpers.isQPlus
|
||||
import com.simplemobiletools.commons.helpers.isRPlus
|
||||
import com.simplemobiletools.voicerecorder.R
|
||||
import com.simplemobiletools.voicerecorder.activities.SplashActivity
|
||||
import com.simplemobiletools.voicerecorder.extensions.config
|
||||
import com.simplemobiletools.voicerecorder.extensions.getDefaultRecordingsRelativePath
|
||||
import com.simplemobiletools.voicerecorder.extensions.updateWidgets
|
||||
import com.simplemobiletools.voicerecorder.helpers.*
|
||||
import com.simplemobiletools.voicerecorder.models.Events
|
||||
|
@ -72,14 +72,14 @@ class RecorderService : Service() {
|
|||
return
|
||||
}
|
||||
|
||||
val baseFolder = if (isQPlus()) {
|
||||
val defaultFolder = File(config.saveRecordingsFolder)
|
||||
if (!defaultFolder.exists()) {
|
||||
defaultFolder.mkdir()
|
||||
}
|
||||
|
||||
val baseFolder = if (isRPlus() && !hasProperStoredFirstParentUri(defaultFolder.absolutePath)) {
|
||||
cacheDir
|
||||
} else {
|
||||
val defaultFolder = File(config.saveRecordingsFolder)
|
||||
if (!defaultFolder.exists()) {
|
||||
defaultFolder.mkdir()
|
||||
}
|
||||
|
||||
defaultFolder.absolutePath
|
||||
}
|
||||
|
||||
|
@ -93,7 +93,12 @@ class RecorderService : Service() {
|
|||
setAudioEncodingBitRate(config.bitrate)
|
||||
setAudioSamplingRate(44100)
|
||||
|
||||
if (!isQPlus() && isPathOnSD(currFilePath)) {
|
||||
if (isRPlus() && hasProperStoredFirstParentUri(currFilePath)) {
|
||||
val fileUri = createDocumentUriUsingFirstParentTreeUri(currFilePath)
|
||||
createSAFFileSdk30(currFilePath)
|
||||
val outputFileDescriptor = contentResolver.openFileDescriptor(fileUri, "w")!!.fileDescriptor
|
||||
setOutputFile(outputFileDescriptor)
|
||||
} else if (!isRPlus() && isPathOnSD(currFilePath)) {
|
||||
var document = getDocumentFile(currFilePath.getParentPath())
|
||||
document = document?.createFile("", currFilePath.getFilenameFromPath())
|
||||
|
||||
|
@ -132,7 +137,7 @@ class RecorderService : Service() {
|
|||
release()
|
||||
|
||||
ensureBackgroundThread {
|
||||
if (isQPlus()) {
|
||||
if (isRPlus() && !hasProperStoredFirstParentUri(currFilePath) ) {
|
||||
addFileInNewMediaStore()
|
||||
} else {
|
||||
addFileInLegacyMediaStore()
|
||||
|
@ -184,7 +189,7 @@ class RecorderService : Service() {
|
|||
put(Media.DISPLAY_NAME, storeFilename)
|
||||
put(Media.TITLE, storeFilename)
|
||||
put(Media.MIME_TYPE, storeFilename.getMimeType())
|
||||
put(Media.RELATIVE_PATH, "${Environment.DIRECTORY_MUSIC}/Recordings")
|
||||
put(Media.RELATIVE_PATH, getDefaultRecordingsRelativePath())
|
||||
}
|
||||
|
||||
val newUri = contentResolver.insert(audioCollection, newSongDetails)
|
||||
|
|
Loading…
Reference in New Issue