Compare commits
80 Commits
Author | SHA1 | Date |
---|---|---|
Tibor Kaputa | db0d3e30b7 | |
Tibor Kaputa | 6dc37ab1fd | |
Tibor Kaputa | c2e3910cf4 | |
Tibor Kaputa | c143fbff1e | |
Guillaume | 09e4195cd9 | |
Lionel HANNEQUIN | 6e1c8cfbd3 | |
Puppelimies | b8ea987465 | |
Tibor Kaputa | e7931bff94 | |
Ensar Sarajčić | 60eda663a3 | |
tibbi | 4b1c045676 | |
tibbi | 9a7a12755a | |
tibbi | 077373e6cf | |
tibbi | 8e418971a8 | |
tibbi | 387b9e6b00 | |
tibbi | 6ec445ee64 | |
tibbi | 65f22bae88 | |
Tibor Kaputa | 8499afe01b | |
tibbi | 6c9221f238 | |
Ensar Sarajčić | 9a8c2f9210 | |
Tibor Kaputa | ffe5e8a72a | |
Lionel HANNEQUIN | 00d5ab7be8 | |
Ensar Sarajčić | cd62cabb52 | |
gallegonovato | 9b83060801 | |
Puppelimies | 6135b529a1 | |
Tibor Kaputa | 15d23dbca1 | |
gallegonovato | 6f874233ce | |
gallegonovato | dfd847461f | |
tibbi | 2311c17a03 | |
tibbi | ebe55079fb | |
tibbi | 430ddd5994 | |
Tibor Kaputa | a2461d0e89 | |
Ensar Sarajčić | 5942d1f6ce | |
Tibor Kaputa | 12d1bb3c3f | |
Ensar Sarajčić | 57ee97e060 | |
tibbi | 2cea945f43 | |
tibbi | 600630e193 | |
Tibor Kaputa | 673110e489 | |
Ensar Sarajčić | fca700375a | |
Tibor Kaputa | 152ef60d24 | |
tibbi | 616138df8c | |
Usland | 16ff51f00a | |
tibbi | c96a4e1c0d | |
Tibor Kaputa | a53d6985d7 | |
Tibor Kaputa | bb3824bef4 | |
Ensar Sarajčić | f0737fa83c | |
Ensar Sarajčić | 109f8aafb7 | |
Ensar Sarajčić | 89e76cc293 | |
tibbi | a212b51e64 | |
tibbi | eeb957c243 | |
tibbi | 7c161e13d4 | |
tibbi | 7eeaa14fbb | |
Tibor Kaputa | 904c40d27d | |
Ensar Sarajčić | eec47921e0 | |
tibbi | 8e9b73a96e | |
Tibor Kaputa | 272f5a8b21 | |
tibbi | fe2c7dfc44 | |
Ensar Sarajčić | 3c3be72c90 | |
tibbi | ff65d8949c | |
Ensar Sarajčić | dda0a9aa87 | |
Ensar Sarajčić | f5079dd8bf | |
Ensar Sarajčić | e9c35fae0f | |
Ensar Sarajčić | f0db5d72aa | |
Ensar Sarajčić | a0278914ee | |
Ensar Sarajčić | 68166c318c | |
tibbi | fee947feac | |
Tibor Kaputa | fa4d2904cf | |
Ensar Sarajčić | ce855965b9 | |
Ensar Sarajčić | f511da5180 | |
Ensar Sarajčić | 950f5a0207 | |
Ensar Sarajčić | b9035f2612 | |
Ensar Sarajčić | 7aba993726 | |
Ensar Sarajčić | 5883e91bc4 | |
Priit Jõerüüt | 31e194202b | |
Priit Jõerüüt | 2d6e7dc29e | |
abc0922001 | 58f74c23ce | |
abc0922001 | 423f07fae5 | |
elgratea | 439b944a1e | |
tibbi | bc07f3c9fe | |
tibbi | 4d01b6d4a0 | |
tibbi | 37c3c3c863 |
|
@ -1,3 +1,3 @@
|
|||
github: [tibbi]
|
||||
patreon: tiborkaputa
|
||||
custom: ["https://www.paypal.com/paypalme/simplemobiletools", "https://www.simplemobiletools.com/donate"]
|
||||
custom: ["https://www.paypal.me/simplemobiletools"]
|
||||
|
|
22
CHANGELOG.md
22
CHANGELOG.md
|
@ -1,6 +1,28 @@
|
|||
Changelog
|
||||
==========
|
||||
|
||||
Version 5.12.3 *(2023-10-09)*
|
||||
----------------------------
|
||||
|
||||
* Fixed a glitch with inability to change save folder
|
||||
* Added some stability, UI and translation improvements
|
||||
|
||||
Version 5.12.2 *(2023-09-18)*
|
||||
----------------------------
|
||||
|
||||
* Added some stability, UI and translation improvements
|
||||
|
||||
Version 5.12.1 *(2023-09-13)*
|
||||
----------------------------
|
||||
|
||||
* Added some stability, UI and translation improvements
|
||||
|
||||
Version 5.12.0 *(2023-07-31)*
|
||||
----------------------------
|
||||
|
||||
* Added a Recycle bin
|
||||
* Added some stability, UI and translation improvements
|
||||
|
||||
Version 5.11.4 *(2023-06-20)*
|
||||
----------------------------
|
||||
|
||||
|
|
23
README.md
23
README.md
|
@ -15,20 +15,17 @@ It comes with material design and dark theme by default, provides great user exp
|
|||
|
||||
Contains no ads or unnecessary permissions. It is fully opensource, provides customizable colors.
|
||||
|
||||
Check out the full suite of Simple Tools here:
|
||||
https://www.simplemobiletools.com
|
||||
<a href="https://f-droid.org/packages/com.simplemobiletools.voicerecorder">Get it on F-Droid</a>
|
||||
|
||||
Facebook:
|
||||
https://www.facebook.com/simplemobiletools
|
||||
|
||||
Reddit:
|
||||
https://www.reddit.com/r/SimpleMobileTools
|
||||
|
||||
Telegram:
|
||||
https://t.me/SimpleMobileTools
|
||||
|
||||
<a href='https://play.google.com/store/apps/details?id=com.simplemobiletools.voicerecorder'><img src='https://simplemobiletools.com/images/button-google-play.svg' alt='Get it on Google Play' height=45/></a>
|
||||
<a href='https://f-droid.org/packages/com.simplemobiletools.voicerecorder/'><img src='https://simplemobiletools.com/images/button-f-droid.png' alt='Get it on F-Droid' height=45 ></a>
|
||||
Support us:
|
||||
IBAN: SK4083300000002000965231
|
||||
Bitcoin: 19Hc8A7sWGud8sP19VXDC5a5j28UyJfpyJ
|
||||
Ethereum: 0xB7a2DD6f2408Bce77334655CF5E7639aE31feb30
|
||||
Litecoin: LYACbHTKaM9ZubKQGxJ4NRyVy1gHUuztRP
|
||||
Bitcoin Cash: qz6dvmhq5vzkcsypxpp2mnur30muxdah4gvulx3y85
|
||||
Tether: 0x250f9cC32863E59b87037a14955Ed64F879653F0
|
||||
<a href="https://paypal.me/SimpleMobileTools?country.x=SK&locale.x=en_US">PayPal</a>
|
||||
<a href="https://www.patreon.com/tiborkaputa">Patreon</a>
|
||||
<div style="display:flex;">
|
||||
<img alt="App image" src="fastlane/metadata/android/en-US/images/phoneScreenshots/1_en-US.jpeg" width="30%">
|
||||
<img alt="App image" src="fastlane/metadata/android/en-US/images/phoneScreenshots/2_en-US.jpeg" width="30%">
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
|
||||
def keystorePropertiesFile = rootProject.file("keystore.properties")
|
||||
def keystoreProperties = new Properties()
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.simplemobiletools.voicerecorder"
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 33
|
||||
versionCode 37
|
||||
versionName "5.11.4"
|
||||
setProperty("archivesBaseName", "voice-recorder")
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
release {
|
||||
keyAlias keystoreProperties['keyAlias']
|
||||
keyPassword keystoreProperties['keyPassword']
|
||||
storeFile file(keystoreProperties['storeFile'])
|
||||
storePassword keystoreProperties['storePassword']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
applicationIdSuffix ".debug"
|
||||
}
|
||||
release {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flavorDimensions "variants"
|
||||
productFlavors {
|
||||
core {}
|
||||
fdroid {}
|
||||
prepaid {}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
checkReleaseBuilds false
|
||||
abortOnError false
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.github.SimpleMobileTools:Simple-Commons:b9309035a9'
|
||||
implementation 'org.greenrobot:eventbus:3.3.1'
|
||||
implementation 'com.github.Armen101:AudioRecordView:1.0.4'
|
||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'com.github.naman14:TAndroidLame:1.1'
|
||||
implementation 'me.grantland:autofittextview:0.2.1'
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
import java.io.FileInputStream
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
import org.jetbrains.kotlin.konan.properties.Properties
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android)
|
||||
alias(libs.plugins.kotlinAndroid)
|
||||
alias(libs.plugins.ksp)
|
||||
base
|
||||
}
|
||||
|
||||
base {
|
||||
archivesName.set("voice-recorder")
|
||||
}
|
||||
|
||||
val keystorePropertiesFile: File = rootProject.file("keystore.properties")
|
||||
val keystoreProperties = Properties()
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = project.libs.versions.app.build.compileSDKVersion.get().toInt()
|
||||
|
||||
defaultConfig {
|
||||
applicationId = libs.versions.app.version.appId.get()
|
||||
minSdk = project.libs.versions.app.build.minimumSDK.get().toInt()
|
||||
targetSdk = project.libs.versions.app.build.targetSDK.get().toInt()
|
||||
versionName = project.libs.versions.app.version.versionName.get()
|
||||
versionCode = project.libs.versions.app.version.versionCode.get().toInt()
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
register("release") {
|
||||
keyAlias = keystoreProperties.getProperty("keyAlias")
|
||||
keyPassword = keystoreProperties.getProperty("keyPassword")
|
||||
storeFile = file(keystoreProperties.getProperty("storeFile"))
|
||||
storePassword = keystoreProperties.getProperty("storePassword")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
applicationIdSuffix = ".debug"
|
||||
}
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
signingConfig = signingConfigs.getByName("release")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flavorDimensions.add("variants")
|
||||
productFlavors {
|
||||
register("core")
|
||||
register("fdroid")
|
||||
register("prepaid")
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
getByName("main").java.srcDirs("src/main/kotlin")
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
val currentJavaVersionFromLibs = JavaVersion.valueOf(libs.versions.app.build.javaVersion.get().toString())
|
||||
sourceCompatibility = currentJavaVersionFromLibs
|
||||
targetCompatibility = currentJavaVersionFromLibs
|
||||
}
|
||||
|
||||
tasks.withType<KotlinCompile> {
|
||||
kotlinOptions.jvmTarget = project.libs.versions.app.build.kotlinJVMTarget.get()
|
||||
}
|
||||
|
||||
namespace = libs.versions.app.version.appId.get()
|
||||
|
||||
lint {
|
||||
checkReleaseBuilds = false
|
||||
abortOnError = false
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.simple.tools.commons)
|
||||
implementation(libs.eventbus)
|
||||
implementation(libs.audiorecordview)
|
||||
implementation(libs.androidx.documentfile)
|
||||
implementation(libs.androidx.swiperefreshlayout)
|
||||
implementation(libs.androidx.constraintlayout)
|
||||
implementation(libs.tandroidlame)
|
||||
implementation(libs.autofittextview)
|
||||
}
|
|
@ -4,3 +4,5 @@
|
|||
@org.greenrobot.eventbus.Subscribe <methods>;
|
||||
}
|
||||
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
|
||||
|
||||
-keep class androidx.compose.runtime.** { *; }
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.simplemobiletools.voicerecorder"
|
||||
android:installLocation="auto">
|
||||
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
|
@ -106,6 +106,7 @@
|
|||
|
||||
<service
|
||||
android:name=".services.RecorderService"
|
||||
android:foregroundServiceType="microphone"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.simplemobiletools.voicerecorder.action.GET_RECORDER_INFO" />
|
||||
|
|
|
@ -2,7 +2,7 @@ package com.simplemobiletools.voicerecorder.activities
|
|||
|
||||
import android.content.Intent
|
||||
import com.simplemobiletools.commons.dialogs.PermissionRequiredDialog
|
||||
import com.simplemobiletools.voicerecorder.R
|
||||
import com.simplemobiletools.commons.extensions.openNotificationSettings
|
||||
import com.simplemobiletools.voicerecorder.services.RecorderService
|
||||
|
||||
class BackgroundRecordActivity : SimpleActivity() {
|
||||
|
@ -26,7 +26,7 @@ class BackgroundRecordActivity : SimpleActivity() {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
PermissionRequiredDialog(this, R.string.allow_notifications_voice_recorder)
|
||||
PermissionRequiredDialog(this, com.simplemobiletools.commons.R.string.allow_notifications_voice_recorder, { openNotificationSettings() })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,11 +12,12 @@ import com.simplemobiletools.commons.models.FAQItem
|
|||
import com.simplemobiletools.voicerecorder.BuildConfig
|
||||
import com.simplemobiletools.voicerecorder.R
|
||||
import com.simplemobiletools.voicerecorder.adapters.ViewPagerAdapter
|
||||
import com.simplemobiletools.voicerecorder.databinding.ActivityMainBinding
|
||||
import com.simplemobiletools.voicerecorder.extensions.checkRecycleBinItems
|
||||
import com.simplemobiletools.voicerecorder.extensions.config
|
||||
import com.simplemobiletools.voicerecorder.helpers.STOP_AMPLITUDE_UPDATE
|
||||
import com.simplemobiletools.voicerecorder.models.Events
|
||||
import com.simplemobiletools.voicerecorder.services.RecorderService
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import me.grantland.widget.AutofitHelper
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
|
@ -25,26 +26,32 @@ import org.greenrobot.eventbus.ThreadMode
|
|||
class MainActivity : SimpleActivity() {
|
||||
|
||||
private var bus: EventBus? = null
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
isMaterialActivity = true
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
appLaunched(BuildConfig.APPLICATION_ID)
|
||||
setupOptionsMenu()
|
||||
refreshMenuItems()
|
||||
|
||||
updateMaterialActivityViews(main_coordinator, main_holder, useTransparentNavigation = false, useTopSearchMenu = true)
|
||||
updateMaterialActivityViews(binding.mainCoordinator, binding.mainHolder, useTransparentNavigation = false, useTopSearchMenu = true)
|
||||
|
||||
if (checkAppSideloading()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
checkRecycleBinItems()
|
||||
}
|
||||
|
||||
handlePermission(PERMISSION_RECORD_AUDIO) {
|
||||
if (it) {
|
||||
tryInitVoiceRecorder()
|
||||
} else {
|
||||
toast(R.string.no_audio_permissions)
|
||||
toast(com.simplemobiletools.commons.R.string.no_audio_permissions)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
@ -63,14 +70,17 @@ class MainActivity : SimpleActivity() {
|
|||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
setupTabColors()
|
||||
updateMenuColors()
|
||||
if (getPagerAdapter()?.showRecycleBin != config.useRecycleBin) {
|
||||
setupViewPager()
|
||||
}
|
||||
setupTabColors()
|
||||
getPagerAdapter()?.onResume()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
config.lastUsedViewPagerPage = view_pager.currentItem
|
||||
config.lastUsedViewPagerPage = binding.viewPager.currentItem
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
@ -88,8 +98,8 @@ class MainActivity : SimpleActivity() {
|
|||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (main_menu.isSearchOpen) {
|
||||
main_menu.closeSearch()
|
||||
if (binding.mainMenu.isSearchOpen) {
|
||||
binding.mainMenu.closeSearch()
|
||||
} else if (isThirdPartyIntent()) {
|
||||
setResult(Activity.RESULT_CANCELED, null)
|
||||
super.onBackPressed()
|
||||
|
@ -99,25 +109,27 @@ class MainActivity : SimpleActivity() {
|
|||
}
|
||||
|
||||
private fun refreshMenuItems() {
|
||||
main_menu.getToolbar().menu.apply {
|
||||
findItem(R.id.more_apps_from_us).isVisible = !resources.getBoolean(R.bool.hide_google_relations)
|
||||
binding.mainMenu.getToolbar().menu.apply {
|
||||
findItem(R.id.more_apps_from_us).isVisible = !resources.getBoolean(com.simplemobiletools.commons.R.bool.hide_google_relations)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupOptionsMenu() {
|
||||
main_menu.getToolbar().inflateMenu(R.menu.menu)
|
||||
main_menu.toggleHideOnScroll(false)
|
||||
main_menu.setupMenu()
|
||||
binding.mainMenu.getToolbar().inflateMenu(R.menu.menu)
|
||||
binding.mainMenu.toggleHideOnScroll(false)
|
||||
binding.mainMenu.setupMenu()
|
||||
|
||||
main_menu.onSearchOpenListener = {
|
||||
view_pager.currentItem = 1
|
||||
binding.mainMenu.onSearchOpenListener = {
|
||||
if (binding.viewPager.currentItem == 0) {
|
||||
binding.viewPager.currentItem = 1
|
||||
}
|
||||
}
|
||||
|
||||
main_menu.onSearchTextChangedListener = { text ->
|
||||
binding.mainMenu.onSearchTextChangedListener = { text ->
|
||||
getPagerAdapter()?.searchTextChanged(text)
|
||||
}
|
||||
|
||||
main_menu.getToolbar().setOnMenuItemClickListener { menuItem ->
|
||||
binding.mainMenu.getToolbar().setOnMenuItemClickListener { menuItem ->
|
||||
when (menuItem.itemId) {
|
||||
R.id.more_apps_from_us -> launchMoreAppsFromUsIntent()
|
||||
R.id.settings -> launchSettings()
|
||||
|
@ -130,7 +142,7 @@ class MainActivity : SimpleActivity() {
|
|||
|
||||
private fun updateMenuColors() {
|
||||
updateStatusbarColor(getProperBackgroundColor())
|
||||
main_menu.updateColors()
|
||||
binding.mainMenu.updateColors()
|
||||
}
|
||||
|
||||
private fun tryInitVoiceRecorder() {
|
||||
|
@ -148,61 +160,68 @@ class MainActivity : SimpleActivity() {
|
|||
}
|
||||
|
||||
private fun setupViewPager() {
|
||||
main_tabs_holder.removeAllTabs()
|
||||
val tabDrawables = arrayOf(R.drawable.ic_microphone_vector, R.drawable.ic_headset_vector)
|
||||
val tabLabels = arrayOf(R.string.recorder, R.string.player)
|
||||
binding.mainTabsHolder.removeAllTabs()
|
||||
var tabDrawables = arrayOf(com.simplemobiletools.commons.R.drawable.ic_microphone_vector, R.drawable.ic_headset_vector)
|
||||
var tabLabels = arrayOf(R.string.recorder, R.string.player)
|
||||
if (config.useRecycleBin) {
|
||||
tabDrawables += com.simplemobiletools.commons.R.drawable.ic_delete_vector
|
||||
tabLabels += com.simplemobiletools.commons.R.string.recycle_bin
|
||||
}
|
||||
|
||||
tabDrawables.forEachIndexed { i, drawableId ->
|
||||
main_tabs_holder.newTab().setCustomView(R.layout.bottom_tablayout_item).apply {
|
||||
customView?.findViewById<ImageView>(R.id.tab_item_icon)?.setImageDrawable(getDrawable(drawableId))
|
||||
customView?.findViewById<TextView>(R.id.tab_item_label)?.setText(tabLabels[i])
|
||||
AutofitHelper.create(customView?.findViewById(R.id.tab_item_label))
|
||||
main_tabs_holder.addTab(this)
|
||||
binding.mainTabsHolder.newTab().setCustomView(com.simplemobiletools.commons.R.layout.bottom_tablayout_item).apply {
|
||||
customView?.findViewById<ImageView>(com.simplemobiletools.commons.R.id.tab_item_icon)?.setImageDrawable(getDrawable(drawableId))
|
||||
customView?.findViewById<TextView>(com.simplemobiletools.commons.R.id.tab_item_label)?.setText(tabLabels[i])
|
||||
AutofitHelper.create(customView?.findViewById(com.simplemobiletools.commons.R.id.tab_item_label))
|
||||
binding.mainTabsHolder.addTab(this)
|
||||
}
|
||||
}
|
||||
|
||||
main_tabs_holder.onTabSelectionChanged(
|
||||
binding.mainTabsHolder.onTabSelectionChanged(
|
||||
tabUnselectedAction = {
|
||||
updateBottomTabItemColors(it.customView, false)
|
||||
if (it.position == 1) {
|
||||
main_menu.closeSearch()
|
||||
if (it.position == 1 || it.position == 2) {
|
||||
binding.mainMenu.closeSearch()
|
||||
}
|
||||
},
|
||||
tabSelectedAction = {
|
||||
view_pager.currentItem = it.position
|
||||
binding.viewPager.currentItem = it.position
|
||||
updateBottomTabItemColors(it.customView, true)
|
||||
}
|
||||
)
|
||||
|
||||
view_pager.adapter = ViewPagerAdapter(this)
|
||||
view_pager.onPageChangeListener {
|
||||
main_tabs_holder.getTabAt(it)?.select()
|
||||
(view_pager.adapter as ViewPagerAdapter).finishActMode()
|
||||
binding.viewPager.adapter = ViewPagerAdapter(this, config.useRecycleBin)
|
||||
binding.viewPager.offscreenPageLimit = 2
|
||||
binding.viewPager.onPageChangeListener {
|
||||
binding.mainTabsHolder.getTabAt(it)?.select()
|
||||
(binding.viewPager.adapter as ViewPagerAdapter).finishActMode()
|
||||
}
|
||||
|
||||
if (isThirdPartyIntent()) {
|
||||
view_pager.currentItem = 0
|
||||
binding.viewPager.currentItem = 0
|
||||
} else {
|
||||
view_pager.currentItem = config.lastUsedViewPagerPage
|
||||
main_tabs_holder.getTabAt(config.lastUsedViewPagerPage)?.select()
|
||||
binding.viewPager.currentItem = config.lastUsedViewPagerPage
|
||||
binding.mainTabsHolder.getTabAt(config.lastUsedViewPagerPage)?.select()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupTabColors() {
|
||||
val activeView = main_tabs_holder.getTabAt(view_pager.currentItem)?.customView
|
||||
val inactiveView = main_tabs_holder.getTabAt(getInactiveTabIndex())?.customView
|
||||
val activeView = binding.mainTabsHolder.getTabAt(binding.viewPager.currentItem)?.customView
|
||||
updateBottomTabItemColors(activeView, true)
|
||||
updateBottomTabItemColors(inactiveView, false)
|
||||
for (i in 0 until binding.mainTabsHolder.tabCount) {
|
||||
if (i != binding.viewPager.currentItem) {
|
||||
val inactiveView = binding.mainTabsHolder.getTabAt(i)?.customView
|
||||
updateBottomTabItemColors(inactiveView, false)
|
||||
}
|
||||
}
|
||||
|
||||
main_tabs_holder.getTabAt(view_pager.currentItem)?.select()
|
||||
binding.mainTabsHolder.getTabAt(binding.viewPager.currentItem)?.select()
|
||||
val bottomBarColor = getBottomNavigationBackgroundColor()
|
||||
main_tabs_holder.setBackgroundColor(bottomBarColor)
|
||||
binding.mainTabsHolder.setBackgroundColor(bottomBarColor)
|
||||
updateNavigationBarColor(bottomBarColor)
|
||||
}
|
||||
|
||||
private fun getInactiveTabIndex() = if (view_pager.currentItem == 0) 1 else 0
|
||||
|
||||
private fun getPagerAdapter() = (view_pager.adapter as? ViewPagerAdapter)
|
||||
private fun getPagerAdapter() = (binding.viewPager.adapter as? ViewPagerAdapter)
|
||||
|
||||
private fun launchSettings() {
|
||||
hideKeyboard()
|
||||
|
@ -214,12 +233,12 @@ class MainActivity : SimpleActivity() {
|
|||
|
||||
val faqItems = arrayListOf(
|
||||
FAQItem(R.string.faq_1_title, R.string.faq_1_text),
|
||||
FAQItem(R.string.faq_9_title_commons, R.string.faq_9_text_commons)
|
||||
FAQItem(com.simplemobiletools.commons.R.string.faq_9_title_commons, com.simplemobiletools.commons.R.string.faq_9_text_commons)
|
||||
)
|
||||
|
||||
if (!resources.getBoolean(R.bool.hide_google_relations)) {
|
||||
faqItems.add(FAQItem(R.string.faq_2_title_commons, R.string.faq_2_text_commons))
|
||||
faqItems.add(FAQItem(R.string.faq_6_title_commons, R.string.faq_6_text_commons))
|
||||
if (!resources.getBoolean(com.simplemobiletools.commons.R.bool.hide_google_relations)) {
|
||||
faqItems.add(FAQItem(com.simplemobiletools.commons.R.string.faq_2_title_commons, com.simplemobiletools.commons.R.string.faq_2_text_commons))
|
||||
faqItems.add(FAQItem(com.simplemobiletools.commons.R.string.faq_6_title_commons, com.simplemobiletools.commons.R.string.faq_6_text_commons))
|
||||
}
|
||||
|
||||
startAboutActivity(R.string.app_name, licenses, BuildConfig.VERSION_NAME, faqItems, true)
|
||||
|
|
|
@ -3,37 +3,41 @@ package com.simplemobiletools.voicerecorder.activities
|
|||
import android.content.Intent
|
||||
import android.media.MediaRecorder
|
||||
import android.os.Bundle
|
||||
import com.simplemobiletools.commons.dialogs.ChangeDateTimeFormatDialog
|
||||
import com.simplemobiletools.commons.dialogs.FeatureLockedDialog
|
||||
import com.simplemobiletools.commons.dialogs.FilePickerDialog
|
||||
import com.simplemobiletools.commons.dialogs.RadioGroupDialog
|
||||
import com.simplemobiletools.commons.dialogs.*
|
||||
import com.simplemobiletools.commons.extensions.*
|
||||
import com.simplemobiletools.commons.helpers.*
|
||||
import com.simplemobiletools.commons.models.RadioItem
|
||||
import com.simplemobiletools.voicerecorder.R
|
||||
import com.simplemobiletools.voicerecorder.databinding.ActivitySettingsBinding
|
||||
import com.simplemobiletools.voicerecorder.extensions.config
|
||||
import com.simplemobiletools.voicerecorder.extensions.emptyTheRecycleBin
|
||||
import com.simplemobiletools.voicerecorder.extensions.getAllRecordings
|
||||
import com.simplemobiletools.voicerecorder.helpers.BITRATES
|
||||
import com.simplemobiletools.voicerecorder.helpers.EXTENSION_M4A
|
||||
import com.simplemobiletools.voicerecorder.helpers.EXTENSION_MP3
|
||||
import com.simplemobiletools.voicerecorder.helpers.EXTENSION_OGG
|
||||
import kotlinx.android.synthetic.main.activity_settings.*
|
||||
import java.util.*
|
||||
import com.simplemobiletools.voicerecorder.models.Events
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.util.Locale
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
class SettingsActivity : SimpleActivity() {
|
||||
private var recycleBinContentSize = 0
|
||||
private lateinit var binding: ActivitySettingsBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
isMaterialActivity = true
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_settings)
|
||||
binding = ActivitySettingsBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
updateMaterialActivityViews(settings_coordinator, settings_holder, useTransparentNavigation = true, useTopSearchMenu = false)
|
||||
setupMaterialScrollListener(settings_nested_scrollview, settings_toolbar)
|
||||
updateMaterialActivityViews(binding.settingsCoordinator, binding.settingsHolder, useTransparentNavigation = true, useTopSearchMenu = false)
|
||||
setupMaterialScrollListener(binding.settingsNestedScrollview, binding.settingsToolbar)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
setupToolbar(settings_toolbar, NavigationIcon.Arrow)
|
||||
setupToolbar(binding.settingsToolbar, NavigationIcon.Arrow)
|
||||
|
||||
setupPurchaseThankYou()
|
||||
setupCustomizeColors()
|
||||
|
@ -47,29 +51,31 @@ class SettingsActivity : SimpleActivity() {
|
|||
setupBitrate()
|
||||
setupAudioSource()
|
||||
setupRecordAfterLaunch()
|
||||
updateTextColors(settings_nested_scrollview)
|
||||
setupUseRecycleBin()
|
||||
setupEmptyRecycleBin()
|
||||
updateTextColors(binding.settingsNestedScrollview)
|
||||
|
||||
arrayOf(settings_color_customization_section_label, settings_general_settings_label).forEach {
|
||||
arrayOf(binding.settingsColorCustomizationSectionLabel, binding.settingsGeneralSettingsLabel, binding.settingsRecycleBinLabel).forEach {
|
||||
it.setTextColor(getProperPrimaryColor())
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupPurchaseThankYou() {
|
||||
settings_purchase_thank_you_holder.beGoneIf(isOrWasThankYouInstalled())
|
||||
settings_purchase_thank_you_holder.setOnClickListener {
|
||||
binding.settingsPurchaseThankYouHolder.beGoneIf(isOrWasThankYouInstalled())
|
||||
binding.settingsPurchaseThankYouHolder.setOnClickListener {
|
||||
launchPurchaseThankYouIntent()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupCustomizeColors() {
|
||||
settings_color_customization_label.text = getCustomizeColorsString()
|
||||
settings_color_customization_holder.setOnClickListener {
|
||||
binding.settingsColorCustomizationLabel.text = getCustomizeColorsString()
|
||||
binding.settingsColorCustomizationHolder.setOnClickListener {
|
||||
handleCustomizeColorsClick()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupCustomizeWidgetColors() {
|
||||
settings_widget_color_customization_holder.setOnClickListener {
|
||||
binding.settingsWidgetColorCustomizationHolder.setOnClickListener {
|
||||
Intent(this, WidgetRecordDisplayConfigureActivity::class.java).apply {
|
||||
putExtra(IS_CUSTOMIZING_COLORS, true)
|
||||
startActivity(this)
|
||||
|
@ -78,41 +84,41 @@ class SettingsActivity : SimpleActivity() {
|
|||
}
|
||||
|
||||
private fun setupUseEnglish() {
|
||||
settings_use_english_holder.beVisibleIf((config.wasUseEnglishToggled || Locale.getDefault().language != "en") && !isTiramisuPlus())
|
||||
settings_use_english.isChecked = config.useEnglish
|
||||
settings_use_english_holder.setOnClickListener {
|
||||
settings_use_english.toggle()
|
||||
config.useEnglish = settings_use_english.isChecked
|
||||
binding.settingsUseEnglishHolder.beVisibleIf((config.wasUseEnglishToggled || Locale.getDefault().language != "en") && !isTiramisuPlus())
|
||||
binding.settingsUseEnglish.isChecked = config.useEnglish
|
||||
binding.settingsUseEnglishHolder.setOnClickListener {
|
||||
binding.settingsUseEnglish.toggle()
|
||||
config.useEnglish = binding.settingsUseEnglish.isChecked
|
||||
exitProcess(0)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupLanguage() {
|
||||
settings_language.text = Locale.getDefault().displayLanguage
|
||||
settings_language_holder.beVisibleIf(isTiramisuPlus())
|
||||
settings_language_holder.setOnClickListener {
|
||||
binding.settingsLanguage.text = Locale.getDefault().displayLanguage
|
||||
binding.settingsLanguageHolder.beVisibleIf(isTiramisuPlus())
|
||||
binding.settingsLanguageHolder.setOnClickListener {
|
||||
launchChangeAppLanguageIntent()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupChangeDateTimeFormat() {
|
||||
settings_change_date_time_format_holder.setOnClickListener {
|
||||
binding.settingsChangeDateTimeFormatHolder.setOnClickListener {
|
||||
ChangeDateTimeFormatDialog(this) {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupHideNotification() {
|
||||
settings_hide_notification.isChecked = config.hideNotification
|
||||
settings_hide_notification_holder.setOnClickListener {
|
||||
settings_hide_notification.toggle()
|
||||
config.hideNotification = settings_hide_notification.isChecked
|
||||
binding.settingsHideNotification.isChecked = config.hideNotification
|
||||
binding.settingsHideNotificationHolder.setOnClickListener {
|
||||
binding.settingsHideNotification.toggle()
|
||||
config.hideNotification = binding.settingsHideNotification.isChecked
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupSaveRecordingsFolder() {
|
||||
settings_save_recordings_label.text = addLockedLabelIfNeeded(R.string.save_recordings_in)
|
||||
settings_save_recordings.text = humanizePath(config.saveRecordingsFolder)
|
||||
settings_save_recordings_holder.setOnClickListener {
|
||||
binding.settingsSaveRecordingsLabel.text = addLockedLabelIfNeeded(R.string.save_recordings_in)
|
||||
binding.settingsSaveRecordings.text = humanizePath(config.saveRecordingsFolder)
|
||||
binding.settingsSaveRecordingsHolder.setOnClickListener {
|
||||
if (isOrWasThankYouInstalled()) {
|
||||
FilePickerDialog(this, config.saveRecordingsFolder, false, showFAB = true) {
|
||||
val path = it
|
||||
|
@ -127,7 +133,7 @@ class SettingsActivity : SimpleActivity() {
|
|||
}
|
||||
|
||||
config.saveRecordingsFolder = path
|
||||
settings_save_recordings.text = humanizePath(config.saveRecordingsFolder)
|
||||
binding.settingsSaveRecordings.text = humanizePath(config.saveRecordingsFolder)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -138,8 +144,8 @@ class SettingsActivity : SimpleActivity() {
|
|||
}
|
||||
|
||||
private fun setupExtension() {
|
||||
settings_extension.text = config.getExtensionText()
|
||||
settings_extension_holder.setOnClickListener {
|
||||
binding.settingsExtension.text = config.getExtensionText()
|
||||
binding.settingsExtensionHolder.setOnClickListener {
|
||||
val items = arrayListOf(
|
||||
RadioItem(EXTENSION_M4A, getString(R.string.m4a)),
|
||||
RadioItem(EXTENSION_MP3, getString(R.string.mp3))
|
||||
|
@ -151,19 +157,19 @@ class SettingsActivity : SimpleActivity() {
|
|||
|
||||
RadioGroupDialog(this@SettingsActivity, items, config.extension) {
|
||||
config.extension = it as Int
|
||||
settings_extension.text = config.getExtensionText()
|
||||
binding.settingsExtension.text = config.getExtensionText()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupBitrate() {
|
||||
settings_bitrate.text = getBitrateText(config.bitrate)
|
||||
settings_bitrate_holder.setOnClickListener {
|
||||
binding.settingsBitrate.text = getBitrateText(config.bitrate)
|
||||
binding.settingsBitrateHolder.setOnClickListener {
|
||||
val items = BITRATES.map { RadioItem(it, getBitrateText(it)) } as ArrayList
|
||||
|
||||
RadioGroupDialog(this@SettingsActivity, items, config.bitrate) {
|
||||
config.bitrate = it as Int
|
||||
settings_bitrate.text = getBitrateText(config.bitrate)
|
||||
binding.settingsBitrate.text = getBitrateText(config.bitrate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -171,21 +177,69 @@ class SettingsActivity : SimpleActivity() {
|
|||
private fun getBitrateText(value: Int): String = getString(R.string.bitrate_value).format(value / 1000)
|
||||
|
||||
private fun setupRecordAfterLaunch() {
|
||||
settings_record_after_launch.isChecked = config.recordAfterLaunch
|
||||
settings_record_after_launch_holder.setOnClickListener {
|
||||
settings_record_after_launch.toggle()
|
||||
config.recordAfterLaunch = settings_record_after_launch.isChecked
|
||||
binding.settingsRecordAfterLaunch.isChecked = config.recordAfterLaunch
|
||||
binding.settingsRecordAfterLaunchHolder.setOnClickListener {
|
||||
binding.settingsRecordAfterLaunch.toggle()
|
||||
config.recordAfterLaunch = binding.settingsRecordAfterLaunch.isChecked
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupUseRecycleBin() {
|
||||
updateRecycleBinButtons()
|
||||
binding.settingsUseRecycleBin.isChecked = config.useRecycleBin
|
||||
binding.settingsUseRecycleBinHolder.setOnClickListener {
|
||||
binding.settingsUseRecycleBin.toggle()
|
||||
config.useRecycleBin = binding.settingsUseRecycleBin.isChecked
|
||||
updateRecycleBinButtons()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateRecycleBinButtons() {
|
||||
binding.settingsEmptyRecycleBinHolder.beVisibleIf(config.useRecycleBin)
|
||||
}
|
||||
|
||||
private fun setupEmptyRecycleBin() {
|
||||
ensureBackgroundThread {
|
||||
try {
|
||||
recycleBinContentSize = getAllRecordings(trashed = true).sumByInt {
|
||||
it.size
|
||||
}
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
|
||||
runOnUiThread {
|
||||
binding.settingsEmptyRecycleBinSize.text = recycleBinContentSize.formatSize()
|
||||
}
|
||||
}
|
||||
|
||||
binding.settingsEmptyRecycleBinHolder.setOnClickListener {
|
||||
if (recycleBinContentSize == 0) {
|
||||
toast(com.simplemobiletools.commons.R.string.recycle_bin_empty)
|
||||
} else {
|
||||
ConfirmationDialog(
|
||||
this,
|
||||
"",
|
||||
com.simplemobiletools.commons.R.string.empty_recycle_bin_confirmation,
|
||||
com.simplemobiletools.commons.R.string.yes,
|
||||
com.simplemobiletools.commons.R.string.no
|
||||
) {
|
||||
emptyTheRecycleBin()
|
||||
recycleBinContentSize = 0
|
||||
binding.settingsEmptyRecycleBinSize.text = 0.formatSize()
|
||||
EventBus.getDefault().post(Events.RecordingTrashUpdated())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupAudioSource() {
|
||||
settings_audio_source.text = config.getAudioSourceText(config.audioSource)
|
||||
settings_audio_source_holder.setOnClickListener {
|
||||
binding.settingsAudioSource.text = config.getAudioSourceText(config.audioSource)
|
||||
binding.settingsAudioSourceHolder.setOnClickListener {
|
||||
val items = getAudioSources().map { RadioItem(it, config.getAudioSourceText(it)) } as ArrayList
|
||||
|
||||
RadioGroupDialog(this@SettingsActivity, items, config.audioSource) {
|
||||
config.audioSource = it as Int
|
||||
settings_audio_source.text = config.getAudioSourceText(config.audioSource)
|
||||
binding.settingsAudioSource.text = config.getAudioSourceText(config.audioSource)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,9 +12,9 @@ import com.simplemobiletools.commons.dialogs.FeatureLockedDialog
|
|||
import com.simplemobiletools.commons.extensions.*
|
||||
import com.simplemobiletools.commons.helpers.IS_CUSTOMIZING_COLORS
|
||||
import com.simplemobiletools.voicerecorder.R
|
||||
import com.simplemobiletools.voicerecorder.databinding.WidgetRecordDisplayConfigBinding
|
||||
import com.simplemobiletools.voicerecorder.extensions.config
|
||||
import com.simplemobiletools.voicerecorder.helpers.MyWidgetRecordDisplayProvider
|
||||
import kotlinx.android.synthetic.main.widget_record_display_config.*
|
||||
|
||||
class WidgetRecordDisplayConfigureActivity : SimpleActivity() {
|
||||
private var mWidgetAlpha = 0f
|
||||
|
@ -22,12 +22,14 @@ class WidgetRecordDisplayConfigureActivity : SimpleActivity() {
|
|||
private var mWidgetColor = 0
|
||||
private var mWidgetColorWithoutTransparency = 0
|
||||
private var mFeatureLockedDialog: FeatureLockedDialog? = null
|
||||
private lateinit var binding: WidgetRecordDisplayConfigBinding
|
||||
|
||||
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||
useDynamicTheme = false
|
||||
super.onCreate(savedInstanceState)
|
||||
setResult(Activity.RESULT_CANCELED)
|
||||
setContentView(R.layout.widget_record_display_config)
|
||||
binding = WidgetRecordDisplayConfigBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
initVariables()
|
||||
|
||||
val isCustomizingColors = intent.extras?.getBoolean(IS_CUSTOMIZING_COLORS) ?: false
|
||||
|
@ -37,11 +39,11 @@ class WidgetRecordDisplayConfigureActivity : SimpleActivity() {
|
|||
finish()
|
||||
}
|
||||
|
||||
config_save.setOnClickListener { saveConfig() }
|
||||
config_widget_color.setOnClickListener { pickBackgroundColor() }
|
||||
binding.configSave.setOnClickListener { saveConfig() }
|
||||
binding.configWidgetColor.setOnClickListener { pickBackgroundColor() }
|
||||
|
||||
val primaryColor = getProperPrimaryColor()
|
||||
config_widget_seekbar.setColors(getProperTextColor(), primaryColor, primaryColor)
|
||||
binding.configWidgetSeekbar.setColors(getProperTextColor(), primaryColor, primaryColor)
|
||||
|
||||
if (!isCustomizingColors && !isOrWasThankYouInstalled()) {
|
||||
mFeatureLockedDialog = FeatureLockedDialog(this) {
|
||||
|
@ -51,8 +53,8 @@ class WidgetRecordDisplayConfigureActivity : SimpleActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
config_save.backgroundTintList = ColorStateList.valueOf(getProperPrimaryColor())
|
||||
config_save.setTextColor(getProperPrimaryColor().getContrastColor())
|
||||
binding.configSave.backgroundTintList = ColorStateList.valueOf(getProperPrimaryColor())
|
||||
binding.configSave.setTextColor(getProperPrimaryColor().getContrastColor())
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
@ -67,14 +69,14 @@ class WidgetRecordDisplayConfigureActivity : SimpleActivity() {
|
|||
private fun initVariables() {
|
||||
mWidgetColor = config.widgetBgColor
|
||||
if (mWidgetColor == resources.getColor(R.color.default_widget_bg_color) && config.isUsingSystemTheme) {
|
||||
mWidgetColor = resources.getColor(R.color.you_primary_color, theme)
|
||||
mWidgetColor = resources.getColor(com.simplemobiletools.commons.R.color.you_primary_color, theme)
|
||||
}
|
||||
|
||||
mWidgetAlpha = Color.alpha(mWidgetColor) / 255.toFloat()
|
||||
|
||||
mWidgetColorWithoutTransparency = Color.rgb(Color.red(mWidgetColor), Color.green(mWidgetColor), Color.blue(mWidgetColor))
|
||||
config_widget_seekbar.setOnSeekBarChangeListener(seekbarChangeListener)
|
||||
config_widget_seekbar.progress = (mWidgetAlpha * 100).toInt()
|
||||
binding.configWidgetSeekbar.setOnSeekBarChangeListener(seekbarChangeListener)
|
||||
binding.configWidgetSeekbar.progress = (mWidgetAlpha * 100).toInt()
|
||||
updateColors()
|
||||
}
|
||||
|
||||
|
@ -107,8 +109,8 @@ class WidgetRecordDisplayConfigureActivity : SimpleActivity() {
|
|||
|
||||
private fun updateColors() {
|
||||
mWidgetColor = mWidgetColorWithoutTransparency.adjustAlpha(mWidgetAlpha)
|
||||
config_widget_color.setFillWithStroke(mWidgetColor, mWidgetColor)
|
||||
config_image.background.mutate().applyColorFilter(mWidgetColor)
|
||||
binding.configWidgetColor.setFillWithStroke(mWidgetColor, mWidgetColor)
|
||||
binding.configImage.background.mutate().applyColorFilter(mWidgetColor)
|
||||
}
|
||||
|
||||
private val seekbarChangeListener = object : SeekBar.OnSeekBarChangeListener {
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
package com.simplemobiletools.voicerecorder.adapters
|
||||
|
||||
import android.provider.MediaStore
|
||||
import android.provider.MediaStore.Audio.Media
|
||||
import android.view.*
|
||||
import android.widget.PopupMenu
|
||||
import android.widget.TextView
|
||||
import androidx.core.net.toUri
|
||||
import com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller
|
||||
import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter
|
||||
import com.simplemobiletools.commons.dialogs.ConfirmationDialog
|
||||
import com.simplemobiletools.commons.extensions.*
|
||||
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
|
||||
import com.simplemobiletools.commons.helpers.isQPlus
|
||||
import com.simplemobiletools.commons.helpers.isRPlus
|
||||
import com.simplemobiletools.commons.views.MyRecyclerView
|
||||
import com.simplemobiletools.voicerecorder.BuildConfig
|
||||
import com.simplemobiletools.voicerecorder.R
|
||||
import com.simplemobiletools.voicerecorder.activities.SimpleActivity
|
||||
import com.simplemobiletools.voicerecorder.databinding.ItemRecordingBinding
|
||||
import com.simplemobiletools.voicerecorder.dialogs.DeleteConfirmationDialog
|
||||
import com.simplemobiletools.voicerecorder.dialogs.RenameRecordingDialog
|
||||
import com.simplemobiletools.voicerecorder.extensions.config
|
||||
import com.simplemobiletools.voicerecorder.extensions.deleteRecordings
|
||||
import com.simplemobiletools.voicerecorder.extensions.moveRecordingsToRecycleBin
|
||||
import com.simplemobiletools.voicerecorder.helpers.getAudioFileContentUri
|
||||
import com.simplemobiletools.voicerecorder.interfaces.RefreshRecordingsListener
|
||||
import com.simplemobiletools.voicerecorder.models.Events
|
||||
import com.simplemobiletools.voicerecorder.models.Recording
|
||||
import kotlinx.android.synthetic.main.item_recording.view.*
|
||||
import java.io.File
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
|
||||
class RecordingsAdapter(
|
||||
activity: SimpleActivity,
|
||||
|
@ -30,8 +30,7 @@ class RecordingsAdapter(
|
|||
val refreshListener: RefreshRecordingsListener,
|
||||
recyclerView: MyRecyclerView,
|
||||
itemClick: (Any) -> Unit
|
||||
) :
|
||||
MyRecyclerViewAdapter(activity, recyclerView, itemClick), RecyclerViewFastScroller.OnPopupTextUpdate {
|
||||
) : MyRecyclerViewAdapter(activity, recyclerView, itemClick), RecyclerViewFastScroller.OnPopupTextUpdate {
|
||||
|
||||
var currRecordingId = 0
|
||||
|
||||
|
@ -74,7 +73,9 @@ class RecordingsAdapter(
|
|||
|
||||
override fun onActionModeDestroyed() {}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = createViewHolder(R.layout.item_recording, parent)
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
return createViewHolder(ItemRecordingBinding.inflate(layoutInflater, parent, false).root)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val recording = recordings[position]
|
||||
|
@ -135,12 +136,21 @@ class RecordingsAdapter(
|
|||
resources.getQuantityString(R.plurals.delete_recordings, itemsCnt, itemsCnt)
|
||||
}
|
||||
|
||||
val baseString = R.string.delete_recordings_confirmation
|
||||
val baseString = if (activity.config.useRecycleBin) {
|
||||
com.simplemobiletools.commons.R.string.move_to_recycle_bin_confirmation
|
||||
} else {
|
||||
R.string.delete_recordings_confirmation
|
||||
}
|
||||
val question = String.format(resources.getString(baseString), items)
|
||||
|
||||
ConfirmationDialog(activity, question) {
|
||||
DeleteConfirmationDialog(activity, question, activity.config.useRecycleBin) { skipRecycleBin ->
|
||||
ensureBackgroundThread {
|
||||
deleteMediaStoreRecordings()
|
||||
val toRecycleBin = !skipRecycleBin && activity.config.useRecycleBin
|
||||
if (toRecycleBin) {
|
||||
moveMediaStoreRecordingsToRecycleBin()
|
||||
} else {
|
||||
deleteMediaStoreRecordings()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -154,38 +164,26 @@ class RecordingsAdapter(
|
|||
val recordingsToRemove = recordings.filter { selectedKeys.contains(it.id) } as ArrayList<Recording>
|
||||
val positions = getSelectedItemPositions()
|
||||
|
||||
when {
|
||||
isRPlus() -> {
|
||||
val fileUris = recordingsToRemove.map { recording ->
|
||||
"${Media.EXTERNAL_CONTENT_URI}/${recording.id.toLong()}".toUri()
|
||||
}
|
||||
|
||||
activity.deleteSDK30Uris(fileUris) { success ->
|
||||
if (success) {
|
||||
doDeleteAnimation(oldRecordingIndex, recordingsToRemove, positions)
|
||||
}
|
||||
}
|
||||
}
|
||||
isQPlus() -> {
|
||||
recordingsToRemove.forEach {
|
||||
val uri = Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
||||
val selection = "${Media._ID} = ?"
|
||||
val selectionArgs = arrayOf(it.id.toString())
|
||||
val result = activity.contentResolver.delete(uri, selection, selectionArgs)
|
||||
|
||||
if (result == 0) {
|
||||
recordingsToRemove.forEach {
|
||||
activity.deleteFile(File(it.path).toFileDirItem(activity))
|
||||
}
|
||||
}
|
||||
}
|
||||
activity.deleteRecordings(recordingsToRemove) { success ->
|
||||
if (success) {
|
||||
doDeleteAnimation(oldRecordingIndex, recordingsToRemove, positions)
|
||||
}
|
||||
else -> {
|
||||
recordingsToRemove.forEach {
|
||||
activity.deleteFile(File(it.path).toFileDirItem(activity))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun moveMediaStoreRecordingsToRecycleBin() {
|
||||
if (selectedKeys.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
val oldRecordingIndex = recordings.indexOfFirst { it.id == currRecordingId }
|
||||
val recordingsToRemove = recordings.filter { selectedKeys.contains(it.id) } as ArrayList<Recording>
|
||||
val positions = getSelectedItemPositions()
|
||||
|
||||
activity.moveRecordingsToRecycleBin(recordingsToRemove) { success ->
|
||||
if (success) {
|
||||
doDeleteAnimation(oldRecordingIndex, recordingsToRemove, positions)
|
||||
EventBus.getDefault().post(Events.RecordingTrashUpdated())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -218,30 +216,30 @@ class RecordingsAdapter(
|
|||
private fun getSelectedItems() = recordings.filter { selectedKeys.contains(it.id) } as ArrayList<Recording>
|
||||
|
||||
private fun setupView(view: View, recording: Recording) {
|
||||
view.apply {
|
||||
setupViewBackground(activity)
|
||||
recording_frame?.isSelected = selectedKeys.contains(recording.id)
|
||||
ItemRecordingBinding.bind(view).apply {
|
||||
root.setupViewBackground(activity)
|
||||
recordingFrame.isSelected = selectedKeys.contains(recording.id)
|
||||
|
||||
arrayListOf<TextView>(recording_title, recording_date, recording_duration, recording_size).forEach {
|
||||
arrayListOf<TextView>(recordingTitle, recordingDate, recordingDuration, recordingSize).forEach {
|
||||
it.setTextColor(textColor)
|
||||
}
|
||||
|
||||
if (recording.id == currRecordingId) {
|
||||
recording_title.setTextColor(context.getProperPrimaryColor())
|
||||
recordingTitle.setTextColor(root.context.getProperPrimaryColor())
|
||||
}
|
||||
|
||||
recording_title.text = recording.title
|
||||
recording_date.text = recording.timestamp.formatDate(context)
|
||||
recording_duration.text = recording.duration.getFormattedDuration()
|
||||
recording_size.text = recording.size.formatSize()
|
||||
recordingTitle.text = recording.title
|
||||
recordingDate.text = recording.timestamp.formatDate(root.context)
|
||||
recordingDuration.text = recording.duration.getFormattedDuration()
|
||||
recordingSize.text = recording.size.formatSize()
|
||||
|
||||
overflow_menu_icon.drawable.apply {
|
||||
overflowMenuIcon.drawable.apply {
|
||||
mutate()
|
||||
setTint(activity.getProperTextColor())
|
||||
}
|
||||
|
||||
overflow_menu_icon.setOnClickListener {
|
||||
showPopupMenu(overflow_menu_anchor, recording)
|
||||
overflowMenuIcon.setOnClickListener {
|
||||
showPopupMenu(overflowMenuAnchor, recording)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -269,16 +267,19 @@ class RecordingsAdapter(
|
|||
renameRecording()
|
||||
}
|
||||
}
|
||||
|
||||
R.id.cab_share -> {
|
||||
executeItemMenuOperation(recordingId) {
|
||||
shareRecordings()
|
||||
}
|
||||
}
|
||||
|
||||
R.id.cab_open_with -> {
|
||||
executeItemMenuOperation(recordingId) {
|
||||
openRecordingWith()
|
||||
}
|
||||
}
|
||||
|
||||
R.id.cab_delete -> {
|
||||
executeItemMenuOperation(recordingId, removeAfterCallback = false) {
|
||||
askConfirmDelete()
|
||||
|
|
|
@ -0,0 +1,221 @@
|
|||
package com.simplemobiletools.voicerecorder.adapters
|
||||
|
||||
import android.view.*
|
||||
import android.widget.PopupMenu
|
||||
import android.widget.TextView
|
||||
import com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller
|
||||
import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter
|
||||
import com.simplemobiletools.commons.dialogs.ConfirmationDialog
|
||||
import com.simplemobiletools.commons.extensions.*
|
||||
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
|
||||
import com.simplemobiletools.commons.views.MyRecyclerView
|
||||
import com.simplemobiletools.voicerecorder.R
|
||||
import com.simplemobiletools.voicerecorder.activities.SimpleActivity
|
||||
import com.simplemobiletools.voicerecorder.databinding.ItemRecordingBinding
|
||||
import com.simplemobiletools.voicerecorder.extensions.deleteRecordings
|
||||
import com.simplemobiletools.voicerecorder.extensions.restoreRecordings
|
||||
import com.simplemobiletools.voicerecorder.interfaces.RefreshRecordingsListener
|
||||
import com.simplemobiletools.voicerecorder.models.Events
|
||||
import com.simplemobiletools.voicerecorder.models.Recording
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
|
||||
class TrashAdapter(
|
||||
activity: SimpleActivity,
|
||||
var recordings: ArrayList<Recording>,
|
||||
val refreshListener: RefreshRecordingsListener,
|
||||
recyclerView: MyRecyclerView
|
||||
) :
|
||||
MyRecyclerViewAdapter(activity, recyclerView, {}), RecyclerViewFastScroller.OnPopupTextUpdate {
|
||||
|
||||
init {
|
||||
setupDragListener(true)
|
||||
}
|
||||
|
||||
override fun getActionMenuId() = R.menu.cab_trash
|
||||
|
||||
override fun prepareActionMode(menu: Menu) {}
|
||||
|
||||
override fun actionItemPressed(id: Int) {
|
||||
if (selectedKeys.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
when (id) {
|
||||
R.id.cab_restore -> restoreRecordings()
|
||||
R.id.cab_delete -> askConfirmDelete()
|
||||
R.id.cab_select_all -> selectAll()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSelectableItemCount() = recordings.size
|
||||
|
||||
override fun getIsItemSelectable(position: Int) = true
|
||||
|
||||
override fun getItemSelectionKey(position: Int) = recordings.getOrNull(position)?.id
|
||||
|
||||
override fun getItemKeyPosition(key: Int) = recordings.indexOfFirst { it.id == key }
|
||||
|
||||
override fun onActionModeCreated() {}
|
||||
|
||||
override fun onActionModeDestroyed() {}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
return createViewHolder(ItemRecordingBinding.inflate(layoutInflater, parent, false).root)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val recording = recordings[position]
|
||||
holder.bindView(recording, true, true) { itemView, layoutPosition ->
|
||||
setupView(itemView, recording)
|
||||
}
|
||||
bindViewHolder(holder)
|
||||
}
|
||||
|
||||
override fun getItemCount() = recordings.size
|
||||
|
||||
private fun getItemWithKey(key: Int): Recording? = recordings.firstOrNull { it.id == key }
|
||||
|
||||
fun updateItems(newItems: ArrayList<Recording>) {
|
||||
if (newItems.hashCode() != recordings.hashCode()) {
|
||||
recordings = newItems
|
||||
notifyDataSetChanged()
|
||||
finishActMode()
|
||||
}
|
||||
}
|
||||
|
||||
private fun restoreRecordings() {
|
||||
if (selectedKeys.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
val recordingsToRestore = recordings.filter { selectedKeys.contains(it.id) } as ArrayList<Recording>
|
||||
val positions = getSelectedItemPositions()
|
||||
|
||||
activity.restoreRecordings(recordingsToRestore) { success ->
|
||||
if (success) {
|
||||
doDeleteAnimation(recordingsToRestore, positions)
|
||||
EventBus.getDefault().post(Events.RecordingTrashUpdated())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun askConfirmDelete() {
|
||||
val itemsCnt = selectedKeys.size
|
||||
val firstItem = getSelectedItems().firstOrNull() ?: return
|
||||
val items = if (itemsCnt == 1) {
|
||||
"\"${firstItem.title}\""
|
||||
} else {
|
||||
resources.getQuantityString(R.plurals.delete_recordings, itemsCnt, itemsCnt)
|
||||
}
|
||||
|
||||
val baseString = R.string.delete_recordings_confirmation
|
||||
val question = String.format(resources.getString(baseString), items)
|
||||
|
||||
ConfirmationDialog(activity, question) {
|
||||
ensureBackgroundThread {
|
||||
deleteMediaStoreRecordings()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteMediaStoreRecordings() {
|
||||
if (selectedKeys.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
val recordingsToRemove = recordings.filter { selectedKeys.contains(it.id) } as ArrayList<Recording>
|
||||
val positions = getSelectedItemPositions()
|
||||
|
||||
activity.deleteRecordings(recordingsToRemove) { success ->
|
||||
if (success) {
|
||||
doDeleteAnimation(recordingsToRemove, positions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun doDeleteAnimation(recordingsToRemove: ArrayList<Recording>, positions: ArrayList<Int>) {
|
||||
recordings.removeAll(recordingsToRemove.toSet())
|
||||
activity.runOnUiThread {
|
||||
if (recordings.isEmpty()) {
|
||||
refreshListener.refreshRecordings()
|
||||
finishActMode()
|
||||
} else {
|
||||
positions.sortDescending()
|
||||
removeSelectedItems(positions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSelectedItems() = recordings.filter { selectedKeys.contains(it.id) } as ArrayList<Recording>
|
||||
|
||||
private fun setupView(view: View, recording: Recording) {
|
||||
ItemRecordingBinding.bind(view).apply {
|
||||
root.setupViewBackground(activity)
|
||||
recordingFrame.isSelected = selectedKeys.contains(recording.id)
|
||||
|
||||
arrayListOf<TextView>(recordingTitle, recordingDate, recordingDuration, recordingSize).forEach {
|
||||
it.setTextColor(textColor)
|
||||
}
|
||||
|
||||
recordingTitle.text = recording.title
|
||||
recordingDate.text = recording.timestamp.formatDate(root.context)
|
||||
recordingDuration.text = recording.duration.getFormattedDuration()
|
||||
recordingSize.text = recording.size.formatSize()
|
||||
|
||||
overflowMenuIcon.drawable.apply {
|
||||
mutate()
|
||||
setTint(activity.getProperTextColor())
|
||||
}
|
||||
|
||||
overflowMenuIcon.setOnClickListener {
|
||||
showPopupMenu(overflowMenuAnchor, recording)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onChange(position: Int) = recordings.getOrNull(position)?.title ?: ""
|
||||
|
||||
private fun showPopupMenu(view: View, recording: Recording) {
|
||||
if (selectedKeys.isNotEmpty()) {
|
||||
selectedKeys.clear()
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
finishActMode()
|
||||
val theme = activity.getPopupMenuTheme()
|
||||
val contextTheme = ContextThemeWrapper(activity, theme)
|
||||
|
||||
PopupMenu(contextTheme, view, Gravity.END).apply {
|
||||
inflate(getActionMenuId())
|
||||
menu.findItem(R.id.cab_select_all).isVisible = false
|
||||
menu.findItem(R.id.cab_restore).title = resources.getString(com.simplemobiletools.commons.R.string.restore_this_file)
|
||||
setOnMenuItemClickListener { item ->
|
||||
val recordingId = recording.id
|
||||
when (item.itemId) {
|
||||
R.id.cab_restore -> {
|
||||
executeItemMenuOperation(recordingId, removeAfterCallback = false) {
|
||||
restoreRecordings()
|
||||
}
|
||||
}
|
||||
|
||||
R.id.cab_delete -> {
|
||||
executeItemMenuOperation(recordingId, removeAfterCallback = false) {
|
||||
askConfirmDelete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun executeItemMenuOperation(callId: Int, removeAfterCallback: Boolean = true, callback: () -> Unit) {
|
||||
selectedKeys.add(callId)
|
||||
callback()
|
||||
if (removeAfterCallback) {
|
||||
selectedKeys.remove(callId)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,12 +8,18 @@ import com.simplemobiletools.voicerecorder.R
|
|||
import com.simplemobiletools.voicerecorder.activities.SimpleActivity
|
||||
import com.simplemobiletools.voicerecorder.fragments.MyViewPagerFragment
|
||||
import com.simplemobiletools.voicerecorder.fragments.PlayerFragment
|
||||
import com.simplemobiletools.voicerecorder.fragments.TrashFragment
|
||||
|
||||
class ViewPagerAdapter(private val activity: SimpleActivity) : PagerAdapter() {
|
||||
class ViewPagerAdapter(private val activity: SimpleActivity, val showRecycleBin: Boolean) : PagerAdapter() {
|
||||
private val mFragments = SparseArray<MyViewPagerFragment>()
|
||||
|
||||
override fun instantiateItem(container: ViewGroup, position: Int): Any {
|
||||
val layout = if (position == 0) R.layout.fragment_recorder else R.layout.fragment_player
|
||||
val layout = when (position) {
|
||||
0 -> R.layout.fragment_recorder
|
||||
1 -> R.layout.fragment_player
|
||||
2 -> R.layout.fragment_trash
|
||||
else -> throw IllegalArgumentException("Invalid position. Count = $count, requested position = $position")
|
||||
}
|
||||
val view = activity.layoutInflater.inflate(layout, container, false)
|
||||
container.addView(view)
|
||||
|
||||
|
@ -26,7 +32,11 @@ class ViewPagerAdapter(private val activity: SimpleActivity) : PagerAdapter() {
|
|||
container.removeView(item as View)
|
||||
}
|
||||
|
||||
override fun getCount() = 2
|
||||
override fun getCount() = if (showRecycleBin) {
|
||||
3
|
||||
} else {
|
||||
2
|
||||
}
|
||||
|
||||
override fun isViewFromObject(view: View, item: Any) = view == item
|
||||
|
||||
|
@ -42,9 +52,17 @@ class ViewPagerAdapter(private val activity: SimpleActivity) : PagerAdapter() {
|
|||
}
|
||||
}
|
||||
|
||||
fun finishActMode() = (mFragments[1] as? PlayerFragment)?.finishActMode()
|
||||
fun finishActMode() {
|
||||
(mFragments[1] as? PlayerFragment)?.finishActMode()
|
||||
if (showRecycleBin) {
|
||||
(mFragments[2] as? TrashFragment)?.finishActMode()
|
||||
}
|
||||
}
|
||||
|
||||
fun searchTextChanged(text: String) {
|
||||
(mFragments[1] as? PlayerFragment)?.onSearchTextChanged(text)
|
||||
if (showRecycleBin) {
|
||||
(mFragments[2] as? TrashFragment)?.onSearchTextChanged(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
package com.simplemobiletools.voicerecorder.dialogs
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.simplemobiletools.commons.extensions.beGoneIf
|
||||
import com.simplemobiletools.commons.extensions.getAlertDialogBuilder
|
||||
import com.simplemobiletools.commons.extensions.setupDialogStuff
|
||||
import com.simplemobiletools.voicerecorder.databinding.DialogDeleteConfirmationBinding
|
||||
|
||||
class DeleteConfirmationDialog(
|
||||
private val activity: Activity,
|
||||
private val message: String,
|
||||
private val showSkipRecycleBinOption: Boolean,
|
||||
private val callback: (skipRecycleBin: Boolean) -> Unit
|
||||
) {
|
||||
|
||||
private var dialog: AlertDialog? = null
|
||||
val binding = DialogDeleteConfirmationBinding.inflate(activity.layoutInflater)
|
||||
val view = binding.root
|
||||
|
||||
init {
|
||||
binding.deleteRememberTitle.text = message
|
||||
binding.skipTheRecycleBinCheckbox.beGoneIf(!showSkipRecycleBinOption)
|
||||
activity.getAlertDialogBuilder()
|
||||
.setPositiveButton(com.simplemobiletools.commons.R.string.yes) { _, _ -> dialogConfirmed() }
|
||||
.setNegativeButton(com.simplemobiletools.commons.R.string.no, null)
|
||||
.apply {
|
||||
activity.setupDialogStuff(view, this) { alertDialog ->
|
||||
dialog = alertDialog
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun dialogConfirmed() {
|
||||
dialog?.dismiss()
|
||||
callback(binding.skipTheRecycleBinCheckbox.isChecked)
|
||||
}
|
||||
}
|
|
@ -7,36 +7,36 @@ import com.simplemobiletools.commons.activities.BaseSimpleActivity
|
|||
import com.simplemobiletools.commons.extensions.*
|
||||
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
|
||||
import com.simplemobiletools.commons.helpers.isRPlus
|
||||
import com.simplemobiletools.voicerecorder.R
|
||||
import com.simplemobiletools.voicerecorder.databinding.DialogRenameRecordingBinding
|
||||
import com.simplemobiletools.voicerecorder.extensions.config
|
||||
import com.simplemobiletools.voicerecorder.helpers.getAudioFileContentUri
|
||||
import com.simplemobiletools.voicerecorder.models.Events
|
||||
import com.simplemobiletools.voicerecorder.models.Recording
|
||||
import kotlinx.android.synthetic.main.dialog_rename_recording.view.*
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.io.File
|
||||
|
||||
class RenameRecordingDialog(val activity: BaseSimpleActivity, val recording: Recording, val callback: () -> Unit) {
|
||||
init {
|
||||
val view = activity.layoutInflater.inflate(R.layout.dialog_rename_recording, null).apply {
|
||||
rename_recording_title.setText(recording.title.substringBeforeLast('.'))
|
||||
val binding = DialogRenameRecordingBinding.inflate(activity.layoutInflater).apply {
|
||||
renameRecordingTitle.setText(recording.title.substringBeforeLast('.'))
|
||||
}
|
||||
val view = binding.root
|
||||
|
||||
activity.getAlertDialogBuilder()
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(com.simplemobiletools.commons.R.string.ok, null)
|
||||
.setNegativeButton(com.simplemobiletools.commons.R.string.cancel, null)
|
||||
.apply {
|
||||
activity.setupDialogStuff(view, this, R.string.rename) { alertDialog ->
|
||||
alertDialog.showKeyboard(view.rename_recording_title)
|
||||
activity.setupDialogStuff(view, this, com.simplemobiletools.commons.R.string.rename) { alertDialog ->
|
||||
alertDialog.showKeyboard(binding.renameRecordingTitle)
|
||||
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
|
||||
val newTitle = view.rename_recording_title.value
|
||||
val newTitle = binding.renameRecordingTitle.value
|
||||
if (newTitle.isEmpty()) {
|
||||
activity.toast(R.string.empty_name)
|
||||
activity.toast(com.simplemobiletools.commons.R.string.empty_name)
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
if (!newTitle.isAValidFilename()) {
|
||||
activity.toast(R.string.invalid_name)
|
||||
activity.toast(com.simplemobiletools.commons.R.string.invalid_name)
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
package com.simplemobiletools.voicerecorder.extensions
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.provider.MediaStore
|
||||
import android.provider.MediaStore.Audio.Media
|
||||
import com.simplemobiletools.commons.activities.BaseSimpleActivity
|
||||
import com.simplemobiletools.commons.extensions.deleteFile
|
||||
import com.simplemobiletools.commons.extensions.getParentPath
|
||||
import com.simplemobiletools.commons.extensions.toFileDirItem
|
||||
import com.simplemobiletools.commons.helpers.*
|
||||
import com.simplemobiletools.commons.models.FileDirItem
|
||||
import com.simplemobiletools.voicerecorder.helpers.getAudioFileContentUri
|
||||
import com.simplemobiletools.voicerecorder.models.Recording
|
||||
import java.io.File
|
||||
|
||||
fun BaseSimpleActivity.deleteRecordings(recordingsToRemove: Collection<Recording>, callback: (success: Boolean) -> Unit) {
|
||||
when {
|
||||
isRPlus() -> {
|
||||
val fileUris = recordingsToRemove.map { recording ->
|
||||
getAudioFileContentUri(recording.id.toLong())
|
||||
}
|
||||
|
||||
deleteSDK30Uris(fileUris, callback)
|
||||
}
|
||||
|
||||
isQPlus() -> {
|
||||
recordingsToRemove.forEach {
|
||||
val uri = Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
||||
val selection = "${Media._ID} = ?"
|
||||
val selectionArgs = arrayOf(it.id.toString())
|
||||
val result = contentResolver.delete(uri, selection, selectionArgs)
|
||||
|
||||
if (result == 0) {
|
||||
val fileDirItem = File(it.path).toFileDirItem(this)
|
||||
deleteFile(fileDirItem)
|
||||
}
|
||||
}
|
||||
callback(true)
|
||||
}
|
||||
|
||||
else -> {
|
||||
recordingsToRemove.forEach {
|
||||
val fileDirItem = File(it.path).toFileDirItem(this)
|
||||
deleteFile(fileDirItem)
|
||||
}
|
||||
callback(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun BaseSimpleActivity.restoreRecordings(recordingsToRestore: Collection<Recording>, callback: (success: Boolean) -> Unit) {
|
||||
when {
|
||||
isRPlus() -> {
|
||||
val fileUris = recordingsToRestore.map { recording ->
|
||||
getAudioFileContentUri(recording.id.toLong())
|
||||
}
|
||||
|
||||
trashSDK30Uris(fileUris, false, callback)
|
||||
}
|
||||
|
||||
isQPlus() -> {
|
||||
var wait = false
|
||||
recordingsToRestore.forEach {
|
||||
val uri = Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
||||
val selection = "${Media._ID} = ?"
|
||||
val selectionArgs = arrayOf(it.id.toString())
|
||||
val values = ContentValues().apply {
|
||||
put(Media.IS_TRASHED, 0)
|
||||
}
|
||||
val result = contentResolver.update(uri, values, selection, selectionArgs)
|
||||
|
||||
if (result == 0) {
|
||||
wait = true
|
||||
copyMoveFilesTo(
|
||||
fileDirItems = arrayListOf(File(it.path).toFileDirItem(this)),
|
||||
source = it.path.getParentPath(),
|
||||
destination = config.saveRecordingsFolder,
|
||||
isCopyOperation = false,
|
||||
copyPhotoVideoOnly = false,
|
||||
copyHidden = false
|
||||
) {
|
||||
callback(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!wait) {
|
||||
callback(true)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
copyMoveFilesTo(
|
||||
fileDirItems = recordingsToRestore.map { File(it.path).toFileDirItem(this) }.toMutableList() as ArrayList<FileDirItem>,
|
||||
source = recordingsToRestore.first().path.getParentPath(),
|
||||
destination = config.saveRecordingsFolder,
|
||||
isCopyOperation = false,
|
||||
copyPhotoVideoOnly = false,
|
||||
copyHidden = false
|
||||
) {
|
||||
callback(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun BaseSimpleActivity.moveRecordingsToRecycleBin(recordingsToMove: Collection<Recording>, callback: (success: Boolean) -> Unit) {
|
||||
when {
|
||||
isRPlus() -> {
|
||||
val fileUris = recordingsToMove.map { recording ->
|
||||
getAudioFileContentUri(recording.id.toLong())
|
||||
}
|
||||
|
||||
trashSDK30Uris(fileUris, true, callback)
|
||||
}
|
||||
|
||||
isQPlus() -> {
|
||||
var wait = false
|
||||
recordingsToMove.forEach {
|
||||
val uri = Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
||||
val selection = "${Media._ID} = ?"
|
||||
val selectionArgs = arrayOf(it.id.toString())
|
||||
val values = ContentValues().apply {
|
||||
put(Media.IS_TRASHED, 1)
|
||||
}
|
||||
val result = contentResolver.update(uri, values, selection, selectionArgs)
|
||||
|
||||
if (result == 0) {
|
||||
wait = true
|
||||
copyMoveFilesTo(
|
||||
fileDirItems = arrayListOf(File(it.path).toFileDirItem(this)),
|
||||
source = it.path.getParentPath(),
|
||||
destination = getOrCreateTrashFolder(),
|
||||
isCopyOperation = false,
|
||||
copyPhotoVideoOnly = false,
|
||||
copyHidden = false
|
||||
) {
|
||||
callback(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!wait) {
|
||||
callback(true)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
copyMoveFilesTo(
|
||||
fileDirItems = recordingsToMove.map { File(it.path).toFileDirItem(this) }.toMutableList() as ArrayList<FileDirItem>,
|
||||
source = recordingsToMove.first().path.getParentPath(),
|
||||
destination = getOrCreateTrashFolder(),
|
||||
isCopyOperation = false,
|
||||
copyPhotoVideoOnly = false,
|
||||
copyHidden = false
|
||||
) {
|
||||
callback(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun BaseSimpleActivity.checkRecycleBinItems() {
|
||||
if (isQPlus()) {
|
||||
// System is handling recycle bin on Q+ devices
|
||||
return
|
||||
}
|
||||
|
||||
if (config.useRecycleBin && config.lastRecycleBinCheck < System.currentTimeMillis() - DAY_SECONDS * 1000) {
|
||||
config.lastRecycleBinCheck = System.currentTimeMillis()
|
||||
ensureBackgroundThread {
|
||||
try {
|
||||
val recordingsToRemove = getLegacyRecordings(trashed = true).filter { it.timestamp < System.currentTimeMillis() - MONTH_SECONDS * 1000L }
|
||||
if (recordingsToRemove.isNotEmpty()) {
|
||||
deleteRecordings(recordingsToRemove) {}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun BaseSimpleActivity.emptyTheRecycleBin() {
|
||||
deleteRecordings(getAllRecordings(trashed = true)) {}
|
||||
}
|
|
@ -1,20 +1,28 @@
|
|||
package com.simplemobiletools.voicerecorder.extensions
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.ComponentName
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.database.Cursor
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.media.MediaMetadataRetriever
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import com.simplemobiletools.commons.extensions.internalStoragePath
|
||||
import com.simplemobiletools.commons.helpers.isQPlus
|
||||
import android.provider.MediaStore
|
||||
import android.provider.MediaStore.Audio.Media
|
||||
import com.simplemobiletools.commons.extensions.*
|
||||
import com.simplemobiletools.commons.helpers.*
|
||||
import com.simplemobiletools.voicerecorder.R
|
||||
import com.simplemobiletools.voicerecorder.helpers.Config
|
||||
import com.simplemobiletools.voicerecorder.helpers.IS_RECORDING
|
||||
import com.simplemobiletools.voicerecorder.helpers.MyWidgetRecordDisplayProvider
|
||||
import com.simplemobiletools.voicerecorder.helpers.TOGGLE_WIDGET_UI
|
||||
import com.simplemobiletools.voicerecorder.helpers.*
|
||||
import com.simplemobiletools.voicerecorder.models.Recording
|
||||
import java.io.File
|
||||
import kotlin.math.roundToLong
|
||||
|
||||
val Context.config: Config get() = Config.newInstance(applicationContext)
|
||||
|
||||
|
@ -51,3 +59,182 @@ fun Context.getDefaultRecordingsRelativePath(): String {
|
|||
getString(R.string.app_name)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
fun Context.getNewMediaStoreRecordings(trashed: Boolean = false): ArrayList<Recording> {
|
||||
val recordings = ArrayList<Recording>()
|
||||
|
||||
val uri = Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
||||
val projection = arrayOf(
|
||||
Media._ID,
|
||||
Media.DISPLAY_NAME,
|
||||
Media.DATE_ADDED,
|
||||
Media.DURATION,
|
||||
Media.SIZE
|
||||
)
|
||||
|
||||
val bundle = Bundle().apply {
|
||||
putStringArray(ContentResolver.QUERY_ARG_SORT_COLUMNS, arrayOf(Media.DATE_ADDED))
|
||||
putInt(ContentResolver.QUERY_ARG_SORT_DIRECTION, ContentResolver.QUERY_SORT_DIRECTION_DESCENDING)
|
||||
putString(ContentResolver.QUERY_ARG_SQL_SELECTION, "${Media.OWNER_PACKAGE_NAME} = ?")
|
||||
putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, arrayOf(packageName))
|
||||
if (config.useRecycleBin) {
|
||||
val trashedValue = if (trashed) MediaStore.MATCH_ONLY else MediaStore.MATCH_EXCLUDE
|
||||
putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, trashedValue)
|
||||
}
|
||||
}
|
||||
queryCursor(uri, projection, bundle, true) { cursor ->
|
||||
val recording = readRecordingFromCursor(cursor)
|
||||
recordings.add(recording)
|
||||
}
|
||||
|
||||
return recordings
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
fun Context.getMediaStoreRecordings(trashed: Boolean = false): ArrayList<Recording> {
|
||||
val recordings = ArrayList<Recording>()
|
||||
|
||||
val uri = Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
||||
val projection = arrayOf(
|
||||
Media._ID,
|
||||
Media.DISPLAY_NAME,
|
||||
Media.DATE_ADDED,
|
||||
Media.DURATION,
|
||||
Media.SIZE
|
||||
)
|
||||
|
||||
var selection = "${Media.OWNER_PACKAGE_NAME} = ?"
|
||||
var selectionArgs = arrayOf(packageName)
|
||||
val sortOrder = "${Media.DATE_ADDED} DESC"
|
||||
|
||||
if (config.useRecycleBin) {
|
||||
val trashedValue = if (trashed) 1 else 0
|
||||
selection += " AND ${Media.IS_TRASHED} = ?"
|
||||
selectionArgs = selectionArgs.plus(trashedValue.toString())
|
||||
}
|
||||
|
||||
queryCursor(uri, projection, selection, selectionArgs, sortOrder, true) { cursor ->
|
||||
val recording = readRecordingFromCursor(cursor)
|
||||
recordings.add(recording)
|
||||
}
|
||||
|
||||
return recordings
|
||||
}
|
||||
|
||||
fun Context.getLegacyRecordings(trashed: Boolean = false): ArrayList<Recording> {
|
||||
val recordings = ArrayList<Recording>()
|
||||
val folder = if (trashed) {
|
||||
trashFolder
|
||||
} else {
|
||||
config.saveRecordingsFolder
|
||||
}
|
||||
val files = File(folder).listFiles() ?: return recordings
|
||||
|
||||
files.filter { it.isAudioFast() }.forEach {
|
||||
val id = it.hashCode()
|
||||
val title = it.name
|
||||
val path = it.absolutePath
|
||||
val timestamp = (it.lastModified() / 1000).toInt()
|
||||
val duration = getDuration(it.absolutePath) ?: 0
|
||||
val size = it.length().toInt()
|
||||
val recording = Recording(id, title, path, timestamp, duration, size)
|
||||
recordings.add(recording)
|
||||
}
|
||||
return recordings
|
||||
}
|
||||
|
||||
fun Context.getSAFRecordings(trashed: Boolean = false): ArrayList<Recording> {
|
||||
val recordings = ArrayList<Recording>()
|
||||
val folder = if (trashed) {
|
||||
trashFolder
|
||||
} else {
|
||||
config.saveRecordingsFolder
|
||||
}
|
||||
val files = getDocumentSdk30(folder)?.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
|
||||
}
|
||||
|
||||
fun Context.getAllRecordings(trashed: Boolean = false): ArrayList<Recording> {
|
||||
val recordings = ArrayList<Recording>()
|
||||
return when {
|
||||
isRPlus() -> {
|
||||
recordings.addAll(getNewMediaStoreRecordings(trashed))
|
||||
recordings.addAll(getSAFRecordings(trashed))
|
||||
recordings
|
||||
}
|
||||
|
||||
isQPlus() -> {
|
||||
recordings.addAll(getMediaStoreRecordings(trashed))
|
||||
recordings.addAll(getLegacyRecordings(trashed))
|
||||
recordings
|
||||
}
|
||||
|
||||
else -> {
|
||||
recordings.addAll(getLegacyRecordings(trashed))
|
||||
recordings
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val Context.trashFolder
|
||||
get() = "${config.saveRecordingsFolder}/.trash"
|
||||
|
||||
fun Context.getOrCreateTrashFolder(): String {
|
||||
val folder = File(trashFolder)
|
||||
if (!folder.exists()) {
|
||||
folder.mkdir()
|
||||
}
|
||||
return trashFolder
|
||||
}
|
||||
|
||||
private fun Context.readRecordingFromCursor(cursor: Cursor): Recording {
|
||||
val id = cursor.getIntValue(Media._ID)
|
||||
val title = cursor.getStringValue(Media.DISPLAY_NAME)
|
||||
val timestamp = cursor.getIntValue(Media.DATE_ADDED)
|
||||
var duration = cursor.getLongValue(Media.DURATION) / 1000
|
||||
var size = cursor.getIntValue(Media.SIZE)
|
||||
|
||||
if (duration == 0L) {
|
||||
duration = getDurationFromUri(getAudioFileContentUri(id.toLong()))
|
||||
}
|
||||
|
||||
if (size == 0) {
|
||||
size = getSizeFromUri(id.toLong())
|
||||
}
|
||||
|
||||
return Recording(id, title, "", timestamp, duration.toInt(), size)
|
||||
}
|
||||
|
||||
private fun Context.getSizeFromUri(id: Long): Int {
|
||||
val recordingUri = getAudioFileContentUri(id)
|
||||
return try {
|
||||
contentResolver.openInputStream(recordingUri)?.available() ?: 0
|
||||
} catch (e: Exception) {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
private fun Context.getDurationFromUri(uri: Uri): Long {
|
||||
return try {
|
||||
val retriever = MediaMetadataRetriever()
|
||||
retriever.setDataSource(this, uri)
|
||||
val time = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)!!
|
||||
(time.toLong() / 1000.toDouble()).roundToLong()
|
||||
} catch (e: Exception) {
|
||||
0L
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,39 +1,34 @@
|
|||
package com.simplemobiletools.voicerecorder.fragments
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
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.ensureBackgroundThread
|
||||
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
|
||||
import com.simplemobiletools.voicerecorder.databinding.FragmentPlayerBinding
|
||||
import com.simplemobiletools.voicerecorder.extensions.config
|
||||
import com.simplemobiletools.voicerecorder.extensions.getAllRecordings
|
||||
import com.simplemobiletools.voicerecorder.helpers.getAudioFileContentUri
|
||||
import com.simplemobiletools.voicerecorder.interfaces.RefreshRecordingsListener
|
||||
import com.simplemobiletools.voicerecorder.models.Events
|
||||
import com.simplemobiletools.voicerecorder.models.Recording
|
||||
import kotlinx.android.synthetic.main.fragment_player.view.*
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import kotlin.math.roundToLong
|
||||
import java.util.Stack
|
||||
import java.util.Timer
|
||||
import java.util.TimerTask
|
||||
|
||||
class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPagerFragment(context, attributeSet), RefreshRecordingsListener {
|
||||
private val FAST_FORWARD_SKIP_MS = 10000
|
||||
|
@ -45,18 +40,25 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
|
|||
private var lastSearchQuery = ""
|
||||
private var bus: EventBus? = null
|
||||
private var prevSavePath = ""
|
||||
private var prevRecycleBinState = context.config.useRecycleBin
|
||||
private var playOnPreparation = true
|
||||
private lateinit var binding: FragmentPlayerBinding
|
||||
|
||||
override fun onFinishInflate() {
|
||||
super.onFinishInflate()
|
||||
binding = FragmentPlayerBinding.bind(this)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
setupColors()
|
||||
if (prevSavePath.isNotEmpty() && context!!.config.saveRecordingsFolder != prevSavePath) {
|
||||
if (prevSavePath.isNotEmpty() && context!!.config.saveRecordingsFolder != prevSavePath || context.config.useRecycleBin != prevRecycleBinState) {
|
||||
itemsIgnoringSearch = getRecordings()
|
||||
setupAdapter(itemsIgnoringSearch)
|
||||
} else {
|
||||
getRecordingsAdapter()?.updateTextColor(context.getProperTextColor())
|
||||
}
|
||||
|
||||
storePrevPath()
|
||||
storePrevState()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
@ -78,27 +80,27 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
|
|||
setupAdapter(itemsIgnoringSearch)
|
||||
initMediaPlayer()
|
||||
setupViews()
|
||||
storePrevPath()
|
||||
storePrevState()
|
||||
}
|
||||
|
||||
private fun setupViews() {
|
||||
play_pause_btn.setOnClickListener {
|
||||
if (playedRecordingIDs.empty() || player_progressbar.max == 0) {
|
||||
next_btn.callOnClick()
|
||||
binding.playPauseBtn.setOnClickListener {
|
||||
if (playedRecordingIDs.empty() || binding.playerProgressbar.max == 0) {
|
||||
binding.nextBtn.callOnClick()
|
||||
} else {
|
||||
togglePlayPause()
|
||||
}
|
||||
}
|
||||
|
||||
player_progress_current.setOnClickListener {
|
||||
binding.playerProgressCurrent.setOnClickListener {
|
||||
skip(false)
|
||||
}
|
||||
|
||||
player_progress_max.setOnClickListener {
|
||||
binding.playerProgressMax.setOnClickListener {
|
||||
skip(true)
|
||||
}
|
||||
|
||||
previous_btn.setOnClickListener {
|
||||
binding.previousBtn.setOnClickListener {
|
||||
if (playedRecordingIDs.isEmpty()) {
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
@ -114,14 +116,14 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
|
|||
playRecording(prevRecording, true)
|
||||
}
|
||||
|
||||
player_title.setOnLongClickListener {
|
||||
if (player_title.value.isNotEmpty()) {
|
||||
context.copyToClipboard(player_title.value)
|
||||
binding.playerTitle.setOnLongClickListener {
|
||||
if (binding.playerTitle.value.isNotEmpty()) {
|
||||
context.copyToClipboard(binding.playerTitle.value)
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
next_btn.setOnClickListener {
|
||||
binding.nextBtn.setOnClickListener {
|
||||
val adapter = getRecordingsAdapter()
|
||||
if (adapter == null || adapter.recordings.isEmpty()) {
|
||||
return@setOnClickListener
|
||||
|
@ -141,164 +143,49 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
|
|||
}
|
||||
|
||||
private fun setupAdapter(recordings: ArrayList<Recording>) {
|
||||
ensureBackgroundThread {
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
recordings_fastscroller.beVisibleIf(recordings.isNotEmpty())
|
||||
recordings_placeholder.beVisibleIf(recordings.isEmpty())
|
||||
if (recordings.isEmpty()) {
|
||||
val stringId = if (lastSearchQuery.isEmpty()) {
|
||||
if (isQPlus()) {
|
||||
R.string.no_recordings_found
|
||||
} else {
|
||||
R.string.no_recordings_in_folder_found
|
||||
}
|
||||
} else {
|
||||
R.string.no_items_found
|
||||
}
|
||||
|
||||
recordings_placeholder.text = context.getString(stringId)
|
||||
resetProgress(null)
|
||||
player?.stop()
|
||||
}
|
||||
|
||||
val adapter = getRecordingsAdapter()
|
||||
if (adapter == null) {
|
||||
RecordingsAdapter(context as SimpleActivity, recordings, this, recordings_list) {
|
||||
playRecording(it as Recording, true)
|
||||
if (playedRecordingIDs.isEmpty() || playedRecordingIDs.peek() != it.id) {
|
||||
playedRecordingIDs.push(it.id)
|
||||
}
|
||||
}.apply {
|
||||
recordings_list.adapter = this
|
||||
}
|
||||
|
||||
if (context.areSystemAnimationsEnabled) {
|
||||
recordings_list.scheduleLayoutAnimation()
|
||||
}
|
||||
binding.recordingsFastscroller.beVisibleIf(recordings.isNotEmpty())
|
||||
binding.recordingsPlaceholder.beVisibleIf(recordings.isEmpty())
|
||||
if (recordings.isEmpty()) {
|
||||
val stringId = if (lastSearchQuery.isEmpty()) {
|
||||
if (isQPlus()) {
|
||||
R.string.no_recordings_found
|
||||
} else {
|
||||
adapter.updateItems(recordings)
|
||||
R.string.no_recordings_in_folder_found
|
||||
}
|
||||
} else {
|
||||
com.simplemobiletools.commons.R.string.no_items_found
|
||||
}
|
||||
|
||||
binding.recordingsPlaceholder.text = context.getString(stringId)
|
||||
resetProgress(null)
|
||||
player?.stop()
|
||||
}
|
||||
|
||||
val adapter = getRecordingsAdapter()
|
||||
if (adapter == null) {
|
||||
RecordingsAdapter(context as SimpleActivity, recordings, this, binding.recordingsList) {
|
||||
playRecording(it as Recording, true)
|
||||
if (playedRecordingIDs.isEmpty() || playedRecordingIDs.peek() != it.id) {
|
||||
playedRecordingIDs.push(it.id)
|
||||
}
|
||||
}.apply {
|
||||
binding.recordingsList.adapter = this
|
||||
}
|
||||
|
||||
if (context.areSystemAnimationsEnabled) {
|
||||
binding.recordingsList.scheduleLayoutAnimation()
|
||||
}
|
||||
} else {
|
||||
adapter.updateItems(recordings)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getRecordings(): ArrayList<Recording> {
|
||||
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 {
|
||||
return context.getAllRecordings().apply {
|
||||
sortByDescending { it.timestamp }
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
private fun getMediaStoreRecordings(): ArrayList<Recording> {
|
||||
val recordings = ArrayList<Recording>()
|
||||
|
||||
val uri = Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
||||
val projection = arrayOf(
|
||||
Media._ID,
|
||||
Media.DISPLAY_NAME,
|
||||
Media.DATE_ADDED,
|
||||
Media.DURATION,
|
||||
Media.SIZE
|
||||
)
|
||||
|
||||
val selection = "${Media.OWNER_PACKAGE_NAME} = ?"
|
||||
val selectionArgs = arrayOf(context.packageName)
|
||||
val sortOrder = "${Media.DATE_ADDED} DESC"
|
||||
|
||||
context.queryCursor(uri, projection, selection, selectionArgs, sortOrder, true) { cursor ->
|
||||
val id = cursor.getIntValue(Media._ID)
|
||||
val title = cursor.getStringValue(Media.DISPLAY_NAME)
|
||||
val timestamp = cursor.getIntValue(Media.DATE_ADDED)
|
||||
var duration = cursor.getLongValue(Media.DURATION) / 1000
|
||||
var size = cursor.getIntValue(Media.SIZE)
|
||||
|
||||
if (duration == 0L) {
|
||||
duration = getDurationFromUri(getAudioFileContentUri(id.toLong()))
|
||||
}
|
||||
|
||||
if (size == 0) {
|
||||
size = getSizeFromUri(id.toLong())
|
||||
}
|
||||
|
||||
val recording = Recording(id, title, "", timestamp, duration.toInt(), size)
|
||||
recordings.add(recording)
|
||||
}
|
||||
|
||||
return recordings
|
||||
}
|
||||
|
||||
private fun getLegacyRecordings(): ArrayList<Recording> {
|
||||
val recordings = ArrayList<Recording>()
|
||||
val files = File(context.config.saveRecordingsFolder).listFiles() ?: return recordings
|
||||
|
||||
files.filter { it.isAudioFast() }.forEach {
|
||||
val id = it.hashCode()
|
||||
val title = it.name
|
||||
val path = it.absolutePath
|
||||
val timestamp = (it.lastModified() / 1000).toInt()
|
||||
val duration = context.getDuration(it.absolutePath) ?: 0
|
||||
val size = it.length().toInt()
|
||||
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(uri: Uri): Long {
|
||||
return try {
|
||||
val retriever = MediaMetadataRetriever()
|
||||
retriever.setDataSource(context, uri)
|
||||
val time = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)!!
|
||||
(time.toLong() / 1000.toDouble()).roundToLong()
|
||||
} catch (e: Exception) {
|
||||
0L
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSizeFromUri(id: Long): Int {
|
||||
val recordingUri = getAudioFileContentUri(id)
|
||||
return try {
|
||||
context.contentResolver.openInputStream(recordingUri)?.available() ?: 0
|
||||
} catch (e: Exception) {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
private fun initMediaPlayer() {
|
||||
player = MediaPlayer().apply {
|
||||
setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK)
|
||||
|
@ -306,9 +193,9 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
|
|||
|
||||
setOnCompletionListener {
|
||||
progressTimer.cancel()
|
||||
player_progressbar.progress = player_progressbar.max
|
||||
player_progress_current.text = player_progress_max.text
|
||||
play_pause_btn.setImageDrawable(getToggleButtonIcon(false))
|
||||
binding.playerProgressbar.progress = binding.playerProgressbar.max
|
||||
binding.playerProgressCurrent.text = binding.playerProgressMax.text
|
||||
binding.playPauseBtn.setImageDrawable(getToggleButtonIcon(false))
|
||||
}
|
||||
|
||||
setOnPreparedListener {
|
||||
|
@ -324,7 +211,7 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
|
|||
|
||||
override fun playRecording(recording: Recording, playOnPrepared: Boolean) {
|
||||
resetProgress(recording)
|
||||
(recordings_list.adapter as RecordingsAdapter).updateCurrentRecording(recording.id)
|
||||
(binding.recordingsList.adapter as RecordingsAdapter).updateCurrentRecording(recording.id)
|
||||
playOnPreparation = playOnPrepared
|
||||
|
||||
player!!.apply {
|
||||
|
@ -336,9 +223,11 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
|
|||
DocumentsContract.isDocumentUri(context, uri) -> {
|
||||
setDataSource(context, uri)
|
||||
}
|
||||
|
||||
recording.path.isEmpty() -> {
|
||||
setDataSource(context, getAudioFileContentUri(recording.id.toLong()))
|
||||
}
|
||||
|
||||
else -> {
|
||||
setDataSource(recording.path)
|
||||
}
|
||||
|
@ -356,12 +245,12 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
|
|||
}
|
||||
}
|
||||
|
||||
play_pause_btn.setImageDrawable(getToggleButtonIcon(playOnPreparation))
|
||||
player_progressbar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
||||
binding.playPauseBtn.setImageDrawable(getToggleButtonIcon(playOnPreparation))
|
||||
binding.playerProgressbar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
||||
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||
if (fromUser && !playedRecordingIDs.isEmpty()) {
|
||||
player?.seekTo(progress * 1000)
|
||||
player_progress_current.text = progress.getFormattedDuration()
|
||||
binding.playerProgressCurrent.text = progress.getFormattedDuration()
|
||||
resumePlayback()
|
||||
}
|
||||
}
|
||||
|
@ -384,22 +273,22 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
|
|||
if (player != null) {
|
||||
val progress = Math.round(player!!.currentPosition / 1000.toDouble()).toInt()
|
||||
updateCurrentProgress(progress)
|
||||
player_progressbar.progress = progress
|
||||
binding.playerProgressbar.progress = progress
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateCurrentProgress(seconds: Int) {
|
||||
player_progress_current.text = seconds.getFormattedDuration()
|
||||
binding.playerProgressCurrent.text = seconds.getFormattedDuration()
|
||||
}
|
||||
|
||||
private fun resetProgress(recording: Recording?) {
|
||||
updateCurrentProgress(0)
|
||||
player_progressbar.progress = 0
|
||||
player_progressbar.max = recording?.duration ?: 0
|
||||
player_title.text = recording?.title ?: ""
|
||||
player_progress_max.text = (recording?.duration ?: 0).getFormattedDuration()
|
||||
binding.playerProgressbar.progress = 0
|
||||
binding.playerProgressbar.max = recording?.duration ?: 0
|
||||
binding.playerTitle.text = recording?.title ?: ""
|
||||
binding.playerProgressMax.text = (recording?.duration ?: 0).getFormattedDuration()
|
||||
}
|
||||
|
||||
fun onSearchTextChanged(text: String) {
|
||||
|
@ -418,18 +307,18 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
|
|||
|
||||
private fun pausePlayback() {
|
||||
player?.pause()
|
||||
play_pause_btn.setImageDrawable(getToggleButtonIcon(false))
|
||||
binding.playPauseBtn.setImageDrawable(getToggleButtonIcon(false))
|
||||
progressTimer.cancel()
|
||||
}
|
||||
|
||||
private fun resumePlayback() {
|
||||
player?.start()
|
||||
play_pause_btn.setImageDrawable(getToggleButtonIcon(true))
|
||||
binding.playPauseBtn.setImageDrawable(getToggleButtonIcon(true))
|
||||
setupProgressTimer()
|
||||
}
|
||||
|
||||
private fun getToggleButtonIcon(isPlaying: Boolean): Drawable {
|
||||
val drawable = if (isPlaying) R.drawable.ic_pause_vector else R.drawable.ic_play_vector
|
||||
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, context.getProperPrimaryColor().getContrastColor())
|
||||
}
|
||||
|
||||
|
@ -450,24 +339,25 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
|
|||
|
||||
private fun getIsPlaying() = player?.isPlaying == true
|
||||
|
||||
private fun getRecordingsAdapter() = recordings_list.adapter as? RecordingsAdapter
|
||||
private fun getRecordingsAdapter() = binding.recordingsList.adapter as? RecordingsAdapter
|
||||
|
||||
private fun storePrevPath() {
|
||||
private fun storePrevState() {
|
||||
prevSavePath = context!!.config.saveRecordingsFolder
|
||||
prevRecycleBinState = context.config.useRecycleBin
|
||||
}
|
||||
|
||||
private fun setupColors() {
|
||||
val properPrimaryColor = context.getProperPrimaryColor()
|
||||
recordings_fastscroller.updateColors(properPrimaryColor)
|
||||
context.updateTextColors(player_holder)
|
||||
binding.recordingsFastscroller.updateColors(properPrimaryColor)
|
||||
context.updateTextColors(binding.playerHolder)
|
||||
|
||||
val textColor = context.getProperTextColor()
|
||||
arrayListOf(previous_btn, next_btn).forEach {
|
||||
arrayListOf(binding.previousBtn, binding.nextBtn).forEach {
|
||||
it.applyColorFilter(textColor)
|
||||
}
|
||||
|
||||
play_pause_btn.background.applyColorFilter(properPrimaryColor)
|
||||
play_pause_btn.setImageDrawable(getToggleButtonIcon(false))
|
||||
binding.playPauseBtn.background.applyColorFilter(properPrimaryColor)
|
||||
binding.playPauseBtn.setImageDrawable(getToggleButtonIcon(false))
|
||||
}
|
||||
|
||||
fun finishActMode() = getRecordingsAdapter()?.finishActMode()
|
||||
|
@ -476,4 +366,9 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
|
|||
fun recordingCompleted(event: Events.RecordingCompleted) {
|
||||
refreshRecordings()
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun recordingMovedToRecycleBin(event: Events.RecordingTrashUpdated) {
|
||||
refreshRecordings()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,20 +10,26 @@ import com.simplemobiletools.commons.activities.BaseSimpleActivity
|
|||
import com.simplemobiletools.commons.dialogs.PermissionRequiredDialog
|
||||
import com.simplemobiletools.commons.extensions.*
|
||||
import com.simplemobiletools.commons.helpers.isNougatPlus
|
||||
import com.simplemobiletools.voicerecorder.R
|
||||
import com.simplemobiletools.voicerecorder.databinding.FragmentRecorderBinding
|
||||
import com.simplemobiletools.voicerecorder.helpers.*
|
||||
import com.simplemobiletools.voicerecorder.models.Events
|
||||
import com.simplemobiletools.voicerecorder.services.RecorderService
|
||||
import kotlinx.android.synthetic.main.fragment_recorder.view.*
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import java.util.*
|
||||
import java.util.Timer
|
||||
import java.util.TimerTask
|
||||
|
||||
class RecorderFragment(context: Context, attributeSet: AttributeSet) : MyViewPagerFragment(context, attributeSet) {
|
||||
private var status = RECORDING_STOPPED
|
||||
private var pauseBlinkTimer = Timer()
|
||||
private var bus: EventBus? = null
|
||||
private lateinit var binding: FragmentRecorderBinding
|
||||
|
||||
override fun onFinishInflate() {
|
||||
super.onFinishInflate()
|
||||
binding = FragmentRecorderBinding.bind(this)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
setupColors()
|
||||
|
@ -42,22 +48,24 @@ class RecorderFragment(context: Context, attributeSet: AttributeSet) : MyViewPag
|
|||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
setupColors()
|
||||
recorder_visualizer.recreate()
|
||||
binding.recorderVisualizer.recreate()
|
||||
bus = EventBus.getDefault()
|
||||
bus!!.register(this)
|
||||
|
||||
updateRecordingDuration(0)
|
||||
toggle_recording_button.setOnClickListener {
|
||||
binding.toggleRecordingButton.setOnClickListener {
|
||||
(context as? BaseSimpleActivity)?.handleNotificationPermission { granted ->
|
||||
if (granted) {
|
||||
toggleRecording()
|
||||
} else {
|
||||
PermissionRequiredDialog(context as BaseSimpleActivity, R.string.allow_notifications_voice_recorder)
|
||||
PermissionRequiredDialog(context as BaseSimpleActivity, com.simplemobiletools.commons.R.string.allow_notifications_voice_recorder, {
|
||||
(context as BaseSimpleActivity).openNotificationSettings()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toggle_pause_button.setOnClickListener {
|
||||
binding.togglePauseButton.setOnClickListener {
|
||||
Intent(context, RecorderService::class.java).apply {
|
||||
action = TOGGLE_PAUSE
|
||||
context.startService(this)
|
||||
|
@ -75,26 +83,27 @@ class RecorderFragment(context: Context, attributeSet: AttributeSet) : MyViewPag
|
|||
|
||||
private fun setupColors() {
|
||||
val properPrimaryColor = context.getProperPrimaryColor()
|
||||
toggle_recording_button.apply {
|
||||
binding.toggleRecordingButton.apply {
|
||||
setImageDrawable(getToggleButtonIcon())
|
||||
background.applyColorFilter(properPrimaryColor)
|
||||
}
|
||||
|
||||
toggle_pause_button.apply {
|
||||
setImageDrawable(resources.getColoredDrawableWithColor(R.drawable.ic_pause_vector, properPrimaryColor.getContrastColor()))
|
||||
binding.togglePauseButton.apply {
|
||||
setImageDrawable(resources.getColoredDrawableWithColor(com.simplemobiletools.commons.R.drawable.ic_pause_vector, properPrimaryColor.getContrastColor()))
|
||||
background.applyColorFilter(properPrimaryColor)
|
||||
}
|
||||
|
||||
recorder_visualizer.chunkColor = properPrimaryColor
|
||||
recording_duration.setTextColor(context.getProperTextColor())
|
||||
binding.recorderVisualizer.chunkColor = properPrimaryColor
|
||||
binding.recordingDuration.setTextColor(context.getProperTextColor())
|
||||
}
|
||||
|
||||
private fun updateRecordingDuration(duration: Int) {
|
||||
recording_duration.text = duration.getFormattedDuration()
|
||||
binding.recordingDuration.text = duration.getFormattedDuration()
|
||||
}
|
||||
|
||||
private fun getToggleButtonIcon(): Drawable {
|
||||
val drawable = if (status == RECORDING_RUNNING || status == RECORDING_PAUSED) R.drawable.ic_stop_vector else R.drawable.ic_microphone_vector
|
||||
val drawable =
|
||||
if (status == RECORDING_RUNNING || status == RECORDING_PAUSED) com.simplemobiletools.commons.R.drawable.ic_stop_vector else com.simplemobiletools.commons.R.drawable.ic_microphone_vector
|
||||
return resources.getColoredDrawableWithColor(drawable, context.getProperPrimaryColor().getContrastColor())
|
||||
}
|
||||
|
||||
|
@ -105,12 +114,12 @@ class RecorderFragment(context: Context, attributeSet: AttributeSet) : MyViewPag
|
|||
RECORDING_RUNNING
|
||||
}
|
||||
|
||||
toggle_recording_button.setImageDrawable(getToggleButtonIcon())
|
||||
binding.toggleRecordingButton.setImageDrawable(getToggleButtonIcon())
|
||||
|
||||
if (status == RECORDING_RUNNING) {
|
||||
startRecording()
|
||||
} else {
|
||||
toggle_pause_button.beGone()
|
||||
binding.togglePauseButton.beGone()
|
||||
stopRecording()
|
||||
}
|
||||
}
|
||||
|
@ -119,7 +128,7 @@ class RecorderFragment(context: Context, attributeSet: AttributeSet) : MyViewPag
|
|||
Intent(context, RecorderService::class.java).apply {
|
||||
context.startService(this)
|
||||
}
|
||||
recorder_visualizer.recreate()
|
||||
binding.recorderVisualizer.recreate()
|
||||
}
|
||||
|
||||
private fun stopRecording() {
|
||||
|
@ -133,15 +142,15 @@ class RecorderFragment(context: Context, attributeSet: AttributeSet) : MyViewPag
|
|||
if (status == RECORDING_PAUSED) {
|
||||
// update just the alpha so that it will always be clickable
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
toggle_pause_button.alpha = if (toggle_pause_button.alpha == 0f) 1f else 0f
|
||||
binding.togglePauseButton.alpha = if (binding.togglePauseButton.alpha == 0f) 1f else 0f
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshView() {
|
||||
toggle_recording_button.setImageDrawable(getToggleButtonIcon())
|
||||
toggle_pause_button.beVisibleIf(status != RECORDING_STOPPED && isNougatPlus())
|
||||
binding.toggleRecordingButton.setImageDrawable(getToggleButtonIcon())
|
||||
binding.togglePauseButton.beVisibleIf(status != RECORDING_STOPPED && isNougatPlus())
|
||||
pauseBlinkTimer.cancel()
|
||||
|
||||
if (status == RECORDING_PAUSED) {
|
||||
|
@ -150,7 +159,7 @@ class RecorderFragment(context: Context, attributeSet: AttributeSet) : MyViewPag
|
|||
}
|
||||
|
||||
if (status == RECORDING_RUNNING) {
|
||||
toggle_pause_button.alpha = 1f
|
||||
binding.togglePauseButton.alpha = 1f
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -169,7 +178,7 @@ class RecorderFragment(context: Context, attributeSet: AttributeSet) : MyViewPag
|
|||
fun gotAmplitudeEvent(event: Events.RecordingAmplitude) {
|
||||
val amplitude = event.amplitude
|
||||
if (status == RECORDING_RUNNING) {
|
||||
recorder_visualizer.update(amplitude)
|
||||
binding.recorderVisualizer.update(amplitude)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
package com.simplemobiletools.voicerecorder.fragments
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import com.simplemobiletools.commons.extensions.*
|
||||
import com.simplemobiletools.voicerecorder.activities.SimpleActivity
|
||||
import com.simplemobiletools.voicerecorder.adapters.TrashAdapter
|
||||
import com.simplemobiletools.voicerecorder.databinding.FragmentTrashBinding
|
||||
import com.simplemobiletools.voicerecorder.extensions.config
|
||||
import com.simplemobiletools.voicerecorder.extensions.getAllRecordings
|
||||
import com.simplemobiletools.voicerecorder.interfaces.RefreshRecordingsListener
|
||||
import com.simplemobiletools.voicerecorder.models.Events
|
||||
import com.simplemobiletools.voicerecorder.models.Recording
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
|
||||
class TrashFragment(context: Context, attributeSet: AttributeSet) : MyViewPagerFragment(context, attributeSet), RefreshRecordingsListener {
|
||||
private var itemsIgnoringSearch = ArrayList<Recording>()
|
||||
private var lastSearchQuery = ""
|
||||
private var bus: EventBus? = null
|
||||
private var prevSavePath = ""
|
||||
private lateinit var binding: FragmentTrashBinding
|
||||
|
||||
override fun onFinishInflate() {
|
||||
super.onFinishInflate()
|
||||
binding = FragmentTrashBinding.bind(this)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
setupColors()
|
||||
if (prevSavePath.isNotEmpty() && context!!.config.saveRecordingsFolder != prevSavePath) {
|
||||
itemsIgnoringSearch = getRecordings()
|
||||
setupAdapter(itemsIgnoringSearch)
|
||||
} else {
|
||||
getRecordingsAdapter()?.updateTextColor(context.getProperTextColor())
|
||||
}
|
||||
|
||||
storePrevPath()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
bus?.unregister(this)
|
||||
}
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
|
||||
bus = EventBus.getDefault()
|
||||
bus!!.register(this)
|
||||
setupColors()
|
||||
itemsIgnoringSearch = getRecordings()
|
||||
setupAdapter(itemsIgnoringSearch)
|
||||
storePrevPath()
|
||||
}
|
||||
|
||||
override fun refreshRecordings() {
|
||||
itemsIgnoringSearch = getRecordings()
|
||||
setupAdapter(itemsIgnoringSearch)
|
||||
}
|
||||
|
||||
override fun playRecording(recording: Recording, playOnPrepared: Boolean) {}
|
||||
|
||||
private fun setupAdapter(recordings: ArrayList<Recording>) {
|
||||
binding.trashFastscroller.beVisibleIf(recordings.isNotEmpty())
|
||||
binding.trashPlaceholder.beVisibleIf(recordings.isEmpty())
|
||||
if (recordings.isEmpty()) {
|
||||
val stringId = if (lastSearchQuery.isEmpty()) {
|
||||
com.simplemobiletools.commons.R.string.recycle_bin_empty
|
||||
} else {
|
||||
com.simplemobiletools.commons.R.string.no_items_found
|
||||
}
|
||||
|
||||
binding.trashPlaceholder.text = context.getString(stringId)
|
||||
}
|
||||
|
||||
val adapter = getRecordingsAdapter()
|
||||
if (adapter == null) {
|
||||
TrashAdapter(context as SimpleActivity, recordings, this, binding.trashList).apply {
|
||||
binding.trashList.adapter = this
|
||||
}
|
||||
|
||||
if (context.areSystemAnimationsEnabled) {
|
||||
binding.trashList.scheduleLayoutAnimation()
|
||||
}
|
||||
} else {
|
||||
adapter.updateItems(recordings)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getRecordings(): ArrayList<Recording> {
|
||||
return context.getAllRecordings(trashed = true).apply {
|
||||
sortByDescending { it.timestamp }
|
||||
}
|
||||
}
|
||||
|
||||
fun onSearchTextChanged(text: String) {
|
||||
lastSearchQuery = text
|
||||
val filtered = itemsIgnoringSearch.filter { it.title.contains(text, true) }.toMutableList() as ArrayList<Recording>
|
||||
setupAdapter(filtered)
|
||||
}
|
||||
|
||||
private fun getRecordingsAdapter() = binding.trashList.adapter as? TrashAdapter
|
||||
|
||||
private fun storePrevPath() {
|
||||
prevSavePath = context!!.config.saveRecordingsFolder
|
||||
}
|
||||
|
||||
private fun setupColors() {
|
||||
val properPrimaryColor = context.getProperPrimaryColor()
|
||||
binding.trashFastscroller.updateColors(properPrimaryColor)
|
||||
context.updateTextColors(binding.trashHolder)
|
||||
}
|
||||
|
||||
fun finishActMode() = getRecordingsAdapter()?.finishActMode()
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun recordingMovedToRecycleBin(event: Events.RecordingTrashUpdated) {
|
||||
refreshRecordings()
|
||||
}
|
||||
}
|
|
@ -72,4 +72,12 @@ class Config(context: Context) : BaseConfig(context) {
|
|||
EXTENSION_OGG -> MediaRecorder.AudioEncoder.OPUS
|
||||
else -> MediaRecorder.AudioEncoder.AAC
|
||||
}
|
||||
|
||||
var useRecycleBin: Boolean
|
||||
get() = prefs.getBoolean(USE_RECYCLE_BIN, true)
|
||||
set(useRecycleBin) = prefs.edit().putBoolean(USE_RECYCLE_BIN, useRecycleBin).apply()
|
||||
|
||||
var lastRecycleBinCheck: Long
|
||||
get() = prefs.getLong(LAST_RECYCLE_BIN_CHECK, 0L)
|
||||
set(lastRecycleBinCheck) = prefs.edit().putLong(LAST_RECYCLE_BIN_CHECK, lastRecycleBinCheck).apply()
|
||||
}
|
||||
|
|
|
@ -36,6 +36,8 @@ const val EXTENSION = "extension"
|
|||
const val AUDIO_SOURCE = "audio_source"
|
||||
const val BITRATE = "bitrate"
|
||||
const val RECORD_AFTER_LAUNCH = "record_after_launch"
|
||||
const val USE_RECYCLE_BIN = "use_recycle_bin"
|
||||
const val LAST_RECYCLE_BIN_CHECK = "last_recycle_bin_check"
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
fun getAudioFileContentUri(id: Long): Uri {
|
||||
|
|
|
@ -57,7 +57,7 @@ class MyWidgetRecordDisplayProvider : AppWidgetProvider() {
|
|||
}
|
||||
|
||||
private fun getColoredIcon(context: Context, color: Int, alpha: Int): Bitmap {
|
||||
val drawable = context.resources.getColoredDrawableWithColor(R.drawable.ic_microphone_vector, color, alpha)
|
||||
val drawable = context.resources.getColoredDrawableWithColor(com.simplemobiletools.commons.R.drawable.ic_microphone_vector, color, alpha)
|
||||
return context.drawableToBitmap(drawable)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,5 +7,6 @@ class Events {
|
|||
class RecordingStatus internal constructor(val status: Int)
|
||||
class RecordingAmplitude internal constructor(val amplitude: Int)
|
||||
class RecordingCompleted internal constructor()
|
||||
class RecordingTrashUpdated internal constructor()
|
||||
class RecordingSaved internal constructor(val uri: Uri?)
|
||||
}
|
||||
|
|
|
@ -195,7 +195,7 @@ class RecorderService : Service() {
|
|||
|
||||
val newUri = contentResolver.insert(audioCollection, newSongDetails)
|
||||
if (newUri == null) {
|
||||
toast(R.string.unknown_error_occurred)
|
||||
toast(com.simplemobiletools.commons.R.string.unknown_error_occurred)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -257,7 +257,7 @@ class RecorderService : Service() {
|
|||
}
|
||||
|
||||
var priority = Notification.PRIORITY_DEFAULT
|
||||
var icon = R.drawable.ic_microphone_vector
|
||||
var icon = com.simplemobiletools.commons.R.drawable.ic_microphone_vector
|
||||
var title = label
|
||||
var visibility = NotificationCompat.VISIBILITY_PUBLIC
|
||||
var text = getString(R.string.recording)
|
||||
|
@ -273,7 +273,7 @@ class RecorderService : Service() {
|
|||
visibility = NotificationCompat.VISIBILITY_SECRET
|
||||
}
|
||||
|
||||
val builder = NotificationCompat.Builder(this)
|
||||
val builder = NotificationCompat.Builder(this, channelId)
|
||||
.setContentTitle(title)
|
||||
.setContentText(text)
|
||||
.setSmallIcon(icon)
|
||||
|
@ -283,7 +283,6 @@ class RecorderService : Service() {
|
|||
.setSound(null)
|
||||
.setOngoing(true)
|
||||
.setAutoCancel(true)
|
||||
.setChannelId(channelId)
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
|
|
@ -268,6 +268,55 @@
|
|||
tools:text="128 kbps" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<include
|
||||
android:id="@+id/settings_general_settings_divider"
|
||||
layout="@layout/divider" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/settings_recycle_bin_label"
|
||||
style="@style/SettingsSectionLabelStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/recycle_bin" />
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/settings_use_recycle_bin_holder"
|
||||
style="@style/SettingsHolderCheckboxStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.simplemobiletools.commons.views.MyAppCompatCheckbox
|
||||
android:id="@+id/settings_use_recycle_bin"
|
||||
style="@style/SettingsCheckboxStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/move_items_into_recycle_bin" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/settings_empty_recycle_bin_holder"
|
||||
style="@style/SettingsHolderTextViewStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.simplemobiletools.commons.views.MyTextView
|
||||
android:id="@+id/settings_empty_recycle_bin_label"
|
||||
style="@style/SettingsTextLabelStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/empty_recycle_bin" />
|
||||
|
||||
<com.simplemobiletools.commons.views.MyTextView
|
||||
android:id="@+id/settings_empty_recycle_bin_size"
|
||||
style="@style/SettingsTextValueStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/settings_empty_recycle_bin_label"
|
||||
tools:text="0 B" />
|
||||
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/delete_remember_holder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="@dimen/big_margin"
|
||||
android:paddingTop="@dimen/big_margin"
|
||||
android:paddingRight="@dimen/big_margin">
|
||||
|
||||
<com.simplemobiletools.commons.views.MyTextView
|
||||
android:id="@+id/delete_remember_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="@dimen/small_margin"
|
||||
android:paddingBottom="@dimen/activity_margin"
|
||||
android:text="@string/delete_recordings_confirmation"
|
||||
android:textSize="@dimen/bigger_text_size" />
|
||||
|
||||
<com.simplemobiletools.commons.views.MyAppCompatCheckbox
|
||||
android:id="@+id/skip_the_recycle_bin_checkbox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/delete_remember_title"
|
||||
android:text="@string/skip_the_recycle_bin" />
|
||||
|
||||
</RelativeLayout>
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.simplemobiletools.voicerecorder.fragments.TrashFragment xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/trash_holder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.simplemobiletools.commons.views.MyTextView
|
||||
android:id="@+id/trash_placeholder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:alpha="0.8"
|
||||
android:gravity="center"
|
||||
android:lineSpacingExtra="@dimen/small_margin"
|
||||
android:padding="@dimen/activity_margin"
|
||||
android:text="@string/recycle_bin_empty"
|
||||
android:textSize="@dimen/bigger_text_size"
|
||||
android:textStyle="italic"
|
||||
android:visibility="visible" />
|
||||
|
||||
<com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller
|
||||
android:id="@+id/trash_fastscroller"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.simplemobiletools.commons.views.MyRecyclerView
|
||||
android:id="@+id/trash_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:layoutAnimation="@anim/layout_animation"
|
||||
android:scrollbars="none"
|
||||
app:layoutManager="com.simplemobiletools.commons.views.MyLinearLayoutManager" />
|
||||
|
||||
</com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller>
|
||||
</com.simplemobiletools.voicerecorder.fragments.TrashFragment>
|
|
@ -0,0 +1,21 @@
|
|||
<?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="AppCompatResource,AlwaysShowAction">
|
||||
<item
|
||||
android:id="@+id/cab_delete"
|
||||
android:icon="@drawable/ic_delete_vector"
|
||||
android:showAsAction="always"
|
||||
android:title="@string/delete" />
|
||||
<item
|
||||
android:id="@+id/cab_restore"
|
||||
android:showAsAction="never"
|
||||
android:title="@string/restore_selected_files"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/cab_select_all"
|
||||
android:icon="@drawable/ic_select_all_vector"
|
||||
android:title="@string/select_all"
|
||||
app:showAsAction="ifRoom" />
|
||||
</menu>
|
|
@ -21,17 +21,17 @@
|
|||
<!-- Settings -->
|
||||
<string name="try_hiding_notification">Опитайте да скриете известието за запис</string>
|
||||
<string name="save_recordings_in">Запазете записите в</string>
|
||||
<string name="audio_source">Audio source</string>
|
||||
<string name="audio_source">Аудио източник</string>
|
||||
<string name="bitrate">Битрейт</string>
|
||||
<string name="record_after_launch">Започнете да записвате автоматично след стартиране на приложението</string>
|
||||
<!-- Settings Audio source selection -->
|
||||
<string name="audio_source_camcorder">Camera</string>
|
||||
<string name="audio_source_default">Android default</string>
|
||||
<string name="audio_source_unprocessed">Unprocessed</string>
|
||||
<string name="audio_source_microphone">Microphone</string>
|
||||
<string name="audio_source_voice_recognition">Voice recognition</string>
|
||||
<string name="audio_source_voice_communication">Voice communication</string>
|
||||
<string name="audio_source_voice_performance">Voice performance</string>
|
||||
<string name="audio_source_camcorder">Камера</string>
|
||||
<string name="audio_source_default">Android по подразбиране</string>
|
||||
<string name="audio_source_unprocessed">Необработени</string>
|
||||
<string name="audio_source_microphone">Микрофон</string>
|
||||
<string name="audio_source_voice_recognition">Гласово разпознаване</string>
|
||||
<string name="audio_source_voice_communication">Гласова комуникация</string>
|
||||
<string name="audio_source_voice_performance">Гласово изпълнение</string>
|
||||
<!-- FAQ -->
|
||||
<string name="faq_1_title">Мога ли да скрия иконата за уведомяване по време на запис\?</string>
|
||||
<string name="faq_1_text">Е, зависи. Докато използвате устройството си, вече не е възможно да скриете напълно известията на подобни приложения. Ако поставите отметка на съответния елемент от настройката, приложението ще направи всичко възможно да го скрие. Все пак можете да го скриете на заключения екран, ако забраните показването на чувствителни известия в настройките на устройството си.</string>
|
||||
|
@ -39,4 +39,4 @@
|
|||
Haven't found some strings? There's more at
|
||||
https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res
|
||||
-->
|
||||
</resources>
|
||||
</resources>
|
|
@ -4,10 +4,9 @@
|
|||
<string name="app_launcher_name">Grabadora de voz</string>
|
||||
<string name="recording_saved_successfully">Grabación guardada con éxito</string>
|
||||
<string name="recording">Grabando</string>
|
||||
<string name="no_recordings_found">No se han encontrado grabaciones creadas por esta aplicación
|
||||
<string name="no_recordings_found">No se han encontrado grabaciones
|
||||
\nse han encontrado</string>
|
||||
<string name="no_recordings_in_folder_found">No se han encontrado grabaciones
|
||||
\nen la carpeta seleccionada</string>
|
||||
<string name="no_recordings_in_folder_found">No se han encontrado grabaciones\nen la carpeta seleccionada</string>
|
||||
<string name="paused">En pausa</string>
|
||||
<string name="recorder">Grabación</string>
|
||||
<string name="player">Reproductor</string>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<string name="no_recordings_in_folder_found">Valitud kaustas ei leidu
|
||||
\nühtegi salvestust</string>
|
||||
<string name="paused">Peatatud</string>
|
||||
<string name="recorder">Salvesti</string>
|
||||
<string name="recorder">Salvestaja</string>
|
||||
<string name="player">Mängija</string>
|
||||
<!-- Confirmation dialog -->
|
||||
<!-- Are you sure you want to delete 5 recordings? -->
|
||||
|
@ -26,7 +26,7 @@
|
|||
<string name="record_after_launch">Rakenduse käivitamisel alusta kohe salvestamist</string>
|
||||
<!-- Settings Audio source selection -->
|
||||
<string name="audio_source_camcorder">Kaamera</string>
|
||||
<string name="audio_source_default">Vaikimisi Androidi audio</string>
|
||||
<string name="audio_source_default">Vaikimisi Androidi helisisend</string>
|
||||
<string name="audio_source_unprocessed">Töötlemata</string>
|
||||
<string name="audio_source_microphone">Mikrofon</string>
|
||||
<string name="audio_source_voice_recognition">Häältuvastus</string>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<resources>
|
||||
<string name="app_name">Enregistreur vocal simple</string>
|
||||
<string name="app_launcher_name">Enregistreur vocal</string>
|
||||
<string name="recording_saved_successfully">Enregistrement sauvegardé avec succès</string>
|
||||
<string name="recording_saved_successfully">Sauvegarde de l\'enregistrement réussie</string>
|
||||
<string name="recording">Enregistrement</string>
|
||||
<string name="no_recordings_found">Aucun enregistrement créé par cette application
|
||||
\nn’a été trouvé</string>
|
||||
|
@ -26,7 +26,7 @@
|
|||
<string name="bitrate">Débit binaire</string>
|
||||
<string name="record_after_launch">Démarrer l’enregistrement automatiquement après le lancement de l’application</string>
|
||||
<!-- Settings Audio source selection -->
|
||||
<string name="audio_source_camcorder">Caméra</string>
|
||||
<string name="audio_source_camcorder">Objectif vidéo</string>
|
||||
<string name="audio_source_default">Par défaut Android</string>
|
||||
<string name="audio_source_unprocessed">Non traitée</string>
|
||||
<string name="audio_source_microphone">Microphone</string>
|
||||
|
|
|
@ -4,13 +4,13 @@
|
|||
<string name="app_launcher_name">Voicerecorder</string>
|
||||
<string name="recording_saved_successfully">Opname is opgeslagen</string>
|
||||
<string name="recording">Opnemen</string>
|
||||
<string name="no_recordings_found">Geen opnames gemaakt door
|
||||
\ndeze app gevonden</string>
|
||||
<string name="no_recordings_found">Geen opnames gevonden
|
||||
\ndie zijn gemaakt door deze app</string>
|
||||
<string name="no_recordings_in_folder_found">Geen opnames gevonden
|
||||
\nin de gekozen map</string>
|
||||
<string name="paused">Gepauzeerd</string>
|
||||
<string name="recorder">Recorder</string>
|
||||
<string name="player">Speler</string>
|
||||
<string name="recorder">Opnemen</string>
|
||||
<string name="player">Afspelen</string>
|
||||
<!-- Confirmation dialog -->
|
||||
<string name="delete_recordings_confirmation">%s verwijderen\?</string>
|
||||
<!-- Are you sure you want to delete 5 recordings? -->
|
||||
|
@ -19,19 +19,19 @@
|
|||
<item quantity="other">%d opnames</item>
|
||||
</plurals>
|
||||
<!-- Settings -->
|
||||
<string name="try_hiding_notification">Tijdens de opname proberen de notificatie te verbergen</string>
|
||||
<string name="try_hiding_notification">Tijdens opnames proberen de notificatie te verbergen</string>
|
||||
<string name="save_recordings_in">Opnames opslaan in</string>
|
||||
<string name="audio_source">Audio source</string>
|
||||
<string name="audio_source">Geluidsbron</string>
|
||||
<string name="bitrate">Bitrate</string>
|
||||
<string name="record_after_launch">Bij het starten van de app direct een opname beginnen</string>
|
||||
<!-- Settings Audio source selection -->
|
||||
<string name="audio_source_camcorder">Camera</string>
|
||||
<string name="audio_source_default">Android default</string>
|
||||
<string name="audio_source_unprocessed">Unprocessed</string>
|
||||
<string name="audio_source_microphone">Microphone</string>
|
||||
<string name="audio_source_voice_recognition">Voice recognition</string>
|
||||
<string name="audio_source_voice_communication">Voice communication</string>
|
||||
<string name="audio_source_voice_performance">Voice performance</string>
|
||||
<string name="audio_source_default">Android-standaard</string>
|
||||
<string name="audio_source_unprocessed">Onverwerkt</string>
|
||||
<string name="audio_source_microphone">Microfoon</string>
|
||||
<string name="audio_source_voice_recognition">Spraakherkenning</string>
|
||||
<string name="audio_source_voice_communication">Gesproken communicatie</string>
|
||||
<string name="audio_source_voice_performance">Optreden</string>
|
||||
<!-- FAQ -->
|
||||
<string name="faq_1_title">Kan ik de notificatie tijdens het opnemen verbergen\?</string>
|
||||
<string name="faq_1_text">Dat ligt eraan. Tijdens het gebruik is het niet langer mogelijk in Android om meldingen van dit soort apps volledig te verbergen. De app bevat wel een instelling waarmee er toch zal worden geprobeerd om dit te bewerkstelligen. In de schermvergrendeling kan de melding wél worden verborgen door het tonen van gevoelige notificaties in de systeeminstellingen uit te schakelen.</string>
|
||||
|
@ -39,4 +39,4 @@
|
|||
Haven't found some strings? There's more at
|
||||
https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res
|
||||
-->
|
||||
</resources>
|
||||
</resources>
|
|
@ -39,4 +39,4 @@
|
|||
Haven't found some strings? There's more at
|
||||
https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res
|
||||
-->
|
||||
</resources>
|
||||
</resources>
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
<string name="no_recordings_in_folder_found">В обраній теці, не знайдено
|
||||
\nжодного запису</string>
|
||||
<string name="paused">Пауза</string>
|
||||
<string name="recorder">Recorder</string>
|
||||
<string name="player">Грач</string>
|
||||
<string name="recorder">Диктофон</string>
|
||||
<string name="player">Програвач</string>
|
||||
<!-- Confirmation dialog -->
|
||||
<string name="delete_recordings_confirmation">Видалити %s\?</string>
|
||||
<!-- Are you sure you want to delete 5 recordings? -->
|
||||
|
@ -24,7 +24,7 @@
|
|||
<string name="try_hiding_notification">Намагатися приховати сповіщення про запис</string>
|
||||
<string name="save_recordings_in">Місце зберігання записів</string>
|
||||
<string name="audio_source">Джерело звуку</string>
|
||||
<string name="bitrate">Bitrate</string>
|
||||
<string name="bitrate">Бітова швидкість</string>
|
||||
<string name="record_after_launch">Автоматично розпочинати запис після запуску програми</string>
|
||||
<!-- Settings Audio source selection -->
|
||||
<string name="audio_source_camcorder">Камера</string>
|
||||
|
@ -41,4 +41,4 @@
|
|||
Haven't found some strings? There's more at
|
||||
https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res
|
||||
-->
|
||||
</resources>
|
||||
</resources>
|
||||
|
|
|
@ -20,17 +20,17 @@
|
|||
<!-- Settings -->
|
||||
<string name="try_hiding_notification">儘量隱藏錄音通知</string>
|
||||
<string name="save_recordings_in">錄音儲存至</string>
|
||||
<string name="audio_source">Audio source</string>
|
||||
<string name="audio_source">音訊來源</string>
|
||||
<string name="bitrate">位元率</string>
|
||||
<string name="record_after_launch">啟動 app 後自動開始錄音</string>
|
||||
<!-- Settings Audio source selection -->
|
||||
<string name="audio_source_camcorder">Camera</string>
|
||||
<string name="audio_source_default">Android default</string>
|
||||
<string name="audio_source_unprocessed">Unprocessed</string>
|
||||
<string name="audio_source_microphone">Microphone</string>
|
||||
<string name="audio_source_voice_recognition">Voice recognition</string>
|
||||
<string name="audio_source_voice_communication">Voice communication</string>
|
||||
<string name="audio_source_voice_performance">Voice performance</string>
|
||||
<string name="audio_source_camcorder">相機</string>
|
||||
<string name="audio_source_default">Android 預設</string>
|
||||
<string name="audio_source_unprocessed">未經處理</string>
|
||||
<string name="audio_source_microphone">麥克風</string>
|
||||
<string name="audio_source_voice_recognition">語音辨識</string>
|
||||
<string name="audio_source_voice_communication">語音通訊</string>
|
||||
<string name="audio_source_voice_performance">聲音表現</string>
|
||||
<!-- FAQ -->
|
||||
<string name="faq_1_title">有辦法隱藏錄音時出現的通知圖示嗎?</string>
|
||||
<string name="faq_1_text">嗯……看情況。當您正在使用裝置時,沒辦法完全隱藏這類 app 的通知。若您有開啟相應的選項,本 app 會盡其所能來隱藏。不過鎖定畫面上的可以隱藏,只要在裝置的設定中停用「敏感通知」即可。</string>
|
||||
|
@ -38,4 +38,4 @@
|
|||
Haven't found some strings? There's more at
|
||||
https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res
|
||||
-->
|
||||
</resources>
|
||||
</resources>
|
29
build.gradle
29
build.gradle
|
@ -1,29 +0,0 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.7.10'
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.3.1'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
maven { url "https://jitpack.io" }
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
plugins {
|
||||
alias(libs.plugins.android).apply(false)
|
||||
alias(libs.plugins.kotlinAndroid).apply(false)
|
||||
alias(libs.plugins.ksp).apply(false)
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
* Added a Recycle bin
|
||||
* Added some stability, UI and translation improvements
|
|
@ -0,0 +1 @@
|
|||
* Added some stability, UI and translation improvements
|
|
@ -0,0 +1 @@
|
|||
* Added some stability, UI and translation improvements
|
|
@ -0,0 +1,2 @@
|
|||
* Fixed a glitch with inability to change save folder
|
||||
* Added some stability, UI and translation improvements
|
|
@ -1 +1 @@
|
|||
Grabadora de Voz Sencilla
|
||||
Grabadora de Voz Simple
|
||||
|
|
|
@ -1 +1 @@
|
|||
Salvestage selle lihtsa helisalvesti abil hõlpsalt heli või looge memosid
|
||||
Salvesta selle lihtsa helisalvesti abil hõlpsalt heli või koosta audiomemosid
|
||||
|
|
|
@ -1 +1 @@
|
|||
Simpel röstinspelare
|
||||
Simple Voice Recorder
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
🎙 您是否曾希望能記住對方所說的話?您的願望現在成真了!透過這款簡易語音錄音器,您可以輕鬆地錄製高品質的音訊和其他語音備忘錄!
|
||||
|
||||
這款錄音應用程式讓您可以錄製不同的音訊。您可以將這款音訊錄音器和語音錄音器當作音樂錄音室使用。這款錄音應用程式讓您自由地錄製周遭發生的每一件事情,並稍後進行視覺化。
|
||||
|
||||
這款免費應用程式直截了當,不含您不會使用的花俏功能。只有您和語音錄音器或音訊錄音器。它以清晰直觀的用戶界面呈現目前的聲音音量,您可以在其中享受很多樂趣。提供極為直覺且整潔的用戶界面,不太容易出錯。您還可以從這款錄音應用程式中製作音訊筆記或保存語音備忘錄以供稍後收聽。這款錄音應用程式還提供了一個獨特且簡單的音樂錄音室功能,您可以將其作為音訊錄音器和音樂錄音器。
|
||||
|
||||
此錄音應用程式還提供了一個實用的音訊錄音器和語音錄音器播放器,讓您可以在錄音應用程式中快速收聽您的錄音,也可以重新命名或刪除它們。您可以自定顯示的日期和時間格式,以提供更好的用戶體驗。
|
||||
|
||||
您可以選擇在錄音期間隱藏頂部通知,以保護您的隱私。它還提供了一個實用且可自定義的小工具,用於快速錄音。這款語音錄音器讓您可以自由地按照自己的需求使用此應用程式。
|
||||
|
||||
它預設採用物料設計和暗黑主題,為簡單使用提供出色的用戶體驗。相比其他應用程式,由於無需網路連接,它具有更高的隱私、安全性和穩定性。
|
||||
|
||||
不包含廣告或不必要的權限。它是完全開源的,提供可自定義的顏色。
|
||||
|
||||
在此處查看完整的 Simple Tools 套件:
|
||||
https://www.simplemobiletools.com
|
||||
|
||||
Facebook:
|
||||
https://www.facebook.com/simplemobiletools
|
||||
|
||||
Reddit:
|
||||
https://www.reddit.com/r/SimpleMobileTools
|
||||
|
||||
Telegram:
|
||||
https://t.me/SimpleMobileTools
|
|
@ -0,0 +1 @@
|
|||
輕鬆使用這款簡易語音錄音器,錄製任何音訊或創建備忘錄
|
|
@ -0,0 +1 @@
|
|||
簡易語音錄音器
|
|
@ -19,3 +19,4 @@ android.useAndroidX=true
|
|||
android.enableJetifier=true
|
||||
# Kotlin code style for this project: "official" or "obsolete":
|
||||
kotlin.code.style=official
|
||||
android.nonTransitiveRClass=true
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
[versions]
|
||||
#jetbrains
|
||||
kotlin = "1.9.0"
|
||||
#KSP
|
||||
ksp = "1.9.0-1.0.12"
|
||||
#AndroidX
|
||||
androidx-constraintlayout = "2.1.4"
|
||||
androidx-documentfile = "1.0.1"
|
||||
androidx-swiperefreshlayout = "1.1.0"
|
||||
#Eventbus
|
||||
eventbus = "3.3.1"
|
||||
#Room
|
||||
room = "2.6.0-alpha02"
|
||||
#Simple tools
|
||||
simple-commons = "8e7dd4508e"
|
||||
#AudioRecordView
|
||||
audiorecordview = "1.0.4"
|
||||
#TAndroidLame
|
||||
tandroidlame = "1.1"
|
||||
#AutofitTextView
|
||||
autofittextview = "0.2.1"
|
||||
#Gradle
|
||||
gradlePlugins-agp = "8.1.1"
|
||||
#build
|
||||
app-build-compileSDKVersion = "34"
|
||||
app-build-targetSDK = "34"
|
||||
app-build-minimumSDK = "23"
|
||||
app-build-javaVersion = "VERSION_17"
|
||||
app-build-kotlinJVMTarget = "17"
|
||||
#versioning
|
||||
app-version-appId = "com.simplemobiletools.voicerecorder"
|
||||
app-version-versionCode = "41"
|
||||
app-version-versionName = "5.12.3"
|
||||
[libraries]
|
||||
#AndroidX
|
||||
androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "androidx-constraintlayout" }
|
||||
androidx-documentfile = { module = "androidx.documentfile:documentfile", version.ref = "androidx-documentfile" }
|
||||
androidx-swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version.ref = "androidx-swiperefreshlayout" }
|
||||
#Room
|
||||
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
|
||||
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
|
||||
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
|
||||
#Simple Mobile Tools
|
||||
simple-tools-commons = { module = "com.github.SimpleMobileTools:Simple-Commons", version.ref = "simple-commons" }
|
||||
#EventBus
|
||||
eventbus = { module = "org.greenrobot:eventbus", version.ref = "eventbus" }
|
||||
#AudioRecordView
|
||||
audiorecordview = { module = "com.github.Armen101:AudioRecordView", version.ref = "audiorecordview" }
|
||||
#TAndroidLame
|
||||
tandroidlame = { module = "com.github.naman14:TAndroidLame", version.ref = "tandroidlame" }
|
||||
#AutofitTextView
|
||||
autofittextview = { module = "me.grantland:autofittextview", version.ref = "autofittextview" }
|
||||
[bundles]
|
||||
room = [
|
||||
"androidx-room-ktx",
|
||||
"androidx-room-runtime",
|
||||
]
|
||||
[plugins]
|
||||
android = { id = "com.android.application", version.ref = "gradlePlugins-agp" }
|
||||
kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
|
|
@ -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-7.4-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
include ':app'
|
|
@ -0,0 +1,16 @@
|
|||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven { setUrl("https://jitpack.io") }
|
||||
}
|
||||
}
|
||||
include(":app")
|
Loading…
Reference in New Issue