diff --git a/data/src/main/java/com/moez/QKSMS/extensions/MmsPartExtensions.kt b/data/src/main/java/com/moez/QKSMS/extensions/MmsPartExtensions.kt
index e9c9ebac..64906b3b 100644
--- a/data/src/main/java/com/moez/QKSMS/extensions/MmsPartExtensions.kt
+++ b/data/src/main/java/com/moez/QKSMS/extensions/MmsPartExtensions.kt
@@ -21,14 +21,14 @@ package dev.octoshrimpy.quik.extensions
import com.google.android.mms.ContentType
import dev.octoshrimpy.quik.model.MmsPart
-fun MmsPart.isSmil() = ContentType.APP_SMIL == type
+fun MmsPart.isSmil() = ContentType.APP_SMIL.lowercase() == type.lowercase()
-fun MmsPart.isImage() = ContentType.isImageType(type)
+fun MmsPart.isImage() = ContentType.isImageType(type.lowercase())
-fun MmsPart.isVideo() = ContentType.isVideoType(type)
+fun MmsPart.isVideo() = ContentType.isVideoType(type.lowercase())
-fun MmsPart.isAudio() = ContentType.isAudioType(type)
+fun MmsPart.isAudio() = ContentType.isAudioType(type.lowercase())
-fun MmsPart.isText() = ContentType.TEXT_PLAIN == type
+fun MmsPart.isText() = ContentType.TEXT_PLAIN.lowercase() == type.lowercase()
-fun MmsPart.isVCard() = ContentType.TEXT_VCARD == type
+fun MmsPart.isVCard() = ContentType.TEXT_VCARD.lowercase() == type.lowercase()
diff --git a/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt
index 5cb96793..5771cff3 100644
--- a/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt
+++ b/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt
@@ -443,8 +443,8 @@ class MessageRepositoryImpl @Inject constructor(
imageBytesByAttachment.forEach { (attachment, bytes) ->
parts += when (attachment.getType(context) == "image/gif") {
- true -> MMSPart("image", ContentType.IMAGE_GIF, bytes)
- false -> MMSPart("image", ContentType.IMAGE_JPEG, bytes)
+ true -> MMSPart(attachment.getName(context), ContentType.IMAGE_GIF, bytes)
+ false -> MMSPart(attachment.getName(context), ContentType.IMAGE_JPEG, bytes)
}
}
diff --git a/domain/src/main/AndroidManifest.xml b/domain/src/main/AndroidManifest.xml
index 21edd1fd..d70e29d4 100644
--- a/domain/src/main/AndroidManifest.xml
+++ b/domain/src/main/AndroidManifest.xml
@@ -16,4 +16,14 @@
~ You should have received a copy of the GNU General Public License
~ along with QKSMS. If not, see .
-->
-
+
+
+
+
+
+
diff --git a/domain/src/main/java/com/moez/QKSMS/contentproviders/MmsPartProvider.kt b/domain/src/main/java/com/moez/QKSMS/contentproviders/MmsPartProvider.kt
new file mode 100644
index 00000000..84dd6bc3
--- /dev/null
+++ b/domain/src/main/java/com/moez/QKSMS/contentproviders/MmsPartProvider.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2017 Moez Bhatti
+ *
+ * This file is part of QKSMS.
+ *
+ * QKSMS 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.
+ *
+ * QKSMS 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 QKSMS. If not, see .
+ */
+package com.moez.QKSMS.contentproviders
+
+import android.content.ContentProvider
+import android.content.ContentResolver
+import android.content.ContentValues
+import android.database.Cursor
+import android.net.Uri
+import android.os.ParcelFileDescriptor
+import java.io.FileNotFoundException
+
+
+// the primary use case for this provider is to re-provide mms attachments but with a file name
+// as the last part of the uri as opposed to the system mms provider uri which has a part id as
+// it's 'filename' component. with this provider external applications can save/display using the
+// original file name, if available, of the attachment when it is shared and/or saved
+// it seems a needlessly complicated way to achieve the requirement, but it has to be done
+class MmsPartProvider : ContentProvider() {
+ companion object {
+ private fun getMmsPartIdFromUri(uri: Uri): String? {
+ // ensure at least 2 path segments exist
+ if (uri.pathSegments.size < 2)
+ return null
+
+ return try {
+ return uri.pathSegments[uri.pathSegments.size - 2]
+ } catch (e: Exception) {
+ null
+ }
+ }
+
+ private fun getUnderlyingUriFromUri(uri: Uri): Uri {
+ val partId = getMmsPartIdFromUri(uri) ?: return Uri.EMPTY
+ return Uri
+ .Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority("mms")
+ .encodedPath("part/$partId")
+ .build()
+ }
+
+ fun getUriForMmsPartId(partId: Long, partName: String): Uri {
+ return Uri
+ .Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority("dev.octoshrimpy.quik.mmspart")
+ .encodedPath("part/$partId/$partName")
+ .build()
+ }
+ }
+
+ override fun onCreate(): Boolean {
+ return true
+ }
+
+ override fun query(
+ uri: Uri,
+ projection: Array?,
+ selection: String?,
+ selectionArgs: Array?,
+ sortOrder: String?
+ ): Cursor? {
+ return context?.contentResolver?.query(
+ getUnderlyingUriFromUri(uri),
+ projection,
+ selection,
+ selectionArgs,
+ sortOrder
+ )
+ }
+
+ override fun getType(uri: Uri): String? {
+ return context?.contentResolver?.getType(getUnderlyingUriFromUri(uri))
+ }
+
+ override fun insert(uri: Uri, values: ContentValues?): Uri? {
+ return context?.contentResolver?.insert(getUnderlyingUriFromUri(uri), values)
+ }
+
+ override fun update(
+ uri: Uri,
+ values: ContentValues?,
+ selection: String?,
+ selectionArgs: Array?
+ ): Int {
+ return context?.contentResolver?.update(
+ getUnderlyingUriFromUri(uri),
+ values,
+ selection,
+ selectionArgs
+ ) ?: 0
+ }
+
+ override fun delete(
+ uri: Uri,
+ selection: String?,
+ selectionArgs: Array?
+ ): Int {
+ return context?.contentResolver?.delete(
+ getUnderlyingUriFromUri(uri),
+ selection,
+ selectionArgs
+ ) ?: 0
+ }
+
+ @Throws(FileNotFoundException::class)
+ override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
+ return context?.contentResolver?.openFileDescriptor(
+ getUnderlyingUriFromUri(uri),
+ mode)
+ }
+}
\ No newline at end of file
diff --git a/domain/src/main/java/com/moez/QKSMS/model/MmsPart.kt b/domain/src/main/java/com/moez/QKSMS/model/MmsPart.kt
index 0afb53b1..0e13988c 100644
--- a/domain/src/main/java/com/moez/QKSMS/model/MmsPart.kt
+++ b/domain/src/main/java/com/moez/QKSMS/model/MmsPart.kt
@@ -18,12 +18,17 @@
*/
package dev.octoshrimpy.quik.model
+import android.content.ContentResolver
+import android.content.ContentResolver.MimeTypeInfo
+import android.net.Uri
+import android.webkit.MimeTypeMap
import androidx.core.net.toUri
import io.realm.RealmObject
import io.realm.RealmResults
import io.realm.annotations.Index
import io.realm.annotations.LinkingObjects
import io.realm.annotations.PrimaryKey
+import java.io.File
open class MmsPart : RealmObject() {
@@ -37,13 +42,23 @@ open class MmsPart : RealmObject() {
@LinkingObjects("parts")
val messages: RealmResults? = null
- fun getUri() = "content://mms/part/$id".toUri()
+ fun getUri(): Uri = Uri
+ .Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority("mms")
+ .encodedPath("part/$id")
+ .build()
+
+ fun getBestFilename(): String =
+ if (File(name).extension.isNotEmpty()) name ?: "unknown"
+ else "$name." + (MimeTypeMap.getSingleton().getExtensionFromMimeType(type)
+ ?: type.substringAfter("/"))
fun getSummary(): String? = when {
type == "application/smil" -> null
type == "text/plain" -> text
type == "text/x-vcard" -> "Contact card"
- type.startsWith("image") -> "Photo"
+ type.startsWith("image") -> "Picture"
type.startsWith("video") -> "Video"
type.startsWith("audio") -> "Audio"
else -> type.substring(type.indexOf('/') + 1)
diff --git a/presentation/src/main/AndroidManifest.xml b/presentation/src/main/AndroidManifest.xml
index e2bddfa4..f93b74f3 100644
--- a/presentation/src/main/AndroidManifest.xml
+++ b/presentation/src/main/AndroidManifest.xml
@@ -252,17 +252,6 @@
-
-
-
-
-
diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/AttachmentAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/AttachmentAdapter.kt
index 682e7415..d8a7d70b 100644
--- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/AttachmentAdapter.kt
+++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/AttachmentAdapter.kt
@@ -90,6 +90,8 @@ class AttachmentAdapter @Inject constructor(
return
}
+ holder.fileName.visibility = View.GONE
+
// if attachment uri is missing
if (!attachment.getUri().resourceExists(context)) {
holder.thumbnail.setImageResource(android.R.drawable.ic_delete)
diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt
index a94558bd..14242737 100644
--- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt
+++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt
@@ -24,6 +24,7 @@ import android.os.Vibrator
import android.provider.ContactsContract
import android.telephony.SmsMessage
import androidx.core.content.getSystemService
+import com.moez.QKSMS.contentproviders.MmsPartProvider
import dev.octoshrimpy.quik.R
import dev.octoshrimpy.quik.common.Navigator
import dev.octoshrimpy.quik.common.base.QkViewModel
@@ -470,12 +471,11 @@ class ComposeViewModel @Inject constructor(
.mapNotNull(messageRepo::getPart)
.filter { part -> !part.isImage() && !part.isVideo() }
.autoDisposable(view.scope())
- .subscribe { part ->
- if (permissionManager.hasStorage()) {
- messageRepo.savePart(part.id)?.let { navigator.viewFile(it, part.type) }
- } else {
- view.requestStoragePermission()
- }
+ .subscribe {
+ navigator.viewFile(
+ MmsPartProvider.getUriForMmsPartId(it.id, it.getBestFilename()),
+ it.type
+ )
}
// Update the State when the message selected count changes
diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/AudioBinder.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/AudioBinder.kt
index b253185c..89d2352d 100644
--- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/AudioBinder.kt
+++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/AudioBinder.kt
@@ -25,7 +25,9 @@ import android.media.MediaMetadataRetriever
import android.view.View
import android.widget.SeekBar
import com.moez.QKSMS.common.QkMediaPlayer
+import com.moez.QKSMS.contentproviders.MmsPartProvider
import dev.octoshrimpy.quik.R
+import dev.octoshrimpy.quik.common.Navigator
import dev.octoshrimpy.quik.common.base.QkViewHolder
import dev.octoshrimpy.quik.common.util.Colors
import dev.octoshrimpy.quik.common.util.extensions.resolveThemeColor
@@ -49,6 +51,12 @@ import javax.inject.Inject
class AudioBinder @Inject constructor(colors: Colors, private val context: Context) :
PartBinder() {
+ companion object {
+ const val DEFAULT_SHARE_FILENAME = "quik-audio-attachment.mp3"
+ }
+
+ @Inject lateinit var navigator: Navigator
+
override val partLayout = R.layout.mms_audio_preview_list_item
override var theme = colors.theme()
@@ -169,7 +177,13 @@ class AudioBinder @Inject constructor(colors: Colors, private val context: Conte
}
}
- holder.containerView.setOnClickListener { clicks.onNext(part.id) }
+ // share button click handling
+ holder.share.setOnClickListener {
+ navigator.shareFile(
+ MmsPartProvider.getUriForMmsPartId(part.id, part.getBestFilename()),
+ part.type
+ )
+ }
// if this item is the active active audio item update the active view holder
if (audioState.partId == part.id)
@@ -224,6 +238,10 @@ class AudioBinder @Inject constructor(colors: Colors, private val context: Conte
setBackgroundTint(primaryColor)
}
+ // share button
+ holder.share.setTint(secondaryColor)
+ holder.share.setBackgroundTint(primaryColor)
+
MediaMetadataRetriever().use {
it.setDataSource(context, part.getUri())
diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/FileBinder.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/FileBinder.kt
index 0bb60856..7fe60f4b 100644
--- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/FileBinder.kt
+++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/FileBinder.kt
@@ -22,7 +22,9 @@ import android.annotation.SuppressLint
import android.content.Context
import android.view.Gravity
import android.widget.FrameLayout
+import com.moez.QKSMS.contentproviders.MmsPartProvider
import dev.octoshrimpy.quik.R
+import dev.octoshrimpy.quik.common.Navigator
import dev.octoshrimpy.quik.common.base.QkViewHolder
import dev.octoshrimpy.quik.common.util.Colors
import dev.octoshrimpy.quik.common.util.extensions.resolveThemeColor
@@ -34,11 +36,14 @@ import dev.octoshrimpy.quik.model.MmsPart
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
+import kotlinx.android.synthetic.main.mms_audio_preview_list_item.share
import kotlinx.android.synthetic.main.mms_file_list_item.*
import javax.inject.Inject
class FileBinder @Inject constructor(colors: Colors, private val context: Context) : PartBinder() {
+ @Inject lateinit var navigator: Navigator
+
override val partLayout = R.layout.mms_file_list_item
override var theme = colors.theme()
@@ -56,8 +61,6 @@ class FileBinder @Inject constructor(colors: Colors, private val context: Contex
BubbleUtils.getBubble(false, canGroupWithPrevious, canGroupWithNext, message.isMe())
.let(holder.fileBackground::setBackgroundResource)
- holder.containerView.setOnClickListener { clicks.onNext(part.id) }
-
Observable.just(part.getUri())
.map(context.contentResolver::openInputStream)
.map { inputStream -> inputStream.use { it.available() } }
@@ -73,6 +76,14 @@ class FileBinder @Inject constructor(colors: Colors, private val context: Contex
.observeOn(AndroidSchedulers.mainThread())
.subscribe { size -> holder.size.text = size }
+ // share button click handling
+ holder.share.setOnClickListener {
+ navigator.shareFile(
+ MmsPartProvider.getUriForMmsPartId(part.id, part.getBestFilename()),
+ part.type
+ )
+ }
+
holder.filename.text = part.name
val params = holder.fileBackground.layoutParams as FrameLayout.LayoutParams
@@ -82,12 +93,16 @@ class FileBinder @Inject constructor(colors: Colors, private val context: Contex
holder.icon.setTint(theme.textPrimary)
holder.filename.setTextColor(theme.textPrimary)
holder.size.setTextColor(theme.textTertiary)
+ holder.share.setTint(theme.theme)
+ holder.share.setBackgroundTint(theme.textPrimary)
} else {
holder.fileBackground.layoutParams = params.apply { gravity = Gravity.END }
holder.fileBackground.setBackgroundTint(holder.containerView.context.resolveThemeColor(R.attr.bubbleColor))
holder.icon.setTint(holder.containerView.context.resolveThemeColor(android.R.attr.textColorSecondary))
holder.filename.setTextColor(holder.containerView.context.resolveThemeColor(android.R.attr.textColorPrimary))
holder.size.setTextColor(holder.containerView.context.resolveThemeColor(android.R.attr.textColorTertiary))
+ holder.share.setTint(holder.containerView.context.resolveThemeColor(R.attr.bubbleColor))
+ holder.share.setBackgroundTint(holder.containerView.context.resolveThemeColor(android.R.attr.textColorTertiary))
}
}
diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/gallery/GalleryViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/gallery/GalleryViewModel.kt
index 43d4432e..6e4950d9 100644
--- a/presentation/src/main/java/com/moez/QKSMS/feature/gallery/GalleryViewModel.kt
+++ b/presentation/src/main/java/com/moez/QKSMS/feature/gallery/GalleryViewModel.kt
@@ -19,6 +19,7 @@
package dev.octoshrimpy.quik.feature.gallery
import android.content.Context
+import com.moez.QKSMS.contentproviders.MmsPartProvider
import dev.octoshrimpy.quik.R
import dev.octoshrimpy.quik.common.Navigator
import dev.octoshrimpy.quik.common.base.QkViewModel
@@ -45,6 +46,9 @@ class GalleryViewModel @Inject constructor(
private val saveImage: SaveImage,
private val permissions: PermissionManager
) : QkViewModel(GalleryState()) {
+ companion object {
+ const val DEFAULT_SHARE_FILENAME = "quik-media-attachment.jpg"
+ }
init {
disposables += Flowable.just(partId)
@@ -80,11 +84,13 @@ class GalleryViewModel @Inject constructor(
// Share image externally
view.optionsItemSelected()
.filter { itemId -> itemId == R.id.share }
- .filter { permissions.hasStorage().also { if (!it) view.requestStoragePermission() } }
.withLatestFrom(view.pageChanged()) { _, part -> part }
.autoDisposable(view.scope())
- .subscribe { part ->
- messageRepo.savePart(part.id)?.let { navigator.shareFile(it, part.type) }
+ .subscribe {
+ navigator.shareFile(
+ MmsPartProvider.getUriForMmsPartId(it.id, it.getBestFilename()),
+ it.type
+ )
}
}
diff --git a/presentation/src/main/res/layout/mms_audio_preview_list_item.xml b/presentation/src/main/res/layout/mms_audio_preview_list_item.xml
index 97fc8839..a9f65b55 100644
--- a/presentation/src/main/res/layout/mms_audio_preview_list_item.xml
+++ b/presentation/src/main/res/layout/mms_audio_preview_list_item.xml
@@ -53,30 +53,29 @@
android:progressTint="@color/tools_theme"
android:thumbTint="@color/tools_theme" />
-
+ android:layout_marginBottom="12dp">
+ android:textColor="@color/black"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/share"
+ app:layout_constraintStart_toEndOf="@id/playPause" />
-
+
+
\ No newline at end of file
diff --git a/presentation/src/main/res/layout/mms_file_list_item.xml b/presentation/src/main/res/layout/mms_file_list_item.xml
index d8ee901b..8acc1d77 100644
--- a/presentation/src/main/res/layout/mms_file_list_item.xml
+++ b/presentation/src/main/res/layout/mms_file_list_item.xml
@@ -35,36 +35,53 @@
android:layout_height="24dp"
android:layout_marginStart="12dp"
android:src="@drawable/ic_attachment_black_24dp"
- android:tint="?android:attr/textColorSecondary"
- app:layout_constraintBottom_toBottomOf="@id/fileBackground"
+ app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="@id/fileBackground" />
-
-
+ app:tint="?android:attr/textColorSecondary" />
-
+
+
+
+
+
+
+
+
diff --git a/presentation/src/main/res/xml/provider_paths.xml b/presentation/src/main/res/xml/provider_paths.xml
deleted file mode 100644
index 98b55d83..00000000
--- a/presentation/src/main/res/xml/provider_paths.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
-