kopia lustrzana https://github.com/ryukoposting/Signal-Android
Add entry points for adding to a group story.
rodzic
7949996c5c
commit
7b13550086
|
@ -77,6 +77,7 @@ import org.thoughtcrime.securesms.recipients.ui.bottomsheet.RecipientBottomSheet
|
|||
import org.thoughtcrime.securesms.stories.Stories
|
||||
import org.thoughtcrime.securesms.stories.StoryViewerArgs
|
||||
import org.thoughtcrime.securesms.stories.dialogs.StoryDialogs
|
||||
import org.thoughtcrime.securesms.stories.viewer.AddToGroupStoryDelegate
|
||||
import org.thoughtcrime.securesms.stories.viewer.StoryViewerActivity
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions
|
||||
import org.thoughtcrime.securesms.util.ContextUtil
|
||||
|
@ -137,6 +138,7 @@ class ConversationSettingsFragment : DSLSettingsFragment(
|
|||
private lateinit var toolbarBadge: BadgeImageView
|
||||
private lateinit var toolbarTitle: TextView
|
||||
private lateinit var toolbarBackground: View
|
||||
private lateinit var addToGroupStoryDelegate: AddToGroupStoryDelegate
|
||||
|
||||
private val navController get() = Navigation.findNavController(requireView())
|
||||
|
||||
|
@ -221,6 +223,7 @@ class ConversationSettingsFragment : DSLSettingsFragment(
|
|||
}
|
||||
}
|
||||
|
||||
addToGroupStoryDelegate = AddToGroupStoryDelegate(this)
|
||||
viewModel.state.observe(viewLifecycleOwner) { state ->
|
||||
|
||||
if (state.recipient != Recipient.UNKNOWN) {
|
||||
|
@ -368,6 +371,17 @@ class ConversationSettingsFragment : DSLSettingsFragment(
|
|||
customPref(
|
||||
ButtonStripPreference.Model(
|
||||
state = state.buttonStripState,
|
||||
onAddToStoryClick = {
|
||||
if (state.recipient.isPushV2Group && state.requireGroupSettingsState().isAnnouncementGroup && !state.requireGroupSettingsState().isSelfAdmin) {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.ConversationSettingsFragment__cant_add_to_group_story)
|
||||
.setMessage(R.string.ConversationSettingsFragment__only_admins_of_this_group_can_add_to_its_story)
|
||||
.setPositiveButton(android.R.string.ok) { d, _ -> d.dismiss() }
|
||||
.show()
|
||||
} else {
|
||||
addToGroupStoryDelegate.addToStory(state.recipient.id)
|
||||
}
|
||||
},
|
||||
onVideoClick = {
|
||||
if (state.recipient.isPushV2Group && state.requireGroupSettingsState().isAnnouncementGroup && !state.requireGroupSettingsState().isSelfAdmin) {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
|
|
|
@ -272,7 +272,8 @@ sealed class ConversationSettingsViewModel(
|
|||
isAudioSecure = recipient.isPushV2Group,
|
||||
isMuted = recipient.isMuted,
|
||||
isMuteAvailable = true,
|
||||
isSearchAvailable = true
|
||||
isSearchAvailable = true,
|
||||
isAddToStoryAvailable = recipient.isPushV2Group && !recipient.isBlocked && isActive
|
||||
),
|
||||
canModifyBlockedState = RecipientUtil.isBlockable(recipient),
|
||||
specificSettingsState = state.requireGroupSettingsState().copy(
|
||||
|
|
|
@ -24,6 +24,7 @@ object ButtonStripPreference {
|
|||
class Model(
|
||||
val state: State,
|
||||
val background: DSLSettingsIcon? = null,
|
||||
val onAddToStoryClick: () -> Unit = {},
|
||||
val onMessageClick: () -> Unit = {},
|
||||
val onVideoClick: () -> Unit = {},
|
||||
val onAudioClick: () -> Unit = {},
|
||||
|
@ -41,6 +42,8 @@ object ButtonStripPreference {
|
|||
|
||||
class ViewHolder(itemView: View) : MappingViewHolder<Model>(itemView) {
|
||||
|
||||
private val addToStory: View = itemView.findViewById(R.id.add_to_story)
|
||||
private val addToStoryContainer: View = itemView.findViewById(R.id.button_strip_add_to_story_container)
|
||||
private val message: View = itemView.findViewById(R.id.message)
|
||||
private val messageContainer: View = itemView.findViewById(R.id.button_strip_message_container)
|
||||
private val videoCall: View = itemView.findViewById(R.id.start_video)
|
||||
|
@ -60,6 +63,7 @@ object ButtonStripPreference {
|
|||
audioContainer.visible = model.state.isAudioAvailable
|
||||
muteContainer.visible = model.state.isMuteAvailable
|
||||
searchContainer.visible = model.state.isSearchAvailable
|
||||
addToStoryContainer.visible = model.state.isAddToStoryAvailable
|
||||
|
||||
if (model.state.isAudioSecure) {
|
||||
audioLabel.setText(R.string.ConversationSettingsFragment__audio)
|
||||
|
@ -88,6 +92,7 @@ object ButtonStripPreference {
|
|||
audioCall.setOnClickListener { model.onAudioClick() }
|
||||
mute.setOnClickListener { model.onMuteClick() }
|
||||
search.setOnClickListener { model.onSearchClick() }
|
||||
addToStory.setOnClickListener { model.onAddToStoryClick() }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -99,5 +104,6 @@ object ButtonStripPreference {
|
|||
val isSearchAvailable: Boolean = false,
|
||||
val isAudioSecure: Boolean = false,
|
||||
val isMuted: Boolean = false,
|
||||
val isAddToStoryAvailable: Boolean = false
|
||||
)
|
||||
}
|
||||
|
|
|
@ -93,8 +93,9 @@ class MediaSelectionActivity :
|
|||
val initialMedia: List<Media> = intent.getParcelableArrayListExtra(MEDIA) ?: listOf()
|
||||
val message: CharSequence? = if (shareToTextStory) null else draftText
|
||||
val isReply: Boolean = intent.getBooleanExtra(IS_REPLY, false)
|
||||
val isAddToGroupStoryFlow: Boolean = intent.getBooleanExtra(IS_ADD_TO_GROUP_STORY_FLOW, false)
|
||||
|
||||
val factory = MediaSelectionViewModel.Factory(destination, sendType, initialMedia, message, isReply, isStory, MediaSelectionRepository(this))
|
||||
val factory = MediaSelectionViewModel.Factory(destination, sendType, initialMedia, message, isReply, isStory, isAddToGroupStoryFlow, MediaSelectionRepository(this))
|
||||
viewModel = ViewModelProvider(this, factory)[MediaSelectionViewModel::class.java]
|
||||
|
||||
val textStoryToggle: ConstraintLayout = findViewById(R.id.switch_widget)
|
||||
|
@ -221,7 +222,7 @@ class MediaSelectionActivity :
|
|||
return Stories.isFeatureEnabled() &&
|
||||
isCameraFirst() &&
|
||||
!viewModel.hasSelectedMedia() &&
|
||||
destination == MediaSelectionDestination.ChooseAfterMediaSelection
|
||||
(destination == MediaSelectionDestination.ChooseAfterMediaSelection || destination is MediaSelectionDestination.SingleStory)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
|
@ -348,6 +349,7 @@ class MediaSelectionActivity :
|
|||
private const val IS_REPLY = "is_reply"
|
||||
private const val IS_STORY = "is_story"
|
||||
private const val AS_TEXT_STORY = "as_text_story"
|
||||
private const val IS_ADD_TO_GROUP_STORY_FLOW = "is_add_to_group_story_flow"
|
||||
|
||||
@JvmStatic
|
||||
fun camera(context: Context): Intent {
|
||||
|
@ -363,6 +365,19 @@ class MediaSelectionActivity :
|
|||
)
|
||||
}
|
||||
|
||||
fun addToGroupStory(
|
||||
context: Context,
|
||||
recipientId: RecipientId
|
||||
): Intent {
|
||||
return buildIntent(
|
||||
context = context,
|
||||
startAction = R.id.action_directly_to_mediaCaptureFragment,
|
||||
isStory = true,
|
||||
isAddToGroupStoryFlow = true,
|
||||
destination = MediaSelectionDestination.SingleStory(recipientId)
|
||||
)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun camera(
|
||||
context: Context,
|
||||
|
@ -457,7 +472,8 @@ class MediaSelectionActivity :
|
|||
message: CharSequence? = null,
|
||||
isReply: Boolean = false,
|
||||
isStory: Boolean = false,
|
||||
asTextStory: Boolean = false
|
||||
asTextStory: Boolean = false,
|
||||
isAddToGroupStoryFlow: Boolean = false
|
||||
): Intent {
|
||||
return Intent(context, MediaSelectionActivity::class.java).apply {
|
||||
putExtra(START_ACTION, startAction)
|
||||
|
@ -468,6 +484,7 @@ class MediaSelectionActivity :
|
|||
putExtra(IS_REPLY, isReply)
|
||||
putExtra(IS_STORY, isStory)
|
||||
putExtra(AS_TEXT_STORY, asTextStory)
|
||||
putExtra(IS_ADD_TO_GROUP_STORY_FLOW, isAddToGroupStoryFlow)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,16 @@ sealed class MediaSelectionDestination {
|
|||
}
|
||||
}
|
||||
|
||||
class SingleStory(private val id: RecipientId) : MediaSelectionDestination() {
|
||||
override fun getRecipientSearchKey(): ContactSearchKey.RecipientSearchKey = ContactSearchKey.RecipientSearchKey.Story(id)
|
||||
|
||||
override fun toBundle(): Bundle {
|
||||
return Bundle().apply {
|
||||
putParcelable(STORY, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MultipleRecipients(val recipientSearchKeys: List<ContactSearchKey.RecipientSearchKey>) : MediaSelectionDestination() {
|
||||
|
||||
companion object {
|
||||
|
@ -72,6 +82,7 @@ sealed class MediaSelectionDestination {
|
|||
private const val WALLPAPER = "wallpaper"
|
||||
private const val AVATAR = "avatar"
|
||||
private const val RECIPIENT = "recipient"
|
||||
private const val STORY = "story"
|
||||
private const val RECIPIENT_LIST = "recipient_list"
|
||||
|
||||
fun fromBundle(bundle: Bundle): MediaSelectionDestination {
|
||||
|
@ -79,6 +90,7 @@ sealed class MediaSelectionDestination {
|
|||
bundle.containsKey(WALLPAPER) -> Wallpaper
|
||||
bundle.containsKey(AVATAR) -> Avatar
|
||||
bundle.containsKey(RECIPIENT) -> SingleRecipient(requireNotNull(bundle.getParcelable(RECIPIENT)))
|
||||
bundle.containsKey(STORY) -> SingleStory(requireNotNull(bundle.getParcelable(STORY)))
|
||||
bundle.containsKey(RECIPIENT_LIST) -> MultipleRecipients.fromParcel(requireNotNull(bundle.getParcelableArrayList(RECIPIENT_LIST)))
|
||||
else -> ChooseAfterMediaSelection
|
||||
}
|
||||
|
|
|
@ -105,6 +105,8 @@ class MediaSelectionRepository(context: Context) {
|
|||
val singleRecipient: Recipient? = singleContact?.let { Recipient.resolved(it.recipientId) }
|
||||
val storyType: StoryType = if (singleRecipient?.isDistributionList == true) {
|
||||
SignalDatabase.distributionLists.getStoryType(singleRecipient.requireDistributionListId())
|
||||
} else if (singleRecipient?.isGroup == true && singleContact.isStory) {
|
||||
StoryType.STORY_WITH_REPLIES
|
||||
} else {
|
||||
StoryType.NONE
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ class MediaSelectionViewModel(
|
|||
initialMessage: CharSequence?,
|
||||
val isReply: Boolean,
|
||||
isStory: Boolean,
|
||||
val isAddToGroupStoryFlow: Boolean,
|
||||
private val repository: MediaSelectionRepository,
|
||||
private val identityChangesSince: Long = System.currentTimeMillis()
|
||||
) : ViewModel() {
|
||||
|
@ -360,10 +361,10 @@ class MediaSelectionViewModel(
|
|||
return
|
||||
}
|
||||
|
||||
val filteredPreUploadMedia = if (Stories.isFeatureEnabled()) {
|
||||
media.filter { Stories.MediaTransform.canPreUploadMedia(it) }
|
||||
} else {
|
||||
val filteredPreUploadMedia = if (destination is MediaSelectionDestination.SingleRecipient || !Stories.isFeatureEnabled()) {
|
||||
media
|
||||
} else {
|
||||
media.filter { Stories.MediaTransform.canPreUploadMedia(it) }
|
||||
}
|
||||
|
||||
repository.uploadRepository.startUpload(filteredPreUploadMedia, store.state.recipient)
|
||||
|
@ -482,10 +483,11 @@ class MediaSelectionViewModel(
|
|||
private val initialMessage: CharSequence?,
|
||||
private val isReply: Boolean,
|
||||
private val isStory: Boolean,
|
||||
private val isAddToGroupStoryFlow: Boolean,
|
||||
private val repository: MediaSelectionRepository
|
||||
) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return requireNotNull(modelClass.cast(MediaSelectionViewModel(destination, sendType, initialMedia, initialMessage, isReply, isStory, repository)))
|
||||
return requireNotNull(modelClass.cast(MediaSelectionViewModel(destination, sendType, initialMedia, initialMessage, isReply, isStory, isAddToGroupStoryFlow, repository)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import androidx.recyclerview.widget.ItemTouchHelper
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import app.cash.exhaustive.Exhaustive
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import org.signal.core.util.concurrent.SimpleTask
|
||||
import org.thoughtcrime.securesms.R
|
||||
|
@ -200,6 +201,12 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment) {
|
|||
} else {
|
||||
multiselectLauncher.launch(args)
|
||||
}
|
||||
} else if (sharedViewModel.isAddToGroupStoryFlow) {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setMessage(getString(R.string.MediaReviewFragment__add_to_the_group_story, sharedViewModel.state.value!!.recipient!!.getDisplayName(requireContext())))
|
||||
.setPositiveButton(R.string.MediaReviewFragment__add_to_story) { _, _ -> performSend() }
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
.show()
|
||||
} else {
|
||||
performSend()
|
||||
}
|
||||
|
@ -317,7 +324,7 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment) {
|
|||
.setInterpolator(MediaAnimations.interpolator)
|
||||
.alpha(1f)
|
||||
|
||||
sharedViewModel
|
||||
disposables += sharedViewModel
|
||||
.send(selection.filterIsInstance(ContactSearchKey.RecipientSearchKey::class.java))
|
||||
.subscribe(
|
||||
{ result -> callback.onSentWithResult(result) },
|
||||
|
|
|
@ -12,6 +12,7 @@ import androidx.core.view.updateLayoutParams
|
|||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
|
||||
|
@ -157,6 +158,12 @@ class TextStoryPostCreationFragment : Fragment(R.layout.stories_text_post_creati
|
|||
)
|
||||
)
|
||||
}
|
||||
} else if (sharedViewModel.isAddToGroupStoryFlow) {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setMessage(getString(R.string.MediaReviewFragment__add_to_the_group_story, sharedViewModel.state.value!!.recipient!!.getDisplayName(requireContext())))
|
||||
.setPositiveButton(R.string.MediaReviewFragment__add_to_story) { _, _ -> performSend(contacts) }
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
.show()
|
||||
} else {
|
||||
performSend(contacts)
|
||||
}
|
||||
|
|
|
@ -231,18 +231,20 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF
|
|||
!recipient.isReleaseNotes();
|
||||
|
||||
ButtonStripPreference.State buttonStripState = new ButtonStripPreference.State(
|
||||
/* isMessageAvailable = */ !recipient.isBlocked() && !recipient.isSelf() && !recipient.isReleaseNotes(),
|
||||
/* isVideoAvailable = */ !recipient.isBlocked() && !recipient.isSelf() && recipient.isRegistered(),
|
||||
/* isAudioAvailable = */ isAudioAvailable,
|
||||
/* isMuteAvailable = */ false,
|
||||
/* isSearchAvailable = */ false,
|
||||
/* isAudioSecure = */ recipient.isRegistered(),
|
||||
/* isMuted = */ false
|
||||
/* isMessageAvailable = */ !recipient.isBlocked() && !recipient.isSelf() && !recipient.isReleaseNotes(),
|
||||
/* isVideoAvailable = */ !recipient.isBlocked() && !recipient.isSelf() && recipient.isRegistered(),
|
||||
/* isAudioAvailable = */ isAudioAvailable,
|
||||
/* isMuteAvailable = */ false,
|
||||
/* isSearchAvailable = */ false,
|
||||
/* isAudioSecure = */ recipient.isRegistered(),
|
||||
/* isMuted = */ false,
|
||||
/* isAddToStoryAvailable = */ false
|
||||
);
|
||||
|
||||
ButtonStripPreference.Model buttonStripModel = new ButtonStripPreference.Model(
|
||||
buttonStripState,
|
||||
DSLSettingsIcon.from(ContextUtil.requireDrawable(requireContext(), R.drawable.selectable_recipient_bottom_sheet_icon_button)),
|
||||
() -> Unit.INSTANCE,
|
||||
() -> {
|
||||
dismiss();
|
||||
viewModel.onMessageClicked(requireActivity());
|
||||
|
|
|
@ -0,0 +1,161 @@
|
|||
package org.thoughtcrime.securesms.stories.viewer
|
||||
|
||||
import android.content.Intent
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.CheckResult
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.fragment.app.Fragment
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Completable
|
||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import io.reactivex.rxjava3.subjects.CompletableSubject
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.ThreadTable
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.mediasend.MediaSendActivityResult
|
||||
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionActivity
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage
|
||||
import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.sharing.MultiShareArgs
|
||||
import org.thoughtcrime.securesms.sharing.MultiShareSender
|
||||
import org.thoughtcrime.securesms.sms.MessageSender
|
||||
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
||||
|
||||
/**
|
||||
* Delegate for dealing with sending stories directly to a group.
|
||||
*/
|
||||
class AddToGroupStoryDelegate(
|
||||
private val fragment: Fragment
|
||||
) {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(AddToGroupStoryDelegate::class.java)
|
||||
}
|
||||
|
||||
private val lifecycleDisposable = LifecycleDisposable().apply {
|
||||
bindTo(fragment.viewLifecycleOwner)
|
||||
}
|
||||
|
||||
private val addToStoryLauncher: ActivityResultLauncher<Intent> = fragment.registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
val data = result.data
|
||||
if (data == null) {
|
||||
Log.d(TAG, "No result data.")
|
||||
} else {
|
||||
Log.d(TAG, "Processing result...")
|
||||
val mediaSelectionResult: MediaSendActivityResult = MediaSendActivityResult.fromData(data)
|
||||
handleResult(mediaSelectionResult)
|
||||
}
|
||||
}
|
||||
|
||||
fun addToStory(recipientId: RecipientId) {
|
||||
val addToStoryIntent = MediaSelectionActivity.addToGroupStory(
|
||||
fragment.requireContext(),
|
||||
recipientId
|
||||
)
|
||||
|
||||
addToStoryLauncher.launch(addToStoryIntent)
|
||||
}
|
||||
|
||||
private fun handleResult(result: MediaSendActivityResult) {
|
||||
lifecycleDisposable += ResultHandler.handleResult(result)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeBy {
|
||||
Toast.makeText(fragment.requireContext(), R.string.TextStoryPostCreationFragment__sent_story, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches the send result on a background thread, isolated from the fragment.
|
||||
*/
|
||||
private object ResultHandler {
|
||||
|
||||
/**
|
||||
* Handles the result, completing after sending the message.
|
||||
*/
|
||||
@CheckResult
|
||||
fun handleResult(result: MediaSendActivityResult): Completable {
|
||||
Log.d(TAG, "Dispatching result handler.")
|
||||
val subject = CompletableSubject.create()
|
||||
SignalExecutors.BOUNDED_IO.execute {
|
||||
if (result.isPushPreUpload) {
|
||||
sendPreUploadedMedia(result)
|
||||
} else {
|
||||
sendNonPreUploadedMedia(result)
|
||||
}
|
||||
|
||||
subject.onComplete()
|
||||
}
|
||||
|
||||
return subject
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun sendPreUploadedMedia(result: MediaSendActivityResult) {
|
||||
Log.d(TAG, "Sending preupload media.")
|
||||
|
||||
val recipient = Recipient.resolved(result.recipientId)
|
||||
val secureMessage = OutgoingSecureMediaMessage(
|
||||
OutgoingMediaMessage(
|
||||
Recipient.resolved(result.recipientId),
|
||||
SlideDeck(),
|
||||
"",
|
||||
System.currentTimeMillis(),
|
||||
-1,
|
||||
0,
|
||||
false,
|
||||
ThreadTable.DistributionTypes.DEFAULT,
|
||||
result.storyType,
|
||||
null,
|
||||
false,
|
||||
null,
|
||||
emptyList(),
|
||||
emptyList(),
|
||||
result.mentions.toList(),
|
||||
null
|
||||
)
|
||||
)
|
||||
|
||||
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
|
||||
if (result.body.isNotEmpty()) {
|
||||
result.preUploadResults.forEach {
|
||||
SignalDatabase.attachments.updateAttachmentCaption(it.attachmentId, result.body)
|
||||
}
|
||||
}
|
||||
|
||||
MessageSender.sendPushWithPreUploadedMedia(
|
||||
ApplicationDependencies.getApplication(),
|
||||
secureMessage,
|
||||
result.preUploadResults,
|
||||
threadId
|
||||
) {
|
||||
Log.d(TAG, "Sent.")
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun sendNonPreUploadedMedia(result: MediaSendActivityResult) {
|
||||
Log.d(TAG, "Sending non-preupload media.")
|
||||
|
||||
val multiShareArgs = MultiShareArgs.Builder(setOf(ContactSearchKey.RecipientSearchKey.Story(result.recipientId)))
|
||||
.withMedia(result.nonUploadedMedia.toList())
|
||||
.withDraftText(result.body)
|
||||
.withMentions(result.mentions.toList())
|
||||
.build()
|
||||
|
||||
val results = MultiShareSender.sendSync(multiShareArgs)
|
||||
|
||||
Log.d(TAG, "Sent. Failures? ${results.containsFailures()}")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -63,6 +63,7 @@ import org.thoughtcrime.securesms.stories.StorySlateView
|
|||
import org.thoughtcrime.securesms.stories.StoryVolumeOverlayView
|
||||
import org.thoughtcrime.securesms.stories.dialogs.StoryContextMenu
|
||||
import org.thoughtcrime.securesms.stories.dialogs.StoryDialogs
|
||||
import org.thoughtcrime.securesms.stories.viewer.AddToGroupStoryDelegate
|
||||
import org.thoughtcrime.securesms.stories.viewer.StoryViewerViewModel
|
||||
import org.thoughtcrime.securesms.stories.viewer.StoryVolumeViewModel
|
||||
import org.thoughtcrime.securesms.stories.viewer.info.StoryInfoBottomSheetDialogFragment
|
||||
|
@ -109,6 +110,7 @@ class StoryViewerPageFragment :
|
|||
private lateinit var sendingBar: View
|
||||
private lateinit var storyNormalBottomGradient: View
|
||||
private lateinit var storyCaptionBottomGradient: View
|
||||
private lateinit var addToGroupStoryButton: MaterialButton
|
||||
|
||||
private lateinit var callback: Callback
|
||||
|
||||
|
@ -176,6 +178,7 @@ class StoryViewerPageFragment :
|
|||
val storyGradientTop: View = view.findViewById(R.id.story_gradient_top)
|
||||
val storyGradientBottom: View = view.findViewById(R.id.story_bottom_gradient_container)
|
||||
val storyVolumeOverlayView: StoryVolumeOverlayView = view.findViewById(R.id.story_volume_overlay)
|
||||
val addToGroupStoryButtonWrapper: View = view.findViewById(R.id.add_wrapper)
|
||||
|
||||
storyNormalBottomGradient = view.findViewById(R.id.story_gradient_bottom)
|
||||
storyCaptionBottomGradient = view.findViewById(R.id.story_caption_gradient)
|
||||
|
@ -187,6 +190,7 @@ class StoryViewerPageFragment :
|
|||
viewsAndReplies = view.findViewById(R.id.views_and_replies_bar)
|
||||
sendingBarTextView = view.findViewById(R.id.sending_text_view)
|
||||
sendingBar = view.findViewById(R.id.sending_bar)
|
||||
addToGroupStoryButton = view.findViewById(R.id.add)
|
||||
|
||||
storySlate.callback = this
|
||||
|
||||
|
@ -202,7 +206,8 @@ class StoryViewerPageFragment :
|
|||
progressBar,
|
||||
storyGradientTop,
|
||||
storyGradientBottom,
|
||||
storyCaptionContainer
|
||||
storyCaptionContainer,
|
||||
addToGroupStoryButtonWrapper
|
||||
)
|
||||
|
||||
senderAvatar.setFallbackPhotoProvider(FallbackPhotoProvider())
|
||||
|
@ -212,6 +217,11 @@ class StoryViewerPageFragment :
|
|||
requireActivity().onBackPressed()
|
||||
}
|
||||
|
||||
val addToGroupStoryDelegate = AddToGroupStoryDelegate(this)
|
||||
addToGroupStoryButton.setOnClickListener {
|
||||
addToGroupStoryDelegate.addToStory(storyViewerPageArgs.recipientId)
|
||||
}
|
||||
|
||||
val singleTapHandler = SingleTapHandler(
|
||||
cardWrapper,
|
||||
viewModel::goToNextPost,
|
||||
|
@ -384,6 +394,8 @@ class StoryViewerPageFragment :
|
|||
if (state.posts.isNotEmpty() && state.selectedPostIndex in state.posts.indices) {
|
||||
val post = state.posts[state.selectedPostIndex]
|
||||
|
||||
addToGroupStoryButton.visible = post.group != null
|
||||
|
||||
presentBottomBar(post, state.replyState, state.isReceiptsEnabled)
|
||||
presentSenderAvatar(senderAvatar, post)
|
||||
presentGroupAvatar(groupAvatar, post)
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:viewportWidth="16"
|
||||
android:viewportHeight="16">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M0,0h16v16h-16z"/>
|
||||
<path
|
||||
android:pathData="M13.074,6.414C13.374,6.414 13.618,6.171 13.618,5.871V4.084H15.404C15.704,4.084 15.948,3.841 15.948,3.54C15.948,3.24 15.704,2.997 15.404,2.997H13.618V1.21C13.618,0.91 13.374,0.667 13.074,0.667C12.774,0.667 12.53,0.91 12.53,1.21V2.997H10.744C10.443,2.997 10.2,3.24 10.2,3.54C10.2,3.841 10.443,4.084 10.744,4.084H12.53V5.871C12.53,6.171 12.774,6.414 13.074,6.414Z"
|
||||
android:fillColor="#000000"/>
|
||||
<path
|
||||
android:pathData="M6.045,1.708H7.991V2.958H6.045C5.944,2.958 5.848,2.999 5.777,3.071L4.304,4.574H2.625C1.866,4.574 1.25,5.189 1.25,5.949V13C1.25,13.759 1.866,14.375 2.625,14.375H11.958C12.718,14.375 13.333,13.759 13.333,13V8.521H14.583V13C14.583,14.45 13.408,15.625 11.958,15.625H2.625C1.175,15.625 0,14.45 0,13V5.949C0,4.499 1.175,3.324 2.625,3.324H3.779L4.885,2.196C5.19,1.884 5.608,1.708 6.045,1.708Z"
|
||||
android:fillColor="#000000"/>
|
||||
<path
|
||||
android:pathData="M7.292,5.042C5.106,5.042 3.333,6.814 3.333,9C3.333,11.186 5.106,12.958 7.292,12.958C9.478,12.958 11.25,11.186 11.25,9C11.25,6.814 9.478,5.042 7.292,5.042ZM4.583,9C4.583,7.504 5.796,6.292 7.292,6.292C8.788,6.292 10,7.504 10,9C10,10.496 8.788,11.708 7.292,11.708C5.796,11.708 4.583,10.496 4.583,9Z"
|
||||
android:fillColor="#000000"
|
||||
android:fillType="evenOdd"/>
|
||||
</group>
|
||||
</vector>
|
|
@ -0,0 +1,20 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M0,0h24v24h-24z"/>
|
||||
<path
|
||||
android:pathData="M19.69,9.621C20.14,9.621 20.505,9.256 20.505,8.806V6.126H23.185C23.635,6.126 24,5.761 24,5.311C24,4.86 23.635,4.495 23.185,4.495H20.505V1.815C20.505,1.365 20.14,1 19.69,1C19.239,1 18.874,1.365 18.874,1.815V4.495H16.194C15.744,4.495 15.379,4.86 15.379,5.311C15.379,5.761 15.744,6.126 16.194,6.126H18.874V8.806C18.874,9.256 19.239,9.621 19.69,9.621Z"
|
||||
android:fillColor="#000000"/>
|
||||
<path
|
||||
android:pathData="M13.049,2.631H9.059C8.85,2.632 8.644,2.679 8.455,2.769C8.267,2.859 8.1,2.99 7.969,3.153L6.524,4.961H5.126C4.575,4.96 4.029,5.068 3.52,5.278C3.01,5.488 2.547,5.797 2.158,6.187C1.768,6.577 1.459,7.039 1.249,7.549C1.038,8.058 0.931,8.604 0.932,9.155V17.544C0.931,18.095 1.038,18.641 1.249,19.15C1.459,19.66 1.768,20.122 2.158,20.512C2.547,20.902 3.01,21.211 3.52,21.421C4.029,21.631 4.575,21.739 5.126,21.738H17.243C17.794,21.739 18.34,21.631 18.849,21.421C19.359,21.211 19.822,20.902 20.211,20.512C20.601,20.122 20.91,19.66 21.12,19.15C21.33,18.641 21.438,18.095 21.437,17.544V14.281H19.806V17.544C19.806,18.223 19.536,18.875 19.055,19.356C18.574,19.837 17.923,20.107 17.243,20.107H5.126C4.446,20.107 3.794,19.837 3.314,19.356C2.833,18.875 2.563,18.223 2.563,17.544V9.155C2.563,8.476 2.833,7.824 3.314,7.343C3.794,6.862 4.446,6.592 5.126,6.592H7.307L7.801,5.977L9.171,4.262H13.049V2.631Z"
|
||||
android:fillColor="#000000"/>
|
||||
<path
|
||||
android:pathData="M8.078,8.234C8.997,7.619 10.078,7.291 11.184,7.291C12.668,7.291 14.09,7.88 15.139,8.929C16.188,9.978 16.777,11.4 16.777,12.884C16.777,13.99 16.449,15.071 15.834,15.99C15.22,16.91 14.346,17.627 13.325,18.05C12.303,18.473 11.178,18.584 10.094,18.368C9.009,18.153 8.012,17.62 7.23,16.838C6.448,16.056 5.915,15.059 5.7,13.974C5.484,12.89 5.595,11.765 6.018,10.743C6.441,9.722 7.158,8.848 8.078,8.234ZM13.385,9.59C12.734,9.155 11.968,8.922 11.184,8.922C10.135,8.925 9.129,9.343 8.386,10.085C7.644,10.828 7.226,11.834 7.223,12.884C7.223,13.667 7.456,14.433 7.891,15.084C8.326,15.736 8.945,16.243 9.669,16.543C10.392,16.843 11.189,16.921 11.957,16.768C12.726,16.616 13.431,16.238 13.985,15.684C14.539,15.13 14.917,14.425 15.069,13.656C15.222,12.888 15.144,12.091 14.844,11.368C14.544,10.644 14.037,10.025 13.385,9.59Z"
|
||||
android:fillColor="#000000"
|
||||
android:fillType="evenOdd"/>
|
||||
</group>
|
||||
</vector>
|
|
@ -17,6 +17,29 @@
|
|||
android:paddingTop="24dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/button_strip_add_to_story_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="2dp"
|
||||
android:layout_marginEnd="2dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/add_to_story"
|
||||
style="@style/Signal.Widget.ImageView.ActionButton"
|
||||
android:contentDescription="@string/ConversationSettingsFragment__story"
|
||||
app:srcCompat="@drawable/add_to_story_24" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/add_to_story_label"
|
||||
style="@style/Signal.Widget.TextView.ActionButton"
|
||||
android:text="@string/ConversationSettingsFragment__story" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/button_strip_message_container"
|
||||
android:layout_width="0dp"
|
||||
|
|
|
@ -201,6 +201,42 @@
|
|||
app:srcCompat="@drawable/ic_x_24"
|
||||
app:tint="@color/core_white" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/add_wrapper"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/add"
|
||||
style="@style/Widget.MaterialComponents.Button.UnelevatedButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:insetTop="8dp"
|
||||
android:insetBottom="8dp"
|
||||
android:minWidth="0dp"
|
||||
android:minHeight="48dp"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:text="@string/StoryViewerPageFragment__add"
|
||||
android:textAllCaps="false"
|
||||
android:textAppearance="@style/Signal.Text.BodyMedium"
|
||||
android:textColor="@color/signal_dark_colorOnSurface"
|
||||
android:visibility="gone"
|
||||
app:backgroundTint="@color/signal_colorSurfaceVariant_64"
|
||||
app:backgroundTintMode="src_in"
|
||||
app:cornerRadius="16dp"
|
||||
app:icon="@drawable/add_to_story_16"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="8dp"
|
||||
app:iconSize="16dp"
|
||||
app:iconTint="@color/signal_dark_colorOnSurface"
|
||||
tools:visibility="visible" />
|
||||
</FrameLayout>
|
||||
|
||||
<org.thoughtcrime.securesms.components.AvatarImageView
|
||||
android:id="@+id/sender_avatar"
|
||||
android:layout_width="32dp"
|
||||
|
|
|
@ -51,6 +51,7 @@
|
|||
<color name="signal_colorSecondaryContainer_12">#1F414659</color>
|
||||
<color name="signal_colorSurface_60">#991B1C1F</color>
|
||||
<color name="signal_colorSurfaceVariant_38">#61303133</color>
|
||||
<color name="signal_colorSurfaceVariant_64">#A3303133</color>
|
||||
<color name="signal_colorOnSurface_12">#1FE2E1E5</color>
|
||||
<color name="signal_colorOnSurfaceVariant_60">#99BEBFC5</color>
|
||||
<color name="signal_colorOnBackground_60">#99E2E1E5</color>
|
||||
|
|
|
@ -51,6 +51,7 @@
|
|||
<color name="signal_colorSecondaryContainer_12">#1FDCE5F9</color>
|
||||
<color name="signal_colorSurface_60">#99FBFCFF</color>
|
||||
<color name="signal_colorSurfaceVariant_38">#61E7EBF3</color>
|
||||
<color name="signal_colorSurfaceVariant_64">#A3E7EBF3</color>
|
||||
<color name="signal_colorOnSurface_12">#1F1B1B1D</color>
|
||||
<color name="signal_colorOnSurfaceVariant_60">#99545863</color>
|
||||
<color name="signal_colorOnBackground_60">#991B1D1D</color>
|
||||
|
|
|
@ -4216,11 +4216,16 @@
|
|||
<string name="NotificationsSettingsFragment__unknown_ringtone">Unknown ringtone</string>
|
||||
|
||||
<!-- ConversationSettingsFragment -->
|
||||
<!-- Dialog title displayed when non-admin tries to add a story to an audience group -->
|
||||
<string name="ConversationSettingsFragment__cant_add_to_group_story">Can\'t add to group story</string>
|
||||
<!-- Dialog message displayed when non-admin tries to add a story to an audience group -->
|
||||
<string name="ConversationSettingsFragment__only_admins_of_this_group_can_add_to_its_story">Only admins of this group can add to its story</string>
|
||||
<!-- Error toasted when no activity can handle the add contact intent -->
|
||||
<string name="ConversationSettingsFragment__contacts_app_not_found">Contacts app not found</string>
|
||||
<string name="ConversationSettingsFragment__send_message">Send message</string>
|
||||
<string name="ConversationSettingsFragment__start_video_call">Start video call</string>
|
||||
<string name="ConversationSettingsFragment__start_audio_call">Start audio call</string>
|
||||
<string name="ConversationSettingsFragment__story">Story</string>
|
||||
<string name="ConversationSettingsFragment__message">Message</string>
|
||||
<string name="ConversationSettingsFragment__video">Video</string>
|
||||
<string name="ConversationSettingsFragment__audio">Audio</string>
|
||||
|
@ -4385,6 +4390,10 @@
|
|||
<string name="MultiselectForwardFragment__limit_reached">Limit reached</string>
|
||||
|
||||
<!-- Media V2 -->
|
||||
<!-- Dialog message when sending a story via an add to group story button -->
|
||||
<string name="MediaReviewFragment__add_to_the_group_story">Add to the group story \"%s\"</string>
|
||||
<!-- Positive dialog action when sending a story via an add to group story button -->
|
||||
<string name ="MediaReviewFragment__add_to_story">Add to story</string>
|
||||
<string name="MediaReviewFragment__add_a_message">Add a message</string>
|
||||
<string name="MediaReviewFragment__add_a_reply">Add a reply</string>
|
||||
<string name="MediaReviewFragment__send_to">Send to</string>
|
||||
|
@ -4893,6 +4902,8 @@
|
|||
<item quantity="one">%1$d reply</item>
|
||||
<item quantity="other">%1$d replies</item>
|
||||
</plurals>
|
||||
<!-- Label on group stories to add a story -->
|
||||
<string name="StoryViewerPageFragment__add">Add</string>
|
||||
<!-- Used when view receipts are disabled -->
|
||||
<string name="StoryViewerPageFragment__views_off">Views off</string>
|
||||
<!-- Used to join views and replies when both exist on a story item -->
|
||||
|
|
Ładowanie…
Reference in New Issue