diff --git a/CHANGELOG.md b/CHANGELOG.md
index bb68270d..d8fe0db8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
+- Option to customize recording filename pattern with datetime placeholders ([#15])
- Added support for custom fonts
### Changed
@@ -132,6 +133,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Initial release
+[#15]: https://github.com/FossifyOrg/Voice-Recorder/issues/15
[#16]: https://github.com/FossifyOrg/Voice-Recorder/issues/16
[#22]: https://github.com/FossifyOrg/Voice-Recorder/issues/22
[#33]: https://github.com/FossifyOrg/Voice-Recorder/issues/33
diff --git a/app/src/main/kotlin/org/fossify/voicerecorder/activities/SettingsActivity.kt b/app/src/main/kotlin/org/fossify/voicerecorder/activities/SettingsActivity.kt
index 6d0bc5ae..b4ebb40a 100644
--- a/app/src/main/kotlin/org/fossify/voicerecorder/activities/SettingsActivity.kt
+++ b/app/src/main/kotlin/org/fossify/voicerecorder/activities/SettingsActivity.kt
@@ -24,6 +24,7 @@ import org.fossify.commons.helpers.sumByInt
import org.fossify.commons.models.RadioItem
import org.fossify.voicerecorder.R
import org.fossify.voicerecorder.databinding.ActivitySettingsBinding
+import org.fossify.voicerecorder.dialogs.FilenamePatternDialog
import org.fossify.voicerecorder.dialogs.MoveRecordingsDialog
import org.fossify.voicerecorder.extensions.config
import org.fossify.voicerecorder.extensions.deleteTrashedRecordings
@@ -67,6 +68,7 @@ class SettingsActivity : SimpleActivity() {
setupLanguage()
setupChangeDateTimeFormat()
setupSaveRecordingsFolder()
+ setupFilenamePattern()
setupExtension()
setupBitrate()
setupSamplingRate()
@@ -81,6 +83,7 @@ class SettingsActivity : SimpleActivity() {
binding.settingsColorCustomizationSectionLabel,
binding.settingsGeneralSettingsLabel,
binding.settingsRecordingSectionLabel,
+ binding.settingsAudioSectionLabel,
binding.settingsRecycleBinLabel
).forEach {
it.setTextColor(getProperPrimaryColor())
@@ -166,6 +169,15 @@ class SettingsActivity : SimpleActivity() {
}
}
+ private fun setupFilenamePattern() {
+ binding.settingsFilenamePattern.text = config.filenamePattern
+ binding.settingsFilenamePatternHolder.setOnClickListener {
+ FilenamePatternDialog(this) { newPattern ->
+ binding.settingsFilenamePattern.text = newPattern
+ }
+ }
+ }
+
private fun setupExtension() {
binding.settingsExtension.text = config.getExtensionText()
binding.settingsExtensionHolder.setOnClickListener {
@@ -211,7 +223,7 @@ class SettingsActivity : SimpleActivity() {
val currentBitrate = config.bitrate
val closestBitrate = availableBitrates.minByOrNull { abs(it - currentBitrate) }
?: DEFAULT_BITRATE
-
+
config.bitrate = closestBitrate
binding.settingsBitrate.text = getBitrateText(config.bitrate)
}
diff --git a/app/src/main/kotlin/org/fossify/voicerecorder/dialogs/DateTimePatternInfoDialog.kt b/app/src/main/kotlin/org/fossify/voicerecorder/dialogs/DateTimePatternInfoDialog.kt
new file mode 100644
index 00000000..8af09f93
--- /dev/null
+++ b/app/src/main/kotlin/org/fossify/voicerecorder/dialogs/DateTimePatternInfoDialog.kt
@@ -0,0 +1,19 @@
+package org.fossify.voicerecorder.dialogs
+
+import org.fossify.commons.activities.BaseSimpleActivity
+import org.fossify.commons.extensions.getAlertDialogBuilder
+import org.fossify.commons.extensions.setupDialogStuff
+import org.fossify.commons.extensions.viewBinding
+import org.fossify.voicerecorder.databinding.DatetimePatternInfoLayoutBinding
+
+class DateTimePatternInfoDialog(activity: BaseSimpleActivity) {
+ val binding by activity.viewBinding(DatetimePatternInfoLayoutBinding::inflate)
+
+ init {
+ activity.getAlertDialogBuilder()
+ .setPositiveButton(org.fossify.commons.R.string.ok) { _, _ -> { } }
+ .apply {
+ activity.setupDialogStuff(binding.root, this)
+ }
+ }
+}
diff --git a/app/src/main/kotlin/org/fossify/voicerecorder/dialogs/FilenamePatternDialog.kt b/app/src/main/kotlin/org/fossify/voicerecorder/dialogs/FilenamePatternDialog.kt
new file mode 100644
index 00000000..84af7a90
--- /dev/null
+++ b/app/src/main/kotlin/org/fossify/voicerecorder/dialogs/FilenamePatternDialog.kt
@@ -0,0 +1,62 @@
+package org.fossify.voicerecorder.dialogs
+
+import androidx.appcompat.app.AlertDialog
+import androidx.core.widget.doAfterTextChanged
+import org.fossify.commons.extensions.getAlertDialogBuilder
+import org.fossify.commons.extensions.setupDialogStuff
+import org.fossify.commons.extensions.viewBinding
+import org.fossify.voicerecorder.R
+import org.fossify.voicerecorder.activities.SimpleActivity
+import org.fossify.voicerecorder.databinding.DialogFilenamePatternBinding
+import org.fossify.voicerecorder.extensions.config
+
+class FilenamePatternDialog(private val activity: SimpleActivity, private val callback: (String) -> Unit) {
+ private val binding by activity.viewBinding(DialogFilenamePatternBinding::inflate)
+ private val config = activity.config
+ private var dialog: AlertDialog? = null
+
+ init {
+ binding.apply {
+ filenamePatternValue.setText(config.filenamePattern)
+ filenamePatternHint.setEndIconOnClickListener {
+ DateTimePatternInfoDialog(activity)
+ }
+ filenamePatternHint.setEndIconOnLongClickListener {
+ DateTimePatternInfoDialog(activity)
+ true
+ }
+
+ filenamePatternValue.doAfterTextChanged { validatePattern(it?.toString().orEmpty()) }
+ }
+
+ activity.getAlertDialogBuilder()
+ .setPositiveButton(org.fossify.commons.R.string.ok, null)
+ .setNegativeButton(org.fossify.commons.R.string.cancel, null)
+ .apply {
+ activity.setupDialogStuff(binding.root, this, R.string.filename_pattern) { alertDialog ->
+ dialog = alertDialog
+ alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
+ val newPattern = binding.filenamePatternValue.text.toString()
+ config.filenamePattern = newPattern
+ callback(newPattern)
+ alertDialog.dismiss()
+ }
+ }
+ }
+ }
+
+ private fun validatePattern(pattern: String) {
+ binding.filenamePatternHint.error = when {
+ pattern.isEmpty() -> activity.getString(org.fossify.commons.R.string.filename_cannot_be_empty)
+ !containsAllDateTimePlaceholders(pattern) -> activity.getString(R.string.filename_pattern_must_contain_all)
+ else -> null
+ }
+ val isValid = binding.filenamePatternHint.error.isNullOrEmpty()
+ dialog?.getButton(AlertDialog.BUTTON_POSITIVE)?.isEnabled = isValid
+ }
+
+ private fun containsAllDateTimePlaceholders(pattern: String): Boolean {
+ return pattern.contains("%Y") && pattern.contains("%M") && pattern.contains("%D") &&
+ pattern.contains("%h") && pattern.contains("%m") && pattern.contains("%s")
+ }
+}
diff --git a/app/src/main/kotlin/org/fossify/voicerecorder/extensions/Context.kt b/app/src/main/kotlin/org/fossify/voicerecorder/extensions/Context.kt
index 55e09589..a16f9c7a 100644
--- a/app/src/main/kotlin/org/fossify/voicerecorder/extensions/Context.kt
+++ b/app/src/main/kotlin/org/fossify/voicerecorder/extensions/Context.kt
@@ -11,6 +11,7 @@ import android.media.MediaMetadataRetriever
import android.net.Uri
import android.os.Environment
import android.provider.DocumentsContract
+import androidx.core.graphics.createBitmap
import androidx.documentfile.provider.DocumentFile
import org.fossify.commons.extensions.createFirstParentTreeUri
import org.fossify.commons.extensions.createSAFDirectorySdk30
@@ -33,6 +34,8 @@ import org.fossify.voicerecorder.helpers.MyWidgetRecordDisplayProvider
import org.fossify.voicerecorder.helpers.TOGGLE_WIDGET_UI
import org.fossify.voicerecorder.models.Recording
import java.io.File
+import java.util.Calendar
+import java.util.Locale
import kotlin.math.roundToLong
val Context.config: Config get() = Config.newInstance(applicationContext)
@@ -42,7 +45,7 @@ val Context.trashFolder
fun Context.drawableToBitmap(drawable: Drawable): Bitmap {
val size = (60 * resources.displayMetrics.density).toInt()
- val mutableBitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
+ val mutableBitmap = createBitmap(size, size)
val canvas = Canvas(mutableBitmap)
drawable.setBounds(0, 0, size, size)
drawable.draw(canvas)
@@ -235,3 +238,24 @@ fun Context.createDocumentFile(path: String): Uri? {
null
}
}
+
+// move to commons in the future
+fun Context.getFormattedFilename(): String {
+ val pattern = config.filenamePattern
+ val calendar = Calendar.getInstance()
+
+ val year = calendar.get(Calendar.YEAR).toString()
+ val month = String.format(Locale.ROOT, "%02d", calendar.get(Calendar.MONTH) + 1)
+ val day = String.format(Locale.ROOT, "%02d", calendar.get(Calendar.DAY_OF_MONTH))
+ val hour = String.format(Locale.ROOT, "%02d", calendar.get(Calendar.HOUR_OF_DAY))
+ val minute = String.format(Locale.ROOT, "%02d", calendar.get(Calendar.MINUTE))
+ val second = String.format(Locale.ROOT, "%02d", calendar.get(Calendar.SECOND))
+
+ return pattern
+ .replace("%Y", year, false)
+ .replace("%M", month, false)
+ .replace("%D", day, false)
+ .replace("%h", hour, false)
+ .replace("%m", minute, false)
+ .replace("%s", second, false)
+}
diff --git a/app/src/main/kotlin/org/fossify/voicerecorder/helpers/Config.kt b/app/src/main/kotlin/org/fossify/voicerecorder/helpers/Config.kt
index 8d079aea..2ab91c17 100644
--- a/app/src/main/kotlin/org/fossify/voicerecorder/helpers/Config.kt
+++ b/app/src/main/kotlin/org/fossify/voicerecorder/helpers/Config.kt
@@ -3,10 +3,10 @@ package org.fossify.voicerecorder.helpers
import android.annotation.SuppressLint
import android.content.Context
import android.media.MediaRecorder
+import androidx.core.content.edit
import org.fossify.commons.helpers.BaseConfig
import org.fossify.voicerecorder.R
import org.fossify.voicerecorder.extensions.getDefaultRecordingsFolder
-import androidx.core.content.edit
class Config(context: Context) : BaseConfig(context) {
companion object {
@@ -96,4 +96,8 @@ class Config(context: Context) : BaseConfig(context) {
set(wasMicModeWarningShown) = prefs.edit {
putBoolean(WAS_MIC_MODE_WARNING_SHOWN, wasMicModeWarningShown)
}
+
+ var filenamePattern: String
+ get() = prefs.getString(FILENAME_PATTERN, DEFAULT_FILENAME_PATTERN)!!
+ set(filenamePattern) = prefs.edit { putString(FILENAME_PATTERN, filenamePattern) }
}
diff --git a/app/src/main/kotlin/org/fossify/voicerecorder/helpers/Constants.kt b/app/src/main/kotlin/org/fossify/voicerecorder/helpers/Constants.kt
index b68056a4..847b696b 100644
--- a/app/src/main/kotlin/org/fossify/voicerecorder/helpers/Constants.kt
+++ b/app/src/main/kotlin/org/fossify/voicerecorder/helpers/Constants.kt
@@ -102,5 +102,7 @@ const val USE_RECYCLE_BIN = "use_recycle_bin"
const val LAST_RECYCLE_BIN_CHECK = "last_recycle_bin_check"
const val KEEP_SCREEN_ON = "keep_screen_on"
const val WAS_MIC_MODE_WARNING_SHOWN = "was_mic_mode_warning_shown"
+const val FILENAME_PATTERN = "filename_pattern"
const val DEFAULT_RECORDINGS_FOLDER = "Recordings"
+const val DEFAULT_FILENAME_PATTERN = "%Y%M%D_%h%m%s"
diff --git a/app/src/main/kotlin/org/fossify/voicerecorder/services/RecorderService.kt b/app/src/main/kotlin/org/fossify/voicerecorder/services/RecorderService.kt
index 53c8ea27..755c6efd 100644
--- a/app/src/main/kotlin/org/fossify/voicerecorder/services/RecorderService.kt
+++ b/app/src/main/kotlin/org/fossify/voicerecorder/services/RecorderService.kt
@@ -6,7 +6,6 @@ import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
-import android.content.Context
import android.content.Intent
import android.media.MediaScannerConnection
import android.net.Uri
@@ -16,7 +15,6 @@ import androidx.core.app.NotificationCompat
import androidx.core.content.FileProvider
import org.fossify.commons.extensions.createDocumentUriUsingFirstParentTreeUri
import org.fossify.commons.extensions.createSAFFileSdk30
-import org.fossify.commons.extensions.getCurrentFormattedDateTime
import org.fossify.commons.extensions.getDocumentFile
import org.fossify.commons.extensions.getFilenameFromPath
import org.fossify.commons.extensions.getLaunchIntent
@@ -31,6 +29,7 @@ import org.fossify.voicerecorder.BuildConfig
import org.fossify.voicerecorder.R
import org.fossify.voicerecorder.activities.SplashActivity
import org.fossify.voicerecorder.extensions.config
+import org.fossify.voicerecorder.extensions.getFormattedFilename
import org.fossify.voicerecorder.extensions.updateWidgets
import org.fossify.voicerecorder.helpers.CANCEL_RECORDING
import org.fossify.voicerecorder.helpers.EXTENSION_MP3
@@ -105,7 +104,7 @@ class RecorderService : Service() {
}
val recordingFolder = defaultFolder.absolutePath
- recordingPath = "$recordingFolder/${getCurrentFormattedDateTime()}.${config.getExtension()}"
+ recordingPath = "$recordingFolder/${getFormattedFilename()}.${config.getExtension()}"
resultUri = null
try {
@@ -280,8 +279,7 @@ class RecorderService : Service() {
private fun showNotification(): Notification {
val channelId = "simple_recorder"
val label = getString(R.string.app_name)
- val notificationManager =
- getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+ val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
NotificationChannel(channelId, label, NotificationManager.IMPORTANCE_DEFAULT).apply {
setSound(null, null)
diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml
index a7724392..3006feb9 100644
--- a/app/src/main/res/layout/activity_settings.xml
+++ b/app/src/main/res/layout/activity_settings.xml
@@ -90,7 +90,7 @@
+ android:layout_height="wrap_content" />
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/dialog_filename_pattern.xml b/app/src/main/res/layout/dialog_filename_pattern.xml
new file mode 100644
index 00000000..96abfdb1
--- /dev/null
+++ b/app/src/main/res/layout/dialog_filename_pattern.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml
new file mode 100644
index 00000000..90b7dfc2
--- /dev/null
+++ b/app/src/main/res/values-night/colors.xml
@@ -0,0 +1,4 @@
+
+
+ @color/md_red_400
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 9f8b27da..2e439b96 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -1,4 +1,5 @@
#FFF57C00
+ @color/md_red_500_dark
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d9cfcb02..a7f2cd3d 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -24,6 +24,8 @@
Save recordings in
+ Filename pattern
+ Must include all placeholders for unique filenames (%Y, %M, %D, %h, %m, %s)
Audio source
Microphone mode
Bitrate