Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • circles/circles-android
1 result
Show changes
Commits on Source (7)
Showing with 196 additions and 61 deletions
......@@ -2,6 +2,7 @@ package org.futo.circles.feature.direct.timeline
import android.os.Bundle
import android.view.View
import android.view.WindowManager
import androidx.fragment.app.viewModels
import androidx.media3.common.Player
import androidx.media3.exoplayer.ExoPlayer
......@@ -16,10 +17,13 @@ import org.futo.circles.core.extensions.observeResponse
import org.futo.circles.core.extensions.showNoInternetConnection
import org.futo.circles.core.extensions.showSuccess
import org.futo.circles.core.extensions.withConfirmation
import org.futo.circles.core.feature.picker.helper.MediaPickerHelper
import org.futo.circles.core.feature.share.ShareProvider
import org.futo.circles.core.model.MediaType
import org.futo.circles.core.model.PostContent
import org.futo.circles.databinding.DialogFragmentDmTimelineBinding
import org.futo.circles.feature.direct.timeline.list.DMTimelineAdapter
import org.futo.circles.feature.direct.timeline.listeners.SendDmMessageListener
import org.futo.circles.feature.timeline.list.PostOptionsListener
import org.futo.circles.feature.timeline.post.create.PostSentListener
import org.futo.circles.feature.timeline.post.emoji.EmojiPickerListener
......@@ -30,7 +34,8 @@ import org.futo.circles.model.RemovePost
@AndroidEntryPoint
class DMTimelineDialogFragment :
BaseFullscreenDialogFragment<DialogFragmentDmTimelineBinding>(DialogFragmentDmTimelineBinding::inflate),
PostOptionsListener, PostMenuListener, EmojiPickerListener, PostSentListener {
PostOptionsListener, PostMenuListener, EmojiPickerListener, PostSentListener,
SendDmMessageListener {
private val args: DMTimelineDialogFragmentArgs by navArgs()
private val viewModel by viewModels<DMTimelineViewModel>()
......@@ -40,6 +45,7 @@ class DMTimelineDialogFragment :
repeatMode = Player.REPEAT_MODE_ONE
}
}
private val mediaPickerHelper = MediaPickerHelper(this, isVideoAvailable = true)
private val navigator by lazy { DMTimelineNavigator(this) }
private val listAdapter by lazy {
......@@ -50,8 +56,11 @@ class DMTimelineDialogFragment :
private var onLocalAddEmojiCallback: ((String) -> Unit)? = null
@Suppress("DEPRECATION")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
dialog?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
setupViews()
setupObservers()
stopVideoOnNewScreenOpen()
......@@ -80,6 +89,8 @@ class DMTimelineDialogFragment :
//addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
addPageEndListener { viewModel.loadMore() }
}
binding.vSendMessage.setup(this)
}
......@@ -161,14 +172,6 @@ class DMTimelineDialogFragment :
override fun endPoll(roomId: String, eventId: String) {
}
override fun onEmojiSelected(roomId: String?, eventId: String?, emoji: String) {
roomId ?: return
eventId ?: return
onLocalAddEmojiCallback?.invoke(emoji)
onLocalAddEmojiCallback = null
viewModel.sendReaction(roomId, eventId, emoji)
}
private fun stopVideoOnNewScreenOpen() {
findNavController().addOnDestinationChangedListener { _, destination, _ ->
if (destination.id != R.id.timelineFragment) listAdapter.stopVideoPlayback()
......@@ -180,4 +183,23 @@ class DMTimelineDialogFragment :
binding.rvTimeline.layoutManager?.scrollToPosition(count - 1)
}
}
override fun onEmojiSelected(roomId: String?, eventId: String?, emoji: String) {
binding.vSendMessage.insertEmojiIntoMessage(emoji)
}
override fun onAddEmojiToMessageClicked() {
navigator.navigateToEmojiPicker()
}
override fun onSendTextMessageClicked(message: String) {
viewModel.sendTextMessageDm(message)
}
override fun onSendMediaButtonClicked() {
mediaPickerHelper.showMediaPickerDialog(
onImageSelected = { _, uri -> viewModel.sendMediaDm(uri, MediaType.Image) },
onVideoSelected = { uri -> viewModel.sendMediaDm(uri, MediaType.Video) }
)
}
}
\ No newline at end of file
......@@ -10,4 +10,10 @@ class DMTimelineNavigator(private val fragment: DMTimelineDialogFragment) {
DMTimelineDialogFragmentDirections.toUserNavGraph(userId)
)
}
fun navigateToEmojiPicker() {
fragment.findNavController().navigateSafe(
DMTimelineDialogFragmentDirections.toEmojiBottomSheet(null,null)
)
}
}
\ No newline at end of file
package org.futo.circles.feature.direct.timeline
import android.content.Context
import android.net.Uri
import android.view.View
import androidx.lifecycle.SavedStateHandle
import dagger.hilt.android.lifecycle.HiltViewModel
......@@ -14,10 +15,15 @@ import org.futo.circles.core.feature.circles.filter.CircleFilterAccountDataManag
import org.futo.circles.core.feature.timeline.BaseTimelineViewModel
import org.futo.circles.core.feature.timeline.data_source.BaseTimelineDataSource
import org.futo.circles.core.feature.timeline.post.PostOptionsDataSource
import org.futo.circles.core.feature.timeline.post.SendMessageDataSource
import org.futo.circles.core.model.MediaType
import org.futo.circles.core.model.PostContent
import org.futo.circles.core.model.ShareableContent
import org.futo.circles.core.provider.MatrixSessionProvider
import org.futo.circles.feature.timeline.data_source.ReadMessageDataSource
import org.futo.circles.model.CreatePostContent
import org.futo.circles.model.MediaPostContent
import org.futo.circles.model.TextPostContent
import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.util.Cancelable
import javax.inject.Inject
......@@ -29,7 +35,8 @@ class DMTimelineViewModel @Inject constructor(
timelineDataSourceFactory: BaseTimelineDataSource.Factory,
private val postOptionsDataSource: PostOptionsDataSource,
private val readMessageDataSource: ReadMessageDataSource,
circleFilterAccountDataManager: CircleFilterAccountDataManager
circleFilterAccountDataManager: CircleFilterAccountDataManager,
private val sendMessageDataSource: SendMessageDataSource
) : BaseTimelineViewModel(
savedStateHandle,
context,
......@@ -79,4 +86,33 @@ class DMTimelineViewModel @Inject constructor(
}?.awaitAll()
}
}
fun sendTextMessageDm(message: String) {
launchBg { sendMessage(roomId, TextPostContent(message)) }
}
fun sendMediaDm(uri: Uri, mediaType: MediaType) {
launchBg { sendMessage(roomId, MediaPostContent(null, uri, mediaType)) }
}
private suspend fun sendMessage(
roomId: String,
postContent: CreatePostContent
): String = when (postContent) {
is MediaPostContent -> sendMessageDataSource.sendMedia(
roomId,
postContent.uri,
null,
null,
postContent.mediaType
).first
is TextPostContent -> sendMessageDataSource.sendTextMessage(
roomId, postContent.text, null
)
}
fun editTextMessage(eventId: String, roomId: String, message: String) {
sendMessageDataSource.editTextMessage(eventId, roomId, message)
}
}
\ No newline at end of file
package org.futo.circles.feature.direct.timeline.listeners
interface SendDmMessageListener {
fun onAddEmojiToMessageClicked()
fun onSendTextMessageClicked(message: String)
fun onSendMediaButtonClicked()
}
\ No newline at end of file
package org.futo.circles.view
import android.content.Context
import android.text.Editable
import android.util.AttributeSet
import android.view.LayoutInflater
import androidx.constraintlayout.widget.ConstraintLayout
import org.futo.circles.core.model.Post
import org.futo.circles.databinding.ViewPostFooterBinding
import androidx.core.widget.doAfterTextChanged
import org.futo.circles.core.extensions.getText
import org.futo.circles.core.extensions.setIsVisible
import org.futo.circles.databinding.ViewSendMessageBinding
import org.futo.circles.feature.timeline.list.PostOptionsListener
import org.futo.circles.feature.direct.timeline.listeners.SendDmMessageListener
class SendMessageView(
context: Context,
attrs: AttributeSet? = null,
) : ConstraintLayout(context, attrs) {
private val binding =
ViewSendMessageBinding.inflate(LayoutInflater.from(context), this)
private val binding = ViewSendMessageBinding.inflate(LayoutInflater.from(context), this)
private var sendDmMessageListener: SendDmMessageListener? = null
init {
binding.etMessage.doAfterTextChanged { text: Editable? ->
binding.ivAddImage.setIsVisible(text.isNullOrBlank())
binding.ivSend.setIsVisible(text?.isNotBlank() == true)
}
}
fun setup(listener: SendDmMessageListener) {
sendDmMessageListener = listener
with(binding) {
ivEmoji.setOnClickListener { sendDmMessageListener?.onAddEmojiToMessageClicked() }
ivAddImage.setOnClickListener { sendDmMessageListener?.onSendMediaButtonClicked() }
ivSend.setOnClickListener { sendDmMessageListener?.onSendTextMessageClicked(tilMessage.getText()) }
}
}
fun insertEmojiIntoMessage(unicode: String) {
val selection = binding.etMessage.selectionStart
binding.etMessage.append(unicode)
binding.etMessage.setSelection(selection + unicode.length)
}
}
\ No newline at end of file
......@@ -38,7 +38,7 @@
<org.futo.circles.view.SendMessageView
android:id="@+id/lCreatePost"
android:id="@+id/vSendMessage"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
......
......@@ -5,74 +5,88 @@
android:id="@+id/lCreatePost"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnEmoji"
<View
android:id="@+id/topDivider"
android:layout_width="0dp"
android:layout_height="@dimen/divider_height"
android:background="@color/divider_color"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/ivEmoji"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="8dp"
android:background="?selectableItemBackgroundBorderless"
android:enabled="false"
android:clickable="true"
android:focusable="true"
android:minWidth="0dp"
android:minHeight="0dp"
android:padding="0dp"
app:icon="@drawable/ic_emoji"
app:iconGravity="end"
app:iconSize="40dp"
app:iconTint="@color/send_ic_state_color"
app:layout_constraintBottom_toBottomOf="parent"
android:padding="12dp"
android:src="@drawable/ic_emoji"
app:layout_constraintBottom_toBottomOf="@id/tilMessage"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:tint="@color/menu_icon_color" />
<EditText
android:id="@+id/etMessage"
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/tilMessage"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@null"
android:hint="@string/message"
android:minHeight="48dp"
app:boxBackgroundMode="none"
app:hintEnabled="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/btnAddImage"
app:layout_constraintStart_toEndOf="@id/btnEmoji"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintEnd_toStartOf="@id/ivAddImage"
app:layout_constraintStart_toEndOf="@id/ivEmoji"
app:layout_constraintTop_toTopOf="parent">
<io.element.android.wysiwyg.EditorEditText
android:id="@+id/etMessage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="@string/message"
android:inputType="textCapSentences|textMultiLine"
android:minHeight="48dp"
android:padding="12dp"
android:textSize="17sp" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/btnAddImage"
<ImageView
android:id="@+id/ivAddImage"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?selectableItemBackgroundBorderless"
android:enabled="false"
android:clickable="true"
android:focusable="true"
android:minWidth="0dp"
android:minHeight="0dp"
android:padding="0dp"
app:icon="@drawable/ic_image"
app:iconGravity="end"
app:iconSize="40dp"
app:iconTint="@color/send_ic_state_color"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/btnSend"
app:layout_constraintTop_toTopOf="parent" />
android:padding="12dp"
android:src="@drawable/ic_image"
app:layout_constraintBottom_toBottomOf="@id/tilMessage"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toStartOf="@id/ivSend"
app:tint="@color/menu_icon_color" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnSend"
<ImageView
android:id="@+id/ivSend"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?selectableItemBackgroundBorderless"
android:enabled="false"
android:clickable="true"
android:focusable="true"
android:minWidth="0dp"
android:minHeight="0dp"
android:padding="0dp"
app:icon="@drawable/ic_send"
app:iconGravity="end"
app:iconSize="40dp"
app:iconTint="@color/send_ic_state_color"
app:layout_constraintBottom_toBottomOf="parent"
android:padding="12dp"
android:src="@drawable/ic_send"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/tilMessage"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:tint="@color/blue"
tools:visibility="visible" />
</merge>
......@@ -29,8 +29,29 @@
app:nullable="false" />
</action>
<action
android:id="@+id/to_emojiBottomSheet"
app:destination="@id/emojiBottomSheet" />
</dialog>
<include app:graph="@navigation/user_nav_graph" />
<dialog
android:id="@+id/emojiBottomSheet"
android:name="org.futo.circles.feature.timeline.post.emoji.EmojiBottomSheet"
android:label="EmojiBottomSheet"
tools:layout="@layout/bottom_sheet_emoji">
<argument
android:name="roomId"
app:argType="string"
app:nullable="true" />
<argument
android:name="eventId"
app:argType="string"
app:nullable="true" />
</dialog>
</navigation>
\ No newline at end of file
......@@ -103,6 +103,7 @@ abstract class BaseTimelineViewModel(
)
}
@Suppress("DeferredResultUnused")
private fun prefetchVideo(context: Context, postId: String, data: MediaFileData) {
launchBg {
async {
......