Add viewer count and list to 'All Signal Connections'.
This commit is contained in:
parent
c239ba1e35
commit
842626e96c
20 changed files with 408 additions and 27 deletions
|
@ -11,6 +11,7 @@ import android.util.AttributeSet;
|
|||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import org.signal.core.util.BreakIteratorCompat;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.emoji.SimpleEmojiTextView;
|
||||
|
@ -19,6 +20,7 @@ import org.thoughtcrime.securesms.util.ContextUtil;
|
|||
import org.thoughtcrime.securesms.util.SpanUtil;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Objects;
|
||||
|
||||
public class FromTextView extends SimpleEmojiTextView {
|
||||
|
|
|
@ -52,7 +52,8 @@ class ContactSearchConfiguration private constructor(
|
|||
val includeSelf: Boolean,
|
||||
val transportType: TransportType,
|
||||
override val includeHeader: Boolean,
|
||||
override val expandConfig: ExpandConfig? = null
|
||||
override val expandConfig: ExpandConfig? = null,
|
||||
val includeLetterHeaders: Boolean = false
|
||||
) : Section(SectionKey.INDIVIDUALS)
|
||||
|
||||
/**
|
||||
|
|
|
@ -23,7 +23,11 @@ sealed class ContactSearchData(val contactSearchKey: ContactSearchKey) {
|
|||
/**
|
||||
* A row displaying a known recipient.
|
||||
*/
|
||||
data class KnownRecipient(val recipient: Recipient, val shortSummary: Boolean = false) : ContactSearchData(ContactSearchKey.RecipientSearchKey.KnownRecipient(recipient.id))
|
||||
data class KnownRecipient(
|
||||
val recipient: Recipient,
|
||||
val shortSummary: Boolean = false,
|
||||
val headerLetter: String? = null
|
||||
) : ContactSearchData(ContactSearchKey.RecipientSearchKey.KnownRecipient(recipient.id))
|
||||
|
||||
/**
|
||||
* A row containing a title for a given section
|
||||
|
|
|
@ -11,6 +11,7 @@ import org.thoughtcrime.securesms.components.AvatarImageView
|
|||
import org.thoughtcrime.securesms.components.FromTextView
|
||||
import org.thoughtcrime.securesms.components.menu.ActionItem
|
||||
import org.thoughtcrime.securesms.components.menu.SignalContextMenu
|
||||
import org.thoughtcrime.securesms.contacts.LetterHeaderDecoration
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListPrivacyMode
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
|
@ -226,7 +227,10 @@ object ContactSearchItems {
|
|||
}
|
||||
}
|
||||
|
||||
private class KnownRecipientViewHolder(itemView: View, displayCheckBox: Boolean, onClick: RecipientClickListener) : BaseRecipientViewHolder<RecipientModel, ContactSearchData.KnownRecipient>(itemView, displayCheckBox, onClick) {
|
||||
private class KnownRecipientViewHolder(itemView: View, displayCheckBox: Boolean, onClick: RecipientClickListener) : BaseRecipientViewHolder<RecipientModel, ContactSearchData.KnownRecipient>(itemView, displayCheckBox, onClick), LetterHeaderDecoration.LetterHeaderItem {
|
||||
|
||||
private var headerLetter: String? = null
|
||||
|
||||
override fun isSelected(model: RecipientModel): Boolean = model.isSelected
|
||||
override fun getData(model: RecipientModel): ContactSearchData.KnownRecipient = model.knownRecipient
|
||||
override fun getRecipient(model: RecipientModel): Recipient = model.knownRecipient.recipient
|
||||
|
@ -235,10 +239,16 @@ object ContactSearchItems {
|
|||
|
||||
if (model.shortSummary && recipient.isGroup) {
|
||||
val count = recipient.participantIds.size
|
||||
number.setText(context.resources.getQuantityString(R.plurals.ContactSearchItems__group_d_members, count, count))
|
||||
number.text = context.resources.getQuantityString(R.plurals.ContactSearchItems__group_d_members, count, count)
|
||||
} else {
|
||||
super.bindNumberField(model)
|
||||
}
|
||||
|
||||
headerLetter = model.knownRecipient.headerLetter
|
||||
}
|
||||
|
||||
override fun getHeaderLetter(): String? {
|
||||
return headerLetter
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import org.thoughtcrime.securesms.database.model.DistributionListPrivacyMode
|
|||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.keyvalue.StorySend
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
|
@ -129,6 +130,13 @@ class ContactSearchPagedDataSource(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getNonGroupHeaderLetterMap(section: ContactSearchConfiguration.Section.Individuals, query: String?): Map<RecipientId, String> {
|
||||
return when (section.transportType) {
|
||||
ContactSearchConfiguration.TransportType.PUSH -> contactSearchPagedDataSourceRepository.querySignalContactLetterHeaders(query, section.includeSelf)
|
||||
else -> error("This has only been implemented for push recipients.")
|
||||
}
|
||||
}
|
||||
|
||||
private fun getStoriesSearchIterator(query: String?): ContactSearchIterator<Cursor> {
|
||||
return CursorSearchIterator(contactSearchPagedDataSourceRepository.getStories(query))
|
||||
}
|
||||
|
@ -193,6 +201,12 @@ class ContactSearchPagedDataSource(
|
|||
}
|
||||
|
||||
private fun getNonGroupContactsData(section: ContactSearchConfiguration.Section.Individuals, query: String?, startIndex: Int, endIndex: Int): List<ContactSearchData> {
|
||||
val headerMap: Map<RecipientId, String> = if (section.includeLetterHeaders) {
|
||||
getNonGroupHeaderLetterMap(section, query)
|
||||
} else {
|
||||
emptyMap()
|
||||
}
|
||||
|
||||
return getNonGroupSearchIterator(section, query).use { records ->
|
||||
readContactData(
|
||||
records = records,
|
||||
|
@ -201,7 +215,8 @@ class ContactSearchPagedDataSource(
|
|||
startIndex = startIndex,
|
||||
endIndex = endIndex,
|
||||
recordMapper = {
|
||||
ContactSearchData.KnownRecipient(contactSearchPagedDataSourceRepository.getRecipientFromRecipientCursor(it))
|
||||
val recipient = contactSearchPagedDataSourceRepository.getRecipientFromRecipientCursor(it)
|
||||
ContactSearchData.KnownRecipient(recipient, headerLetter = headerMap[recipient.id])
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -36,6 +36,10 @@ open class ContactSearchPagedDataSourceRepository(
|
|||
return contactRepository.querySignalContacts(query ?: "", includeSelf)
|
||||
}
|
||||
|
||||
open fun querySignalContactLetterHeaders(query: String?, includeSelf: Boolean): Map<RecipientId, String> {
|
||||
return SignalDatabase.recipients.querySignalContactLetterHeaders(query ?: "", includeSelf)
|
||||
}
|
||||
|
||||
open fun queryNonSignalContacts(query: String?): Cursor? {
|
||||
return contactRepository.queryNonSignalContacts(query ?: "")
|
||||
}
|
||||
|
|
|
@ -3112,6 +3112,44 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
|||
return readableDatabase.query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, orderBy)
|
||||
}
|
||||
|
||||
fun querySignalContactLetterHeaders(inputQuery: String, includeSelf: Boolean): Map<RecipientId, String> {
|
||||
val searchSelection = ContactSearchSelection.Builder()
|
||||
.withRegistered(true)
|
||||
.withGroups(false)
|
||||
.excludeId(if (includeSelf) null else Recipient.self().id)
|
||||
.withSearchQuery(inputQuery)
|
||||
.build()
|
||||
|
||||
return readableDatabase.query(
|
||||
"""
|
||||
SELECT
|
||||
_id,
|
||||
UPPER(SUBSTR($SORT_NAME, 0, 2)) AS letter_header
|
||||
FROM (
|
||||
SELECT ${SEARCH_PROJECTION.joinToString(", ")}
|
||||
FROM recipient
|
||||
WHERE ${searchSelection.where}
|
||||
ORDER BY $SORT_NAME, $SYSTEM_JOINED_NAME, $SEARCH_PROFILE_NAME, $PHONE
|
||||
)
|
||||
GROUP BY letter_header
|
||||
""".trimIndent(),
|
||||
searchSelection.args
|
||||
).use { cursor ->
|
||||
if (cursor.count == 0) {
|
||||
emptyMap()
|
||||
} else {
|
||||
val resultsMap = mutableMapOf<RecipientId, String>()
|
||||
while (cursor.moveToNext()) {
|
||||
cursor.requireString("letter_header")?.let {
|
||||
resultsMap[RecipientId.from(cursor.requireLong(ID))] = it
|
||||
}
|
||||
}
|
||||
|
||||
resultsMap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getNonSignalContacts(): Cursor? {
|
||||
val searchSelection = ContactSearchSelection.Builder().withNonRegistered(true)
|
||||
.withGroups(false)
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
package org.thoughtcrime.securesms.stories.settings.connections
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.ViewBinderDelegate
|
||||
import org.thoughtcrime.securesms.components.WrapperDialogFragment
|
||||
import org.thoughtcrime.securesms.contacts.LetterHeaderDecoration
|
||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchConfiguration
|
||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchMediator
|
||||
import org.thoughtcrime.securesms.databinding.ViewAllSignalConnectionsFragmentBinding
|
||||
import org.thoughtcrime.securesms.groups.SelectionLimits
|
||||
|
||||
class ViewAllSignalConnectionsFragment : Fragment(R.layout.view_all_signal_connections_fragment) {
|
||||
|
||||
private val binding by ViewBinderDelegate(ViewAllSignalConnectionsFragmentBinding::bind)
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
binding.recycler.addItemDecoration(LetterHeaderDecoration(requireContext()) { false })
|
||||
binding.toolbar.setNavigationOnClickListener {
|
||||
requireActivity().onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
|
||||
ContactSearchMediator(
|
||||
fragment = this,
|
||||
recyclerView = binding.recycler,
|
||||
selectionLimits = SelectionLimits(0, 0),
|
||||
displayCheckBox = false,
|
||||
mapStateToConfiguration = { getConfiguration() },
|
||||
performSafetyNumberChecks = false
|
||||
)
|
||||
}
|
||||
|
||||
private fun getConfiguration(): ContactSearchConfiguration {
|
||||
return ContactSearchConfiguration.build {
|
||||
addSection(
|
||||
ContactSearchConfiguration.Section.Individuals(
|
||||
includeHeader = false,
|
||||
includeSelf = false,
|
||||
includeLetterHeaders = true,
|
||||
transportType = ContactSearchConfiguration.TransportType.PUSH
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class Dialog : WrapperDialogFragment() {
|
||||
override fun getWrappedFragment(): Fragment {
|
||||
return ViewAllSignalConnectionsFragment()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun show(fragmentManager: FragmentManager) {
|
||||
Dialog().show(fragmentManager, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package org.thoughtcrime.securesms.stories.settings.my
|
||||
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.databinding.AllSignalConnectionsRowItemBinding
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.BindingFactory
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.BindingViewHolder
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
|
||||
import org.thoughtcrime.securesms.util.visible
|
||||
|
||||
/**
|
||||
* AllSignalConnections privacy setting row item with "View" support
|
||||
*/
|
||||
object AllSignalConnectionsRowItem {
|
||||
|
||||
private const val IS_CHECKED = 0
|
||||
private const val IS_COUNT = 1
|
||||
|
||||
fun register(mappingAdapter: MappingAdapter) {
|
||||
mappingAdapter.registerFactory(Model::class.java, BindingFactory(::ViewHolder, AllSignalConnectionsRowItemBinding::inflate))
|
||||
}
|
||||
|
||||
class Model(
|
||||
val isChecked: Boolean,
|
||||
val count: Int,
|
||||
val onRowClicked: () -> Unit,
|
||||
val onViewClicked: () -> Unit
|
||||
) : MappingModel<Model> {
|
||||
|
||||
override fun areItemsTheSame(newItem: Model): Boolean = true
|
||||
|
||||
override fun areContentsTheSame(newItem: Model): Boolean = isChecked == newItem.isChecked && count == newItem.count
|
||||
|
||||
override fun getChangePayload(newItem: Model): Any? {
|
||||
val isCheckedDifferent = isChecked != newItem.isChecked
|
||||
val isCountDifferent = count != newItem.count
|
||||
|
||||
return when {
|
||||
isCheckedDifferent && !isCountDifferent -> IS_CHECKED
|
||||
!isCheckedDifferent && isCountDifferent -> IS_COUNT
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ViewHolder(binding: AllSignalConnectionsRowItemBinding) : BindingViewHolder<Model, AllSignalConnectionsRowItemBinding>(binding) {
|
||||
override fun bind(model: Model) {
|
||||
binding.root.setOnClickListener { model.onRowClicked() }
|
||||
binding.view.setOnClickListener { model.onViewClicked() }
|
||||
|
||||
when {
|
||||
payload.contains(IS_COUNT) -> presentCount(model.count)
|
||||
payload.contains(IS_CHECKED) -> presentSelected(model.isChecked)
|
||||
else -> {
|
||||
presentCount(model.count)
|
||||
presentSelected(model.isChecked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun presentCount(count: Int) {
|
||||
binding.count.visible = count > 0
|
||||
binding.count.text = context.resources.getQuantityString(R.plurals.MyStorySettingsFragment__viewers, count, count)
|
||||
}
|
||||
|
||||
private fun presentSelected(isChecked: Boolean) {
|
||||
binding.radio.isChecked = isChecked
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
|||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||
import org.thoughtcrime.securesms.components.settings.configure
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListPrivacyMode
|
||||
import org.thoughtcrime.securesms.stories.settings.connections.ViewAllSignalConnectionsFragment
|
||||
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
|
@ -38,6 +39,7 @@ class MyStorySettingsFragment : DSLSettingsFragment(
|
|||
}
|
||||
|
||||
override fun bindAdapter(adapter: MappingAdapter) {
|
||||
AllSignalConnectionsRowItem.register(adapter)
|
||||
viewModel.state.observe(viewLifecycleOwner) { state ->
|
||||
adapter.submitList(getConfiguration(state).toMappingModelList())
|
||||
}
|
||||
|
@ -47,14 +49,18 @@ class MyStorySettingsFragment : DSLSettingsFragment(
|
|||
return configure {
|
||||
sectionHeaderPref(R.string.MyStorySettingsFragment__who_can_view_this_story)
|
||||
|
||||
radioPref(
|
||||
title = DSLSettingsText.from(R.string.MyStorySettingsFragment__all_signal_connections),
|
||||
summary = DSLSettingsText.from(R.string.MyStorySettingsFragment__share_with_all_connections),
|
||||
isChecked = state.myStoryPrivacyState.privacyMode == DistributionListPrivacyMode.ALL,
|
||||
onClick = {
|
||||
lifecycleDisposable += viewModel.setMyStoryPrivacyMode(DistributionListPrivacyMode.ALL)
|
||||
.subscribe()
|
||||
}
|
||||
customPref(
|
||||
AllSignalConnectionsRowItem.Model(
|
||||
isChecked = state.myStoryPrivacyState.privacyMode == DistributionListPrivacyMode.ALL,
|
||||
count = state.allSignalConnectionsCount,
|
||||
onRowClicked = {
|
||||
lifecycleDisposable += viewModel.setMyStoryPrivacyMode(DistributionListPrivacyMode.ALL)
|
||||
.subscribe()
|
||||
},
|
||||
onViewClicked = {
|
||||
ViewAllSignalConnectionsFragment.Dialog.show(parentFragmentManager)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
val exceptText = if (state.myStoryPrivacyState.privacyMode == DistributionListPrivacyMode.ALL_EXCEPT) {
|
||||
|
|
|
@ -22,11 +22,15 @@ class MyStorySettingsRepository {
|
|||
}
|
||||
|
||||
fun observeChooseInitialPrivacy(): Observable<ChooseInitialMyStoryMembershipState> {
|
||||
return Single.fromCallable { SignalDatabase.distributionLists.getRecipientId(DistributionListId.MY_STORY)!! }
|
||||
return Single
|
||||
.fromCallable { SignalDatabase.distributionLists.getRecipientId(DistributionListId.MY_STORY)!! }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.flatMapObservable { recipientId ->
|
||||
Recipient.observable(recipientId)
|
||||
val allSignalConnectionsCount = getAllSignalConnectionsCount().toObservable()
|
||||
val stateWithoutCount = Recipient.observable(recipientId)
|
||||
.flatMap { Observable.just(ChooseInitialMyStoryMembershipState(recipientId = recipientId, privacyState = getStoryPrivacyState())) }
|
||||
|
||||
Observable.combineLatest(allSignalConnectionsCount, stateWithoutCount) { count, state -> state.copy(allSignalConnectionsCount = count) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,6 +54,12 @@ class MyStorySettingsRepository {
|
|||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun getAllSignalConnectionsCount(): Single<Int> {
|
||||
return Single.fromCallable {
|
||||
SignalDatabase.recipients.getSignalContactsCount(false)
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun getStoryPrivacyState(): MyStoryPrivacyState {
|
||||
val privacyData: DistributionListPrivacyData = SignalDatabase.distributionLists.getPrivacyData(DistributionListId.MY_STORY)
|
||||
|
|
|
@ -2,5 +2,6 @@ package org.thoughtcrime.securesms.stories.settings.my
|
|||
|
||||
data class MyStorySettingsState(
|
||||
val myStoryPrivacyState: MyStoryPrivacyState = MyStoryPrivacyState(),
|
||||
val areRepliesAndReactionsEnabled: Boolean = false
|
||||
val areRepliesAndReactionsEnabled: Boolean = false,
|
||||
val allSignalConnectionsCount: Int = 0
|
||||
)
|
||||
|
|
|
@ -25,6 +25,8 @@ class MyStorySettingsViewModel @JvmOverloads constructor(private val repository:
|
|||
.subscribe { myStoryPrivacyState -> store.update { it.copy(myStoryPrivacyState = myStoryPrivacyState) } }
|
||||
disposables += repository.getRepliesAndReactionsEnabled()
|
||||
.subscribe { repliesAndReactionsEnabled -> store.update { it.copy(areRepliesAndReactionsEnabled = repliesAndReactionsEnabled) } }
|
||||
disposables += repository.getAllSignalConnectionsCount()
|
||||
.subscribe { allSignalConnectionsCount -> store.update { it.copy(allSignalConnectionsCount = allSignalConnectionsCount) } }
|
||||
}
|
||||
|
||||
fun setRepliesAndReactionsEnabled(repliesAndReactionsEnabled: Boolean) {
|
||||
|
|
|
@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.components.FixedRoundedCornerBottomSheetDialog
|
|||
import org.thoughtcrime.securesms.components.WrapperDialogFragment
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListPrivacyMode
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.stories.settings.connections.ViewAllSignalConnectionsFragment
|
||||
import org.thoughtcrime.securesms.stories.settings.select.BaseStoryRecipientSelectionFragment
|
||||
import org.thoughtcrime.securesms.util.BottomSheetUtil
|
||||
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
||||
|
@ -42,9 +43,12 @@ class ChooseInitialMyStoryMembershipBottomSheetDialogFragment :
|
|||
private lateinit var allExceptRadio: MaterialRadioButton
|
||||
private lateinit var onlyWitRadio: MaterialRadioButton
|
||||
|
||||
private lateinit var allCount: TextView
|
||||
private lateinit var allExceptCount: TextView
|
||||
private lateinit var onlyWithCount: TextView
|
||||
|
||||
private lateinit var allView: View
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.choose_initial_my_story_membership_fragment, container, false)
|
||||
}
|
||||
|
@ -58,9 +62,15 @@ class ChooseInitialMyStoryMembershipBottomSheetDialogFragment :
|
|||
allExceptRadio = view.findViewById(R.id.choose_initial_my_story_all_signal_connnections_except_radio)
|
||||
onlyWitRadio = view.findViewById(R.id.choose_initial_my_story_only_share_with_radio)
|
||||
|
||||
allCount = view.findViewById(R.id.choose_initial_my_story_all_signal_connnections_count)
|
||||
allExceptCount = view.findViewById(R.id.choose_initial_my_story_all_signal_connnections_except_count)
|
||||
onlyWithCount = view.findViewById(R.id.choose_initial_my_story_only_share_with_count)
|
||||
|
||||
allView = view.findViewById(R.id.choose_initial_my_story_all_signal_connnections_view)
|
||||
allView.setOnClickListener {
|
||||
ViewAllSignalConnectionsFragment.Dialog.show(parentFragmentManager)
|
||||
}
|
||||
|
||||
val save = view.findViewById<View>(R.id.choose_initial_my_story_save).apply {
|
||||
isEnabled = false
|
||||
}
|
||||
|
@ -76,6 +86,9 @@ class ChooseInitialMyStoryMembershipBottomSheetDialogFragment :
|
|||
allExceptCount.visible = allExceptRadio.isChecked
|
||||
onlyWithCount.visible = onlyWitRadio.isChecked
|
||||
|
||||
allCount.visible = state.allSignalConnectionsCount > 0
|
||||
allCount.text = resources.getQuantityString(R.plurals.MyStorySettingsFragment__viewers, state.allSignalConnectionsCount, state.allSignalConnectionsCount)
|
||||
|
||||
when (state.privacyState.privacyMode) {
|
||||
DistributionListPrivacyMode.ALL_EXCEPT -> allExceptCount.text = resources.getQuantityString(R.plurals.MyStorySettingsFragment__d_people_excluded, state.privacyState.connectionCount, state.privacyState.connectionCount)
|
||||
DistributionListPrivacyMode.ONLY_WITH -> onlyWithCount.text = resources.getQuantityString(R.plurals.MyStorySettingsFragment__d_people, state.privacyState.connectionCount, state.privacyState.connectionCount)
|
||||
|
|
|
@ -3,4 +3,8 @@ package org.thoughtcrime.securesms.stories.settings.privacy
|
|||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.stories.settings.my.MyStoryPrivacyState
|
||||
|
||||
data class ChooseInitialMyStoryMembershipState(val recipientId: RecipientId? = null, val privacyState: MyStoryPrivacyState = MyStoryPrivacyState())
|
||||
data class ChooseInitialMyStoryMembershipState(
|
||||
val recipientId: RecipientId? = null,
|
||||
val privacyState: MyStoryPrivacyState = MyStoryPrivacyState(),
|
||||
val allSignalConnectionsCount: Int = 0
|
||||
)
|
||||
|
|
63
app/src/main/res/layout/all_signal_connections_row_item.xml
Normal file
63
app/src/main/res/layout/all_signal_connections_row_item.xml
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/row"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="?selectableItemBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="56dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="@dimen/dsl_settings_gutter">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/radio"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="false"
|
||||
android:theme="@style/Signal.Widget.CompoundButton.RadioButton"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/message"
|
||||
style="@style/Signal.Text.BodyLarge"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:text="@string/ChooseInitialMyStoryMembershipFragment__all_signal_connections"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toTopOf="@+id/count"
|
||||
app:layout_constraintEnd_toStartOf="@id/view"
|
||||
app:layout_constraintStart_toEndOf="@id/radio"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/count"
|
||||
style="@style/Signal.Text.BodyMedium"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/signal_colorOnSurfaceVariant"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@+id/message"
|
||||
app:layout_constraintStart_toStartOf="@+id/message"
|
||||
app:layout_constraintTop_toBottomOf="@+id/message"
|
||||
tools:text="Asdf"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/view"
|
||||
style="@style/Widget.Signal.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/MyStorySettingsFragment__view"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -1,10 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
tools:viewBindingIgnore="true"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
tools:viewBindingIgnore="true">
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:layout_width="match_parent"
|
||||
|
@ -41,7 +41,7 @@
|
|||
android:text="@string/ChooseInitialMyStoryMembershipFragment__choose_who_can_see_posts_to_my_story_you_can_always_make_changes_in_settings"
|
||||
android:textColor="@color/signal_colorOnSurfaceVariant" />
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/choose_initial_my_story_all_signal_connnections_row"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -50,22 +50,56 @@
|
|||
android:gravity="center_vertical"
|
||||
android:minHeight="56dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="24dp">
|
||||
android:paddingStart="@dimen/dsl_settings_gutter">
|
||||
|
||||
<com.google.android.material.radiobutton.MaterialRadioButton
|
||||
android:id="@+id/choose_initial_my_story_all_signal_connnections_radio"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="false" />
|
||||
android:clickable="false"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/choose_initial_my_story_all_signal_connnections_message"
|
||||
style="@style/Signal.Text.BodyLarge"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:text="@string/ChooseInitialMyStoryMembershipFragment__all_signal_connections" />
|
||||
android:text="@string/ChooseInitialMyStoryMembershipFragment__all_signal_connections"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toTopOf="@+id/choose_initial_my_story_all_signal_connnections_count"
|
||||
app:layout_constraintEnd_toStartOf="@id/choose_initial_my_story_all_signal_connnections_view"
|
||||
app:layout_constraintStart_toEndOf="@id/choose_initial_my_story_all_signal_connnections_radio"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
<TextView
|
||||
android:id="@+id/choose_initial_my_story_all_signal_connnections_count"
|
||||
style="@style/Signal.Text.BodyMedium"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/signal_colorOnSurfaceVariant"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@+id/choose_initial_my_story_all_signal_connnections_message"
|
||||
app:layout_constraintStart_toStartOf="@+id/choose_initial_my_story_all_signal_connnections_message"
|
||||
app:layout_constraintTop_toBottomOf="@+id/choose_initial_my_story_all_signal_connnections_message"
|
||||
tools:text="Asdf"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/choose_initial_my_story_all_signal_connnections_view"
|
||||
style="@style/Widget.Signal.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/MyStorySettingsFragment__view"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/choose_initial_my_story_all_signal_connnections_except_row"
|
||||
|
@ -97,7 +131,8 @@
|
|||
app:layout_constraintBottom_toTopOf="@+id/choose_initial_my_story_all_signal_connnections_except_count"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/choose_initial_my_story_all_signal_connnections_except_radio"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/choose_initial_my_story_all_signal_connnections_except_count"
|
||||
|
@ -145,7 +180,8 @@
|
|||
app:layout_constraintBottom_toTopOf="@+id/choose_initial_my_story_only_share_with_count"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/choose_initial_my_story_only_share_with_radio"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/choose_initial_my_story_only_share_with_count"
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<org.thoughtcrime.securesms.util.views.DarkOverflowToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/signal_m3_toolbar_height"
|
||||
android:background="@null"
|
||||
android:minHeight="@dimen/signal_m3_toolbar_height"
|
||||
android:theme="?attr/settingsToolbarStyle"
|
||||
app:navigationContentDescription="@string/DSLSettingsToolbar__navigate_up"
|
||||
app:navigationIcon="@drawable/ic_arrow_left_24"
|
||||
app:title="@string/MyStorySettingsFragment__all_signal_connections"
|
||||
app:titleTextAppearance="@style/Signal.Text.TitleLarge" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/stories_recipient_item" />
|
||||
|
||||
</LinearLayout>
|
|
@ -4849,6 +4849,13 @@
|
|||
<string name="StoryGroupReplyItem__delete">Delete</string>
|
||||
<!-- Page title for My Story options -->
|
||||
<string name="MyStorySettingsFragment__my_story">My Story</string>
|
||||
<!-- Number of total signal connections displayed in "All connections" row item -->
|
||||
<plurals name="MyStorySettingsFragment__viewers">
|
||||
<item quantity="one">%1$d viewer</item>
|
||||
<item quantity="other">%1$d viewers</item>
|
||||
</plurals>
|
||||
<!-- Button on all signal connections row to view all signal connections. Please keep as short as possible. -->
|
||||
<string name="MyStorySettingsFragment__view">View</string>
|
||||
<!-- Section heading for story visibility -->
|
||||
<string name="MyStorySettingsFragment__who_can_view_this_story">Who can view this story</string>
|
||||
<!-- Clickable option for selecting people to hide your story from -->
|
||||
|
|
|
@ -17,6 +17,11 @@
|
|||
</style>
|
||||
|
||||
<style name="Widget.Signal.Button.TextButton" parent="Widget.Material3.Button.TextButton">
|
||||
<item name="materialThemeOverlay">@style/ThemeOverlay.Signal.Button.TextButton</item>
|
||||
</style>
|
||||
|
||||
<style name="ThemeOverlay.Signal.Button.TextButton" parent="ThemeOverlay.Material3.Button.TextButton">
|
||||
<item name="colorOnContainer">@color/signal_colorPrimary</item>
|
||||
</style>
|
||||
|
||||
<style name="NoAnimation.Theme.BlackScreen" parent="Theme.AppCompat.NoActionBar">
|
||||
|
|
Loading…
Add table
Reference in a new issue