rebased to latest master

This commit is contained in:
gavine99
2025-02-17 19:10:59 +11:00
parent 0f6c2fb694
commit 94019f5e7a
18 changed files with 1220 additions and 131 deletions
@@ -18,19 +18,24 @@
*/
package dev.octoshrimpy.quik.extensions
import android.content.ContentResolver
import android.content.Context
import android.net.Uri
import androidx.core.net.toFile
fun Uri.resourceExists(context: Context): Boolean {
// check if resource at uri still exists
var retVal: Boolean? = null
try {
return context.contentResolver.query(
this, null, null, null, null
)?.use {
it.moveToFirst()
} ?: false
when (this.scheme) {
ContentResolver.SCHEME_CONTENT -> {
retVal = context.contentResolver.query(
this, null, null, null, null
)?.use { it.moveToFirst() }
}
ContentResolver.SCHEME_FILE -> retVal = this.toFile().exists()
}
} catch (e: Exception) { /* nothing */
}
catch (e: Exception) { /* nothing */ }
return false
return retVal ?: false
}
@@ -0,0 +1,95 @@
/*
* Copyright (C) 2025
*
* This file is part of QUIK.
*
* QUIK is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QUIK is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QUIK. If not, see <http://www.gnu.org/licenses/>.
*/
package com.moez.QKSMS.manager
import android.content.Context
import android.media.MediaRecorder
import android.net.Uri
import androidx.core.net.toUri
import java.io.File
import java.util.UUID
object MediaRecorderManager : MediaRecorder() {
enum class RecordingState {
Initial,
DataSourceConfigured,
Prepared,
Recording,
Error
}
const val AUDIO_FILE_PREFIX = "recorded-"
const val AUDIO_FILE_SUFFIX = ".3ga"
var recordingState: RecordingState = RecordingState.Initial
private set
var uri: Uri = Uri.EMPTY
private set
fun stopRecording(): Uri {
return try {
if (recordingState == RecordingState.Recording)
stop()
reset()
recordingState = RecordingState.Initial
uri
}
catch (e: Exception) {
Uri.EMPTY
}
}
fun startRecording(context: Context): Uri {
return try {
val file = File(
context.cacheDir,
AUDIO_FILE_PREFIX + UUID.randomUUID() + AUDIO_FILE_SUFFIX
)
// ensure stopped before using again
stopRecording()
// configure
setAudioSource(AudioSource.MIC)
setOutputFormat(OutputFormat.THREE_GPP)
setAudioEncoder(AudioEncoder.AMR_WB)
recordingState = RecordingState.DataSourceConfigured
setOutputFile(file.path)
prepare()
recordingState = RecordingState.Prepared
start()
recordingState = RecordingState.Recording
uri = file.toUri()
uri
}
catch (e: Exception) {
Uri.EMPTY
}
}
}
@@ -72,6 +72,10 @@ class PermissionManagerImpl @Inject constructor(private val context: Context) :
return hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
override fun hasRecordAudio(): Boolean {
return hasPermission(Manifest.permission.RECORD_AUDIO)
}
override fun hasExactAlarms(): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
return true
@@ -47,6 +47,7 @@ import dev.octoshrimpy.quik.compat.TelephonyCompat
import dev.octoshrimpy.quik.extensions.anyOf
import dev.octoshrimpy.quik.extensions.isImage
import dev.octoshrimpy.quik.extensions.isVideo
import dev.octoshrimpy.quik.extensions.resourceExists
import dev.octoshrimpy.quik.manager.ActiveConversationManager
import dev.octoshrimpy.quik.manager.KeyManager
import dev.octoshrimpy.quik.model.Attachment
@@ -368,28 +369,31 @@ class MessageRepositoryImpl @Inject constructor(
parts += MMSPart("text", ContentType.TEXT_PLAIN, bytes)
}
// Attach those that can't be compressed (ie. everything but not images)
// Attach those that can't be compressed (ie. everything but images)
parts += attachments
// filter out images
// filter in non-images only
.filter { !it.isImage(context) }
// filter out items that have no data available (ie user may have deleted
// the file from storage)
.filter { it.getResourceBytes(context).isNotEmpty() }
// filter in only items that exist (user may have deleted the file)
.filter { it.uri.resourceExists(context) }
.map {
remainingBytes -= it.getResourceBytes(context).size
MMSPart(
val mmsPart = MMSPart(
it.getName(context),
it.getType(context),
it.getResourceBytes(context)
)
// release the attachment hold on the image bytes so the GC can reclaim
it.releaseResourceBytes()
mmsPart
}
val imageBytesByAttachment = attachments
// filter out non-images
// filter in images only
.filter { it.isImage(context) }
// filter out items that have no data available (ie user may have deleted
// the file from storage)
.filter { it.getResourceBytes(context).isNotEmpty() }
// filter in only items that exist (user may have deleted the file)
.filter { it.uri.resourceExists(context) }
.associateWith {
when (it.getType(context) == "image/gif") {
true -> ImageUtils.getScaledGif(context, it.uri, maxWidth, maxHeight)
@@ -434,6 +438,9 @@ class MessageRepositoryImpl @Inject constructor(
}
Timber.d("Compression attempt $attempts: ${scaledBytes.size / 1024}/${maxBytes.toInt() / 1024}Kb ($width*$height -> $newWidth*$newHeight)")
// release the attachment hold on the image bytes so the GC can reclaim
attachment.releaseResourceBytes()
}
Timber.v("Compressed ${originalBytes.size / 1024}Kb to ${scaledBytes.size / 1024}Kb with a target size of ${maxBytes.toInt() / 1024}Kb in $attempts attempts")