diff --git a/build.gradle b/build.gradle index 6ce554a1..c26c2ad6 100644 --- a/build.gradle +++ b/build.gradle @@ -4,6 +4,7 @@ buildscript { ext.autodispose_version = '0.5.1' ext.constraintlayout_version = '1.1.0-beta5' ext.dagger_version = "2.16" + ext.exoplayer_version = "2.8.1" ext.glide_version = "4.3.0" ext.kotlin_version = '1.2.31' ext.ktx_version = "0.2" @@ -36,6 +37,7 @@ buildscript { allprojects { repositories { + google() jcenter() maven { url "https://jitpack.io" } maven { url 'https://maven.google.com' } diff --git a/data/src/main/java/repository/MessageRepositoryImpl.kt b/data/src/main/java/repository/MessageRepositoryImpl.kt index f82aec85..bf5140c3 100644 --- a/data/src/main/java/repository/MessageRepositoryImpl.kt +++ b/data/src/main/java/repository/MessageRepositoryImpl.kt @@ -222,7 +222,11 @@ class MessageRepositoryImpl @Inject constructor( return Realm.getDefaultInstance() .where(MmsPart::class.java) .equalTo("messages.threadId", threadId) + .beginGroup() .contains("type", "image/") + .or() + .contains("type", "video/") + .endGroup() .sort("id", Sort.DESCENDING) .findAllAsync() } diff --git a/presentation/build.gradle b/presentation/build.gradle index ed99b9ad..e289709e 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -92,6 +92,10 @@ dependencies { implementation "com.github.bumptech.glide:glide:$glide_version" kapt "com.github.bumptech.glide:compiler:$glide_version" + // exoplayer + implementation "com.google.android.exoplayer:exoplayer-core:$exoplayer_version" + implementation "com.google.android.exoplayer:exoplayer-ui:$exoplayer_version" + // rxbinding implementation "com.jakewharton.rxbinding2:rxbinding-kotlin:$rxbinding_version" implementation "com.jakewharton.rxbinding2:rxbinding-support-v4-kotlin:$rxbinding_version" diff --git a/presentation/src/main/java/common/Navigator.kt b/presentation/src/main/java/common/Navigator.kt index 8015e323..026eff1a 100644 --- a/presentation/src/main/java/common/Navigator.kt +++ b/presentation/src/main/java/common/Navigator.kt @@ -97,19 +97,12 @@ class Navigator @Inject constructor(private val context: Context, private val no startActivity(intent) } - fun showImage(partId: Long) { + fun showMedia(partId: Long) { val intent = Intent(context, GalleryActivity::class.java) intent.putExtra("partId", partId) startActivity(intent) } - fun showVideo(uri: Uri, type: String) { - val intent = Intent(Intent.ACTION_VIEW, uri) - .setDataAndType(uri, type) - - startActivityExternal(intent) - } - /** * Shows the attachment full-screen * The transitionName for the view should be the id of the image being displayed diff --git a/presentation/src/main/java/feature/compose/MessagesAdapter.kt b/presentation/src/main/java/feature/compose/MessagesAdapter.kt index 168c2605..7112aa47 100644 --- a/presentation/src/main/java/feature/compose/MessagesAdapter.kt +++ b/presentation/src/main/java/feature/compose/MessagesAdapter.kt @@ -272,10 +272,7 @@ class MessagesAdapter @Inject constructor( mediaView.video.setVisible(part.isVideo()) mediaView.forwardTouches(view) mediaView.clicks().subscribe { - when { - part.isImage() -> navigator.showImage(part.id) - part.isVideo() -> navigator.showVideo(part.getUri(), part.type) - } + if (part.isImage() || part.isVideo()) navigator.showMedia(part.id) } val canMediaGroupWithPrevious = canGroup(message, previous) || index > 0 diff --git a/presentation/src/main/java/feature/conversationinfo/ConversationMediaAdapter.kt b/presentation/src/main/java/feature/conversationinfo/ConversationMediaAdapter.kt index e5e2805f..afeda1dd 100644 --- a/presentation/src/main/java/feature/conversationinfo/ConversationMediaAdapter.kt +++ b/presentation/src/main/java/feature/conversationinfo/ConversationMediaAdapter.kt @@ -24,17 +24,22 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.moez.QKSMS.R +import common.Navigator import common.base.QkRealmAdapter import common.base.QkViewHolder import common.util.GlideApp +import common.util.extensions.setVisible import io.reactivex.disposables.CompositeDisposable import io.reactivex.subjects.PublishSubject import kotlinx.android.synthetic.main.conversation_media_list_item.view.* import model.MmsPart +import util.extensions.isImage +import util.extensions.isVideo import javax.inject.Inject class ConversationMediaAdapter @Inject constructor( - private val context: Context + private val context: Context, + private val navigator: Navigator ) : QkRealmAdapter() { val thumbnailClicks: PublishSubject = PublishSubject.create() @@ -55,8 +60,11 @@ class ConversationMediaAdapter @Inject constructor( .fitCenter() .into(view.thumbnail) + view.video.setVisible(part.isVideo()) view.thumbnail.transitionName = part.id.toString() - view.thumbnail.setOnClickListener { thumbnailClicks.onNext(it) } + view.thumbnail.setOnClickListener { + if (part.isImage()) thumbnailClicks.onNext(it) else navigator.showMedia(part.id) + } } override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { diff --git a/presentation/src/main/java/feature/gallery/GalleryActivity.kt b/presentation/src/main/java/feature/gallery/GalleryActivity.kt index 82e58428..3b19defa 100644 --- a/presentation/src/main/java/feature/gallery/GalleryActivity.kt +++ b/presentation/src/main/java/feature/gallery/GalleryActivity.kt @@ -20,12 +20,21 @@ package feature.gallery import android.arch.lifecycle.ViewModelProvider import android.arch.lifecycle.ViewModelProviders +import android.net.Uri import android.os.Bundle import android.transition.ChangeBounds import android.transition.ChangeImageTransform import android.transition.TransitionSet import android.view.Menu import android.view.MenuItem +import com.google.android.exoplayer2.ExoPlayerFactory +import com.google.android.exoplayer2.SimpleExoPlayer +import com.google.android.exoplayer2.source.ExtractorMediaSource +import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection +import com.google.android.exoplayer2.trackselection.DefaultTrackSelector +import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory +import com.google.android.exoplayer2.util.Util +import com.google.android.mms.ContentType import com.jakewharton.rxbinding2.view.clicks import com.moez.QKSMS.R import common.GlideCompletionListener @@ -38,6 +47,7 @@ import io.reactivex.subjects.Subject import kotlinx.android.synthetic.main.gallery_activity.* import javax.inject.Inject + class GalleryActivity : QkActivity(), GalleryView { @Inject lateinit var viewModelFactory: ViewModelProvider.Factory @@ -47,6 +57,8 @@ class GalleryActivity : QkActivity(), GalleryView { private val viewModel by lazy { ViewModelProviders.of(this, viewModelFactory)[GalleryViewModel::class.java] } + private var exoPlayer: SimpleExoPlayer? = null + override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) @@ -91,9 +103,19 @@ class GalleryActivity : QkActivity(), GalleryView { title = state.title + when { + ContentType.isImageType(state.type) -> showImage(state.uri) + ContentType.isVideoType(state.type) -> showVideo(state.uri) + else -> hideMedia() + } + } + + private fun showImage(uri: Uri?) { + image.setVisible(true) + video.setVisible(false) if (image.drawable == null) { GlideApp.with(this) - .load(state.imageUri) + .load(uri) .dontAnimate() .listener(GlideCompletionListener { startPostponedEnterTransition() @@ -102,6 +124,27 @@ class GalleryActivity : QkActivity(), GalleryView { } } + private fun showVideo(uri: Uri?) { + image.setVisible(false) + video.setVisible(true) + + if (video.player == null) { + val videoTrackSelectionFactory = AdaptiveTrackSelection.Factory(null) + val trackSelector = DefaultTrackSelector(videoTrackSelectionFactory) + exoPlayer = ExoPlayerFactory.newSimpleInstance(this, trackSelector) + video.player = exoPlayer + + val dataSourceFactory = DefaultDataSourceFactory(this, Util.getUserAgent(this, "QKSMS")) + val videoSource = ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(uri) + exoPlayer?.prepare(videoSource) + } + } + + private fun hideMedia() { + image.setVisible(false) + video.setVisible(false) + } + override fun onCreateOptionsMenu(menu: Menu?): Boolean { menuInflater.inflate(R.menu.gallery, menu) return super.onCreateOptionsMenu(menu) @@ -115,4 +158,9 @@ class GalleryActivity : QkActivity(), GalleryView { return true } + override fun onDestroy() { + super.onDestroy() + exoPlayer?.release() + } + } \ No newline at end of file diff --git a/presentation/src/main/java/feature/gallery/GalleryState.kt b/presentation/src/main/java/feature/gallery/GalleryState.kt index 08726bc2..b65dcd12 100644 --- a/presentation/src/main/java/feature/gallery/GalleryState.kt +++ b/presentation/src/main/java/feature/gallery/GalleryState.kt @@ -23,5 +23,6 @@ import android.net.Uri data class GalleryState( val navigationVisible: Boolean = true, val title: String = "", - val imageUri: Uri? = null + val uri: Uri? = null, + val type: String = "" ) \ No newline at end of file diff --git a/presentation/src/main/java/feature/gallery/GalleryViewModel.kt b/presentation/src/main/java/feature/gallery/GalleryViewModel.kt index ee0bade1..0db9d00e 100644 --- a/presentation/src/main/java/feature/gallery/GalleryViewModel.kt +++ b/presentation/src/main/java/feature/gallery/GalleryViewModel.kt @@ -47,8 +47,7 @@ class GalleryViewModel @Inject constructor( init { disposables += partIdFlowable .mapNotNull { partId -> messageRepo.getPart(partId) } - .mapNotNull { part -> part.getUri() } - .subscribe { uri -> newState { copy(imageUri = uri) } } + .subscribe { part -> newState { copy(uri = part.getUri(), type = part.type) } } disposables += partIdFlowable .mapNotNull { partId -> messageRepo.getMessageForPart(partId) } diff --git a/presentation/src/main/res/layout/conversation_media_list_item.xml b/presentation/src/main/res/layout/conversation_media_list_item.xml index cb7d5954..61c758dd 100644 --- a/presentation/src/main/res/layout/conversation_media_list_item.xml +++ b/presentation/src/main/res/layout/conversation_media_list_item.xml @@ -17,11 +17,27 @@ ~ You should have received a copy of the GNU General Public License ~ along with QKSMS. If not, see . --> - \ No newline at end of file + android:layout_height="wrap_content"> + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/layout/gallery_activity.xml b/presentation/src/main/res/layout/gallery_activity.xml index 8b822bbc..8505c7ac 100644 --- a/presentation/src/main/res/layout/gallery_activity.xml +++ b/presentation/src/main/res/layout/gallery_activity.xml @@ -30,6 +30,11 @@ android:layout_width="match_parent" android:layout_height="match_parent" /> + + Call Details - Save image + Save to gallery %d selected Clear @@ -111,7 +111,7 @@ Unblock Delete conversation - Saved image + Saved to gallery Appearance General